From 3051cc35f532792557407150d1b524e5019fafe5 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 29 Jan 2025 03:30:25 +0300 Subject: [PATCH 01/41] up pkg --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 1d2eb0625..9999d6ce4 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.28", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", - "mineflayer": "github:zardoy/mineflayer", + "mineflayer": "github:GenerelSchwerz/mineflayer", "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", @@ -170,7 +170,6 @@ "pnpm": { "overrides": { "buffer": "^6.0.3", - "@nxg-org/mineflayer-physics-util": "1.5.8", "three": "0.154.0", "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", From aed5b4051697ef8c1a7e39f5085b1ac936727e42 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 29 Jan 2025 03:31:16 +0300 Subject: [PATCH 02/41] use local --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9999d6ce4..17edf346e 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.28", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", - "mineflayer": "github:GenerelSchwerz/mineflayer", + "mineflayer": "file:../mineflayer", "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", @@ -170,6 +170,7 @@ "pnpm": { "overrides": { "buffer": "^6.0.3", + "@nxg-org/mineflayer-physics-util": "file:../mineflayer-physics-utils", "three": "0.154.0", "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", From 380c21486b41c9691a80de122d974fb7bf17e140 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 30 Jan 2025 21:33:30 +0300 Subject: [PATCH 03/41] make it finally pass grim checks! --- package.json | 2 +- pnpm-lock.yaml | 33 +++++++++++++-------- src/index.ts | 5 ++-- src/react/components/LibraryVersions.tsx | 37 ++++++++++++++++++++++++ src/reactUi.tsx | 2 ++ 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 src/react/components/LibraryVersions.tsx diff --git a/package.json b/package.json index 17edf346e..ea6a347e4 100644 --- a/package.json +++ b/package.json @@ -169,8 +169,8 @@ }, "pnpm": { "overrides": { + "@nxg-org/mineflayer-physics-util": "latest", "buffer": "^6.0.3", - "@nxg-org/mineflayer-physics-util": "file:../mineflayer-physics-utils", "three": "0.154.0", "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a3a5ef16..a469d2c1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,8 +5,8 @@ settings: excludeLinksFromLockfile: false overrides: + '@nxg-org/mineflayer-physics-util': latest buffer: ^6.0.3 - '@nxg-org/mineflayer-physics-util': 1.5.8 three: 0.154.0 diamond-square: github:zardoy/diamond-square prismarine-block: github:zardoy/prismarine-block#next-era @@ -352,8 +352,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) mineflayer: - specifier: github:zardoy/mineflayer - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/54f8c2282d822ad02967a197bda36302a4e7b4a5(encoding@0.1.13) + specifier: file:../mineflayer + version: file:../mineflayer(encoding@0.1.13) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -1899,8 +1899,8 @@ packages: '@nxg-org/mineflayer-auto-jump@0.7.12': resolution: {integrity: sha512-F5vX/lerlWx/5HVlkDNbvrtQ19PL6iG8i4ItPTIRtjGiFzusDefP7DI226zSFR8Wlaw45qHv0jn814p/4/qVdQ==} - '@nxg-org/mineflayer-physics-util@1.5.8': - resolution: {integrity: sha512-KmCkAqpUo8BbuRdIBs6+V2hWHehz++PRz3lRwIsb47CuG0u4sgLYh37RY3ifAznC6uWvmPK+q3B4ZXwJzPy1MQ==} + '@nxg-org/mineflayer-physics-util@1.7.8': + resolution: {integrity: sha512-4GoEUizqRict5myB4rjZJuEq8jctiqne1CqixfRfKD7SvSruOJLNo34aJBnz4wYs+VuwEMvPvOD6Jj8VrEW0mg==} '@nxg-org/mineflayer-tracker@1.2.1': resolution: {integrity: sha512-SI1ffF8zvg3/ZNE021Ja2W0FZPN+WbQDZf8yFqOcXtPRXAtM9W6HvoACdzXep8BZid7WYgYLIgjKpB+9RqvCNQ==} @@ -6479,9 +6479,8 @@ packages: resolution: {integrity: sha512-q7cmpZFaSI6sodcMJxc2GkV8IO84HbsUP+xNipGKfGg+FMISKabzdJ838Axb60qRtZrp6ny7LluQE7lesHvvxQ==} engines: {node: '>=18'} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/54f8c2282d822ad02967a197bda36302a4e7b4a5: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/54f8c2282d822ad02967a197bda36302a4e7b4a5} - version: 4.25.0 + mineflayer@file:../mineflayer: + resolution: {directory: ../mineflayer, type: directory} engines: {node: '>=18'} minimalistic-assert@1.0.1: @@ -7175,6 +7174,9 @@ packages: prismarine-entity@2.3.1: resolution: {integrity: sha512-HOv8l7IetHNf4hwZ7V/W4vM3GNl+e6VCtKDkH9h02TRq7jWngsggKtJV+VanCce/sNwtJUhJDjORGs728ep4MA==} + 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==} @@ -10926,10 +10928,10 @@ snapshots: '@nxg-org/mineflayer-auto-jump@0.7.12': dependencies: - '@nxg-org/mineflayer-physics-util': 1.5.8 + '@nxg-org/mineflayer-physics-util': 1.7.8 strict-event-emitter-types: 2.0.0 - '@nxg-org/mineflayer-physics-util@1.5.8': + '@nxg-org/mineflayer-physics-util@1.7.8': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.3 @@ -17027,7 +17029,7 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/54f8c2282d822ad02967a197bda36302a4e7b4a5(encoding@0.1.13): + mineflayer@file:../mineflayer(encoding@0.1.13): dependencies: minecraft-data: 3.83.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/e9eb551ba30ec2e742c49e6927be6402b413bb76(patch_hash=3wm2z233n46lqi64rbxem4nyv4)(encoding@0.1.13) @@ -17035,7 +17037,7 @@ snapshots: 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 + prismarine-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.5.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b @@ -17843,6 +17845,13 @@ snapshots: prismarine-registry: 1.11.0 vec3: 0.1.8 + prismarine-entity@2.5.0: + dependencies: + prismarine-chat: 1.10.1 + prismarine-item: 1.16.0 + prismarine-registry: 1.11.0 + vec3: 0.1.8 + prismarine-item@1.16.0: dependencies: prismarine-nbt: 2.5.0 diff --git a/src/index.ts b/src/index.ts index 2f4129544..b27c6bbe6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -750,9 +750,8 @@ async function connect (connectOptions: ConnectOptions) { const minPitch = -0.5 * Math.PI mouseMovePostHandle = ({ x, y }) => { viewer.world.lastCamUpdate = Date.now() - bot.entity.pitch -= y - bot.entity.pitch = Math.max(minPitch, Math.min(maxPitch, bot.entity.pitch)) - bot.entity.yaw -= x + const pitch = bot.entity.pitch - y + void bot.look(bot.entity.yaw - x, Math.max(minPitch, Math.min(maxPitch, pitch)), true) } function changeCallback () { diff --git a/src/react/components/LibraryVersions.tsx b/src/react/components/LibraryVersions.tsx new file mode 100644 index 000000000..f749b8094 --- /dev/null +++ b/src/react/components/LibraryVersions.tsx @@ -0,0 +1,37 @@ +import React from 'react' +import physicsUtilPkg from '@nxg-org/mineflayer-physics-util/package.json' +import mineflayerPkg from 'mineflayer/package.json' +import mcProtocolPkg from 'minecraft-protocol/package.json' + +const LibraryVersions: React.FC = () => { + const versions = { + 'mineflayer-physics-util': physicsUtilPkg.version, + 'mineflayer': mineflayerPkg.version, + 'minecraft-protocol': mcProtocolPkg.version + } + + return ( +
+
Library Versions:
+ {Object.entries(versions).map(([lib, version]) => ( +
+ {lib}: {version} +
+ ))} +
+ ) +} + +export default LibraryVersions diff --git a/src/reactUi.tsx b/src/reactUi.tsx index b432c58d0..3be75524a 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -46,6 +46,7 @@ import BookProvider from './react/BookProvider' import { options } from './optionsStorage' import BossBarOverlayProvider from './react/BossBarOverlayProvider' import DebugEdges from './react/DebugEdges' +import LibraryVersions from './react/components/LibraryVersions' const RobustPortal = ({ children, to }) => { return createPortal({children}, to) @@ -174,6 +175,7 @@ const App = () => {
+ From 044153c2dc2943e46684480211c22db9702ea415 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 30 Jan 2025 21:36:23 +0300 Subject: [PATCH 04/41] up ver --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3eb1fc743..8bf75f681 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - uses: cypress-io/github-action@v5 with: install: false - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-images From 537658476da4c36f6822b1fc4aa05311fcee4a46 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 30 Jan 2025 21:38:35 +0300 Subject: [PATCH 05/41] use gen mineflayer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ea6a347e4..3ef77437a 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "https-browserify": "^1.0.0", "mc-assets": "^0.2.28", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", - "mineflayer": "file:../mineflayer", + "mineflayer": "github:GenerelSchwerz/mineflayer", "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", From 2848ab63d3717148f85b415c02ad4fb8c01bfb18 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 31 Jan 2025 05:30:59 +0300 Subject: [PATCH 06/41] disable silly creative fly patching --- pnpm-lock.yaml | 20 ++-- src/controls.ts | 242 ++++++++++++++++++++++++------------------------ 2 files changed, 132 insertions(+), 130 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a469d2c1d..6e040cd3f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -352,8 +352,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) mineflayer: - specifier: file:../mineflayer - version: file:../mineflayer(encoding@0.1.13) + specifier: github:GenerelSchwerz/mineflayer + version: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/400ceb2bd0ff8d67d488d48c9dc94da5e6fcdc1a(encoding@0.1.13) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -1899,8 +1899,8 @@ packages: '@nxg-org/mineflayer-auto-jump@0.7.12': resolution: {integrity: sha512-F5vX/lerlWx/5HVlkDNbvrtQ19PL6iG8i4ItPTIRtjGiFzusDefP7DI226zSFR8Wlaw45qHv0jn814p/4/qVdQ==} - '@nxg-org/mineflayer-physics-util@1.7.8': - resolution: {integrity: sha512-4GoEUizqRict5myB4rjZJuEq8jctiqne1CqixfRfKD7SvSruOJLNo34aJBnz4wYs+VuwEMvPvOD6Jj8VrEW0mg==} + '@nxg-org/mineflayer-physics-util@1.7.11': + resolution: {integrity: sha512-IUgIIEqopXuirM2ZI1+iPFdZQMs8/ckfsHS6634nEG4DpavnxG5NsfewJQgvVr+xxtndJ+eJpnrfMQul2d42iw==} '@nxg-org/mineflayer-tracker@1.2.1': resolution: {integrity: sha512-SI1ffF8zvg3/ZNE021Ja2W0FZPN+WbQDZf8yFqOcXtPRXAtM9W6HvoACdzXep8BZid7WYgYLIgjKpB+9RqvCNQ==} @@ -6479,8 +6479,9 @@ packages: resolution: {integrity: sha512-q7cmpZFaSI6sodcMJxc2GkV8IO84HbsUP+xNipGKfGg+FMISKabzdJ838Axb60qRtZrp6ny7LluQE7lesHvvxQ==} engines: {node: '>=18'} - mineflayer@file:../mineflayer: - resolution: {directory: ../mineflayer, type: directory} + mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/400ceb2bd0ff8d67d488d48c9dc94da5e6fcdc1a: + resolution: {tarball: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/400ceb2bd0ff8d67d488d48c9dc94da5e6fcdc1a} + version: 4.25.0 engines: {node: '>=18'} minimalistic-assert@1.0.1: @@ -10928,10 +10929,10 @@ snapshots: '@nxg-org/mineflayer-auto-jump@0.7.12': dependencies: - '@nxg-org/mineflayer-physics-util': 1.7.8 + '@nxg-org/mineflayer-physics-util': 1.7.11 strict-event-emitter-types: 2.0.0 - '@nxg-org/mineflayer-physics-util@1.7.8': + '@nxg-org/mineflayer-physics-util@1.7.11': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.3 @@ -17029,8 +17030,9 @@ snapshots: - encoding - supports-color - mineflayer@file:../mineflayer(encoding@0.1.13): + mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/400ceb2bd0ff8d67d488d48c9dc94da5e6fcdc1a(encoding@0.1.13): dependencies: + '@nxg-org/mineflayer-physics-util': 1.7.11 minecraft-data: 3.83.1 minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/e9eb551ba30ec2e742c49e6927be6402b413bb76(patch_hash=3wm2z233n46lqi64rbxem4nyv4)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) diff --git a/src/controls.ts b/src/controls.ts index 559379a39..507630bca 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -158,10 +158,10 @@ let lastCommandTrigger = null as { command: string, time: number } | null const secondActionActivationTimeout = 300 const secondActionCommands = { - 'general.jump' () { - // if (bot.game.gameMode === 'spectator') return - toggleFly() - }, + // 'general.jump' () { + // // if (bot.game.gameMode === 'spectator') return + // toggleFly() + // }, 'general.forward' () { setSprinting(true) } @@ -582,135 +582,135 @@ const makeInterval = (fn, interval) => { return cleanup } -const isFlying = () => bot.physics.gravity === 0 -let endFlyLoop: ReturnType | undefined +// const isFlying = () => bot.physics.gravity === 0 +// let endFlyLoop: ReturnType | undefined -const currentFlyVector = new Vec3(0, 0, 0) -window.currentFlyVector = currentFlyVector +// const currentFlyVector = new Vec3(0, 0, 0) +// window.currentFlyVector = currentFlyVector // todo cleanup -const flyingPressedKeys = { - down: false, - up: false -} +// const flyingPressedKeys = { +// down: false, +// up: false +// } -const startFlyLoop = () => { - if (!isFlying()) return - endFlyLoop?.() +// const startFlyLoop = () => { +// if (!isFlying()) return +// endFlyLoop?.() - endFlyLoop = makeInterval(() => { - if (!bot) { - endFlyLoop?.() - return - } +// endFlyLoop = makeInterval(() => { +// if (!bot) { +// endFlyLoop?.() +// return +// } - bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(0, 0.5, 0))) - }, 50) -} +// bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(0, 0.5, 0))) +// }, 50) +// } // todo we will get rid of patching it when refactor controls -let originalSetControlState -const patchedSetControlState = (action, state) => { - if (!isFlying()) { - return originalSetControlState(action, state) - } - - const actionPerFlyVector = { - jump: new Vec3(0, 1, 0), - sneak: new Vec3(0, -1, 0), - } - - const changeVec = actionPerFlyVector[action] - if (!changeVec) { - return originalSetControlState(action, state) - } - if (flyingPressedKeys[state === 'jump' ? 'up' : 'down'] === state) return - const toAddVec = changeVec.scaled(state ? 1 : -1) - for (const coord of ['x', 'y', 'z']) { - if (toAddVec[coord] === 0) continue - if (currentFlyVector[coord] === toAddVec[coord]) return - } - currentFlyVector.add(toAddVec) - flyingPressedKeys[state === 'jump' ? 'up' : 'down'] = state -} - -const startFlying = (sendAbilities = true) => { - bot.entity['creativeFly'] = true - if (sendAbilities) { - bot._client.write('abilities', { - flags: 2, - }) - } - // window.flyingSpeed will be removed - bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 // todo use abilities - bot.entity.velocity = new Vec3(0, 0, 0) - bot.creative.startFlying() - startFlyLoop() -} - -const endFlying = (sendAbilities = true) => { - bot.entity['creativeFly'] = false - if (bot.physics.gravity !== 0) return - if (sendAbilities) { - bot._client.write('abilities', { - flags: 0, - }) - } - Object.assign(flyingPressedKeys, { - up: false, - down: false - }) - currentFlyVector.set(0, 0, 0) - bot.physics['airborneAcceleration'] = standardAirborneAcceleration - bot.creative.stopFlying() - endFlyLoop?.() -} - -let allowFlying = false +// let originalSetControlState +// const patchedSetControlState = (action, state) => { +// if (!isFlying()) { +// return originalSetControlState(action, state) +// } + +// const actionPerFlyVector = { +// jump: new Vec3(0, 1, 0), +// sneak: new Vec3(0, -1, 0), +// } + +// const changeVec = actionPerFlyVector[action] +// if (!changeVec) { +// return originalSetControlState(action, state) +// } +// if (flyingPressedKeys[state === 'jump' ? 'up' : 'down'] === state) return +// const toAddVec = changeVec.scaled(state ? 1 : -1) +// for (const coord of ['x', 'y', 'z']) { +// if (toAddVec[coord] === 0) continue +// if (currentFlyVector[coord] === toAddVec[coord]) return +// } +// currentFlyVector.add(toAddVec) +// flyingPressedKeys[state === 'jump' ? 'up' : 'down'] = state +// } + +// const startFlying = (sendAbilities = true) => { +// bot.entity['creativeFly'] = true +// if (sendAbilities) { +// bot._client.write('abilities', { +// flags: 2, +// }) +// } +// // window.flyingSpeed will be removed +// bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 // todo use abilities +// bot.entity.velocity = new Vec3(0, 0, 0) +// bot.creative.startFlying() +// startFlyLoop() +// } + +// const endFlying = (sendAbilities = true) => { +// bot.entity['creativeFly'] = false +// if (bot.physics.gravity !== 0) return +// if (sendAbilities) { +// bot._client.write('abilities', { +// flags: 0, +// }) +// } +// Object.assign(flyingPressedKeys, { +// up: false, +// down: false +// }) +// currentFlyVector.set(0, 0, 0) +// bot.physics['airborneAcceleration'] = standardAirborneAcceleration +// bot.creative.stopFlying() +// endFlyLoop?.() +// } + +const allowFlying = false export const onBotCreate = () => { - let wasSpectatorFlying = false - bot._client.on('abilities', ({ flags }) => { - if (flags & 2) { // flying - toggleFly(true, false) - } else { - toggleFly(false, false) - } - allowFlying = !!(flags & 4) - }) - const gamemodeCheck = () => { - if (bot.game.gameMode === 'spectator') { - toggleFly(true, false) - wasSpectatorFlying = true - } else if (wasSpectatorFlying) { - toggleFly(false, false) - wasSpectatorFlying = false - } - } - bot.on('game', () => { - gamemodeCheck() - }) - bot.on('login', () => { - gamemodeCheck() - }) + // let wasSpectatorFlying = false + // bot._client.on('abilities', ({ flags }) => { + // if (flags & 2) { // flying + // toggleFly(true, false) + // } else { + // toggleFly(false, false) + // } + // allowFlying = !!(flags & 4) + // }) + // const gamemodeCheck = () => { + // if (bot.game.gameMode === 'spectator') { + // toggleFly(true, false) + // wasSpectatorFlying = true + // } else if (wasSpectatorFlying) { + // toggleFly(false, false) + // wasSpectatorFlying = false + // } + // } + // bot.on('game', () => { + // gamemodeCheck() + // }) + // bot.on('login', () => { + // gamemodeCheck() + // }) } -const standardAirborneAcceleration = 0.02 -const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => { - // if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return - if (!allowFlying) return - if (bot.setControlState !== patchedSetControlState) { - originalSetControlState = bot.setControlState - bot.setControlState = patchedSetControlState - } - - if (newState) { - startFlying(sendAbilities) - } else { - endFlying(sendAbilities) - } - gameAdditionalState.isFlying = isFlying() -} +// const standardAirborneAcceleration = 0.02 +// const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => { +// // if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return +// if (!allowFlying) return +// if (bot.setControlState !== patchedSetControlState) { +// originalSetControlState = bot.setControlState +// bot.setControlState = patchedSetControlState +// } + +// if (newState) { +// startFlying(sendAbilities) +// } else { +// endFlying(sendAbilities) +// } +// gameAdditionalState.isFlying = isFlying() +// } // #endregion const selectItem = async () => { From 8db6b5bb51fabb9c3adef23b2100a1faf3e34af5 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 31 Jan 2025 06:00:50 +0300 Subject: [PATCH 07/41] fix library versions --- src/react/components/LibraryVersions.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/react/components/LibraryVersions.tsx b/src/react/components/LibraryVersions.tsx index f749b8094..4ab76d09a 100644 --- a/src/react/components/LibraryVersions.tsx +++ b/src/react/components/LibraryVersions.tsx @@ -2,11 +2,12 @@ import React from 'react' import physicsUtilPkg from '@nxg-org/mineflayer-physics-util/package.json' import mineflayerPkg from 'mineflayer/package.json' import mcProtocolPkg from 'minecraft-protocol/package.json' +import packageJson from '../../../package.json' const LibraryVersions: React.FC = () => { const versions = { - 'mineflayer-physics-util': physicsUtilPkg.version, - 'mineflayer': mineflayerPkg.version, + '@nxg-org/mineflayer-physics-util': physicsUtilPkg.version, + 'mineflayer': packageJson.devDependencies['mineflayer'], 'minecraft-protocol': mcProtocolPkg.version } @@ -26,7 +27,7 @@ const LibraryVersions: React.FC = () => { >
Library Versions:
{Object.entries(versions).map(([lib, version]) => ( -
+
{lib}: {version}
))} From c626d105ffd05bef251857b98155450ca3e310e8 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 31 Jan 2025 18:44:26 +0300 Subject: [PATCH 08/41] use actually useful server --- config.json | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/config.json b/config.json index 7813b5911..6d8284a5c 100644 --- a/config.json +++ b/config.json @@ -7,19 +7,8 @@ "peerJsServerFallback": "https://p2p.mcraft.fun", "promoteServers": [ { - "ip": "kaboom.pw", - "version": "1.18.2", - "description": "Chaos and destruction server. Free for everyone." - }, - { - "ip": "play.applemc.fun", - "version": "1.18.2", - "description": "Very nice server. Try it now!" - }, - { - "ip": "sus.shhnowisnottheti.me", - "version": "1.18.2", - "description": "Creative, your own 'boxes' (islands)" + "ip": "grim.mcraft.fun", + "version": "1.19.4" } ] } From fb10179691d1eddf8b3d79a79ad96f9b9f5075b3 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 3 Feb 2025 10:26:24 +0300 Subject: [PATCH 09/41] set 1.19.4 for now! --- src/optionsStorage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 12188a17b..005edfb38 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -53,7 +53,7 @@ const defaultOptions = { serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', handDisplay: false, packetsLoggerPreset: 'all' as 'all' | 'no-buffers', - serversAutoVersionSelect: 'auto' as 'auto' | 'latest' | '1.20.4' | string, + serversAutoVersionSelect: '1.19.4' as 'auto' | 'latest' | '1.20.4' | string, // TODO! revert // antiAliasing: false, From c65db9a8cbe4deb9616149b2b65118d62cc2133a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Mar 2025 12:12:42 +0300 Subject: [PATCH 10/41] a working refactor --- renderer/viewer/{prepare => common}/utils.ts | 0 renderer/viewer/lib/worldrendererCommon.ts | 11 +- renderer/viewer/lib/worldrendererThree.ts | 2 - renderer/viewer/three/graphicsBackend.ts | 71 ++++++ renderer/viewer/three/panorama.ts | 90 ++++++++ renderer/viewer/three/renderer.ts | 228 +++++++++++++++++++ src/appViewer.ts | 143 ++++++++++++ src/botUtils.ts | 2 +- src/chatUtils.ts | 2 +- src/devtools.ts | 47 ++++ src/entities.ts | 2 +- src/globals.d.ts | 1 + src/index.ts | 174 +++++--------- src/inventoryWindows.ts | 2 +- src/loadSave.ts | 2 +- src/mineflayer/playerState.ts | 2 +- src/mineflayer/plugins/ping.ts | 2 +- src/optimizeJson.ts | 2 +- src/panorama.ts | 32 --- src/react/BookProvider.tsx | 2 +- src/react/DebugOverlay.tsx | 11 +- src/react/MinimapProvider.tsx | 2 +- src/reactUi.tsx | 13 ++ src/shims/minecraftData.ts | 2 +- src/sounds/botSoundSystem.ts | 2 +- src/sounds/soundsMap.ts | 2 +- src/topRightStats.ts | 129 ----------- src/watchOptions.ts | 101 ++++---- 28 files changed, 741 insertions(+), 338 deletions(-) rename renderer/viewer/{prepare => common}/utils.ts (100%) create mode 100644 renderer/viewer/three/graphicsBackend.ts create mode 100644 renderer/viewer/three/panorama.ts create mode 100644 renderer/viewer/three/renderer.ts create mode 100644 src/appViewer.ts delete mode 100644 src/topRightStats.ts diff --git a/renderer/viewer/prepare/utils.ts b/renderer/viewer/common/utils.ts similarity index 100% rename from renderer/viewer/prepare/utils.ts rename to renderer/viewer/common/utils.ts diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index a54f9fe8b..46e57ce28 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -33,12 +33,17 @@ export const worldCleanup = buildCleanupDecorator('resetWorld') export const defaultWorldRendererConfig = { showChunkBorders: false, - numWorkers: 4, + mesherWorkers: 4, isPlayground: false, renderEars: true, // game renderer setting actually showHand: false, - viewBobbing: false + viewBobbing: false, + extraBlockRenderers: true, + clipWorldBelowY: undefined as number | undefined, + smoothLighting: true, + enableLighting: true, + starfield: true } export type WorldRendererConfig = typeof defaultWorldRendererConfig @@ -182,7 +187,7 @@ export abstract class WorldRendererCommon snapshotInitialValues () { } - initWorkers (numWorkers = this.config.numWorkers) { + initWorkers (numWorkers = this.config.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 diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 697717c93..977ee2df3 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -27,7 +27,6 @@ export class WorldRendererThree extends WorldRendererCommon { cameraSectionPos: Vec3 = new Vec3(0, 0, 0) holdingBlock: HoldingBlock holdingBlockLeft: HoldingBlock - rendererDevice = '...' get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -39,7 +38,6 @@ export class WorldRendererThree extends WorldRendererCommon { constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig, public playerState: IPlayerState) { super(config) - this.rendererDevice = `${WorldRendererThree.getRendererInfo(this.renderer)} powered by three.js r${THREE.REVISION}` this.starField = new StarField(scene) this.holdingBlock = new HoldingBlock(playerState, this.config) this.holdingBlockLeft = new HoldingBlock(playerState, this.config, true) diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts new file mode 100644 index 000000000..c6159145c --- /dev/null +++ b/renderer/viewer/three/graphicsBackend.ts @@ -0,0 +1,71 @@ +import * as THREE from 'three' +import { GraphicsBackendLoader, GraphicsBackend, GraphicsBackendOptions, DisplayWorldOptions } from '../../../src/appViewer' +import { ProgressReporter } from '../../../src/core/progressReporter' +import { WorldRendererThree } from '../lib/worldrendererThree' +import { DocumentRenderer } from './renderer' +import { PanoramaRenderer } from './panorama' + +const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOptions) => { + // Private state + const documentRenderer = new DocumentRenderer(options) + globalThis.renderer = documentRenderer.renderer + let panoramaRenderer: PanoramaRenderer | null = null + + // Private methods + const startPanorama = () => { + if (!panoramaRenderer) { + panoramaRenderer = new PanoramaRenderer(documentRenderer) + void panoramaRenderer.start() + } + } + + const updateResources = async (version: string, progressReporter: ProgressReporter): Promise => { + // Implementation for updating resources will be added here + } + + const startWorld = (options: DisplayWorldOptions) => { + if (panoramaRenderer) { + panoramaRenderer.dispose() + panoramaRenderer = null + } + } + + const disconnect = () => { + if (panoramaRenderer) { + panoramaRenderer.dispose() + panoramaRenderer = null + } + if (documentRenderer) { + documentRenderer.dispose() + } + } + + const startRender = () => { + documentRenderer.setPaused(false) + } + + const stopRender = () => { + documentRenderer.setPaused(true) + } + + const renderer = WorldRendererThree.getRendererInfo(documentRenderer.renderer) ?? '...' + // viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch) + + // Public interface + const backend: GraphicsBackend = { + NAME: `three.js ${THREE.REVISION}`, + startPanorama, + updateResources, + startWorld, + disconnect, + startRender, + stopRender, + getRenderer: () => renderer, + getDebugOverlay: () => ({ + }) + } + + return backend +} + +export default createGraphicsBackend diff --git a/renderer/viewer/three/panorama.ts b/renderer/viewer/three/panorama.ts new file mode 100644 index 000000000..28776498e --- /dev/null +++ b/renderer/viewer/three/panorama.ts @@ -0,0 +1,90 @@ +import * as THREE from 'three' +import { EntityMesh } from '../lib/entity/EntityMesh' +import { DocumentRenderer } from './renderer' + +const panoramaFiles = [ + 'panorama_1.png', // WS + 'panorama_3.png', // ES + 'panorama_4.png', // Up + 'panorama_5.png', // Down + 'panorama_0.png', // NS + 'panorama_2.png' // SS +] + +export class PanoramaRenderer { + private readonly camera: THREE.PerspectiveCamera + private readonly scene: THREE.Scene + private panoramaGroup: THREE.Object3D | null = null + private readonly documentRenderer: DocumentRenderer + private time = 0 + + constructor (documentRenderer: DocumentRenderer) { + this.documentRenderer = documentRenderer + this.scene = new THREE.Scene() + this.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000) + this.camera.position.set(0, 0, 0) + this.camera.rotation.set(0, 0, 0) + } + + async start () { + const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000) + const loader = new THREE.TextureLoader() + const panorMaterials = [] as THREE.MeshBasicMaterial[] + + for (const file of panoramaFiles) { + panorMaterials.push(new THREE.MeshBasicMaterial({ + map: loader.load(`background/${file}`), + transparent: true, + side: THREE.DoubleSide + })) + } + + const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials) + panoramaBox.onBeforeRender = () => { + this.time += 0.01 + panoramaBox.rotation.y = Math.PI + this.time * 0.01 + panoramaBox.rotation.z = Math.sin(-this.time * 0.001) * 0.001 + } + + const group = new THREE.Object3D() + group.add(panoramaBox) + + // Add squids + for (let i = 0; i < 20; i++) { + const m = new EntityMesh('1.16.4', 'squid', this.documentRenderer.world).mesh + m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17) + m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX') + const v = Math.random() * 0.01 + m.children[0].onBeforeRender = () => { + m.rotation.y += v + m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2 + } + group.add(m) + } + + this.scene.add(group) + this.panoramaGroup = group + + // Override renderer's render method + const originalRender = this.documentRenderer.render.bind(this.documentRenderer) + this.documentRenderer.render = () => { + if (this.panoramaGroup) { + this.documentRenderer.renderer.render(this.scene, this.camera) + } else { + originalRender() + } + } + } + + stop () { + if (this.panoramaGroup) { + this.scene.remove(this.panoramaGroup) + this.panoramaGroup = null + } + } + + dispose () { + this.stop() + this.scene.clear() + } +} diff --git a/renderer/viewer/three/renderer.ts b/renderer/viewer/three/renderer.ts new file mode 100644 index 000000000..0c648cbf6 --- /dev/null +++ b/renderer/viewer/three/renderer.ts @@ -0,0 +1,228 @@ +import * as THREE from 'three' +import Stats from 'stats.js' +import StatsGl from 'stats-gl' +import { GraphicsBackendConfig, GraphicsBackendOptions } from '../../../src/appViewer' +import { activeModalStack } from '../../../src/globalState' +import { isCypress } from '../../../src/standaloneUtils' + +export class DocumentRenderer { + readonly canvas = document.createElement('canvas') + readonly renderer: THREE.WebGLRenderer + private animationFrameId?: number + private lastRenderTime = 0 + private previousWindowWidth = window.innerWidth + private previousWindowHeight = window.innerHeight + private renderedFps = 0 + private fpsInterval: any + private readonly stats: TopRightStats + private paused = false + disconnected = false + preRender = () => { } + render = (sizeChanged: boolean) => { } + postRender = () => { } + sizeChanged = () => { } + droppedFpsPercentage: number + config: GraphicsBackendConfig + + constructor (initOptions: GraphicsBackendOptions) { + this.config = initOptions.config + + try { + this.renderer = new THREE.WebGLRenderer({ + canvas: this.canvas, + preserveDrawingBuffer: true, + logarithmicDepthBuffer: true, + powerPreference: this.config.powerPreference + }) + } catch (err) { + initOptions.displayCriticalError(new Error(`Failed to create WebGL context, not possible to render (restart browser): ${err.message}`)) + } + this.updatePixelRatio() + this.addToPage() + + this.stats = new TopRightStats(this.canvas, this.config.statsVisible) + + this.setupFpsTracking() + this.startRenderLoop() + } + + updatePixelRatio () { + let pixelRatio = window.devicePixelRatio || 1 // todo this value is too high on ios, need to check, probably we should use avg, also need to make it configurable + if (!this.renderer.capabilities.isWebGL2) { + pixelRatio = 1 // webgl1 has issues with high pixel ratio (sometimes screen is clipped) + } + this.renderer.setPixelRatio(pixelRatio) + } + + private addToPage () { + this.canvas.id = 'viewer-canvas' + this.canvas.style.width = '100%' + this.canvas.style.height = '100%' + document.body.appendChild(this.canvas) + } + + private setupFpsTracking () { + let max = 0 + this.fpsInterval = setInterval(() => { + if (max > 0) { + this.droppedFpsPercentage = this.renderedFps / max + } + max = Math.max(this.renderedFps, max) + this.renderedFps = 0 + }, 1000) + } + + // private handleResize () { + // const width = window.innerWidth + // const height = window.innerHeight + + // viewer.camera.aspect = width / height + // viewer.camera.updateProjectionMatrix() + // this.renderer.setSize(width, height) + // viewer.world.handleResize() + // } + + private startRenderLoop () { + const animate = () => { + if (this.disconnected) return + this.animationFrameId = requestAnimationFrame(animate) + + if (this.paused) return + + let sizeChanged = false + if (this.previousWindowWidth !== window.innerWidth || this.previousWindowHeight !== window.innerHeight) { + this.previousWindowWidth = window.innerWidth + this.previousWindowHeight = window.innerHeight + sizeChanged = true + } + + // Handle FPS limiting + if (this.config.fpsLimit) { + const now = performance.now() + const elapsed = now - this.lastRenderTime + const fpsInterval = 1000 / this.config.fpsLimit + + if (elapsed < fpsInterval) { + return + } + + this.lastRenderTime = now - (elapsed % fpsInterval) + } + + this.preRender() + this.stats.markStart() + this.render(sizeChanged) + this.renderedFps++ + this.stats.markEnd() + this.postRender() + + // Update stats visibility each frame + if (this.config.statsVisible !== undefined) { + this.stats.setVisibility(this.config.statsVisible) + } + } + + animate() + } + + setPaused (paused: boolean) { + this.paused = paused + } + + dispose () { + this.disconnected = true + if (this.animationFrameId) { + cancelAnimationFrame(this.animationFrameId) + } + this.canvas.remove() + this.renderer.dispose() + clearInterval(this.fpsInterval) + this.stats.dispose() + } +} + +class TopRightStats { + private readonly stats: Stats + private readonly stats2: Stats + private readonly statsGl: StatsGl + private total = 0 + private readonly denseMode: boolean + + constructor (private readonly canvas: HTMLCanvasElement, initialStatsVisible = 0) { + this.stats = new Stats() + this.stats2 = new Stats() + this.statsGl = new StatsGl({ minimal: true }) + this.stats2.showPanel(2) + this.denseMode = process.env.NODE_ENV === 'production' || window.innerHeight < 500 + + this.initStats() + this.setVisibility(initialStatsVisible) + } + + private addStat (dom: HTMLElement, size = 80) { + dom.style.position = 'absolute' + if (this.denseMode) dom.style.height = '12px' + dom.style.overflow = 'hidden' + dom.style.left = '' + dom.style.top = '0' + dom.style.right = `${this.total}px` + dom.style.width = '80px' + dom.style.zIndex = '1' + dom.style.opacity = '0.8' + document.body.appendChild(dom) + this.total += size + } + + private initStats () { + const hasRamPanel = this.stats2.dom.children.length === 3 + + this.addStat(this.stats.dom) + if (hasRamPanel) { + this.addStat(this.stats2.dom) + } + + this.statsGl.init(this.canvas) + this.statsGl.container.style.display = 'flex' + this.statsGl.container.style.justifyContent = 'flex-end' + + let i = 0 + for (const _child of this.statsGl.container.children) { + const child = _child as HTMLElement + if (i++ === 0) { + child.style.display = 'none' + } + child.style.position = '' + } + } + + setVisibility (level: number) { + const visible = level > 0 + if (visible) { + this.stats.dom.style.display = 'block' + this.stats2.dom.style.display = level >= 2 ? 'block' : 'none' + this.statsGl.container.style.display = level >= 2 ? 'block' : 'none' + } else { + this.stats.dom.style.display = 'none' + this.stats2.dom.style.display = 'none' + this.statsGl.container.style.display = 'none' + } + } + + markStart () { + this.stats.begin() + this.stats2.begin() + this.statsGl.begin() + } + + markEnd () { + this.stats.end() + this.stats2.end() + this.statsGl.end() + } + + dispose () { + this.stats.dom.remove() + this.stats2.dom.remove() + this.statsGl.container.remove() + } +} diff --git a/src/appViewer.ts b/src/appViewer.ts new file mode 100644 index 000000000..534ff47b3 --- /dev/null +++ b/src/appViewer.ts @@ -0,0 +1,143 @@ +import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' +import { IPlayerState } from 'renderer/viewer/lib/basePlayerState' +import { subscribeKey } from 'valtio/utils' +import { defaultWorldRendererConfig, WorldRendererConfig } from 'renderer/viewer/lib/worldrendererCommon' +import { PlayerStateManager } from './mineflayer/playerState' +import { createNotificationProgressReporter, ProgressReporter } from './core/progressReporter' +import { setLoadingScreenStatus } from './appStatus' +import { activeModalStack } from './globalState' +import { options } from './optionsStorage' + +export interface GraphicsBackendConfig { + fpsLimit?: number + powerPreference?: 'high-performance' | 'low-power' + statsVisible?: number +} + +const defaultGraphicsBackendConfig: GraphicsBackendConfig = { + fpsLimit: undefined, + powerPreference: undefined +} + +export interface GraphicsBackendOptions { + resourcesManager: ResourcesManager + config: GraphicsBackendConfig + displayCriticalError: (error: Error) => void +} + +export interface DisplayWorldOptions { + resourcesManager: ResourcesManager + worldView: WorldDataEmitter + inWorldRenderingConfig: WorldRendererConfig +} + +export type GraphicsBackendLoader = (options: GraphicsBackendOptions) => GraphicsBackend + +export interface GraphicsBackend { + NAME: string + startPanorama: () => void + updateResources: (version: string, progressReporter: ProgressReporter) => Promise + startWorld: (options: DisplayWorldOptions) => void + disconnect: () => void + + startRender: () => void + stopRender: () => void + + getRenderer: () => string + getDebugOverlay: () => { + entitiesString?: string + right?: Record + left?: Record + } +} + +export class AppViewer { + resourcesManager: ResourcesManager + worldView: WorldDataEmitter + playerState = new PlayerStateManager() + readonly config: GraphicsBackendConfig = { + ...defaultGraphicsBackendConfig, + powerPreference: options.gpuPreference === 'default' ? undefined : options.gpuPreference + } + backend?: GraphicsBackend + private queuedDisplay?: { + method: string + args: any[] + } + inWorldRenderingConfig: WorldRendererConfig = defaultWorldRendererConfig + + loadBackend (loader: GraphicsBackendLoader, loadResourcesVersion?: string) { + if (this.backend) { + this.backend.disconnect() + } + + this.backend = loader({ + resourcesManager: this.resourcesManager, + config: this.config, + displayCriticalError (error) { + console.error(error) + setLoadingScreenStatus(error.message, true) + }, + }) + + if (loadResourcesVersion) { + void this.updateResources(loadResourcesVersion, createNotificationProgressReporter()) + } + + // Execute queued action if exists + if (this.queuedDisplay) { + const { method, args } = this.queuedDisplay + this.backend[method](...args) + } + } + + startPanorama () { + if (this.backend) { + this.backend.startPanorama() + } + this.queuedDisplay = { method: 'startPanorama', args: [] } + } + + async updateResources (version: string, progressReporter: ProgressReporter) { + if (this.backend) { + await this.backend.updateResources(version, progressReporter) + } + } + + startWorld (world, renderDistance, startPosition) { + this.worldView = new WorldDataEmitter(world, renderDistance, startPosition) + if (this.backend) { + this.backend.startWorld({ + resourcesManager: this.resourcesManager, + worldView: this.worldView, + inWorldRenderingConfig: this.inWorldRenderingConfig + }) + } + this.queuedDisplay = { method: 'startWorld', args: [options] } + } + + disconnect () { + if (this.backend) { + this.backend.disconnect() + this.backend = undefined + } + this.queuedDisplay = undefined + } +} + +export const appViewer = new AppViewer() +window.appViewer = appViewer + +class ResourcesManager { +} + +subscribeKey(activeModalStack, 'length', () => { + if (appViewer.backend) { + const hasAppStatus = activeModalStack.some(m => m.reactType === 'app-status') + if (hasAppStatus) { + appViewer.backend.stopRender() + } else { + appViewer.backend.startRender() + } + } +}) diff --git a/src/botUtils.ts b/src/botUtils.ts index ac2deb025..5a6f92f77 100644 --- a/src/botUtils.ts +++ b/src/botUtils.ts @@ -1,4 +1,4 @@ -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import * as nbt from 'prismarine-nbt' export const displayClientChat = (text: string) => { diff --git a/src/chatUtils.ts b/src/chatUtils.ts index a5e9e0f89..d0a84939c 100644 --- a/src/chatUtils.ts +++ b/src/chatUtils.ts @@ -2,7 +2,7 @@ import { fromFormattedString, TextComponent } from '@xmcl/text-component' import type { IndexedData } from 'minecraft-data' -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' export type MessageFormatPart = Pick & { text: string diff --git a/src/devtools.ts b/src/devtools.ts index 617a4440f..da0f48c22 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -159,3 +159,50 @@ window.clearStorage = (...keysToKeep: string[]) => { } return `Cleared ${localStorage.length - keysToKeep.length} items from localStorage. Kept: ${keysToKeep.join(', ')}` } + + +// PERF DEBUG + +// for advanced debugging, use with watch expression + +window.statsPerSecAvg = {} +let currentStatsPerSec = {} as Record +const waitingStatsPerSec = {} +window.markStart = (label) => { + waitingStatsPerSec[label] ??= [] + waitingStatsPerSec[label][0] = performance.now() +} +window.markEnd = (label) => { + if (!waitingStatsPerSec[label]?.[0]) return + currentStatsPerSec[label] ??= [] + currentStatsPerSec[label].push(performance.now() - waitingStatsPerSec[label][0]) + delete waitingStatsPerSec[label] +} +const updateStatsPerSecAvg = () => { + window.statsPerSecAvg = Object.fromEntries(Object.entries(currentStatsPerSec).map(([key, value]) => { + return [key, { + avg: value.reduce((a, b) => a + b, 0) / value.length, + count: value.length + }] + })) + currentStatsPerSec = {} +} + + +window.statsPerSec = {} +let statsPerSecCurrent = {} +let lastReset = performance.now() +window.addStatPerSec = (name) => { + statsPerSecCurrent[name] ??= 0 + statsPerSecCurrent[name]++ +} +window.statsPerSecCurrent = statsPerSecCurrent +setInterval(() => { + window.statsPerSec = { duration: Math.floor(performance.now() - lastReset), ...statsPerSecCurrent, } + statsPerSecCurrent = {} + window.statsPerSecCurrent = statsPerSecCurrent + updateStatsPerSecAvg() + lastReset = performance.now() +}, 1000) + +// --- diff --git a/src/entities.ts b/src/entities.ts index 774ce32e1..058cb37fc 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -1,5 +1,5 @@ import { Entity } from 'prismarine-entity' -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import tracker from '@nxg-org/mineflayer-tracker' import { loader as autoJumpPlugin } from '@nxg-org/mineflayer-auto-jump' import { subscribeKey } from 'valtio/utils' diff --git a/src/globals.d.ts b/src/globals.d.ts index 6b2c66406..972f65ceb 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -12,6 +12,7 @@ declare const bot: Omit & { } declare const __type_bot: typeof bot declare const viewer: import('renderer/viewer/lib/viewer').Viewer +declare const appViewer: import('./appViewer').AppViewer declare const worldView: import('renderer/viewer/lib/worldDataEmitter').WorldDataEmitter | undefined declare const addStatPerSec: (name: string) => void declare const localServer: import('flying-squid/dist/index').FullServer & { options } | undefined diff --git a/src/index.ts b/src/index.ts index 7269781d7..b5f908c66 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,14 +14,13 @@ import './mineflayer/java-tester/index' import './external' import { getServerInfo } from './mineflayer/mc-protocol' import { onGameLoad, renderSlot } from './inventoryWindows' -import { GeneralInputItem, RenderItem } from './mineflayer/items' +import { GeneralInputItem } from './mineflayer/items' import initCollisionShapes from './getCollisionInteractionShapes' import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth' import microsoftAuthflow from './microsoftAuthflow' import { Duplex } from 'stream' import './scaleInterface' -import { initWithRenderer } from './topRightStats' import PrismarineBlock from 'prismarine-block' import PrismarineItem from 'prismarine-item' @@ -36,7 +35,7 @@ import downloadAndOpenFile from './downloadAndOpenFile' import fs from 'fs' import net from 'net' import mineflayer from 'mineflayer' -import { WorldDataEmitter, Viewer } from 'renderer/viewer' +import { WorldDataEmitter } from 'renderer/viewer' import pathfinder from 'mineflayer-pathfinder' import { Vec3 } from 'vec3' @@ -67,7 +66,6 @@ import { isCypress } from './standaloneUtils' import { removePanorama } from './panorama' -import { getItemDefinition } from 'mc-assets/dist/itemDefinitions' import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer' import defaultServerOptions from './defaultLocalServerOptions' @@ -83,38 +81,35 @@ import { fsState } from './loadSave' import { watchFov } from './rendererUtils' import { loadInMemorySave } from './react/SingleplayerProvider' -import { ua } from './react/utils' import { possiblyHandleStateVariable } from './googledrive' import flyingSquidEvents from './flyingSquidEvents' -import { hideNotification, notificationProxy, showNotification } from './react/NotificationProvider' +import { showNotification } from './react/NotificationProvider' import { saveToBrowserMemory } from './react/PauseScreen' -import { ViewerWrapper } from 'renderer/viewer/lib/viewerWrapper' import './devReload' import './water' import { ConnectOptions, downloadMcDataOnConnect, getVersionAutoSelect, downloadOtherGameData, downloadAllMinecraftData } from './connect' import { ref, subscribe } from 'valtio' import { signInMessageState } from './react/SignInMessageProvider' import { updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage' -import { versionToNumber } from 'renderer/viewer/prepare/utils' import packetsPatcher from './mineflayer/plugins/packetsPatcher' import { mainMenuState } from './react/MainMenuRenderApp' -import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' import './mobileShim' import { parseFormattedMessagePacket } from './botUtils' import { getViewerVersionData, getWsProtocolStream, handleCustomChannel } from './viewerConnector' import { getWebsocketStream } from './mineflayer/websocket-core' import { appQueryParams, appQueryParamsArray } from './appParams' -import { playerState, PlayerStateManager } from './mineflayer/playerState' +import { playerState } from './mineflayer/playerState' import { states } from 'minecraft-protocol' import { initMotionTracking } from './react/uiMotion' import { UserError } from './mineflayer/userError' import ping from './mineflayer/plugins/ping' import mouse from './mineflayer/plugins/mouse' -import { LocalServer } from './customServer' import { startLocalReplayServer } from './packetsReplay/replayPackets' import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording' import { createFullScreenProgressReporter } from './core/progressReporter' import { getItemModelName } from './resourcesManager' +import { appViewer } from './appViewer' +import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend' window.debug = debug window.THREE = THREE @@ -131,105 +126,65 @@ initializePacketsReplay() packetsPatcher() onAppLoad() -// Create three.js context, add to page -let renderer: THREE.WebGLRenderer -try { - renderer = new THREE.WebGLRenderer({ - powerPreference: options.gpuPreference, - preserveDrawingBuffer: true, - logarithmicDepthBuffer: true, - }) -} catch (err) { - console.error(err) - throw new Error(`Failed to create WebGL context, not possible to render (restart browser): ${err.message}`) -} - -// renderer.localClippingEnabled = true -initWithRenderer(renderer.domElement) -const renderWrapper = new ViewerWrapper(renderer.domElement, renderer) -renderWrapper.addToPage() -watchValue(options, (o) => { - renderWrapper.renderInterval = o.frameLimit ? 1000 / o.frameLimit : 0 - renderWrapper.renderIntervalUnfocused = o.backgroundRendering === '5fps' ? 1000 / 5 : o.backgroundRendering === '20fps' ? 1000 / 20 : undefined -}) - -const isFirefox = ua.getBrowser().name === 'Firefox' -if (isFirefox) { - // set custom property - document.body.style.setProperty('--thin-if-firefox', 'thin') -} - -const isIphone = ua.getDevice().model === 'iPhone' // todo ipad? - -if (isIphone) { - document.documentElement.style.setProperty('--hud-bottom-max', '21px') // env-safe-aria-inset-bottom -} - if (appQueryParams.testCrashApp === '2') throw new Error('test') -// Create viewer -const viewer: import('renderer/viewer/lib/viewer').Viewer = new Viewer(renderer, undefined, playerState) -window.viewer = viewer -Object.defineProperty(window, 'world', { - get () { - return viewer.world - }, -}) -// todo unify -viewer.entities.getItemUv = (item, specificProps) => { - const idOrName = item.itemId ?? item.blockId - try { - const name = typeof idOrName === 'number' ? loadedData.items[idOrName]?.name : idOrName - if (!name) throw new Error(`Item not found: ${idOrName}`) - - const model = getItemModelName({ - ...item, - name, - } as GeneralInputItem, specificProps) - - const renderInfo = renderSlot({ - modelName: model, - }, false, true) - - if (!renderInfo) throw new Error(`Failed to get render info for item ${name}`) +appViewer.loadBackend(createGraphicsBackend) - const textureThree = renderInfo.texture === 'blocks' ? viewer.world.material.map! : viewer.entities.itemsTexture! - const img = textureThree.image - - if (renderInfo.blockData) { - return { - resolvedModel: renderInfo.blockData.resolvedModel, - modelName: renderInfo.modelName! - } - } - if (renderInfo.slice) { - // Get slice coordinates from either block or item texture - const [x, y, w, h] = renderInfo.slice - const [u, v, su, sv] = [x / img.width, y / img.height, (w / img.width), (h / img.height)] - return { - u, v, su, sv, - texture: textureThree, - modelName: renderInfo.modelName - } - } - - throw new Error(`Invalid render info for item ${name}`) - } catch (err) { - reportError?.(err) - // Return default UV coordinates for missing texture - return { - u: 0, - v: 0, - su: 16 / viewer.world.material.map!.image.width, - sv: 16 / viewer.world.material.map!.image.width, - texture: viewer.world.material.map! - } - } -} - -viewer.entities.entitiesOptions = { - fontFamily: 'mojangles' -} +// todo unify +// viewer.entities.getItemUv = (item, specificProps) => { +// const idOrName = item.itemId ?? item.blockId +// try { +// const name = typeof idOrName === 'number' ? loadedData.items[idOrName]?.name : idOrName +// if (!name) throw new Error(`Item not found: ${idOrName}`) + +// const model = getItemModelName({ +// ...item, +// name, +// } as GeneralInputItem, specificProps) + +// const renderInfo = renderSlot({ +// modelName: model, +// }, false, true) + +// if (!renderInfo) throw new Error(`Failed to get render info for item ${name}`) + +// const textureThree = renderInfo.texture === 'blocks' ? viewer.world.material.map! : viewer.entities.itemsTexture! +// const img = textureThree.image + +// if (renderInfo.blockData) { +// return { +// resolvedModel: renderInfo.blockData.resolvedModel, +// modelName: renderInfo.modelName! +// } +// } +// if (renderInfo.slice) { +// // Get slice coordinates from either block or item texture +// const [x, y, w, h] = renderInfo.slice +// const [u, v, su, sv] = [x / img.width, y / img.height, (w / img.width), (h / img.height)] +// return { +// u, v, su, sv, +// texture: textureThree, +// modelName: renderInfo.modelName +// } +// } + +// throw new Error(`Invalid render info for item ${name}`) +// } catch (err) { +// reportError?.(err) +// // Return default UV coordinates for missing texture +// return { +// u: 0, +// v: 0, +// su: 16 / viewer.world.material.map!.image.width, +// sv: 16 / viewer.world.material.map!.image.width, +// texture: viewer.world.material.map! +// } +// } +// } + +// viewer.entities.entitiesOptions = { +// fontFamily: 'mojangles' +// } watchOptionsAfterViewerInit() function hideCurrentScreens () { @@ -326,7 +281,6 @@ export async function connect (connectOptions: ConnectOptions) { localServer = window.localServer = window.server = undefined gameAdditionalState.viewerConnection = false - renderWrapper.postRender = () => { } if (bot) { bot.end() // ensure mineflayer plugins receive this event for cleanup @@ -809,10 +763,6 @@ export async function connect (connectOptions: ConnectOptions) { void initVR() initMotionTracking() - renderWrapper.postRender = () => { - viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch) - } - // Link WorldDataEmitter and Viewer viewer.connect(worldView) worldView.listenToBot(bot) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index d473f5be2..be5831301 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -6,7 +6,7 @@ import { RecipeItem } from 'minecraft-data' import { flat, fromFormattedString } from '@xmcl/text-component' import { splitEvery, equals } from 'rambda' import PItem, { Item } from 'prismarine-item' -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import { getRenamedData } from 'flying-squid/dist/blockRenames' import PrismarineChatLoader from 'prismarine-chat' import { BlockModel } from 'mc-assets' diff --git a/src/loadSave.ts b/src/loadSave.ts index 861cc2121..4746a95c9 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -3,7 +3,7 @@ import path from 'path' import * as nbt from 'prismarine-nbt' import { proxy } from 'valtio' import { gzip } from 'node-gzip' -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import { options } from './optionsStorage' import { nameToMcOfflineUUID, disconnect } from './flyingSquidUtils' import { existsViaStats, forceCachedDataPaths, forceRedirectPaths, mkdirRecursive } from './browserfs' diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 260f8e534..712d0d2b9 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -40,7 +40,7 @@ export class PlayerStateManager implements IPlayerState { return this.instance } - private constructor () { + constructor () { this.updateState = this.updateState.bind(this) customEvents.on('mineflayerBotCreated', () => { this.ready = false diff --git a/src/mineflayer/plugins/ping.ts b/src/mineflayer/plugins/ping.ts index 47af8986f..9753e4ed4 100644 --- a/src/mineflayer/plugins/ping.ts +++ b/src/mineflayer/plugins/ping.ts @@ -1,4 +1,4 @@ -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' export default () => { let i = 0 diff --git a/src/optimizeJson.ts b/src/optimizeJson.ts index d19d25d5e..95289c177 100644 --- a/src/optimizeJson.ts +++ b/src/optimizeJson.ts @@ -1,4 +1,4 @@ -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' type IdMap = Record diff --git a/src/panorama.ts b/src/panorama.ts index efc06e16e..a145745b5 100644 --- a/src/panorama.ts +++ b/src/panorama.ts @@ -5,9 +5,6 @@ import fs from 'fs' import * as THREE from 'three' import { subscribeKey } from 'valtio/utils' import { EntityMesh } from 'renderer/viewer/lib/entity/EntityMesh' -import { fromTexturePackPath, resourcePackState } from './resourcePack' -import { options, watchValue } from './optionsStorage' -import { miscUiState } from './globalState' let panoramaCubeMap let shouldDisplayPanorama = false @@ -47,28 +44,9 @@ const updateResourcePackSupportPanorama = async () => { } } -watchValue(miscUiState, m => { - if (m.appLoaded) { - // Also adds panorama on app load here - watchValue(resourcePackState, async (s) => { - const oldState = panoramaUsesResourcePack - const newState = s.resourcePackInstalled && (await updateResourcePackSupportPanorama(), panoramaUsesResourcePack) - if (newState === oldState) return - removePanorama() - void addPanoramaCubeMap() - }) - } -}) - -subscribeKey(miscUiState, 'loadedDataVersion', () => { - if (miscUiState.loadedDataVersion) removePanorama() - else void addPanoramaCubeMap() -}) - // Menu panorama background // TODO-low use abort controller export async function addPanoramaCubeMap () { - if (panoramaCubeMap || miscUiState.loadedDataVersion || options.disableAssets) return shouldDisplayPanorama = true let time = 0 @@ -119,13 +97,3 @@ export async function addPanoramaCubeMap () { viewer.scene.add(group) panoramaCubeMap = group } - -export function removePanorama () { - shouldDisplayPanorama = false - if (!panoramaCubeMap) return - viewer.camera.fov = options.fov - viewer.camera.near = 0.1 - viewer.camera.updateProjectionMatrix() - viewer.scene.remove(panoramaCubeMap) - panoramaCubeMap = null -} diff --git a/src/react/BookProvider.tsx b/src/react/BookProvider.tsx index 056377cc4..cd2219ffe 100644 --- a/src/react/BookProvider.tsx +++ b/src/react/BookProvider.tsx @@ -1,4 +1,4 @@ -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import nbt from 'prismarine-nbt' import { useEffect, useState } from 'react' import { useSnapshot } from 'valtio' diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index d7fc16d76..da2fdff0b 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -34,12 +34,11 @@ export default () => { const [blockL, setBlockL] = useState(0) const [biomeId, setBiomeId] = useState(0) const [day, setDay] = useState(0) - const [entitiesCount, setEntitiesCount] = useState('0') const [dimension, setDimension] = useState('') const [cursorBlock, setCursorBlock] = useState(null) const minecraftYaw = useRef(0) const minecraftQuad = useRef(0) - const { rendererDevice } = viewer.world + const rendererDevice = appViewer.backend?.getRenderer() ?? 'No render backend' const quadsDescription = [ 'north (towards negative Z)', @@ -106,8 +105,7 @@ export default () => { setBiomeId(bot.world.getBiome(bot.entity.position)) setDimension(bot.game.dimension) setDay(bot.time.day) - setCursorBlock(bot.blockAtCursor(5)) - setEntitiesCount(`${viewer.entities.entitiesRenderingCount} (${Object.values(bot.entities).length})`) + setCursorBlock(bot.mouse.getCursorState().cursorBlock) }, 100) // @ts-expect-error @@ -136,7 +134,7 @@ export default () => { return <>

Prismarine Web Client ({bot.version})

-

E: {entitiesCount}

+ {appViewer.backend?.getDebugOverlay().entitiesString &&

E: {appViewer.backend.getDebugOverlay().entitiesString}

}

{dimension}

XYZ: {pos.x.toFixed(3)} / {pos.y.toFixed(3)} / {pos.z.toFixed(3)}

@@ -149,7 +147,7 @@ export default () => {

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

Day: {day}

- {Object.entries(customEntries.current).map(([name, value]) =>

{name}: {value}

)} + {Object.entries(appViewer.backend?.getDebugOverlay().left ?? {}).map(([name, value]) =>

{name}: {value}

)}
@@ -193,6 +191,7 @@ export default () => { ) })()} + {Object.entries(appViewer.backend?.getDebugOverlay().right ?? {}).map(([name, value]) =>

{name}: {value}

)}
} diff --git a/src/react/MinimapProvider.tsx b/src/react/MinimapProvider.tsx index 25df83aaf..10a9c738d 100644 --- a/src/react/MinimapProvider.tsx +++ b/src/react/MinimapProvider.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react' import { simplify } from 'prismarine-nbt' import RegionFile from 'prismarine-provider-anvil/src/region' import { Vec3 } from 'vec3' -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import { WorldWarp } from 'flying-squid/dist/lib/modules/warps' import { TypedEventEmitter } from 'contro-max/build/typedEventEmitter' import { PCChunk } from 'prismarine-chunk' diff --git a/src/reactUi.tsx b/src/reactUi.tsx index b6d4b6d2d..701c734a0 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -52,6 +52,19 @@ import MineflayerPluginConsole from './react/MineflayerPluginConsole' import { UIProvider } from './react/UIProvider' import { useAppScale } from './scaleInterface' import PacketsReplayProvider from './react/PacketsReplayProvider' +import { ua } from './react/utils' + +const isFirefox = ua.getBrowser().name === 'Firefox' +if (isFirefox) { + // set custom property + document.body.style.setProperty('--thin-if-firefox', 'thin') +} + +const isIphone = ua.getDevice().model === 'iPhone' // todo ipad? + +if (isIphone) { + document.documentElement.style.setProperty('--hud-bottom-max', '21px') // env-safe-aria-inset-bottom +} const RobustPortal = ({ children, to }) => { return createPortal({children}, to) diff --git a/src/shims/minecraftData.ts b/src/shims/minecraftData.ts index c25bcfb0d..b389621b7 100644 --- a/src/shims/minecraftData.ts +++ b/src/shims/minecraftData.ts @@ -1,4 +1,4 @@ -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import JsonOptimizer from '../optimizeJson' // import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json' import { toMajorVersion } from '../utils' diff --git a/src/sounds/botSoundSystem.ts b/src/sounds/botSoundSystem.ts index aa33db60a..949e9fc73 100644 --- a/src/sounds/botSoundSystem.ts +++ b/src/sounds/botSoundSystem.ts @@ -1,5 +1,5 @@ import { Vec3 } from 'vec3' -import { versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionToNumber } from 'renderer/viewer/common/utils' import { loadScript } from 'renderer/viewer/lib/utils' import type { Block } from 'prismarine-block' import { subscribeKey } from 'valtio/utils' diff --git a/src/sounds/soundsMap.ts b/src/sounds/soundsMap.ts index c461dd8da..1b0a0178b 100644 --- a/src/sounds/soundsMap.ts +++ b/src/sounds/soundsMap.ts @@ -1,6 +1,6 @@ import fs from 'fs' import path from 'path' -import { versionsMapToMajor, versionToMajor, versionToNumber } from 'renderer/viewer/prepare/utils' +import { versionsMapToMajor, versionToMajor, versionToNumber } from 'renderer/viewer/common/utils' import { stopAllSounds } from '../basicSounds' import { musicSystem } from './musicSystem' diff --git a/src/topRightStats.ts b/src/topRightStats.ts deleted file mode 100644 index 529aa9647..000000000 --- a/src/topRightStats.ts +++ /dev/null @@ -1,129 +0,0 @@ -import Stats from 'stats.js' -import StatsGl from 'stats-gl' -import { isCypress } from './standaloneUtils' - -const stats = new Stats() -const stats2 = new Stats() -const hasRamPanel = stats2.dom.children.length === 3 -const statsGl = new StatsGl({ minimal: true }) -// in my case values are good: gpu: < 0.5, cpu < 0.15 - -stats2.showPanel(2) - -// prod or small screen -const denseMode = process.env.NODE_ENV === 'production' || window.innerHeight < 500 - -let total = 0 -const addStat = (dom, size = 80) => { - dom.style.position = 'absolute' - if (denseMode) dom.style.height = '12px' - dom.style.overflow = 'hidden' - dom.style.left = '' - dom.style.top = 0 - dom.style.right = `${total}px` - dom.style.width = '80px' - dom.style.zIndex = 1 - dom.style.opacity = '0.8' - document.body.appendChild(dom) - total += size -} -const addStatsGlStat = (canvas) => { - const container = document.createElement('div') - canvas.style.position = 'static' - canvas.style.display = 'block' - container.appendChild(canvas) - addStat(container) -} -addStat(stats.dom) -if (hasRamPanel) { - addStat(stats2.dom) -} - -export const toggleStatsVisibility = (visible: boolean) => { - if (visible) { - stats.dom.style.display = 'block' - stats2.dom.style.display = 'block' - statsGl.container.style.display = 'block' - } else { - stats.dom.style.display = 'none' - stats2.dom.style.display = 'none' - statsGl.container.style.display = 'none' - } -} - -const hideStats = localStorage.hideStats || isCypress() -if (hideStats) { - toggleStatsVisibility(false) -} - -export const initWithRenderer = (canvas) => { - if (hideStats) return - statsGl.init(canvas) - // if (statsGl.gpuPanel && process.env.NODE_ENV !== 'production') { - // addStatsGlStat(statsGl.gpuPanel.canvas) - // } - // addStatsGlStat(statsGl.msPanel.canvas) - statsGl.container.style.display = 'flex' - statsGl.container.style.justifyContent = 'flex-end' - let i = 0 - for (const _child of statsGl.container.children) { - const child = _child as HTMLElement - if (i++ === 0) { - child.style.display = 'none' - } - child.style.position = '' - } -} - -export const statsStart = () => { - stats.begin() - stats2.begin() - statsGl.begin() -} -export const statsEnd = () => { - stats.end() - stats2.end() - statsGl.end() -} - -// for advanced debugging, use with watch expression - -window.statsPerSecAvg = {} -let currentStatsPerSec = {} as Record -const waitingStatsPerSec = {} -window.markStart = (label) => { - waitingStatsPerSec[label] ??= [] - waitingStatsPerSec[label][0] = performance.now() -} -window.markEnd = (label) => { - if (!waitingStatsPerSec[label]?.[0]) return - currentStatsPerSec[label] ??= [] - currentStatsPerSec[label].push(performance.now() - waitingStatsPerSec[label][0]) - delete waitingStatsPerSec[label] -} -const updateStatsPerSecAvg = () => { - window.statsPerSecAvg = Object.fromEntries(Object.entries(currentStatsPerSec).map(([key, value]) => { - return [key, { - avg: value.reduce((a, b) => a + b, 0) / value.length, - count: value.length - }] - })) - currentStatsPerSec = {} -} - - -window.statsPerSec = {} -let statsPerSecCurrent = {} -let lastReset = performance.now() -window.addStatPerSec = (name) => { - statsPerSecCurrent[name] ??= 0 - statsPerSecCurrent[name]++ -} -window.statsPerSecCurrent = statsPerSecCurrent -setInterval(() => { - window.statsPerSec = { duration: Math.floor(performance.now() - lastReset), ...statsPerSecCurrent, } - statsPerSecCurrent = {} - window.statsPerSecCurrent = statsPerSecCurrent - updateStatsPerSecAvg() - lastReset = performance.now() -}, 1000) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 724fb2ba4..0520a361c 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -1,12 +1,11 @@ // not all options are watched here import { subscribeKey } from 'valtio/utils' -import { WorldRendererThree } from 'renderer/viewer/lib/worldrendererThree' import { isMobile } from 'renderer/viewer/lib/simpleUtils' import { options, watchValue } from './optionsStorage' import { reloadChunks } from './utils' import { miscUiState } from './globalState' -import { toggleStatsVisibility } from './topRightStats' +import { isCypress } from './standaloneUtils' subscribeKey(options, 'renderDistance', reloadChunks) subscribeKey(options, 'multiplayerRenderDistance', reloadChunks) @@ -17,75 +16,95 @@ watchValue(options, o => { document.documentElement.style.setProperty('--chatHeight', `${o.chatHeight}px`) // gui scale is set in scaleInterface.ts }) +const updateTouch = (o) => { + miscUiState.currentTouch = o.alwaysShowMobileControls || isMobile() +} +watchValue(options, updateTouch) +window.matchMedia('(pointer: coarse)').addEventListener('change', (e) => { + updateTouch(options) +}) /** happens once */ export const watchOptionsAfterViewerInit = () => { - const updateTouch = (o) => { - miscUiState.currentTouch = o.alwaysShowMobileControls || isMobile() - } - - watchValue(options, updateTouch) - window.matchMedia('(pointer: coarse)').addEventListener('change', (e) => { - updateTouch(options) + watchValue(options, o => { + appViewer.inWorldRenderingConfig.showChunkBorders = o.showChunkBorders + // Note: entities debug mode still needs viewer access + if (viewer) viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none') }) watchValue(options, o => { - if (!viewer) return - viewer.world.config.showChunkBorders = o.showChunkBorders - viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none') + appViewer.inWorldRenderingConfig.mesherWorkers = o.lowMemoryMode ? 1 : o.numWorkers }) watchValue(options, o => { - if (!viewer) return - // todo ideally there shouldnt be this setting and we don't need to send all same chunks to all workers - viewer.world.config.numWorkers = o.lowMemoryMode ? 1 : o.numWorkers + if (viewer) viewer.entities.setRendering(o.renderEntities) }) watchValue(options, o => { - viewer.entities.setRendering(o.renderEntities) + const { renderDebug } = o + if (renderDebug === 'none' || isCypress()) { + appViewer.config.statsVisible = 0 + } else if (o.renderDebug === 'basic') { + appViewer.config.statsVisible = 1 + } else if (o.renderDebug === 'advanced') { + appViewer.config.statsVisible = 2 + } }) - if (options.renderDebug === 'none') { - toggleStatsVisibility(false) - } - subscribeKey(options, 'renderDebug', () => { - if (options.renderDebug === 'none') { - toggleStatsVisibility(false) + // Track window focus state and update FPS limit accordingly + let windowFocused = true + const updateFpsLimit = (o: typeof options) => { + const backgroundFpsLimit = o.backgroundRendering + const normalFpsLimit = o.frameLimit + + if (windowFocused) { + appViewer.config.fpsLimit = normalFpsLimit || undefined + } else if (backgroundFpsLimit === '5fps') { + appViewer.config.fpsLimit = 5 + } else if (backgroundFpsLimit === '20fps') { + appViewer.config.fpsLimit = 20 } else { - toggleStatsVisibility(true) + appViewer.config.fpsLimit = undefined } + } + + window.addEventListener('focus', () => { + windowFocused = true + updateFpsLimit(options) }) + window.addEventListener('blur', () => { + windowFocused = false + updateFpsLimit(options) + }) + watchValue(options, o => { - viewer.world.displayStats = o.renderDebug === 'advanced' + updateFpsLimit(o) }) + watchValue(options, (o, isChanged) => { - viewer.world.mesherConfig.clipWorldBelowY = o.clipWorldBelowY - viewer.world.mesherConfig.disableSignsMapsSupport = o.disableSignsMapsSupport - if (isChanged) { - (viewer.world as WorldRendererThree).rerenderAllChunks() - } + appViewer.inWorldRenderingConfig.clipWorldBelowY = o.clipWorldBelowY + appViewer.inWorldRenderingConfig.extraBlockRenderers = !o.disableSignsMapsSupport }) - viewer.world.mesherConfig.smoothLighting = options.smoothLighting + appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting subscribeKey(options, 'smoothLighting', () => { - viewer.world.mesherConfig.smoothLighting = options.smoothLighting; - (viewer.world as WorldRendererThree).rerenderAllChunks() + appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting }) + subscribeKey(options, 'newVersionsLighting', () => { - viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting; - (viewer.world as WorldRendererThree).rerenderAllChunks() + appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting }) + customEvents.on('gameLoaded', () => { - viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting + appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting }) watchValue(options, o => { - if (!(viewer.world instanceof WorldRendererThree)) return - viewer.world.starField.enabled = o.starfieldRendering + appViewer.inWorldRenderingConfig.starfield = o.starfieldRendering }) watchValue(options, o => { - viewer.world.neighborChunkUpdates = o.neighborChunkUpdates + // appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates }) } @@ -96,8 +115,8 @@ export const watchOptionsAfterWorldViewInit = () => { watchValue(options, o => { if (!worldView) return worldView.keepChunksDistance = o.keepChunksDistance - viewer.world.config.renderEars = o.renderEars - viewer.world.config.showHand = o.showHand - viewer.world.config.viewBobbing = o.viewBobbing + appViewer.inWorldRenderingConfig.renderEars = o.renderEars + appViewer.inWorldRenderingConfig.showHand = o.showHand + appViewer.inWorldRenderingConfig.viewBobbing = o.viewBobbing }) } From 4381ef4f75c815b6210a8f6c73f6bf4aef3e9551 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Mar 2025 12:57:04 +0300 Subject: [PATCH 11/41] removing bobbing & panorama --- renderer/viewer/lib/viewer.ts | 21 -------- src/panorama.ts | 99 ----------------------------------- 2 files changed, 120 deletions(-) delete mode 100644 src/panorama.ts diff --git a/renderer/viewer/lib/viewer.ts b/renderer/viewer/lib/viewer.ts index 941f21828..7c7b6d2d3 100644 --- a/renderer/viewer/lib/viewer.ts +++ b/renderer/viewer/lib/viewer.ts @@ -156,30 +156,9 @@ export class Viewer { setFirstPersonCamera (pos: Vec3 | null, yaw: number, pitch: number) { const cam = this.cameraObjectOverride || this.camera const yOffset = this.playerState.getEyeHeight() - // if (this.playerState.isSneaking()) yOffset -= 0.3 this.world.camera = cam as THREE.PerspectiveCamera this.world.updateCamera(pos?.offset(0, yOffset, 0) ?? null, yaw, pitch) - - // // Update camera bobbing based on movement state - // const velocity = this.playerState.getVelocity() - // const movementState = this.playerState.getMovementState() - // const isMoving = movementState === 'SPRINTING' || movementState === 'WALKING' - // const speed = Math.hypot(velocity.x, velocity.z) - - // // Update bobbing state - // this.cameraBobbing.updateWalkDistance(speed) - // this.cameraBobbing.updateBobAmount(isMoving) - - // // Get bobbing offsets - // const bobbing = isMoving ? this.cameraBobbing.getBobbing() : { position: { x: 0, y: 0 }, rotation: { x: 0, z: 0 } } - - // // Apply camera position with bobbing - // const finalPos = pos ? pos.offset(bobbing.position.x, yOffset + bobbing.position.y, 0) : null - // this.world.updateCamera(finalPos, yaw + bobbing.rotation.x, pitch) - - // // Apply roll rotation separately since updateCamera doesn't handle it - // this.camera.rotation.z = bobbing.rotation.z } playSound (position: Vec3, path: string, volume = 1, pitch = 1) { diff --git a/src/panorama.ts b/src/panorama.ts deleted file mode 100644 index a145745b5..000000000 --- a/src/panorama.ts +++ /dev/null @@ -1,99 +0,0 @@ -//@ts-check - -import { join } from 'path' -import fs from 'fs' -import * as THREE from 'three' -import { subscribeKey } from 'valtio/utils' -import { EntityMesh } from 'renderer/viewer/lib/entity/EntityMesh' - -let panoramaCubeMap -let shouldDisplayPanorama = false -let panoramaUsesResourcePack = null as boolean | null - -const panoramaFiles = [ - 'panorama_1.png', // WS - 'panorama_3.png', // ES - 'panorama_4.png', // Up - 'panorama_5.png', // Down - 'panorama_0.png', // NS - 'panorama_2.png' // SS -] - -const panoramaResourcePackPath = 'assets/minecraft/textures/gui/title/background' -const possiblyLoadPanoramaFromResourcePack = async (file) => { - let base64Texture - if (panoramaUsesResourcePack) { - try { - // TODO! - // base64Texture = await fs.promises.readFile(fromTexturePackPath(join(panoramaResourcePackPath, file)), 'base64') - } catch (err) { - panoramaUsesResourcePack = false - } - } - if (base64Texture) return `data:image/png;base64,${base64Texture}` - else return join('background', file) -} - -const updateResourcePackSupportPanorama = async () => { - try { - // TODO! - // await fs.promises.readFile(fromTexturePackPath(join(panoramaResourcePackPath, panoramaFiles[0])), 'base64') - panoramaUsesResourcePack = true - } catch (err) { - panoramaUsesResourcePack = false - } -} - -// Menu panorama background -// TODO-low use abort controller -export async function addPanoramaCubeMap () { - shouldDisplayPanorama = true - - let time = 0 - viewer.camera.fov = 85 - viewer.camera.near = 0.05 - viewer.camera.updateProjectionMatrix() - viewer.camera.position.set(0, 0, 0) - viewer.camera.rotation.set(0, 0, 0) - const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000) - - const loader = new THREE.TextureLoader() - const panorMaterials = [] as THREE.MeshBasicMaterial[] - await updateResourcePackSupportPanorama() - for (const file of panoramaFiles) { - panorMaterials.push(new THREE.MeshBasicMaterial({ - map: loader.load(await possiblyLoadPanoramaFromResourcePack(file)), - transparent: true, - side: THREE.DoubleSide - })) - } - - if (!shouldDisplayPanorama) return - - const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials) - - panoramaBox.onBeforeRender = () => { - time += 0.01 - panoramaBox.rotation.y = Math.PI + time * 0.01 - panoramaBox.rotation.z = Math.sin(-time * 0.001) * 0.001 - } - - const group = new THREE.Object3D() - group.add(panoramaBox) - - // should be rewritten entirely - for (let i = 0; i < 20; i++) { - const m = new EntityMesh('1.16.4', 'squid', viewer.world).mesh - m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17) - m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX') - const v = Math.random() * 0.01 - m.children[0].onBeforeRender = () => { - m.rotation.y += v - m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2 - } - group.add(m) - } - - viewer.scene.add(group) - panoramaCubeMap = group -} From d8294d565b1fed200aea0088a1e6984a4686bfbf Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Mar 2025 13:53:28 +0300 Subject: [PATCH 12/41] even more appViewer usage --- renderer/viewer/index.js | 6 --- renderer/viewer/lib/viewer.ts | 57 +++++++++++++---------- renderer/viewer/lib/worldDataEmitter.ts | 6 +++ renderer/viewer/lib/worldrendererThree.ts | 22 ++++++++- renderer/viewer/three/graphicsBackend.ts | 39 +++++++++++----- renderer/viewer/three/panorama.ts | 38 +++++++-------- renderer/viewer/three/renderer.ts | 22 ++++++--- src/appViewer.ts | 37 +++++++++------ src/cameraRotationControls.ts | 2 +- src/core/progressReporter.ts | 15 ++++-- src/devtools.ts | 4 +- src/index.ts | 57 ++++++----------------- src/mineflayer/cameraShake.ts | 22 +++------ src/rendererUtils.ts | 2 +- src/vr.ts | 4 +- 15 files changed, 181 insertions(+), 152 deletions(-) delete mode 100644 renderer/viewer/index.js diff --git a/renderer/viewer/index.js b/renderer/viewer/index.js deleted file mode 100644 index 3e263db97..000000000 --- a/renderer/viewer/index.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - Viewer: require('./lib/viewer').Viewer, - WorldDataEmitter: require('./lib/worldDataEmitter').WorldDataEmitter, - Entity: require('./lib/entity/EntityMesh'), - getBufferFromStream: require('./lib/simpleUtils').getBufferFromStream -} diff --git a/renderer/viewer/lib/viewer.ts b/renderer/viewer/lib/viewer.ts index 7c7b6d2d3..3fdd51a3b 100644 --- a/renderer/viewer/lib/viewer.ts +++ b/renderer/viewer/lib/viewer.ts @@ -2,32 +2,28 @@ import EventEmitter from 'events' import * as THREE from 'three' import { Vec3 } from 'vec3' import { generateSpiralMatrix } from 'flying-squid/dist/utils' -import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import { DisplayWorldOptions, GraphicsBackendOptions } from '../../../src/appViewer' import { Entities } from './entities' -import { Primitives } from './primitives' import { WorldRendererThree } from './worldrendererThree' -import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon' -import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' -import { addNewStat } from './ui/newStats' +import { WorldRendererCommon } from './worldrendererCommon' +import { setBlockPosition } from './mesher/standaloneRenderer' import { getMyHand } from './hand' -import { IPlayerState, BasePlayerState } from './basePlayerState' import { CameraBobbing } from './cameraBobbing' +import { WorldDataEmitter } from './worldDataEmitter' -export class Viewer { +export class ThreeJsWorldRenderer { scene: THREE.Scene ambientLight: THREE.AmbientLight directionalLight: THREE.DirectionalLight world: WorldRendererCommon entities: Entities // primitives: Primitives - domElement: HTMLCanvasElement - playerHeight = 1.62 threeJsWorld: WorldRendererThree cameraObjectOverride?: THREE.Object3D // for xr audioListener: THREE.AudioListener renderingUntilNoUpdates = false - processEntityOverrides = (e, overrides) => overrides private readonly cameraBobbing: CameraBobbing + private cameraRoll = 0 get camera () { return this.world.camera @@ -37,21 +33,17 @@ export class Viewer { this.world.camera = camera } - constructor (public renderer: THREE.WebGLRenderer, worldConfig = defaultWorldRendererConfig, public playerState: IPlayerState = new BasePlayerState()) { - // https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 - THREE.ColorManagement.enabled = false - renderer.outputColorSpace = THREE.LinearSRGBColorSpace - + constructor (public renderer: THREE.WebGLRenderer, private readonly worldOptionsHolder: DisplayWorldOptions) { this.scene = new THREE.Scene() this.scene.matrixAutoUpdate = false // for perf - this.threeJsWorld = new WorldRendererThree(this.scene, this.renderer, worldConfig, this.playerState) + this.threeJsWorld = new WorldRendererThree(this.scene, this.renderer, this.worldOptionsHolder) this.setWorld() this.resetScene() this.entities = new Entities(this) // this.primitives = new Primitives(this.scene, this.camera) this.cameraBobbing = new CameraBobbing() - this.domElement = renderer.domElement + this.connect(this.worldOptionsHolder.worldView) } setWorld () { @@ -142,7 +134,7 @@ export class Viewer { } updateEntity (e) { - this.entities.update(e, this.processEntityOverrides(e, { + this.entities.update(e, { rotation: { head: { x: e.headPitch ?? e.pitch, @@ -150,12 +142,26 @@ export class Viewer { z: 0 } } - })) + }) + } + + setCameraRoll (roll: number) { + this.cameraRoll = roll + const rollQuat = new THREE.Quaternion() + rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(roll)) + + // Get camera's current rotation + const camQuat = new THREE.Quaternion() + this.camera.getWorldQuaternion(camQuat) + + // Apply roll after camera rotation + const finalQuat = camQuat.multiply(rollQuat) + this.camera.setRotationFromQuaternion(finalQuat) } setFirstPersonCamera (pos: Vec3 | null, yaw: number, pitch: number) { const cam = this.cameraObjectOverride || this.camera - const yOffset = this.playerState.getEyeHeight() + const yOffset = this.worldOptionsHolder.playerState.getEyeHeight() this.world.camera = cam as THREE.PerspectiveCamera this.world.updateCamera(pos?.offset(0, yOffset, 0) ?? null, yaw, pitch) @@ -192,15 +198,13 @@ export class Viewer { addChunksBatchWaitTime = 200 - connect (worldEmitter: EventEmitter) { + connect (worldView: WorldDataEmitter) { + const worldEmitter = worldView + worldEmitter.on('entity', (e) => { this.updateEntity(e) }) - worldEmitter.on('primitive', (p) => { - // this.updatePrimitive(p) - }) - let currentLoadChunkBatch = null as { timeout data @@ -301,4 +305,7 @@ export class Viewer { async waitForChunksToRender () { await this.world.waitForChunksToRender() } + + dispose () { + } } diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 7c2be7154..fe221e131 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -5,6 +5,7 @@ import { EventEmitter } from 'events' import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils' import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' +import { proxy } from 'valtio' import { getItemFromBlock } from '../../../src/chatUtils' import { delayedIterator } from '../../playground/shared' import { playerState } from '../../../src/mineflayer/playerState' @@ -26,6 +27,11 @@ export class WorldDataEmitter extends EventEmitter { addWaitTime = 1 isPlayground = false + public reactive = proxy({ + cursorBlock: null as Vec3 | null, + cursorBlockBreakingStage: null as number | null, + }) + constructor (public world: typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { super() this.loadedChunks = {} diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 977ee2df3..6a8fa75d8 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -6,6 +6,7 @@ import * as tweenJs from '@tweenjs/tween.js' import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass, LineSegmentsGeometry, Wireframe, LineMaterial } from 'three-stdlib' import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' import { renderSign } from '../sign-renderer' +import { DisplayWorldOptions, GraphicsBackendOptions } from '../../../src/appViewer' import { chunkPos, sectionPos } from './simpleUtils' import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' @@ -27,6 +28,7 @@ export class WorldRendererThree extends WorldRendererCommon { cameraSectionPos: Vec3 = new Vec3(0, 0, 0) holdingBlock: HoldingBlock holdingBlockLeft: HoldingBlock + cameraRoll = 0 get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -36,8 +38,10 @@ export class WorldRendererThree extends WorldRendererCommon { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).blocksCount, 0) } - constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig, public playerState: IPlayerState) { - super(config) + constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public worldOptionsHolder: DisplayWorldOptions) { + super(worldOptionsHolder.inWorldRenderingConfig) + const { playerState } = worldOptionsHolder + this.starField = new StarField(scene) this.holdingBlock = new HoldingBlock(playerState, this.config) this.holdingBlockLeft = new HoldingBlock(playerState, this.config, true) @@ -455,6 +459,20 @@ export class WorldRendererThree extends WorldRendererCommon { console.warn('Failed to get renderer info', err) } } + + setCameraRoll (roll: number) { + this.cameraRoll = roll + const rollQuat = new THREE.Quaternion() + rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(roll)) + + // Get camera's current rotation + const camQuat = new THREE.Quaternion() + this.camera.getWorldQuaternion(camQuat) + + // Apply roll after camera rotation + const finalQuat = camQuat.multiply(rollQuat) + this.camera.setRotationFromQuaternion(finalQuat) + } } class StarField { diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index c6159145c..7633b8dd2 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -1,26 +1,35 @@ import * as THREE from 'three' +import { Vec3 } from 'vec3' import { GraphicsBackendLoader, GraphicsBackend, GraphicsBackendOptions, DisplayWorldOptions } from '../../../src/appViewer' import { ProgressReporter } from '../../../src/core/progressReporter' +import { ThreeJsWorldRenderer } from '../lib/viewer' import { WorldRendererThree } from '../lib/worldrendererThree' import { DocumentRenderer } from './renderer' import { PanoramaRenderer } from './panorama' +// https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 +THREE.ColorManagement.enabled = false + const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOptions) => { // Private state const documentRenderer = new DocumentRenderer(options) globalThis.renderer = documentRenderer.renderer + let panoramaRenderer: PanoramaRenderer | null = null + let worldRenderer: ThreeJsWorldRenderer | null = null - // Private methods const startPanorama = () => { + if (worldRenderer) return if (!panoramaRenderer) { panoramaRenderer = new PanoramaRenderer(documentRenderer) void panoramaRenderer.start() } } - const updateResources = async (version: string, progressReporter: ProgressReporter): Promise => { + let version = '' + const updateResources = async (ver: string, progressReporter: ProgressReporter): Promise => { // Implementation for updating resources will be added here + version = ver } const startWorld = (options: DisplayWorldOptions) => { @@ -28,6 +37,8 @@ const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOp panoramaRenderer.dispose() panoramaRenderer = null } + worldRenderer = new ThreeJsWorldRenderer(documentRenderer.renderer, options) + void worldRenderer.setVersion(version) } const disconnect = () => { @@ -38,14 +49,11 @@ const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOp if (documentRenderer) { documentRenderer.dispose() } - } - const startRender = () => { - documentRenderer.setPaused(false) - } - - const stopRender = () => { - documentRenderer.setPaused(true) + if (worldRenderer) { + worldRenderer.dispose() + worldRenderer = null + } } const renderer = WorldRendererThree.getRendererInfo(documentRenderer.renderer) ?? '...' @@ -58,11 +66,18 @@ const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOp updateResources, startWorld, disconnect, - startRender, - stopRender, + setRendering (rendering) { + documentRenderer.setPaused(!rendering) + }, getRenderer: () => renderer, getDebugOverlay: () => ({ - }) + }), + updateCamera (pos: Vec3 | null, yaw: number, pitch: number) { + worldRenderer?.setFirstPersonCamera(pos, yaw, pitch) + }, + setRoll (roll: number) { + worldRenderer?.setCameraRoll(roll) + } } return backend diff --git a/renderer/viewer/three/panorama.ts b/renderer/viewer/three/panorama.ts index 28776498e..8ef35cb3a 100644 --- a/renderer/viewer/three/panorama.ts +++ b/renderer/viewer/three/panorama.ts @@ -14,13 +14,24 @@ const panoramaFiles = [ export class PanoramaRenderer { private readonly camera: THREE.PerspectiveCamera private readonly scene: THREE.Scene + private readonly ambientLight: THREE.AmbientLight + private readonly directionalLight: THREE.DirectionalLight private panoramaGroup: THREE.Object3D | null = null - private readonly documentRenderer: DocumentRenderer private time = 0 - constructor (documentRenderer: DocumentRenderer) { - this.documentRenderer = documentRenderer + constructor (private readonly documentRenderer: DocumentRenderer) { this.scene = new THREE.Scene() + + // Add ambient light + this.ambientLight = new THREE.AmbientLight(0xcc_cc_cc) + this.scene.add(this.ambientLight) + + // Add directional light + this.directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5) + this.directionalLight.position.set(1, 1, 0.5).normalize() + this.directionalLight.castShadow = true + this.scene.add(this.directionalLight) + this.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000) this.camera.position.set(0, 0, 0) this.camera.rotation.set(0, 0, 0) @@ -51,7 +62,7 @@ export class PanoramaRenderer { // Add squids for (let i = 0; i < 20; i++) { - const m = new EntityMesh('1.16.4', 'squid', this.documentRenderer.world).mesh + const m = new EntityMesh('1.16.4', 'squid').mesh m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17) m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX') const v = Math.random() * 0.01 @@ -65,26 +76,15 @@ export class PanoramaRenderer { this.scene.add(group) this.panoramaGroup = group - // Override renderer's render method - const originalRender = this.documentRenderer.render.bind(this.documentRenderer) - this.documentRenderer.render = () => { - if (this.panoramaGroup) { - this.documentRenderer.renderer.render(this.scene, this.camera) - } else { - originalRender() + this.documentRenderer.render = (sizeChanged = false) => { + if (sizeChanged) { + this.camera.aspect = window.innerWidth / window.innerHeight } - } - } - - stop () { - if (this.panoramaGroup) { - this.scene.remove(this.panoramaGroup) - this.panoramaGroup = null + this.documentRenderer.renderer.render(this.scene, this.camera) } } dispose () { - this.stop() this.scene.clear() } } diff --git a/renderer/viewer/three/renderer.ts b/renderer/viewer/three/renderer.ts index 0c648cbf6..3a95ea225 100644 --- a/renderer/viewer/three/renderer.ts +++ b/renderer/viewer/three/renderer.ts @@ -36,8 +36,11 @@ export class DocumentRenderer { }) } catch (err) { initOptions.displayCriticalError(new Error(`Failed to create WebGL context, not possible to render (restart browser): ${err.message}`)) + throw err } + this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace this.updatePixelRatio() + this.updateSize() this.addToPage() this.stats = new TopRightStats(this.canvas, this.config.statsVisible) @@ -54,6 +57,10 @@ export class DocumentRenderer { this.renderer.setPixelRatio(pixelRatio) } + updateSize () { + this.renderer.setSize(window.innerWidth, window.innerHeight) + } + private addToPage () { this.canvas.id = 'viewer-canvas' this.canvas.style.width = '100%' @@ -89,13 +96,6 @@ export class DocumentRenderer { if (this.paused) return - let sizeChanged = false - if (this.previousWindowWidth !== window.innerWidth || this.previousWindowHeight !== window.innerHeight) { - this.previousWindowWidth = window.innerWidth - this.previousWindowHeight = window.innerHeight - sizeChanged = true - } - // Handle FPS limiting if (this.config.fpsLimit) { const now = performance.now() @@ -109,6 +109,14 @@ export class DocumentRenderer { this.lastRenderTime = now - (elapsed % fpsInterval) } + let sizeChanged = false + if (this.previousWindowWidth !== window.innerWidth || this.previousWindowHeight !== window.innerHeight) { + this.previousWindowWidth = window.innerWidth + this.previousWindowHeight = window.innerHeight + this.updateSize() + sizeChanged = true + } + this.preRender() this.stats.markStart() this.render(sizeChanged) diff --git a/src/appViewer.ts b/src/appViewer.ts index 534ff47b3..1ee4635c8 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -2,10 +2,11 @@ import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' import { IPlayerState } from 'renderer/viewer/lib/basePlayerState' import { subscribeKey } from 'valtio/utils' import { defaultWorldRendererConfig, WorldRendererConfig } from 'renderer/viewer/lib/worldrendererCommon' -import { PlayerStateManager } from './mineflayer/playerState' +import { Vec3 } from 'vec3' +import { playerState, PlayerStateManager } from './mineflayer/playerState' import { createNotificationProgressReporter, ProgressReporter } from './core/progressReporter' import { setLoadingScreenStatus } from './appStatus' -import { activeModalStack } from './globalState' +import { activeModalStack, miscUiState } from './globalState' import { options } from './optionsStorage' export interface GraphicsBackendConfig { @@ -29,6 +30,7 @@ export interface DisplayWorldOptions { resourcesManager: ResourcesManager worldView: WorldDataEmitter inWorldRenderingConfig: WorldRendererConfig + playerState: IPlayerState } export type GraphicsBackendLoader = (options: GraphicsBackendOptions) => GraphicsBackend @@ -40,8 +42,7 @@ export interface GraphicsBackend { startWorld: (options: DisplayWorldOptions) => void disconnect: () => void - startRender: () => void - stopRender: () => void + setRendering: (rendering: boolean) => void getRenderer: () => string getDebugOverlay: () => { @@ -49,12 +50,14 @@ export interface GraphicsBackend { right?: Record left?: Record } + updateCamera: (pos: Vec3 | null, yaw: number, pitch: number) => void + setRoll: (roll: number) => void } export class AppViewer { resourcesManager: ResourcesManager worldView: WorldDataEmitter - playerState = new PlayerStateManager() + // playerState: IPlayerState readonly config: GraphicsBackendConfig = { ...defaultGraphicsBackendConfig, powerPreference: options.gpuPreference === 'default' ? undefined : options.gpuPreference @@ -65,6 +68,7 @@ export class AppViewer { args: any[] } inWorldRenderingConfig: WorldRendererConfig = defaultWorldRendererConfig + lastCamUpdate = 0 loadBackend (loader: GraphicsBackendLoader, loadResourcesVersion?: string) { if (this.backend) { @@ -92,6 +96,7 @@ export class AppViewer { } startPanorama () { + if (options.disableAssets) return if (this.backend) { this.backend.startPanorama() } @@ -106,11 +111,14 @@ export class AppViewer { startWorld (world, renderDistance, startPosition) { this.worldView = new WorldDataEmitter(world, renderDistance, startPosition) + window.worldView = this.worldView + // this.playerState = new PlayerStateManager() if (this.backend) { this.backend.startWorld({ resourcesManager: this.resourcesManager, worldView: this.worldView, - inWorldRenderingConfig: this.inWorldRenderingConfig + inWorldRenderingConfig: this.inWorldRenderingConfig, + playerState }) } this.queuedDisplay = { method: 'startWorld', args: [options] } @@ -131,13 +139,16 @@ window.appViewer = appViewer class ResourcesManager { } -subscribeKey(activeModalStack, 'length', () => { +const modalStackUpdate = () => { + if (activeModalStack.length === 0 && !miscUiState.gameLoaded) { + // tood reset backend + appViewer.startPanorama() + } + if (appViewer.backend) { const hasAppStatus = activeModalStack.some(m => m.reactType === 'app-status') - if (hasAppStatus) { - appViewer.backend.stopRender() - } else { - appViewer.backend.startRender() - } + appViewer.backend.setRendering(!hasAppStatus) } -}) +} +subscribeKey(activeModalStack, 'length', modalStackUpdate) +modalStackUpdate() diff --git a/src/cameraRotationControls.ts b/src/cameraRotationControls.ts index 0c222dc6e..cf220d5ff 100644 --- a/src/cameraRotationControls.ts +++ b/src/cameraRotationControls.ts @@ -37,7 +37,7 @@ export const moveCameraRawHandler = ({ x, y }: { x: number; y: number }) => { const maxPitch = 0.5 * Math.PI const minPitch = -0.5 * Math.PI - viewer.world.lastCamUpdate = Date.now() + appViewer.lastCamUpdate = Date.now() if (viewer.world.freeFlyMode) { // Update freeFlyState directly diff --git a/src/core/progressReporter.ts b/src/core/progressReporter.ts index e2bdf5bc4..bc6f9ab70 100644 --- a/src/core/progressReporter.ts +++ b/src/core/progressReporter.ts @@ -166,13 +166,13 @@ export const createNotificationProgressReporter = (endMessage?: string): Progres }) } -export const createConsoleLogProgressReporter = (): ProgressReporter => { +export const createConsoleLogProgressReporter = (group?: string): ProgressReporter => { return createProgressReporter({ setMessage (message: string) { - console.log(message) + console.log(group ? `[${group}] ${message}` : message) }, end () { - console.log('done') + console.log(group ? `[${group}] done` : 'done') } }) } @@ -194,3 +194,12 @@ export const createWrappedProgressReporter = (reporter: ProgressReporter, messag } }) } + +export const createNullProgressReporter = (): ProgressReporter => { + return createProgressReporter({ + setMessage (message: string) { + }, + end () { + } + }) +} diff --git a/src/devtools.ts b/src/devtools.ts index da0f48c22..6b374baad 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -19,7 +19,7 @@ window.inspectPlayer = () => require('fs').promises.readFile('/world/playerdata/ Object.defineProperty(window, 'debugSceneChunks', { get () { - return (viewer.world as WorldRendererThree).getLoadedChunksRelative?.(bot.entity.position, true) + return (window.world as WorldRendererThree)?.getLoadedChunksRelative?.(bot.entity.position, true) }, }) @@ -147,7 +147,7 @@ Object.defineProperty(window, 'debugToggle', { }) customEvents.on('gameLoaded', () => { - window.holdingBlock = (viewer.world as WorldRendererThree).holdingBlock + window.holdingBlock = (window.world as WorldRendererThree).holdingBlock }) window.clearStorage = (...keysToKeep: string[]) => { diff --git a/src/index.ts b/src/index.ts index b5f908c66..91e1de040 100644 --- a/src/index.ts +++ b/src/index.ts @@ -35,7 +35,6 @@ import downloadAndOpenFile from './downloadAndOpenFile' import fs from 'fs' import net from 'net' import mineflayer from 'mineflayer' -import { WorldDataEmitter } from 'renderer/viewer' import pathfinder from 'mineflayer-pathfinder' import { Vec3 } from 'vec3' @@ -63,10 +62,6 @@ import { parseServerAddress } from './parseServerAddress' import { setLoadingScreenStatus } from './appStatus' import { isCypress } from './standaloneUtils' -import { - removePanorama -} from './panorama' - import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer' import defaultServerOptions from './defaultLocalServerOptions' import dayCycle from './dayCycle' @@ -106,7 +101,7 @@ import ping from './mineflayer/plugins/ping' import mouse from './mineflayer/plugins/mouse' import { startLocalReplayServer } from './packetsReplay/replayPackets' import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording' -import { createFullScreenProgressReporter } from './core/progressReporter' +import { createConsoleLogProgressReporter, createFullScreenProgressReporter } from './core/progressReporter' import { getItemModelName } from './resourcesManager' import { appViewer } from './appViewer' import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend' @@ -210,30 +205,10 @@ function listenGlobalEvents () { }) } -let listeners = [] as Array<{ target, event, callback }> -let cleanupFunctions = [] as Array<() => void> -// only for dom listeners (no removeAllListeners) -// todo refactor them out of connect fn instead -const registerListener: import('./utilsTs').RegisterListener = (target, event, callback) => { - target.addEventListener(event, callback) - listeners.push({ target, event, callback }) -} -const removeAllListeners = () => { - for (const { target, event, callback } of listeners) { - target.removeEventListener(event, callback) - } - for (const cleanupFunction of cleanupFunctions) { - cleanupFunction() - } - cleanupFunctions = [] - listeners = [] -} - export async function connect (connectOptions: ConnectOptions) { if (miscUiState.gameLoaded) return miscUiState.hasErrors = false lastConnectOptions.value = connectOptions - removePanorama() const { singleplayer } = connectOptions const p2pMultiplayer = !!connectOptions.peerId @@ -276,8 +251,8 @@ export async function connect (connectOptions: ConnectOptions) { const destroyAll = () => { if (ended) return ended = true - viewer.resetAll() progress.end() + // dont reset viewer so we can still do debugging localServer = window.localServer = window.server = undefined gameAdditionalState.viewerConnection = false @@ -294,7 +269,6 @@ export async function connect (connectOptions: ConnectOptions) { } resetStateAfterDisconnect() cleanFs() - removeAllListeners() } const cleanFs = () => { if (singleplayer && !fsState.inMemorySave) { @@ -387,8 +361,9 @@ export async function connect (connectOptions: ConnectOptions) { await progress.executeWithMessage( 'Loading minecraft models', async () => { - viewer.world.blockstatesModels = await import('mc-assets/dist/blockStatesModels.json') - void viewer.setVersion(version, options.useVersionsTextures === 'latest' ? version : options.useVersionsTextures) + // viewer.world.blockstatesModels = await import('mc-assets/dist/blockStatesModels.json') + // void viewer.setVersion(version, options.useVersionsTextures === 'latest' ? version : options.useVersionsTextures) + void appViewer.updateResources(version, createConsoleLogProgressReporter()) miscUiState.loadedDataVersion = version } ) @@ -740,7 +715,7 @@ export async function connect (connectOptions: ConnectOptions) { if (p2pConnectTimeout) clearTimeout(p2pConnectTimeout) playerState.onlineMode = !!connectOptions.authenticatedAccount - setLoadingScreenStatus('Placing blocks (starting viewer)') + progress.setMessage('Placing blocks (starting viewer)') if (!connectOptions.worldStateFileContents || connectOptions.worldStateFileContents.length < 3 * 1024 * 1024) { localStorage.lastConnectOptions = JSON.stringify(connectOptions) } @@ -753,34 +728,30 @@ export async function connect (connectOptions: ConnectOptions) { bot.chat(`/login ${connectOptions.autoLoginPassword}`) } - console.log('bot spawned - starting viewer') - const center = bot.entity.position - - const worldView = window.worldView = new WorldDataEmitter(bot.world, renderDistance, center) + console.log('bot spawned - starting viewer') + appViewer.startWorld(bot.world, renderDistance, bot.entity.position) watchOptionsAfterWorldViewInit() void initVR() initMotionTracking() - // Link WorldDataEmitter and Viewer - viewer.connect(worldView) - worldView.listenToBot(bot) - void worldView.init(bot.entity.position) + appViewer.worldView.listenToBot(bot) + void appViewer.worldView.init(bot.entity.position) dayCycle() // Bot position callback function botPosition () { - viewer.world.lastCamUpdate = Date.now() + appViewer.lastCamUpdate = Date.now() // this might cause lag, but not sure - viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) - void worldView.updatePosition(bot.entity.position) + appViewer.backend?.updateCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) + void appViewer.worldView.updatePosition(bot.entity.position) } bot.on('move', botPosition) botPosition() - setLoadingScreenStatus('Setting callbacks') + progress.setMessage('Setting callbacks') onGameLoad(() => {}) diff --git a/src/mineflayer/cameraShake.ts b/src/mineflayer/cameraShake.ts index 043eb207b..cf2c3d117 100644 --- a/src/mineflayer/cameraShake.ts +++ b/src/mineflayer/cameraShake.ts @@ -52,18 +52,8 @@ class CameraShake { } } - // Apply roll in camera's local space to maintain consistent left/right roll - const { camera } = viewer - const rollQuat = new THREE.Quaternion() - rollQuat.setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle)) - - // Get camera's current rotation - const camQuat = new THREE.Quaternion() - camera.getWorldQuaternion(camQuat) - - // Apply roll after camera rotation - const finalQuat = camQuat.multiply(rollQuat) - camera.setRotationFromQuaternion(finalQuat) + // Apply roll to camera + appViewer.backend?.setRoll(this.rollAngle) } private easeOut (t: number): number { @@ -77,6 +67,10 @@ class CameraShake { let cameraShake: CameraShake +customEvents.on('hurtAnimation', () => { + cameraShake.shakeFromDamage() +}) + customEvents.on('mineflayerBotCreated', () => { if (!cameraShake) { cameraShake = new CameraShake() @@ -85,10 +79,6 @@ customEvents.on('mineflayerBotCreated', () => { }) } - customEvents.on('hurtAnimation', () => { - cameraShake.shakeFromDamage() - }) - bot._client.on('hurt_animation', () => { customEvents.emit('hurtAnimation') }) diff --git a/src/rendererUtils.ts b/src/rendererUtils.ts index 120816494..493636f15 100644 --- a/src/rendererUtils.ts +++ b/src/rendererUtils.ts @@ -91,6 +91,6 @@ export const watchFov = () => { }) subscribeKey(gameAdditionalState, 'isSneaking', () => { - viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) + appViewer.backend?.updateCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) }) } diff --git a/src/vr.ts b/src/vr.ts index dd97560ec..ff20d7c7b 100644 --- a/src/vr.ts +++ b/src/vr.ts @@ -191,8 +191,8 @@ export async function initVR () { rotSnapReset = true } - // viewer.setFirstPersonCamera(null, yawOffset, 0) - viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch) + // appViewer.backend?.updateCamera(null, yawOffset, 0) + appViewer.backend?.updateCamera(null, bot.entity.yaw, bot.entity.pitch) // todo restore this logic (need to preserve ability to move camera) // const xrCamera = renderer.xr.getCamera() From 1861edf567e8661a46b12e38d8be1be22b9da55b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 4 Mar 2025 14:17:44 +0300 Subject: [PATCH 13/41] delayLoadUntilFocus --- src/controls.ts | 9 ++++++++- src/index.ts | 11 +++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/controls.ts b/src/controls.ts index a3a54ffb9..221db53c6 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -916,9 +916,16 @@ window.addEventListener('keydown', (e) => { // #region experimental debug things window.addEventListener('keydown', (e) => { - if (e.code === 'KeyL' && e.altKey) { + if (e.code === 'KeyL' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { console.clear() } + if (e.code === 'KeyK' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { + if (sessionStorage.delayLoadUntilFocus) { + sessionStorage.removeItem('delayLoadUntilFocus') + } else { + sessionStorage.setItem('delayLoadUntilFocus', 'true') + } + } }) // #endregion diff --git a/src/index.ts b/src/index.ts index 91e1de040..c2552a5b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -207,6 +207,17 @@ function listenGlobalEvents () { export async function connect (connectOptions: ConnectOptions) { if (miscUiState.gameLoaded) return + + if (sessionStorage.delayLoadUntilFocus) { + await new Promise(resolve => { + if (document.hasFocus()) { + resolve(undefined) + } else { + window.addEventListener('focus', resolve) + } + }) + } + miscUiState.hasErrors = false lastConnectOptions.value = connectOptions From d197859d478907fc005f407b16ac4dead2a2f8cd Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 5 Mar 2025 15:30:04 +0300 Subject: [PATCH 14/41] big resourcemanager refactor --- renderer/viewer/lib/worldrendererCommon.ts | 102 +++----------- renderer/viewer/lib/worldrendererThree.ts | 32 +++-- renderer/viewer/three/graphicsBackend.ts | 11 +- src/appViewer.ts | 154 +++++++++++++++++++-- src/index.ts | 18 +-- src/inventoryWindows.ts | 4 +- src/mineflayer/plugins/mouse.ts | 2 +- src/react/HotbarRenderApp.tsx | 6 +- src/resourcePack.ts | 36 ++--- 9 files changed, 218 insertions(+), 147 deletions(-) diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 46e57ce28..aeba4d4ca 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -3,27 +3,20 @@ import { EventEmitter } from 'events' import { Vec3 } from 'vec3' import * as THREE from 'three' import mcDataRaw from 'minecraft-data/data.js' // note: using alias -import blocksAtlases from 'mc-assets/dist/blocksAtlases.json' -import blocksAtlasLatest from 'mc-assets/dist/blocksAtlasLatest.png' -import blocksAtlasLegacy from 'mc-assets/dist/blocksAtlasLegacy.png' -import itemsAtlases from 'mc-assets/dist/itemsAtlases.json' -import itemsAtlasLatest from 'mc-assets/dist/itemsAtlasLatest.png' -import itemsAtlasLegacy from 'mc-assets/dist/itemsAtlasLegacy.png' -import { AtlasParser, getLoadedItemDefinitionsStore } from 'mc-assets' +import { AtlasParser } from 'mc-assets' import TypedEmitter from 'typed-emitter' import { LineMaterial } from 'three-stdlib' -import christmasPack from 'mc-assets/dist/textureReplacements/christmas' import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' -import itemDefinitionsJson from 'mc-assets/dist/itemDefinitions.json' import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs' import { toMajorVersion } from '../../../src/utils' +import { ResourcesManager } from '../../../src/appViewer' import { buildCleanupDecorator } from './cleanupDecorator' import { defaultMesherConfig, HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo } from './mesher/shared' import { chunkPos } from './simpleUtils' -import { HandItemBlock } from './holdingBlock' import { updateStatText } from './ui/newStats' -import { WorldRendererThree } from './worldrendererThree' + +const appViewer = undefined function mod (x, n) { return ((x % n) + n) % n @@ -93,8 +86,6 @@ export abstract class WorldRendererCommon renderUpdateEmitter = new EventEmitter() as unknown as TypedEmitter<{ dirty (pos: Vec3, value: boolean): void update (/* pos: Vec3, value: boolean */): void - textureDownloaded (): void - itemsTextureDownloaded (): void chunkFinished (key: string): void }> customTexturesDataUrl = undefined as string | undefined @@ -120,29 +111,16 @@ export abstract class WorldRendererCommon mesherConfig = defaultMesherConfig camera: THREE.PerspectiveCamera highestBlocks = new Map() - blockstatesModels: any customBlockStates: Record | undefined customModels: Record | undefined itemsAtlasParser: AtlasParser | undefined blocksAtlasParser: AtlasParser | undefined - sourceData = { - blocksAtlases, - itemsAtlases, - itemDefinitionsJson - } - customTextures: { - items?: CustomTexturesData - blocks?: CustomTexturesData - armor?: CustomTexturesData - } = {} - itemsDefinitionsStore = getLoadedItemDefinitionsStore(this.sourceData.itemDefinitionsJson) workersProcessAverageTime = 0 workersProcessAverageTimeCount = 0 maxWorkersProcessTime = 0 geometryReceiveCount = {} allLoadedIn: undefined | number - rendererDevice = '...' edgeChunks = {} as Record lastAddChunk = null as null | { @@ -154,14 +132,6 @@ export abstract class WorldRendererCommon lastChunkDistance = 0 debugStopGeometryUpdate = false - @worldCleanup() - freeFlyMode = false - @worldCleanup() - freeFlyState = { - yaw: 0, - pitch: 0, - position: new Vec3(0, 0, 0) - } @worldCleanup() itemsRenderer: ItemsRenderer | undefined @@ -175,7 +145,7 @@ export abstract class WorldRendererCommon abstract changeBackgroundColor (color: [number, number, number]): void - constructor (public config: WorldRendererConfig) { + constructor (private readonly resourcesManager: ResourcesManager, private readonly worldRendererConfig: WorldRendererConfig) { // this.initWorkers(1) // preload script on page load this.snapshotInitialValues() @@ -183,11 +153,15 @@ export abstract class WorldRendererCommon const loadedChunks = Object.keys(this.finishedChunks).length updateStatText('loaded-chunks', `${loadedChunks}/${this.chunksLength} chunks (${this.lastChunkDistance}/${this.viewDistance})`) }) + + this.resourcesManager.on('assetsTexturesUpdated', () => { + void this.updateAssetsData() + }) } snapshotInitialValues () { } - initWorkers (numWorkers = this.config.mesherWorkers) { + 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 @@ -332,7 +306,6 @@ export abstract class WorldRendererCommon // new game load happens here async setVersion (version, texturesVersion = version) { - if (!this.blockstatesModels) throw new Error('Blockstates models is not loaded yet') this.version = version this.texturesVersion = texturesVersion this.resetWorld() @@ -342,7 +315,7 @@ export abstract class WorldRendererCommon this.mesherConfig.version = this.version! this.sendMesherMcData() - await this.updateAssetsData() + await this.resourcesManager.updateAssetsData() } sendMesherMcData () { @@ -359,38 +332,8 @@ export abstract class WorldRendererCommon } } - async updateAssetsData (resourcePackUpdate = false, prioritizeBlockTextures?: string[]) { - const blocksAssetsParser = new AtlasParser(this.sourceData.blocksAtlases, blocksAtlasLatest, blocksAtlasLegacy) - const itemsAssetsParser = new AtlasParser(this.sourceData.itemsAtlases, itemsAtlasLatest, itemsAtlasLegacy) - - const blockTexturesChanges = {} as Record - const date = new Date() - if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) { - Object.assign(blockTexturesChanges, christmasPack) - } - - const customBlockTextures = Object.keys(this.customTextures.blocks?.textures ?? {}) - const customItemTextures = Object.keys(this.customTextures.items?.textures ?? {}) - console.time('createBlocksAtlas') - const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas(this.texturesVersion ?? this.version ?? 'latest', (textureName) => { - const texture = this.customTextures?.blocks?.textures[textureName] - return blockTexturesChanges[textureName] ?? texture - }, /* this.customTextures?.blocks?.tileSize */undefined, prioritizeBlockTextures, customBlockTextures) - console.timeEnd('createBlocksAtlas') - console.time('createItemsAtlas') - const { atlas: itemsAtlas, canvas: itemsCanvas } = await itemsAssetsParser.makeNewAtlas(this.texturesVersion ?? this.version ?? 'latest', (textureName) => { - const texture = this.customTextures?.items?.textures[textureName] - if (!texture) return - return texture - }, this.customTextures?.items?.tileSize, undefined, customItemTextures) - console.timeEnd('createItemsAtlas') - this.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL()) - this.itemsAtlasParser = new AtlasParser({ latest: itemsAtlas }, itemsCanvas.toDataURL()) - - this.itemsRenderer = new ItemsRenderer(this.version!, this.blockstatesModels, this.itemsAtlasParser, this.blocksAtlasParser) - this.worldBlockProvider = worldBlockProvider(this.blockstatesModels, this.blocksAtlasParser.atlas, 'latest') - - const texture = await new THREE.TextureLoader().loadAsync(this.blocksAtlasParser.latestImage) + async updateAssetsData () { + const texture = await new THREE.TextureLoader().loadAsync(this.resourcesManager.blocksAtlasParser!.latestImage) texture.magFilter = THREE.NearestFilter texture.minFilter = THREE.NearestFilter texture.flipY = false @@ -399,39 +342,34 @@ export abstract class WorldRendererCommon this.mesherConfig.textureSize = this.material.map.image.width for (const [i, worker] of this.workers.entries()) { - const { blockstatesModels } = this - if (this.customBlockStates) { - // TODO! remove from other versions as well + const { blockstatesModels } = this.resourcesManager + if (this.resourcesManager.customBlockStates) { blockstatesModels.blockstates.latest = { ...blockstatesModels.blockstates.latest, - ...this.customBlockStates + ...this.resourcesManager.customBlockStates } } - if (this.customModels) { + if (this.resourcesManager.customModels) { blockstatesModels.models.latest = { ...blockstatesModels.models.latest, - ...this.customModels + ...this.resourcesManager.customModels } } worker.postMessage({ type: 'mesherData', workerIndex: i, blocksAtlas: { - latest: blocksAtlas + latest: this.resourcesManager.blocksAtlasParser!.atlas }, blockstatesModels, config: this.mesherConfig, }) } - const itemsTexture = await new THREE.TextureLoader().loadAsync(this.itemsAtlasParser.latestImage) + const itemsTexture = await new THREE.TextureLoader().loadAsync(this.resourcesManager.itemsAtlasParser!.latestImage) itemsTexture.magFilter = THREE.NearestFilter itemsTexture.minFilter = THREE.NearestFilter itemsTexture.flipY = false viewer.entities.itemsTexture = itemsTexture - - this.renderUpdateEmitter.emit('textureDownloaded') - this.renderUpdateEmitter.emit('itemsTextureDownloaded') - console.log('textures loaded') } async downloadDebugAtlas (isItems = false) { diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 6a8fa75d8..ab9e17b3a 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -17,6 +17,8 @@ import { IPlayerState } from './basePlayerState' import { getMesh } from './entity/EntityMesh' import { armorModel } from './entity/armorModels' +const appViewer = undefined + export class WorldRendererThree extends WorldRendererCommon { interactionLines: null | { blockPos; mesh } = null outputFormat = 'threeJs' as const @@ -38,15 +40,15 @@ export class WorldRendererThree extends WorldRendererCommon { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).blocksCount, 0) } - constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public worldOptionsHolder: DisplayWorldOptions) { - super(worldOptionsHolder.inWorldRenderingConfig) - const { playerState } = worldOptionsHolder + constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public options: DisplayWorldOptions) { + super(options.resourcesManager, options.inWorldRenderingConfig) + const { playerState, inWorldRenderingConfig: config } = options this.starField = new StarField(scene) - this.holdingBlock = new HoldingBlock(playerState, this.config) - this.holdingBlockLeft = new HoldingBlock(playerState, this.config, true) + this.holdingBlock = new HoldingBlock(playerState, config) + this.holdingBlockLeft = new HoldingBlock(playerState, config, true) - this.renderUpdateEmitter.on('itemsTextureDownloaded', () => { + this.options.resourcesManager.on('assetsTexturesUpdated', () => { this.holdingBlock.ready = true this.holdingBlock.updateItem() this.holdingBlockLeft.ready = true @@ -168,7 +170,7 @@ export class WorldRendererThree extends WorldRendererCommon { object.name = 'chunk'; (object as any).tilesCount = data.geometry.positions.length / 3 / 4; (object as any).blocksCount = data.geometry.blocksCount - if (!this.config.showChunkBorders) { + if (!this.options.inWorldRenderingConfig.showChunkBorders) { boxHelper.visible = false } // should not compute it once @@ -225,15 +227,15 @@ export class WorldRendererThree extends WorldRendererCommon { } updateCamera (pos: Vec3 | null, yaw: number, pitch: number): void { - if (this.freeFlyMode) { - pos = this.freeFlyState.position - pitch = this.freeFlyState.pitch - yaw = this.freeFlyState.yaw - } + // if (this.freeFlyMode) { + // pos = this.freeFlyState.position + // pitch = this.freeFlyState.pitch + // yaw = this.freeFlyState.yaw + // } if (pos) { new tweenJs.Tween(this.camera.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start() - this.freeFlyState.position = pos + // this.freeFlyState.position = pos } this.camera.rotation.set(pitch, yaw, this.cameraRoll, 'ZYX') } @@ -243,7 +245,7 @@ export class WorldRendererThree extends WorldRendererCommon { // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera this.renderer.render(this.scene, cam) - if (this.config.showHand && !this.freeFlyMode) { + if (this.options.inWorldRenderingConfig.showHand/* && !this.freeFlyMode */) { this.holdingBlock.render(this.camera, this.renderer, viewer.ambientLight, viewer.directionalLight) this.holdingBlockLeft.render(this.camera, this.renderer, viewer.ambientLight, viewer.directionalLight) } @@ -347,7 +349,7 @@ export class WorldRendererThree extends WorldRendererCommon { } updateShowChunksBorder (value: boolean) { - this.config.showChunkBorders = value + this.options.inWorldRenderingConfig.showChunkBorders = value for (const object of Object.values(this.sectionObjects)) { for (const child of object.children) { if (child.name === 'helper') { diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 7633b8dd2..0bd3144e8 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -1,5 +1,6 @@ import * as THREE from 'three' import { Vec3 } from 'vec3' +import { proxy } from 'valtio' import { GraphicsBackendLoader, GraphicsBackend, GraphicsBackendOptions, DisplayWorldOptions } from '../../../src/appViewer' import { ProgressReporter } from '../../../src/core/progressReporter' import { ThreeJsWorldRenderer } from '../lib/viewer' @@ -18,6 +19,11 @@ const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOp let panoramaRenderer: PanoramaRenderer | null = null let worldRenderer: ThreeJsWorldRenderer | null = null + const worldState = proxy({ + chunksLoaded: 0, + chunksTotal: 0 + }) + const startPanorama = () => { if (worldRenderer) return if (!panoramaRenderer) { @@ -28,7 +34,6 @@ const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOp let version = '' const updateResources = async (ver: string, progressReporter: ProgressReporter): Promise => { - // Implementation for updating resources will be added here version = ver } @@ -49,7 +54,6 @@ const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOp if (documentRenderer) { documentRenderer.dispose() } - if (worldRenderer) { worldRenderer.dispose() worldRenderer = null @@ -77,7 +81,8 @@ const createGraphicsBackend: GraphicsBackendLoader = (options: GraphicsBackendOp }, setRoll (roll: number) { worldRenderer?.setCameraRoll(roll) - } + }, + worldState } return backend diff --git a/src/appViewer.ts b/src/appViewer.ts index 1ee4635c8..bfd383712 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -1,14 +1,33 @@ +import { EventEmitter } from 'events' import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' import { IPlayerState } from 'renderer/viewer/lib/basePlayerState' import { subscribeKey } from 'valtio/utils' import { defaultWorldRendererConfig, WorldRendererConfig } from 'renderer/viewer/lib/worldrendererCommon' import { Vec3 } from 'vec3' +import { proxy } from 'valtio' +import blocksAtlases from 'mc-assets/dist/blocksAtlases.json' +import itemsAtlases from 'mc-assets/dist/itemsAtlases.json' +import itemDefinitionsJson from 'mc-assets/dist/itemDefinitions.json' +import blocksAtlasLatest from 'mc-assets/dist/blocksAtlasLatest.png' +import blocksAtlasLegacy from 'mc-assets/dist/blocksAtlasLegacy.png' +import itemsAtlasLatest from 'mc-assets/dist/itemsAtlasLatest.png' +import itemsAtlasLegacy from 'mc-assets/dist/itemsAtlasLegacy.png' +import christmasPack from 'mc-assets/dist/textureReplacements/christmas' +import { AtlasParser, getLoadedItemDefinitionsStore } from 'mc-assets' +import TypedEmitter from 'typed-emitter' +import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' +import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import { playerState, PlayerStateManager } from './mineflayer/playerState' import { createNotificationProgressReporter, ProgressReporter } from './core/progressReporter' import { setLoadingScreenStatus } from './appStatus' import { activeModalStack, miscUiState } from './globalState' import { options } from './optionsStorage' +export interface WorldReactiveState { + chunksLoaded: number + chunksTotal: number +} + export interface GraphicsBackendConfig { fpsLimit?: number powerPreference?: 'high-performance' | 'low-power' @@ -41,23 +60,17 @@ export interface GraphicsBackend { updateResources: (version: string, progressReporter: ProgressReporter) => Promise startWorld: (options: DisplayWorldOptions) => void disconnect: () => void - setRendering: (rendering: boolean) => void - getRenderer: () => string - getDebugOverlay: () => { - entitiesString?: string - right?: Record - left?: Record - } + getDebugOverlay: () => Record updateCamera: (pos: Vec3 | null, yaw: number, pitch: number) => void setRoll: (roll: number) => void + worldState: WorldReactiveState } export class AppViewer { - resourcesManager: ResourcesManager + resourcesManager: ResourcesManager = new ResourcesManager() worldView: WorldDataEmitter - // playerState: IPlayerState readonly config: GraphicsBackendConfig = { ...defaultGraphicsBackendConfig, powerPreference: options.gpuPreference === 'default' ? undefined : options.gpuPreference @@ -105,14 +118,14 @@ export class AppViewer { async updateResources (version: string, progressReporter: ProgressReporter) { if (this.backend) { - await this.backend.updateResources(version, progressReporter) + // await this.backend.updateResources(version, progressReporter) } } - startWorld (world, renderDistance, startPosition) { + async startWorld (world, renderDistance, startPosition) { this.worldView = new WorldDataEmitter(world, renderDistance, startPosition) window.worldView = this.worldView - // this.playerState = new PlayerStateManager() + if (this.backend) { this.backend.startWorld({ resourcesManager: this.resourcesManager, @@ -133,12 +146,123 @@ export class AppViewer { } } -export const appViewer = new AppViewer() -window.appViewer = appViewer +export interface UpdateAssetsRequest { + includeOnlyBlocks?: string[] +} + +type ResourceManagerEvents = { + assetsTexturesUpdated: () => void +} + +export class ResourcesManager extends (EventEmitter as new () => TypedEmitter) { + // Source data (imported, not changing) + sourceBlockStatesModels: any = null + sourceBlocksAtlases: any = blocksAtlases + sourceItemsAtlases: any = itemsAtlases + sourceItemDefinitionsJson: any = itemDefinitionsJson + itemsDefinitionsStore = getLoadedItemDefinitionsStore(this.sourceItemDefinitionsJson) + + // Atlas parsers + itemsAtlasParser: AtlasParser | undefined + blocksAtlasParser: AtlasParser | undefined + + // User data (specific to current resourcepack/version) + customBlockStates?: Record + customModels?: Record + customTextures: { + items?: { tileSize: number | undefined, textures: Record } + blocks?: { tileSize: number | undefined, textures: Record } + armor?: { tileSize: number | undefined, textures: Record } + } = {} + + // Moved from WorldRendererCommon + itemsRenderer: ItemsRenderer | undefined + worldBlockProvider: WorldBlockProvider | undefined + blockstatesModels: any = null + + version?: string + texturesVersion?: string + + async updateAssetsData (request: UpdateAssetsRequest = {}) { + const blocksAssetsParser = new AtlasParser(this.sourceBlocksAtlases, blocksAtlasLatest, blocksAtlasLegacy) + const itemsAssetsParser = new AtlasParser(this.sourceItemsAtlases, itemsAtlasLatest, itemsAtlasLegacy) -class ResourcesManager { + const blockTexturesChanges = {} as Record + const date = new Date() + if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) { + Object.assign(blockTexturesChanges, christmasPack) + } + + const customBlockTextures = Object.keys(this.customTextures.blocks?.textures ?? {}) + const customItemTextures = Object.keys(this.customTextures.items?.textures ?? {}) + + console.time('createBlocksAtlas') + const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas( + this.texturesVersion ?? this.version ?? 'latest', + (textureName) => { + if (request.includeOnlyBlocks && !request.includeOnlyBlocks.includes(textureName)) return false + const texture = this.customTextures?.blocks?.textures[textureName] + return blockTexturesChanges[textureName] ?? texture + }, + undefined, + undefined, + customBlockTextures + ) + console.timeEnd('createBlocksAtlas') + + console.time('createItemsAtlas') + const { atlas: itemsAtlas, canvas: itemsCanvas } = await itemsAssetsParser.makeNewAtlas( + this.texturesVersion ?? this.version ?? 'latest', + (textureName) => { + const texture = this.customTextures?.items?.textures[textureName] + if (!texture) return + return texture + }, + this.customTextures?.items?.tileSize, + undefined, + customItemTextures + ) + console.timeEnd('createItemsAtlas') + + this.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL()) + this.itemsAtlasParser = new AtlasParser({ latest: itemsAtlas }, itemsCanvas.toDataURL()) + + // Initialize ItemsRenderer and WorldBlockProvider + if (this.version && this.blockstatesModels && this.itemsAtlasParser && this.blocksAtlasParser) { + this.itemsRenderer = new ItemsRenderer( + this.version, + this.blockstatesModels, + this.itemsAtlasParser, + this.blocksAtlasParser + ) + this.worldBlockProvider = worldBlockProvider( + this.blockstatesModels, + this.blocksAtlasParser.atlas, + 'latest' + ) + } + + // Emit event that textures were updated + this.emit('assetsTexturesUpdated') + + return { + blocksAtlas, + itemsAtlas, + blocksCanvas, + itemsCanvas + } + } + + async setVersion (version: string, texturesVersion?: string) { + this.version = version + this.texturesVersion = texturesVersion + await this.updateAssetsData() + } } +export const appViewer = new AppViewer() +window.appViewer = appViewer + const modalStackUpdate = () => { if (activeModalStack.length === 0 && !miscUiState.gameLoaded) { // tood reset backend diff --git a/src/index.ts b/src/index.ts index c2552a5b3..884f85941 100644 --- a/src/index.ts +++ b/src/index.ts @@ -372,7 +372,7 @@ export async function connect (connectOptions: ConnectOptions) { await progress.executeWithMessage( 'Loading minecraft models', async () => { - // viewer.world.blockstatesModels = await import('mc-assets/dist/blockStatesModels.json') + appViewer.resourcesManager.sourceBlockStatesModels ??= await import('mc-assets/dist/blockStatesModels.json') // void viewer.setVersion(version, options.useVersionsTextures === 'latest' ? version : options.useVersionsTextures) void appViewer.updateResources(version, createConsoleLogProgressReporter()) miscUiState.loadedDataVersion = version @@ -699,14 +699,14 @@ export async function connect (connectOptions: ConnectOptions) { const start = Date.now() let worldWasReady = false - void viewer.world.renderUpdateEmitter.on('update', () => { - // todo might not emit as servers simply don't send chunk if it's empty - if (!viewer.world.allChunksFinished || worldWasReady) return - worldWasReady = true - console.log('All chunks done and ready! Time from renderer open to ready', (Date.now() - start) / 1000, 's') - viewer.render() // ensure the last state is rendered - document.dispatchEvent(new Event('cypress-world-ready')) - }) + // void viewer.world.renderUpdateEmitter.on('update', () => { + // // todo might not emit as servers simply don't send chunk if it's empty + // if (!viewer.world.allChunksFinished || worldWasReady) return + // worldWasReady = true + // console.log('All chunks done and ready! Time from renderer open to ready', (Date.now() - start) / 1000, 's') + // viewer.render() // ensure the last state is rendered + // document.dispatchEvent(new Event('cypress-world-ready')) + // }) const spawnEarlier = !singleplayer && !p2pMultiplayer // don't use spawn event, player can be dead diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index be5831301..353426cfc 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -41,7 +41,7 @@ export const onGameLoad = (onLoad) => { version = bot.version const checkIfLoaded = () => { - if (!viewer.world.itemsAtlasParser) return + if (!appViewer.resourcesManager.itemsAtlasParser) return if (!allImagesLoadedState.value) { onLoad?.() } @@ -50,7 +50,7 @@ export const onGameLoad = (onLoad) => { allImagesLoadedState.value = true }, 0) } - viewer.world.renderUpdateEmitter.on('textureDownloaded', checkIfLoaded) + appViewer.resourcesManager.on('assetsTexturesUpdated', checkIfLoaded) checkIfLoaded() PrismarineItem = PItem(version) diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index e5b5e283c..1953d7e45 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -143,7 +143,7 @@ export default (bot: Bot) => { bot.loadPlugin(createMouse({})) domListeners(bot) - createDisplayManager(bot, viewer.scene, viewer.renderer) + // createDisplayManager(bot, viewer.scene, viewer.renderer) otherListeners() } diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index af7d1387a..3f1325c7a 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -110,7 +110,7 @@ const HotbarInner = () => { inv.canvas.style.pointerEvents = 'auto' container.current.appendChild(inv.canvas) const upHotbarItems = () => { - if (!viewer.world.currentTextureImage || !allImagesLoadedState.value) return + if (!appViewer.resourcesManager.itemsAtlasParser || !allImagesLoadedState.value) return upInventoryItems(true, inv) } @@ -124,7 +124,7 @@ const HotbarInner = () => { upHotbarItems() bot.inventory.on('updateSlot', upHotbarItems) - viewer.world.renderUpdateEmitter.on('textureDownloaded', upHotbarItems) + appViewer.resourcesManager.on('assetsTexturesUpdated', upHotbarItems) const unsub2 = subscribe(allImagesLoadedState, () => { upHotbarItems() }) @@ -197,7 +197,7 @@ const HotbarInner = () => { inv.destroy() controller.abort() unsub2() - viewer.world.renderUpdateEmitter.off('textureDownloaded', upHotbarItems) + appViewer.resourcesManager.off('assetsTexturesUpdated', upHotbarItems) } }, []) diff --git a/src/resourcePack.ts b/src/resourcePack.ts index 6e9c28a4e..a5921be9d 100644 --- a/src/resourcePack.ts +++ b/src/resourcePack.ts @@ -14,6 +14,7 @@ import { appReplacableResources, resourcesContentOriginal } from './generated/re import { gameAdditionalState, miscUiState } from './globalState' import { watchUnloadForCleanup } from './gameUnload' import { createConsoleLogProgressReporter, createFullScreenProgressReporter, ProgressReporter } from './core/progressReporter' +import { appViewer } from './appViewer' export const resourcePackState = proxy({ resourcePackInstalled: false, @@ -312,8 +313,8 @@ export const getResourcepackTiles = async (type: 'blocks' | 'items' | 'armor', e } const prepareBlockstatesAndModels = async (progressReporter: ProgressReporter) => { - viewer.world.customBlockStates = {} - viewer.world.customModels = {} + appViewer.resourcesManager.customBlockStates = {} + appViewer.resourcesManager.customModels = {} const usedBlockTextures = new Set() const usedItemTextures = new Set() const basePath = await getActiveResourcepackBasePath() @@ -360,9 +361,9 @@ const prepareBlockstatesAndModels = async (progressReporter: ProgressReporter) = const blockModelsPath = `${basePath}/assets/${namespaceDir}/models/block` const itemModelsPath = `${basePath}/assets/${namespaceDir}/models/item` - Object.assign(viewer.world.customBlockStates!, await readModelData(blockstatesPath, 'blockstates', namespaceDir)) - Object.assign(viewer.world.customModels!, await readModelData(blockModelsPath, 'models', namespaceDir)) - Object.assign(viewer.world.customModels!, await readModelData(itemModelsPath, 'models', namespaceDir)) + Object.assign(appViewer.resourcesManager.customBlockStates!, await readModelData(blockstatesPath, 'blockstates', namespaceDir)) + Object.assign(appViewer.resourcesManager.customModels!, await readModelData(blockModelsPath, 'models', namespaceDir)) + Object.assign(appViewer.resourcesManager.customModels!, await readModelData(itemModelsPath, 'models', namespaceDir)) } try { @@ -372,8 +373,8 @@ const prepareBlockstatesAndModels = async (progressReporter: ProgressReporter) = } } catch (err) { console.error('Failed to read some of resource pack blockstates and models', err) - viewer.world.customBlockStates = undefined - viewer.world.customModels = undefined + appViewer.resourcesManager.customBlockStates = undefined + appViewer.resourcesManager.customModels = undefined } return { usedBlockTextures, @@ -523,38 +524,39 @@ const repeatArr = (arr, i) => Array.from({ length: i }, () => arr) const updateTextures = async (progressReporter = createConsoleLogProgressReporter()) => { currentErrors = [] - const origBlocksFiles = Object.keys(viewer.world.sourceData.blocksAtlases.latest.textures) - const origItemsFiles = Object.keys(viewer.world.sourceData.itemsAtlases.latest.textures) + const origBlocksFiles = Object.keys(appViewer.resourcesManager.sourceBlocksAtlases.latest.textures) + const origItemsFiles = Object.keys(appViewer.resourcesManager.sourceItemsAtlases.latest.textures) const origArmorFiles = Object.keys(armorTextures) const { usedBlockTextures, usedItemTextures } = await prepareBlockstatesAndModels(progressReporter) ?? {} const blocksData = await getResourcepackTiles('blocks', [...origBlocksFiles, ...usedBlockTextures ?? []], progressReporter) const itemsData = await getResourcepackTiles('items', [...origItemsFiles, ...usedItemTextures ?? []], progressReporter) const armorData = await getResourcepackTiles('armor', origArmorFiles, progressReporter) await updateAllReplacableTextures() - viewer.world.customTextures = {} + appViewer.resourcesManager.customTextures = {} + if (blocksData) { - viewer.world.customTextures.blocks = { + appViewer.resourcesManager.customTextures.blocks = { tileSize: blocksData.firstTextureSize, textures: blocksData.textures } } if (itemsData) { - viewer.world.customTextures.items = { + appViewer.resourcesManager.customTextures.items = { tileSize: itemsData.firstTextureSize, textures: itemsData.textures } } if (armorData) { - viewer.world.customTextures.armor = { + appViewer.resourcesManager.customTextures.armor = { tileSize: armorData.firstTextureSize, textures: armorData.textures } } - if (viewer.world.active) { - await viewer.world.updateAssetsData() - if (viewer.world instanceof WorldRendererThree) { - viewer.world.rerenderAllChunks?.() + if (appViewer.backend) { + await appViewer.resourcesManager.updateAssetsData() + if (appViewer.backend instanceof WorldRendererThree) { + appViewer.backend.rerenderAllChunks?.() } } } From 0597a3dad2b1b96305bac2ada31e637a364038d6 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 9 Mar 2025 04:57:48 +0300 Subject: [PATCH 15/41] make library versions less annoying --- src/react/components/LibraryVersions.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/react/components/LibraryVersions.tsx b/src/react/components/LibraryVersions.tsx index 4ab76d09a..a3daf2655 100644 --- a/src/react/components/LibraryVersions.tsx +++ b/src/react/components/LibraryVersions.tsx @@ -2,7 +2,9 @@ import React from 'react' import physicsUtilPkg from '@nxg-org/mineflayer-physics-util/package.json' import mineflayerPkg from 'mineflayer/package.json' import mcProtocolPkg from 'minecraft-protocol/package.json' +import { useSnapshot } from 'valtio' import packageJson from '../../../package.json' +import { miscUiState } from '../../globalState' const LibraryVersions: React.FC = () => { const versions = { @@ -11,17 +13,22 @@ const LibraryVersions: React.FC = () => { 'minecraft-protocol': mcProtocolPkg.version } + const { gameLoaded } = useSnapshot(miscUiState) + + if (!gameLoaded) return null + return (
From 3a9e2aa384c89c6be526e435547047d67124b7db Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 15 Mar 2025 00:46:21 +0300 Subject: [PATCH 16/41] fix --- package.json | 10 +++++----- pnpm-lock.yaml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 0f3cb49be..6241c925f 100644 --- a/package.json +++ b/package.json @@ -118,11 +118,11 @@ "workbox-build": "^7.0.0" }, "devDependencies": { - "@rsbuild/core": "^1.0.1-beta.9", - "@rsbuild/plugin-node-polyfill": "^1.0.3", - "@rsbuild/plugin-react": "^1.0.1-beta.9", - "@rsbuild/plugin-type-check": "^1.0.1-beta.9", - "@rsbuild/plugin-typed-css-modules": "^1.0.1", + "@rsbuild/core": "1.0.1-beta.9", + "@rsbuild/plugin-node-polyfill": "1.0.3", + "@rsbuild/plugin-react": "1.0.1-beta.9", + "@rsbuild/plugin-type-check": "1.0.1-beta.9", + "@rsbuild/plugin-typed-css-modules": "1.0.1", "@storybook/addon-essentials": "^7.4.6", "@storybook/addon-links": "^7.4.6", "@storybook/blocks": "^7.4.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd0429169..96ee58e84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -257,19 +257,19 @@ importers: version: 5.25.11 devDependencies: '@rsbuild/core': - specifier: ^1.0.1-beta.9 + specifier: 1.0.1-beta.9 version: 1.0.1-beta.9 '@rsbuild/plugin-node-polyfill': - specifier: ^1.0.3 + specifier: 1.0.3 version: 1.0.3(@rsbuild/core@1.0.1-beta.9) '@rsbuild/plugin-react': - specifier: ^1.0.1-beta.9 + specifier: 1.0.1-beta.9 version: 1.0.1-beta.9(@rsbuild/core@1.0.1-beta.9) '@rsbuild/plugin-type-check': - specifier: ^1.0.1-beta.9 + specifier: 1.0.1-beta.9 version: 1.0.1-beta.9(@rsbuild/core@1.0.1-beta.9)(esbuild@0.19.12)(typescript@5.5.4) '@rsbuild/plugin-typed-css-modules': - specifier: ^1.0.1 + specifier: 1.0.1 version: 1.0.1(@rsbuild/core@1.0.1-beta.9) '@storybook/addon-essentials': specifier: ^7.4.6 From 8a3c84745d5c3c74a11a8a0957753316c4c1b90f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 15 Mar 2025 02:18:44 +0300 Subject: [PATCH 17/41] test flying! --- src/controls.ts | 170 +++++++++--------------------------------------- 1 file changed, 30 insertions(+), 140 deletions(-) diff --git a/src/controls.ts b/src/controls.ts index dd82c4d87..ed974a0b5 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -185,10 +185,10 @@ let lastCommandTrigger = null as { command: string, time: number } | null const secondActionActivationTimeout = 300 const secondActionCommands = { - // 'general.jump' () { - // // if (bot.game.gameMode === 'spectator') return - // toggleFly() - // }, + 'general.jump' () { + // if (bot.game.gameMode === 'spectator') return + toggleFly() + }, 'general.forward' () { setSprinting(true) } @@ -706,150 +706,40 @@ document.addEventListener('visibilitychange', (e) => { } }) -// #region creative fly -// these controls are more like for gamemode 3 - -const makeInterval = (fn, interval) => { - const intervalId = setInterval(fn, interval) +const isFlying = () => (bot.entity as any).flying - const cleanup = () => { - clearInterval(intervalId) - cleanup.active = false +const startFlying = (sendAbilities = true) => { + if (sendAbilities) { + bot._client.write('abilities', { + flags: 2, + }) } - cleanup.active = true - return cleanup + (bot.entity as any).flying = true } -// const isFlying = () => bot.physics.gravity === 0 -// let endFlyLoop: ReturnType | undefined - -// const currentFlyVector = new Vec3(0, 0, 0) -// window.currentFlyVector = currentFlyVector - -// todo cleanup -// const flyingPressedKeys = { -// down: false, -// up: false -// } - -// const startFlyLoop = () => { -// if (!isFlying()) return -// endFlyLoop?.() - -// endFlyLoop = makeInterval(() => { -// if (!bot) { -// endFlyLoop?.() -// return -// } - -// bot.entity.position.add(currentFlyVector.clone().multiply(new Vec3(0, 0.5, 0))) -// }, 50) -// } - -// todo we will get rid of patching it when refactor controls -// let originalSetControlState -// const patchedSetControlState = (action, state) => { -// if (!isFlying()) { -// return originalSetControlState(action, state) -// } - -// const actionPerFlyVector = { -// jump: new Vec3(0, 1, 0), -// sneak: new Vec3(0, -1, 0), -// } - -// const changeVec = actionPerFlyVector[action] -// if (!changeVec) { -// return originalSetControlState(action, state) -// } -// if (flyingPressedKeys[state === 'jump' ? 'up' : 'down'] === state) return -// const toAddVec = changeVec.scaled(state ? 1 : -1) -// for (const coord of ['x', 'y', 'z']) { -// if (toAddVec[coord] === 0) continue -// if (currentFlyVector[coord] === toAddVec[coord]) return -// } -// currentFlyVector.add(toAddVec) -// flyingPressedKeys[state === 'jump' ? 'up' : 'down'] = state -// } - -// const startFlying = (sendAbilities = true) => { -// bot.entity['creativeFly'] = true -// if (sendAbilities) { -// bot._client.write('abilities', { -// flags: 2, -// }) -// } -// // window.flyingSpeed will be removed -// bot.physics['airborneAcceleration'] = window.flyingSpeed ?? 0.1 // todo use abilities -// bot.entity.velocity = new Vec3(0, 0, 0) -// bot.creative.startFlying() -// startFlyLoop() -// } - -// const endFlying = (sendAbilities = true) => { -// bot.entity['creativeFly'] = false -// if (bot.physics.gravity !== 0) return -// if (sendAbilities) { -// bot._client.write('abilities', { -// flags: 0, -// }) -// } -// Object.assign(flyingPressedKeys, { -// up: false, -// down: false -// }) -// currentFlyVector.set(0, 0, 0) -// bot.physics['airborneAcceleration'] = standardAirborneAcceleration -// bot.creative.stopFlying() -// endFlyLoop?.() -// } - -const allowFlying = false +const endFlying = (sendAbilities = true) => { + if (!isFlying()) return + if (sendAbilities) { + bot._client.write('abilities', { + flags: 0, + }) + } + (bot.entity as any).flying = false +} export const onBotCreate = () => { - // let wasSpectatorFlying = false - // bot._client.on('abilities', ({ flags }) => { - // if (flags & 2) { // flying - // toggleFly(true, false) - // } else { - // toggleFly(false, false) - // } - // allowFlying = !!(flags & 4) - // }) - // const gamemodeCheck = () => { - // if (bot.game.gameMode === 'spectator') { - // toggleFly(true, false) - // wasSpectatorFlying = true - // } else if (wasSpectatorFlying) { - // toggleFly(false, false) - // wasSpectatorFlying = false - // } - // } - // bot.on('game', () => { - // gamemodeCheck() - // }) - // bot.on('login', () => { - // gamemodeCheck() - // }) } -// const standardAirborneAcceleration = 0.02 -// const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => { -// // if (bot.game.gameMode !== 'creative' && bot.game.gameMode !== 'spectator') return -// if (!allowFlying) return -// if (bot.setControlState !== patchedSetControlState) { -// originalSetControlState = bot.setControlState -// bot.setControlState = patchedSetControlState -// } - -// if (newState) { -// startFlying(sendAbilities) -// } else { -// endFlying(sendAbilities) -// } -// gameAdditionalState.isFlying = isFlying() -// } -// #endregion +const toggleFly = (newState = !isFlying(), sendAbilities?: boolean) => { + if (!bot.entity.canFly) return + + if (newState) { + startFlying(sendAbilities) + } else { + endFlying(sendAbilities) + } + gameAdditionalState.isFlying = isFlying() +} const selectItem = async () => { const block = bot.blockAtCursor(5) From 847314d50fd69655cf45f5b14a45490c19fcf732 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 15 Mar 2025 02:18:51 +0300 Subject: [PATCH 18/41] hide hand in spectator --- renderer/viewer/lib/basePlayerState.ts | 1 + renderer/viewer/lib/worldrendererThree.ts | 2 +- src/mineflayer/playerState.ts | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index 1bbb690fc..e69d4556b 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -24,6 +24,7 @@ export interface IPlayerState { getItemUsageTicks?(): number // isUsingItem?(): boolean getHeldItem?(isLeftHand: boolean): HandItemBlock | undefined + gameMode?: string username?: string onlineMode?: boolean diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 697717c93..9d241171e 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -241,7 +241,7 @@ export class WorldRendererThree extends WorldRendererCommon { // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera this.renderer.render(this.scene, cam) - if (this.config.showHand && !this.freeFlyMode) { + if (this.config.showHand && !this.freeFlyMode && this.playerState.gameMode !== 'spectator') { this.holdingBlock.render(this.camera, this.renderer, viewer.ambientLight, viewer.directionalLight) this.holdingBlockLeft.render(this.camera, this.renderer, viewer.ambientLight, viewer.directionalLight) } diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 260f8e534..a44ee5ecc 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -29,6 +29,10 @@ export class PlayerStateManager implements IPlayerState { return bot.player?.username ?? '' } + get gameMode () { + return bot.game?.gameMode + } + reactive = proxy({ playerSkin: undefined as string | undefined, }) From 6eb50cde241926e7fc7ca4de752422d454114c20 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 19 Mar 2025 01:25:03 +0300 Subject: [PATCH 19/41] last dont crash --- src/react/IndicatorEffectsProvider.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/react/IndicatorEffectsProvider.tsx b/src/react/IndicatorEffectsProvider.tsx index d1e9fdd0a..6086cc2f3 100644 --- a/src/react/IndicatorEffectsProvider.tsx +++ b/src/react/IndicatorEffectsProvider.tsx @@ -71,10 +71,10 @@ export default () => { if (alreadyWaiting) return state.indicators.chunksLoading = true alreadyWaiting = true - void viewer.waitForChunksToRender().then(() => { - state.indicators.chunksLoading = false - alreadyWaiting = false - }) + // void viewer.waitForChunksToRender().then(() => { + // state.indicators.chunksLoading = false + // alreadyWaiting = false + // }) } viewer.world.renderUpdateEmitter.on('dirty', listener) From 400f5982befca29bd70fad2dde3b32ed21c89e51 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Mar 2025 04:34:55 +0300 Subject: [PATCH 20/41] update server data for index 0 --- src/react/serversStorage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/react/serversStorage.ts b/src/react/serversStorage.ts index b2edb6ea4..42ca7ad83 100644 --- a/src/react/serversStorage.ts +++ b/src/react/serversStorage.ts @@ -44,8 +44,8 @@ export function updateServerConnectionHistory (ip: string, version?: string) { } export const updateLoadedServerData = (callback: (data: StoreServerItem) => StoreServerItem, index = miscUiState.loadedServerIndex) => { - if (!index) index = miscUiState.loadedServerIndex - if (!index) return + if (index === undefined) index = miscUiState.loadedServerIndex + if (index === undefined) return const servers = [...(appStorage.serversList ?? [])] const server = servers[index] From 53640850305b55baeeb3f05347ced5a53458d0fb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Mar 2025 04:37:02 +0300 Subject: [PATCH 21/41] fix possible crash on non existing server data update --- src/react/serversStorage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/react/serversStorage.ts b/src/react/serversStorage.ts index 42ca7ad83..b320e2f35 100644 --- a/src/react/serversStorage.ts +++ b/src/react/serversStorage.ts @@ -49,6 +49,7 @@ export const updateLoadedServerData = (callback: (data: StoreServerItem) => Stor const servers = [...(appStorage.serversList ?? [])] const server = servers[index] + if (!server) return servers[index] = callback(server) setNewServersList(servers) } From f2f1c2538ee9c96bc2f99c9f35dbf774f812ccbe Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Mar 2025 04:53:52 +0300 Subject: [PATCH 22/41] 10x inventory performance --- src/inventoryWindows.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 052fc6645..303e91bec 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -258,12 +258,25 @@ const getItemName = (slot: Item | RenderItem | null) => { return text.join('') } +let lastMappedSots = [] as any[] +const itemToVisualKey = (slot: RenderItem | Item | null) => { + if (!slot) return null + return slot.name + (slot['metadata'] ?? '-') + (slot.nbt ? JSON.stringify(slot.nbt) : '') + (slot['components'] ? JSON.stringify(slot['components']) : '') +} const mapSlots = (slots: Array, isJei = false) => { - return slots.map((slot, i) => { + const newSlots = slots.map((slot, i) => { // todo stateid if (!slot) return + if (!isJei) { + const oldKey = itemToVisualKey(lastMappedSots[i]) + if (oldKey && oldKey === itemToVisualKey(slot)) { + return lastMappedSots[i] + } + } + try { + if (slot.durabilityUsed && slot.maxDurability) slot.durabilityUsed = Math.min(slot.durabilityUsed, slot.maxDurability) const debugIsQuickbar = !isJei && i === bot.inventory.hotbarStart + bot.quickBarSlot const modelName = getItemModelName(slot, { 'minecraft:display_context': 'gui', }) const slotCustomProps = renderSlot({ modelName }, debugIsQuickbar) @@ -281,6 +294,8 @@ const mapSlots = (slots: Array, isJei = false) => { } return slot }) + lastMappedSots = newSlots + return newSlots } export const upInventoryItems = (isInventory: boolean, invWindow = lastWindow) => { @@ -353,7 +368,7 @@ export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { return [...allRecipes ?? [], ...itemDescription ? [ [ 'GenericDescription', - mapSlots([item])[0], + mapSlots([item], true)[0], [], itemDescription ] @@ -471,7 +486,7 @@ const openWindow = (type: string | undefined) => { if (freeSlot === null) return void bot.creative.setInventorySlot(freeSlot, item) } else { - inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item])[0]) + inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0]) } } @@ -600,8 +615,8 @@ const getAllItemRecipes = (itemName: string) => { return results.map(({ result, ingredients, description }) => { return [ 'CraftingTableGuide', - mapSlots([result])[0], - mapSlots(ingredients), + mapSlots([result], true)[0], + mapSlots(ingredients, true), description ] }) From de3eddad898f545a5bf101949eadd9e08ebc7adb Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Mar 2025 05:36:22 +0300 Subject: [PATCH 23/41] a lot of imports update, data cleanup, add thousands errors --- renderer/playground/scenes/main.ts | 1 - renderer/viewer/lib/basePlayerState.ts | 10 +++++++++- renderer/viewer/lib/entity/EntityMesh.ts | 2 +- renderer/viewer/lib/guiRenderer.ts | 15 ++++++++------- renderer/viewer/lib/worldrendererCommon.ts | 2 +- renderer/viewer/lib/worldrendererThree.ts | 22 ++++++++++++++++++++++ renderer/viewer/three/graphicsBackend.ts | 7 ++++--- renderer/viewer/three/panorama.ts | 8 ++++---- src/appViewer.ts | 18 ++++++++++++++++++ src/dayCycle.ts | 6 ++---- src/globals.d.ts | 1 - src/inventoryWindows.ts | 15 +++++++-------- src/mineflayer/playerState.ts | 6 ++---- src/react/HotbarRenderApp.tsx | 2 +- src/resourcesManager.ts | 8 ++++---- src/water.ts | 6 +++--- 16 files changed, 86 insertions(+), 43 deletions(-) diff --git a/renderer/playground/scenes/main.ts b/renderer/playground/scenes/main.ts index 73a36762e..2dedbd062 100644 --- a/renderer/playground/scenes/main.ts +++ b/renderer/playground/scenes/main.ts @@ -173,7 +173,6 @@ class MainScene extends BasePlaygroundScene { canvas.height = size renderer.setSize(size, size) - //@ts-expect-error viewer.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 10) viewer.scene.background = null diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index 1bbb690fc..5f69a0a0a 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -31,12 +31,20 @@ export interface IPlayerState { reactive: { playerSkin: string | undefined + inWater: boolean + backgroundColor: [number, number, number] + ambientLight: number + directionalLight: number } } export class BasePlayerState implements IPlayerState { reactive = proxy({ - playerSkin: undefined + playerSkin: undefined as string | undefined, + inWater: false, + backgroundColor: [0, 0, 0] as [number, number, number], + ambientLight: 0, + directionalLight: 0, }) protected movementState: MovementState = 'NOT_MOVING' protected velocity = new Vec3(0, 0, 0) diff --git a/renderer/viewer/lib/entity/EntityMesh.ts b/renderer/viewer/lib/entity/EntityMesh.ts index 74023794b..e3ea4076f 100644 --- a/renderer/viewer/lib/entity/EntityMesh.ts +++ b/renderer/viewer/lib/entity/EntityMesh.ts @@ -237,7 +237,7 @@ export function getMesh ( if (useBlockTexture) { if (!worldRenderer) throw new Error('worldRenderer is required for block textures') const blockName = texture.slice(6) - const textureInfo = worldRenderer.blocksAtlasParser!.getTextureInfo(blockName) + const textureInfo = worldRenderer.resourcesManager.currentResources!.blocksAtlasParser.getTextureInfo(blockName) if (textureInfo) { textureWidth = blocksTexture!.image.width textureHeight = blocksTexture!.image.height diff --git a/renderer/viewer/lib/guiRenderer.ts b/renderer/viewer/lib/guiRenderer.ts index bd7b2bfdb..2360e104e 100644 --- a/renderer/viewer/lib/guiRenderer.ts +++ b/renderer/viewer/lib/guiRenderer.ts @@ -14,14 +14,14 @@ export const activeGuiAtlas = proxy({ }) export const getNonFullBlocksModels = () => { - let version = viewer.world.texturesVersion ?? 'latest' + let version = appViewer.resourcesManager.currentResources!.version ?? 'latest' if (versionToNumber(version) < versionToNumber('1.13')) version = '1.13' - const itemsDefinitions = viewer.world.itemsDefinitionsStore.data.latest + const itemsDefinitions = appViewer.resourcesManager.itemsDefinitionsStore.data.latest const blockModelsResolved = {} as Record const itemsModelsResolved = {} as Record const fullBlocksWithNonStandardDisplay = [] as string[] const handledItemsWithDefinitions = new Set() - const assetsParser = new AssetsParser(version, getLoadedBlockstatesStore(viewer.world.blockstatesModels), getLoadedModelsStore(viewer.world.blockstatesModels)) + const assetsParser = new AssetsParser(version, getLoadedBlockstatesStore(appViewer.resourcesManager.currentResources!.blockstatesModels), getLoadedModelsStore(appViewer.resourcesManager.currentResources!.blockstatesModels)) const standardGuiDisplay = { 'rotation': [ @@ -53,7 +53,7 @@ export const getNonFullBlocksModels = () => { } for (const [name, definition] of Object.entries(itemsDefinitions)) { - const item = getItemDefinition(viewer.world.itemsDefinitionsStore, { + const item = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, { version, name, properties: { @@ -96,7 +96,7 @@ export const getNonFullBlocksModels = () => { } } - for (const [name, blockstate] of Object.entries(viewer.world.blockstatesModels.blockstates.latest)) { + for (const [name, blockstate] of Object.entries(appViewer.resourcesManager.currentResources!.blockstatesModels.blockstates.latest)) { if (handledItemsWithDefinitions.has(name)) { continue } @@ -119,7 +119,8 @@ export const getNonFullBlocksModels = () => { const RENDER_SIZE = 64 const generateItemsGui = async (models: Record, isItems = false) => { - const img = await getLoadedImage(isItems ? viewer.world.itemsAtlasParser!.latestImage : viewer.world.blocksAtlasParser!.latestImage) + const { currentResources } = appViewer.resourcesManager + const img = await getLoadedImage(isItems ? currentResources!.itemsAtlasParser.latestImage : currentResources!.blocksAtlasParser.latestImage) const canvasTemp = document.createElement('canvas') canvasTemp.width = img.width canvasTemp.height = img.height @@ -128,7 +129,7 @@ const generateItemsGui = async (models: Record, isIt ctx.imageSmoothingEnabled = false ctx.drawImage(img, 0, 0) - const atlasParser = isItems ? viewer.world.itemsAtlasParser! : viewer.world.blocksAtlasParser! + const atlasParser = isItems ? currentResources!.itemsAtlasParser : currentResources!.blocksAtlasParser const textureAtlas = new TextureAtlas( ctx.getImageData(0, 0, img.width, img.height), Object.fromEntries(Object.entries(atlasParser.atlas.latest.textures).map(([key, value]) => { diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index a94599f34..07f54f5c0 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -151,7 +151,7 @@ export abstract class WorldRendererCommon worldRendererConfig: WorldRendererConfig - constructor (private readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) { + constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) { // this.initWorkers(1) // preload script on page load this.snapshotInitialValues() this.worldRendererConfig = displayOptions.inWorldRenderingConfig diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 47e62ff03..57658678b 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -5,6 +5,7 @@ import PrismarineChatLoader from 'prismarine-chat' import * as tweenJs from '@tweenjs/tween.js' import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass, LineSegmentsGeometry, Wireframe, LineMaterial } from 'three-stdlib' import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import { subscribeKey } from 'valtio/utils' import { renderSign } from '../sign-renderer' import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer' import { chunkPos, sectionPos } from './simpleUtils' @@ -91,6 +92,27 @@ export class WorldRendererThree extends WorldRendererCommon { this.camera = new THREE.PerspectiveCamera(75, size.x / size.y, 0.1, 1000) } + watchReactivePlayerState () { + const updateValue = (key: T, callback: (value: typeof this.displayOptions.playerState.reactive[T]) => void) => { + callback(this.displayOptions.playerState.reactive[key]) + subscribeKey(this.displayOptions.playerState.reactive, key, callback) + } + updateValue('backgroundColor', (value) => { + this.changeBackgroundColor(value) + }) + updateValue('inWater', (value) => { + this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, 100) : null + }) + updateValue('ambientLight', (value) => { + if (!value) return + this.ambientLight.intensity = value + }) + updateValue('directionalLight', (value) => { + if (!value) return + this.directionalLight.intensity = value + }) + } + changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) { const holdingBlock = isLeft ? this.holdingBlockLeft : this.holdingBlock if (isAnimationPlaying) { diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 951328a47..1a5e580ad 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -1,7 +1,7 @@ import * as THREE from 'three' import { Vec3 } from 'vec3' import { proxy } from 'valtio' -import { GraphicsBackendLoader, GraphicsBackend, GraphicsInitOptions, DisplayWorldOptions } from '../../../src/appViewer' +import { GraphicsBackendLoader, GraphicsBackend, GraphicsInitOptions, DisplayWorldOptions, WorldReactiveState } from '../../../src/appViewer' import { ProgressReporter } from '../../../src/core/progressReporter' import { WorldRendererThree } from '../lib/worldrendererThree' import { DocumentRenderer } from './documentRenderer' @@ -18,9 +18,10 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO let panoramaRenderer: PanoramaRenderer | null = null let worldRenderer: WorldRendererThree | null = null - const worldState = proxy({ + const worldState: WorldReactiveState = proxy({ chunksLoaded: 0, - chunksTotal: 0 + chunksTotal: 0, + allChunksLoaded: false }) const startPanorama = () => { diff --git a/renderer/viewer/three/panorama.ts b/renderer/viewer/three/panorama.ts index 559e9796e..ecd263336 100644 --- a/renderer/viewer/three/panorama.ts +++ b/renderer/viewer/three/panorama.ts @@ -6,7 +6,7 @@ import * as tweenJs from '@tweenjs/tween.js' import { EntityMesh } from '../lib/entity/EntityMesh' import type { GraphicsInitOptions } from '../../../src/appViewer' import { WorldDataEmitter } from '../lib/worldDataEmitter' -import { ThreeJsWorldRenderer } from '../lib/viewer' +import { WorldRendererThree } from '../lib/worldrendererThree' import { defaultWorldRendererConfig } from '../lib/worldrendererCommon' import { BasePlayerState } from '../lib/basePlayerState' import { DocumentRenderer } from './documentRenderer' @@ -28,7 +28,7 @@ export class PanoramaRenderer { private panoramaGroup: THREE.Object3D | null = null private time = 0 private readonly abortController = new AbortController() - private worldRenderer: ThreeJsWorldRenderer | undefined + private worldRenderer: WorldRendererThree | undefined constructor (private readonly documentRenderer: DocumentRenderer, private readonly options: GraphicsInitOptions, private readonly doWorldBlocksPanorama = false) { this.scene = new THREE.Scene() @@ -153,7 +153,7 @@ export class PanoramaRenderer { // worldView.addWaitTime = 0 if (this.abortController.signal.aborted) return - this.worldRenderer = new ThreeJsWorldRenderer( + this.worldRenderer = new WorldRendererThree( this.documentRenderer.renderer, this.options, { @@ -198,7 +198,7 @@ export class PanoramaRenderer { dispose () { this.scene.clear() - this.worldRenderer?.dispose() + this.worldRenderer?.destroy() this.abortController.abort() } } diff --git a/src/appViewer.ts b/src/appViewer.ts index b804c1406..73ebecd4a 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -17,6 +17,7 @@ import { ResourcesManager } from './resourcesManager' export interface WorldReactiveState { chunksLoaded: number chunksTotal: number + allChunksLoaded: boolean } export interface GraphicsBackendConfig { @@ -76,6 +77,7 @@ export class AppViewer { currentDisplay = null as 'menu' | 'world' | null inWorldRenderingConfig: WorldRendererConfig = defaultWorldRendererConfig lastCamUpdate = 0 + playerState = playerState loadBackend (loader: GraphicsBackendLoader) { if (this.backend) { @@ -156,6 +158,22 @@ export class AppViewer { this.currentDisplay = null // this.queuedDisplay = undefined } + + get utils () { + return { + async waitingForChunks () { + if (this.backend?.worldState.allChunksLoaded) return + return new Promise((resolve) => { + const interval = setInterval(() => { + if (this.backend?.worldState.allChunksLoaded) { + clearInterval(interval) + resolve(true) + } + }, 100) + }) + } + } + } } export const appViewer = new AppViewer() diff --git a/src/dayCycle.ts b/src/dayCycle.ts index f5f55eb24..50e63a21b 100644 --- a/src/dayCycle.ts +++ b/src/dayCycle.ts @@ -4,8 +4,6 @@ import { updateBackground } from './water' export default () => { const timeUpdated = () => { - return - assertDefined(viewer) // 0 morning const dayTotal = 24_000 const evening = 11_500 @@ -38,8 +36,8 @@ export default () => { 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')) { - viewer.ambientLight.intensity = Math.max(int, 0.25) - viewer.directionalLight.intensity = Math.min(int, 0.5) + appViewer.playerState.reactive.ambientLight = Math.max(int, 0.25) + appViewer.playerState.reactive.directionalLight = Math.min(int, 0.5) } } diff --git a/src/globals.d.ts b/src/globals.d.ts index 972f65ceb..6631811dd 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -11,7 +11,6 @@ declare const bot: Omit & { } } declare const __type_bot: typeof bot -declare const viewer: import('renderer/viewer/lib/viewer').Viewer declare const appViewer: import('./appViewer').AppViewer declare const worldView: import('renderer/viewer/lib/worldDataEmitter').WorldDataEmitter | undefined declare const addStatPerSec: (name: string) => void diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 1c445d14f..9511ed374 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -46,7 +46,7 @@ export const onGameLoad = (onLoad) => { version = bot.version const checkIfLoaded = () => { - if (!appViewer.resourcesManager.itemsAtlasParser) return + if (!appViewer.resourcesManager.currentResources?.itemsAtlasParser) return if (!allImagesLoadedState.value) { onLoad?.() } @@ -137,11 +137,10 @@ export const onGameLoad = (onLoad) => { } const getImageSrc = (path): string | HTMLImageElement => { - assertDefined(viewer) switch (path) { case 'gui/container/inventory': return appReplacableResources.latest_gui_container_inventory.content - case 'blocks': return viewer.world.blocksAtlasParser!.latestImage - case 'items': return viewer.world.itemsAtlasParser!.latestImage + case 'blocks': return appViewer.resourcesManager.currentResources!.blocksAtlasParser.latestImage + case 'items': return appViewer.resourcesManager.currentResources!.itemsAtlasParser.latestImage case 'gui/container/dispenser': return appReplacableResources.latest_gui_container_dispenser.content case 'gui/container/furnace': return appReplacableResources.latest_gui_container_furnace.content case 'gui/container/crafting_table': return appReplacableResources.latest_gui_container_crafting_table.content @@ -223,13 +222,13 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal } try { - assertDefined(viewer.world.itemsRenderer) + assertDefined(appViewer.resourcesManager.currentResources?.itemsRenderer) itemTexture = - viewer.world.itemsRenderer.getItemTexture(itemModelName, {}, false, fullBlockModelSupport) - ?? viewer.world.itemsRenderer.getItemTexture('item/missing_texture')! + appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture(itemModelName, {}, false, fullBlockModelSupport) + ?? appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture('item/missing_texture')! } catch (err) { inGameError(`Failed to render item ${itemModelName} (original: ${originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`) - itemTexture = viewer.world.itemsRenderer!.getItemTexture('block/errored')! + itemTexture = appViewer.resourcesManager.currentResources!.itemsRenderer.getItemTexture('block/errored')! } diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 712d0d2b9..c41241bd8 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events' import { Vec3 } from 'vec3' -import { IPlayerState, ItemSpecificContextProperties, MovementState, PlayerStateEvents } from 'renderer/viewer/lib/basePlayerState' +import { BasePlayerState, IPlayerState, ItemSpecificContextProperties, MovementState, PlayerStateEvents } from 'renderer/viewer/lib/basePlayerState' import { HandItemBlock } from 'renderer/viewer/lib/holdingBlock' import TypedEmitter from 'typed-emitter' import { ItemSelector } from 'mc-assets/dist/itemDefinitions' @@ -29,9 +29,7 @@ export class PlayerStateManager implements IPlayerState { return bot.player?.username ?? '' } - reactive = proxy({ - playerSkin: undefined as string | undefined, - }) + reactive = new BasePlayerState().reactive static getInstance (): PlayerStateManager { if (!this.instance) { diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 3f1325c7a..f0fadbfba 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -110,7 +110,7 @@ const HotbarInner = () => { inv.canvas.style.pointerEvents = 'auto' container.current.appendChild(inv.canvas) const upHotbarItems = () => { - if (!appViewer.resourcesManager.itemsAtlasParser || !allImagesLoadedState.value) return + if (!appViewer.resourcesManager.currentResources?.itemsAtlasParser || !allImagesLoadedState.value) return upInventoryItems(true, inv) } diff --git a/src/resourcesManager.ts b/src/resourcesManager.ts index ca67b6baa..0e25ff627 100644 --- a/src/resourcesManager.ts +++ b/src/resourcesManager.ts @@ -30,9 +30,9 @@ export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpec const itemSelector = playerState.getItemSelector({ ...specificProps }) - const modelFromDef = getItemDefinition(viewer.world.itemsDefinitionsStore, { + const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, { name: itemModelName, - version: viewer.world.texturesVersion!, + version: appViewer.resourcesManager.currentResources!.version, properties: itemSelector })?.model const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName @@ -57,8 +57,8 @@ class LoadedResources { armor?: { tileSize: number | undefined, textures: Record } } = {} - itemsRenderer: ItemsRenderer | undefined - worldBlockProvider: WorldBlockProvider | undefined + itemsRenderer: ItemsRenderer + worldBlockProvider: WorldBlockProvider blockstatesModels: any = null version: string diff --git a/src/water.ts b/src/water.ts index 94875e33d..318e7c0d8 100644 --- a/src/water.ts +++ b/src/water.ts @@ -5,14 +5,14 @@ let inWater = false customEvents.on('gameLoaded', () => { const cleanup = () => { - viewer.scene.fog = null + appViewer.playerState.reactive.inWater = false } watchUnloadForCleanup(cleanup) const updateInWater = () => { const waterBr = Object.keys(bot.entity.effects).find((effect: any) => loadedData.effects[effect.id].name === 'water_breathing') if (inWater) { - viewer.scene.fog = new THREE.Fog(0x00_00_ff, 0.1, waterBr ? 100 : 20) // Set the fog color to blue if the bot is in water. + appViewer.playerState.reactive.inWater = true } else { cleanup() } @@ -32,5 +32,5 @@ let sceneBg = { r: 0, g: 0, b: 0 } export const updateBackground = (newSceneBg = sceneBg) => { sceneBg = newSceneBg const color: [number, number, number] = inWater ? [0, 0, 1] : [sceneBg.r, sceneBg.g, sceneBg.b] - viewer.world.changeBackgroundColor(color) + appViewer.playerState.reactive.backgroundColor = color } From c1a7765fcb333992913bb860f36d0b0a637d2738 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Mar 2025 22:10:14 +0300 Subject: [PATCH 24/41] final code cleanup i think --- renderer/viewer/baseGraphicsBackend.ts | 11 +++ renderer/viewer/lib/basePlayerState.ts | 5 ++ renderer/viewer/lib/entities.ts | 65 +++++++++-------- renderer/viewer/lib/holdingBlock.ts | 15 ++-- renderer/viewer/lib/mesher/mesher.ts | 4 +- renderer/viewer/lib/worldrendererCommon.ts | 69 ++++++++++-------- renderer/viewer/lib/worldrendererThree.ts | 48 ++++++++++--- renderer/viewer/three/appShared.ts | 70 ++++++++++++++++++ renderer/viewer/three/graphicsBackend.ts | 38 +++++++--- renderer/viewer/three/panorama.ts | 4 +- renderer/viewer/three/threeJsMethods.ts | 15 ++++ src/appViewer.ts | 39 ++++++---- src/cameraRotationControls.ts | 14 ++-- src/customChannels.ts | 24 +------ src/index.ts | 84 +++------------------- src/inventoryWindows.ts | 3 +- src/mineflayer/items.ts | 22 ++++++ src/mineflayer/maps.ts | 3 +- src/mineflayer/playerState.ts | 4 ++ src/react/DebugOverlay.tsx | 26 +++++-- src/rendererUtils.ts | 3 +- src/resourcesManager.ts | 29 ++------ src/sounds/botSoundSystem.ts | 2 +- src/vr.ts | 35 ++++----- src/watchOptions.ts | 10 +-- 25 files changed, 375 insertions(+), 267 deletions(-) create mode 100644 renderer/viewer/baseGraphicsBackend.ts create mode 100644 renderer/viewer/three/appShared.ts create mode 100644 renderer/viewer/three/threeJsMethods.ts diff --git a/renderer/viewer/baseGraphicsBackend.ts b/renderer/viewer/baseGraphicsBackend.ts new file mode 100644 index 000000000..e0286b153 --- /dev/null +++ b/renderer/viewer/baseGraphicsBackend.ts @@ -0,0 +1,11 @@ +export const getDefaultReactiveState = () => { + return { + world: { + chunksLoaded: 0, + chunksTotal: 0, + allChunksLoaded: true, + }, + renderer: '', + preventEscapeMenu: false + } +} diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index 5f69a0a0a..3739411f1 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -22,6 +22,7 @@ export interface IPlayerState { isFlying(): boolean isSprinting (): boolean getItemUsageTicks?(): number + getPosition(): Vec3 // isUsingItem?(): boolean getHeldItem?(isLeftHand: boolean): HandItemBlock | undefined username?: string @@ -82,6 +83,10 @@ export class BasePlayerState implements IPlayerState { return this.sprinting } + getPosition (): Vec3 { + return new Vec3(0, 0, 0) + } + // For testing purposes setState (state: Partial<{ movementState: MovementState diff --git a/renderer/viewer/lib/entities.ts b/renderer/viewer/lib/entities.ts index f07322199..f41e690f4 100644 --- a/renderer/viewer/lib/entities.ts +++ b/renderer/viewer/lib/entities.ts @@ -210,26 +210,28 @@ export class Entities extends EventEmitter { entities = {} as Record entitiesOptions: { fontFamily?: string - } = {} + } = { + fontFamily: 'mojangles' + } debugMode: string onSkinUpdate: () => void clock = new THREE.Clock() - rendering = true + currentlyRendering = true itemsTexture: THREE.Texture | null = null cachedMapsImages = {} as Record itemFrameMaps = {} as Record>> - getItemUv: undefined | ((item: Record, specificProps: ItemSpecificContextProperties) => { - texture: THREE.Texture; - u: number; - v: number; - su?: number; - sv?: number; - size?: number; - modelName?: string; - } | { - resolvedModel: BlockModel - modelName: string - } | undefined) + // getItemUv: undefined | (() => { + // texture: THREE.Texture; + // u: number; + // v: number; + // su?: number; + // sv?: number; + // size?: number; + // modelName?: string; + // } | { + // resolvedModel: BlockModel + // modelName: string + // } | undefined) get entitiesByName (): Record { const byName: Record = {} @@ -273,7 +275,7 @@ export class Entities extends EventEmitter { } setRendering (rendering: boolean, entity: THREE.Object3D | null = null) { - this.rendering = rendering + this.currentlyRendering = rendering for (const ent of entity ? [entity] : Object.values(this.entities)) { if (rendering) { if (!this.worldRenderer.scene.children.includes(ent)) this.worldRenderer.scene.add(ent) @@ -284,6 +286,11 @@ export class Entities extends EventEmitter { } render () { + const renderEntitiesConfig = this.worldRenderer.worldRendererConfig.renderEntities + if (renderEntitiesConfig !== this.currentlyRendering) { + this.setRendering(renderEntitiesConfig) + } + const dt = this.clock.getDelta() const botPos = this.worldRenderer.viewerPosition const VISIBLE_DISTANCE = 8 * 8 @@ -500,11 +507,11 @@ export class Entities extends EventEmitter { } getItemMesh (item, specificProps: ItemSpecificContextProperties, previousModel?: string) { - const textureUv = this.getItemUv?.(item, specificProps) + const textureUv = this.worldRenderer.getItemRenderData(item, specificProps) if (previousModel && previousModel === textureUv?.modelName) return undefined if (textureUv && 'resolvedModel' in textureUv) { - const mesh = getBlockMeshFromModel(this.worldRenderer.material, textureUv.resolvedModel, textureUv.modelName) + const mesh = getBlockMeshFromModel(this.worldRenderer.material, textureUv.resolvedModel, textureUv.modelName, this.worldRenderer.resourcesManager.currentResources!.worldBlockProvider) let SCALE = 1 if (specificProps['minecraft:display_context'] === 'ground') { SCALE = 0.5 @@ -525,9 +532,11 @@ export class Entities extends EventEmitter { // TODO: Render proper model (especially for blocks) instead of flat texture if (textureUv) { + const textureThree = textureUv.renderInfo?.texture === 'blocks' ? this.worldRenderer.material.map! : this.itemsTexture! // todo use geometry buffer uv instead! - const { u, v, size, su, sv, texture } = textureUv - const itemsTexture = texture.clone() + const { u, v, su, sv, texture } = textureUv + const size = undefined + const itemsTexture = textureThree.clone() itemsTexture.flipY = true const sizeY = (sv ?? size)! const sizeX = (su ?? size)! @@ -707,7 +716,7 @@ export class Entities extends EventEmitter { this.updatePlayerSkin(entity.id, entity.username, entity.uuid, overrides?.texture || stevePngUrl) } this.setDebugMode(this.debugMode, group) - this.setRendering(this.rendering, group) + this.setRendering(this.currentlyRendering, group) } else { mesh = e.children.find(c => c.name === 'mesh') } @@ -716,10 +725,10 @@ export class Entities extends EventEmitter { if (entity.equipment) { this.addItemModel(e, 'left', entity.equipment[0]) this.addItemModel(e, 'right', entity.equipment[1]) - addArmorModel(e, 'feet', entity.equipment[2]) - addArmorModel(e, 'legs', entity.equipment[3], 2) - addArmorModel(e, 'chest', entity.equipment[4]) - addArmorModel(e, 'head', entity.equipment[5]) + addArmorModel(this.worldRenderer, e, 'feet', entity.equipment[2]) + addArmorModel(this.worldRenderer, e, 'legs', entity.equipment[3], 2) + addArmorModel(this.worldRenderer, e, 'chest', entity.equipment[4]) + addArmorModel(this.worldRenderer, e, 'head', entity.equipment[5]) } const meta = getGeneralEntitiesMetadata(entity) @@ -1043,7 +1052,7 @@ function getSpecificEntityMetadata (name return getGeneralEntitiesMetadata(entity) as any } -function addArmorModel (entityMesh: THREE.Object3D, slotType: string, item: Item, layer = 1, overlay = false) { +function addArmorModel (worldRenderer: WorldRendererThree, entityMesh: THREE.Object3D, slotType: string, item: Item, layer = 1, overlay = false) { if (!item) { removeArmorModel(entityMesh, slotType) return @@ -1077,7 +1086,7 @@ function addArmorModel (entityMesh: THREE.Object3D, slotType: string, item: Item if (!texturePath) { // TODO: Support mirroring on certain parts of the model const armorTextureName = `${armorMaterial}_layer_${layer}${overlay ? '_overlay' : ''}` - texturePath = viewer.world.customTextures.armor?.textures[armorTextureName]?.src ?? armorTextures[armorTextureName] + texturePath = worldRenderer.resourcesManager.currentResources!.customTextures.armor?.textures[armorTextureName]?.src ?? armorTextures[armorTextureName] } if (!texturePath || !armorModel[slotType]) { removeArmorModel(entityMesh, slotType) @@ -1098,7 +1107,7 @@ function addArmorModel (entityMesh: THREE.Object3D, slotType: string, item: Item material.map = texture }) } else { - mesh = getMesh(viewer.world, texturePath, armorModel[slotType]) + mesh = getMesh(worldRenderer, texturePath, armorModel[slotType]) mesh.name = meshName material = mesh.material if (!isPlayerHead) { @@ -1115,7 +1124,7 @@ function addArmorModel (entityMesh: THREE.Object3D, slotType: string, item: Item } else { material.color.setHex(0xB5_6D_51) // default brown color } - addArmorModel(entityMesh, slotType, item, layer, true) + addArmorModel(worldRenderer, entityMesh, slotType, item, layer, true) } else { material.color.setHex(0xFF_FF_FF) } diff --git a/renderer/viewer/lib/holdingBlock.ts b/renderer/viewer/lib/holdingBlock.ts index 084953fdc..8b75b02a9 100644 --- a/renderer/viewer/lib/holdingBlock.ts +++ b/renderer/viewer/lib/holdingBlock.ts @@ -1,6 +1,6 @@ import * as THREE from 'three' import * as tweenJs from '@tweenjs/tween.js' -import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import { BlockModel } from 'mc-assets' import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from './mesher/standaloneRenderer' import { getMyHand } from './hand' @@ -10,6 +10,7 @@ import { SmoothSwitcher } from './smoothSwitcher' import { watchProperty } from './utils/proxy' import { disposeObject } from './threeJsUtils' import { WorldRendererConfig } from './worldrendererCommon' +import { WorldRendererThree } from './worldrendererThree' export type HandItemBlock = { name? @@ -114,14 +115,17 @@ export default class HoldingBlock { offHandModeLegacy = false swingAnimator: HandSwingAnimator | undefined + playerState: IPlayerState + config: WorldRendererConfig - constructor (public playerState: IPlayerState, public config: WorldRendererConfig, public offHand = false) { + constructor (public worldRenderer: WorldRendererThree, public offHand = false) { this.initCameraGroup() - + this.playerState = worldRenderer.displayOptions.playerState this.playerState.events.on('heldItemChanged', (_, isOffHand) => { if (this.offHand !== isOffHand) return this.updateItem() }) + this.config = worldRenderer.displayOptions.inWorldRenderingConfig this.offHandDisplay = this.offHand // this.offHandDisplay = true @@ -327,7 +331,7 @@ export default class HoldingBlock { let blockInner: THREE.Object3D | undefined if (handItem.type === 'item' || handItem.type === 'block') { - const result = viewer.entities.getItemMesh({ + const result = this.worldRenderer.entities.getItemMesh({ ...handItem.fullItem, itemId: handItem.id, }, { @@ -901,8 +905,7 @@ class HandSwingAnimator { } } -export const getBlockMeshFromModel = (material: THREE.Material, model: BlockModel, name: string) => { - const blockProvider = worldBlockProvider(viewer.world.blockstatesModels, viewer.world.blocksAtlasParser!.atlas, 'latest') +export const getBlockMeshFromModel = (material: THREE.Material, model: BlockModel, name: string, blockProvider: WorldBlockProvider) => { const worldRenderModel = blockProvider.transformModel(model, { name, properties: {} diff --git a/renderer/viewer/lib/mesher/mesher.ts b/renderer/viewer/lib/mesher/mesher.ts index 9afcfeb3d..42432a6d0 100644 --- a/renderer/viewer/lib/mesher/mesher.ts +++ b/renderer/viewer/lib/mesher/mesher.ts @@ -121,7 +121,9 @@ const handleMessage = data => { } case 'blockUpdate': { const loc = new Vec3(data.pos.x, data.pos.y, data.pos.z).floored() - world.setBlockStateId(loc, data.stateId) + if (data.stateId !== undefined && data.stateId !== null) { + world.setBlockStateId(loc, data.stateId) + } const chunkKey = `${Math.floor(loc.x / 16) * 16},${Math.floor(loc.z / 16) * 16}` if (data.customBlockModels) { diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 07f54f5c0..a4dac4744 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -9,20 +9,19 @@ import { LineMaterial } from 'three-stdlib' import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import { generateSpiralMatrix } from 'flying-squid/dist/utils' +import { proxy } from 'valtio' import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs' import { toMajorVersion } from '../../../src/utils' import { ResourcesManager } from '../../../src/resourcesManager' import { DisplayWorldOptions } from '../../../src/appViewer' import { buildCleanupDecorator } from './cleanupDecorator' -import { defaultMesherConfig, HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo } from './mesher/shared' +import { defaultMesherConfig, HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey } from './mesher/shared' import { chunkPos } from './simpleUtils' import { updateStatText } from './ui/newStats' import { generateGuiAtlas } from './guiRenderer' import { WorldDataEmitter } from './worldDataEmitter' import { WorldRendererThree } from './worldrendererThree' -import { SoundSystem } from './threeJsSound' - -const appViewer = undefined +import { SoundSystem, ThreeJsSound } from './threeJsSound' function mod (x, n) { return ((x % n) + n) % n @@ -43,16 +42,14 @@ export const defaultWorldRendererConfig = { smoothLighting: true, enableLighting: true, starfield: true, - addChunksBatchWaitTime: 200 + addChunksBatchWaitTime: 200, + vrSupport: true, + renderEntities: true, + fov: 75 } export type WorldRendererConfig = typeof defaultWorldRendererConfig -type CustomTexturesData = { - tileSize: number | undefined - textures: Record -} - export abstract class WorldRendererCommon { // todo @worldCleanup() @@ -62,8 +59,6 @@ export abstract class WorldRendererCommon displayStats = true @worldCleanup() worldSizeParams = { minY: 0, worldHeight: 256 } - // todo need to cleanup - material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) cameraRoll = 0 @worldCleanup() @@ -96,7 +91,6 @@ export abstract class WorldRendererCommon }> customTexturesDataUrl = undefined as string | undefined @worldCleanup() - currentTextureImage = undefined as any workers: any[] = [] @worldCleanup() viewerPosition?: Vec3 @@ -154,7 +148,7 @@ export abstract class WorldRendererCommon constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) { // this.initWorkers(1) // preload script on page load this.snapshotInitialValues() - this.worldRendererConfig = displayOptions.inWorldRenderingConfig + this.worldRendererConfig = proxy(displayOptions.inWorldRenderingConfig) this.renderUpdateEmitter.on('update', () => { const loadedChunks = Object.keys(this.finishedChunks).length @@ -178,6 +172,32 @@ export abstract class WorldRendererCommon snapshotInitialValues () { } + wasChunkSentToWorker (chunkKey: string) { + return this.loadedChunks[chunkKey] + } + + updateCustomBlock (chunkKey: string, blockPos: string, model: string) { + this.protocolCustomBlocks.set(chunkKey, { + ...this.protocolCustomBlocks.get(chunkKey), + [blockPos]: model + }) + if (this.wasChunkSentToWorker(chunkKey)) { + const [x, y, z] = blockPos.split(',').map(Number) + this.setBlockStateId(new Vec3(x, y, z), undefined) + } + } + + async getBlockInfo (blockPos: { x: number, y: number, z: number }, stateId: number) { + const chunkKey = `${Math.floor(blockPos.x / 16) * 16},${Math.floor(blockPos.z / 16) * 16}` + const customBlockName = this.protocolCustomBlocks.get(chunkKey)?.[`${blockPos.x},${blockPos.y},${blockPos.z}`] + const cacheKey = getBlockAssetsCacheKey(stateId, customBlockName) + const modelInfo = this.blockStateModelInfo.get(cacheKey) + return { + customBlockName, + modelInfo + } + } + initWorkers (numWorkers = this.worldRendererConfig.mesherWorkers) { // init workers for (let i = 0; i < numWorkers + 1; i++) { @@ -323,7 +343,6 @@ export abstract class WorldRendererCommon worker.terminate() } this.workers = [] - this.currentTextureImage = undefined } // new game load happens here @@ -371,13 +390,8 @@ export abstract class WorldRendererCommon async updateAssetsData () { const resources = this.resourcesManager.currentResources! - const texture = await new THREE.TextureLoader().loadAsync(resources.blocksAtlasParser.latestImage) - texture.magFilter = THREE.NearestFilter - texture.minFilter = THREE.NearestFilter - texture.flipY = false - this.material.map = texture - this.currentTextureImage = this.material.map.image - this.mesherConfig.textureSize = this.material.map.image.width + + this.mesherConfig.textureSize = this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width if (this.workers.length === 0) throw new Error('workers not initialized yet') for (const [i, worker] of this.workers.entries()) { @@ -476,7 +490,7 @@ export abstract class WorldRendererCommon } } - setBlockStateId (pos: Vec3, stateId: number) { + setBlockStateId (pos: Vec3, stateId: number | undefined) { const set = async () => { const sectionX = Math.floor(pos.x / 16) * 16 const sectionZ = Math.floor(pos.z / 16) * 16 @@ -594,7 +608,7 @@ export abstract class WorldRendererCommon worldEmitter.emit('listening') } - setBlockStateIdInner (pos: Vec3, stateId: number) { + setBlockStateIdInner (pos: Vec3, stateId: number | undefined) { const needAoRecalculation = true const chunkKey = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.z / 16) * 16}` const blockPosKey = `${pos.x},${pos.y},${pos.z}` @@ -745,15 +759,10 @@ export abstract class WorldRendererCommon this.soundSystem = undefined } - // if not necessary as renderer is destroyed anyway - if (this.material.map) { - this.material.map.dispose() - } - this.material.dispose() - this.active = false this.renderUpdateEmitter.removeAllListeners() + this.displayOptions.worldView.removeAllListeners() // todo } abstract setHighlightCursorBlock (block: typeof this.cursorBlock, shapePositions?: Array<{ position; width; height; depth }>): void diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 57658678b..803fa3cc6 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -3,23 +3,25 @@ import { Vec3 } from 'vec3' import nbt from 'prismarine-nbt' import PrismarineChatLoader from 'prismarine-chat' import * as tweenJs from '@tweenjs/tween.js' -import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass, LineSegmentsGeometry, Wireframe, LineMaterial } from 'three-stdlib' -import worldBlockProvider from 'mc-assets/dist/worldBlockProvider' +import { LineSegmentsGeometry, Wireframe } from 'three-stdlib' import { subscribeKey } from 'valtio/utils' import { renderSign } from '../sign-renderer' -import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer' +import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState } from '../../../src/appViewer' +import { initVR } from '../../../src/vr' +import { getItemUv } from '../three/appShared' import { chunkPos, sectionPos } from './simpleUtils' -import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' +import { WorldRendererCommon } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' -import HoldingBlock, { HandItemBlock } from './holdingBlock' +import HoldingBlock from './holdingBlock' import { addNewStat } from './ui/newStats' import { MesherGeometryOutput } from './mesher/shared' -import { IPlayerState } from './basePlayerState' +import { ItemSpecificContextProperties } from './basePlayerState' import { getMesh } from './entity/EntityMesh' import { armorModel } from './entity/armorModels' import { getMyHand } from './hand' import { setBlockPosition } from './mesher/standaloneRenderer' import { Entities } from './entities' +import { ThreeJsSound } from './threeJsSound' export class WorldRendererThree extends WorldRendererCommon { interactionLines: null | { blockPos; mesh } = null @@ -38,6 +40,7 @@ export class WorldRendererThree extends WorldRendererCommon { directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5) entities = new Entities(this) cameraObjectOverride?: THREE.Object3D // for xr + material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -47,14 +50,13 @@ export class WorldRendererThree extends WorldRendererCommon { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).blocksCount, 0) } - constructor (public renderer: THREE.WebGLRenderer, public initOptions: GraphicsInitOptions, public displayOptions: DisplayWorldOptions, public version: string) { + constructor (public renderer: THREE.WebGLRenderer, public initOptions: GraphicsInitOptions, public displayOptions: DisplayWorldOptions, public version: string, public reactiveState: RendererReactiveState) { if (!initOptions.resourcesManager) throw new Error('resourcesManager is required') super(initOptions.resourcesManager, displayOptions, version) - const { playerState, inWorldRenderingConfig: config } = displayOptions this.starField = new StarField(this.scene) - this.holdingBlock = new HoldingBlock(playerState, config) - this.holdingBlockLeft = new HoldingBlock(playerState, config, true) + this.holdingBlock = new HoldingBlock(this) + this.holdingBlockLeft = new HoldingBlock(this, true) initOptions.resourcesManager.on('assetsTexturesUpdated', () => { this.holdingBlock.ready = true @@ -63,9 +65,13 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlockLeft.updateItem() }) + this.soundSystem = new ThreeJsSound(this) + this.addDebugOverlay() this.resetScene() + this.watchReactivePlayerState() this.init() + void initVR(this) } updateEntity (e) { @@ -122,6 +128,16 @@ export class WorldRendererThree extends WorldRendererCommon { } } + async updateAssetsData (): Promise { + const resources = this.resourcesManager.currentResources! + const texture = await new THREE.TextureLoader().loadAsync(resources.blocksAtlasParser.latestImage) + texture.magFilter = THREE.NearestFilter + texture.minFilter = THREE.NearestFilter + texture.flipY = false + this.material.map = texture + await super.updateAssetsData() + } + changeBackgroundColor (color: [number, number, number]): void { this.scene.background = new THREE.Color(color[0], color[1], color[2]) } @@ -137,6 +153,10 @@ export class WorldRendererThree extends WorldRendererCommon { } } + getItemRenderData (item: Record, specificProps: ItemSpecificContextProperties) { + return getItemUv(item, specificProps, this.resourcesManager) + } + async demoModel () { //@ts-expect-error const pos = cursorBlockRel(0, 1, 0).position @@ -329,8 +349,10 @@ export class WorldRendererThree extends WorldRendererCommon { } render (sizeChanged = false) { - if (sizeChanged) { + const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov + if (sizeOrFovChanged) { this.camera.aspect = window.innerWidth / window.innerHeight + this.camera.fov = this.displayOptions.inWorldRenderingConfig.fov this.camera.updateProjectionMatrix() } // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style @@ -567,6 +589,10 @@ export class WorldRendererThree extends WorldRendererCommon { const finalQuat = camQuat.multiply(rollQuat) this.camera.setRotationFromQuaternion(finalQuat) } + + destroy (): void { + super.destroy() + } } class StarField { diff --git a/renderer/viewer/three/appShared.ts b/renderer/viewer/three/appShared.ts new file mode 100644 index 000000000..b41d32dbf --- /dev/null +++ b/renderer/viewer/three/appShared.ts @@ -0,0 +1,70 @@ +import { BlockModel } from 'mc-assets/dist/types' +import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState' +import { renderSlot } from '../../../src/inventoryWindows' +import { GeneralInputItem, getItemModelName } from '../../../src/mineflayer/items' +import { ResourcesManager } from '../../../src/resourcesManager' + +export const getItemUv = (item: Record, specificProps: ItemSpecificContextProperties, resourcesManager: ResourcesManager): { + u: number + v: number + su: number + sv: number + renderInfo?: ReturnType + texture: HTMLImageElement + modelName: string +} | { + resolvedModel: BlockModel + modelName: string +} => { + const resources = resourcesManager.currentResources + if (!resources) throw new Error('Resources not loaded') + const idOrName = item.itemId ?? item.blockId ?? item.name + try { + const name = typeof idOrName === 'number' ? loadedData.items[idOrName]?.name : idOrName + if (!name) throw new Error(`Item not found: ${idOrName}`) + + const model = getItemModelName({ + ...item, + name, + } as GeneralInputItem, specificProps) + + const renderInfo = renderSlot({ + modelName: model, + }, false, true) + + if (!renderInfo) throw new Error(`Failed to get render info for item ${name}`) + + const img = renderInfo.texture === 'blocks' ? resources.blocksAtlasImage : resources.itemsAtlasImage + + if (renderInfo.blockData) { + return { + resolvedModel: renderInfo.blockData.resolvedModel, + modelName: renderInfo.modelName! + } + } + if (renderInfo.slice) { + // Get slice coordinates from either block or item texture + const [x, y, w, h] = renderInfo.slice + const [u, v, su, sv] = [x / img.width, y / img.height, (w / img.width), (h / img.height)] + return { + u, v, su, sv, + renderInfo, + texture: img, + modelName: renderInfo.modelName! + } + } + + throw new Error(`Invalid render info for item ${name}`) + } catch (err) { + reportError?.(err) + // Return default UV coordinates for missing texture + return { + u: 0, + v: 0, + su: 16 / resources.blocksAtlasImage.width, + sv: 16 / resources.blocksAtlasImage.width, + texture: resources.blocksAtlasImage, + modelName: 'missing' + } + } +} diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 1a5e580ad..a4d968b8d 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -1,7 +1,7 @@ import * as THREE from 'three' import { Vec3 } from 'vec3' import { proxy } from 'valtio' -import { GraphicsBackendLoader, GraphicsBackend, GraphicsInitOptions, DisplayWorldOptions, WorldReactiveState } from '../../../src/appViewer' +import { GraphicsBackendLoader, GraphicsBackend, GraphicsInitOptions, DisplayWorldOptions, RendererReactiveState } from '../../../src/appViewer' import { ProgressReporter } from '../../../src/core/progressReporter' import { WorldRendererThree } from '../lib/worldrendererThree' import { DocumentRenderer } from './documentRenderer' @@ -10,6 +10,16 @@ import { PanoramaRenderer } from './panorama' // https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 THREE.ColorManagement.enabled = false +const getBackendMethods = (worldRenderer: WorldRendererThree) => { + return { + updateMap: worldRenderer.entities.updateMap.bind(worldRenderer.entities), + updateCustomBlock: worldRenderer.updateCustomBlock.bind(worldRenderer), + getBlockInfo: worldRenderer.getBlockInfo.bind(worldRenderer), + } +} + +export type ThreeJsBackendMethods = ReturnType + const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitOptions) => { // Private state const documentRenderer = new DocumentRenderer(initOptions) @@ -18,10 +28,14 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO let panoramaRenderer: PanoramaRenderer | null = null let worldRenderer: WorldRendererThree | null = null - const worldState: WorldReactiveState = proxy({ - chunksLoaded: 0, - chunksTotal: 0, - allChunksLoaded: false + const reactiveState: RendererReactiveState = proxy({ + world: { + chunksLoaded: 0, + chunksTotal: 0, + allChunksLoaded: false, + }, + renderer: WorldRendererThree.getRendererInfo(documentRenderer.renderer) ?? '...', + preventEscapeMenu: false }) const startPanorama = () => { @@ -43,7 +57,7 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO panoramaRenderer.dispose() panoramaRenderer = null } - worldRenderer = new WorldRendererThree(documentRenderer.renderer, initOptions, displayOptions, version) + worldRenderer = new WorldRendererThree(documentRenderer.renderer, initOptions, displayOptions, version, reactiveState) documentRenderer.render = (sizeChanged: boolean) => { worldRenderer?.render(sizeChanged) } @@ -66,11 +80,10 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO } } - const renderer = WorldRendererThree.getRendererInfo(documentRenderer.renderer) ?? '...' - // viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch) - // Public interface const backend: GraphicsBackend = { + //@ts-expect-error mark as three.js renderer + __isThreeJsRenderer: true, NAME: `three.js ${THREE.REVISION}`, startPanorama, prepareResources, @@ -79,7 +92,6 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO setRendering (rendering) { documentRenderer.setPaused(!rendering) }, - getRenderer: () => renderer, getDebugOverlay: () => ({ }), updateCamera (pos: Vec3 | null, yaw: number, pitch: number) { @@ -88,9 +100,13 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO setRoll (roll: number) { worldRenderer?.setCameraRoll(roll) }, - worldState, + reactiveState, get soundSystem () { return worldRenderer?.soundSystem + }, + get backendMethods () { + if (!worldRenderer) return undefined + return getBackendMethods(worldRenderer) } } diff --git a/renderer/viewer/three/panorama.ts b/renderer/viewer/three/panorama.ts index ecd263336..f3e3e5033 100644 --- a/renderer/viewer/three/panorama.ts +++ b/renderer/viewer/three/panorama.ts @@ -9,6 +9,7 @@ import { WorldDataEmitter } from '../lib/worldDataEmitter' import { WorldRendererThree } from '../lib/worldrendererThree' import { defaultWorldRendererConfig } from '../lib/worldrendererCommon' import { BasePlayerState } from '../lib/basePlayerState' +import { getDefaultReactiveState } from '../baseGraphicsBackend' import { DocumentRenderer } from './documentRenderer' const panoramaFiles = [ @@ -161,7 +162,8 @@ export class PanoramaRenderer { inWorldRenderingConfig: defaultWorldRendererConfig, playerState: new BasePlayerState() }, - version + version, + getDefaultReactiveState() ) this.scene = this.worldRenderer.scene void worldView.init(initPos) diff --git a/renderer/viewer/three/threeJsMethods.ts b/renderer/viewer/three/threeJsMethods.ts new file mode 100644 index 000000000..0dc7bf79d --- /dev/null +++ b/renderer/viewer/three/threeJsMethods.ts @@ -0,0 +1,15 @@ +import type { GraphicsBackend } from '../../../src/appViewer' +import type { ThreeJsBackendMethods } from './graphicsBackend' + +export function getThreeJsRendererMethods (): ThreeJsBackendMethods | undefined { + const renderer = appViewer.backend + if (!renderer?.['__isThreeJsRenderer'] || !renderer.backendMethods) return + return new Proxy(renderer.backendMethods, { + get (target, prop) { + return async (...args) => { + const result = await (target[prop as any] as any)(...args) + return result + } + } + }) as ThreeJsBackendMethods +} diff --git a/src/appViewer.ts b/src/appViewer.ts index 73ebecd4a..df2976aa8 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -1,23 +1,26 @@ -import { EventEmitter } from 'events' import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' -import { BasePlayerState, IPlayerState } from 'renderer/viewer/lib/basePlayerState' +import { IPlayerState } from 'renderer/viewer/lib/basePlayerState' import { subscribeKey } from 'valtio/utils' import { defaultWorldRendererConfig, WorldRendererConfig } from 'renderer/viewer/lib/worldrendererCommon' import { Vec3 } from 'vec3' -import { getSyncWorld } from 'renderer/playground/shared' import { SoundSystem } from 'renderer/viewer/lib/threeJsSound' -import { playerState, PlayerStateManager } from './mineflayer/playerState' -import { createNotificationProgressReporter, createNullProgressReporter, ProgressReporter } from './core/progressReporter' +import { proxy } from 'valtio' +import { playerState } from './mineflayer/playerState' +import { createNotificationProgressReporter, ProgressReporter } from './core/progressReporter' import { setLoadingScreenStatus } from './appStatus' import { activeModalStack, miscUiState } from './globalState' import { options } from './optionsStorage' -import { loadMinecraftData } from './connect' import { ResourcesManager } from './resourcesManager' +import { watchOptionsAfterWorldViewInit } from './watchOptions' -export interface WorldReactiveState { - chunksLoaded: number - chunksTotal: number - allChunksLoaded: boolean +export interface RendererReactiveState { + world: { + chunksLoaded: number + chunksTotal: number + allChunksLoaded: boolean + } + renderer: string + preventEscapeMenu: boolean } export interface GraphicsBackendConfig { @@ -46,6 +49,7 @@ export interface DisplayWorldOptions { export type GraphicsBackendLoader = (options: GraphicsInitOptions) => GraphicsBackend +// no sync methods export interface GraphicsBackend { NAME: string startPanorama: () => void @@ -53,12 +57,13 @@ export interface GraphicsBackend { startWorld: (options: DisplayWorldOptions) => void disconnect: () => void setRendering: (rendering: boolean) => void - getRenderer: () => string getDebugOverlay: () => Record updateCamera: (pos: Vec3 | null, yaw: number, pitch: number) => void setRoll: (roll: number) => void - worldState: WorldReactiveState + reactiveState: RendererReactiveState soundSystem: SoundSystem | undefined + + backendMethods: Record | undefined } export class AppViewer { @@ -75,7 +80,7 @@ export class AppViewer { args: any[] } currentDisplay = null as 'menu' | 'world' | null - inWorldRenderingConfig: WorldRendererConfig = defaultWorldRendererConfig + inWorldRenderingConfig: WorldRendererConfig = proxy(defaultWorldRendererConfig) lastCamUpdate = 0 playerState = playerState @@ -103,6 +108,9 @@ export class AppViewer { if (this.queuedDisplay) { const { method, args } = this.queuedDisplay this.backend[method](...args) + if (method === 'startWorld') { + void this.worldView.init(args[0].playerState.getPosition()) + } } } @@ -128,11 +136,13 @@ export class AppViewer { } } - startWorld (world, renderDistance: number, startPosition: Vec3, playerStateSend: IPlayerState = playerState) { + startWorld (world, renderDistance: number, playerStateSend: IPlayerState = playerState) { if (this.currentDisplay === 'world') throw new Error('World already started') this.currentDisplay = 'world' + const startPosition = playerStateSend.getPosition() this.worldView = new WorldDataEmitter(world, renderDistance, startPosition) window.worldView = this.worldView + watchOptionsAfterWorldViewInit(this.worldView) const displayWorldOptions: DisplayWorldOptions = { worldView: this.worldView, @@ -141,6 +151,7 @@ export class AppViewer { } if (this.backend) { this.backend.startWorld(displayWorldOptions) + void this.worldView.init(startPosition) } this.queuedDisplay = { method: 'startWorld', args: [displayWorldOptions] } } diff --git a/src/cameraRotationControls.ts b/src/cameraRotationControls.ts index cf220d5ff..27fa35957 100644 --- a/src/cameraRotationControls.ts +++ b/src/cameraRotationControls.ts @@ -39,12 +39,12 @@ export const moveCameraRawHandler = ({ x, y }: { x: number; y: number }) => { appViewer.lastCamUpdate = Date.now() - if (viewer.world.freeFlyMode) { - // Update freeFlyState directly - viewer.world.freeFlyState.yaw = (viewer.world.freeFlyState.yaw - x) % (2 * Math.PI) - viewer.world.freeFlyState.pitch = Math.max(minPitch, Math.min(maxPitch, viewer.world.freeFlyState.pitch - y)) - return - } + // if (viewer.world.freeFlyMode) { + // // Update freeFlyState directly + // viewer.world.freeFlyState.yaw = (viewer.world.freeFlyState.yaw - x) % (2 * Math.PI) + // viewer.world.freeFlyState.pitch = Math.max(minPitch, Math.min(maxPitch, viewer.world.freeFlyState.pitch - y)) + // return + // } if (!bot?.entity) return const pitch = bot.entity.pitch - y @@ -76,7 +76,7 @@ function pointerLockChangeCallback () { if (notificationProxy.id === 'pointerlockchange') { hideNotification() } - if (viewer.renderer.xr.isPresenting) return // todo + if (appViewer.backend?.reactiveState.preventEscapeMenu) return if (!pointerLock.hasPointerLock && activeModalStack.length === 0 && miscUiState.gameLoaded) { showModal({ reactType: 'pause-screen' }) } diff --git a/src/customChannels.ts b/src/customChannels.ts index ff0f8a32d..19a17b513 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -1,4 +1,5 @@ import { Vec3 } from 'vec3' +import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { options } from './optionsStorage' customEvents.on('mineflayerBotCreated', async () => { @@ -50,28 +51,7 @@ const registerBlockModelsChannel = () => { const chunkKey = `${chunkX},${chunkZ}` const blockPosKey = `${x},${y},${z}` - const chunkModels = viewer.world.protocolCustomBlocks.get(chunkKey) || {} - - if (model) { - chunkModels[blockPosKey] = model - } else { - delete chunkModels[blockPosKey] - } - - if (Object.keys(chunkModels).length > 0) { - viewer.world.protocolCustomBlocks.set(chunkKey, chunkModels) - } else { - viewer.world.protocolCustomBlocks.delete(chunkKey) - } - - // Trigger update - if (worldView) { - const block = worldView.world.getBlock(new Vec3(x, y, z)) - if (block) { - worldView.world.setBlockStateId(new Vec3(x, y, z), block.stateId) - } - } - + getThreeJsRendererMethods()?.updateCustomBlock(chunkKey, blockPosKey, model) }) console.debug(`registered custom channel ${CHANNEL_NAME} channel`) diff --git a/src/index.ts b/src/index.ts index 932ac6521..fea243efe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,8 +14,7 @@ import './mineflayer/java-tester/index' import './external' import './appConfig' import { getServerInfo } from './mineflayer/mc-protocol' -import { onGameLoad, renderSlot } from './inventoryWindows' -import { GeneralInputItem } from './mineflayer/items' +import { onGameLoad } from './inventoryWindows' import initCollisionShapes from './getCollisionInteractionShapes' import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth' import microsoftAuthflow from './microsoftAuthflow' @@ -23,7 +22,7 @@ import { Duplex } from 'stream' import './scaleInterface' -import { options, watchValue } from './optionsStorage' +import { options } from './optionsStorage' import './reactUi' import { lockUrl, onBotCreate } from './controls' import './dragndrop' @@ -34,15 +33,12 @@ import downloadAndOpenFile from './downloadAndOpenFile' import fs from 'fs' import net from 'net' import mineflayer from 'mineflayer' -import pathfinder from 'mineflayer-pathfinder' import * as THREE from 'three' -import MinecraftData from 'minecraft-data' import debug from 'debug' import { defaultsDeep } from 'lodash-es' import initializePacketsReplay from './packetsReplay/packetsReplayLegacy' -import { initVR } from './vr' import { activeModalStack, activeModalStacks, @@ -62,7 +58,7 @@ import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalS import defaultServerOptions from './defaultLocalServerOptions' import dayCycle from './dayCycle' -import { onAppLoad, resourcepackReload, resourcePackState } from './resourcePack' +import { onAppLoad, resourcePackState } from './resourcePack' import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer' import CustomChannelClient from './customClient' import { registerServiceWorker } from './serviceWorker' @@ -98,10 +94,8 @@ import mouse from './mineflayer/plugins/mouse' import { startLocalReplayServer } from './packetsReplay/replayPackets' import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording' import { createConsoleLogProgressReporter, createFullScreenProgressReporter } from './core/progressReporter' -import { getItemModelName } from './resourcesManager' import { appViewer } from './appViewer' import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend' -import { importLargeData } from '../generated/large-data-aliases' import { subscribeKey } from 'valtio/utils' window.debug = debug @@ -137,61 +131,6 @@ if (process.env.SINGLE_FILE_BUILD_MODE) { loadBackend() } -// todo unify -// viewer.entities.getItemUv = (item, specificProps) => { -// const idOrName = item.itemId ?? item.blockId ?? item.name -// try { -// const name = typeof idOrName === 'number' ? loadedData.items[idOrName]?.name : idOrName -// if (!name) throw new Error(`Item not found: ${idOrName}`) - -// const model = getItemModelName({ -// ...item, -// name, -// } as GeneralInputItem, specificProps) - -// const renderInfo = renderSlot({ -// modelName: model, -// }, false, true) - -// if (!renderInfo) throw new Error(`Failed to get render info for item ${name}`) - -// const textureThree = renderInfo.texture === 'blocks' ? viewer.world.material.map! : viewer.entities.itemsTexture! -// const img = textureThree.image - -// if (renderInfo.blockData) { -// return { -// resolvedModel: renderInfo.blockData.resolvedModel, -// modelName: renderInfo.modelName! -// } -// } -// if (renderInfo.slice) { -// // Get slice coordinates from either block or item texture -// const [x, y, w, h] = renderInfo.slice -// const [u, v, su, sv] = [x / img.width, y / img.height, (w / img.width), (h / img.height)] -// return { -// u, v, su, sv, -// texture: textureThree, -// modelName: renderInfo.modelName -// } -// } - -// throw new Error(`Invalid render info for item ${name}`) -// } catch (err) { -// reportError?.(err) -// // Return default UV coordinates for missing texture -// return { -// u: 0, -// v: 0, -// su: 16 / viewer.world.material.map!.image.width, -// sv: 16 / viewer.world.material.map!.image.width, -// texture: viewer.world.material.map! -// } -// } -// } - -// viewer.entities.entitiesOptions = { -// fontFamily: 'mojangles' -// } watchOptionsAfterViewerInit() function hideCurrentScreens () { @@ -750,15 +689,10 @@ export async function connect (connectOptions: ConnectOptions) { console.log('bot spawned - starting viewer') - appViewer.startWorld(bot.world, renderDistance, bot.entity.position) - watchOptionsAfterWorldViewInit() - - // void initVR() - initMotionTracking() - + appViewer.startWorld(bot.world, renderDistance) appViewer.worldView.listenToBot(bot) - void appViewer.worldView.init(bot.entity.position) + initMotionTracking() dayCycle() // Bot position callback @@ -780,7 +714,7 @@ export async function connect (connectOptions: ConnectOptions) { const waitForChunks = async () => { if (appQueryParams.sp === '1') return //todo const waitForChunks = options.waitForChunksRender === 'sp-only' ? !!singleplayer : options.waitForChunksRender - if (viewer.world.allChunksFinished || !waitForChunks) { + if (!appViewer.backend || appViewer.backend.reactiveState.world.allChunksLoaded || !waitForChunks) { return } @@ -790,13 +724,13 @@ export async function connect (connectOptions: ConnectOptions) { async () => { await new Promise(resolve => { let wasFinished = false - void viewer.world.renderUpdateEmitter.on('update', () => { + const unsub = subscribe(appViewer.backend!.reactiveState, () => { if (wasFinished) return - if (viewer.world.allChunksFinished) { + if (appViewer.backend!.reactiveState.world.allChunksLoaded) { wasFinished = true resolve() } else { - const perc = Math.round(Object.keys(viewer.world.finishedChunks).length / viewer.world.chunksLength * 100) + const perc = Math.round(appViewer.backend!.reactiveState.world.chunksLoaded / appViewer.backend!.reactiveState.world.chunksTotal * 100) progress.reportProgress('chunks', perc / 100) } }) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 9511ed374..744f1314e 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -20,8 +20,7 @@ import { displayClientChat } from './botUtils' import { currentScaling } from './scaleInterface' import { getItemDescription } from './itemsDescriptions' import { MessageFormatPart } from './chatUtils' -import { GeneralInputItem, getItemMetadata, getItemNameRaw, RenderItem } from './mineflayer/items' -import { getItemModelName } from './resourcesManager' +import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' const loadedImagesCache = new Map() const cleanLoadedImagesCache = () => { diff --git a/src/mineflayer/items.ts b/src/mineflayer/items.ts index 3c2134f1b..8ddb476c0 100644 --- a/src/mineflayer/items.ts +++ b/src/mineflayer/items.ts @@ -1,7 +1,10 @@ import mojangson from 'mojangson' import nbt from 'prismarine-nbt' import { fromFormattedString } from '@xmcl/text-component' +import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState' +import { getItemDefinition } from 'mc-assets/dist/itemDefinitions' import { MessageFormatPart } from '../chatUtils' +import { playerState } from './playerState' type RenderSlotComponent = { type: string, @@ -86,3 +89,22 @@ export const getItemNameRaw = (item: Pick } } } + +export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpecificContextProperties) => { + let itemModelName = item.name + const { customModel } = getItemMetadata(item) + if (customModel) { + itemModelName = customModel + } + + const itemSelector = playerState.getItemSelector({ + ...specificProps + }) + const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, { + name: itemModelName, + version: appViewer.resourcesManager.currentResources!.version, + properties: itemSelector + })?.model + const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName + return model +} diff --git a/src/mineflayer/maps.ts b/src/mineflayer/maps.ts index c5d4f7165..5e9682055 100644 --- a/src/mineflayer/maps.ts +++ b/src/mineflayer/maps.ts @@ -1,5 +1,6 @@ import { mapDownloader } from 'mineflayer-item-map-downloader' import { setImageConverter } from 'mineflayer-item-map-downloader/lib/util' +import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' setImageConverter((buf: Uint8Array) => { const canvas = document.createElement('canvas') @@ -17,7 +18,7 @@ customEvents.on('mineflayerBotCreated', () => { bot.on('login', () => { bot.loadPlugin(mapDownloader) bot.mapDownloader.on('new_map', ({ png, id }) => { - viewer.entities.updateMap(id, png) + getThreeJsRendererMethods()?.updateMap(id, png) }) }) }) diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index c41241bd8..789307c95 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -131,6 +131,10 @@ export class PlayerStateManager implements IPlayerState { isSprinting (): boolean { return gameAdditionalState.isSprinting } + + getPosition (): Vec3 { + return bot.player?.entity.position ?? new Vec3(0, 0, 0) + } // #endregion // #region Held Item State diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index da2fdff0b..3c05ed994 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -1,9 +1,11 @@ import { useEffect, useRef, useMemo, useState } from 'react' import * as THREE from 'three' import type { Block } from 'prismarine-block' +import { proxy, useSnapshot } from 'valtio' +import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { getFixedFilesize } from '../downloadAndOpenFile' import { options } from '../optionsStorage' -import { getBlockAssetsCacheKey } from '../../renderer/viewer/lib/mesher/shared' +import { BlockStateModelInfo, getBlockAssetsCacheKey } from '../../renderer/viewer/lib/mesher/shared' import styles from './DebugOverlay.module.css' export default () => { @@ -36,9 +38,11 @@ export default () => { const [day, setDay] = useState(0) const [dimension, setDimension] = useState('') const [cursorBlock, setCursorBlock] = useState(null) + const [blockInfo, setBlockInfo] = useState<{ customBlockName?: string, modelInfo?: BlockStateModelInfo } | null>(null) const minecraftYaw = useRef(0) const minecraftQuad = useRef(0) - const rendererDevice = appViewer.backend?.getRenderer() ?? 'No render backend' + const { reactiveState } = appViewer.backend ?? {} + const rendererDevice = reactiveState?.renderer ?? 'No render backend' const quadsDescription = [ 'north (towards negative Z)', @@ -108,6 +112,16 @@ export default () => { setCursorBlock(bot.mouse.getCursorState().cursorBlock) }, 100) + const notFrequentUpdateInterval = setInterval(async () => { + const block = bot.mouse.cursorBlock + if (!block) { + setBlockInfo(null) + return + } + const { customBlockName, modelInfo } = await getThreeJsRendererMethods()?.getBlockInfo(pos, block.stateId) ?? {} + setBlockInfo({ customBlockName, modelInfo }) + }, 300) + // @ts-expect-error bot._client.on('packet', readPacket) // @ts-expect-error @@ -121,6 +135,7 @@ export default () => { document.removeEventListener('keydown', handleF3) clearInterval(packetsUpdateInterval) clearInterval(freqUpdateInterval) + clearInterval(notFrequentUpdateInterval) } }, []) @@ -172,11 +187,8 @@ export default () => {

Looking at: {cursorBlock.position.x} {cursorBlock.position.y} {cursorBlock.position.z}

) : ''}
- {cursorBlock && (() => { - const chunkKey = `${Math.floor(cursorBlock.position.x / 16) * 16},${Math.floor(cursorBlock.position.z / 16) * 16}` - const customBlockName = viewer.world.protocolCustomBlocks.get(chunkKey)?.[`${cursorBlock.position.x},${cursorBlock.position.y},${cursorBlock.position.z}`] - const cacheKey = getBlockAssetsCacheKey(cursorBlock.stateId, customBlockName) - const modelInfo = viewer.world.blockStateModelInfo.get(cacheKey) + {blockInfo && (() => { + const { customBlockName, modelInfo } = blockInfo return modelInfo && ( <> {customBlockName &&

Custom block: {customBlockName}

} diff --git a/src/rendererUtils.ts b/src/rendererUtils.ts index a82ad7f6f..49a77801a 100644 --- a/src/rendererUtils.ts +++ b/src/rendererUtils.ts @@ -74,8 +74,7 @@ const updateFovAnimation = () => { currentFov = targetFov } - // viewer.camera.fov = currentFov - // viewer.camera.updateProjectionMatrix() + appViewer.inWorldRenderingConfig.fov = currentFov } lastUpdateTime = now } diff --git a/src/resourcesManager.ts b/src/resourcesManager.ts index 0e25ff627..2b355c6b3 100644 --- a/src/resourcesManager.ts +++ b/src/resourcesManager.ts @@ -1,7 +1,4 @@ import { EventEmitter } from 'events' -import { getItemDefinition } from 'mc-assets/dist/itemDefinitions' -import { ItemSpecificContextProperties } from 'renderer/viewer/lib/basePlayerState' -import { Item } from 'prismarine-item' import TypedEmitter from 'typed-emitter' import blocksAtlases from 'mc-assets/dist/blocksAtlases.json' import itemsAtlases from 'mc-assets/dist/itemsAtlases.json' @@ -15,30 +12,10 @@ import { AtlasParser } from 'mc-assets/dist/atlasParser' import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' import { getLoadedItemDefinitionsStore } from 'mc-assets' +import { getLoadedImage } from 'mc-assets/dist/utils' import { importLargeData } from '../generated/large-data-aliases' -import { GeneralInputItem, getItemMetadata } from './mineflayer/items' -import { playerState } from './mineflayer/playerState' import { loadMinecraftData } from './connect' -export const getItemModelName = (item: GeneralInputItem, specificProps: ItemSpecificContextProperties) => { - let itemModelName = item.name - const { customModel } = getItemMetadata(item) - if (customModel) { - itemModelName = customModel - } - - const itemSelector = playerState.getItemSelector({ - ...specificProps - }) - const modelFromDef = getItemDefinition(appViewer.resourcesManager.itemsDefinitionsStore, { - name: itemModelName, - version: appViewer.resourcesManager.currentResources!.version, - properties: itemSelector - })?.model - const model = (modelFromDef === 'minecraft:special' ? undefined : modelFromDef) ?? itemModelName - return model -} - type ResourceManagerEvents = { assetsTexturesUpdated: () => void } @@ -47,6 +24,8 @@ class LoadedResources { // Atlas parsers itemsAtlasParser: AtlasParser blocksAtlasParser: AtlasParser + itemsAtlasImage: HTMLImageElement + blocksAtlasImage: HTMLImageElement // User data (specific to current resourcepack/version) customBlockStates?: Record @@ -160,6 +139,8 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter { const isMuted = options.mutedSounds.includes(soundKey) || options.volume === 0 if (position) { if (!isMuted) { - viewer.playSound( + appViewer.backend?.soundSystem?.playSound( position, soundData.url, soundData.volume * (options.volume / 100), diff --git a/src/vr.ts b/src/vr.ts index ff20d7c7b..719abba24 100644 --- a/src/vr.ts +++ b/src/vr.ts @@ -5,36 +5,37 @@ import { buttonMap as standardButtonsMap } from 'contro-max/build/gamepad' import * as THREE from 'three' import { subscribeKey } from 'valtio/utils' import { subscribe } from 'valtio' +import { WorldRendererThree } from 'renderer/viewer/lib/worldrendererThree' import { activeModalStack, hideModal } from './globalState' import { watchUnloadForCleanup } from './gameUnload' -import { options } from './optionsStorage' -export async function initVR () { - options.vrSupport = true - const { renderer } = viewer - if (!('xr' in navigator)) return +export async function initVR (worldRenderer: WorldRendererThree) { + if (!('xr' in navigator) || !worldRenderer.worldRendererConfig.vrSupport) return + const { renderer } = worldRenderer const isSupported = await checkVRSupport() if (!isSupported) return - enableVr(renderer) + enableVr() const vrButtonContainer = createVrButtonContainer(renderer) const updateVrButtons = () => { - vrButtonContainer.hidden = !options.vrSupport || activeModalStack.length !== 0 + vrButtonContainer.hidden = !worldRenderer.worldRendererConfig.vrSupport || activeModalStack.length !== 0 } - const unsubWatchSetting = subscribeKey(options, 'vrSupport', updateVrButtons) + const unsubWatchSetting = subscribeKey(worldRenderer.worldRendererConfig, 'vrSupport', updateVrButtons) const unsubWatchModals = subscribe(activeModalStack, updateVrButtons) - function enableVr (renderer) { + function enableVr () { renderer.xr.enabled = true + worldRenderer.reactiveState.preventEscapeMenu = true } function disableVr () { renderer.xr.enabled = false - viewer.cameraObjectOverride = undefined - viewer.scene.remove(user) + worldRenderer.cameraObjectOverride = undefined + worldRenderer.reactiveState.preventEscapeMenu = false + worldRenderer.scene.remove(user) vrButtonContainer.hidden = true unsubWatchSetting() unsubWatchModals() @@ -84,7 +85,7 @@ export async function initVR () { closeButton.addEventListener('click', () => { container.hidden = true - options.vrSupport = false + worldRenderer.worldRendererConfig.vrSupport = false }) return closeButton @@ -103,8 +104,8 @@ export async function initVR () { // hack for vr camera const user = new THREE.Group() - user.add(viewer.camera) - viewer.scene.add(user) + user.add(worldRenderer.camera) + worldRenderer.scene.add(user) const controllerModelFactory = new XRControllerModelFactory(new GLTFLoader()) const controller1 = renderer.xr.getControllerGrip(0) const controller2 = renderer.xr.getControllerGrip(1) @@ -203,17 +204,17 @@ export async function initVR () { // todo ? // bot.physics.stepHeight = 1 - viewer.render() + worldRenderer.render() }) renderer.xr.addEventListener('sessionstart', () => { - viewer.cameraObjectOverride = user + worldRenderer.cameraObjectOverride = user // close all modals to be in game for (const _modal of activeModalStack) { hideModal(undefined, {}, { force: true }) } }) renderer.xr.addEventListener('sessionend', () => { - viewer.cameraObjectOverride = undefined + worldRenderer.cameraObjectOverride = undefined }) watchUnloadForCleanup(disableVr) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 86764203e..4c9d47f6e 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -2,6 +2,7 @@ import { subscribeKey } from 'valtio/utils' import { isMobile } from 'renderer/viewer/lib/simpleUtils' +import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' import { options, watchValue } from './optionsStorage' import { reloadChunks } from './utils' import { miscUiState } from './globalState' @@ -28,8 +29,6 @@ window.matchMedia('(pointer: coarse)').addEventListener('change', (e) => { export const watchOptionsAfterViewerInit = () => { watchValue(options, o => { appViewer.inWorldRenderingConfig.showChunkBorders = o.showChunkBorders - // Note: entities debug mode still needs viewer access - if (viewer) viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none') }) watchValue(options, o => { @@ -37,7 +36,7 @@ export const watchOptionsAfterViewerInit = () => { }) watchValue(options, o => { - if (viewer) viewer.entities.setRendering(o.renderEntities) + appViewer.inWorldRenderingConfig.renderEntities = o.renderEntities }) watchValue(options, o => { @@ -108,10 +107,7 @@ export const watchOptionsAfterViewerInit = () => { }) } -let viewWatched = false -export const watchOptionsAfterWorldViewInit = () => { - if (viewWatched) return - viewWatched = true +export const watchOptionsAfterWorldViewInit = (worldView: WorldDataEmitter) => { watchValue(options, o => { if (!worldView) return worldView.keepChunksDistance = o.keepChunksDistance From ccb00043cf879556cc5795f1fa1bd054264d8d9b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Mar 2025 22:38:23 +0300 Subject: [PATCH 25/41] fix reload hand --- renderer/viewer/lib/worldrendererThree.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 803fa3cc6..3b5d18925 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -58,12 +58,17 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlock = new HoldingBlock(this) this.holdingBlockLeft = new HoldingBlock(this, true) - initOptions.resourcesManager.on('assetsTexturesUpdated', () => { + const onAssetsReaady = () => { this.holdingBlock.ready = true this.holdingBlock.updateItem() this.holdingBlockLeft.ready = true this.holdingBlockLeft.updateItem() - }) + } + + initOptions.resourcesManager.on('assetsTexturesUpdated', onAssetsReaady) + if (initOptions.resourcesManager.currentResources) { + onAssetsReaady() + } this.soundSystem = new ThreeJsSound(this) From 9fedafe77682c441310e16030ed29d556125ca87 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 05:07:27 +0300 Subject: [PATCH 26/41] move threejs entities & cursor block to renderer --- renderer/viewer/lib/basePlayerState.ts | 2 + renderer/viewer/lib/entities.ts | 69 ++++++++--- renderer/viewer/lib/entity/EntityMesh.ts | 6 +- renderer/viewer/lib/worldDataEmitter.ts | 25 +++- renderer/viewer/lib/worldrendererCommon.ts | 32 +++-- renderer/viewer/lib/worldrendererThree.ts | 73 +++++------- renderer/viewer/three/graphicsBackend.ts | 6 + src/entities.ts | 62 ++-------- src/mineflayer/playerState.ts | 7 +- src/mineflayer/plugins/mouse.ts | 132 ++------------------- src/resourcesManager.ts | 3 +- src/watchOptions.ts | 2 + 12 files changed, 157 insertions(+), 262 deletions(-) diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index 3739411f1..180ede3ad 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -3,6 +3,7 @@ import { Vec3 } from 'vec3' import TypedEmitter from 'typed-emitter' import { ItemSelector } from 'mc-assets/dist/itemDefinitions' import { proxy } from 'valtio' +import { GameMode } from 'mineflayer' import { HandItemBlock } from './holdingBlock' export type MovementState = 'NOT_MOVING' | 'WALKING' | 'SPRINTING' | 'SNEAKING' @@ -36,6 +37,7 @@ export interface IPlayerState { backgroundColor: [number, number, number] ambientLight: number directionalLight: number + gameMode?: GameMode } } diff --git a/renderer/viewer/lib/entities.ts b/renderer/viewer/lib/entities.ts index f41e690f4..46eea9d83 100644 --- a/renderer/viewer/lib/entities.ts +++ b/renderer/viewer/lib/entities.ts @@ -206,7 +206,7 @@ export type SceneEntity = THREE.Object3D & { additionalCleanup?: () => void } -export class Entities extends EventEmitter { +export class Entities { entities = {} as Record entitiesOptions: { fontFamily?: string @@ -217,7 +217,6 @@ export class Entities extends EventEmitter { onSkinUpdate: () => void clock = new THREE.Clock() currentlyRendering = true - itemsTexture: THREE.Texture | null = null cachedMapsImages = {} as Record itemFrameMaps = {} as Record>> // getItemUv: undefined | (() => { @@ -248,7 +247,6 @@ export class Entities extends EventEmitter { } constructor (public worldRenderer: WorldRendererThree) { - super() this.entitiesOptions = {} this.debugMode = 'none' this.onSkinUpdate = () => { } @@ -532,9 +530,9 @@ export class Entities extends EventEmitter { // TODO: Render proper model (especially for blocks) instead of flat texture if (textureUv) { - const textureThree = textureUv.renderInfo?.texture === 'blocks' ? this.worldRenderer.material.map! : this.itemsTexture! + const textureThree = textureUv.renderInfo?.texture === 'blocks' ? this.worldRenderer.material.map! : this.worldRenderer.itemsTexture // todo use geometry buffer uv instead! - const { u, v, su, sv, texture } = textureUv + const { u, v, su, sv } = textureUv const size = undefined const itemsTexture = textureThree.clone() itemsTexture.flipY = true @@ -593,6 +591,8 @@ export class Entities extends EventEmitter { } update (entity: import('prismarine-entity').Entity & { delete?; pos, name }, 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`}` @@ -610,7 +610,7 @@ export class Entities extends EventEmitter { e.traverse(c => { if (c['additionalCleanup']) c['additionalCleanup']() }) - this.emit('remove', entity) + this.onRemoveEntity(entity) this.worldRenderer.scene.remove(e) disposeObject(e) // todo dispose textures as well ? @@ -710,7 +710,7 @@ export class Entities extends EventEmitter { e['realName'] = entity.name this.entities[entity.id] = e - this.emit('add', entity) + this.onAddEntity(entity) if (isPlayerModel) { this.updatePlayerSkin(entity.id, entity.username, entity.uuid, overrides?.texture || stevePngUrl) @@ -893,6 +893,30 @@ export class Entities extends EventEmitter { e.username = entity.username } + if (entity.type === 'player' && entity.equipment && e.playerObject) { + const { playerObject } = e + playerObject.backEquipment = entity.equipment.some((item) => item?.name === 'elytra') ? 'elytra' : 'cape' + if (playerObject.cape.map === null) { + playerObject.cape.visible = false + } + } + + this.updateEntityPosition(entity, justAdded, overrides) + } + + updateEntityPosition (entity: import('prismarine-entity').Entity, justAdded: boolean, overrides: { rotation?: { head?: { y: number, x: number } } }) { + const e = this.entities[entity.id] + if (!e) return + const ANIMATION_DURATION = justAdded ? 0 : TWEEN_DURATION + if (entity.position) { + new TWEEN.Tween(e.position).to({ x: entity.position.x, y: entity.position.y, z: entity.position.z }, ANIMATION_DURATION).start() + } + if (entity.yaw) { + const da = (entity.yaw - e.rotation.y) % (Math.PI * 2) + const dy = 2 * da % (Math.PI * 2) - da + new TWEEN.Tween(e.rotation).to({ y: e.rotation.y + dy }, ANIMATION_DURATION).start() + } + if (e?.playerObject && overrides?.rotation?.head) { const playerObject = e.playerObject as PlayerObjectType const headRotationDiff = overrides.rotation.head.y ? overrides.rotation.head.y - entity.yaw : 0 @@ -900,16 +924,33 @@ export class Entities extends EventEmitter { playerObject.skin.head.rotation.x = overrides.rotation.head.x ? - overrides.rotation.head.x : 0 } - if (entity.pos) { - new TWEEN.Tween(e.position).to({ x: entity.pos.x, y: entity.pos.y, z: entity.pos.z }, TWEEN_DURATION).start() - } - if (entity.yaw) { - const da = (entity.yaw - e.rotation.y) % (Math.PI * 2) - const dy = 2 * da % (Math.PI * 2) - da - new TWEEN.Tween(e.rotation).to({ y: e.rotation.y + dy }, TWEEN_DURATION).start() + this.maybeRenderPlayerSkin(entity) + } + + onAddEntity (entity: import('prismarine-entity').Entity) { + } + + loadedSkinEntityIds = new Set() + maybeRenderPlayerSkin (entity: import('prismarine-entity').Entity) { + const mesh = this.entities[entity.id] + if (!mesh) return + if (!mesh.playerObject || !this.worldRenderer.worldRendererConfig.fetchPlayerSkins) return + const MAX_DISTANCE_SKIN_LOAD = 128 + const distance = entity.position.distanceTo(bot.entity.position) + if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (bot.settings.viewDistance as number) * 16) { + if (this.entities[entity.id]) { + if (this.loadedSkinEntityIds.has(entity.id)) return + this.loadedSkinEntityIds.add(entity.id) + this.updatePlayerSkin(entity.id, entity.username, entity.uuid, true, true) + } } } + playerPerAnimation = {} as Record + onRemoveEntity (entity: import('prismarine-entity').Entity) { + this.loadedSkinEntityIds.delete(entity.id) + } + updateMap (mapNumber: string | number, data: string) { this.cachedMapsImages[mapNumber] = data let itemFrameMeshes = this.itemFrameMaps[mapNumber] diff --git a/renderer/viewer/lib/entity/EntityMesh.ts b/renderer/viewer/lib/entity/EntityMesh.ts index e3ea4076f..a6381c4ba 100644 --- a/renderer/viewer/lib/entity/EntityMesh.ts +++ b/renderer/viewer/lib/entity/EntityMesh.ts @@ -5,8 +5,8 @@ import { Vec3 } from 'vec3' import arrowTexture from '../../../../node_modules/mc-assets/dist/other-textures/1.21.2/entity/projectiles/arrow.png' import spectralArrowTexture from '../../../../node_modules/mc-assets/dist/other-textures/1.21.2/entity/projectiles/spectral_arrow.png' import tippedArrowTexture from '../../../../node_modules/mc-assets/dist/other-textures/1.21.2/entity/projectiles/tipped_arrow.png' -import { WorldRendererCommon } from '../worldrendererCommon' import { loadTexture } from '../utils' +import { WorldRendererThree } from '../worldrendererThree' import entities from './entities.json' import { externalModels } from './objModels' import externalTexturesJson from './externalTextures.json' @@ -223,7 +223,7 @@ function addCube ( } export function getMesh ( - worldRenderer: WorldRendererCommon | undefined, + worldRenderer: WorldRendererThree | undefined, texture: string, jsonModel: JsonModel, overrides: EntityOverrides = {}, @@ -437,7 +437,7 @@ export class EntityMesh { constructor ( version: string, type: string, - worldRenderer?: WorldRendererCommon, + worldRenderer?: WorldRendererThree, overrides: EntityOverrides = {}, debugFlags: EntityDebugFlags = {} ) { diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 08dd93d4d..e56799d1b 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -6,6 +6,7 @@ import { generateSpiralMatrix, ViewRect } from 'flying-squid/dist/utils' import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' import { proxy } from 'valtio' +import TypedEmitter from 'typed-emitter' import { getItemFromBlock } from '../../../src/chatUtils' import { delayedIterator } from '../../playground/shared' import { playerState } from '../../../src/mineflayer/playerState' @@ -14,11 +15,26 @@ import { chunkPos } from './simpleUtils' export type ChunkPosKey = string type ChunkPos = { x: number, z: number } +export type WorldDataEmitterEvents = { + chunkPosUpdate: (data: { pos: Vec3 }) => void + blockUpdate: (data: { pos: Vec3, stateId: number }) => void + entity: (data: any) => void + entityMoved: (data: any) => void + time: (data: number) => void + renderDistance: (viewDistance: number) => void + blockEntities: (data: Record | { blockEntities: Record }) => void + listening: () => void + markAsLoaded: (data: { x: number, z: number }) => void + unloadChunk: (data: { x: number, z: number }) => void + loadChunk: (data: { x: number, z: number, chunk: any, blockEntities: any, worldConfig: any, isLightUpdate: boolean }) => void + updateLight: (data: { pos: Vec3 }) => void +} + /** * Usually connects to mineflayer bot and emits world data (chunks, entities) * It's up to the consumer to serialize the data if needed */ -export class WorldDataEmitter extends EventEmitter { +export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter) { private loadedChunks: Record private readonly lastPos: Vec3 private eventListeners: Record = {} @@ -33,6 +49,7 @@ export class WorldDataEmitter extends EventEmitter { }) constructor (public world: typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { + // eslint-disable-next-line constructor-super super() this.loadedChunks = {} this.lastPos = new Vec3(0, 0, 0).update(position) @@ -59,9 +76,9 @@ export class WorldDataEmitter extends EventEmitter { } listenToBot (bot: typeof __type_bot) { - const emitEntity = (e) => { + const emitEntity = (e, name = 'entity') => { if (!e || e === bot.entity) return - this.emitter.emit('entity', { + this.emitter.emit(name as any, { ...e, pos: e.position, username: e.username, @@ -84,7 +101,7 @@ export class WorldDataEmitter extends EventEmitter { emitEntity(e) }, entityMoved (e: any) { - emitEntity(e) + emitEntity(e, 'entityMoved') }, entityGone: (e: any) => { this.emitter.emit('entity', { id: e.id, delete: true }) diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index a4dac4744..6b68d8082 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -5,7 +5,6 @@ import * as THREE from 'three' import mcDataRaw from 'minecraft-data/data.js' // note: using alias import { AtlasParser } from 'mc-assets' import TypedEmitter from 'typed-emitter' -import { LineMaterial } from 'three-stdlib' import { ItemsRenderer } from 'mc-assets/dist/itemsRenderer' import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider' import { generateSpiralMatrix } from 'flying-squid/dist/utils' @@ -22,6 +21,7 @@ import { generateGuiAtlas } from './guiRenderer' import { WorldDataEmitter } from './worldDataEmitter' import { WorldRendererThree } from './worldrendererThree' import { SoundSystem, ThreeJsSound } from './threeJsSound' +import { IPlayerState } from './basePlayerState' function mod (x, n) { return ((x % n) + n) % n @@ -45,17 +45,14 @@ export const defaultWorldRendererConfig = { addChunksBatchWaitTime: 200, vrSupport: true, renderEntities: true, - fov: 75 + fov: 75, + fetchPlayerSkins: true, + highlightBlockColor: 'blue' } export type WorldRendererConfig = typeof defaultWorldRendererConfig export abstract class WorldRendererCommon { - // todo - @worldCleanup() - threejsCursorLineMaterial: LineMaterial - @worldCleanup() - cursorBlock = null as Vec3 | null displayStats = true @worldCleanup() worldSizeParams = { minY: 0, worldHeight: 256 } @@ -138,17 +135,18 @@ export abstract class WorldRendererCommon abstract outputFormat: 'threeJs' | 'webgpu' worldBlockProvider: WorldBlockProvider - itemsTexture: THREE.Texture soundSystem: SoundSystem | undefined abstract changeBackgroundColor (color: [number, number, number]): void worldRendererConfig: WorldRendererConfig + playerState: IPlayerState constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) { // this.initWorkers(1) // preload script on page load this.snapshotInitialValues() this.worldRendererConfig = proxy(displayOptions.inWorldRenderingConfig) + this.playerState = displayOptions.playerState this.renderUpdateEmitter.on('update', () => { const loadedChunks = Object.keys(this.finishedChunks).length @@ -407,11 +405,6 @@ export abstract class WorldRendererCommon config: this.mesherConfig, }) } - const itemsTexture = await new THREE.TextureLoader().loadAsync(resources.itemsAtlasParser.latestImage) - itemsTexture.magFilter = THREE.NearestFilter - itemsTexture.minFilter = THREE.NearestFilter - itemsTexture.flipY = false - this.itemsTexture = itemsTexture console.log('textures loaded') } @@ -509,13 +502,18 @@ export abstract class WorldRendererCommon void set() } - updateEntity (e: any) { } + updateEntity (e: any, isUpdate = false) { } + + lightUpdate (chunkX: number, chunkZ: number) { } connect (worldView: WorldDataEmitter) { const worldEmitter = worldView worldEmitter.on('entity', (e) => { - this.updateEntity(e) + this.updateEntity(e, false) + }) + worldEmitter.on('entityMoved', (e) => { + this.updateEntity(e, true) }) let currentLoadChunkBatch = null as { @@ -579,7 +577,7 @@ export abstract class WorldRendererCommon }) worldEmitter.on('updateLight', ({ pos }) => { - if (this instanceof WorldRendererThree) (this).updateLight(pos.x, pos.z) + this.lightUpdate(pos.x, pos.z) }) worldEmitter.on('time', (timeOfDay) => { @@ -764,6 +762,4 @@ export abstract class WorldRendererCommon this.renderUpdateEmitter.removeAllListeners() this.displayOptions.worldView.removeAllListeners() // todo } - - abstract setHighlightCursorBlock (block: typeof this.cursorBlock, shapePositions?: Array<{ position; width; height; depth }>): void } diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 3b5d18925..8f20e8bde 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -3,12 +3,12 @@ import { Vec3 } from 'vec3' import nbt from 'prismarine-nbt' import PrismarineChatLoader from 'prismarine-chat' import * as tweenJs from '@tweenjs/tween.js' -import { LineSegmentsGeometry, Wireframe } from 'three-stdlib' import { subscribeKey } from 'valtio/utils' import { renderSign } from '../sign-renderer' import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState } from '../../../src/appViewer' import { initVR } from '../../../src/vr' import { getItemUv } from '../three/appShared' +import { CursorBlock } from '../three/world/cursorBlock' import { chunkPos, sectionPos } from './simpleUtils' import { WorldRendererCommon } from './worldrendererCommon' import { disposeObject } from './threeJsUtils' @@ -24,7 +24,6 @@ import { Entities } from './entities' import { ThreeJsSound } from './threeJsSound' export class WorldRendererThree extends WorldRendererCommon { - interactionLines: null | { blockPos; mesh } = null outputFormat = 'threeJs' as const blockEntities = {} sectionObjects: Record = {} @@ -41,6 +40,8 @@ export class WorldRendererThree extends WorldRendererCommon { entities = new Entities(this) cameraObjectOverride?: THREE.Object3D // for xr material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) + itemsTexture: THREE.Texture + cursorBlock = new CursorBlock(this) get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -58,18 +59,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlock = new HoldingBlock(this) this.holdingBlockLeft = new HoldingBlock(this, true) - const onAssetsReaady = () => { - this.holdingBlock.ready = true - this.holdingBlock.updateItem() - this.holdingBlockLeft.ready = true - this.holdingBlockLeft.updateItem() - } - - initOptions.resourcesManager.on('assetsTexturesUpdated', onAssetsReaady) - if (initOptions.resourcesManager.currentResources) { - onAssetsReaady() - } - this.soundSystem = new ThreeJsSound(this) this.addDebugOverlay() @@ -79,8 +68,8 @@ export class WorldRendererThree extends WorldRendererCommon { void initVR(this) } - updateEntity (e) { - this.entities.update(e, { + updateEntity (e, isPosUpdate = false) { + const overrides = { rotation: { head: { x: e.headPitch ?? e.pitch, @@ -88,7 +77,12 @@ export class WorldRendererThree extends WorldRendererCommon { z: 0 } } - }) + } + if (isPosUpdate) { + this.entities.updateEntityPosition(e, false, overrides) + } else { + this.entities.update(e, overrides) + } } resetScene () { @@ -140,7 +134,21 @@ export class WorldRendererThree extends WorldRendererCommon { texture.minFilter = THREE.NearestFilter texture.flipY = false this.material.map = texture + + const itemsTexture = await new THREE.TextureLoader().loadAsync(resources.itemsAtlasParser.latestImage) + itemsTexture.magFilter = THREE.NearestFilter + itemsTexture.minFilter = THREE.NearestFilter + itemsTexture.flipY = false + this.itemsTexture = itemsTexture await super.updateAssetsData() + this.onAllTexturesLoaded() + } + + onAllTexturesLoaded () { + this.holdingBlock.ready = true + this.holdingBlock.updateItem() + this.holdingBlockLeft.ready = true + this.holdingBlockLeft.updateItem() } changeBackgroundColor (color: [number, number, number]): void { @@ -354,6 +362,7 @@ export class WorldRendererThree extends WorldRendererCommon { } render (sizeChanged = false) { + this.cursorBlock.render() const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov if (sizeOrFovChanged) { this.camera.aspect = window.innerWidth / window.innerHeight @@ -441,7 +450,7 @@ export class WorldRendererThree extends WorldRendererCommon { return group } - updateLight (chunkX: number, chunkZ: number) { + lightUpdate (chunkX: number, chunkZ: number) { // set all sections in the chunk dirty for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) { this.setSectionDirty(new Vec3(chunkX, y, chunkZ)) @@ -544,34 +553,6 @@ export class WorldRendererThree extends WorldRendererCommon { super.setSectionDirty(...args) } - setHighlightCursorBlock (blockPos: typeof this.cursorBlock, shapePositions?: Array<{ position: any; width: any; height: any; depth: any; }>): void { - this.cursorBlock = blockPos - if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) { - return - } - if (this.interactionLines !== null) { - this.scene.remove(this.interactionLines.mesh) - this.interactionLines = null - } - if (blockPos === null) { - return - } - - const group = new THREE.Group() - for (const { position, width, height, depth } of shapePositions ?? []) { - const scale = [1.0001 * width, 1.0001 * height, 1.0001 * depth] as const - const geometry = new THREE.BoxGeometry(...scale) - const lines = new LineSegmentsGeometry().fromEdgesGeometry(new THREE.EdgesGeometry(geometry)) - const wireframe = new Wireframe(lines, this.threejsCursorLineMaterial) - const pos = blockPos.plus(position) - wireframe.position.set(pos.x, pos.y, pos.z) - wireframe.computeLineDistances() - group.add(wireframe) - } - this.scene.add(group) - this.interactionLines = { blockPos, mesh: group } - } - static getRendererInfo (renderer: THREE.WebGLRenderer) { try { const gl = renderer.getContext() diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index a4d968b8d..a70b00d29 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -15,6 +15,12 @@ const getBackendMethods = (worldRenderer: WorldRendererThree) => { updateMap: worldRenderer.entities.updateMap.bind(worldRenderer.entities), updateCustomBlock: worldRenderer.updateCustomBlock.bind(worldRenderer), getBlockInfo: worldRenderer.getBlockInfo.bind(worldRenderer), + playEntityAnimation: worldRenderer.entities.playAnimation.bind(worldRenderer.entities), + damageEntity: worldRenderer.entities.handleDamageEvent.bind(worldRenderer.entities), + updatePlayerSkin: worldRenderer.entities.updatePlayerSkin.bind(worldRenderer.entities), + setHighlightCursorBlock: worldRenderer.cursorBlock.setHighlightCursorBlock.bind(worldRenderer.cursorBlock), + updateBreakAnimation: worldRenderer.cursorBlock.updateBreakAnimation.bind(worldRenderer.cursorBlock), + changeHandSwingingState: worldRenderer.changeHandSwingingState.bind(worldRenderer), } } diff --git a/src/entities.ts b/src/entities.ts index 78d56a4f3..9ba31f2e0 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -3,6 +3,7 @@ import { versionToNumber } from 'renderer/viewer/common/utils' import tracker from '@nxg-org/mineflayer-tracker' import { loader as autoJumpPlugin } from '@nxg-org/mineflayer-auto-jump' import { subscribeKey } from 'valtio/utils' +import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { options, watchValue } from './optionsStorage' import { miscUiState } from './globalState' @@ -36,28 +37,17 @@ subscribeKey(miscUiState, 'currentTouch', () => { }) customEvents.on('gameLoaded', () => { - return bot.loadPlugin(tracker) bot.loadPlugin(autoJumpPlugin) updateAutoJump() - // todo cleanup (move to viewer, also shouldnt be used at all) const playerPerAnimation = {} as Record const entityData = (e: Entity) => { if (!e.username) return window.debugEntityMetadata ??= {} window.debugEntityMetadata[e.username] = e - // todo entity spawn timing issue, check perf - const playerObject = viewer.entities.entities[e.id]?.playerObject - if (playerObject) { - // todo throttle! + if (e.type === 'player') { bot.tracker.trackEntity(e) - playerObject.backEquipment = e.equipment.some((item) => item?.name === 'elytra') ? 'elytra' : 'cape' - if (playerObject.cape.map === null) { - playerObject.cape.visible = false - } - // todo (easy, important) elytra flying animation - // todo cleanup states } } @@ -77,57 +67,34 @@ customEvents.on('gameLoaded', () => { const isSprinting = Math.abs(speed.x) > SPRINTING_SPEED || Math.abs(speed.z) > SPRINTING_SPEED const newAnimation = isWalking ? (isSprinting ? 'running' : 'walking') : 'idle' if (newAnimation !== playerPerAnimation[id]) { - viewer.entities.playAnimation(e.id, newAnimation) + getThreeJsRendererMethods()?.playEntityAnimation(e.id, newAnimation) playerPerAnimation[id] = newAnimation } } }) bot.on('entitySwingArm', (e) => { - if (viewer.entities.entities[e.id]?.playerObject) { - viewer.entities.playAnimation(e.id, 'oneSwing') - } + getThreeJsRendererMethods()?.playEntityAnimation(e.id, 'oneSwing') }) bot._client.on('damage_event', (data) => { const { entityId, sourceTypeId: damage } = data - if (viewer.entities.entities[entityId]) { - viewer.entities.handleDamageEvent(entityId, damage) - } + getThreeJsRendererMethods()?.damageEntity(entityId, damage) }) bot._client.on('entity_status', (data) => { if (versionToNumber(bot.version) >= versionToNumber('1.19.4')) return const { entityId, entityStatus } = data - if (entityStatus === 2 && viewer.entities.entities[entityId]) { - viewer.entities.handleDamageEvent(entityId, entityStatus) + if (entityStatus === 2) { + getThreeJsRendererMethods()?.damageEntity(entityId, entityStatus) } }) - const loadedSkinEntityIds = new Set() - - const playerRenderSkin = (e: Entity) => { - const mesh = viewer.entities.entities[e.id] - if (!mesh) return - if (!mesh.playerObject || !options.loadPlayerSkins) return - const MAX_DISTANCE_SKIN_LOAD = 128 - const distance = e.position.distanceTo(bot.entity.position) - if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (bot.settings.viewDistance as number) * 16) { - if (viewer.entities.entities[e.id]) { - if (loadedSkinEntityIds.has(e.id)) return - loadedSkinEntityIds.add(e.id) - viewer.entities.updatePlayerSkin(e.id, e.username, e.uuid, true, true) - } - } - } - viewer.entities.addListener('remove', (e) => { - loadedSkinEntityIds.delete(e.id) - playerPerAnimation[e.id] = '' - bot.tracker.stopTrackingEntity(e, true) + bot.on('entityGone', (entity) => { + bot.tracker.stopTrackingEntity(entity, true) }) bot.on('entityMoved', (e) => { - playerRenderSkin(e) entityData(e) }) bot._client.on('entity_velocity', (packet) => { @@ -136,11 +103,6 @@ customEvents.on('gameLoaded', () => { entityData(e) }) - viewer.entities.addListener('add', (e) => { - if (!viewer.entities.entities[e.id]) throw new Error('mesh still not loaded') - playerRenderSkin(e) - }) - for (const entity of Object.values(bot.entities)) { if (entity !== bot.entity) { entityData(entity) @@ -151,10 +113,6 @@ customEvents.on('gameLoaded', () => { bot.on('entityUpdate', entityData) bot.on('entityEquip', entityData) - watchValue(options, o => { - viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none') - }) - // Texture override from packet properties bot._client.on('player_info', (packet) => { for (const playerEntry of packet.data) { @@ -178,7 +136,7 @@ customEvents.on('gameLoaded', () => { } } // even if not found, still record to cache - viewer.entities.updatePlayerSkin(entityId, playerEntry.player?.name, playerEntry.uuid, skinUrl, capeUrl) + getThreeJsRendererMethods()?.updatePlayerSkin(entityId, playerEntry.player?.name, playerEntry.uuid, skinUrl, capeUrl) } catch (err) { console.error('Error decoding player texture:', err) } diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 789307c95..6ef9a90df 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -29,7 +29,7 @@ export class PlayerStateManager implements IPlayerState { return bot.player?.username ?? '' } - reactive = new BasePlayerState().reactive + reactive: IPlayerState['reactive'] = new BasePlayerState().reactive static getInstance (): PlayerStateManager { if (!this.instance) { @@ -68,6 +68,11 @@ export class PlayerStateManager implements IPlayerState { // Initial held items setup this.updateHeldItem(false) this.updateHeldItem(true) + + bot.on('game', () => { + this.reactive.gameMode = bot.game.gameMode + }) + this.reactive.gameMode = bot.game?.gameMode } // #region Movement and Physics State diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index 1953d7e45..85c1ddaa7 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -1,141 +1,29 @@ import { createMouse } from 'mineflayer-mouse' -import * as THREE from 'three' import { Bot } from 'mineflayer' import { Block } from 'prismarine-block' -import { Vec3 } from 'vec3' -import { LineMaterial } from 'three-stdlib' -import { subscribeKey } from 'valtio/utils' -import { disposeObject } from 'renderer/viewer/lib/threeJsUtils' +import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { isGameActive, showModal } from '../../globalState' -// wouldn't better to create atlas instead? -import destroyStage0 from '../../../assets/destroy_stage_0.png' -import destroyStage1 from '../../../assets/destroy_stage_1.png' -import destroyStage2 from '../../../assets/destroy_stage_2.png' -import destroyStage3 from '../../../assets/destroy_stage_3.png' -import destroyStage4 from '../../../assets/destroy_stage_4.png' -import destroyStage5 from '../../../assets/destroy_stage_5.png' -import destroyStage6 from '../../../assets/destroy_stage_6.png' -import destroyStage7 from '../../../assets/destroy_stage_7.png' -import destroyStage8 from '../../../assets/destroy_stage_8.png' -import destroyStage9 from '../../../assets/destroy_stage_9.png' -import { options } from '../../optionsStorage' import { isCypress } from '../../standaloneUtils' import { playerState } from '../playerState' -function createDisplayManager (bot: Bot, scene: THREE.Scene, renderer: THREE.WebGLRenderer) { - // State - const state = { - blockBreakMesh: null as THREE.Mesh | null, - breakTextures: [] as THREE.Texture[], - } - - // Initialize break mesh and textures - const loader = new THREE.TextureLoader() - const destroyStagesImages = [ - destroyStage0, destroyStage1, destroyStage2, destroyStage3, destroyStage4, - destroyStage5, destroyStage6, destroyStage7, destroyStage8, destroyStage9 - ] - - for (let i = 0; i < 10; i++) { - const texture = loader.load(destroyStagesImages[i]) - texture.magFilter = THREE.NearestFilter - texture.minFilter = THREE.NearestFilter - state.breakTextures.push(texture) - } - - const breakMaterial = new THREE.MeshBasicMaterial({ - transparent: true, - blending: THREE.MultiplyBlending, - alphaTest: 0.5, - }) - state.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial) - state.blockBreakMesh.visible = false - state.blockBreakMesh.renderOrder = 999 - state.blockBreakMesh.name = 'blockBreakMesh' - scene.add(state.blockBreakMesh) - - // Update functions - function updateLineMaterial () { - const inCreative = bot.game.gameMode === 'creative' - const pixelRatio = viewer.renderer.getPixelRatio() - - viewer.world.threejsCursorLineMaterial = new LineMaterial({ - color: (() => { - switch (options.highlightBlockColor) { - case 'blue': - return 0x40_80_ff - case 'classic': - return 0x00_00_00 - default: - return inCreative ? 0x40_80_ff : 0x00_00_00 - } - })(), - linewidth: Math.max(pixelRatio * 0.7, 1) * 2, - // dashed: true, - // dashSize: 5, - }) - } - - function updateDisplay () { - if (viewer.world.threejsCursorLineMaterial) { - const { renderer } = viewer - viewer.world.threejsCursorLineMaterial.resolution.set(renderer.domElement.width, renderer.domElement.height) - viewer.world.threejsCursorLineMaterial.dashOffset = performance.now() / 750 - } - } - beforeRenderFrame.push(updateDisplay) - - // Update cursor line material on game mode change - bot.on('game', updateLineMaterial) - // Update material when highlight color setting changes - subscribeKey(options, 'highlightBlockColor', updateLineMaterial) - - function updateBreakAnimation (block: Block | undefined, stage: number | null) { - hideBreakAnimation() - if (!state.blockBreakMesh) return // todo - if (stage === null || !block) return - - const mergedShape = bot.mouse.getMergedCursorShape(block) - if (!mergedShape) return - const { position, width, height, depth } = bot.mouse.getDataFromShape(mergedShape) - state.blockBreakMesh.scale.set(width * 1.001, height * 1.001, depth * 1.001) - position.add(block.position) - state.blockBreakMesh.position.set(position.x, position.y, position.z) - state.blockBreakMesh.visible = true - - //@ts-expect-error - state.blockBreakMesh.material.map = state.breakTextures[stage] ?? state.breakTextures.at(-1) - //@ts-expect-error - state.blockBreakMesh.material.needsUpdate = true - } - - function hideBreakAnimation () { - if (state.blockBreakMesh) { - state.blockBreakMesh.visible = false - } - } - - function updateCursorBlock (data?: { block: Block }) { +function cursorBlockDisplay (bot: Bot) { + const updateCursorBlock = (data?: { block: Block }) => { if (!data?.block) { - viewer.world.setHighlightCursorBlock(null) + getThreeJsRendererMethods()?.setHighlightCursorBlock(null) return } const { block } = data - viewer.world.setHighlightCursorBlock(block.position, bot.mouse.getBlockCursorShapes(block).map(shape => { + getThreeJsRendererMethods()?.setHighlightCursorBlock(block.position, bot.mouse.getBlockCursorShapes(block).map(shape => { return bot.mouse.getDataFromShape(shape) })) } bot.on('highlightCursorBlock', updateCursorBlock) - bot.on('blockBreakProgressStage', updateBreakAnimation) - - bot.on('end', () => { - disposeObject(state.blockBreakMesh!, true) - scene.remove(state.blockBreakMesh!) - viewer.world.setHighlightCursorBlock(null) + bot.on('blockBreakProgressStage', (block, stage) => { + getThreeJsRendererMethods()?.updateBreakAnimation(block, stage) }) } @@ -143,7 +31,7 @@ export default (bot: Bot) => { bot.loadPlugin(createMouse({})) domListeners(bot) - // createDisplayManager(bot, viewer.scene, viewer.renderer) + cursorBlockDisplay(bot) otherListeners() } @@ -158,11 +46,11 @@ const otherListeners = () => { }) bot.on('botArmSwingStart', (hand) => { - viewer.world.changeHandSwingingState(true, hand === 'left') + getThreeJsRendererMethods()?.changeHandSwingingState(true, hand === 'left') }) bot.on('botArmSwingEnd', (hand) => { - viewer.world.changeHandSwingingState(false, hand === 'left') + getThreeJsRendererMethods()?.changeHandSwingingState(false, hand === 'left') }) bot.on('startUsingItem', (item, slot, isOffhand, duration) => { diff --git a/src/resourcesManager.ts b/src/resourcesManager.ts index 2b355c6b3..0d20fbfbe 100644 --- a/src/resourcesManager.ts +++ b/src/resourcesManager.ts @@ -158,9 +158,8 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter { watchValue(options, (o, isChanged) => { appViewer.inWorldRenderingConfig.clipWorldBelowY = o.clipWorldBelowY appViewer.inWorldRenderingConfig.extraBlockRenderers = !o.disableSignsMapsSupport + appViewer.inWorldRenderingConfig.fetchPlayerSkins = o.loadPlayerSkins + appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor }) appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting From 136b051695998e0bb163de6104ff340aeef8bbf7 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 05:16:30 +0300 Subject: [PATCH 27/41] smooth camera movement! --- src/cameraRotationControls.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cameraRotationControls.ts b/src/cameraRotationControls.ts index 27fa35957..0e4dd3441 100644 --- a/src/cameraRotationControls.ts +++ b/src/cameraRotationControls.ts @@ -49,6 +49,7 @@ export const moveCameraRawHandler = ({ x, y }: { x: number; y: number }) => { if (!bot?.entity) return const pitch = bot.entity.pitch - y void bot.look(bot.entity.yaw - x, Math.max(minPitch, Math.min(maxPitch, pitch)), true) + appViewer.backend?.updateCamera(null, bot.entity.yaw, pitch) } window.addEventListener('mousemove', (e: MouseEvent) => { From f88e9c8b6121b3350ed13635d333ff33a44eda4b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 06:11:31 +0300 Subject: [PATCH 28/41] Refactor renderer state management and move vr --- .gitignore | 2 +- renderer/viewer/baseGraphicsBackend.ts | 3 +- renderer/viewer/lib/worldrendererCommon.ts | 12 +- renderer/viewer/lib/worldrendererThree.ts | 15 ++- renderer/viewer/three/graphicsBackend.ts | 15 +-- renderer/viewer/three/panorama.ts | 8 +- renderer/viewer/three/world/cursorBlock.ts | 144 +++++++++++++++++++++ {src => renderer/viewer/three/world}/vr.ts | 22 +--- src/appViewer.ts | 9 +- src/react/IndicatorEffectsProvider.tsx | 27 +--- 10 files changed, 194 insertions(+), 63 deletions(-) create mode 100644 renderer/viewer/three/world/cursorBlock.ts rename {src => renderer/viewer/three/world}/vr.ts (91%) diff --git a/.gitignore b/.gitignore index f2a0006e0..bd7743159 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ localSettings.mjs dist* .DS_Store .idea/ -world +/world data*.json out *.iml diff --git a/renderer/viewer/baseGraphicsBackend.ts b/renderer/viewer/baseGraphicsBackend.ts index e0286b153..ad7b7d141 100644 --- a/renderer/viewer/baseGraphicsBackend.ts +++ b/renderer/viewer/baseGraphicsBackend.ts @@ -1,9 +1,10 @@ -export const getDefaultReactiveState = () => { +export const getDefaultRendererState = () => { return { world: { chunksLoaded: 0, chunksTotal: 0, allChunksLoaded: true, + mesherWork: false }, renderer: '', preventEscapeMenu: false diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 6b68d8082..2763c747a 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -12,7 +12,7 @@ import { proxy } from 'valtio' import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs' import { toMajorVersion } from '../../../src/utils' import { ResourcesManager } from '../../../src/resourcesManager' -import { DisplayWorldOptions } from '../../../src/appViewer' +import { DisplayWorldOptions, RendererReactiveState } from '../../../src/appViewer' import { buildCleanupDecorator } from './cleanupDecorator' import { defaultMesherConfig, HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey } from './mesher/shared' import { chunkPos } from './simpleUtils' @@ -47,7 +47,8 @@ export const defaultWorldRendererConfig = { renderEntities: true, fov: 75, fetchPlayerSkins: true, - highlightBlockColor: 'blue' + highlightBlockColor: 'blue', + foreground: true } export type WorldRendererConfig = typeof defaultWorldRendererConfig @@ -141,12 +142,16 @@ export abstract class WorldRendererCommon worldRendererConfig: WorldRendererConfig playerState: IPlayerState + reactiveState: RendererReactiveState + + abortController = new AbortController() constructor (public readonly resourcesManager: ResourcesManager, public displayOptions: DisplayWorldOptions, public version: string) { // this.initWorkers(1) // preload script on page load this.snapshotInitialValues() - this.worldRendererConfig = proxy(displayOptions.inWorldRenderingConfig) + this.worldRendererConfig = displayOptions.inWorldRenderingConfig this.playerState = displayOptions.playerState + this.reactiveState = displayOptions.rendererState this.renderUpdateEmitter.on('update', () => { const loadedChunks = Object.keys(this.finishedChunks).length @@ -761,5 +766,6 @@ export abstract class WorldRendererCommon this.renderUpdateEmitter.removeAllListeners() this.displayOptions.worldView.removeAllListeners() // todo + this.abortController.abort() } } diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 8f20e8bde..0d2538453 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -6,7 +6,7 @@ import * as tweenJs from '@tweenjs/tween.js' import { subscribeKey } from 'valtio/utils' import { renderSign } from '../sign-renderer' import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState } from '../../../src/appViewer' -import { initVR } from '../../../src/vr' +import { initVR } from '../three/world/vr' import { getItemUv } from '../three/appShared' import { CursorBlock } from '../three/world/cursorBlock' import { chunkPos, sectionPos } from './simpleUtils' @@ -42,6 +42,7 @@ export class WorldRendererThree extends WorldRendererCommon { material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) itemsTexture: THREE.Texture cursorBlock = new CursorBlock(this) + onRender: Array<() => void> = [] get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) @@ -51,7 +52,7 @@ export class WorldRendererThree extends WorldRendererCommon { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).blocksCount, 0) } - constructor (public renderer: THREE.WebGLRenderer, public initOptions: GraphicsInitOptions, public displayOptions: DisplayWorldOptions, public version: string, public reactiveState: RendererReactiveState) { + constructor (public renderer: THREE.WebGLRenderer, public initOptions: GraphicsInitOptions, public displayOptions: DisplayWorldOptions, public version: string) { if (!initOptions.resourcesManager) throw new Error('resourcesManager is required') super(initOptions.resourcesManager, displayOptions, version) @@ -363,20 +364,28 @@ export class WorldRendererThree extends WorldRendererCommon { render (sizeChanged = false) { this.cursorBlock.render() + const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov if (sizeOrFovChanged) { this.camera.aspect = window.innerWidth / window.innerHeight this.camera.fov = this.displayOptions.inWorldRenderingConfig.fov this.camera.updateProjectionMatrix() } + + this.entities.render() + // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera - this.entities.render() this.renderer.render(this.scene, cam) + if (this.displayOptions.inWorldRenderingConfig.showHand/* && !this.freeFlyMode */) { this.holdingBlock.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) this.holdingBlockLeft.render(this.camera, this.renderer, this.ambientLight, this.directionalLight) } + + for (const onRender of this.onRender) { + onRender() + } } renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) { diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index a70b00d29..57585cc7a 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -34,16 +34,6 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO let panoramaRenderer: PanoramaRenderer | null = null let worldRenderer: WorldRendererThree | null = null - const reactiveState: RendererReactiveState = proxy({ - world: { - chunksLoaded: 0, - chunksTotal: 0, - allChunksLoaded: false, - }, - renderer: WorldRendererThree.getRendererInfo(documentRenderer.renderer) ?? '...', - preventEscapeMenu: false - }) - const startPanorama = () => { if (worldRenderer) return if (!panoramaRenderer) { @@ -63,13 +53,11 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO panoramaRenderer.dispose() panoramaRenderer = null } - worldRenderer = new WorldRendererThree(documentRenderer.renderer, initOptions, displayOptions, version, reactiveState) + worldRenderer = new WorldRendererThree(documentRenderer.renderer, initOptions, displayOptions, version) documentRenderer.render = (sizeChanged: boolean) => { worldRenderer?.render(sizeChanged) } - window.viewer ??= {} window.world = worldRenderer - window.viewer.world = worldRenderer } const disconnect = () => { @@ -106,7 +94,6 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO setRoll (roll: number) { worldRenderer?.setCameraRoll(roll) }, - reactiveState, get soundSystem () { return worldRenderer?.soundSystem }, diff --git a/renderer/viewer/three/panorama.ts b/renderer/viewer/three/panorama.ts index f3e3e5033..abfbf51a6 100644 --- a/renderer/viewer/three/panorama.ts +++ b/renderer/viewer/three/panorama.ts @@ -9,7 +9,7 @@ import { WorldDataEmitter } from '../lib/worldDataEmitter' import { WorldRendererThree } from '../lib/worldrendererThree' import { defaultWorldRendererConfig } from '../lib/worldrendererCommon' import { BasePlayerState } from '../lib/basePlayerState' -import { getDefaultReactiveState } from '../baseGraphicsBackend' +import { getDefaultRendererState } from '../baseGraphicsBackend' import { DocumentRenderer } from './documentRenderer' const panoramaFiles = [ @@ -160,10 +160,10 @@ export class PanoramaRenderer { { worldView, inWorldRenderingConfig: defaultWorldRendererConfig, - playerState: new BasePlayerState() + playerState: new BasePlayerState(), + rendererState: getDefaultRendererState() }, - version, - getDefaultReactiveState() + version ) this.scene = this.worldRenderer.scene void worldView.init(initPos) diff --git a/renderer/viewer/three/world/cursorBlock.ts b/renderer/viewer/three/world/cursorBlock.ts new file mode 100644 index 000000000..ec7e59a2e --- /dev/null +++ b/renderer/viewer/three/world/cursorBlock.ts @@ -0,0 +1,144 @@ +import * as THREE from 'three' +import { LineMaterial, LineSegmentsGeometry, Wireframe } from 'three-stdlib' +import { Vec3 } from 'vec3' +import { subscribeKey } from 'valtio/utils' +import { Block } from 'prismarine-block' +import { WorldRendererThree } from '../../lib/worldrendererThree' +import destroyStage0 from '../../../../assets/destroy_stage_0.png' +import destroyStage1 from '../../../../assets/destroy_stage_1.png' +import destroyStage2 from '../../../../assets/destroy_stage_2.png' +import destroyStage3 from '../../../../assets/destroy_stage_3.png' +import destroyStage4 from '../../../../assets/destroy_stage_4.png' +import destroyStage5 from '../../../../assets/destroy_stage_5.png' +import destroyStage6 from '../../../../assets/destroy_stage_6.png' +import destroyStage7 from '../../../../assets/destroy_stage_7.png' +import destroyStage8 from '../../../../assets/destroy_stage_8.png' +import destroyStage9 from '../../../../assets/destroy_stage_9.png' + +export class CursorBlock { + cursorLineMaterial: LineMaterial + interactionLines: null | { blockPos; mesh } = null + prevColor + blockBreakMesh: THREE.Mesh + breakTextures: THREE.Texture[] = [] + + constructor (public readonly worldRenderer: WorldRendererThree) { + // Initialize break mesh and textures + const loader = new THREE.TextureLoader() + const destroyStagesImages = [ + destroyStage0, destroyStage1, destroyStage2, destroyStage3, destroyStage4, + destroyStage5, destroyStage6, destroyStage7, destroyStage8, destroyStage9 + ] + + for (let i = 0; i < 10; i++) { + const texture = loader.load(destroyStagesImages[i]) + texture.magFilter = THREE.NearestFilter + texture.minFilter = THREE.NearestFilter + this.breakTextures.push(texture) + } + + const breakMaterial = new THREE.MeshBasicMaterial({ + transparent: true, + blending: THREE.MultiplyBlending, + alphaTest: 0.5, + }) + this.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial) + this.blockBreakMesh.visible = false + this.blockBreakMesh.renderOrder = 999 + this.blockBreakMesh.name = 'blockBreakMesh' + this.worldRenderer.scene.add(this.blockBreakMesh) + + subscribeKey(this.worldRenderer.playerState.reactive, 'gameMode', () => { + this.updateLineMaterial() + }) + } + + // Update functions + updateLineMaterial () { + const inCreative = this.worldRenderer.displayOptions.playerState.reactive.gameMode === 'creative' + const pixelRatio = this.worldRenderer.renderer.getPixelRatio() + + this.cursorLineMaterial = new LineMaterial({ + color: (() => { + switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) { + case 'blue': + return 0x40_80_ff + case 'classic': + return 0x00_00_00 + default: + return inCreative ? 0x40_80_ff : 0x00_00_00 + } + })(), + linewidth: Math.max(pixelRatio * 0.7, 1) * 2, + // dashed: true, + // dashSize: 5, + }) + this.prevColor = this.worldRenderer.worldRendererConfig.highlightBlockColor + } + + updateBreakAnimation (block: Block | undefined, stage: number | null) { + this.hideBreakAnimation() + if (stage === null || !block) return + + const mergedShape = bot.mouse.getMergedCursorShape(block) + if (!mergedShape) return + const { position, width, height, depth } = bot.mouse.getDataFromShape(mergedShape) + this.blockBreakMesh.scale.set(width * 1.001, height * 1.001, depth * 1.001) + position.add(block.position) + this.blockBreakMesh.position.set(position.x, position.y, position.z) + this.blockBreakMesh.visible = true + + //@ts-expect-error + state.blockBreakMesh.material.map = state.breakTextures[stage] ?? state.breakTextures.at(-1) + //@ts-expect-error + state.blockBreakMesh.material.needsUpdate = true + } + + hideBreakAnimation () { + if (this.blockBreakMesh) { + this.blockBreakMesh.visible = false + } + } + + updateDisplay () { + if (this.cursorLineMaterial) { + const { renderer } = this.worldRenderer + this.cursorLineMaterial.resolution.set(renderer.domElement.width, renderer.domElement.height) + this.cursorLineMaterial.dashOffset = performance.now() / 750 + } + } + + setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: Array<{ position: any; width: any; height: any; depth: any; }>): void { + if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) { + return + } + if (this.interactionLines !== null) { + this.worldRenderer.scene.remove(this.interactionLines.mesh) + this.interactionLines = null + } + if (blockPos === null) { + return + } + + const group = new THREE.Group() + for (const { position, width, height, depth } of shapePositions ?? []) { + const scale = [1.0001 * width, 1.0001 * height, 1.0001 * depth] as const + const geometry = new THREE.BoxGeometry(...scale) + const lines = new LineSegmentsGeometry().fromEdgesGeometry(new THREE.EdgesGeometry(geometry)) + const wireframe = new Wireframe(lines, this.cursorLineMaterial) + const pos = blockPos.plus(position) + wireframe.position.set(pos.x, pos.y, pos.z) + wireframe.computeLineDistances() + group.add(wireframe) + } + this.worldRenderer.scene.add(group) + this.interactionLines = { blockPos, mesh: group } + } + + render () { + if (this.prevColor !== this.worldRenderer.worldRendererConfig.highlightBlockColor) { + this.updateLineMaterial() + } + this.updateDisplay() + } +} diff --git a/src/vr.ts b/renderer/viewer/three/world/vr.ts similarity index 91% rename from src/vr.ts rename to renderer/viewer/three/world/vr.ts index 719abba24..2d1f3cdd0 100644 --- a/src/vr.ts +++ b/renderer/viewer/three/world/vr.ts @@ -3,11 +3,7 @@ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js' import { buttonMap as standardButtonsMap } from 'contro-max/build/gamepad' import * as THREE from 'three' -import { subscribeKey } from 'valtio/utils' -import { subscribe } from 'valtio' import { WorldRendererThree } from 'renderer/viewer/lib/worldrendererThree' -import { activeModalStack, hideModal } from './globalState' -import { watchUnloadForCleanup } from './gameUnload' export async function initVR (worldRenderer: WorldRendererThree) { if (!('xr' in navigator) || !worldRenderer.worldRendererConfig.vrSupport) return @@ -20,11 +16,13 @@ export async function initVR (worldRenderer: WorldRendererThree) { const vrButtonContainer = createVrButtonContainer(renderer) const updateVrButtons = () => { - vrButtonContainer.hidden = !worldRenderer.worldRendererConfig.vrSupport || activeModalStack.length !== 0 + const newHidden = !worldRenderer.worldRendererConfig.vrSupport || !worldRenderer.worldRendererConfig.foreground + if (vrButtonContainer.hidden !== newHidden) { + vrButtonContainer.hidden = newHidden + } } - const unsubWatchSetting = subscribeKey(worldRenderer.worldRendererConfig, 'vrSupport', updateVrButtons) - const unsubWatchModals = subscribe(activeModalStack, updateVrButtons) + worldRenderer.onRender.push(updateVrButtons) function enableVr () { renderer.xr.enabled = true @@ -37,8 +35,6 @@ export async function initVR (worldRenderer: WorldRendererThree) { worldRenderer.reactiveState.preventEscapeMenu = false worldRenderer.scene.remove(user) vrButtonContainer.hidden = true - unsubWatchSetting() - unsubWatchModals() } function createVrButtonContainer (renderer) { @@ -193,7 +189,7 @@ export async function initVR (worldRenderer: WorldRendererThree) { } // appViewer.backend?.updateCamera(null, yawOffset, 0) - appViewer.backend?.updateCamera(null, bot.entity.yaw, bot.entity.pitch) + worldRenderer.updateCamera(null, bot.entity.yaw, bot.entity.pitch) // todo restore this logic (need to preserve ability to move camera) // const xrCamera = renderer.xr.getCamera() @@ -208,16 +204,12 @@ export async function initVR (worldRenderer: WorldRendererThree) { }) renderer.xr.addEventListener('sessionstart', () => { worldRenderer.cameraObjectOverride = user - // close all modals to be in game - for (const _modal of activeModalStack) { - hideModal(undefined, {}, { force: true }) - } }) renderer.xr.addEventListener('sessionend', () => { worldRenderer.cameraObjectOverride = undefined }) - watchUnloadForCleanup(disableVr) + worldRenderer.abortController.signal.addEventListener('abort', disableVr) } const xrStandardRightButtonsMap = [ diff --git a/src/appViewer.ts b/src/appViewer.ts index df2976aa8..6fb64d14a 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -5,6 +5,7 @@ import { defaultWorldRendererConfig, WorldRendererConfig } from 'renderer/viewer import { Vec3 } from 'vec3' import { SoundSystem } from 'renderer/viewer/lib/threeJsSound' import { proxy } from 'valtio' +import { getDefaultRendererState } from 'renderer/viewer/baseGraphicsBackend' import { playerState } from './mineflayer/playerState' import { createNotificationProgressReporter, ProgressReporter } from './core/progressReporter' import { setLoadingScreenStatus } from './appStatus' @@ -45,6 +46,7 @@ export interface DisplayWorldOptions { worldView: WorldDataEmitter inWorldRenderingConfig: WorldRendererConfig playerState: IPlayerState + rendererState: RendererReactiveState } export type GraphicsBackendLoader = (options: GraphicsInitOptions) => GraphicsBackend @@ -60,7 +62,6 @@ export interface GraphicsBackend { getDebugOverlay: () => Record updateCamera: (pos: Vec3 | null, yaw: number, pitch: number) => void setRoll: (roll: number) => void - reactiveState: RendererReactiveState soundSystem: SoundSystem | undefined backendMethods: Record | undefined @@ -83,6 +84,7 @@ export class AppViewer { inWorldRenderingConfig: WorldRendererConfig = proxy(defaultWorldRendererConfig) lastCamUpdate = 0 playerState = playerState + rendererState = proxy(getDefaultRendererState()) loadBackend (loader: GraphicsBackendLoader) { if (this.backend) { @@ -147,7 +149,8 @@ export class AppViewer { const displayWorldOptions: DisplayWorldOptions = { worldView: this.worldView, inWorldRenderingConfig: this.inWorldRenderingConfig, - playerState: playerStateSend + playerState: playerStateSend, + rendererState: this.rendererState } if (this.backend) { this.backend.startWorld(displayWorldOptions) @@ -216,6 +219,8 @@ const modalStackUpdateChecks = () => { const hasAppStatus = activeModalStack.some(m => m.reactType === 'app-status') appViewer.backend.setRendering(!hasAppStatus) } + + appViewer.inWorldRenderingConfig.foreground = activeModalStack.length === 0 } subscribeKey(activeModalStack, 'length', modalStackUpdateChecks) modalStackUpdateChecks() diff --git a/src/react/IndicatorEffectsProvider.tsx b/src/react/IndicatorEffectsProvider.tsx index 6086cc2f3..83b321ee0 100644 --- a/src/react/IndicatorEffectsProvider.tsx +++ b/src/react/IndicatorEffectsProvider.tsx @@ -1,5 +1,6 @@ -import { proxy, useSnapshot } from 'valtio' +import { proxy, subscribe, useSnapshot } from 'valtio' import { useEffect, useMemo } from 'react' +import { subscribeKey } from 'valtio/utils' import { inGameError } from '../utils' import { fsState } from '../loadSave' import { gameAdditionalState, miscUiState } from '../globalState' @@ -9,7 +10,6 @@ import { images } from './effectsImages' export const state = proxy({ indicators: { - chunksLoading: false }, effects: [] as EffectType[] }) @@ -52,6 +52,9 @@ const getEffectIndex = (newEffect: EffectType) => { export default () => { const stateIndicators = useSnapshot(state.indicators) + const chunksLoading = !useSnapshot(appViewer.rendererState).world.allChunksLoaded + const { mesherWork } = useSnapshot(appViewer.rendererState).world + const { hasErrors } = useSnapshot(miscUiState) const { disabledUiParts } = useSnapshot(options) const { isReadonly, openReadOperations, openWriteOperations } = useSnapshot(fsState) @@ -62,27 +65,11 @@ export default () => { readingFiles: openReadOperations > 0, appHasErrors: hasErrors, connectionIssues: poorConnection ? 1 : noConnection ? 2 : 0, + chunksLoading, + // mesherWork, ...stateIndicators, } - useEffect(() => { - let alreadyWaiting = false - const listener = () => { - if (alreadyWaiting) return - state.indicators.chunksLoading = true - alreadyWaiting = true - // void viewer.waitForChunksToRender().then(() => { - // state.indicators.chunksLoading = false - // alreadyWaiting = false - // }) - } - viewer.world.renderUpdateEmitter.on('dirty', listener) - - return () => { - viewer.world.renderUpdateEmitter.off('dirty', listener) - } - }, []) - const effects = useSnapshot(state.effects) useMemo(() => { From cae2b612baedc2d7bcb9612fe1ee1035ed756a44 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 06:19:36 +0300 Subject: [PATCH 29/41] fix all remaining reactive state linking --- renderer/viewer/baseGraphicsBackend.ts | 8 ++- renderer/viewer/lib/entities.ts | 21 +------ renderer/viewer/lib/ui/newStats.ts | 6 ++ renderer/viewer/lib/worldrendererCommon.ts | 34 +++++++---- renderer/viewer/lib/worldrendererThree.ts | 1 + renderer/viewer/three/graphicsBackend.ts | 1 + src/appViewer.ts | 5 +- src/cameraRotationControls.ts | 2 +- src/controls.ts | 67 +++++++++++----------- src/index.ts | 17 ++++-- src/react/DebugOverlay.tsx | 4 +- src/react/MinimapProvider.tsx | 32 ++++++++--- 12 files changed, 115 insertions(+), 83 deletions(-) diff --git a/renderer/viewer/baseGraphicsBackend.ts b/renderer/viewer/baseGraphicsBackend.ts index ad7b7d141..833a24c5a 100644 --- a/renderer/viewer/baseGraphicsBackend.ts +++ b/renderer/viewer/baseGraphicsBackend.ts @@ -1,8 +1,10 @@ -export const getDefaultRendererState = () => { +import { RendererReactiveState } from '../../src/appViewer' + +export const getDefaultRendererState = (): RendererReactiveState => { return { world: { - chunksLoaded: 0, - chunksTotal: 0, + chunksLoaded: [], + chunksTotalNumber: 0, allChunksLoaded: true, mesherWork: false }, diff --git a/renderer/viewer/lib/entities.ts b/renderer/viewer/lib/entities.ts index 46eea9d83..22a05b563 100644 --- a/renderer/viewer/lib/entities.ts +++ b/renderer/viewer/lib/entities.ts @@ -208,29 +208,15 @@ export type SceneEntity = THREE.Object3D & { export class Entities { entities = {} as Record - entitiesOptions: { - fontFamily?: string - } = { - fontFamily: 'mojangles' - } + entitiesOptions = { + fontFamily: 'mojangles' + } debugMode: string onSkinUpdate: () => void clock = new THREE.Clock() currentlyRendering = true cachedMapsImages = {} as Record itemFrameMaps = {} as Record>> - // getItemUv: undefined | (() => { - // texture: THREE.Texture; - // u: number; - // v: number; - // su?: number; - // sv?: number; - // size?: number; - // modelName?: string; - // } | { - // resolvedModel: BlockModel - // modelName: string - // } | undefined) get entitiesByName (): Record { const byName: Record = {} @@ -247,7 +233,6 @@ export class Entities { } constructor (public worldRenderer: WorldRendererThree) { - this.entitiesOptions = {} this.debugMode = 'none' this.onSkinUpdate = () => { } } diff --git a/renderer/viewer/lib/ui/newStats.ts b/renderer/viewer/lib/ui/newStats.ts index d18ad16c3..0425482a2 100644 --- a/renderer/viewer/lib/ui/newStats.ts +++ b/renderer/viewer/lib/ui/newStats.ts @@ -40,6 +40,12 @@ export const updateStatText = (id, text) => { stats[id].innerText = text } +export const removeStat = (id) => { + if (!stats[id]) return + stats[id].remove() + delete stats[id] +} + if (typeof customEvents !== 'undefined') { customEvents.on('gameLoaded', () => { const chunksLoaded = addNewStat('chunks-loaded', 80, 0, 0) diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 2763c747a..67e20e1e5 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -16,7 +16,7 @@ import { DisplayWorldOptions, RendererReactiveState } from '../../../src/appView import { buildCleanupDecorator } from './cleanupDecorator' import { defaultMesherConfig, HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey } from './mesher/shared' import { chunkPos } from './simpleUtils' -import { updateStatText } from './ui/newStats' +import { removeStat, updateStatText } from './ui/newStats' import { generateGuiAtlas } from './guiRenderer' import { WorldDataEmitter } from './worldDataEmitter' import { WorldRendererThree } from './worldrendererThree' @@ -179,6 +179,10 @@ export abstract class WorldRendererCommon return this.loadedChunks[chunkKey] } + async getHighestBlocks () { + return this.highestBlocks + } + updateCustomBlock (chunkKey: string, blockPos: string, model: string) { this.protocolCustomBlocks.set(chunkKey, { ...this.protocolCustomBlocks.get(chunkKey), @@ -291,13 +295,16 @@ export abstract class WorldRendererCommon checkAllFinished () { if (this.sectionsWaiting.size === 0) { - const allFinished = Object.keys(this.finishedChunks).length === this.chunksLength - if (allFinished) { - this.allChunksLoaded?.() - this.allChunksFinished = true - this.allLoadedIn ??= Date.now() - this.initialChunkLoadWasStartedIn! - } + this.reactiveState.world.mesherWork = false + } + // todo check exact surrounding chunks + const allFinished = Object.keys(this.finishedChunks).length >= this.chunksLength + if (allFinished) { + this.allChunksLoaded?.() + this.allChunksFinished = true + this.allLoadedIn ??= Date.now() - this.initialChunkLoadWasStartedIn! } + this.updateChunksStats() } changeHandSwingingState (isAnimationPlaying: boolean, isLeftHand: boolean): void { } @@ -418,7 +425,11 @@ export abstract class WorldRendererCommon return Math.floor(Math.max(this.worldSizeParams.minY, this.mesherConfig.clipWorldBelowY ?? -Infinity) / 16) * 16 } - updateChunksStatsText () { + updateChunksStats () { + const loadedChunks = Object.keys(this.finishedChunks) + this.reactiveState.world.chunksLoaded = loadedChunks + this.reactiveState.world.chunksTotalNumber = this.chunksLength + this.reactiveState.world.allChunksLoaded = this.allChunksFinished updateStatText('downloaded-chunks', `${Object.keys(this.loadedChunks).length}/${this.chunksLength} chunks D (${this.workers.length}:${this.workersProcessAverageTime.toFixed(0)}ms/${this.allLoadedIn?.toFixed(1) ?? '-'}s)`) } @@ -428,7 +439,7 @@ export abstract class WorldRendererCommon this.initialChunksLoad = false this.initialChunkLoadWasStartedIn ??= Date.now() this.loadedChunks[`${x},${z}`] = true - this.updateChunksStatsText() + this.updateChunksStats() const chunkKey = `${x},${z}` const customBlockModels = this.protocolCustomBlocks.get(chunkKey) @@ -461,6 +472,7 @@ export abstract class WorldRendererCommon } removeColumn (x, z) { + this.updateChunksStats() delete this.loadedChunks[`${x},${z}`] for (const worker of this.workers) { worker.postMessage({ type: 'unloadChunk', x, z }) @@ -675,7 +687,7 @@ export abstract class WorldRendererCommon setSectionDirty (pos: Vec3, value = true, useChangeWorker = false) { // value false is used for unloading chunks if (this.viewDistance === -1) throw new Error('viewDistance not set') - this.allChunksFinished = false + this.reactiveState.world.mesherWork = true const distance = this.getDistance(pos) 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}` @@ -767,5 +779,7 @@ export abstract class WorldRendererCommon this.renderUpdateEmitter.removeAllListeners() this.displayOptions.worldView.removeAllListeners() // todo this.abortController.abort() + removeStat('chunks-loaded') + removeStat('chunks-read') } } diff --git a/renderer/viewer/lib/worldrendererThree.ts b/renderer/viewer/lib/worldrendererThree.ts index 0d2538453..781dff22e 100644 --- a/renderer/viewer/lib/worldrendererThree.ts +++ b/renderer/viewer/lib/worldrendererThree.ts @@ -56,6 +56,7 @@ export class WorldRendererThree extends WorldRendererCommon { if (!initOptions.resourcesManager) throw new Error('resourcesManager is required') super(initOptions.resourcesManager, displayOptions, version) + displayOptions.rendererState.renderer = WorldRendererThree.getRendererInfo(renderer) ?? '...' this.starField = new StarField(this.scene) this.holdingBlock = new HoldingBlock(this) this.holdingBlockLeft = new HoldingBlock(this, true) diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 57585cc7a..46d1234d0 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -21,6 +21,7 @@ const getBackendMethods = (worldRenderer: WorldRendererThree) => { setHighlightCursorBlock: worldRenderer.cursorBlock.setHighlightCursorBlock.bind(worldRenderer.cursorBlock), updateBreakAnimation: worldRenderer.cursorBlock.updateBreakAnimation.bind(worldRenderer.cursorBlock), changeHandSwingingState: worldRenderer.changeHandSwingingState.bind(worldRenderer), + getHighestBlocks: worldRenderer.getHighestBlocks.bind(worldRenderer) } } diff --git a/src/appViewer.ts b/src/appViewer.ts index 6fb64d14a..73ec60dc8 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -16,9 +16,10 @@ import { watchOptionsAfterWorldViewInit } from './watchOptions' export interface RendererReactiveState { world: { - chunksLoaded: number - chunksTotal: number + chunksLoaded: string[] + chunksTotalNumber: number allChunksLoaded: boolean + mesherWork: boolean } renderer: string preventEscapeMenu: boolean diff --git a/src/cameraRotationControls.ts b/src/cameraRotationControls.ts index 0e4dd3441..8b21e53d7 100644 --- a/src/cameraRotationControls.ts +++ b/src/cameraRotationControls.ts @@ -77,7 +77,7 @@ function pointerLockChangeCallback () { if (notificationProxy.id === 'pointerlockchange') { hideNotification() } - if (appViewer.backend?.reactiveState.preventEscapeMenu) return + if (appViewer.rendererState.preventEscapeMenu) return if (!pointerLock.hasPointerLock && activeModalStack.length === 0 && miscUiState.gameLoaded) { showModal({ reactType: 'pause-screen' }) } diff --git a/src/controls.ts b/src/controls.ts index 325c7ac94..5e5d079c5 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -133,21 +133,21 @@ contro.on('movementUpdate', ({ vector, soleVector, gamepadIndex }) => { miscUiState.usingGamepadInput = gamepadIndex !== undefined if (!bot || !isGameActive(false)) return - if (viewer.world.freeFlyMode) { - // Create movement vector from input - const direction = new THREE.Vector3(0, 0, 0) - if (vector.z !== undefined) direction.z = vector.z - if (vector.x !== undefined) direction.x = vector.x - - // Apply camera rotation to movement direction - direction.applyQuaternion(viewer.camera.quaternion) - - // Update freeFlyState position with normalized direction - const moveSpeed = 1 - direction.multiplyScalar(moveSpeed) - viewer.world.freeFlyState.position.add(new Vec3(direction.x, direction.y, direction.z)) - return - } + // if (viewer.world.freeFlyMode) { + // // Create movement vector from input + // const direction = new THREE.Vector3(0, 0, 0) + // if (vector.z !== undefined) direction.z = vector.z + // if (vector.x !== undefined) direction.x = vector.x + + // // Apply camera rotation to movement direction + // direction.applyQuaternion(viewer.camera.quaternion) + + // // Update freeFlyState position with normalized direction + // const moveSpeed = 1 + // direction.multiplyScalar(moveSpeed) + // viewer.world.freeFlyState.position.add(new Vec3(direction.x, direction.y, direction.z)) + // return + // } // gamepadIndex will be used for splitscreen in future const coordToAction = [ @@ -355,20 +355,20 @@ const onTriggerOrReleased = (command: Command, pressed: boolean) => { // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (command) { case 'general.jump': - if (viewer.world.freeFlyMode) { - const moveSpeed = 0.5 - viewer.world.freeFlyState.position.add(new Vec3(0, pressed ? moveSpeed : 0, 0)) - } else { - bot.setControlState('jump', pressed) - } + // if (viewer.world.freeFlyMode) { + // const moveSpeed = 0.5 + // viewer.world.freeFlyState.position.add(new Vec3(0, pressed ? moveSpeed : 0, 0)) + // } else { + bot.setControlState('jump', pressed) + // } break case 'general.sneak': - if (viewer.world.freeFlyMode) { - const moveSpeed = 0.5 - viewer.world.freeFlyState.position.add(new Vec3(0, pressed ? -moveSpeed : 0, 0)) - } else { - setSneaking(pressed) - } + // if (viewer.world.freeFlyMode) { + // const moveSpeed = 0.5 + // viewer.world.freeFlyState.position.add(new Vec3(0, pressed ? -moveSpeed : 0, 0)) + // } else { + setSneaking(pressed) + // } break case 'general.sprint': // todo add setting to change behavior @@ -592,12 +592,12 @@ export const f3Keybinds: Array<{ for (const [x, z] of loadedChunks) { worldView!.unloadChunk({ x, z }) } - for (const child of viewer.scene.children) { - if (child.name === 'chunk') { // should not happen - viewer.scene.remove(child) - console.warn('forcefully removed chunk from scene') - } - } + // for (const child of viewer.scene.children) { + // if (child.name === 'chunk') { // should not happen + // viewer.scene.remove(child) + // console.warn('forcefully removed chunk from scene') + // } + // } if (localServer) { //@ts-expect-error not sure why it is private... maybe revisit api? localServer.players[0].world.columns = {} @@ -610,7 +610,6 @@ export const f3Keybinds: Array<{ key: 'KeyG', action () { options.showChunkBorders = !options.showChunkBorders - viewer.world.updateShowChunksBorder(options.showChunkBorders) }, mobileTitle: 'Toggle chunk borders', }, diff --git a/src/index.ts b/src/index.ts index fea243efe..0eef884d3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -131,6 +131,12 @@ if (process.env.SINGLE_FILE_BUILD_MODE) { loadBackend() } +const animLoop = () => { + for (const fn of beforeRenderFrame) fn() + requestAnimationFrame(animLoop) +} +requestAnimationFrame(animLoop) + watchOptionsAfterViewerInit() function hideCurrentScreens () { @@ -714,7 +720,7 @@ export async function connect (connectOptions: ConnectOptions) { const waitForChunks = async () => { if (appQueryParams.sp === '1') return //todo const waitForChunks = options.waitForChunksRender === 'sp-only' ? !!singleplayer : options.waitForChunksRender - if (!appViewer.backend || appViewer.backend.reactiveState.world.allChunksLoaded || !waitForChunks) { + if (!appViewer.backend || appViewer.rendererState.world.allChunksLoaded || !waitForChunks) { return } @@ -724,13 +730,14 @@ export async function connect (connectOptions: ConnectOptions) { async () => { await new Promise(resolve => { let wasFinished = false - const unsub = subscribe(appViewer.backend!.reactiveState, () => { + const unsub = subscribe(appViewer.rendererState, () => { if (wasFinished) return - if (appViewer.backend!.reactiveState.world.allChunksLoaded) { + if (appViewer.rendererState.world.allChunksLoaded) { wasFinished = true resolve() + unsub() } else { - const perc = Math.round(appViewer.backend!.reactiveState.world.chunksLoaded / appViewer.backend!.reactiveState.world.chunksTotal * 100) + const perc = Math.round(appViewer.rendererState.world.chunksLoaded.length / appViewer.rendererState.world.chunksTotalNumber * 100) progress.reportProgress('chunks', perc / 100) } }) @@ -739,7 +746,7 @@ export async function connect (connectOptions: ConnectOptions) { ) } - // await waitForChunks() + await waitForChunks() setTimeout(() => { if (appQueryParams.suggest_save) { diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index 3c05ed994..9a0c4c655 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -41,8 +41,7 @@ export default () => { const [blockInfo, setBlockInfo] = useState<{ customBlockName?: string, modelInfo?: BlockStateModelInfo } | null>(null) const minecraftYaw = useRef(0) const minecraftQuad = useRef(0) - const { reactiveState } = appViewer.backend ?? {} - const rendererDevice = reactiveState?.renderer ?? 'No render backend' + const rendererDevice = appViewer.rendererState.renderer ?? 'No render backend' const quadsDescription = [ 'north (towards negative Z)', @@ -166,6 +165,7 @@ export default () => {
+

Backend: {appViewer.backend?.NAME}

Renderer: {rendererDevice}

{cursorBlock ? (<> diff --git a/src/react/MinimapProvider.tsx b/src/react/MinimapProvider.tsx index 10a9c738d..b1d39e805 100644 --- a/src/react/MinimapProvider.tsx +++ b/src/react/MinimapProvider.tsx @@ -11,6 +11,8 @@ import { Block } from 'prismarine-block' import { INVISIBLE_BLOCKS } from 'renderer/viewer/lib/mesher/worldConstants' import { getRenamedData } from 'flying-squid/dist/blockRenames' import { useSnapshot } from 'valtio' +import { subscribeKey } from 'valtio/utils' +import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import BlockData from '../../renderer/viewer/lib/moreBlockDataGenerated.json' import preflatMap from '../preflatMap.json' import { contro } from '../controls' @@ -54,6 +56,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements loadChunkFullmap: (key: string) => Promise _full = false isBuiltinHeightmapAvailable = false + unsubscribers: Array<() => void> = [] constructor (pos?: Vec3) { super() @@ -111,13 +114,22 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements this.blockData.set(renamedKey, BlockData.colors[blockKey]) } - viewer.world?.renderUpdateEmitter.on('chunkFinished', (key) => { - if (!this.loadingChunksQueue.has(key)) return - this.loadingChunksQueue.delete(key) - void this.loadChunk(key) + subscribeKey(appViewer.rendererState, 'world', () => { + for (const key of this.loadingChunksQueue) { + if (appViewer.rendererState.world.chunksLoaded.includes(key)) { + this.loadingChunksQueue.delete(key) + void this.loadChunk(key) + } + } }) } + destroy () { + for (const unsubscriber of this.unsubscribers) { + unsubscriber() + } + } + get full () { return this._full } @@ -188,7 +200,9 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements const [chunkX, chunkZ] = key.split(',').map(Number) const chunkWorldX = chunkX * 16 const chunkWorldZ = chunkZ * 16 - if (viewer.world.finishedChunks[`${chunkWorldX},${chunkWorldZ}`]) { + if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) { + const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks() + if (!highestBlocks) return undefined const heightmap = new Uint8Array(256) const colors = Array.from({ length: 256 }).fill('') as string[] // avoid creating new object every time @@ -198,7 +212,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements for (let x = 0; x < 16; x += 1) { const blockX = chunkWorldX + x const blockZ = chunkWorldZ + z - const hBlock = viewer.world.highestBlocks.get(`${blockX},${blockZ}`) + const hBlock = highestBlocks.get(`${blockX},${blockZ}`) blockPos.x = blockX; blockPos.z = blockZ; blockPos.y = hBlock?.y ?? 0 let block = bot.world.getBlock(blockPos) while (block?.name.includes('air')) { @@ -319,14 +333,16 @@ export class DrawerAdapterImpl extends TypedEventEmitter implements const [chunkX, chunkZ] = key.split(',').map(Number) const chunkWorldX = chunkX * 16 const chunkWorldZ = chunkZ * 16 - if (viewer.world.finishedChunks[`${chunkWorldX},${chunkWorldZ}`]) { + const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks() + if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) { const heightmap = new Uint8Array(256) const colors = Array.from({ length: 256 }).fill('') as string[] + if (!highestBlocks) return null for (let z = 0; z < 16; z += 1) { for (let x = 0; x < 16; x += 1) { const blockX = chunkWorldX + x const blockZ = chunkWorldZ + z - const hBlock = viewer.world.highestBlocks.get(`${blockX},${blockZ}`) + const hBlock = highestBlocks.get(`${blockX},${blockZ}`) const block = bot.world.getBlock(new Vec3(blockX, hBlock?.y ?? 0, blockZ)) // const block = Block.fromStateId(hBlock?.stateId ?? -1, hBlock?.biomeId ?? -1) const index = z * 16 + x From b483923009863e8b0ba728ce4165f8f5caa3f704 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 06:22:30 +0300 Subject: [PATCH 30/41] rm unused three imports --- renderer/viewer/three/graphicsBackend.ts | 1 + src/controls.ts | 2 -- src/index.ts | 2 -- src/mineflayer/cameraShake.ts | 2 -- src/react/DebugOverlay.tsx | 6 ++---- src/water.ts | 1 - 6 files changed, 3 insertions(+), 11 deletions(-) diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 46d1234d0..124d402d3 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -9,6 +9,7 @@ import { PanoramaRenderer } from './panorama' // https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 THREE.ColorManagement.enabled = false +window.THREE = THREE const getBackendMethods = (worldRenderer: WorldRendererThree) => { return { diff --git a/src/controls.ts b/src/controls.ts index 5e5d079c5..b91ae7ab7 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -2,12 +2,10 @@ import { Vec3 } from 'vec3' import { proxy, subscribe } from 'valtio' -import * as THREE from 'three' import { ControMax } from 'contro-max/build/controMax' import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types' import { stringStartsWith } from 'contro-max/build/stringUtils' -import { UserOverrideCommand, UserOverridesConfig } from 'contro-max/build/types/store' import { GameMode } from 'mineflayer' import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCurrentModal, miscUiState, hideModal, hideAllModals } from './globalState' import { goFullscreen, isInRealGameSession, pointerLock, reloadChunks } from './utils' diff --git a/src/index.ts b/src/index.ts index 0eef884d3..06447ee4c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,6 @@ import fs from 'fs' import net from 'net' import mineflayer from 'mineflayer' -import * as THREE from 'three' import debug from 'debug' import { defaultsDeep } from 'lodash-es' import initializePacketsReplay from './packetsReplay/packetsReplayLegacy' @@ -99,7 +98,6 @@ import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend' import { subscribeKey } from 'valtio/utils' window.debug = debug -window.THREE = THREE window.beforeRenderFrame = [] // ACTUAL CODE diff --git a/src/mineflayer/cameraShake.ts b/src/mineflayer/cameraShake.ts index d0639cd87..f11192b86 100644 --- a/src/mineflayer/cameraShake.ts +++ b/src/mineflayer/cameraShake.ts @@ -1,5 +1,3 @@ -import * as THREE from 'three' - class CameraShake { private rollAngle = 0 private get damageRollAmount () { return 5 } diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index 9a0c4c655..89c6bf574 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -1,11 +1,9 @@ -import { useEffect, useRef, useMemo, useState } from 'react' -import * as THREE from 'three' +import { useEffect, useRef, useState } from 'react' import type { Block } from 'prismarine-block' -import { proxy, useSnapshot } from 'valtio' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { getFixedFilesize } from '../downloadAndOpenFile' import { options } from '../optionsStorage' -import { BlockStateModelInfo, getBlockAssetsCacheKey } from '../../renderer/viewer/lib/mesher/shared' +import { BlockStateModelInfo } from '../../renderer/viewer/lib/mesher/shared' import styles from './DebugOverlay.module.css' export default () => { diff --git a/src/water.ts b/src/water.ts index 318e7c0d8..9221ec8a2 100644 --- a/src/water.ts +++ b/src/water.ts @@ -1,4 +1,3 @@ -import * as THREE from 'three' import { watchUnloadForCleanup } from './gameUnload' let inWater = false From dc2ad7ccce225f1907d876817a69c5dc0edba051 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 06:36:15 +0300 Subject: [PATCH 31/41] disable signs, disable playground since its not used by anyone --- renderer/playground/baseScene.ts | 1 + renderer/playground/scenes/allEntities.ts | 1 + renderer/playground/scenes/frequentUpdates.ts | 1 + .../playground/scenes/lightingStarfield.ts | 1 + renderer/playground/scenes/main.ts | 1 + src/react/ModuleSignsViewer.tsx | 31 ++++++++++--------- 6 files changed, 21 insertions(+), 15 deletions(-) diff --git a/renderer/playground/baseScene.ts b/renderer/playground/baseScene.ts index 02a6432f3..b9e7791da 100644 --- a/renderer/playground/baseScene.ts +++ b/renderer/playground/baseScene.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import { Vec3 } from 'vec3' import * as THREE from 'three' import '../../src/getCollisionShapes' diff --git a/renderer/playground/scenes/allEntities.ts b/renderer/playground/scenes/allEntities.ts index 7fa0b6eb5..c7a270ae0 100644 --- a/renderer/playground/scenes/allEntities.ts +++ b/renderer/playground/scenes/allEntities.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import { BasePlaygroundScene } from '../baseScene' import { EntityDebugFlags, EntityMesh, rendererSpecialHandled } from '../../viewer/lib/entity/EntityMesh' diff --git a/renderer/playground/scenes/frequentUpdates.ts b/renderer/playground/scenes/frequentUpdates.ts index bc4012557..caaf72076 100644 --- a/renderer/playground/scenes/frequentUpdates.ts +++ b/renderer/playground/scenes/frequentUpdates.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import { Vec3 } from 'vec3' import { BasePlaygroundScene } from '../baseScene' diff --git a/renderer/playground/scenes/lightingStarfield.ts b/renderer/playground/scenes/lightingStarfield.ts index 4b259b897..48669d790 100644 --- a/renderer/playground/scenes/lightingStarfield.ts +++ b/renderer/playground/scenes/lightingStarfield.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import * as THREE from 'three' import { Vec3 } from 'vec3' import { BasePlaygroundScene } from '../baseScene' diff --git a/renderer/playground/scenes/main.ts b/renderer/playground/scenes/main.ts index 2dedbd062..057fe2ede 100644 --- a/renderer/playground/scenes/main.ts +++ b/renderer/playground/scenes/main.ts @@ -1,3 +1,4 @@ +//@ts-nocheck // eslint-disable-next-line import/no-named-as-default import GUI, { Controller } from 'lil-gui' import * as THREE from 'three' diff --git a/src/react/ModuleSignsViewer.tsx b/src/react/ModuleSignsViewer.tsx index 70b42ba04..2cc9ad695 100644 --- a/src/react/ModuleSignsViewer.tsx +++ b/src/react/ModuleSignsViewer.tsx @@ -7,21 +7,22 @@ export const name = 'loaded world signs' export default () => { const [selected, setSelected] = useState([] as string[]) const allSignsPos = [] as string[] - const signs = viewer.world instanceof WorldRendererThree ? [...viewer.world.chunkTextures.values()].flatMap(textures => { - return Object.entries(textures).map(([signPosKey, texture]) => { - allSignsPos.push(signPosKey) - const pos = signPosKey.split(',').map(Number) - const isSelected = selected.includes(signPosKey) - return
-
{pos.join(', ')}
-
setSelected(selected.includes(signPosKey) ? selected.filter(x => x !== signPosKey) : [...selected, signPosKey])}> - -
-
- }) - }) : [] + const signs = [] + // const signs = viewer.world instanceof WorldRendererThree ? [...viewer.world.chunkTextures.values()].flatMap(textures => { + // return Object.entries(textures).map(([signPosKey, texture]) => { + // allSignsPos.push(signPosKey) + // const pos = signPosKey.split(',').map(Number) + // const isSelected = selected.includes(signPosKey) + // return
+ //
{pos.join(', ')}
+ //
setSelected(selected.includes(signPosKey) ? selected.filter(x => x !== signPosKey) : [...selected, signPosKey])}> + // + //
+ //
+ // }) + // }) : [] return
From 8ee4dc37e7f564d7d806c73d1d996d063662c633 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 07:07:26 +0300 Subject: [PATCH 32/41] disable displaying unknown non interactible entitites --- renderer/viewer/lib/entities.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/entities.ts b/renderer/viewer/lib/entities.ts index 22a05b563..39045b4b2 100644 --- a/renderer/viewer/lib/entities.ts +++ b/renderer/viewer/lib/entities.ts @@ -14,6 +14,7 @@ import mojangson from 'mojangson' import { snakeCase } from 'change-case' import { Item } from 'prismarine-item' import { BlockModel } from 'mc-assets' +import { isEntityAttackable } from 'mineflayer-mouse/dist/attackableEntity' import { EntityMetadataVersions } from '../../../src/mcDataTypes' import * as Entity from './entity/EntityMesh' import { getMesh } from './entity/EntityMesh' @@ -167,7 +168,7 @@ const nametags = {} const isFirstUpperCase = (str) => str.charAt(0) === str.charAt(0).toUpperCase() -function getEntityMesh (entity, world, options, overrides) { +function getEntityMesh (entity: import('prismarine-entity').Entity & { delete?: any; pos: any; name: any }, world: WorldRendererThree | undefined, options: { fontFamily: string }, overrides) { if (entity.name) { try { // https://github.com/PrismarineJS/prismarine-viewer/pull/410 @@ -183,6 +184,7 @@ function getEntityMesh (entity, world, options, overrides) { } } + if (!isEntityAttackable(loadedData, entity)) return const geometry = new THREE.BoxGeometry(entity.width, entity.height, entity.width) geometry.translate(0, entity.height / 2, 0) const material = new THREE.MeshBasicMaterial({ color: 0xff_00_ff }) @@ -1018,7 +1020,7 @@ export class Entities { const itemObject = this.getItemMesh(item, { 'minecraft:display_context': 'thirdperson', }) - if (itemObject) { + if (itemObject?.mesh) { entityMesh.traverse(c => { if (c.name.toLowerCase() === parentName) { const group = new THREE.Object3D() From 11abbfcbb12b1d272eb5c76a9053ca5d1d27cfc1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 07:07:56 +0300 Subject: [PATCH 33/41] disable check --- renderer/playground/scenes/entities.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/renderer/playground/scenes/entities.ts b/renderer/playground/scenes/entities.ts index 11b1591af..ef4035a90 100644 --- a/renderer/playground/scenes/entities.ts +++ b/renderer/playground/scenes/entities.ts @@ -1,3 +1,4 @@ +//@ts-nocheck import * as THREE from 'three' import { Vec3 } from 'vec3' import { BasePlaygroundScene } from '../baseScene' From 5eedb3c456220d13d91171dbf01c6f21d2c8b260 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 10:59:46 +0300 Subject: [PATCH 34/41] disable playground --- .github/workflows/ci.yml | 2 +- .github/workflows/next-deploy.yml | 10 +++++----- .github/workflows/preview.yml | 10 +++++----- .github/workflows/publish.yml | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92b7e7f3f..332f8e205 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: pnpm/action-setup@v4 - run: pnpm install - run: pnpm check-build - - run: pnpm build-playground + # - run: pnpm build-playground - run: pnpm build-storybook - run: pnpm test-unit - run: pnpm lint diff --git a/.github/workflows/next-deploy.yml b/.github/workflows/next-deploy.yml index 042302a41..c398b68e3 100644 --- a/.github/workflows/next-deploy.yml +++ b/.github/workflows/next-deploy.yml @@ -35,11 +35,11 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - - name: Copy playground files - run: | - mkdir -p .vercel/output/static/playground - pnpm build-playground - cp -r renderer/dist/* .vercel/output/static/playground/ + # - name: Copy playground files + # run: | + # mkdir -p .vercel/output/static/playground + # pnpm build-playground + # cp -r renderer/dist/* .vercel/output/static/playground/ - name: Download Generated Sounds map run: node scripts/downloadSoundsMap.mjs - name: Deploy Project Artifacts to Vercel diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 6408c86a1..a13f34c70 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -64,11 +64,11 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - - name: Copy playground files - run: | - mkdir -p .vercel/output/static/playground - pnpm build-playground - cp -r renderer/dist/* .vercel/output/static/playground/ + # - name: Copy playground files + # run: | + # mkdir -p .vercel/output/static/playground + # pnpm build-playground + # cp -r renderer/dist/* .vercel/output/static/playground/ - name: Write pr redirect index.html run: | mkdir -p .vercel/output/static/pr diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 986bf6cc5..4f7f75002 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,11 +33,11 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - - name: Copy playground files - run: | - mkdir -p .vercel/output/static/playground - pnpm build-playground - cp -r renderer/dist/* .vercel/output/static/playground/ + # - name: Copy playground files + # run: | + # mkdir -p .vercel/output/static/playground + # pnpm build-playground + # cp -r renderer/dist/* .vercel/output/static/playground/ - name: Download Generated Sounds map run: node scripts/downloadSoundsMap.mjs - name: Deploy Project to Vercel From 8ddac9741411b1321659182b62a1f5e2160979f8 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 11:02:30 +0300 Subject: [PATCH 35/41] dont use bot --- renderer/viewer/lib/entities.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/entities.ts b/renderer/viewer/lib/entities.ts index 39045b4b2..678343d8b 100644 --- a/renderer/viewer/lib/entities.ts +++ b/renderer/viewer/lib/entities.ts @@ -15,6 +15,7 @@ import { snakeCase } from 'change-case' import { Item } from 'prismarine-item' import { BlockModel } from 'mc-assets' import { isEntityAttackable } from 'mineflayer-mouse/dist/attackableEntity' +import { Vec3 } from 'vec3' import { EntityMetadataVersions } from '../../../src/mcDataTypes' import * as Entity from './entity/EntityMesh' import { getMesh } from './entity/EntityMesh' @@ -923,8 +924,9 @@ export class Entities { if (!mesh) return if (!mesh.playerObject || !this.worldRenderer.worldRendererConfig.fetchPlayerSkins) return const MAX_DISTANCE_SKIN_LOAD = 128 - const distance = entity.position.distanceTo(bot.entity.position) - if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (bot.settings.viewDistance as number) * 16) { + const caameraPos = this.worldRenderer.camera.position + const distance = entity.position.distanceTo(new Vec3(caameraPos.x, caameraPos.y, caameraPos.z)) + if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (this.worldRenderer.viewDistance * 16)) { if (this.entities[entity.id]) { if (this.loadedSkinEntityIds.has(entity.id)) return this.loadedSkinEntityIds.add(entity.id) From 853e0e1d8467e4f1a8399da5e2ab2e3a1fc8323a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 11:05:19 +0300 Subject: [PATCH 36/41] up readme --- README.MD | 1 + 1 file changed, 1 insertion(+) diff --git a/README.MD b/README.MD index 90e8f35fa..67e463335 100644 --- a/README.MD +++ b/README.MD @@ -24,6 +24,7 @@ For building the project yourself / contributing, see [Development, Debugging & - Custom protocol channel extensions (eg for custom block models in the world) - Play with friends over internet! (P2P is powered by Peer.js discovery servers) - ~~Google Drive support for reading / saving worlds back to the cloud~~ +- Support for custom rendering 3D engines. Modular architecture. - even even more! All components that are in [Storybook](https://mcraft.fun/storybook) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react) From d450a315476aaa33a6ad1675194a362757f2fafd Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 11:27:12 +0300 Subject: [PATCH 37/41] fix typo --- renderer/viewer/lib/entities.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/renderer/viewer/lib/entities.ts b/renderer/viewer/lib/entities.ts index 678343d8b..daeb090ec 100644 --- a/renderer/viewer/lib/entities.ts +++ b/renderer/viewer/lib/entities.ts @@ -924,8 +924,8 @@ export class Entities { if (!mesh) return if (!mesh.playerObject || !this.worldRenderer.worldRendererConfig.fetchPlayerSkins) return const MAX_DISTANCE_SKIN_LOAD = 128 - const caameraPos = this.worldRenderer.camera.position - const distance = entity.position.distanceTo(new Vec3(caameraPos.x, caameraPos.y, caameraPos.z)) + const cameraPos = this.worldRenderer.camera.position + const distance = entity.position.distanceTo(new Vec3(cameraPos.x, cameraPos.y, cameraPos.z)) if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (this.worldRenderer.viewDistance * 16)) { if (this.entities[entity.id]) { if (this.loadedSkinEntityIds.has(entity.id)) return From b579ee1767e10673b5bd3f415b46e3d0b4e6c1c4 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 13:48:39 +0300 Subject: [PATCH 38/41] Revert "disable playground" This reverts commit 5eedb3c456220d13d91171dbf01c6f21d2c8b260. --- .github/workflows/ci.yml | 2 +- .github/workflows/next-deploy.yml | 10 +++++----- .github/workflows/preview.yml | 10 +++++----- .github/workflows/publish.yml | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 332f8e205..92b7e7f3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: pnpm/action-setup@v4 - run: pnpm install - run: pnpm check-build - # - run: pnpm build-playground + - run: pnpm build-playground - run: pnpm build-storybook - run: pnpm test-unit - run: pnpm lint diff --git a/.github/workflows/next-deploy.yml b/.github/workflows/next-deploy.yml index c398b68e3..042302a41 100644 --- a/.github/workflows/next-deploy.yml +++ b/.github/workflows/next-deploy.yml @@ -35,11 +35,11 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - # - name: Copy playground files - # run: | - # mkdir -p .vercel/output/static/playground - # pnpm build-playground - # cp -r renderer/dist/* .vercel/output/static/playground/ + - name: Copy playground files + run: | + mkdir -p .vercel/output/static/playground + pnpm build-playground + cp -r renderer/dist/* .vercel/output/static/playground/ - name: Download Generated Sounds map run: node scripts/downloadSoundsMap.mjs - name: Deploy Project Artifacts to Vercel diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index a13f34c70..6408c86a1 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -64,11 +64,11 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - # - name: Copy playground files - # run: | - # mkdir -p .vercel/output/static/playground - # pnpm build-playground - # cp -r renderer/dist/* .vercel/output/static/playground/ + - name: Copy playground files + run: | + mkdir -p .vercel/output/static/playground + pnpm build-playground + cp -r renderer/dist/* .vercel/output/static/playground/ - name: Write pr redirect index.html run: | mkdir -p .vercel/output/static/pr diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4f7f75002..986bf6cc5 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -33,11 +33,11 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED - run: pnpm build-storybook - # - name: Copy playground files - # run: | - # mkdir -p .vercel/output/static/playground - # pnpm build-playground - # cp -r renderer/dist/* .vercel/output/static/playground/ + - name: Copy playground files + run: | + mkdir -p .vercel/output/static/playground + pnpm build-playground + cp -r renderer/dist/* .vercel/output/static/playground/ - name: Download Generated Sounds map run: node scripts/downloadSoundsMap.mjs - name: Deploy Project to Vercel From ed041972c44ff9cdb05b480beedd9776046df189 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 13:50:11 +0300 Subject: [PATCH 39/41] disable in other way --- renderer/playground/playground.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/renderer/playground/playground.ts b/renderer/playground/playground.ts index a233bca2e..de201d8fb 100644 --- a/renderer/playground/playground.ts +++ b/renderer/playground/playground.ts @@ -1,11 +1,12 @@ -import { BasePlaygroundScene } from './baseScene' -import { playgroundGlobalUiState } from './playgroundUi' -import * as scenes from './scenes' +if (!new URL(location.href).searchParams.get('playground')) location.href = '/?playground=true' +// import { BasePlaygroundScene } from './baseScene' +// import { playgroundGlobalUiState } from './playgroundUi' +// import * as scenes from './scenes' -const qsScene = new URLSearchParams(window.location.search).get('scene') -const Scene: typeof BasePlaygroundScene = qsScene ? scenes[qsScene] : scenes.main -playgroundGlobalUiState.scenes = ['main', 'railsCobweb', 'floorRandom', 'lightingStarfield', 'transparencyIssue', 'entities', 'frequentUpdates', 'slabsOptimization', 'allEntities'] -playgroundGlobalUiState.selected = qsScene ?? 'main' +// const qsScene = new URLSearchParams(window.location.search).get('scene') +// const Scene: typeof BasePlaygroundScene = qsScene ? scenes[qsScene] : scenes.main +// playgroundGlobalUiState.scenes = ['main', 'railsCobweb', 'floorRandom', 'lightingStarfield', 'transparencyIssue', 'entities', 'frequentUpdates', 'slabsOptimization', 'allEntities'] +// playgroundGlobalUiState.selected = qsScene ?? 'main' -const scene = new Scene() -globalThis.scene = scene +// const scene = new Scene() +// globalThis.scene = scene From e917764b765453dd4aa86da5d46dbf7307ead4fd Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Mar 2025 13:58:53 +0300 Subject: [PATCH 40/41] fix lint --- renderer/viewer/three/documentRenderer.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/renderer/viewer/three/documentRenderer.ts b/renderer/viewer/three/documentRenderer.ts index c7783322c..29e43ee93 100644 --- a/renderer/viewer/three/documentRenderer.ts +++ b/renderer/viewer/three/documentRenderer.ts @@ -1,10 +1,8 @@ import * as THREE from 'three' import Stats from 'stats.js' import StatsGl from 'stats-gl' -import tween from '@tweenjs/tween.js' +import * as tween from '@tweenjs/tween.js' import { GraphicsBackendConfig, GraphicsInitOptions } from '../../../src/appViewer' -import { activeModalStack } from '../../../src/globalState' -import { isCypress } from '../../../src/standaloneUtils' export class DocumentRenderer { readonly canvas = document.createElement('canvas') From b501893ab2ae17733afa65d88d7f0fd5f5ff7cb3 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sun, 23 Mar 2025 03:13:52 +0000 Subject: [PATCH 41/41] ip pkgs --- pnpm-lock.yaml | 110 ++++++++++++++++++------------------------------- 1 file changed, 39 insertions(+), 71 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6ae4ef97..472eb4cf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,13 +136,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.14 - version: 0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/b56898490fb26dd78f532d916274b0e928953080(encoding@0.1.13)) + version: 0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/729e4ba78d36f298665eb173675abc19d3db4c31(encoding@0.1.13)) minecraft-data: specifier: 3.83.1 version: 3.83.1 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(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=bck55yjvd4wrgz46x7o4vfur5q)(encoding@0.1.13) @@ -360,7 +360,7 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/75e940a4cd50d89e0ba03db3733d5d704917a3c8(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:GenerelSchwerz/mineflayer - version: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/b56898490fb26dd78f532d916274b0e928953080(encoding@0.1.13) + version: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/729e4ba78d36f298665eb173675abc19d3db4c31(encoding@0.1.13) mineflayer-mouse: specifier: ^0.1.2 version: 0.1.2(@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: 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/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) @@ -2004,8 +2004,8 @@ packages: '@nxg-org/mineflayer-auto-jump@0.7.12': resolution: {integrity: sha512-F5vX/lerlWx/5HVlkDNbvrtQ19PL6iG8i4ItPTIRtjGiFzusDefP7DI226zSFR8Wlaw45qHv0jn814p/4/qVdQ==} - '@nxg-org/mineflayer-physics-util@1.8.0': - resolution: {integrity: sha512-Den3xFFcQd2oEqc3lzbzHV9EzKM6dBtzTwP6h9uXgsVabcj2UxYSebfk77CkTn9DpTKZ69IKj4FjsmAEE+mOgg==} + '@nxg-org/mineflayer-physics-util@1.8.1': + resolution: {integrity: sha512-ncttlkrI6nek6/qkb3fOIUm9o9jSPl56NuRfLqDorlEqJac3Bdrf2YxlMFW9axkTRfcM7p1OBD3dnf2wpw+jzw==} '@nxg-org/mineflayer-tracker@1.2.1': resolution: {integrity: sha512-SI1ffF8zvg3/ZNE021Ja2W0FZPN+WbQDZf8yFqOcXtPRXAtM9W6HvoACdzXep8BZid7WYgYLIgjKpB+9RqvCNQ==} @@ -6767,11 +6767,6 @@ packages: version: 1.54.0 engines: {node: '>=22'} - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d} - version: 1.54.0 - engines: {node: '>=22'} - minecraft-wrap@1.6.0: resolution: {integrity: sha512-A1GjIR72x9H9cEaxAQsXZe5uhw7CPgq1pGwYkdbPe6mQraePinmj/jRIuntXYWEKrYamwQFT3igIafA+PEG11w==} hasBin: true @@ -6790,14 +6785,14 @@ packages: mineflayer-pathfinder@2.4.5: resolution: {integrity: sha512-Jh3JnUgRLwhMh2Dugo4SPza68C41y+NPP5sdsgxRu35ydndo70i1JJGxauVWbXrpNwIxYNztUw78aFyb7icw8g==} - mineflayer@4.25.0: - resolution: {integrity: sha512-q7cmpZFaSI6sodcMJxc2GkV8IO84HbsUP+xNipGKfGg+FMISKabzdJ838Axb60qRtZrp6ny7LluQE7lesHvvxQ==} - engines: {node: '>=18'} + mineflayer@4.26.0: + resolution: {integrity: sha512-1mCuyqIJUieq/ul7s7UUvcUvycYEMN7GFL5iCUB8DraDsJhj1waP74WkaMUMOKmNukv72T8+6S9O0Jaxer1QHw==} + engines: {node: '>=22'} - mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/b56898490fb26dd78f532d916274b0e928953080: - resolution: {tarball: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/b56898490fb26dd78f532d916274b0e928953080} - version: 4.25.0 - engines: {node: '>=18'} + mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/729e4ba78d36f298665eb173675abc19d3db4c31: + resolution: {tarball: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/729e4ba78d36f298665eb173675abc19d3db4c31} + version: 4.26.0 + engines: {node: '>=22'} minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -7097,8 +7092,8 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} - object.entries@1.1.8: - resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} engines: {node: '>= 0.4'} object.fromentries@2.0.8: @@ -11437,10 +11432,10 @@ snapshots: '@nxg-org/mineflayer-auto-jump@0.7.12': dependencies: - '@nxg-org/mineflayer-physics-util': 1.8.0 + '@nxg-org/mineflayer-physics-util': 1.8.1 strict-event-emitter-types: 2.0.0 - '@nxg-org/mineflayer-physics-util@1.8.0': + '@nxg-org/mineflayer-physics-util@1.8.1': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 @@ -11456,8 +11451,8 @@ snapshots: dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 minecraft-data: 3.83.1 - mineflayer: 4.25.0(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + mineflayer: 4.26.0(encoding@0.1.13) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.16.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -13385,7 +13380,7 @@ snapshots: flatmap: 0.0.3 long: 5.3.1 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) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 @@ -15405,7 +15400,7 @@ snapshots: hasown: 2.0.2 jsx-ast-utils: 3.3.5 minimatch: 3.1.2 - object.entries: 1.1.8 + object.entries: 1.1.9 object.fromentries: 2.0.8 object.values: 1.2.1 prop-types: 15.8.1 @@ -17238,12 +17233,12 @@ snapshots: maxrects-packer: 2.7.3 zod: 3.24.2 - mcraft-fun-mineflayer@0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/b56898490fb26dd78f532d916274b0e928953080(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/729e4ba78d36f298665eb173675abc19d3db4c31(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/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) - mineflayer: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/b56898490fb26dd78f532d916274b0e928953080(encoding@0.1.13) + mineflayer: https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/729e4ba78d36f298665eb173675abc19d3db4c31(encoding@0.1.13) prismarine-item: 1.16.0 ws: 8.18.1 transitivePeerDependencies: @@ -17580,32 +17575,6 @@ snapshots: - encoding - supports-color - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13): - dependencies: - '@types/node-rsa': 1.1.4 - '@types/readable-stream': 4.0.18 - aes-js: 3.1.2 - buffer-equal: 1.0.1 - debug: 4.4.0(supports-color@8.1.1) - endian-toggle: 0.0.0 - lodash.get: 4.4.2 - lodash.merge: 4.6.2 - minecraft-data: 3.83.1 - minecraft-folder-path: 1.2.0 - node-fetch: 2.7.0(encoding@0.1.13) - node-rsa: 0.4.2 - prismarine-auth: 2.7.0 - prismarine-chat: 1.11.0 - prismarine-nbt: 2.7.0 - prismarine-realms: 1.3.2(encoding@0.1.13) - protodef: 1.18.0 - readable-stream: 4.7.0 - uuid-1345: 1.0.2 - yggdrasil: 1.7.0(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - supports-color - minecraft-wrap@1.6.0(encoding@0.1.13): dependencies: adm-zip: 0.5.16 @@ -17633,7 +17602,7 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=bck55yjvd4wrgz46x7o4vfur5q)(encoding@0.1.13): dependencies: - mineflayer: 4.25.0(encoding@0.1.13) + mineflayer: 4.26.0(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding @@ -17670,19 +17639,19 @@ snapshots: mineflayer-pathfinder@2.4.5: dependencies: minecraft-data: 3.83.1 - 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-entity: 2.5.0 prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 - mineflayer@4.25.0(encoding@0.1.13): + mineflayer@4.26.0(encoding@0.1.13): dependencies: minecraft-data: 3.83.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(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-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-entity: 2.5.0 @@ -17700,13 +17669,13 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/b56898490fb26dd78f532d916274b0e928953080(encoding@0.1.13): + mineflayer@https://codeload.github.com/GenerelSchwerz/mineflayer/tar.gz/729e4ba78d36f298665eb173675abc19d3db4c31(encoding@0.1.13): dependencies: - '@nxg-org/mineflayer-physics-util': 1.8.0 + '@nxg-org/mineflayer-physics-util': 1.8.1 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) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1(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-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-entity: 2.5.0 @@ -18092,9 +18061,10 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 - object.entries@1.1.8: + object.entries@1.1.9: dependencies: call-bind: 1.0.8 + call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -18494,7 +18464,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-registry@1.11.0): + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: minecraft-data: 3.83.1 prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0) @@ -18502,8 +18472,6 @@ snapshots: prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - transitivePeerDependencies: - - prismarine-registry prismarine-chat@1.11.0: dependencies: @@ -18514,7 +18482,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-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 @@ -18548,7 +18516,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-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/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18572,13 +18540,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-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: dependencies: minecraft-data: 3.83.1 - 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