diff --git a/.eslintrc.json b/.eslintrc.json index 63f6749a..3552f6a7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,7 +23,6 @@ // ], "@stylistic/arrow-spacing": "error", "@stylistic/block-spacing": "error", - "@typescript-eslint/no-this-alias": "off", "@stylistic/brace-style": [ "error", "1tbs", diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index e80b7100..f913b9b6 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -26,7 +26,7 @@ jobs: uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 18 cache: "pnpm" - name: Move Cypress to dependencies run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3e8c4136..cbf52251 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,20 +49,6 @@ jobs: publish_dir: .vercel/output/static force_orphan: true - # Create CNAME file for custom domain - - name: Create CNAME file - run: echo "github.mcraft.fun" > .vercel/output/static/CNAME - - - name: Deploy to mwc-mcraft-pages repository - uses: peaceiris/actions-gh-pages@v3 - with: - personal_token: ${{ secrets.MCW_MCRAFT_PAGE_DEPLOY_TOKEN }} - external_repository: ${{ github.repository_owner }}/mwc-mcraft-pages - publish_dir: .vercel/output/static - publish_branch: main - destination_dir: docs - force_orphan: true - - name: Change index.html title run: | # change Minecraft Web Client to Minecraft Web Client — Free Online Browser Version diff --git a/README.MD b/README.MD index 018784e3..61a5b733 100644 --- a/README.MD +++ b/README.MD @@ -14,6 +14,7 @@ For building the project yourself / contributing, see [Development, Debugging & > **Note**: You can deploy it on your own server in less than a minute using a one-liner script from [Minecraft Everywhere repo](https://github.com/zardoy/minecraft-everywhere) + ### Big Features - Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely. @@ -54,9 +55,8 @@ Howerver, it's known that these browsers have issues: ### Versions Support -Server versions 1.8 - 1.21.5 are supported. +Server versions 1.8 - 1.21.4 are supported. First class versions (most of the features are tested on these versions): - - 1.19.4 - 1.21.4 @@ -78,8 +78,6 @@ There is a builtin proxy, but you can also host your one! Just clone the repo, r [![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F) -> **Note**: If you want to make **your own** Minecraft server accessible to web clients (without our proxies), you can use [mwc-proxy](https://github.com/zardoy/mwc-proxy) - a lightweight JS WebSocket proxy that runs on the same server as your Minecraft server, allowing players to connect directly via `wss://play.example.com`. `?client_mcraft` is added to the URL, so the proxy will know that it's this client. - Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser. ```mermaid @@ -127,11 +125,11 @@ There is world renderer playground ([link](https://mcon.vercel.app/playground/)) However, there are many things that can be done in online production version (like debugging actual source code). Also you can access some global variables in the console and there are a few useful examples: -- If you type `debugToggle`, press enter in console - It will enables all debug messages! Warning: this will start all packets spam. +- `localStorage.debug = '*'` - Enables all debug messages! Warning: this will start all packets spam. Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can use `debugTopPackets` (with JSON.stringify) to see what packets were received/sent by name - `bot` - Mineflayer bot instance. See Mineflayer documentation for more. -- `world` - Three.js world instance, basically does all the rendering (part of renderer backend). +- `viewer` - Three.js viewer instance, basically does all the rendering. - `world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group. - `debugSceneChunks` - The same as above, but relative to current bot position (e.g. 0,0 is the current chunk). - `debugChangedOptions` - See what options are changed. Don't change options here. @@ -141,7 +139,7 @@ Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can u - `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read. -The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `world.getCameraPosition()` to see the camera position and so on. +The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `camera.position` to see the camera position and so on. Watch expression @@ -178,7 +176,6 @@ Server specific: - `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes. - `?autoConnect=true` - Only works then `ip` and `version` parameters are set and `allowAutoConnect` is `true` in config.json! Directly connects to the specified server. Useful for integrates iframes. - `?serversList=` - `` can be a list of servers in the format `ip:version,ip` or a url to a json file with the same format (array) or a txt file with line-delimited list of server IPs. -- `?addPing=` - Add a latency to both sides of the connection. Useful for testing ping issues. For example `?addPing=100` will add 200ms to your ping. Single player specific: @@ -235,4 +232,3 @@ Only during development: - [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true) - [https://m.eaglercraft.com/](EaglerCraft) - Eaglercraft runnable on mobile (real Minecraft in the browser) -- [js-minecraft](https://github.com/LabyStudio/js-minecraft) - An insanely well done clone from the graphical side that inspired many features here diff --git a/assets/customTextures/readme.md b/assets/customTextures/readme.md deleted file mode 100644 index e2a78c20..00000000 --- a/assets/customTextures/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -here you can place custom textures for bundled files (blocks/items) e.g. blocks/stone.png -get file names from here (blocks/items) https://zardoy.github.io/mc-assets/ diff --git a/config.json b/config.json index 2bfa9cfe..940fb738 100644 --- a/config.json +++ b/config.json @@ -10,10 +10,6 @@ { "ip": "wss://play.mcraft.fun" }, - { - "ip": "wss://play.webmc.fun", - "name": "WebMC" - }, { "ip": "wss://ws.fuchsmc.net" }, diff --git a/config.mcraft-only.json b/config.mcraft-only.json index 52a3aa2c..7d9a7b59 100644 --- a/config.mcraft-only.json +++ b/config.mcraft-only.json @@ -1,5 +1,4 @@ { "alwaysReconnectButton": true, - "reportBugButtonWithReconnect": true, - "allowAutoConnect": true + "reportBugButtonWithReconnect": true } diff --git a/experiments/three-item.html b/experiments/three-item.html deleted file mode 100644 index 70155c50..00000000 --- a/experiments/three-item.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - Minecraft Item Viewer - - - - - - diff --git a/experiments/three-item.ts b/experiments/three-item.ts deleted file mode 100644 index b9d492fe..00000000 --- a/experiments/three-item.ts +++ /dev/null @@ -1,108 +0,0 @@ -import * as THREE from 'three' -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' -import itemsAtlas from 'mc-assets/dist/itemsAtlasLegacy.png' -import { createItemMeshFromCanvas, createItemMesh } from '../renderer/viewer/three/itemMesh' - -// Create scene, camera and renderer -const scene = new THREE.Scene() -const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) -const renderer = new THREE.WebGLRenderer({ antialias: true }) -renderer.setSize(window.innerWidth, window.innerHeight) -document.body.appendChild(renderer.domElement) - -// Setup camera and controls -camera.position.set(0, 0, 3) -const controls = new OrbitControls(camera, renderer.domElement) -controls.enableDamping = true - -// Background and lights -scene.background = new THREE.Color(0x333333) -const ambientLight = new THREE.AmbientLight(0xffffff, 0.7) -scene.add(ambientLight) - -// Animation loop -function animate () { - requestAnimationFrame(animate) - controls.update() - renderer.render(scene, camera) -} - -async function setupItemMesh () { - try { - const loader = new THREE.TextureLoader() - const atlasTexture = await loader.loadAsync(itemsAtlas) - - // Pixel-art configuration - atlasTexture.magFilter = THREE.NearestFilter - atlasTexture.minFilter = THREE.NearestFilter - atlasTexture.generateMipmaps = false - atlasTexture.wrapS = atlasTexture.wrapT = THREE.ClampToEdgeWrapping - - // Extract the tile at x=2, y=0 (16x16) - const tileSize = 16 - const tileX = 2 - const tileY = 0 - - const canvas = document.createElement('canvas') - canvas.width = tileSize - canvas.height = tileSize - const ctx = canvas.getContext('2d')! - - ctx.imageSmoothingEnabled = false - ctx.drawImage( - atlasTexture.image, - tileX * tileSize, - tileY * tileSize, - tileSize, - tileSize, - 0, - 0, - tileSize, - tileSize - ) - - // Test both approaches - working manual extraction: - const meshOld = createItemMeshFromCanvas(canvas, { depth: 0.1 }) - meshOld.position.x = -1 - meshOld.rotation.x = -Math.PI / 12 - meshOld.rotation.y = Math.PI / 12 - scene.add(meshOld) - - // And new unified function: - const atlasWidth = atlasTexture.image.width - const atlasHeight = atlasTexture.image.height - const u = (tileX * tileSize) / atlasWidth - const v = (tileY * tileSize) / atlasHeight - const sizeX = tileSize / atlasWidth - const sizeY = tileSize / atlasHeight - - console.log('Debug texture coords:', {u, v, sizeX, sizeY, atlasWidth, atlasHeight}) - - const resultNew = createItemMesh(atlasTexture, { - u, v, sizeX, sizeY - }, { - faceCamera: false, - use3D: true, - depth: 0.1 - }) - - resultNew.mesh.position.x = 1 - resultNew.mesh.rotation.x = -Math.PI / 12 - resultNew.mesh.rotation.y = Math.PI / 12 - scene.add(resultNew.mesh) - - animate() - } catch (err) { - console.error('Failed to create item mesh:', err) - } -} - -// Handle window resize -window.addEventListener('resize', () => { - camera.aspect = window.innerWidth / window.innerHeight - camera.updateProjectionMatrix() - renderer.setSize(window.innerWidth, window.innerHeight) -}) - -// Start -setupItemMesh() diff --git a/experiments/three-labels.html b/experiments/three-labels.html deleted file mode 100644 index 2b25bc23..00000000 --- a/experiments/three-labels.html +++ /dev/null @@ -1,5 +0,0 @@ - - diff --git a/experiments/three-labels.ts b/experiments/three-labels.ts deleted file mode 100644 index b69dc95b..00000000 --- a/experiments/three-labels.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as THREE from 'three' -import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js' -import { createWaypointSprite, WAYPOINT_CONFIG } from '../renderer/viewer/three/waypointSprite' - -// Create scene, camera and renderer -const scene = new THREE.Scene() -const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) -const renderer = new THREE.WebGLRenderer({ antialias: true }) -renderer.setSize(window.innerWidth, window.innerHeight) -document.body.appendChild(renderer.domElement) - -// Add FirstPersonControls -const controls = new FirstPersonControls(camera, renderer.domElement) -controls.lookSpeed = 0.1 -controls.movementSpeed = 10 -controls.lookVertical = true -controls.constrainVertical = true -controls.verticalMin = 0.1 -controls.verticalMax = Math.PI - 0.1 - -// Position camera -camera.position.y = 1.6 // Typical eye height -camera.lookAt(0, 1.6, -1) - -// Create a helper grid and axes -const grid = new THREE.GridHelper(20, 20) -scene.add(grid) -const axes = new THREE.AxesHelper(5) -scene.add(axes) - -// Create waypoint sprite via utility -const waypoint = createWaypointSprite({ - position: new THREE.Vector3(0, 0, -5), - color: 0xff0000, - label: 'Target', -}) -scene.add(waypoint.group) - -// Use built-in offscreen arrow from utils -waypoint.enableOffscreenArrow(true) -waypoint.setArrowParent(scene) - -// Animation loop -function animate() { - requestAnimationFrame(animate) - - const delta = Math.min(clock.getDelta(), 0.1) - controls.update(delta) - - // Unified camera update (size, distance text, arrow, visibility) - const sizeVec = renderer.getSize(new THREE.Vector2()) - waypoint.updateForCamera(camera.position, camera, sizeVec.width, sizeVec.height) - - renderer.render(scene, camera) -} - -// Handle window resize -window.addEventListener('resize', () => { - camera.aspect = window.innerWidth / window.innerHeight - camera.updateProjectionMatrix() - renderer.setSize(window.innerWidth, window.innerHeight) -}) - -// Add clock for controls -const clock = new THREE.Clock() - -animate() diff --git a/package.json b/package.json index ff673726..fe4adb16 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "dependencies": { "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", - "@monaco-editor/react": "^4.7.0", "@nxg-org/mineflayer-auto-jump": "^0.7.18", "@nxg-org/mineflayer-tracker": "1.3.0", "@react-oauth/google": "^0.12.1", @@ -80,14 +79,14 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "filesize": "^10.0.12", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.104", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.62", "framer-motion": "^12.9.2", "fs-extra": "^11.1.1", "google-drive-browserfs": "github:zardoy/browserfs#google-drive", "jszip": "^3.10.1", "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", - "minecraft-data": "3.98.0", + "minecraft-data": "3.92.0", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -157,7 +156,8 @@ "mc-assets": "^0.2.62", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.21", + "mineflayer-mouse": "^0.1.11", + "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", @@ -197,7 +197,6 @@ }, "pnpm": { "overrides": { - "mineflayer": "github:zardoy/mineflayer#gen-the-master", "@nxg-org/mineflayer-physics-util": "1.8.10", "buffer": "^6.0.3", "vec3": "0.1.10", @@ -205,7 +204,7 @@ "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", "prismarine-world": "github:zardoy/prismarine-world#next-era", - "minecraft-data": "3.98.0", + "minecraft-data": "3.92.0", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "prismarine-physics": "github:zardoy/prismarine-physics", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol.patch index e29f87d9..6a82d5cf 100644 --- a/patches/minecraft-protocol.patch +++ b/patches/minecraft-protocol.patch @@ -1,8 +1,8 @@ diff --git a/src/client/chat.js b/src/client/chat.js -index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b43cb102a 100644 +index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb1856fa582f9 100644 --- a/src/client/chat.js +++ b/src/client/chat.js -@@ -116,7 +116,7 @@ module.exports = function (client, options) { +@@ -109,7 +109,7 @@ module.exports = function (client, options) { for (const player of packet.data) { if (player.chatSession) { client._players[player.uuid] = { @@ -11,7 +11,7 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b publicKeyDER: player.chatSession.publicKey.keyBytes, sessionUuid: player.chatSession.uuid } -@@ -126,7 +126,7 @@ module.exports = function (client, options) { +@@ -119,7 +119,7 @@ module.exports = function (client, options) { if (player.crypto) { client._players[player.uuid] = { @@ -20,7 +20,7 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b publicKeyDER: player.crypto.publicKey, signature: player.crypto.signature, displayName: player.displayName || player.name -@@ -196,7 +196,7 @@ module.exports = function (client, options) { +@@ -189,7 +189,7 @@ module.exports = function (client, options) { if (mcData.supportFeature('useChatSessions')) { const tsDelta = BigInt(Date.now()) - packet.timestamp const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0 @@ -28,8 +28,8 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b + const verified = false && !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired if (verified) client._signatureCache.push(packet.signature) client.emit('playerChat', { - globalIndex: packet.globalIndex, -@@ -362,7 +362,7 @@ module.exports = function (client, options) { + plainMessage: packet.plainMessage, +@@ -354,7 +354,7 @@ module.exports = function (client, options) { } } @@ -38,16 +38,16 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b options.timestamp = options.timestamp || BigInt(Date.now()) options.salt = options.salt || 1n -@@ -407,7 +407,7 @@ module.exports = function (client, options) { +@@ -396,7 +396,7 @@ module.exports = function (client, options) { message, timestamp: options.timestamp, salt: options.salt, - signature: (client.profileKeys && client._session) ? client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined, + signature: (client.profileKeys && client._session) ? await client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined, offset: client._lastSeenMessages.pending, - checksum: computeChatChecksum(client._lastSeenMessages), // 1.21.5+ acknowledged -@@ -422,7 +422,7 @@ module.exports = function (client, options) { + }) +@@ -410,7 +410,7 @@ module.exports = function (client, options) { message, timestamp: options.timestamp, salt: options.salt, @@ -57,7 +57,7 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b previousMessages: client._lastSeenMessages.map((e) => ({ messageSender: e.sender, diff --git a/src/client/encrypt.js b/src/client/encrypt.js -index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f85410eaef 100644 +index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108f4c63536 100644 --- a/src/client/encrypt.js +++ b/src/client/encrypt.js @@ -25,7 +25,11 @@ module.exports = function (client, options) { @@ -73,24 +73,41 @@ index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f8 } function onJoinServerResponse (err) { -diff --git a/src/client/pluginChannels.js b/src/client/pluginChannels.js -index 671eb452f31e6b5fcd57d715f1009d010160c65f..7f69f511c8fb97d431ec5125c851b49be8e2ab76 100644 ---- a/src/client/pluginChannels.js -+++ b/src/client/pluginChannels.js -@@ -57,7 +57,7 @@ module.exports = function (client, options) { - try { - packet.data = proto.parsePacketBuffer(channel, packet.data).data - } catch (error) { -- client.emit('error', error) -+ client.emit('error', error, { customPayload: packet }) - return - } +diff --git a/src/client/play.js b/src/client/play.js +index 559607f34e9a5b2b7809423f8ca4cd6746b60225..4dc1c3139438cc2729b05c57e57bd00252728f8a 100644 +--- a/src/client/play.js ++++ b/src/client/play.js +@@ -53,7 +53,7 @@ module.exports = function (client, options) { + client.write('configuration_acknowledged', {}) } + client.state = states.CONFIGURATION +- client.on('select_known_packs', () => { ++ client.once('select_known_packs', () => { + client.write('select_known_packs', { packs: [] }) + }) + // Server should send finish_configuration on its own right after sending the client a dimension codec diff --git a/src/client.js b/src/client.js -index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875989cdf0c 100644 +index 5c7a62b013daa69be91ec9e763b1f48ffe96ffa6..174d42a77740a937afcb106e1f39a9ee824a24b9 100644 --- a/src/client.js +++ b/src/client.js -@@ -111,7 +111,13 @@ class Client extends EventEmitter { +@@ -89,10 +89,12 @@ class Client extends EventEmitter { + parsed.metadata.name = parsed.data.name + parsed.data = parsed.data.params + parsed.metadata.state = state +- debug('read packet ' + state + '.' + parsed.metadata.name) +- if (debug.enabled) { +- const s = JSON.stringify(parsed.data, null, 2) +- debug(s && s.length > 10000 ? parsed.data : s) ++ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) { ++ debug('read packet ' + state + '.' + parsed.metadata.name) ++ if (debug.enabled) { ++ const s = JSON.stringify(parsed.data, null, 2) ++ debug(s && s.length > 10000 ? parsed.data : s) ++ } + } + if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') { + if (this._mcBundle.length) { // End bundle +@@ -110,7 +112,13 @@ class Client extends EventEmitter { this._hasBundlePacket = false } } else { @@ -105,7 +122,7 @@ index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875 } }) } -@@ -169,7 +175,10 @@ class Client extends EventEmitter { +@@ -168,7 +176,10 @@ class Client extends EventEmitter { } const onFatalError = (err) => { @@ -117,21 +134,25 @@ index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875 endSocket() } -@@ -198,6 +207,10 @@ class Client extends EventEmitter { +@@ -197,6 +208,8 @@ class Client extends EventEmitter { serializer -> framer -> socket -> splitter -> deserializer */ if (this.serializer) { this.serializer.end() -+ setTimeout(() => { -+ this.socket?.end() -+ this.socket?.emit('end') -+ }, 2000) // allow the serializer to finish writing ++ this.socket?.end() ++ this.socket?.emit('end') } else { if (this.socket) this.socket.end() } -@@ -243,6 +256,7 @@ class Client extends EventEmitter { - debug('writing packet ' + this.state + '.' + name) - debug(params) - } +@@ -238,8 +251,11 @@ class Client extends EventEmitter { + + write (name, params) { + if (!this.serializer.writable) { return } +- debug('writing packet ' + this.state + '.' + name) +- debug(params) ++ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) { ++ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name) ++ debug(params) ++ } + this.emit('writePacket', name, params) this.serializer.write({ name, params }) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5bcd74a0..c37e0510 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,6 @@ settings: excludeLinksFromLockfile: false overrides: - mineflayer: github:zardoy/mineflayer#gen-the-master '@nxg-org/mineflayer-physics-util': 1.8.10 buffer: ^6.0.3 vec3: 0.1.10 @@ -13,7 +12,7 @@ overrides: diamond-square: github:zardoy/diamond-square prismarine-block: github:zardoy/prismarine-block#next-era prismarine-world: github:zardoy/prismarine-world#next-era - minecraft-data: 3.98.0 + minecraft-data: 3.92.0 prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything prismarine-physics: github:zardoy/prismarine-physics minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master @@ -23,7 +22,7 @@ overrides: patchedDependencies: minecraft-protocol: - hash: 4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b + hash: a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0 path: patches/minecraft-protocol.patch mineflayer-item-map-downloader@1.2.0: hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad @@ -42,9 +41,6 @@ importers: '@floating-ui/react': specifier: ^0.26.1 version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@monaco-editor/react': - specifier: ^4.7.0 - version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@nxg-org/mineflayer-auto-jump': specifier: ^0.7.18 version: 0.7.18 @@ -121,8 +117,8 @@ importers: specifier: ^10.0.12 version: 10.1.6 flying-squid: - specifier: npm:@zardoy/flying-squid@^0.0.104 - version: '@zardoy/flying-squid@0.0.104(encoding@0.1.13)' + specifier: npm:@zardoy/flying-squid@^0.0.62 + version: '@zardoy/flying-squid@0.0.62(encoding@0.1.13)' framer-motion: specifier: ^12.9.2 version: 12.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -140,13 +136,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)) minecraft-data: - specifier: 3.98.0 - version: 3.98.0 + specifier: 3.92.0 + version: 3.92.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -155,7 +151,7 @@ importers: version: 2.0.4 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618 + version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5 node-gzip: specifier: ^1.1.2 version: 1.1.2 @@ -170,7 +166,7 @@ importers: version: 6.1.1 prismarine-provider-anvil: specifier: github:zardoy/prismarine-provider-anvil#everything - version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.3 @@ -345,10 +341,13 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.21 - version: 0.1.21 + specifier: ^0.1.11 + version: 0.1.11 + mineflayer-pathfinder: + specifier: ^2.4.4 + version: 2.4.5 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -436,7 +435,7 @@ importers: version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -1992,16 +1991,6 @@ packages: '@module-federation/webpack-bundler-runtime@0.11.2': resolution: {integrity: sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==} - '@monaco-editor/loader@1.5.0': - resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==} - - '@monaco-editor/react@4.7.0': - resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} - peerDependencies: - monaco-editor: '>= 0.25.0 < 1' - react: ^18.2.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@msgpack/msgpack@2.8.0': resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} engines: {node: '>= 10'} @@ -3387,13 +3376,13 @@ packages: resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - '@zardoy/flying-squid@0.0.104': - resolution: {integrity: sha512-jGhQ7fn7o8UN+mUwZbt9674D37YLuBi+Au4TwKcopCA6huIQdHTFNl2e+0ZSTI5mnhN+NpyVoR3vmtH6L58vHQ==} + '@zardoy/flying-squid@0.0.49': + resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==} engines: {node: '>=8'} hasBin: true - '@zardoy/flying-squid@0.0.49': - resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==} + '@zardoy/flying-squid@0.0.62': + resolution: {integrity: sha512-M6icydO/yrmwevBhmgKcqEPC63AhWfU/Es9N/uadVrmKaxGm2FQMMLcybbutRYm1xZ6qsdxDUOUZnN56PsVwfQ==} engines: {node: '>=8'} hasBin: true @@ -6444,12 +6433,6 @@ packages: resolution: {integrity: sha512-RYZeD1+joNlPuUpi+tIWkbP0ieVJr+R6IFkI6/8juhSxx9zE4osoSmteybrfspGm8A6u+YbbY1epqRKEMwVR6Q==} engines: {node: '>=18.0.0'} - mc-bridge@0.1.3: - resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - peerDependencies: - minecraft-data: 3.98.0 - mcraft-fun-mineflayer@0.1.23: resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==} version: 0.1.23 @@ -6658,8 +6641,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minecraft-data@3.98.0: - resolution: {integrity: sha512-JAPqJ/TZoxMUlAPPdWUh1v5wdqvYGFSZ4rW9bUtmaKBkGpomDSjw4V02ocBqbxKJvcTtmc5nM/LfN9/0DDqHrQ==} + minecraft-data@3.92.0: + resolution: {integrity: sha512-CGfO50svzm+pSRa4Mbq4owsmRKbPCNkSZ3MCOyH+epC7yNjh+PUhPQFHWq72O51qsY7pAB5qM/bJn1ncwG1J5g==} minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} @@ -6668,9 +6651,9 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41} version: 1.0.1 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9} - version: 1.62.0 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176} + version: 1.58.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6684,13 +6667,20 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.21: - resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==} + mineflayer-mouse@0.1.11: + resolution: {integrity: sha512-BL47pXZ1+92BA/7ym6KaJctEHKnL0up+tpuagVwSKJvAgibeqWQJJwDlNUWkOLvpnruRKDxMR5OB1hUXFoDNSg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659} - version: 8.0.0 + mineflayer-pathfinder@2.4.5: + resolution: {integrity: sha512-Jh3JnUgRLwhMh2Dugo4SPza68C41y+NPP5sdsgxRu35ydndo70i1JJGxauVWbXrpNwIxYNztUw78aFyb7icw8g==} + + mineflayer@4.30.0: + resolution: {integrity: sha512-GtW4hkijyZbSu5LKYYD89xZu+XY7OoP7IkrCnNEn6EdPm0+vr2THoJgFGKrlze9/81+T+P3E4qvJXNFiU/zeJg==} + engines: {node: '>=22'} + + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980} + version: 4.30.0 engines: {node: '>=22'} minimalistic-assert@1.0.1: @@ -6788,9 +6778,6 @@ packages: mojangson@2.0.4: resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==} - monaco-editor@0.52.2: - resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} - moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} @@ -6855,8 +6842,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618} + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5} version: 0.2.4 nice-try@1.0.5: @@ -7387,7 +7374,7 @@ packages: prismarine-biome@1.3.0: resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==} peerDependencies: - minecraft-data: 3.98.0 + minecraft-data: 3.92.0 prismarine-registry: ^1.1.0 prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: @@ -7405,8 +7392,8 @@ packages: prismarine-entity@2.5.0: resolution: {integrity: sha512-nRPCawUwf9r3iKqi4I7mZRlir1Ix+DffWYdWq6p/KNnmiXve+xHE5zv8XCdhZlUmOshugHv5ONl9o6ORAkCNIA==} - prismarine-item@1.17.0: - resolution: {integrity: sha512-wN1OjP+f+Uvtjo3KzeCkVSy96CqZ8yG7cvuvlGwcYupQ6ct7LtNkubHp0AHuLMJ0vbbfAC0oZ2bWOgI1DYp8WA==} + prismarine-item@1.16.0: + resolution: {integrity: sha512-88Tz+/6HquYIsDuseae5G3IbqLeMews2L+ba2gX+p6K6soU9nuFhCfbwN56QuB7d/jZFcWrCYAPE5+UhwWh67w==} prismarine-nbt@2.7.0: resolution: {integrity: sha512-Du9OLQAcCj3y29YtewOJbbV4ARaSUEJiTguw0PPQbPBy83f+eCyDRkyBpnXTi/KPyEpgYCzsjGzElevLpFoYGQ==} @@ -8337,7 +8324,6 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} - deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} @@ -8395,9 +8381,6 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} - state-local@1.0.7: - resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} - static-extend@0.1.2: resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} engines: {node: '>=0.10.0'} @@ -9685,7 +9668,7 @@ snapshots: '@babel/core': 7.26.9 '@babel/helper-compilation-targets': 7.26.5 '@babel/helper-plugin-utils': 7.26.5 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.10 transitivePeerDependencies: @@ -10310,7 +10293,7 @@ snapshots: '@babel/parser': 7.26.9 '@babel/template': 7.26.9 '@babel/types': 7.26.9 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11279,17 +11262,6 @@ snapshots: '@module-federation/runtime': 0.11.2 '@module-federation/sdk': 0.11.2 - '@monaco-editor/loader@1.5.0': - dependencies: - state-local: 1.0.7 - - '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@monaco-editor/loader': 1.5.0 - monaco-editor: 0.52.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - '@msgpack/msgpack@2.8.0': {} '@ndelangen/get-tarball@3.0.9': @@ -11343,10 +11315,10 @@ snapshots: '@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 - minecraft-data: 3.98.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) + minecraft-data: 3.92.0 + mineflayer: 4.30.0(encoding@0.1.13) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 transitivePeerDependencies: @@ -12891,7 +12863,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.5.4) '@typescript-eslint/utils': 6.1.0(eslint@8.57.1)(typescript@5.5.4) - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) eslint: 8.57.1 ts-api-utils: 1.4.3(typescript@5.5.4) optionalDependencies: @@ -12909,7 +12881,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.1.0 '@typescript-eslint/visitor-keys': 6.1.0 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 semver: 7.7.1 @@ -12923,7 +12895,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -12938,7 +12910,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.26.0 '@typescript-eslint/visitor-keys': 8.26.0 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -13094,7 +13066,7 @@ snapshots: '@types/emscripten': 1.40.0 tslib: 1.14.1 - '@zardoy/flying-squid@0.0.104(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.49(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 chalk: 5.4.1 @@ -13104,18 +13076,16 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - mc-bridge: 0.1.3(minecraft-data@3.98.0) - minecraft-data: 3.98.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-data: 3.92.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -13132,7 +13102,7 @@ snapshots: - encoding - supports-color - '@zardoy/flying-squid@0.0.49(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.62(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 chalk: 5.4.1 @@ -13142,16 +13112,16 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - minecraft-data: 3.98.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-data: 3.92.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -14532,7 +14502,7 @@ snapshots: detect-port@1.6.1: dependencies: address: 1.2.2 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -14542,8 +14512,8 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: - minecraft-data: 3.98.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) + minecraft-data: 3.92.0 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -16139,7 +16109,7 @@ snapshots: https-proxy-agent@4.0.0: dependencies: agent-base: 5.1.1 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16986,17 +16956,13 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mc-bridge@0.1.3(minecraft-data@3.98.0): - dependencies: - minecraft-data: 3.98.0 - - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) - prismarine-item: 1.17.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13) + prismarine-item: 1.16.0 ws: 8.18.1 transitivePeerDependencies: - bufferutil @@ -17225,7 +17191,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) decode-named-character-reference: 1.1.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -17302,7 +17268,7 @@ snapshots: min-indent@1.0.1: {} - minecraft-data@3.98.0: {} + minecraft-data@3.92.0: {} minecraft-folder-path@1.2.0: {} @@ -17313,7 +17279,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17322,7 +17288,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) endian-toggle: 0.0.0 lodash.merge: 4.6.2 - minecraft-data: 3.98.0 + minecraft-data: 3.92.0 minecraft-folder-path: 1.2.0 node-fetch: 2.7.0(encoding@0.1.13) node-rsa: 0.4.2 @@ -17365,32 +17331,65 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) + mineflayer: 4.30.0(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding - supports-color - mineflayer-mouse@0.1.21: + mineflayer-mouse@0.1.11: dependencies: change-case: 5.4.4 debug: 4.4.1 - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13): + mineflayer-pathfinder@2.4.5: dependencies: - '@nxg-org/mineflayer-physics-util': 1.8.10 - minecraft-data: 3.98.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) + minecraft-data: 3.92.0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-entity: 2.5.0 + prismarine-item: 1.16.0 + prismarine-nbt: 2.7.0 + prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b + vec3: 0.1.10 + + mineflayer@4.30.0(encoding@0.1.13): + dependencies: + minecraft-data: 3.92.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 + prismarine-nbt: 2.7.0 + prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b + prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) + prismarine-registry: 1.11.0 + prismarine-windows: 2.9.0 + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c + protodef: 1.18.0 + typed-emitter: 1.4.0 + vec3: 0.1.10 + transitivePeerDependencies: + - encoding + - supports-color + + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13): + dependencies: + '@nxg-org/mineflayer-physics-util': 1.8.10 + minecraft-data: 3.92.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13) + prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-chat: 1.11.0 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-entity: 2.5.0 + prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) @@ -17503,8 +17502,6 @@ snapshots: dependencies: nearley: 2.20.1 - monaco-editor@0.52.2: {} - moo@0.5.2: {} morgan@1.10.0: @@ -17586,7 +17583,7 @@ snapshots: neo-async@2.6.2: {} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618: + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5: dependencies: body-parser: 1.20.3 express: 4.21.2 @@ -18174,17 +18171,17 @@ snapshots: transitivePeerDependencies: - supports-color - prismarine-biome@1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0): + prismarine-biome@1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0): dependencies: - minecraft-data: 3.98.0 + minecraft-data: 3.92.0 prismarine-registry: 1.11.0 prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: - minecraft-data: 3.98.0 - prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) + minecraft-data: 3.92.0 + prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18194,9 +18191,9 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0): dependencies: - prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) + prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18210,11 +18207,11 @@ snapshots: prismarine-entity@2.5.0: dependencies: prismarine-chat: 1.11.0 - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 prismarine-registry: 1.11.0 vec3: 0.1.10 - prismarine-item@1.17.0: + prismarine-item@1.16.0: dependencies: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18225,14 +18222,14 @@ snapshots: prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b: dependencies: - minecraft-data: 3.98.0 + minecraft-data: 3.92.0 prismarine-nbt: 2.7.0 vec3: 0.1.10 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0): dependencies: prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 @@ -18254,13 +18251,13 @@ snapshots: prismarine-registry@1.11.0: dependencies: - minecraft-data: 3.98.0 + minecraft-data: 3.92.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.98.0 + minecraft-data: 3.92.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 @@ -18268,7 +18265,7 @@ snapshots: prismarine-windows@2.9.0: dependencies: - prismarine-item: 1.17.0 + prismarine-item: 1.16.0 prismarine-registry: 1.11.0 typed-emitter: 2.1.0 @@ -18459,7 +18456,7 @@ snapshots: puppeteer-core@2.1.1: dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.1 + debug: 4.4.0(supports-color@8.1.1) extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 @@ -19561,8 +19558,6 @@ snapshots: stack-generator: 2.0.10 stacktrace-gps: 3.1.2 - state-local@1.0.7: {} - static-extend@0.1.2: dependencies: define-property: 0.2.5 diff --git a/renderer/playground/shared.ts b/renderer/playground/shared.ts index 9d12fae9..ba58a57f 100644 --- a/renderer/playground/shared.ts +++ b/renderer/playground/shared.ts @@ -65,7 +65,7 @@ function getAllMethods (obj) { return [...methods] as string[] } -export const delayedIterator = async (arr: T[], delay: number, exec: (item: T, index: number) => Promise, chunkSize = 1) => { +export const delayedIterator = async (arr: T[], delay: number, exec: (item: T, index: number) => void, chunkSize = 1) => { // if delay is 0 then don't use setTimeout for (let i = 0; i < arr.length; i += chunkSize) { if (delay) { @@ -74,6 +74,6 @@ export const delayedIterator = async (arr: T[], delay: number, exec: (item: setTimeout(resolve, delay) }) } - await exec(arr[i], i) + exec(arr[i], i) } } diff --git a/renderer/rsbuildSharedConfig.ts b/renderer/rsbuildSharedConfig.ts index 45da30b1..57091d99 100644 --- a/renderer/rsbuildSharedConfig.ts +++ b/renderer/rsbuildSharedConfig.ts @@ -73,12 +73,12 @@ export const appAndRendererSharedConfig = () => defineConfig({ }) export const rspackViewerConfig = (config, { appendPlugins, addRules, rspack }: ModifyRspackConfigUtils) => { - appendPlugins(new rspack.NormalModuleReplacementPlugin(/data|prismarine-physics/, (resource) => { + appendPlugins(new rspack.NormalModuleReplacementPlugin(/data/, (resource) => { let absolute: string const request = resource.request.replaceAll('\\', '/') absolute = path.join(resource.context, request).replaceAll('\\', '/') - if (request.includes('minecraft-data/data/pc/1.') || request.includes('prismarine-physics')) { - console.log('Error: incompatible resource', request, 'from', resource.contextInfo.issuer) + if (request.includes('minecraft-data/data/pc/1.')) { + console.log('Error: incompatible resource', request, resource.contextInfo.issuer) process.exit(1) // throw new Error(`${resource.request} was requested by ${resource.contextInfo.issuer}`) } diff --git a/renderer/viewer/baseGraphicsBackend.ts b/renderer/viewer/baseGraphicsBackend.ts index 486c930f..3cd227de 100644 --- a/renderer/viewer/baseGraphicsBackend.ts +++ b/renderer/viewer/baseGraphicsBackend.ts @@ -1,4 +1,3 @@ -import { proxy } from 'valtio' import { NonReactiveState, RendererReactiveState } from '../../src/appViewer' export const getDefaultRendererState = (): { @@ -6,7 +5,7 @@ export const getDefaultRendererState = (): { nonReactive: NonReactiveState } => { return { - reactive: proxy({ + reactive: { world: { chunksLoaded: new Set(), heightmaps: new Map(), @@ -16,7 +15,7 @@ export const getDefaultRendererState = (): { }, renderer: '', preventEscapeMenu: false - }), + }, nonReactive: { world: { chunksLoaded: new Set(), diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index 9cf1350a..af5d9d06 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -48,7 +48,6 @@ export const getInitialPlayerState = () => proxy({ heldItemMain: undefined as HandItemBlock | undefined, heldItemOff: undefined as HandItemBlock | undefined, perspective: 'first_person' as CameraPerspective, - onFire: false, cameraSpectatingEntity: undefined as number | undefined, diff --git a/renderer/viewer/lib/createPlayerObject.ts b/renderer/viewer/lib/createPlayerObject.ts deleted file mode 100644 index 836c8062..00000000 --- a/renderer/viewer/lib/createPlayerObject.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { PlayerObject, PlayerAnimation } from 'skinview3d' -import * as THREE from 'three' -import { WalkingGeneralSwing } from '../three/entity/animations' -import { loadSkinImage, stevePngUrl } from './utils/skins' - -export type PlayerObjectType = PlayerObject & { - animation?: PlayerAnimation - realPlayerUuid: string - realUsername: string -} - -export function createPlayerObject (options: { - username?: string - uuid?: string - scale?: number -}): { - playerObject: PlayerObjectType - wrapper: THREE.Group - } { - const wrapper = new THREE.Group() - const playerObject = new PlayerObject() as PlayerObjectType - - playerObject.realPlayerUuid = options.uuid ?? '' - playerObject.realUsername = options.username ?? '' - playerObject.position.set(0, 16, 0) - - // fix issues with starfield - playerObject.traverse((obj) => { - if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshStandardMaterial) { - obj.material.transparent = true - } - }) - - wrapper.add(playerObject as any) - const scale = options.scale ?? (1 / 16) - wrapper.scale.set(scale, scale, scale) - wrapper.rotation.set(0, Math.PI, 0) - - // Set up animation - playerObject.animation = new WalkingGeneralSwing() - ;(playerObject.animation as WalkingGeneralSwing).isMoving = false - playerObject.animation.update(playerObject, 0) - - return { playerObject, wrapper } -} - -export const applySkinToPlayerObject = async (playerObject: PlayerObjectType, skinUrl: string) => { - return loadSkinImage(skinUrl || stevePngUrl).then(({ canvas }) => { - const skinTexture = new THREE.CanvasTexture(canvas) - skinTexture.magFilter = THREE.NearestFilter - skinTexture.minFilter = THREE.NearestFilter - skinTexture.needsUpdate = true - playerObject.skin.map = skinTexture as any - }).catch(console.error) -} diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index dfbdb35c..e1ac2f24 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -7,7 +7,6 @@ import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' import { proxy } from 'valtio' import TypedEmitter from 'typed-emitter' -import { Biome } from 'minecraft-data' import { delayedIterator } from '../../playground/shared' import { chunkPos } from './simpleUtils' @@ -29,8 +28,6 @@ export type WorldDataEmitterEvents = { updateLight: (data: { pos: Vec3 }) => void onWorldSwitch: () => void end: () => void - biomeUpdate: (data: { biome: Biome }) => void - biomeReset: () => void } export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmitter) { @@ -38,15 +35,7 @@ export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmit } export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter) { - spiralNumber = 0 - gotPanicLastTime = false - panicChunksReload = () => {} loadedChunks: Record - private inLoading = false - private chunkReceiveTimes: number[] = [] - private lastChunkReceiveTime = 0 - public lastChunkReceiveTimeAvg = 0 - private panicTimeout?: NodeJS.Timeout readonly lastPos: Vec3 private eventListeners: Record = {} private readonly emitter: WorldDataEmitter @@ -144,19 +133,12 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { - const now = performance.now() - if (this.lastChunkReceiveTime) { - this.chunkReceiveTimes.push(now - this.lastChunkReceiveTime) - } - this.lastChunkReceiveTime = now - if (this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`]) { this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`](true) delete this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] } else if (this.loadedChunks[`${pos.x},${pos.z}`]) { void this.loadChunk(pos, false, 'Received another chunkColumnLoad event while already loaded') } - this.chunkProgress() }, chunkColumnUnload: (pos: Vec3) => { this.unloadChunk(pos) @@ -228,7 +210,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter TypedEmitter new Vec3((botX + x) * 16, 0, (botZ + z) * 16)) this.lastPos.update(pos) - await this._loadChunks(positions, pos) + await this._loadChunks(positions) } - chunkProgress () { - if (this.panicTimeout) clearTimeout(this.panicTimeout) - if (this.chunkReceiveTimes.length >= 5) { - const avgReceiveTime = this.chunkReceiveTimes.reduce((a, b) => a + b, 0) / this.chunkReceiveTimes.length - this.lastChunkReceiveTimeAvg = avgReceiveTime - const timeoutDelay = avgReceiveTime * 2 + 1000 // 2x average + 1 second - - // Clear any existing timeout - if (this.panicTimeout) clearTimeout(this.panicTimeout) - - // Set new timeout for panic reload - this.panicTimeout = setTimeout(() => { - if (!this.gotPanicLastTime && this.inLoading) { - console.warn('Chunk loading seems stuck, triggering panic reload') - this.gotPanicLastTime = true - this.panicChunksReload() - } - }, timeoutDelay) - } - } - - async _loadChunks (positions: Vec3[], centerPos: Vec3) { - this.spiralNumber++ - const { spiralNumber } = this + async _loadChunks (positions: Vec3[], sliceSize = 5) { // stop loading previous chunks for (const pos of Object.keys(this.waitingSpiralChunksLoad)) { this.waitingSpiralChunksLoad[pos](false) delete this.waitingSpiralChunksLoad[pos] } + const promises = [] as Array> let continueLoading = true - this.inLoading = true await delayedIterator(positions, this.addWaitTime, async (pos) => { - if (!continueLoading || this.loadedChunks[`${pos.x},${pos.z}`]) return + const promise = (async () => { + if (!continueLoading || this.loadedChunks[`${pos.x},${pos.z}`]) return - // Wait for chunk to be available from server - if (!this.world.getColumnAt(pos)) { - continueLoading = await new Promise(resolve => { - this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] = resolve - }) - } - if (!continueLoading) return - await this.loadChunk(pos, undefined, `spiral ${spiralNumber} from ${centerPos.x},${centerPos.z}`) - this.chunkProgress() + if (!this.world.getColumnAt(pos)) { + continueLoading = await new Promise(resolve => { + this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] = resolve + }) + } + if (!continueLoading) return + await this.loadChunk(pos) + })() + promises.push(promise) }) - if (this.panicTimeout) clearTimeout(this.panicTimeout) - this.inLoading = false - this.gotPanicLastTime = false - this.chunkReceiveTimes = [] - this.lastChunkReceiveTime = 0 + await Promise.all(promises) } readdDebug () { @@ -363,37 +319,8 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter TypedEmitter TypedEmitter !!a) this.lastPos.update(pos) - void this._loadChunks(positions, pos) + void this._loadChunks(positions) } else { this.emitter.emit('chunkPosUpdate', { pos }) // todo-low this.lastPos.update(pos) diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index 4140e3fa..5c4fd6b5 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -32,45 +32,31 @@ const toMajorVersion = version => { export const worldCleanup = buildCleanupDecorator('resetWorld') export const defaultWorldRendererConfig = { - // Debug settings showChunkBorders: false, - enableDebugOverlay: false, - - // Performance settings mesherWorkers: 4, - addChunksBatchWaitTime: 200, - _experimentalSmoothChunkLoading: true, - _renderByChunks: false, - - // Rendering engine settings - dayCycle: true, + isPlayground: false, + renderEars: true, + skinTexturesProxy: undefined as string | undefined, + // game renderer setting actually + showHand: false, + viewBobbing: false, + extraBlockRenderers: true, + clipWorldBelowY: undefined as number | undefined, smoothLighting: true, enableLighting: true, starfield: true, - defaultSkybox: true, - renderEntities: true, - extraBlockRenderers: true, - foreground: true, - fov: 75, - volume: 1, - - // Camera visual related settings - showHand: false, - viewBobbing: false, - renderEars: true, - highlightBlockColor: 'blue', - - // Player models - fetchPlayerSkins: true, - skinTexturesProxy: undefined as string | undefined, - - // VR settings + addChunksBatchWaitTime: 200, vrSupport: true, vrPageGameRendering: true, - - // World settings - clipWorldBelowY: undefined as number | undefined, - isPlayground: false + renderEntities: true, + fov: 75, + fetchPlayerSkins: true, + highlightBlockColor: 'blue', + foreground: true, + enableDebugOverlay: false, + _experimentalSmoothChunkLoading: true, + _renderByChunks: false, + volume: 1 } export type WorldRendererConfig = typeof defaultWorldRendererConfig @@ -510,10 +496,6 @@ export abstract class WorldRendererCommon timeUpdated? (newTime: number): void - biomeUpdated? (biome: any): void - - biomeReset? (): void - updateViewerPosition (pos: Vec3) { this.viewerChunkPosition = pos for (const [key, value] of Object.entries(this.loadedChunks)) { @@ -835,9 +817,12 @@ export abstract class WorldRendererCommon }) worldEmitter.on('time', (timeOfDay) => { - if (!this.worldRendererConfig.dayCycle) return this.timeUpdated?.(timeOfDay) + if (timeOfDay < 0 || timeOfDay > 24_000) { + throw new Error('Invalid time of day. It should be between 0 and 24000.') + } + this.timeOfTheDay = timeOfDay // if (this.worldRendererConfig.skyLight === skyLight) return @@ -846,14 +831,6 @@ export abstract class WorldRendererCommon // (this).rerenderAllChunks?.() // } }) - - worldEmitter.on('biomeUpdate', ({ biome }) => { - this.biomeUpdated?.(biome) - }) - - worldEmitter.on('biomeReset', () => { - this.biomeReset?.() - }) } setBlockStateIdInner (pos: Vec3, stateId: number | undefined, needAoRecalculation = true) { diff --git a/renderer/viewer/three/cameraShake.ts b/renderer/viewer/three/cameraShake.ts index 7b159509..593b4628 100644 --- a/renderer/viewer/three/cameraShake.ts +++ b/renderer/viewer/three/cameraShake.ts @@ -80,12 +80,8 @@ export class CameraShake { camera.setRotationFromQuaternion(yawQuat) } else { // For regular camera, apply all rotations - // Add tiny offsets to prevent z-fighting at ideal angles (90, 180, 270 degrees) - const pitchOffset = this.addAntiZfightingOffset(this.basePitch) - const yawOffset = this.addAntiZfightingOffset(this.baseYaw) - - const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset) - const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset) + const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.basePitch) + const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw) const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle)) // Combine rotations in the correct order: pitch -> yaw -> roll const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat) @@ -100,21 +96,4 @@ export class CameraShake { private easeInOut (t: number): number { return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2 } - - private addAntiZfightingOffset (angle: number): number { - const offset = 0.001 // Very small offset in radians (about 0.057 degrees) - - // Check if the angle is close to ideal angles (0, π/2, π, 3π/2) - const normalizedAngle = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2) - const tolerance = 0.01 // Tolerance for considering an angle "ideal" - - if (Math.abs(normalizedAngle) < tolerance || - Math.abs(normalizedAngle - Math.PI / 2) < tolerance || - Math.abs(normalizedAngle - Math.PI) < tolerance || - Math.abs(normalizedAngle - 3 * Math.PI / 2) < tolerance) { - return angle + offset - } - - return angle - } } diff --git a/renderer/viewer/three/documentRenderer.ts b/renderer/viewer/three/documentRenderer.ts index a5dc060d..8ff31e69 100644 --- a/renderer/viewer/three/documentRenderer.ts +++ b/renderer/viewer/three/documentRenderer.ts @@ -61,9 +61,8 @@ export class DocumentRenderer { this.previousCanvasWidth = this.canvas.width this.previousCanvasHeight = this.canvas.height - const supportsWebGL2 = 'WebGL2RenderingContext' in window // Only initialize stats and DOM-related features in main thread - if (!externalCanvas && supportsWebGL2) { + if (!externalCanvas) { this.stats = new TopRightStats(this.canvas as HTMLCanvasElement, this.config.statsVisible) this.setupFpsTracking() } diff --git a/renderer/viewer/three/entities.ts b/renderer/viewer/three/entities.ts index fad30182..51292d2f 100644 --- a/renderer/viewer/three/entities.ts +++ b/renderer/viewer/three/entities.ts @@ -20,9 +20,7 @@ import { ItemSpecificContextProperties } from '../lib/basePlayerState' import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins' import { renderComponent } from '../sign-renderer' import { createCanvas } from '../lib/utils' -import { PlayerObjectType } from '../lib/createPlayerObject' import { getBlockMeshFromModel } from './holdingBlock' -import { createItemMesh } from './itemMesh' import * as Entity from './entity/EntityMesh' import { getMesh } from './entity/EntityMesh' import { WalkingGeneralSwing } from './entity/animations' @@ -34,6 +32,12 @@ export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl) export const TWEEN_DURATION = 120 +type PlayerObjectType = PlayerObject & { + animation?: PlayerAnimation + realPlayerUuid: string + realUsername: string +} + function convert2sComplementToHex (complement: number) { if (complement < 0) { complement = (0xFF_FF_FF_FF + complement + 1) >>> 0 @@ -137,7 +141,7 @@ const addNametag = (entity, options: { fontFamily: string }, mesh, version: stri const canvas = getUsernameTexture(entity, options, version) const tex = new THREE.Texture(canvas) tex.needsUpdate = true - let nameTag: THREE.Object3D + let nameTag if (entity.nameTagFixed) { const geometry = new THREE.PlaneGeometry() const material = new THREE.MeshBasicMaterial({ map: tex }) @@ -167,7 +171,6 @@ const addNametag = (entity, options: { fontFamily: string }, mesh, version: stri nameTag.name = 'nametag' mesh.add(nameTag) - return nameTag } } @@ -491,10 +494,6 @@ export class Entities { // todo true/undefined doesnt reset the skin to the default one // eslint-disable-next-line max-params async updatePlayerSkin (entityId: string | number, username: string | undefined, uuidCache: string | undefined, skinUrl: string | true, capeUrl: string | true | undefined = undefined) { - const isCustomSkin = skinUrl !== stevePngUrl - if (isCustomSkin) { - this.loadedSkinEntityIds.add(String(entityId)) - } if (uuidCache) { if (typeof skinUrl === 'string' || typeof capeUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache] = {} if (typeof skinUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache].skinUrl = skinUrl @@ -718,7 +717,7 @@ export class Entities { return typeof component === 'string' ? component : component.text ?? '' } - getItemMesh (item, specificProps: ItemSpecificContextProperties, faceCamera = false, previousModel?: string) { + getItemMesh (item, specificProps: ItemSpecificContextProperties, previousModel?: string) { if (!item.nbt && item.nbtData) item.nbt = item.nbtData const textureUv = this.worldRenderer.getItemRenderData(item, specificProps) if (previousModel && previousModel === textureUv?.modelName) return undefined @@ -737,41 +736,60 @@ export class Entities { return { mesh: outerGroup, isBlock: true, + itemsTexture: null, + itemsTextureFlipped: null, modelName: textureUv.modelName, } } - // Render proper 3D model for items + // TODO: Render proper model (especially for blocks) instead of flat texture if (textureUv) { const textureThree = textureUv.renderInfo?.texture === 'blocks' ? this.worldRenderer.material.map! : this.worldRenderer.itemsTexture + // todo use geometry buffer uv instead! const { u, v, su, sv } = textureUv - const sizeX = su ?? 1 // su is actually width - const sizeY = sv ?? 1 // sv is actually height - - // Use the new unified item mesh function - const result = createItemMesh(textureThree, { - u, - v, - sizeX, - sizeY - }, { - faceCamera, - use3D: !faceCamera, // Only use 3D for non-camera-facing items + const size = undefined + const itemsTexture = textureThree.clone() + itemsTexture.flipY = true + const sizeY = (sv ?? size)! + const sizeX = (su ?? size)! + itemsTexture.offset.set(u, 1 - v - sizeY) + itemsTexture.repeat.set(sizeX, sizeY) + itemsTexture.needsUpdate = true + itemsTexture.magFilter = THREE.NearestFilter + itemsTexture.minFilter = THREE.NearestFilter + const itemsTextureFlipped = itemsTexture.clone() + itemsTextureFlipped.repeat.x *= -1 + itemsTextureFlipped.needsUpdate = true + itemsTextureFlipped.offset.set(u + (sizeX), 1 - v - sizeY) + const material = new THREE.MeshStandardMaterial({ + map: itemsTexture, + transparent: true, + alphaTest: 0.1, }) - + const materialFlipped = new THREE.MeshStandardMaterial({ + map: itemsTextureFlipped, + transparent: true, + alphaTest: 0.1, + }) + const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ + // top left and right bottom are black box materials others are transparent + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), + material, materialFlipped, + ]) let SCALE = 1 if (specificProps['minecraft:display_context'] === 'ground') { SCALE = 0.5 } else if (specificProps['minecraft:display_context'] === 'thirdperson') { SCALE = 6 } - result.mesh.scale.set(SCALE, SCALE, SCALE) - + mesh.scale.set(SCALE, SCALE, SCALE) return { - mesh: result.mesh, + mesh, isBlock: false, + itemsTexture, + itemsTextureFlipped, modelName: textureUv.modelName, - cleanup: result.cleanup } } } @@ -787,6 +805,8 @@ export class Entities { } update (entity: SceneEntity['originalEntity'], overrides) { + const justAdded = !this.entities[entity.id] + const isPlayerModel = entity.name === 'player' if (entity.name === 'zombie_villager' || entity.name === 'husk') { overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}` @@ -797,7 +817,6 @@ export class Entities { } // this can be undefined in case where packet entity_destroy was sent twice (so it was already deleted) let e = this.entities[entity.id] - const justAdded = !e if (entity.delete) { if (!e) return @@ -817,23 +836,21 @@ export class Entities { if (e === undefined) { const group = new THREE.Group() as unknown as SceneEntity group.originalEntity = entity - if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block' || entity.name === 'snowball' - || entity.name === 'egg' || entity.name === 'ender_pearl' || entity.name === 'experience_bottle' - || entity.name === 'splash_potion' || entity.name === 'lingering_potion') { - const item = entity.name === 'tnt' || entity.type === 'projectile' - ? { name: entity.name } + if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block') { + const item = entity.name === 'tnt' + ? { name: 'tnt' } : entity.name === 'falling_block' ? { blockState: entity['objectData'] } : entity.metadata?.find((m: any) => typeof m === 'object' && m?.itemCount) if (item) { const object = this.getItemMesh(item, { 'minecraft:display_context': 'ground', - }, entity.type === 'projectile') + }) if (object) { mesh = object.mesh - if (entity.name === 'item' || entity.type === 'projectile') { + if (entity.name === 'item') { mesh.scale.set(0.5, 0.5, 0.5) - mesh.position.set(0, entity.name === 'item' ? 0.2 : 0.1, 0) + mesh.position.set(0, 0.2, 0) } else { mesh.scale.set(2, 2, 2) mesh.position.set(0, 0.5, 0) @@ -841,8 +858,8 @@ export class Entities { // set faces // mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5) // viewer.scene.add(mesh) + const clock = new THREE.Clock() if (entity.name === 'item') { - const clock = new THREE.Clock() mesh.onBeforeRender = () => { const delta = clock.getDelta() mesh!.rotation.y += delta @@ -871,9 +888,8 @@ export class Entities { group.additionalCleanup = () => { // important: avoid texture memory leak and gpu slowdown - if (object.cleanup) { - object.cleanup() - } + object.itemsTexture?.dispose() + object.itemsTextureFlipped?.dispose() } } } @@ -884,14 +900,20 @@ export class Entities { mesh = wrapper if (entity.username) { - const nametag = addNametag(entity, { fontFamily: 'mojangles' }, wrapper, this.worldRenderer.version) - if (nametag) { - nametag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3 - nametag.scale.multiplyScalar(12) - } + // todo proper colors + const nameTag = new NameTagObject(fromFormattedString(entity.username).text, { + font: `48px ${this.entitiesOptions.fontFamily}`, + }) + nameTag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3 + nameTag.renderOrder = 1000 + + nameTag.name = 'nametag' + + //@ts-expect-error + wrapper.add(nameTag) } } else { - mesh = getEntityMesh(entity, this.worldRenderer, this.entitiesOptions, { ...overrides, customModel: entity['customModel'] }) + mesh = getEntityMesh(entity, this.worldRenderer, this.entitiesOptions, overrides) } if (!mesh) return mesh.name = 'mesh' @@ -1147,7 +1169,8 @@ export class Entities { const cameraPos = this.worldRenderer.cameraObject.position const distance = mesh.position.distanceTo(cameraPos) if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (this.worldRenderer.viewDistance * 16)) { - if (this.loadedSkinEntityIds.has(String(entityId))) return + if (this.loadedSkinEntityIds.has(entityId)) return + this.loadedSkinEntityIds.add(entityId) void this.updatePlayerSkin(entityId, mesh.playerObject.realUsername, mesh.playerObject.realPlayerUuid, true, true) } } @@ -1262,9 +1285,8 @@ export class Entities { const group = new THREE.Object3D() group['additionalCleanup'] = () => { // important: avoid texture memory leak and gpu slowdown - if (itemObject.cleanup) { - itemObject.cleanup() - } + itemObject.itemsTexture?.dispose() + itemObject.itemsTextureFlipped?.dispose() } const itemMesh = itemObject.mesh group.rotation.z = -Math.PI / 16 diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 04cb00ca..5ea89b34 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -44,12 +44,6 @@ const getBackendMethods = (worldRenderer: WorldRendererThree) => { shakeFromDamage: worldRenderer.cameraShake.shakeFromDamage.bind(worldRenderer.cameraShake), onPageInteraction: worldRenderer.media.onPageInteraction.bind(worldRenderer.media), downloadMesherLog: worldRenderer.downloadMesherLog.bind(worldRenderer), - - addWaypoint: worldRenderer.waypoints.addWaypoint.bind(worldRenderer.waypoints), - removeWaypoint: worldRenderer.waypoints.removeWaypoint.bind(worldRenderer.waypoints), - - // New method for updating skybox - setSkyboxImage: worldRenderer.skyboxRenderer.setSkyboxImage.bind(worldRenderer.skyboxRenderer) } } diff --git a/renderer/viewer/three/holdingBlock.ts b/renderer/viewer/three/holdingBlock.ts index f9d00f0e..c8a56386 100644 --- a/renderer/viewer/three/holdingBlock.ts +++ b/renderer/viewer/three/holdingBlock.ts @@ -357,7 +357,7 @@ export default class HoldingBlock { 'minecraft:display_context': 'firstperson', 'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks, 'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks, - }, false, this.lastItemModelName) + }, this.lastItemModelName) if (result) { const { mesh: itemMesh, isBlock, modelName } = result if (isBlock) { diff --git a/renderer/viewer/three/itemMesh.ts b/renderer/viewer/three/itemMesh.ts deleted file mode 100644 index 3fa069b9..00000000 --- a/renderer/viewer/three/itemMesh.ts +++ /dev/null @@ -1,427 +0,0 @@ -import * as THREE from 'three' - -export interface Create3DItemMeshOptions { - depth: number - pixelSize?: number -} - -export interface Create3DItemMeshResult { - geometry: THREE.BufferGeometry - totalVertices: number - totalTriangles: number -} - -/** - * Creates a 3D item geometry with front/back faces and connecting edges - * from a canvas containing the item texture - */ -export function create3DItemMesh ( - canvas: HTMLCanvasElement, - options: Create3DItemMeshOptions -): Create3DItemMeshResult { - const { depth, pixelSize } = options - - // Validate canvas dimensions - if (canvas.width <= 0 || canvas.height <= 0) { - throw new Error(`Invalid canvas dimensions: ${canvas.width}x${canvas.height}`) - } - - const ctx = canvas.getContext('2d')! - const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) - const { data } = imageData - - const w = canvas.width - const h = canvas.height - const halfDepth = depth / 2 - const actualPixelSize = pixelSize ?? (1 / Math.max(w, h)) - - // Find opaque pixels - const isOpaque = (x: number, y: number) => { - if (x < 0 || y < 0 || x >= w || y >= h) return false - const i = (y * w + x) * 4 - return data[i + 3] > 128 // alpha > 128 - } - - const vertices: number[] = [] - const indices: number[] = [] - const uvs: number[] = [] - const normals: number[] = [] - - let vertexIndex = 0 - - // Helper to add a vertex - const addVertex = (x: number, y: number, z: number, u: number, v: number, nx: number, ny: number, nz: number) => { - vertices.push(x, y, z) - uvs.push(u, v) - normals.push(nx, ny, nz) - return vertexIndex++ - } - - // Helper to add a quad (two triangles) - const addQuad = (v0: number, v1: number, v2: number, v3: number) => { - indices.push(v0, v1, v2, v0, v2, v3) - } - - // Convert pixel coordinates to world coordinates - const pixelToWorld = (px: number, py: number) => { - const x = (px / w - 0.5) * actualPixelSize * w - const y = -(py / h - 0.5) * actualPixelSize * h - return { x, y } - } - - // Create a grid of vertices for front and back faces - const frontVertices: Array> = Array.from({ length: h + 1 }, () => Array.from({ length: w + 1 }, () => null)) - const backVertices: Array> = Array.from({ length: h + 1 }, () => Array.from({ length: w + 1 }, () => null)) - - // Create vertices at pixel corners - for (let py = 0; py <= h; py++) { - for (let px = 0; px <= w; px++) { - const { x, y } = pixelToWorld(px - 0.5, py - 0.5) - - // UV coordinates should map to the texture space of the extracted tile - const u = px / w - const v = py / h - - // Check if this vertex is needed for any face or edge - let needVertex = false - - // Check all 4 adjacent pixels to see if any are opaque - const adjacentPixels = [ - [px - 1, py - 1], // top-left pixel - [px, py - 1], // top-right pixel - [px - 1, py], // bottom-left pixel - [px, py] // bottom-right pixel - ] - - for (const [adjX, adjY] of adjacentPixels) { - if (isOpaque(adjX, adjY)) { - needVertex = true - break - } - } - - if (needVertex) { - frontVertices[py][px] = addVertex(x, y, halfDepth, u, v, 0, 0, 1) - backVertices[py][px] = addVertex(x, y, -halfDepth, u, v, 0, 0, -1) - } - } - } - - // Create front and back faces - for (let py = 0; py < h; py++) { - for (let px = 0; px < w; px++) { - if (!isOpaque(px, py)) continue - - const v00 = frontVertices[py][px] - const v10 = frontVertices[py][px + 1] - const v11 = frontVertices[py + 1][px + 1] - const v01 = frontVertices[py + 1][px] - - const b00 = backVertices[py][px] - const b10 = backVertices[py][px + 1] - const b11 = backVertices[py + 1][px + 1] - const b01 = backVertices[py + 1][px] - - if (v00 !== null && v10 !== null && v11 !== null && v01 !== null) { - // Front face - addQuad(v00, v10, v11, v01) - } - - if (b00 !== null && b10 !== null && b11 !== null && b01 !== null) { - // Back face (reversed winding) - addQuad(b10, b00, b01, b11) - } - } - } - - // Create edge faces for each side of the pixel with proper UVs - for (let py = 0; py < h; py++) { - for (let px = 0; px < w; px++) { - if (!isOpaque(px, py)) continue - - const pixelU = (px + 0.5) / w // Center of current pixel - const pixelV = (py + 0.5) / h - - // Left edge (x = px) - if (!isOpaque(px - 1, py)) { - const f0 = frontVertices[py][px] - const f1 = frontVertices[py + 1][px] - const b0 = backVertices[py][px] - const b1 = backVertices[py + 1][px] - - if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) { - // Create new vertices for edge with current pixel's UV - const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, -1, 0, 0) - const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, -1, 0, 0) - const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, -1, 0, 0) - const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, -1, 0, 0) - addQuad(ef0, ef1, eb1, eb0) - } - } - - // Right edge (x = px + 1) - if (!isOpaque(px + 1, py)) { - const f0 = frontVertices[py + 1][px + 1] - const f1 = frontVertices[py][px + 1] - const b0 = backVertices[py + 1][px + 1] - const b1 = backVertices[py][px + 1] - - if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) { - const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, 1, 0, 0) - const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, 1, 0, 0) - const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, 1, 0, 0) - const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, 1, 0, 0) - addQuad(ef0, ef1, eb1, eb0) - } - } - - // Top edge (y = py) - if (!isOpaque(px, py - 1)) { - const f0 = frontVertices[py][px] - const f1 = frontVertices[py][px + 1] - const b0 = backVertices[py][px] - const b1 = backVertices[py][px + 1] - - if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) { - const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, 0, -1, 0) - const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, 0, -1, 0) - const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, 0, -1, 0) - const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, 0, -1, 0) - addQuad(ef0, ef1, eb1, eb0) - } - } - - // Bottom edge (y = py + 1) - if (!isOpaque(px, py + 1)) { - const f0 = frontVertices[py + 1][px + 1] - const f1 = frontVertices[py + 1][px] - const b0 = backVertices[py + 1][px + 1] - const b1 = backVertices[py + 1][px] - - if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) { - const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, 0, 1, 0) - const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, 0, 1, 0) - const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, 0, 1, 0) - const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, 0, 1, 0) - addQuad(ef0, ef1, eb1, eb0) - } - } - } - } - - const geometry = new THREE.BufferGeometry() - geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)) - geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2)) - geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3)) - geometry.setIndex(indices) - - // Compute normals properly - geometry.computeVertexNormals() - - return { - geometry, - totalVertices: vertexIndex, - totalTriangles: indices.length / 3 - } -} - -export interface ItemTextureInfo { - u: number - v: number - sizeX: number - sizeY: number -} - -export interface ItemMeshResult { - mesh: THREE.Object3D - itemsTexture?: THREE.Texture - itemsTextureFlipped?: THREE.Texture - cleanup?: () => void -} - -/** - * Extracts item texture region to a canvas - */ -export function extractItemTextureToCanvas ( - sourceTexture: THREE.Texture, - textureInfo: ItemTextureInfo -): HTMLCanvasElement { - const { u, v, sizeX, sizeY } = textureInfo - - // Calculate canvas size - fix the calculation - const canvasWidth = Math.max(1, Math.floor(sizeX * sourceTexture.image.width)) - const canvasHeight = Math.max(1, Math.floor(sizeY * sourceTexture.image.height)) - - const canvas = document.createElement('canvas') - canvas.width = canvasWidth - canvas.height = canvasHeight - - const ctx = canvas.getContext('2d')! - ctx.imageSmoothingEnabled = false - - // Draw the item texture region to canvas - ctx.drawImage( - sourceTexture.image, - u * sourceTexture.image.width, - v * sourceTexture.image.height, - sizeX * sourceTexture.image.width, - sizeY * sourceTexture.image.height, - 0, - 0, - canvas.width, - canvas.height - ) - - return canvas -} - -/** - * Creates either a 2D or 3D item mesh based on parameters - */ -export function createItemMesh ( - sourceTexture: THREE.Texture, - textureInfo: ItemTextureInfo, - options: { - faceCamera?: boolean - use3D?: boolean - depth?: number - } = {} -): ItemMeshResult { - const { faceCamera = false, use3D = true, depth = 0.04 } = options - const { u, v, sizeX, sizeY } = textureInfo - - if (faceCamera) { - // Create sprite for camera-facing items - const itemsTexture = sourceTexture.clone() - itemsTexture.flipY = true - itemsTexture.offset.set(u, 1 - v - sizeY) - itemsTexture.repeat.set(sizeX, sizeY) - itemsTexture.needsUpdate = true - itemsTexture.magFilter = THREE.NearestFilter - itemsTexture.minFilter = THREE.NearestFilter - - const spriteMat = new THREE.SpriteMaterial({ - map: itemsTexture, - transparent: true, - alphaTest: 0.1, - }) - const mesh = new THREE.Sprite(spriteMat) - - return { - mesh, - itemsTexture, - cleanup () { - itemsTexture.dispose() - } - } - } - - if (use3D) { - // Try to create 3D mesh - try { - const canvas = extractItemTextureToCanvas(sourceTexture, textureInfo) - const { geometry } = create3DItemMesh(canvas, { depth }) - - // Create texture from canvas for the 3D mesh - const itemsTexture = new THREE.CanvasTexture(canvas) - itemsTexture.magFilter = THREE.NearestFilter - itemsTexture.minFilter = THREE.NearestFilter - itemsTexture.wrapS = itemsTexture.wrapT = THREE.ClampToEdgeWrapping - itemsTexture.flipY = false - itemsTexture.needsUpdate = true - - const material = new THREE.MeshStandardMaterial({ - map: itemsTexture, - side: THREE.DoubleSide, - transparent: true, - alphaTest: 0.1, - }) - - const mesh = new THREE.Mesh(geometry, material) - - return { - mesh, - itemsTexture, - cleanup () { - itemsTexture.dispose() - geometry.dispose() - if (material.map) material.map.dispose() - material.dispose() - } - } - } catch (error) { - console.warn('Failed to create 3D item mesh, falling back to 2D:', error) - // Fall through to 2D rendering - } - } - - // Fallback to 2D flat rendering - const itemsTexture = sourceTexture.clone() - itemsTexture.flipY = true - itemsTexture.offset.set(u, 1 - v - sizeY) - itemsTexture.repeat.set(sizeX, sizeY) - itemsTexture.needsUpdate = true - itemsTexture.magFilter = THREE.NearestFilter - itemsTexture.minFilter = THREE.NearestFilter - - const itemsTextureFlipped = itemsTexture.clone() - itemsTextureFlipped.repeat.x *= -1 - itemsTextureFlipped.needsUpdate = true - itemsTextureFlipped.offset.set(u + sizeX, 1 - v - sizeY) - - const material = new THREE.MeshStandardMaterial({ - map: itemsTexture, - transparent: true, - alphaTest: 0.1, - }) - const materialFlipped = new THREE.MeshStandardMaterial({ - map: itemsTextureFlipped, - transparent: true, - alphaTest: 0.1, - }) - const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [ - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), - material, materialFlipped, - ]) - - return { - mesh, - itemsTexture, - itemsTextureFlipped, - cleanup () { - itemsTexture.dispose() - itemsTextureFlipped.dispose() - material.dispose() - materialFlipped.dispose() - } - } -} - -/** - * Creates a complete 3D item mesh from a canvas texture - */ -export function createItemMeshFromCanvas ( - canvas: HTMLCanvasElement, - options: Create3DItemMeshOptions -): THREE.Mesh { - const { geometry } = create3DItemMesh(canvas, options) - - // Base color texture for the item - const colorTexture = new THREE.CanvasTexture(canvas) - colorTexture.magFilter = THREE.NearestFilter - colorTexture.minFilter = THREE.NearestFilter - colorTexture.wrapS = colorTexture.wrapT = THREE.ClampToEdgeWrapping - colorTexture.flipY = false // Important for canvas textures - colorTexture.needsUpdate = true - - // Material - no transparency, no alpha test needed for edges - const material = new THREE.MeshBasicMaterial({ - map: colorTexture, - side: THREE.DoubleSide, - transparent: true, - alphaTest: 0.1 - }) - - return new THREE.Mesh(geometry, material) -} diff --git a/renderer/viewer/three/panorama.ts b/renderer/viewer/three/panorama.ts index 254b980c..76aa285b 100644 --- a/renderer/viewer/three/panorama.ts +++ b/renderer/viewer/three/panorama.ts @@ -13,7 +13,6 @@ import { loadThreeJsTextureFromUrl, loadThreeJsTextureFromUrlSync } from './thre import { WorldRendererThree } from './worldrendererThree' import { EntityMesh } from './entity/EntityMesh' import { DocumentRenderer } from './documentRenderer' -import { PANORAMA_VERSION } from './panoramaShared' const panoramaFiles = [ 'panorama_3.png', // right (+x) @@ -157,7 +156,7 @@ export class PanoramaRenderer { } async worldBlocksPanorama () { - const version = PANORAMA_VERSION + const version = '1.21.4' const fullResourceManager = this.options.resourcesManager as ResourcesManager fullResourceManager.currentConfig = { version, noInventoryGui: true, } await fullResourceManager.updateAssetsData({ }) diff --git a/renderer/viewer/three/panoramaShared.ts b/renderer/viewer/three/panoramaShared.ts deleted file mode 100644 index ad80367f..00000000 --- a/renderer/viewer/three/panoramaShared.ts +++ /dev/null @@ -1 +0,0 @@ -export const PANORAMA_VERSION = '1.21.4' diff --git a/renderer/viewer/three/renderSlot.ts b/renderer/viewer/three/renderSlot.ts index 321633eb..d82e58e3 100644 --- a/renderer/viewer/three/renderSlot.ts +++ b/renderer/viewer/three/renderSlot.ts @@ -10,11 +10,11 @@ export type ResolvedItemModelRender = { export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: ResourcesManagerCommon, debugIsQuickbar = false, fullBlockModelSupport = false): { texture: string, - blockData: Record & { resolvedModel: BlockModel } | null, - scale: number | null, - slice: number[] | null, - modelName: string | null, -} => { + blockData?: Record & { resolvedModel: BlockModel }, + scale?: number, + slice?: number[], + modelName?: string, +} | undefined => { let itemModelName = model.modelName const isItem = loadedData.itemsByName[itemModelName] @@ -37,8 +37,6 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res texture: 'gui', slice: [x, y, atlas.tileSize, atlas.tileSize], scale: 0.25, - blockData: null, - modelName: null } } } @@ -65,18 +63,14 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res return { texture: itemTexture.type, slice: itemTexture.slice, - modelName: itemModelName, - blockData: null, - scale: null + modelName: itemModelName } } else { // is block return { texture: 'blocks', blockData: itemTexture, - modelName: itemModelName, - slice: null, - scale: null + modelName: itemModelName } } } diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts deleted file mode 100644 index fb9edae6..00000000 --- a/renderer/viewer/three/skyboxRenderer.ts +++ /dev/null @@ -1,406 +0,0 @@ -import * as THREE from 'three' -import { DebugGui } from '../lib/DebugGui' - -export const DEFAULT_TEMPERATURE = 0.75 - -export class SkyboxRenderer { - private texture: THREE.Texture | null = null - private mesh: THREE.Mesh | null = null - private skyMesh: THREE.Mesh | null = null - private voidMesh: THREE.Mesh | null = null - - // World state - private worldTime = 0 - private partialTicks = 0 - private viewDistance = 4 - private temperature = DEFAULT_TEMPERATURE - private inWater = false - private waterBreathing = false - private fogBrightness = 0 - private prevFogBrightness = 0 - private readonly fogOrangeness = 0 // Debug property to control sky color orangeness - private readonly distanceFactor = 2.7 - - private readonly brightnessAtPosition = 1 - debugGui: DebugGui - - constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) { - this.debugGui = new DebugGui('skybox_renderer', this, [ - 'temperature', - 'worldTime', - 'inWater', - 'waterBreathing', - 'fogOrangeness', - 'brightnessAtPosition', - 'distanceFactor' - ], { - brightnessAtPosition: { min: 0, max: 1, step: 0.01 }, - temperature: { min: 0, max: 1, step: 0.01 }, - worldTime: { min: 0, max: 24_000, step: 1 }, - fogOrangeness: { min: -1, max: 1, step: 0.01 }, - distanceFactor: { min: 0, max: 5, step: 0.01 }, - }) - - if (!initialImage) { - this.createGradientSky() - } - // this.debugGui.activate() - } - - async init () { - if (this.initialImage) { - await this.setSkyboxImage(this.initialImage) - } - } - - async setSkyboxImage (imageUrl: string) { - // Dispose old textures if they exist - if (this.texture) { - this.texture.dispose() - } - - // Load the equirectangular texture - const textureLoader = new THREE.TextureLoader() - this.texture = await new Promise((resolve) => { - textureLoader.load( - imageUrl, - (texture) => { - texture.mapping = THREE.EquirectangularReflectionMapping - texture.encoding = THREE.sRGBEncoding - // Keep pixelated look - texture.minFilter = THREE.NearestFilter - texture.magFilter = THREE.NearestFilter - texture.needsUpdate = true - resolve(texture) - } - ) - }) - - // Create or update the skybox - if (this.mesh) { - // Just update the texture on the existing material - this.mesh.material.map = this.texture - this.mesh.material.needsUpdate = true - } else { - // Create a large sphere geometry for the skybox - const geometry = new THREE.SphereGeometry(500, 60, 40) - // Flip the geometry inside out - geometry.scale(-1, 1, 1) - - // Create material using the loaded texture - const material = new THREE.MeshBasicMaterial({ - map: this.texture, - side: THREE.FrontSide // Changed to FrontSide since we're flipping the geometry - }) - - // Create and add the skybox mesh - this.mesh = new THREE.Mesh(geometry, material) - this.scene.add(this.mesh) - } - } - - update (cameraPosition: THREE.Vector3, newViewDistance: number) { - if (newViewDistance !== this.viewDistance) { - this.viewDistance = newViewDistance - this.updateSkyColors() - } - - if (this.mesh) { - // Update skybox position - this.mesh.position.copy(cameraPosition) - } else if (this.skyMesh) { - // Update gradient sky position - this.skyMesh.position.copy(cameraPosition) - this.voidMesh?.position.copy(cameraPosition) - this.updateSkyColors() // Update colors based on time of day - } - } - - // Update world time - updateTime (timeOfDay: number, partialTicks = 0) { - if (this.debugGui.visible) return - this.worldTime = timeOfDay - this.partialTicks = partialTicks - this.updateSkyColors() - } - - // Update view distance - updateViewDistance (viewDistance: number) { - this.viewDistance = viewDistance - this.updateSkyColors() - } - - // Update temperature (for biome support) - updateTemperature (temperature: number) { - if (this.debugGui.visible) return - this.temperature = temperature - this.updateSkyColors() - } - - // Update water state - updateWaterState (inWater: boolean, waterBreathing: boolean) { - if (this.debugGui.visible) return - this.inWater = inWater - this.waterBreathing = waterBreathing - this.updateSkyColors() - } - - // Update default skybox setting - updateDefaultSkybox (defaultSkybox: boolean) { - if (this.debugGui.visible) return - this.defaultSkybox = defaultSkybox - this.updateSkyColors() - } - - private createGradientSky () { - const size = 64 - const scale = 256 / size + 2 - - { - const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2) - geometry.rotateX(-Math.PI / 2) - geometry.translate(0, 16, 0) - - const material = new THREE.MeshBasicMaterial({ - color: 0xff_ff_ff, - side: THREE.DoubleSide, - depthTest: false - }) - - this.skyMesh = new THREE.Mesh(geometry, material) - this.scene.add(this.skyMesh) - } - - { - const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2) - geometry.rotateX(-Math.PI / 2) - geometry.translate(0, -16, 0) - - const material = new THREE.MeshBasicMaterial({ - color: 0xff_ff_ff, - side: THREE.DoubleSide, - depthTest: false - }) - - this.voidMesh = new THREE.Mesh(geometry, material) - this.scene.add(this.voidMesh) - } - - this.updateSkyColors() - } - - private getFogColor (partialTicks = 0): THREE.Vector3 { - const angle = this.getCelestialAngle(partialTicks) - let rotation = Math.cos(angle * Math.PI * 2) * 2 + 0.5 - rotation = Math.max(0, Math.min(1, rotation)) - - let x = 0.752_941_2 - let y = 0.847_058_83 - let z = 1 - - x *= (rotation * 0.94 + 0.06) - y *= (rotation * 0.94 + 0.06) - z *= (rotation * 0.91 + 0.09) - - return new THREE.Vector3(x, y, z) - } - - private getSkyColor (x = 0, z = 0, partialTicks = 0): THREE.Vector3 { - const angle = this.getCelestialAngle(partialTicks) - let brightness = Math.cos(angle * 3.141_593 * 2) * 2 + 0.5 - - if (brightness < 0) brightness = 0 - if (brightness > 1) brightness = 1 - - const temperature = this.getTemperature(x, z) - const rgb = this.getSkyColorByTemp(temperature) - - const red = ((rgb >> 16) & 0xff) / 255 - const green = ((rgb >> 8) & 0xff) / 255 - const blue = (rgb & 0xff) / 255 - - return new THREE.Vector3( - red * brightness, - green * brightness, - blue * brightness - ) - } - - private calculateCelestialAngle (time: number, partialTicks: number): number { - const modTime = (time % 24_000) - let angle = (modTime + partialTicks) / 24_000 - 0.25 - - if (angle < 0) { - angle++ - } - if (angle > 1) { - angle-- - } - - angle = 1 - ((Math.cos(angle * Math.PI) + 1) / 2) - angle += (angle - angle) / 3 - - return angle - } - - private getCelestialAngle (partialTicks: number): number { - return this.calculateCelestialAngle(this.worldTime, partialTicks) - } - - private getTemperature (x: number, z: number): number { - return this.temperature - } - - private getSkyColorByTemp (temperature: number): number { - temperature /= 3 - if (temperature < -1) temperature = -1 - if (temperature > 1) temperature = 1 - - // Apply debug fog orangeness to hue - positive values make it more orange, negative make it less orange - const baseHue = 0.622_222_2 - temperature * 0.05 - // Orange is around hue 0.08-0.15, so we need to shift from blue-purple (0.62) toward orange - // Use a more dramatic shift and also increase saturation for more noticeable effect - const orangeHue = 0.12 // Orange hue value - const hue = this.fogOrangeness > 0 - ? baseHue + (orangeHue - baseHue) * this.fogOrangeness * 0.8 // Blend toward orange - : baseHue + this.fogOrangeness * 0.1 // Subtle shift for negative values - const saturation = 0.5 + temperature * 0.1 + Math.abs(this.fogOrangeness) * 0.3 // Increase saturation with orangeness - const brightness = 1 - - return this.hsbToRgb(hue, saturation, brightness) - } - - private hsbToRgb (hue: number, saturation: number, brightness: number): number { - let r = 0; let g = 0; let b = 0 - if (saturation === 0) { - r = g = b = Math.floor(brightness * 255 + 0.5) - } else { - const h = (hue - Math.floor(hue)) * 6 - const f = h - Math.floor(h) - const p = brightness * (1 - saturation) - const q = brightness * (1 - saturation * f) - const t = brightness * (1 - (saturation * (1 - f))) - switch (Math.floor(h)) { - case 0: - r = Math.floor(brightness * 255 + 0.5) - g = Math.floor(t * 255 + 0.5) - b = Math.floor(p * 255 + 0.5) - break - case 1: - r = Math.floor(q * 255 + 0.5) - g = Math.floor(brightness * 255 + 0.5) - b = Math.floor(p * 255 + 0.5) - break - case 2: - r = Math.floor(p * 255 + 0.5) - g = Math.floor(brightness * 255 + 0.5) - b = Math.floor(t * 255 + 0.5) - break - case 3: - r = Math.floor(p * 255 + 0.5) - g = Math.floor(q * 255 + 0.5) - b = Math.floor(brightness * 255 + 0.5) - break - case 4: - r = Math.floor(t * 255 + 0.5) - g = Math.floor(p * 255 + 0.5) - b = Math.floor(brightness * 255 + 0.5) - break - case 5: - r = Math.floor(brightness * 255 + 0.5) - g = Math.floor(p * 255 + 0.5) - b = Math.floor(q * 255 + 0.5) - break - } - } - return 0xff_00_00_00 | (r << 16) | (g << 8) | (Math.trunc(b)) - } - - private updateSkyColors () { - if (!this.skyMesh || !this.voidMesh) return - - // If default skybox is disabled, hide the skybox meshes - if (!this.defaultSkybox) { - this.skyMesh.visible = false - this.voidMesh.visible = false - if (this.mesh) { - this.mesh.visible = false - } - return - } - - // Show skybox meshes when default skybox is enabled - this.skyMesh.visible = true - this.voidMesh.visible = true - if (this.mesh) { - this.mesh.visible = true - } - - // Update fog brightness with smooth transition - this.prevFogBrightness = this.fogBrightness - const renderDistance = this.viewDistance / 32 - const targetBrightness = this.brightnessAtPosition * (1 - renderDistance) + renderDistance - this.fogBrightness += (targetBrightness - this.fogBrightness) * 0.1 - - // Handle water fog - if (this.inWater) { - const waterViewDistance = this.waterBreathing ? 100 : 5 - this.scene.fog = new THREE.Fog(new THREE.Color(0, 0, 1), 0.0025, waterViewDistance) - this.scene.background = new THREE.Color(0, 0, 1) - - // Update sky and void colors for underwater effect - ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 1)) - ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 0.6)) - return - } - - // Normal sky colors - const viewDistance = this.viewDistance * 16 - const viewFactor = 1 - (0.25 + 0.75 * this.viewDistance / 32) ** 0.25 - - const angle = this.getCelestialAngle(this.partialTicks) - const skyColor = this.getSkyColor(0, 0, this.partialTicks) - const fogColor = this.getFogColor(this.partialTicks) - - const brightness = Math.cos(angle * Math.PI * 2) * 2 + 0.5 - const clampedBrightness = Math.max(0, Math.min(1, brightness)) - - // Interpolate fog brightness - const interpolatedBrightness = this.prevFogBrightness + (this.fogBrightness - this.prevFogBrightness) * this.partialTicks - - const red = (fogColor.x + (skyColor.x - fogColor.x) * viewFactor) * clampedBrightness * interpolatedBrightness - const green = (fogColor.y + (skyColor.y - fogColor.y) * viewFactor) * clampedBrightness * interpolatedBrightness - const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness - - this.scene.background = new THREE.Color(red, green, blue) - this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * this.distanceFactor) - - ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z)) - ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color( - skyColor.x * 0.2 + 0.04, - skyColor.y * 0.2 + 0.04, - skyColor.z * 0.6 + 0.1 - )) - } - - dispose () { - if (this.texture) { - this.texture.dispose() - } - if (this.mesh) { - this.mesh.geometry.dispose() - ;(this.mesh.material as THREE.Material).dispose() - this.scene.remove(this.mesh) - } - if (this.skyMesh) { - this.skyMesh.geometry.dispose() - ;(this.skyMesh.material as THREE.Material).dispose() - this.scene.remove(this.skyMesh) - } - if (this.voidMesh) { - this.voidMesh.geometry.dispose() - ;(this.voidMesh.material as THREE.Material).dispose() - this.scene.remove(this.voidMesh) - } - } -} diff --git a/renderer/viewer/three/threeJsSound.ts b/renderer/viewer/three/threeJsSound.ts index 699bb2cc..46aefda9 100644 --- a/renderer/viewer/three/threeJsSound.ts +++ b/renderer/viewer/three/threeJsSound.ts @@ -2,7 +2,7 @@ import * as THREE from 'three' import { WorldRendererThree } from './worldrendererThree' export interface SoundSystem { - playSound: (position: { x: number, y: number, z: number }, path: string, volume?: number, pitch?: number, timeout?: number) => void + playSound: (position: { x: number, y: number, z: number }, path: string, volume?: number, pitch?: number) => void destroy: () => void } @@ -10,17 +10,7 @@ export class ThreeJsSound implements SoundSystem { audioListener: THREE.AudioListener | undefined private readonly activeSounds = new Set() private readonly audioContext: AudioContext | undefined - private readonly soundVolumes = new Map() - baseVolume = 1 - constructor (public worldRenderer: WorldRendererThree) { - worldRenderer.onWorldSwitched.push(() => { - this.stopAll() - }) - - worldRenderer.onReactiveConfigUpdated('volume', (volume) => { - this.changeVolume(volume) - }) } initAudioListener () { @@ -29,24 +19,20 @@ export class ThreeJsSound implements SoundSystem { this.worldRenderer.camera.add(this.audioListener) } - playSound (position: { x: number, y: number, z: number }, path: string, volume = 1, pitch = 1, timeout = 500) { + playSound (position: { x: number, y: number, z: number }, path: string, volume = 1, pitch = 1) { this.initAudioListener() const sound = new THREE.PositionalAudio(this.audioListener!) this.activeSounds.add(sound) - this.soundVolumes.set(sound, volume) const audioLoader = new THREE.AudioLoader() const start = Date.now() void audioLoader.loadAsync(path).then((buffer) => { - if (Date.now() - start > timeout) { - console.warn('Ignored playing sound', path, 'due to timeout:', timeout, 'ms <', Date.now() - start, 'ms') - return - } + if (Date.now() - start > 500) return // play sound.setBuffer(buffer) sound.setRefDistance(20) - sound.setVolume(volume * this.baseVolume) + sound.setVolume(volume) sound.setPlaybackRate(pitch) // set the pitch this.worldRenderer.scene.add(sound) // set sound position @@ -57,35 +43,21 @@ export class ThreeJsSound implements SoundSystem { sound.disconnect() } this.activeSounds.delete(sound) - this.soundVolumes.delete(sound) audioLoader.manager.itemEnd(path) } sound.play() }) } - stopAll () { + destroy () { + // Stop and clean up all active sounds for (const sound of this.activeSounds) { - if (!sound) continue sound.stop() if (sound.source) { sound.disconnect() } - this.worldRenderer.scene.remove(sound) } - this.activeSounds.clear() - this.soundVolumes.clear() - } - changeVolume (volume: number) { - this.baseVolume = volume - for (const [sound, individualVolume] of this.soundVolumes) { - sound.setVolume(individualVolume * this.baseVolume) - } - } - - destroy () { - this.stopAll() // Remove and cleanup audio listener if (this.audioListener) { this.audioListener.removeFromParent() diff --git a/renderer/viewer/three/waypointSprite.ts b/renderer/viewer/three/waypointSprite.ts deleted file mode 100644 index 6a30e6db..00000000 --- a/renderer/viewer/three/waypointSprite.ts +++ /dev/null @@ -1,418 +0,0 @@ -import * as THREE from 'three' - -// Centralized visual configuration (in screen pixels) -export const WAYPOINT_CONFIG = { - // Target size in screen pixels (this controls the final sprite size) - TARGET_SCREEN_PX: 150, - // Canvas size for internal rendering (keep power of 2 for textures) - CANVAS_SIZE: 256, - // Relative positions in canvas (0-1) - LAYOUT: { - DOT_Y: 0.3, - NAME_Y: 0.45, - DISTANCE_Y: 0.55, - }, - // Multiplier for canvas internal resolution to keep text crisp - CANVAS_SCALE: 2, - ARROW: { - enabledDefault: false, - pixelSize: 50, - paddingPx: 50, - }, -} - -export type WaypointSprite = { - group: THREE.Group - sprite: THREE.Sprite - // Offscreen arrow controls - enableOffscreenArrow: (enabled: boolean) => void - setArrowParent: (parent: THREE.Object3D | null) => void - // Convenience combined updater - updateForCamera: ( - cameraPosition: THREE.Vector3, - camera: THREE.PerspectiveCamera, - viewportWidthPx: number, - viewportHeightPx: number - ) => boolean - // Utilities - setColor: (color: number) => void - setLabel: (label?: string) => void - updateDistanceText: (label: string, distanceText: string) => void - setVisible: (visible: boolean) => void - setPosition: (x: number, y: number, z: number) => void - dispose: () => void -} - -export function createWaypointSprite (options: { - position: THREE.Vector3 | { x: number, y: number, z: number }, - color?: number, - label?: string, - depthTest?: boolean, - // Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this) - labelYOffset?: number, - metadata?: any, -}): WaypointSprite { - const color = options.color ?? 0xFF_00_00 - const depthTest = options.depthTest ?? false - const labelYOffset = options.labelYOffset ?? 1.5 - - // Build combined sprite - const sprite = createCombinedSprite(color, options.label ?? '', '0m', depthTest) - sprite.renderOrder = 10 - let currentLabel = options.label ?? '' - - // Offscreen arrow (detached by default) - let arrowSprite: THREE.Sprite | undefined - let arrowParent: THREE.Object3D | null = null - let arrowEnabled = WAYPOINT_CONFIG.ARROW.enabledDefault - - // Group for easy add/remove - const group = new THREE.Group() - group.add(sprite) - - // Initial position - const { x, y, z } = options.position - group.position.set(x, y, z) - - function setColor (newColor: number) { - const canvas = drawCombinedCanvas(newColor, currentLabel, '0m') - const texture = new THREE.CanvasTexture(canvas) - const mat = sprite.material - mat.map?.dispose() - mat.map = texture - mat.needsUpdate = true - } - - function setLabel (newLabel?: string) { - currentLabel = newLabel ?? '' - const canvas = drawCombinedCanvas(color, currentLabel, '0m') - const texture = new THREE.CanvasTexture(canvas) - const mat = sprite.material - mat.map?.dispose() - mat.map = texture - mat.needsUpdate = true - } - - function updateDistanceText (label: string, distanceText: string) { - const canvas = drawCombinedCanvas(color, label, distanceText) - const texture = new THREE.CanvasTexture(canvas) - const mat = sprite.material - mat.map?.dispose() - mat.map = texture - mat.needsUpdate = true - } - - function setVisible (visible: boolean) { - sprite.visible = visible - } - - function setPosition (nx: number, ny: number, nz: number) { - group.position.set(nx, ny, nz) - } - - // Keep constant pixel size on screen using global config - function updateScaleScreenPixels ( - cameraPosition: THREE.Vector3, - cameraFov: number, - distance: number, - viewportHeightPx: number - ) { - const vFovRad = cameraFov * Math.PI / 180 - const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance - // Use configured target screen size - const scale = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.TARGET_SCREEN_PX / viewportHeightPx) - sprite.scale.set(scale, scale, 1) - } - - function ensureArrow () { - if (arrowSprite) return - const size = 128 - const canvas = document.createElement('canvas') - canvas.width = size - canvas.height = size - const ctx = canvas.getContext('2d')! - ctx.clearRect(0, 0, size, size) - - // Draw arrow shape - ctx.beginPath() - ctx.moveTo(size * 0.15, size * 0.5) - ctx.lineTo(size * 0.85, size * 0.5) - ctx.lineTo(size * 0.5, size * 0.15) - ctx.closePath() - - // Use waypoint color for arrow - const colorHex = `#${color.toString(16).padStart(6, '0')}` - ctx.lineWidth = 6 - ctx.strokeStyle = 'black' - ctx.stroke() - ctx.fillStyle = colorHex - ctx.fill() - - const texture = new THREE.CanvasTexture(canvas) - const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false }) - arrowSprite = new THREE.Sprite(material) - arrowSprite.renderOrder = 12 - arrowSprite.visible = false - if (arrowParent) arrowParent.add(arrowSprite) - } - - function enableOffscreenArrow (enabled: boolean) { - arrowEnabled = enabled - if (!enabled && arrowSprite) arrowSprite.visible = false - } - - function setArrowParent (parent: THREE.Object3D | null) { - if (arrowSprite?.parent) arrowSprite.parent.remove(arrowSprite) - arrowParent = parent - if (arrowSprite && parent) parent.add(arrowSprite) - } - - function updateOffscreenArrow ( - camera: THREE.PerspectiveCamera, - viewportWidthPx: number, - viewportHeightPx: number - ): boolean { - if (!arrowEnabled) return true - ensureArrow() - if (!arrowSprite) return true - - // Check if onlyLeftRight is enabled in metadata - const onlyLeftRight = options.metadata?.onlyLeftRight === true - - // Build camera basis using camera.up to respect custom orientations - const forward = new THREE.Vector3() - camera.getWorldDirection(forward) // camera look direction - const upWorld = camera.up.clone().normalize() - const right = new THREE.Vector3().copy(forward).cross(upWorld).normalize() - const upCam = new THREE.Vector3().copy(right).cross(forward).normalize() - - // Vector from camera to waypoint - const camPos = new THREE.Vector3().setFromMatrixPosition(camera.matrixWorld) - const toWp = new THREE.Vector3(group.position.x, group.position.y, group.position.z).sub(camPos) - - // Components in camera basis - const z = toWp.dot(forward) - const x = toWp.dot(right) - const y = toWp.dot(upCam) - - const aspect = viewportWidthPx / viewportHeightPx - const vFovRad = camera.fov * Math.PI / 180 - const hFovRad = 2 * Math.atan(Math.tan(vFovRad / 2) * aspect) - - // Determine if waypoint is inside view frustum using angular checks - const thetaX = Math.atan2(x, z) - const thetaY = Math.atan2(y, z) - const visible = z > 0 && Math.abs(thetaX) <= hFovRad / 2 && Math.abs(thetaY) <= vFovRad / 2 - if (visible) { - arrowSprite.visible = false - return true - } - - // Direction on screen in normalized frustum units - let rx = thetaX / (hFovRad / 2) - let ry = thetaY / (vFovRad / 2) - - // If behind the camera, snap to dominant axis to avoid confusing directions - if (z <= 0) { - if (Math.abs(rx) > Math.abs(ry)) { - rx = Math.sign(rx) - ry = 0 - } else { - rx = 0 - ry = Math.sign(ry) - } - } - - // Apply onlyLeftRight logic - restrict arrows to left/right edges only - if (onlyLeftRight) { - // Force the arrow to appear only on left or right edges - if (Math.abs(rx) > Math.abs(ry)) { - // Horizontal direction is dominant, keep it - ry = 0 - } else { - // Vertical direction is dominant, but we want only left/right - // So choose left or right based on the sign of rx - rx = rx >= 0 ? 1 : -1 - ry = 0 - } - } - - // Place on the rectangle border [-1,1]x[-1,1] - const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1 - let ndcX = rx / s - let ndcY = ry / s - - // Apply padding in pixel space by clamping - const padding = WAYPOINT_CONFIG.ARROW.paddingPx - const pxX = ((ndcX + 1) * 0.5) * viewportWidthPx - const pxY = ((1 - ndcY) * 0.5) * viewportHeightPx - const clampedPxX = Math.min(Math.max(pxX, padding), viewportWidthPx - padding) - const clampedPxY = Math.min(Math.max(pxY, padding), viewportHeightPx - padding) - ndcX = (clampedPxX / viewportWidthPx) * 2 - 1 - ndcY = -(clampedPxY / viewportHeightPx) * 2 + 1 - - // Compute world position at a fixed distance in front of the camera using camera basis - const placeDist = Math.max(2, camera.near * 4) - const halfPlaneHeight = Math.tan(vFovRad / 2) * placeDist - const halfPlaneWidth = halfPlaneHeight * aspect - const pos = camPos.clone() - .add(forward.clone().multiplyScalar(placeDist)) - .add(right.clone().multiplyScalar(ndcX * halfPlaneWidth)) - .add(upCam.clone().multiplyScalar(ndcY * halfPlaneHeight)) - - // Update arrow sprite - arrowSprite.visible = true - arrowSprite.position.copy(pos) - - // Angle for rotation relative to screen right/up (derived from camera up vector) - const angle = Math.atan2(ry, rx) - arrowSprite.material.rotation = angle - Math.PI / 2 - - // Constant pixel size for arrow (use fixed placement distance) - const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * placeDist - const sPx = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.ARROW.pixelSize / viewportHeightPx) - arrowSprite.scale.set(sPx, sPx, 1) - return false - } - - function computeDistance (cameraPosition: THREE.Vector3): number { - return cameraPosition.distanceTo(group.position) - } - - function updateForCamera ( - cameraPosition: THREE.Vector3, - camera: THREE.PerspectiveCamera, - viewportWidthPx: number, - viewportHeightPx: number - ): boolean { - const distance = computeDistance(cameraPosition) - // Keep constant pixel size - updateScaleScreenPixels(cameraPosition, camera.fov, distance, viewportHeightPx) - // Update text - updateDistanceText(currentLabel, `${Math.round(distance)}m`) - // Update arrow and visibility - const onScreen = updateOffscreenArrow(camera, viewportWidthPx, viewportHeightPx) - setVisible(onScreen) - return onScreen - } - - function dispose () { - const mat = sprite.material - mat.map?.dispose() - mat.dispose() - if (arrowSprite) { - const am = arrowSprite.material - am.map?.dispose() - am.dispose() - } - } - - return { - group, - sprite, - enableOffscreenArrow, - setArrowParent, - updateForCamera, - setColor, - setLabel, - updateDistanceText, - setVisible, - setPosition, - dispose, - } -} - -// Internal helpers -function drawCombinedCanvas (color: number, id: string, distance: string): HTMLCanvasElement { - const scale = WAYPOINT_CONFIG.CANVAS_SCALE * (globalThis.devicePixelRatio || 1) - const size = WAYPOINT_CONFIG.CANVAS_SIZE * scale - const canvas = document.createElement('canvas') - canvas.width = size - canvas.height = size - const ctx = canvas.getContext('2d')! - - // Clear canvas - ctx.clearRect(0, 0, size, size) - - // Draw dot - const centerX = size / 2 - const dotY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DOT_Y) - const radius = Math.round(size * 0.05) // Dot takes up ~12% of canvas height - const borderWidth = Math.max(2, Math.round(4 * scale)) - - // Outer border (black) - ctx.beginPath() - ctx.arc(centerX, dotY, radius + borderWidth, 0, Math.PI * 2) - ctx.fillStyle = 'black' - ctx.fill() - - // Inner circle (colored) - ctx.beginPath() - ctx.arc(centerX, dotY, radius, 0, Math.PI * 2) - ctx.fillStyle = `#${color.toString(16).padStart(6, '0')}` - ctx.fill() - - // Text properties - ctx.textAlign = 'center' - ctx.textBaseline = 'middle' - - // Title - const nameFontPx = Math.round(size * 0.08) // ~8% of canvas height - const distanceFontPx = Math.round(size * 0.06) // ~6% of canvas height - ctx.font = `bold ${nameFontPx}px mojangles` - ctx.lineWidth = Math.max(2, Math.round(3 * scale)) - const nameY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.NAME_Y) - - ctx.strokeStyle = 'black' - ctx.strokeText(id, centerX, nameY) - ctx.fillStyle = 'white' - ctx.fillText(id, centerX, nameY) - - // Distance - ctx.font = `bold ${distanceFontPx}px mojangles` - ctx.lineWidth = Math.max(2, Math.round(2 * scale)) - const distanceY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DISTANCE_Y) - - ctx.strokeStyle = 'black' - ctx.strokeText(distance, centerX, distanceY) - ctx.fillStyle = '#CCCCCC' - ctx.fillText(distance, centerX, distanceY) - - return canvas -} - -function createCombinedSprite (color: number, id: string, distance: string, depthTest: boolean): THREE.Sprite { - const canvas = drawCombinedCanvas(color, id, distance) - const texture = new THREE.CanvasTexture(canvas) - texture.anisotropy = 1 - texture.magFilter = THREE.LinearFilter - texture.minFilter = THREE.LinearFilter - const material = new THREE.SpriteMaterial({ - map: texture, - transparent: true, - opacity: 1, - depthTest, - depthWrite: false, - }) - const sprite = new THREE.Sprite(material) - sprite.position.set(0, 0, 0) - return sprite -} - -export const WaypointHelpers = { - // World-scale constant size helper - computeWorldScale (distance: number, fixedReference = 10) { - return Math.max(0.0001, distance / fixedReference) - }, - // Screen-pixel constant size helper - computeScreenPixelScale ( - camera: THREE.PerspectiveCamera, - distance: number, - pixelSize: number, - viewportHeightPx: number - ) { - const vFovRad = camera.fov * Math.PI / 180 - const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance - return worldUnitsPerScreenHeightAtDist * (pixelSize / viewportHeightPx) - } -} diff --git a/renderer/viewer/three/waypoints.ts b/renderer/viewer/three/waypoints.ts deleted file mode 100644 index 256ca6df..00000000 --- a/renderer/viewer/three/waypoints.ts +++ /dev/null @@ -1,140 +0,0 @@ -import * as THREE from 'three' -import { WorldRendererThree } from './worldrendererThree' -import { createWaypointSprite, type WaypointSprite } from './waypointSprite' - -interface Waypoint { - id: string - x: number - y: number - z: number - minDistance: number - color: number - label?: string - sprite: WaypointSprite -} - -interface WaypointOptions { - color?: number - label?: string - minDistance?: number - metadata?: any -} - -export class WaypointsRenderer { - private readonly waypoints = new Map() - private readonly waypointScene = new THREE.Scene() - - constructor ( - private readonly worldRenderer: WorldRendererThree - ) { - } - - private updateWaypoints () { - const playerPos = this.worldRenderer.cameraObject.position - const sizeVec = this.worldRenderer.renderer.getSize(new THREE.Vector2()) - - for (const waypoint of this.waypoints.values()) { - const waypointPos = new THREE.Vector3(waypoint.x, waypoint.y, waypoint.z) - const distance = playerPos.distanceTo(waypointPos) - const visible = !waypoint.minDistance || distance >= waypoint.minDistance - - waypoint.sprite.setVisible(visible) - - if (visible) { - // Update position - waypoint.sprite.setPosition(waypoint.x, waypoint.y, waypoint.z) - // Ensure camera-based update each frame - waypoint.sprite.updateForCamera(this.worldRenderer.getCameraPosition(), this.worldRenderer.camera, sizeVec.width, sizeVec.height) - } - } - } - - render () { - if (this.waypoints.size === 0) return - - // Update waypoint scaling - this.updateWaypoints() - - // Render waypoints scene with the world camera - this.worldRenderer.renderer.render(this.waypointScene, this.worldRenderer.camera) - } - - // Removed sprite/label texture creation. Use utils/waypointSprite.ts - - addWaypoint ( - id: string, - x: number, - y: number, - z: number, - options: WaypointOptions = {} - ) { - // Remove existing waypoint if it exists - this.removeWaypoint(id) - - const color = options.color ?? 0xFF_00_00 - const { label, metadata } = options - const minDistance = options.minDistance ?? 0 - - const sprite = createWaypointSprite({ - position: new THREE.Vector3(x, y, z), - color, - label: (label || id), - metadata, - }) - sprite.enableOffscreenArrow(true) - sprite.setArrowParent(this.waypointScene) - - this.waypointScene.add(sprite.group) - - this.waypoints.set(id, { - id, x: x + 0.5, y: y + 0.5, z: z + 0.5, minDistance, - color, label, - sprite, - }) - } - - removeWaypoint (id: string) { - const waypoint = this.waypoints.get(id) - if (waypoint) { - this.waypointScene.remove(waypoint.sprite.group) - waypoint.sprite.dispose() - this.waypoints.delete(id) - } - } - - clear () { - for (const id of this.waypoints.keys()) { - this.removeWaypoint(id) - } - } - - testWaypoint () { - this.addWaypoint('Test Point', 0, 70, 0, { color: 0x00_FF_00, label: 'Test Point' }) - this.addWaypoint('Spawn', 0, 64, 0, { color: 0xFF_FF_00, label: 'Spawn' }) - this.addWaypoint('Far Point', 100, 70, 100, { color: 0x00_00_FF, label: 'Far Point' }) - } - - getWaypoint (id: string): Waypoint | undefined { - return this.waypoints.get(id) - } - - getAllWaypoints (): Waypoint[] { - return [...this.waypoints.values()] - } - - setWaypointColor (id: string, color: number) { - const waypoint = this.waypoints.get(id) - if (waypoint) { - waypoint.sprite.setColor(color) - waypoint.color = color - } - } - - setWaypointLabel (id: string, label?: string) { - const waypoint = this.waypoints.get(id) - if (waypoint) { - waypoint.label = label - waypoint.sprite.setLabel(label) - } - } -} diff --git a/renderer/viewer/three/world/cursorBlock.ts b/renderer/viewer/three/world/cursorBlock.ts index a03a6999..b71c1b8d 100644 --- a/renderer/viewer/three/world/cursorBlock.ts +++ b/renderer/viewer/three/world/cursorBlock.ts @@ -28,7 +28,7 @@ export class CursorBlock { } cursorLineMaterial: LineMaterial - interactionLines: null | { blockPos: Vec3, mesh: THREE.Group, shapePositions: BlocksShapes | undefined } = null + interactionLines: null | { blockPos: Vec3, mesh: THREE.Group } = null prevColor: string | undefined blockBreakMesh: THREE.Mesh breakTextures: THREE.Texture[] = [] @@ -62,13 +62,6 @@ export class CursorBlock { this.worldRenderer.onReactivePlayerStateUpdated('gameMode', () => { this.updateLineMaterial() }) - // todo figure out why otherwise fog from skybox breaks it - setTimeout(() => { - this.updateLineMaterial() - if (this.interactionLines) { - this.setHighlightCursorBlock(this.interactionLines.blockPos, this.interactionLines.shapePositions, true) - } - }) } // Update functions @@ -76,9 +69,6 @@ export class CursorBlock { const inCreative = this.worldRenderer.playerStateReactive.gameMode === 'creative' const pixelRatio = this.worldRenderer.renderer.getPixelRatio() - if (this.cursorLineMaterial) { - this.cursorLineMaterial.dispose() - } this.cursorLineMaterial = new LineMaterial({ color: (() => { switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) { @@ -125,8 +115,8 @@ export class CursorBlock { } } - setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes, force = false): void { - if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos) && !force) { + setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes): void { + if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) { return } if (this.interactionLines !== null) { @@ -150,7 +140,7 @@ export class CursorBlock { } this.worldRenderer.scene.add(group) group.visible = !this.cursorLinesHidden - this.interactionLines = { blockPos, mesh: group, shapePositions } + this.interactionLines = { blockPos, mesh: group } } render () { diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index 1b4e6152..bc95f06b 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -3,7 +3,6 @@ import { Vec3 } from 'vec3' import nbt from 'prismarine-nbt' import PrismarineChatLoader from 'prismarine-chat' import * as tweenJs from '@tweenjs/tween.js' -import { Biome } from 'minecraft-data' import { renderSign } from '../sign-renderer' import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer' import { chunkPos, sectionPos } from '../lib/simpleUtils' @@ -24,8 +23,6 @@ import { ThreeJsSound } from './threeJsSound' import { CameraShake } from './cameraShake' import { ThreeJsMedia } from './threeJsMedia' import { Fountain } from './threeJsParticles' -import { WaypointsRenderer } from './waypoints' -import { DEFAULT_TEMPERATURE, SkyboxRenderer } from './skyboxRenderer' type SectionKey = string @@ -51,7 +48,6 @@ export class WorldRendererThree extends WorldRendererCommon { cameraContainer: THREE.Object3D media: ThreeJsMedia waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] } - waypoints: WaypointsRenderer camera: THREE.PerspectiveCamera renderTimeAvg = 0 sectionsOffsetsAnimations = {} as { @@ -73,7 +69,6 @@ export class WorldRendererThree extends WorldRendererCommon { } fountains: Fountain[] = [] DEBUG_RAYCAST = false - skyboxRenderer: SkyboxRenderer private currentPosTween?: tweenJs.Tween private currentRotTween?: tweenJs.Tween<{ pitch: number, yaw: number }> @@ -97,10 +92,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlock = new HoldingBlock(this) this.holdingBlockLeft = new HoldingBlock(this, true) - // Initialize skybox renderer - this.skyboxRenderer = new SkyboxRenderer(this.scene, this.worldRendererConfig.defaultSkybox, null) - void this.skyboxRenderer.init() - this.addDebugOverlay() this.resetScene() void this.init() @@ -108,8 +99,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.soundSystem = new ThreeJsSound(this) this.cameraShake = new CameraShake(this, this.onRender) this.media = new ThreeJsMedia(this) - this.waypoints = new WaypointsRenderer(this) - // this.fountain = new Fountain(this.scene, this.scene, { // position: new THREE.Vector3(0, 10, 0), // }) @@ -130,8 +119,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.protocolCustomBlocks.clear() // Reset section animations this.sectionsOffsetsAnimations = {} - // Clear waypoints - this.waypoints.clear() }) } @@ -174,10 +161,7 @@ export class WorldRendererThree extends WorldRendererCommon { override watchReactivePlayerState () { super.watchReactivePlayerState() this.onReactivePlayerStateUpdated('inWater', (value) => { - this.skyboxRenderer.updateWaterState(value, this.playerStateReactive.waterBreathing) - }) - this.onReactivePlayerStateUpdated('waterBreathing', (value) => { - this.skyboxRenderer.updateWaterState(this.playerStateReactive.inWater, value) + this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, this.playerStateReactive.waterBreathing ? 100 : 20) : null }) this.onReactivePlayerStateUpdated('ambientLight', (value) => { if (!value) return @@ -206,9 +190,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.onReactiveConfigUpdated('showChunkBorders', (value) => { this.updateShowChunksBorder(value) }) - this.onReactiveConfigUpdated('defaultSkybox', (value) => { - this.skyboxRenderer.updateDefaultSkybox(value) - }) } changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) { @@ -271,19 +252,6 @@ export class WorldRendererThree extends WorldRendererCommon { } else { this.starField.remove() } - - this.skyboxRenderer.updateTime(newTime) - } - - biomeUpdated (biome: Biome): void { - if (biome?.temperature !== undefined) { - this.skyboxRenderer.updateTemperature(biome.temperature) - } - } - - biomeReset (): void { - // Reset to default temperature when biome is unknown - this.skyboxRenderer.updateTemperature(DEFAULT_TEMPERATURE) } getItemRenderData (item: Record, specificProps: ItemSpecificContextProperties) { @@ -485,7 +453,7 @@ export class WorldRendererThree extends WorldRendererCommon { return worldPos } - getSectionCameraPosition () { + getWorldCameraPosition () { const pos = this.getCameraPosition() return new Vec3( Math.floor(pos.x / 16), @@ -495,7 +463,7 @@ export class WorldRendererThree extends WorldRendererCommon { } updateCameraSectionPos () { - const newSectionPos = this.getSectionCameraPosition() + const newSectionPos = this.getWorldCameraPosition() if (!this.cameraSectionPos.equals(newSectionPos)) { this.cameraSectionPos = newSectionPos this.cameraSectionPositionUpdate() @@ -734,10 +702,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.cursorBlock.render() this.updateSectionOffsets() - // Update skybox position to follow camera - const cameraPos = this.getCameraPosition() - this.skyboxRenderer.update(cameraPos, this.viewDistance) - const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov if (sizeOrFovChanged) { const size = this.renderer.getSize(new THREE.Vector2()) @@ -773,8 +737,6 @@ export class WorldRendererThree extends WorldRendererCommon { fountain.render() } - this.waypoints.render() - for (const onRender of this.onRender) { onRender() } @@ -787,17 +749,12 @@ export class WorldRendererThree extends WorldRendererCommon { } renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) { - let textureData: string - if (blockEntity.SkullOwner) { - textureData = blockEntity.SkullOwner.Properties?.textures?.[0]?.Value - } else { - textureData = blockEntity.profile?.properties?.find(p => p.name === 'textures')?.value - } - if (!textureData) return + const textures = blockEntity.SkullOwner?.Properties?.textures[0] + if (!textures) return try { - const decodedData = JSON.parse(Buffer.from(textureData, 'base64').toString()) - let skinUrl = decodedData.textures?.SKIN?.url + const textureData = JSON.parse(Buffer.from(textures.Value, 'base64').toString()) + let skinUrl = textureData.textures?.SKIN?.url const { skinTexturesProxy } = this.worldRendererConfig if (skinTexturesProxy) { skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy) @@ -982,7 +939,6 @@ export class WorldRendererThree extends WorldRendererCommon { destroy (): void { super.destroy() - this.skyboxRenderer.dispose() } shouldObjectVisible (object: THREE.Object3D) { @@ -1066,13 +1022,6 @@ class StarField { constructor ( private readonly worldRenderer: WorldRendererThree ) { - const clock = new THREE.Clock() - const speed = 0.2 - this.worldRenderer.onRender.push(() => { - if (!this.points) return - this.points.position.copy(this.worldRenderer.getCameraPosition()); - (this.points.material as StarfieldMaterial).uniforms.time.value = clock.getElapsedTime() * speed - }) } addToScene () { @@ -1083,6 +1032,7 @@ class StarField { const count = 7000 const factor = 7 const saturation = 10 + const speed = 0.2 const geometry = new THREE.BufferGeometry() @@ -1115,6 +1065,11 @@ class StarField { this.points = new THREE.Points(geometry, material) this.worldRenderer.scene.add(this.points) + const clock = new THREE.Clock() + this.points.onBeforeRender = (renderer, scene, camera) => { + this.points?.position.copy?.(this.worldRenderer.getCameraPosition()) + material.uniforms.time.value = clock.getElapsedTime() * speed + } this.points.renderOrder = -1 } diff --git a/rsbuild.config.ts b/rsbuild.config.ts index 6cd6b2ed..e264f6b7 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -139,13 +139,6 @@ const appConfig = defineConfig({ // 50kb limit for data uri dataUriLimit: SINGLE_FILE_BUILD ? 1 * 1024 * 1024 * 1024 : 50 * 1024 }, - performance: { - // prefetch: { - // include(filename) { - // return filename.includes('mc-data') || filename.includes('mc-assets') - // }, - // }, - }, source: { entry: { index: './src/index.ts', @@ -161,7 +154,7 @@ const appConfig = defineConfig({ 'process.platform': '"browser"', 'process.env.GITHUB_URL': JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` || githubRepositoryFallback}`), - 'process.env.ALWAYS_MINIMAL_SERVER_UI': JSON.stringify(process.env.ALWAYS_MINIMAL_SERVER_UI), + 'process.env.DEPS_VERSIONS': JSON.stringify({}), 'process.env.RELEASE_TAG': JSON.stringify(releaseTag), 'process.env.RELEASE_LINK': JSON.stringify(releaseLink), 'process.env.RELEASE_CHANGELOG': JSON.stringify(releaseChangelog), @@ -197,7 +190,7 @@ const appConfig = defineConfig({ childProcess.execSync('tsx ./scripts/optimizeBlockCollisions.ts', { stdio: 'inherit' }) } // childProcess.execSync(['tsx', './scripts/genLargeDataAliases.ts', ...(SINGLE_FILE_BUILD ? ['--compressed'] : [])].join(' '), { stdio: 'inherit' }) - genLargeDataAliases(SINGLE_FILE_BUILD || process.env.ALWAYS_COMPRESS_LARGE_DATA === 'true') + genLargeDataAliases(SINGLE_FILE_BUILD) fsExtra.copySync('./node_modules/mc-assets/dist/other-textures/latest/entity', './dist/textures/entity') fsExtra.copySync('./assets/background', './dist/background') fs.copyFileSync('./assets/favicon.png', './dist/favicon.png') @@ -240,10 +233,6 @@ const appConfig = defineConfig({ prep() }) build.onAfterBuild(async () => { - if (fs.readdirSync('./assets/customTextures').length > 0) { - childProcess.execSync('tsx ./scripts/patchAssets.ts', { stdio: 'inherit' }) - } - if (SINGLE_FILE_BUILD) { // check that only index.html is in the dist/single folder const singleBuildFiles = fs.readdirSync('./dist/single') diff --git a/scripts/genLargeDataAliases.ts b/scripts/genLargeDataAliases.ts index 2372dbfd..0cf206df 100644 --- a/scripts/genLargeDataAliases.ts +++ b/scripts/genLargeDataAliases.ts @@ -16,8 +16,7 @@ export const genLargeDataAliases = async (isCompressed: boolean) => { let str = `${decoderCode}\nexport const importLargeData = async (mod: ${Object.keys(modules).map(x => `'${x}'`).join(' | ')}) => {\n` for (const [module, { compressed, raw }] of Object.entries(modules)) { - const chunkName = module === 'mcData' ? 'mc-data' : 'mc-assets'; - let importCode = `(await import(/* webpackChunkName: "${chunkName}" */ '${isCompressed ? compressed : raw}')).default`; + let importCode = `(await import('${isCompressed ? compressed : raw}')).default`; if (isCompressed) { importCode = `JSON.parse(decompressFromBase64(${importCode}))` } @@ -31,8 +30,6 @@ export const genLargeDataAliases = async (isCompressed: boolean) => { const decoderCode = /* ts */ ` import pako from 'pako'; -globalThis.pako = { inflate: pako.inflate.bind(pako) } - function decompressFromBase64(input) { console.time('decompressFromBase64') // Decode the Base64 string diff --git a/scripts/makeOptimizedMcData.mjs b/scripts/makeOptimizedMcData.mjs index a572d067..05948cf2 100644 --- a/scripts/makeOptimizedMcData.mjs +++ b/scripts/makeOptimizedMcData.mjs @@ -6,8 +6,8 @@ import { dirname } from 'node:path' import supportedVersions from '../src/supportedVersions.mjs' import { gzipSizeFromFileSync } from 'gzip-size' import fs from 'fs' -import { default as _JsonOptimizer } from '../src/optimizeJson' -import { gzipSync } from 'zlib' +import {default as _JsonOptimizer} from '../src/optimizeJson' +import { gzipSync } from 'zlib'; import MinecraftData from 'minecraft-data' import MCProtocol from 'minecraft-protocol' @@ -21,12 +21,12 @@ const require = Module.createRequire(import.meta.url) const dataPaths = require('minecraft-data/minecraft-data/data/dataPaths.json') -function toMajor(version) { +function toMajor (version) { const [a, b] = (version + '').split('.') return `${a}.${b}` } -let versions = {} +const versions = {} const dataTypes = new Set() for (const [version, dataSet] of Object.entries(dataPaths.pc)) { @@ -42,31 +42,6 @@ const versionToNumber = (ver) => { return +`${x.padStart(2, '0')}${y.padStart(2, '0')}${z.padStart(2, '0')}` } -// Version clipping support -const minVersion = process.env.MIN_MC_VERSION -const maxVersion = process.env.MAX_MC_VERSION - -// Filter versions based on MIN_VERSION and MAX_VERSION if provided -if (minVersion || maxVersion) { - const filteredVersions = {} - const minVersionNum = minVersion ? versionToNumber(minVersion) : 0 - const maxVersionNum = maxVersion ? versionToNumber(maxVersion) : Infinity - - for (const [version, dataSet] of Object.entries(versions)) { - const versionNum = versionToNumber(version) - if (versionNum >= minVersionNum && versionNum <= maxVersionNum) { - filteredVersions[version] = dataSet - } - } - - versions = filteredVersions - - console.log(`Version clipping applied: ${minVersion || 'none'} to ${maxVersion || 'none'}`) - console.log(`Processing ${Object.keys(versions).length} versions:`, Object.keys(versions).sort((a, b) => versionToNumber(a) - versionToNumber(b))) -} - -console.log('Bundling version range:', Object.keys(versions)[0], 'to', Object.keys(versions).at(-1)) - // if not included here (even as {}) will not be bundled & accessible! // const compressedOutput = !!process.env.SINGLE_FILE_BUILD const compressedOutput = true @@ -82,27 +57,22 @@ const dataTypeBundling2 = { } } const dataTypeBundling = { - language: process.env.SKIP_MC_DATA_LANGUAGE === 'true' ? { - raw: {} - } : { + language: { ignoreRemoved: true, ignoreChanges: true }, blocks: { arrKey: 'name', - processData(current, prev, _, version) { + processData (current, prev) { for (const block of current) { - const prevBlock = prev?.find(x => x.name === block.name) if (block.transparent) { const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/) || ['melon_block', 'lit_pumpkin', 'lit_redstone_ore', 'lit_furnace'].includes(block.name) + const prevBlock = prev?.find(x => x.name === block.name); if (forceOpaque || (prevBlock && !prevBlock.transparent)) { block.transparent = false } } - if (block.hardness === 0 && prevBlock && prevBlock.hardness > 0) { - block.hardness = prevBlock.hardness - } } } // ignoreRemoved: true, @@ -166,9 +136,7 @@ const dataTypeBundling = { blockLoot: { arrKey: 'block' }, - recipes: process.env.SKIP_MC_DATA_RECIPES === 'true' ? { - raw: {} - } : { + recipes: { raw: true // processData: processRecipes }, @@ -182,7 +150,7 @@ const dataTypeBundling = { // } } -function processRecipes(current, prev, getData, version) { +function processRecipes (current, prev, getData, version) { // can require the same multiple times per different versions if (current._proccessed) return const items = getData('items') @@ -274,39 +242,30 @@ for (const [i, [version, dataSet]] of versionsArr.reverse().entries()) { for (const [dataType, dataPath] of Object.entries(dataSet)) { const config = dataTypeBundling[dataType] if (!config) continue - const ignoreCollisionShapes = dataType === 'blockCollisionShapes' && versionToNumber(version) >= versionToNumber('1.13') - + if (dataType === 'blockCollisionShapes' && versionToNumber(version) >= versionToNumber('1.13')) { + // contents += ` get ${dataType} () { return window.globalGetCollisionShapes?.("${version}") },\n` + continue + } let injectCode = '' - const getRealData = (type) => { + const getData = (type) => { const loc = `minecraft-data/data/${dataSet[type]}/` const dataPathAbsolute = require.resolve(`minecraft-data/${loc}${type}`) // const data = fs.readFileSync(dataPathAbsolute, 'utf8') const dataRaw = require(dataPathAbsolute) return dataRaw } - const dataRaw = getRealData(dataType) + const dataRaw = getData(dataType) let rawData = dataRaw if (config.raw) { rawDataVersions[dataType] ??= {} rawDataVersions[dataType][version] = rawData - if (config.raw === true) { - rawData = dataRaw - } else { - rawData = config.raw - } - - if (ignoreCollisionShapes && dataType === 'blockCollisionShapes') { - rawData = { - blocks: {}, - shapes: {} - } - } + rawData = dataRaw } else { if (!diffSources[dataType]) { diffSources[dataType] = new JsonOptimizer(config.arrKey, config.ignoreChanges, config.ignoreRemoved) } try { - config.processData?.(dataRaw, previousData[dataType], getRealData, version) + config.processData?.(dataRaw, previousData[dataType], getData, version) diffSources[dataType].recordDiff(version, dataRaw) injectCode = `restoreDiff(sources, ${JSON.stringify(dataType)}, ${JSON.stringify(version)})` } catch (err) { @@ -338,16 +297,16 @@ console.log('total size (mb)', totalSize / 1024 / 1024) console.log( 'size per data type (mb, %)', Object.fromEntries(Object.entries(sizePerDataType).map(([dataType, size]) => { - return [dataType, [size / 1024 / 1024, Math.round(size / totalSize * 100)]] + return [dataType, [size / 1024 / 1024, Math.round(size / totalSize * 100)]]; }).sort((a, b) => { //@ts-ignore - return b[1][1] - a[1][1] + return b[1][1] - a[1][1]; })) ) function compressToBase64(input) { - const buffer = gzipSync(input) - return buffer.toString('base64') + const buffer = gzipSync(input); + return buffer.toString('base64'); } const filePath = './generated/minecraft-data-optimized.json' @@ -371,7 +330,6 @@ console.log('size', fs.lstatSync(filePath).size / 1000 / 1000, gzipSizeFromFileS const { defaultVersion } = MCProtocol const data = MinecraftData(defaultVersion) -console.log('defaultVersion', defaultVersion, !!data) const initialMcData = { [defaultVersion]: { version: data.version, diff --git a/scripts/patchAssets.ts b/scripts/patchAssets.ts deleted file mode 100644 index 99994f5f..00000000 --- a/scripts/patchAssets.ts +++ /dev/null @@ -1,137 +0,0 @@ -import blocksAtlas from 'mc-assets/dist/blocksAtlases.json' -import itemsAtlas from 'mc-assets/dist/itemsAtlases.json' -import * as fs from 'fs' -import * as path from 'path' -import sharp from 'sharp' - -interface AtlasFile { - latest: { - suSv: number - tileSize: number - width: number - height: number - textures: { - [key: string]: { - u: number - v: number - su: number - sv: number - tileIndex: number - } - } - } -} - -async function patchTextureAtlas( - atlasType: 'blocks' | 'items', - atlasData: AtlasFile, - customTexturesDir: string, - distDir: string -) { - // Check if custom textures directory exists and has files - if (!fs.existsSync(customTexturesDir) || fs.readdirSync(customTexturesDir).length === 0) { - return - } - - // Find the latest atlas file - const atlasFiles = fs.readdirSync(distDir) - .filter(file => file.startsWith(`${atlasType}AtlasLatest`) && file.endsWith('.png')) - .sort() - - if (atlasFiles.length === 0) { - console.log(`No ${atlasType}AtlasLatest.png found in ${distDir}`) - return - } - - const latestAtlasFile = atlasFiles[atlasFiles.length - 1] - const atlasPath = path.join(distDir, latestAtlasFile) - console.log(`Patching ${atlasPath}`) - - // Get atlas dimensions - const atlasMetadata = await sharp(atlasPath).metadata() - if (!atlasMetadata.width || !atlasMetadata.height) { - throw new Error(`Failed to get atlas dimensions for ${atlasPath}`) - } - - // Process each custom texture - const customTextureFiles = fs.readdirSync(customTexturesDir) - .filter(file => file.endsWith('.png')) - - if (customTextureFiles.length === 0) return - - // Prepare composite operations - const composites: sharp.OverlayOptions[] = [] - - for (const textureFile of customTextureFiles) { - const textureName = path.basename(textureFile, '.png') - - if (atlasData.latest.textures[textureName]) { - const textureData = atlasData.latest.textures[textureName] - const customTexturePath = path.join(customTexturesDir, textureFile) - - try { - // Convert UV coordinates to pixel coordinates - const x = Math.round(textureData.u * atlasMetadata.width) - const y = Math.round(textureData.v * atlasMetadata.height) - const width = Math.round((textureData.su ?? atlasData.latest.suSv) * atlasMetadata.width) - const height = Math.round((textureData.sv ?? atlasData.latest.suSv) * atlasMetadata.height) - - // Resize custom texture to match atlas dimensions and add to composite operations - const resizedTextureBuffer = await sharp(customTexturePath) - .resize(width, height, { - fit: 'fill', - kernel: 'nearest' // Preserve pixel art quality - }) - .png() - .toBuffer() - - composites.push({ - input: resizedTextureBuffer, - left: x, - top: y, - blend: 'over' - }) - - console.log(`Prepared ${textureName} at (${x}, ${y}) with size (${width}, ${height})`) - } catch (error) { - console.error(`Failed to prepare ${textureName}:`, error) - } - } else { - console.warn(`Texture ${textureName} not found in ${atlasType} atlas`) - } - } - - if (composites.length > 0) { - // Apply all patches at once using Sharp's composite - await sharp(atlasPath) - .composite(composites) - .png() - .toFile(atlasPath + '.tmp') - - // Replace original with patched version - fs.renameSync(atlasPath + '.tmp', atlasPath) - console.log(`Saved patched ${atlasType} atlas to ${atlasPath}`) - } -} - -async function main() { - const customBlocksDir = './assets/customTextures/blocks' - const customItemsDir = './assets/customTextures/items' - const distDir = './dist/static/image' - - try { - // Patch blocks atlas - await patchTextureAtlas('blocks', blocksAtlas as unknown as AtlasFile, customBlocksDir, distDir) - - // Patch items atlas - await patchTextureAtlas('items', itemsAtlas as unknown as AtlasFile, customItemsDir, distDir) - - console.log('Texture atlas patching completed!') - } catch (error) { - console.error('Failed to patch texture atlases:', error) - process.exit(1) - } -} - -// Run the script -main() diff --git a/src/appConfig.ts b/src/appConfig.ts index c29d74e8..92fde21a 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -35,7 +35,7 @@ export type AppConfig = { // defaultVersion?: string peerJsServer?: string peerJsServerFallback?: string - promoteServers?: Array<{ ip, description, name?, version?, }> + promoteServers?: Array<{ ip, description, version? }> mapsProvider?: string appParams?: Record // query string params diff --git a/src/appParams.ts b/src/appParams.ts index 4c8ca186..59a24788 100644 --- a/src/appParams.ts +++ b/src/appParams.ts @@ -12,7 +12,6 @@ export type AppQsParams = { username?: string lockConnect?: string autoConnect?: string - alwaysReconnect?: string // googledrive.ts params state?: string // ServersListProvider.tsx params @@ -47,7 +46,6 @@ export type AppQsParams = { connectText?: string freezeSettings?: string testIosCrash?: string - addPing?: string // Replay params replayFilter?: string diff --git a/src/appViewer.ts b/src/appViewer.ts index 628d11b4..05b1cf41 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -8,7 +8,6 @@ import { proxy, subscribe } from 'valtio' import { getDefaultRendererState } from 'renderer/viewer/baseGraphicsBackend' import { getSyncWorld } from 'renderer/playground/shared' import { MaybePromise } from 'contro-max/build/types/store' -import { PANORAMA_VERSION } from 'renderer/viewer/three/panoramaShared' import { playerState } from './mineflayer/playerState' import { createNotificationProgressReporter, ProgressReporter } from './core/progressReporter' import { setLoadingScreenStatus } from './appStatus' @@ -16,9 +15,6 @@ import { activeModalStack, miscUiState } from './globalState' import { options } from './optionsStorage' import { ResourcesManager, ResourcesManagerTransferred } from './resourcesManager' import { watchOptionsAfterWorldViewInit } from './watchOptions' -import { loadMinecraftData } from './connect' -import { reloadChunks } from './utils' -import { displayClientChat } from './botUtils' export interface RendererReactiveState { world: { @@ -116,7 +112,7 @@ export class AppViewer { inWorldRenderingConfig: WorldRendererConfig = proxy(defaultWorldRendererConfig) lastCamUpdate = 0 playerState = playerState - rendererState = getDefaultRendererState().reactive + rendererState = proxy(getDefaultRendererState().reactive) nonReactiveState: NonReactiveState = getDefaultRendererState().nonReactive worldReady: Promise private resolveWorldReady: () => void @@ -166,15 +162,11 @@ export class AppViewer { // Execute queued action if exists if (this.currentState) { - if (this.currentState.method === 'startPanorama') { - this.startPanorama() - } else { - const { method, args } = this.currentState - this.backend[method](...args) - if (method === 'startWorld') { - void this.worldView!.init(bot.entity.position) - // void this.worldView!.init(args[0].playerState.getPosition()) - } + const { method, args } = this.currentState + this.backend[method](...args) + if (method === 'startWorld') { + void this.worldView!.init(bot.entity.position) + // void this.worldView!.init(args[0].playerState.getPosition()) } } @@ -199,13 +191,6 @@ export class AppViewer { this.currentDisplay = 'world' const startPosition = bot.entity?.position ?? new Vec3(0, 64, 0) this.worldView = new WorldDataEmitter(world, renderDistance, startPosition) - this.worldView.panicChunksReload = () => { - if (!options.experimentalClientSelfReload) return - if (process.env.NODE_ENV === 'development') { - displayClientChat(`[client] client panicked due to too long loading time. Soft reloading chunks...`) - } - void reloadChunks() - } window.worldView = this.worldView watchOptionsAfterWorldViewInit(this.worldView) this.appConfigUdpate() @@ -240,16 +225,10 @@ export class AppViewer { startPanorama () { if (this.currentDisplay === 'menu') return + this.currentDisplay = 'menu' if (options.disableAssets) return - if (this.backend && !hasAppStatus()) { - this.currentDisplay = 'menu' - if (process.env.SINGLE_FILE_BUILD_MODE) { - void loadMinecraftData(PANORAMA_VERSION).then(() => { - this.backend?.startPanorama() - }) - } else { - this.backend.startPanorama() - } + if (this.backend) { + this.backend.startPanorama() } this.currentState = { method: 'startPanorama', args: [] } } @@ -337,16 +316,15 @@ const initialMenuStart = async () => { } window.initialMenuStart = initialMenuStart -const hasAppStatus = () => activeModalStack.some(m => m.reactType === 'app-status') - const modalStackUpdateChecks = () => { // maybe start panorama - if (!miscUiState.gameLoaded && !hasAppStatus()) { + if (!miscUiState.gameLoaded) { void initialMenuStart() } if (appViewer.backend) { - appViewer.backend.setRendering(!hasAppStatus()) + const hasAppStatus = activeModalStack.some(m => m.reactType === 'app-status') + appViewer.backend.setRendering(!hasAppStatus) } appViewer.inWorldRenderingConfig.foreground = activeModalStack.length === 0 diff --git a/src/basicSounds.ts b/src/basicSounds.ts index 54af0d35..40428c6b 100644 --- a/src/basicSounds.ts +++ b/src/basicSounds.ts @@ -7,12 +7,7 @@ let audioContext: AudioContext const sounds: Record = {} // Track currently playing sounds and their gain nodes -const activeSounds: Array<{ - source: AudioBufferSourceNode; - gainNode: GainNode; - volumeMultiplier: number; - isMusic: boolean; -}> = [] +const activeSounds: Array<{ source: AudioBufferSourceNode; gainNode: GainNode; volumeMultiplier: number }> = [] window.activeSounds = activeSounds // load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded @@ -48,7 +43,7 @@ export async function loadSound (path: string, contents = path) { } } -export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false, isMusic = false) => { +export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = 500) => { const soundBuffer = sounds[url] if (!soundBuffer) { const start = Date.now() @@ -56,11 +51,11 @@ export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = option if (cancelled || Date.now() - start > loadTimeout) return } - return playSound(url, soundVolume, loop, isMusic) + return playSound(url, soundVolume) } -export async function playSound (url, soundVolume = 1, loop = false, isMusic = false) { - const volume = soundVolume * (options.volume / 100) * (isMusic ? options.musicVolume / 100 : 1) +export async function playSound (url, soundVolume = 1) { + const volume = soundVolume * (options.volume / 100) if (!volume) return @@ -80,14 +75,13 @@ export async function playSound (url, soundVolume = 1, loop = false, isMusic = f const gainNode = audioContext.createGain() const source = audioContext.createBufferSource() source.buffer = soundBuffer - source.loop = loop source.connect(gainNode) gainNode.connect(audioContext.destination) gainNode.gain.value = volume source.start(0) // Add to active sounds - activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume, isMusic }) + activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume }) const callbacks = [] as Array<() => void> source.onended = () => { @@ -105,17 +99,6 @@ export async function playSound (url, soundVolume = 1, loop = false, isMusic = f onEnded (callback: () => void) { callbacks.push(callback) }, - stop () { - try { - source.stop() - // Remove from active sounds - const index = activeSounds.findIndex(s => s.source === source) - if (index !== -1) activeSounds.splice(index, 1) - } catch (err) { - console.warn('Failed to stop sound:', err) - } - }, - gainNode, } } @@ -130,24 +113,11 @@ export function stopAllSounds () { activeSounds.length = 0 } -export function stopSound (url: string) { - const soundIndex = activeSounds.findIndex(s => s.source.buffer === sounds[url]) - if (soundIndex !== -1) { - const { source } = activeSounds[soundIndex] - try { - source.stop() - } catch (err) { - console.warn('Failed to stop sound:', err) - } - activeSounds.splice(soundIndex, 1) - } -} - -export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusicVolume: number) { +export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { const normalizedVolume = newVolume / 100 - for (const { gainNode, volumeMultiplier, isMusic } of activeSounds) { + for (const { gainNode, volumeMultiplier } of activeSounds) { try { - gainNode.gain.value = normalizedVolume * volumeMultiplier * (isMusic ? newMusicVolume / 100 : 1) + gainNode.gain.value = normalizedVolume * volumeMultiplier } catch (err) { console.warn('Failed to change sound volume:', err) } @@ -155,9 +125,5 @@ export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusi } subscribeKey(options, 'volume', () => { - changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) -}) - -subscribeKey(options, 'musicVolume', () => { - changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) + changeVolumeOfCurrentlyPlayingSounds(options.volume) }) diff --git a/src/chatUtils.ts b/src/chatUtils.ts index 849d5847..88437bc3 100644 --- a/src/chatUtils.ts +++ b/src/chatUtils.ts @@ -118,14 +118,6 @@ export const formatMessage = (message: MessageInput, mcData: IndexedData = globa return msglist } -export const messageToString = (message: MessageInput | string) => { - if (typeof message === 'string') { - return message - } - const msglist = formatMessage(message) - return msglist.map(msg => msg.text).join('') -} - const blockToItemRemaps = { water: 'water_bucket', lava: 'lava_bucket', diff --git a/src/connect.ts b/src/connect.ts index cb6b8f65..914303c6 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -3,6 +3,7 @@ import MinecraftData from 'minecraft-data' import PrismarineBlock from 'prismarine-block' import PrismarineItem from 'prismarine-item' +import pathfinder from 'mineflayer-pathfinder' import { miscUiState } from './globalState' import supportedVersions from './supportedVersions.mjs' import { options } from './optionsStorage' @@ -64,6 +65,7 @@ export const loadMinecraftData = async (version: string) => { window.PrismarineItem = PrismarineItem(mcData.version.minecraftVersion!) window.loadedData = mcData window.mcData = mcData + window.pathfinder = pathfinder miscUiState.loadedDataVersion = version } diff --git a/src/controls.ts b/src/controls.ts index db6a6fc6..9430f9c1 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -818,11 +818,6 @@ export const f3Keybinds: Array<{ } ] -export const reloadChunksAction = () => { - const action = f3Keybinds.find(f3Keybind => f3Keybind.key === 'KeyA') - void action!.action() -} - document.addEventListener('keydown', (e) => { if (!isGameActive(false)) return if (contro.pressedKeys.has('F3')) { @@ -992,17 +987,14 @@ export function updateBinds (commands: any) { } export const onF3LongPress = async () => { - const actions = f3Keybinds.filter(f3Keybind => { + const select = await showOptionsModal('', f3Keybinds.filter(f3Keybind => { return f3Keybind.mobileTitle && (f3Keybind.enabled?.() ?? true) - }) - const actionNames = actions.map(f3Keybind => { + }).map(f3Keybind => { return `${f3Keybind.mobileTitle}${f3Keybind.key ? ` (F3+${f3Keybind.key})` : ''}` - }) - const select = await showOptionsModal('', actionNames) + })) if (!select) return - const actionIndex = actionNames.indexOf(select) - const f3Keybind = actions[actionIndex]! - void f3Keybind.action() + const f3Keybind = f3Keybinds.find(f3Keybind => f3Keybind.mobileTitle === select) + if (f3Keybind) void f3Keybind.action() } export const handleMobileButtonCustomAction = (action: CustomAction) => { diff --git a/src/core/ideChannels.ts b/src/core/ideChannels.ts deleted file mode 100644 index a9c517f7..00000000 --- a/src/core/ideChannels.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { proxy } from 'valtio' - -export const ideState = proxy({ - id: '', - contents: '', - line: 0, - column: 0, - language: 'typescript', - title: '', -}) -globalThis.ideState = ideState - -export const registerIdeChannels = () => { - registerIdeOpenChannel() - registerIdeSaveChannel() -} - -const registerIdeOpenChannel = () => { - const CHANNEL_NAME = 'minecraft-web-client:ide-open' - - const packetStructure = [ - 'container', - [ - { - name: 'id', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'language', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'contents', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'line', - type: 'i32' - }, - { - name: 'column', - type: 'i32' - }, - { - name: 'title', - type: ['pstring', { countType: 'i16' }] - } - ] - ] - - bot._client.registerChannel(CHANNEL_NAME, packetStructure, true) - - bot._client.on(CHANNEL_NAME as any, (data) => { - const { id, language, contents, line, column, title } = data - - ideState.contents = contents - ideState.line = line - ideState.column = column - ideState.id = id - ideState.language = language || 'typescript' - ideState.title = title - }) - - console.debug(`registered custom channel ${CHANNEL_NAME} channel`) -} -const IDE_SAVE_CHANNEL_NAME = 'minecraft-web-client:ide-save' -const registerIdeSaveChannel = () => { - - const packetStructure = [ - 'container', - [ - { - name: 'id', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'contents', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'language', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'line', - type: 'i32' - }, - { - name: 'column', - type: 'i32' - }, - ] - ] - bot._client.registerChannel(IDE_SAVE_CHANNEL_NAME, packetStructure, true) -} - -export const saveIde = () => { - bot._client.writeChannel(IDE_SAVE_CHANNEL_NAME, { - id: ideState.id, - contents: ideState.contents, - language: ideState.language, - // todo: reflect updated - line: ideState.line, - column: ideState.column, - }) -} diff --git a/src/customChannels.ts b/src/customChannels.ts index 506ea776..57c057d5 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -2,20 +2,19 @@ import PItem from 'prismarine-item' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { options } from './optionsStorage' import { jeiCustomCategories } from './inventoryWindows' -import { registerIdeChannels } from './core/ideChannels' export default () => { customEvents.on('mineflayerBotCreated', async () => { if (!options.customChannels) return - bot.once('login', () => { - registerBlockModelsChannel() - registerMediaChannels() - registerSectionAnimationChannels() - registeredJeiChannel() - registerBlockInteractionsCustomizationChannel() - registerWaypointChannels() - registerIdeChannels() + await new Promise(resolve => { + bot.once('login', () => { + resolve(true) + }) }) + registerBlockModelsChannel() + registerMediaChannels() + registerSectionAnimationChannels() + registeredJeiChannel() }) } @@ -33,95 +32,6 @@ const registerChannel = (channelName: string, packetStructure: any[], handler: ( console.debug(`registered custom channel ${channelName} channel`) } -const registerBlockInteractionsCustomizationChannel = () => { - const CHANNEL_NAME = 'minecraft-web-client:block-interactions-customization' - const packetStructure = [ - 'container', - [ - { - name: 'newConfiguration', - type: ['pstring', { countType: 'i16' }] - }, - ] - ] - - registerChannel(CHANNEL_NAME, packetStructure, (data) => { - const config = JSON.parse(data.newConfiguration) - bot.mouse.setConfigFromPacket(config) - }, true) -} - -const registerWaypointChannels = () => { - const packetStructure = [ - 'container', - [ - { - name: 'id', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'x', - type: 'f32' - }, - { - name: 'y', - type: 'f32' - }, - { - name: 'z', - type: 'f32' - }, - { - name: 'minDistance', - type: 'i32' - }, - { - name: 'label', - type: ['pstring', { countType: 'i16' }] - }, - { - name: 'color', - type: 'i32' - }, - { - name: 'metadataJson', - type: ['pstring', { countType: 'i16' }] - } - ] - ] - - registerChannel('minecraft-web-client:waypoint-add', packetStructure, (data) => { - // Parse metadata if provided - let metadata: any = {} - if (data.metadataJson && data.metadataJson.trim() !== '') { - try { - metadata = JSON.parse(data.metadataJson) - } catch (error) { - console.warn('Failed to parse waypoint metadataJson:', error) - } - } - - getThreeJsRendererMethods()?.addWaypoint(data.id, data.x, data.y, data.z, { - minDistance: data.minDistance, - label: data.label || undefined, - color: data.color || undefined, - metadata - }) - }) - - registerChannel('minecraft-web-client:waypoint-delete', [ - 'container', - [ - { - name: 'id', - type: ['pstring', { countType: 'i16' }] - } - ] - ], (data) => { - getThreeJsRendererMethods()?.removeWaypoint(data.id) - }) -} - const registerBlockModelsChannel = () => { const CHANNEL_NAME = 'minecraft-web-client:blockmodels' diff --git a/src/customClient.js b/src/customClient.js index b1a99904..b6c85fcc 100644 --- a/src/customClient.js +++ b/src/customClient.js @@ -1,7 +1,6 @@ -//@ts-check -import * as nbt from 'prismarine-nbt' import { options } from './optionsStorage' +//@ts-check const { EventEmitter } = require('events') const debug = require('debug')('minecraft-protocol') const states = require('minecraft-protocol/src/states') @@ -52,20 +51,8 @@ class CustomChannelClient extends EventEmitter { this.emit('state', newProperty, oldProperty) } - end(endReason, fullReason) { - // eslint-disable-next-line unicorn/no-this-assignment - const client = this - if (client.state === states.PLAY) { - fullReason ||= loadedData.supportFeature('chatPacketsUseNbtComponents') - ? nbt.comp({ text: nbt.string(endReason) }) - : JSON.stringify({ text: endReason }) - client.write('kick_disconnect', { reason: fullReason }) - } else if (client.state === states.LOGIN) { - fullReason ||= JSON.stringify({ text: endReason }) - client.write('disconnect', { reason: fullReason }) - } - - this._endReason = endReason + end(reason) { + this._endReason = reason this.emit('end', this._endReason) // still emits on server side only, doesn't send anything to our client } diff --git a/src/dayCycle.ts b/src/dayCycle.ts new file mode 100644 index 00000000..50e63a21 --- /dev/null +++ b/src/dayCycle.ts @@ -0,0 +1,46 @@ +import { options } from './optionsStorage' +import { assertDefined } from './utils' +import { updateBackground } from './water' + +export default () => { + const timeUpdated = () => { + // 0 morning + const dayTotal = 24_000 + const evening = 11_500 + const night = 13_500 + const morningStart = 23_000 + const morningEnd = 23_961 + const timeProgress = options.dayCycleAndLighting ? bot.time.timeOfDay : 0 + + // todo check actual colors + const dayColorRainy = { r: 111 / 255, g: 156 / 255, b: 236 / 255 } + // todo yes, we should make animations (and rain) + // eslint-disable-next-line unicorn/numeric-separators-style + const dayColor = bot.isRaining ? dayColorRainy : { r: 0.6784313725490196, g: 0.8470588235294118, b: 0.9019607843137255 } // lightblue + // let newColor = dayColor + let int = 1 + if (timeProgress < evening) { + // stay dayily + } else if (timeProgress < night) { + const progressNorm = timeProgress - evening + const progressMax = night - evening + int = 1 - progressNorm / progressMax + } else if (timeProgress < morningStart) { + int = 0 + } else if (timeProgress < morningEnd) { + const progressNorm = timeProgress - morningStart + const progressMax = night - morningEnd + int = progressNorm / progressMax + } + // todo need to think wisely how to set these values & also move directional light around! + const colorInt = Math.max(int, 0.1) + updateBackground({ r: dayColor.r * colorInt, g: dayColor.g * colorInt, b: dayColor.b * colorInt }) + if (!options.newVersionsLighting && bot.supportFeature('blockStateId')) { + appViewer.playerState.reactive.ambientLight = Math.max(int, 0.25) + appViewer.playerState.reactive.directionalLight = Math.min(int, 0.5) + } + } + + bot.on('time', timeUpdated) + timeUpdated() +} diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 48c1cfad..d2d510ec 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -16,11 +16,9 @@ export const defaultOptions = { chatOpacityOpened: 100, messagesLimit: 200, volume: 50, - enableMusic: true, - musicVolume: 50, + enableMusic: false, // fov: 70, fov: 75, - defaultPerspective: 'first_person' as 'first_person' | 'third_person_back' | 'third_person_front', guiScale: 3, autoRequestCompletions: true, touchButtonsSize: 40, @@ -42,7 +40,6 @@ export const defaultOptions = { renderEars: true, lowMemoryMode: false, starfieldRendering: true, - defaultSkybox: true, enabledResourcepack: null as string | null, useVersionsTextures: 'latest', serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', @@ -79,17 +76,13 @@ export const defaultOptions = { frameLimit: false as number | false, alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null, alwaysShowMobileControls: false, - excludeCommunicationDebugEvents: [] as string[], + excludeCommunicationDebugEvents: [], preventDevReloadWhilePlaying: false, numWorkers: 4, localServerOptions: { gameMode: 1 } as any, - saveLoginPassword: 'prompt' as 'prompt' | 'never' | 'always', preferLoadReadonly: false, - experimentalClientSelfReload: false, - remoteSoundsSupport: false, - remoteSoundsLoadTimeout: 500, disableLoadPrompts: false, guestUsername: 'guest', askGuestName: true, diff --git a/src/devtools.ts b/src/devtools.ts index 1f8ef8e8..8890fdea 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -5,17 +5,6 @@ import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree' import { enable, disable, enabled } from 'debug' import { Vec3 } from 'vec3' -customEvents.on('mineflayerBotCreated', () => { - window.debugServerPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toClient.types).map(name => { - name = name.replace('packet_', '') - return [name, name] - })) - window.debugClientPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toServer.types).map(name => { - name = name.replace('packet_', '') - return [name, name] - })) -}) - window.Vec3 = Vec3 window.cursorBlockRel = (x = 0, y = 0, z = 0) => { const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z) @@ -266,6 +255,7 @@ function connectWebSocket () { const wsUrl = getWebSocketUrl() if (!wsUrl) { + console.log('WebSocket server not configured') return } diff --git a/src/downloadAndOpenFile.ts b/src/downloadAndOpenFile.ts index 1ff318ff..1e703369 100644 --- a/src/downloadAndOpenFile.ts +++ b/src/downloadAndOpenFile.ts @@ -11,12 +11,6 @@ export const getFixedFilesize = (bytes: number) => { return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } -export const isInterestedInDownload = () => { - const { map, texturepack, replayFileUrl } = appQueryParams - const { mapDir } = appQueryParamsArray - return !!map || !!texturepack || !!replayFileUrl || !!mapDir -} - const inner = async () => { const { map, texturepack, replayFileUrl } = appQueryParams const { mapDir } = appQueryParamsArray diff --git a/src/dragndrop.ts b/src/dragndrop.ts index 5a16bc05..6be90551 100644 --- a/src/dragndrop.ts +++ b/src/dragndrop.ts @@ -3,7 +3,6 @@ import fs from 'fs' import * as nbt from 'prismarine-nbt' import RegionFile from 'prismarine-provider-anvil/src/region' import { versions } from 'minecraft-data' -import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { openWorldDirectory, openWorldZip } from './browserfs' import { isGameActive } from './globalState' import { showNotification } from './react/NotificationProvider' @@ -13,9 +12,6 @@ const parseNbt = promisify(nbt.parse) const simplifyNbt = nbt.simplify window.nbt = nbt -// Supported image types for skybox -const VALID_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp'] - // todo display drop zone for (const event of ['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop']) { window.addEventListener(event, (e: any) => { @@ -49,34 +45,6 @@ window.addEventListener('drop', async e => { }) async function handleDroppedFile (file: File) { - // Check for image files first when game is active - if (isGameActive(false) && VALID_IMAGE_EXTENSIONS.some(ext => file.name.toLowerCase().endsWith(ext))) { - try { - // Convert image to base64 - const reader = new FileReader() - const base64Promise = new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result as string) - reader.onerror = reject - }) - reader.readAsDataURL(file) - const base64Image = await base64Promise - - // Get ThreeJS backend methods and update skybox - const setSkyboxImage = getThreeJsRendererMethods()?.setSkyboxImage - if (setSkyboxImage) { - await setSkyboxImage(base64Image) - showNotification('Skybox updated successfully') - } else { - showNotification('Cannot update skybox - renderer does not support it') - } - return - } catch (err) { - console.error('Failed to update skybox:', err) - showNotification('Failed to update skybox', 'error') - return - } - } - if (file.name.endsWith('.zip')) { void openWorldZip(file) return diff --git a/src/entities.ts b/src/entities.ts index 674f91ef..79602aa5 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -126,28 +126,6 @@ customEvents.on('gameLoaded', () => { if (entityStatus === EntityStatus.HURT) { getThreeJsRendererMethods()?.damageEntity(entityId, entityStatus) } - - if (entityStatus === EntityStatus.BURNED) { - updateEntityStates(entityId, true, true) - } - }) - - // on fire events - bot._client.on('entity_metadata', (data) => { - if (data.entityId !== bot.entity.id) return - handleEntityMetadata(data) - }) - - bot.on('end', () => { - if (onFireTimeout) { - clearTimeout(onFireTimeout) - } - }) - - bot.on('respawn', () => { - if (onFireTimeout) { - clearTimeout(onFireTimeout) - } }) const updateCamera = (entity: Entity) => { @@ -246,29 +224,22 @@ customEvents.on('gameLoaded', () => { } } // even if not found, still record to cache - void getThreeJsRendererMethods()!.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) + void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) } catch (err) { - reportError(new Error('Error applying skin texture:', { cause: err })) + console.error('Error decoding player texture:', err) } } bot.on('playerJoined', updateSkin) bot.on('playerUpdated', updateSkin) - for (const entity of Object.values(bot.players)) { - updateSkin(entity) - } - const teamUpdated = (team: Team) => { + bot.on('teamUpdated', (team: Team) => { for (const entity of Object.values(bot.entities)) { if (entity.type === 'player' && entity.username && team.members.includes(entity.username) || entity.uuid && team.members.includes(entity.uuid)) { bot.emit('entityUpdate', entity) } } - } - bot.on('teamUpdated', teamUpdated) - for (const team of Object.values(bot.teams)) { - teamUpdated(team) - } + }) const updateEntityNameTags = (team: Team) => { for (const entity of Object.values(bot.entities)) { @@ -317,7 +288,7 @@ customEvents.on('gameLoaded', () => { }) bot.on('teamRemoved', (team: Team) => { - if (appViewer.playerState.reactive.team?.team === team?.team) { + if (appViewer.playerState.reactive.team?.team === team.team) { appViewer.playerState.reactive.team = undefined // Player's team was removed, need to update all entities that are in a team updateEntityNameTags(team) @@ -325,44 +296,3 @@ customEvents.on('gameLoaded', () => { }) }) - -// Constants -const SHARED_FLAGS_KEY = 0 -const ENTITY_FLAGS = { - ON_FIRE: 0x01, // Bit 0 - SNEAKING: 0x02, // Bit 1 - SPRINTING: 0x08, // Bit 3 - SWIMMING: 0x10, // Bit 4 - INVISIBLE: 0x20, // Bit 5 - GLOWING: 0x40, // Bit 6 - FALL_FLYING: 0x80 // Bit 7 (elytra flying) -} - -let onFireTimeout: NodeJS.Timeout | undefined -const updateEntityStates = (entityId: number, onFire: boolean, timeout?: boolean) => { - if (entityId !== bot.entity.id) return - appViewer.playerState.reactive.onFire = onFire - if (onFireTimeout) { - clearTimeout(onFireTimeout) - } - if (timeout) { - onFireTimeout = setTimeout(() => { - updateEntityStates(entityId, false, false) - }, 5000) - } -} - -// Process entity metadata packet -function handleEntityMetadata (packet: { entityId: number, metadata: Array<{ key: number, type: string, value: number }> }) { - const { entityId, metadata } = packet - - // Find shared flags in metadata - const flagsData = metadata.find(meta => meta.key === SHARED_FLAGS_KEY && - meta.type === 'byte') - - // Update fire state if flags were found - if (flagsData) { - const wasOnFire = appViewer.playerState.reactive.onFire - appViewer.playerState.reactive.onFire = (flagsData.value & ENTITY_FLAGS.ON_FIRE) !== 0 - } -} diff --git a/src/env.d.ts b/src/env.d.ts index e565fcec..9b3e9774 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -2,36 +2,30 @@ declare namespace NodeJS { interface ProcessEnv { // Build configuration NODE_ENV: 'development' | 'production' - MIN_MC_VERSION?: string - MAX_MC_VERSION?: string - ALWAYS_COMPRESS_LARGE_DATA?: 'true' | 'false' - SINGLE_FILE_BUILD?: 'true' | 'false' + SINGLE_FILE_BUILD?: string WS_PORT?: string - DISABLE_SERVICE_WORKER?: 'true' | 'false' + DISABLE_SERVICE_WORKER?: string CONFIG_JSON_SOURCE?: 'BUNDLED' | 'REMOTE' LOCAL_CONFIG_FILE?: string BUILD_VERSION?: string - // Build internals + // GitHub and Vercel related GITHUB_REPOSITORY?: string VERCEL_GIT_REPO_OWNER?: string VERCEL_GIT_REPO_SLUG?: string - // UI + // UI and Features MAIN_MENU_LINKS?: string - ALWAYS_MINIMAL_SERVER_UI?: 'true' | 'false' - - // App features ENABLE_COOKIE_STORAGE?: string COOKIE_STORAGE_PREFIX?: string - // Build info. Release information + // Release information RELEASE_TAG?: string RELEASE_LINK?: string RELEASE_CHANGELOG?: string - // Build info + // Other configurations + DEPS_VERSIONS?: string INLINED_APP_CONFIG?: string - GITHUB_URL?: string } } diff --git a/src/index.ts b/src/index.ts index 7764188f..185caab6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,7 +29,7 @@ import './reactUi' import { lockUrl, onBotCreate } from './controls' import './dragndrop' import { possiblyCleanHandle } from './browserfs' -import downloadAndOpenFile, { isInterestedInDownload } from './downloadAndOpenFile' +import downloadAndOpenFile from './downloadAndOpenFile' import fs from 'fs' import net, { Socket } from 'net' @@ -56,12 +56,13 @@ import { isCypress } from './standaloneUtils' import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer' import defaultServerOptions from './defaultLocalServerOptions' +import dayCycle from './dayCycle' import { onAppLoad, resourcepackReload, resourcePackState } from './resourcePack' import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer' import CustomChannelClient from './customClient' import { registerServiceWorker } from './serviceWorker' -import { appStatusState, lastConnectOptions, quickDevReconnect } from './react/AppStatusProvider' +import { appStatusState, lastConnectOptions } from './react/AppStatusProvider' import { fsState } from './loadSave' import { watchFov } from './rendererUtils' @@ -96,7 +97,6 @@ import { registerOpenBenchmarkListener } from './benchmark' import { tryHandleBuiltinCommand } from './builtinCommands' import { loadingTimerState } from './react/LoadingTimer' import { loadPluginsIntoWorld } from './react/CreateWorldProvider' -import { getCurrentProxy, getCurrentUsername } from './react/ServersList' window.debug = debug window.beforeRenderFrame = [] @@ -166,7 +166,6 @@ export async function connect (connectOptions: ConnectOptions) { }) } - appStatusState.showReconnect = false loadingTimerState.loading = true loadingTimerState.start = Date.now() miscUiState.hasErrors = false @@ -214,13 +213,8 @@ export async function connect (connectOptions: ConnectOptions) { const destroyAll = (wasKicked = false) => { if (ended) return loadingTimerState.loading = false - const { alwaysReconnect } = appQueryParams - if ((!wasKicked && miscUiState.appConfig?.allowAutoConnect && appQueryParams.autoConnect && hadConnected) || (alwaysReconnect)) { - if (alwaysReconnect === 'quick' || alwaysReconnect === 'fast') { - quickDevReconnect() - } else { - location.reload() - } + if (!wasKicked && miscUiState.appConfig?.allowAutoConnect && appQueryParams.autoConnect && hadConnected) { + location.reload() } errorAbortController.abort() ended = true @@ -235,12 +229,8 @@ export async function connect (connectOptions: ConnectOptions) { bot.emit('end', '') bot.removeAllListeners() bot._client.removeAllListeners() - bot._client = { - //@ts-expect-error - write (packetName) { - console.warn('Tried to write packet', packetName, 'after bot was destroyed') - } - } + //@ts-expect-error TODO? + bot._client = undefined //@ts-expect-error window.bot = bot = undefined } @@ -286,10 +276,6 @@ export async function connect (connectOptions: ConnectOptions) { return } } - if (e.reason?.stack?.includes('chrome-extension://')) { - // ignore issues caused by chrome extension - return - } handleError(e.reason) }, { signal: errorAbortController.signal @@ -304,7 +290,7 @@ export async function connect (connectOptions: ConnectOptions) { if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) { console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`) - net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` }, artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined }) + net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` } }) } const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance @@ -793,6 +779,7 @@ export async function connect (connectOptions: ConnectOptions) { } initMotionTracking() + dayCycle() // Bot position callback const botPosition = () => { @@ -893,7 +880,37 @@ export async function connect (connectOptions: ConnectOptions) { } } +const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined + listenGlobalEvents() +const unsubscribe = subscribe(miscUiState, async () => { + if (miscUiState.fsReady && miscUiState.appConfig) { + unsubscribe() + if (reconnectOptions) { + sessionStorage.removeItem('reconnectOptions') + if (Date.now() - reconnectOptions.timestamp < 1000 * 60 * 2) { + void connect(reconnectOptions.value) + } + } else { + if (appQueryParams.singleplayer === '1' || appQueryParams.sp === '1') { + loadSingleplayer({}, { + worldFolder: undefined, + ...appQueryParams.version ? { version: appQueryParams.version } : {} + }) + } + if (appQueryParams.loadSave) { + const savePath = `/data/worlds/${appQueryParams.loadSave}` + try { + await fs.promises.stat(savePath) + } catch (err) { + alert(`Save ${savePath} not found`) + return + } + await loadInMemorySave(savePath) + } + } + } +}) // #region fire click event on touch as we disable default behaviors let activeTouch: { touch: Touch, elem: HTMLElement, start: number } | undefined @@ -929,148 +946,90 @@ document.body.addEventListener('touchstart', (e) => { }, { passive: false }) // #endregion -// immediate game enter actions: reconnect or URL QS -const maybeEnterGame = () => { - const waitForConfigFsLoad = (fn: () => void) => { - let unsubscribe: () => void | undefined - const checkDone = () => { - if (miscUiState.fsReady && miscUiState.appConfig) { - fn() - unsubscribe?.() - return true - } - return false - } - - if (!checkDone()) { - const text = miscUiState.appConfig ? 'Loading' : 'Loading config' - setLoadingScreenStatus(text) - unsubscribe = subscribe(miscUiState, checkDone) - } - } - - const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined - - if (reconnectOptions) { - sessionStorage.removeItem('reconnectOptions') - if (Date.now() - reconnectOptions.timestamp < 1000 * 60 * 2) { - return waitForConfigFsLoad(async () => { - void connect(reconnectOptions.value) - }) - } - } - - if (appQueryParams.reconnect && localStorage.lastConnectOptions && process.env.NODE_ENV === 'development') { - const lastConnect = JSON.parse(localStorage.lastConnectOptions ?? {}) - return waitForConfigFsLoad(async () => { +// qs open actions +if (!reconnectOptions) { + downloadAndOpenFile().then((downloadAction) => { + if (downloadAction) return + if (appQueryParams.reconnect && process.env.NODE_ENV === 'development') { + const lastConnect = JSON.parse(localStorage.lastConnectOptions ?? {}) void connect({ botVersion: appQueryParams.version ?? undefined, ...lastConnect, ip: appQueryParams.ip || undefined }) - }) - } - - if (appQueryParams.singleplayer === '1' || appQueryParams.sp === '1') { - return waitForConfigFsLoad(async () => { - loadSingleplayer({}, { - worldFolder: undefined, - ...appQueryParams.version ? { version: appQueryParams.version } : {} - }) - }) - } - if (appQueryParams.loadSave) { - const enterSave = async () => { - const savePath = `/data/worlds/${appQueryParams.loadSave}` - try { - await fs.promises.stat(savePath) - await loadInMemorySave(savePath) - } catch (err) { - alert(`Save ${savePath} not found`) - } + return } - return waitForConfigFsLoad(enterSave) - } - - if (appQueryParams.ip || appQueryParams.proxy) { - const openServerAction = () => { - if (appQueryParams.autoConnect && miscUiState.appConfig?.allowAutoConnect) { - void connect({ - server: appQueryParams.ip, - proxy: getCurrentProxy(), - botVersion: appQueryParams.version ?? undefined, - username: getCurrentUsername()!, - }) - return + if (appQueryParams.ip || appQueryParams.proxy) { + const waitAppConfigLoad = !appQueryParams.proxy + const openServerEditor = () => { + hideModal() + if (appQueryParams.onlyConnect) { + showModal({ reactType: 'only-connect-server' }) + } else { + showModal({ reactType: 'editServer' }) + } } - - setLoadingScreenStatus(undefined) - if (appQueryParams.onlyConnect || process.env.ALWAYS_MINIMAL_SERVER_UI === 'true') { - showModal({ reactType: 'only-connect-server' }) + showModal({ reactType: 'empty' }) + if (waitAppConfigLoad) { + const unsubscribe = subscribe(miscUiState, checkCanDisplay) + checkCanDisplay() + // eslint-disable-next-line no-inner-declarations + function checkCanDisplay () { + if (miscUiState.appConfig) { + unsubscribe() + openServerEditor() + return true + } + } } else { - showModal({ reactType: 'editServer' }) + openServerEditor() } } - // showModal({ reactType: 'empty' }) - return waitForConfigFsLoad(openServerAction) - } - - if (appQueryParams.connectPeer) { - // try to connect to peer - const peerId = appQueryParams.connectPeer - const peerOptions = {} as ConnectPeerOptions - if (appQueryParams.server) { - peerOptions.server = appQueryParams.server - } - const version = appQueryParams.peerVersion - let username: string | null = options.guestUsername - if (options.askGuestName) username = prompt('Enter your username to connect to peer', username) - if (!username) return - options.guestUsername = username - void connect({ - username, - botVersion: version || undefined, - peerId, - peerOptions + void Promise.resolve().then(() => { + // try to connect to peer + const peerId = appQueryParams.connectPeer + const peerOptions = {} as ConnectPeerOptions + if (appQueryParams.server) { + peerOptions.server = appQueryParams.server + } + const version = appQueryParams.peerVersion + if (peerId) { + let username: string | null = options.guestUsername + if (options.askGuestName) username = prompt('Enter your username', username) + if (!username) return + options.guestUsername = username + void connect({ + username, + botVersion: version || undefined, + peerId, + peerOptions + }) + } }) - return - } - - if (appQueryParams.viewerConnect) { - void connect({ - username: `viewer-${Math.random().toString(36).slice(2, 10)}`, - viewerWsConnect: appQueryParams.viewerConnect, - }) - return - } - - if (appQueryParams.modal) { - const modals = appQueryParams.modal.split(',') - for (const modal of modals) { - showModal({ reactType: modal }) + if (appQueryParams.serversList && !appQueryParams.ip) { + showModal({ reactType: 'serversList' }) } - return - } - if (appQueryParams.serversList && !miscUiState.appConfig?.appParams?.serversList) { - // open UI only if it's in URL - showModal({ reactType: 'serversList' }) - } + const viewerWsConnect = appQueryParams.viewerConnect + if (viewerWsConnect) { + void connect({ + username: `viewer-${Math.random().toString(36).slice(2, 10)}`, + viewerWsConnect, + }) + } - if (isInterestedInDownload()) { - void downloadAndOpenFile() - } - - void possiblyHandleStateVariable() -} - -try { - maybeEnterGame() -} catch (err) { - console.error(err) - alert(`Something went wrong: ${err}`) + if (appQueryParams.modal) { + const modals = appQueryParams.modal.split(',') + for (const modal of modals) { + showModal({ reactType: modal }) + } + } + }, (err) => { + console.error(err) + alert(`Something went wrong: ${err}`) + }) } // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion @@ -1081,5 +1040,6 @@ if (initialLoader) { } window.pageLoaded = true +void possiblyHandleStateVariable() appViewer.waitBackendLoadPromises.push(appStartup()) registerOpenBenchmarkListener() diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index d40260df..c9f60d59 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -9,10 +9,8 @@ import PItem, { Item } from 'prismarine-item' import { versionToNumber } from 'renderer/viewer/common/utils' import { getRenamedData } from 'flying-squid/dist/blockRenames' import PrismarineChatLoader from 'prismarine-chat' -import * as nbt from 'prismarine-nbt' import { BlockModel } from 'mc-assets' import { renderSlot } from 'renderer/viewer/three/renderSlot' -import { loadSkinFromUsername } from 'renderer/viewer/lib/utils/skins' import Generic95 from '../assets/generic_95.png' import { appReplacableResources } from './generated/resources' import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' @@ -24,7 +22,6 @@ import { getItemDescription } from './itemsDescriptions' import { MessageFormatPart } from './chatUtils' import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' import { playerState } from './mineflayer/playerState' -import { modelViewerState } from './react/OverlayModelViewer' const loadedImagesCache = new Map() const cleanLoadedImagesCache = () => { @@ -42,34 +39,6 @@ export const jeiCustomCategories = proxy({ value: [] as Array<{ id: string, categoryTitle: string, items: any[] }> }) -let remotePlayerSkin: string | undefined | Promise - -export const showInventoryPlayer = () => { - modelViewerState.model = { - positioning: { - windowWidth: 176, - windowHeight: 166, - x: 25, - y: 8, - width: 50, - height: 70, - scaled: true, - onlyInitialScale: true, - followCursor: true, - }, - // models: ['https://bucket.mcraft.fun/sitarbuckss.glb'], - // debug: true, - steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''), - } - if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) { - remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => { - setTimeout(() => { showInventoryPlayer() }, 0) // todo patch instead and make reactive - remotePlayerSkin = a ?? '' - return remotePlayerSkin - }) - } -} - export const onGameLoad = () => { version = bot.version @@ -87,23 +56,12 @@ export const onGameLoad = () => { return type } - const maybeParseNbtJson = (data: any) => { - if (typeof data === 'string') { - try { - data = JSON.parse(data) - } catch (err) { - // ignore - } - } - return nbt.simplify(data) ?? data - } - bot.on('windowOpen', (win) => { const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)] if (implementedWindow) { - openWindow(implementedWindow, maybeParseNbtJson(win.title)) + openWindow(implementedWindow) } else if (options.unimplementedContainers) { - openWindow('ChestWin', maybeParseNbtJson(win.title)) + openWindow('ChestWin') } else { // todo format displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`) @@ -300,7 +258,6 @@ export const upInventoryItems = (isInventory: boolean, invWindow = lastWindow) = // inv.pwindow.inv.slots[2].blockData = getBlockData('dirt') const customSlots = mapSlots((isInventory ? bot.inventory : bot.currentWindow)!.slots) invWindow.pwindow.setSlots(customSlots) - return customSlots } export const onModalClose = (callback: () => any) => { @@ -397,7 +354,7 @@ const upWindowItemsLocal = () => { } let skipClosePacketSending = false -const openWindow = (type: string | undefined, title: string | any = undefined) => { +const openWindow = (type: string | undefined) => { // if (activeModalStack.some(x => x.reactType?.includes?.('player_win:'))) { if (activeModalStack.length) { // game is not in foreground, don't close current modal if (type) { @@ -422,16 +379,12 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = miscUiState.displaySearchInput = false destroyFn() skipClosePacketSending = false - - modelViewerState.model = undefined }) - if (type === undefined) { - showInventoryPlayer() - } cleanLoadedImagesCache() const inv = openItemsCanvas(type) inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch window.inventory = inv + const title = bot.currentWindow?.title const PrismarineChat = PrismarineChatLoader(bot.version) try { inv.canvasManager.children[0].customTitleText = title ? @@ -470,7 +423,6 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = const isRightClick = type === 'rightclick' const isLeftClick = type === 'leftclick' if (isLeftClick || isRightClick) { - modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item) } } else { @@ -502,7 +454,6 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = if (freeSlot === null) return void bot.creative.setInventorySlot(freeSlot, item) } else { - modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0]) } } @@ -571,7 +522,7 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { type Result = RecipeItem | undefined let shapelessResult: Result let shapeResult: Result - outer: for (const [id, recipeVariants] of Object.entries(loadedData.recipes ?? {})) { + outer: for (const [id, recipeVariants] of Object.entries(loadedData.recipes)) { for (const recipeVariant of recipeVariants) { if ('inShape' in recipeVariant && equals(currentShape, recipeVariant.inShape as number[][])) { shapeResult = recipeVariant.result! @@ -599,7 +550,7 @@ const getAllItemRecipes = (itemName: string) => { const item = loadedData.itemsByName[itemName] if (!item) return const itemId = item.id - const recipes = loadedData.recipes?.[itemId] + const recipes = loadedData.recipes[itemId] if (!recipes) return const results = [] as Array<{ result: Item, @@ -644,7 +595,7 @@ const getAllItemUsages = (itemName: string) => { if (!item) return const foundRecipeIds = [] as string[] - for (const [id, recipes] of Object.entries(loadedData.recipes ?? {})) { + for (const [id, recipes] of Object.entries(loadedData.recipes)) { for (const recipe of recipes) { if ('inShape' in recipe) { if (recipe.inShape.some(row => row.includes(item.id))) { diff --git a/src/mineflayer/mc-protocol.ts b/src/mineflayer/mc-protocol.ts index cd21d01f..2376cd03 100644 --- a/src/mineflayer/mc-protocol.ts +++ b/src/mineflayer/mc-protocol.ts @@ -1,46 +1,13 @@ -import net from 'net' import { Client } from 'minecraft-protocol' import { appQueryParams } from '../appParams' import { downloadAllMinecraftData, getVersionAutoSelect } from '../connect' import { gameAdditionalState } from '../globalState' import { ProgressReporter } from '../core/progressReporter' -import { parseServerAddress } from '../parseServerAddress' -import { getCurrentProxy } from '../react/ServersList' import { pingServerVersion, validatePacket } from './minecraft-protocol-extra' import { getWebsocketStream } from './websocket-core' let lastPacketTime = 0 customEvents.on('mineflayerBotCreated', () => { - // const oldParsePacketBuffer = bot._client.deserializer.parsePacketBuffer - // try { - // const parsed = oldParsePacketBuffer(buffer) - // } catch (err) { - // debugger - // reportError(new Error(`Error parsing packet ${buffer.subarray(0, 30).toString('hex')}`, { cause: err })) - // throw err - // } - // } - class MinecraftProtocolError extends Error { - constructor (message: string, cause?: Error, public data?: any) { - if (data?.customPayload) { - message += ` (Custom payload: ${data.customPayload.channel})` - } - super(message, { cause }) - this.name = 'MinecraftProtocolError' - } - } - - const onClientError = (err, data) => { - const error = new MinecraftProtocolError(`Minecraft protocol client error: ${err.message}`, err, data) - reportError(error) - } - if (typeof bot._client['_events'].error === 'function') { - // dont report to bot for more explicit error - bot._client['_events'].error = onClientError - } else { - bot._client.on('error' as any, onClientError) - } - // todo move more code here if (!appQueryParams.noPacketsValidation) { (bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => { @@ -68,7 +35,7 @@ setInterval(() => { }, 1000) -export const getServerInfo = async (ip: string, port?: number, preferredVersion = getVersionAutoSelect(), ping = false, progressReporter?: ProgressReporter, setProxyParams?: ProxyParams) => { +export const getServerInfo = async (ip: string, port?: number, preferredVersion = getVersionAutoSelect(), ping = false, progressReporter?: ProgressReporter) => { await downloadAllMinecraftData() const isWebSocket = ip.startsWith('ws://') || ip.startsWith('wss://') let stream @@ -76,8 +43,6 @@ export const getServerInfo = async (ip: string, port?: number, preferredVersion progressReporter?.setMessage('Connecting to WebSocket server') stream = (await getWebsocketStream(ip)).mineflayerStream progressReporter?.setMessage('WebSocket connected. Ping packet sent, waiting for response') - } else if (setProxyParams) { - setProxy(setProxyParams) } window.setLoadingMessage = (message?: string) => { if (message === undefined) { @@ -94,46 +59,3 @@ export const getServerInfo = async (ip: string, port?: number, preferredVersion window.setLoadingMessage = undefined }) } - -globalThis.debugTestPing = async (ip: string) => { - const parsed = parseServerAddress(ip, false) - const result = await getServerInfo(parsed.host, parsed.port ? Number(parsed.port) : undefined, undefined, true, undefined, { address: getCurrentProxy(), }) - console.log('result', result) - return result -} - -export const getDefaultProxyParams = () => { - return { - headers: { - Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` - } - } -} - -export type ProxyParams = { - address?: string - headers?: Record -} - -export const setProxy = (proxyParams: ProxyParams) => { - if (proxyParams.address?.startsWith(':')) { - proxyParams.address = `${location.protocol}//${location.hostname}${proxyParams.address}` - } - if (proxyParams.address && location.port !== '80' && location.port !== '443' && !/:\d+$/.test(proxyParams.address)) { - const https = proxyParams.address.startsWith('https://') || location.protocol === 'https:' - proxyParams.address = `${proxyParams.address}:${https ? 443 : 80}` - } - - const parsedProxy = parseServerAddress(proxyParams.address, false) - const proxy = { host: parsedProxy.host, port: parsedProxy.port } - proxyParams.headers ??= getDefaultProxyParams().headers - net['setProxy']({ - hostname: proxy.host, - port: proxy.port, - headers: proxyParams.headers, - artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined - }) - return { - proxy - } -} diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 33f7af77..b8919a14 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -3,7 +3,6 @@ import { getInitialPlayerState, getPlayerStateUtils, PlayerStateReactive, Player import { subscribe } from 'valtio' import { subscribeKey } from 'valtio/utils' import { gameAdditionalState } from '../globalState' -import { options } from '../optionsStorage' /** * can be used only in main thread. Mainly for more convenient reactive state updates. @@ -43,7 +42,6 @@ export class PlayerStateControllerMain { private botCreated () { console.log('bot created & plugins injected') this.reactive = getInitialPlayerState() - this.reactive.perspective = options.defaultPerspective this.utils = getPlayerStateUtils(this.reactive) this.onBotCreatedOrGameJoined() diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index 14e19345..fc1ce0fd 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -110,7 +110,7 @@ const domListeners = (bot: Bot) => { }, { signal: abortController.signal }) bot.mouse.beforeUpdateChecks = () => { - if (!document.hasFocus() || !isGameActive(true)) { + if (!document.hasFocus()) { // deactive all buttons bot.mouse.buttons.fill(false) } diff --git a/src/mineflayer/websocket-core.ts b/src/mineflayer/websocket-core.ts index f8163102..0edd2497 100644 --- a/src/mineflayer/websocket-core.ts +++ b/src/mineflayer/websocket-core.ts @@ -15,12 +15,9 @@ class CustomDuplex extends Duplex { } export const getWebsocketStream = async (host: string) => { - const baseProtocol = host.startsWith('ws://') ? 'ws' : 'wss' + const baseProtocol = location.protocol === 'https:' ? 'wss' : host.startsWith('ws://') ? 'ws' : 'wss' const hostClean = host.replace('ws://', '').replace('wss://', '') - const hostURL = new URL(`${baseProtocol}://${hostClean}`) - const hostParams = hostURL.searchParams - hostParams.append('client_mcraft', '') - const ws = new WebSocket(`${baseProtocol}://${hostURL.host}${hostURL.pathname}?${hostParams.toString()}`) + const ws = new WebSocket(`${baseProtocol}://${hostClean}`) const clientDuplex = new CustomDuplex(undefined, data => { ws.send(data) }) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 0cb0fe1e..b03db37d 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -480,24 +480,6 @@ export const guiOptionsScheme: { ], sound: [ { volume: {} }, - { - custom () { - return { - options.musicVolume = value - }} - item={{ - type: 'slider', - id: 'musicVolume', - text: 'Music Volume', - min: 0, - max: 100, - unit: '%', - }} - /> - }, - }, { custom () { return + } {(noConnection || appConfig?.alwaysReconnectButton) && (
)} - {!lockConnect && <> - - } diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index 42ef2aaa..d95d4e9e 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -1,11 +1,9 @@ import { useEffect, useMemo, useState } from 'react' import { useUtilsEffect } from '@zardoy/react-util' import { useSnapshot } from 'valtio' -import { supportedVersions } from 'minecraft-protocol' -import { versionToNumber } from 'mc-assets/dist/utils' import { ConnectOptions } from '../connect' import { activeModalStack, hideCurrentModal, miscUiState, notHideableModalsWithoutForce, showModal } from '../globalState' -import appSupportedVersions from '../supportedVersions.mjs' +import supportedVersions from '../supportedVersions.mjs' import { appQueryParams } from '../appParams' import { fetchServerStatus, isServerValid } from '../api/mcStatusApi' import { getServerInfo } from '../mineflayer/mc-protocol' @@ -22,10 +20,6 @@ import Button from './Button' import { pixelartIcons } from './PixelartIcon' import { showNotification } from './NotificationProvider' -const firstProtocolVersion = versionToNumber(supportedVersions[0]) -const lastProtocolVersion = versionToNumber(supportedVersions.at(-1)!) -const protocolSupportedVersions = appSupportedVersions.filter(v => versionToNumber(v) >= firstProtocolVersion && versionToNumber(v) <= lastProtocolVersion) - const EXPLICIT_SHARE_SERVER_MODE = false if (appQueryParams.lockConnect) { @@ -119,7 +113,6 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL ...serversListProvided, ...(customServersList ? [] : (miscUiState.appConfig?.promoteServers ?? [])).map((server): StoreServerItem => ({ ip: server.ip, - name: server.name, versionOverride: server.version, description: server.description, isRecommended: true @@ -163,23 +156,13 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL const isWebSocket = server.ip.startsWith('ws://') || server.ip.startsWith('wss://') let data if (isWebSocket) { - try { - const pingResult = await getServerInfo(server.ip, undefined, undefined, true) - console.log('pingResult.fullInfo.description', pingResult.fullInfo.description) - data = { - formattedText: pingResult.fullInfo.description, - icon: pingResult.fullInfo.favicon, - textNameRight: `ws ${pingResult.latency}ms`, - textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`, - offline: false - } - } catch (err) { - data = { - formattedText: 'Failed to connect', - textNameRight: '', - textNameRightGrayed: '', - offline: true - } + const pingResult = await getServerInfo(server.ip, undefined, undefined, true) + console.log('pingResult.fullInfo.description', pingResult.fullInfo.description) + data = { + formattedText: pingResult.fullInfo.description, + textNameRight: `ws ${pingResult.latency}ms`, + textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`, + offline: false } } else { data = await fetchServerStatus(server.ip, /* signal */undefined, server.versionOverride) // DONT ADD SIGNAL IT WILL CRUSH JS RUNTIME @@ -234,6 +217,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL }) const editModalJsx = isEditScreenModal ? : null const serversListJsx = { min?: number; max?: number; disabledReason?: string; - throttle?: number | false; // milliseconds, default 100, false to disable updateValue?: (value: number) => void; updateOnDragEnd?: boolean; @@ -27,24 +26,15 @@ const Slider: React.FC = ({ min = 0, max = 100, disabledReason, - throttle = 0, updateOnDragEnd = false, updateValue, ...divProps }) => { - label = translate(label) - disabledReason = translate(disabledReason) - valueDisplay = typeof valueDisplay === 'string' ? translate(valueDisplay) : valueDisplay - const [value, setValue] = useState(valueProp) const getRatio = (v = value) => Math.max(Math.min((v - min) / (max - min), 1), 0) const [ratio, setRatio] = useState(getRatio()) - // Throttling refs - const timeoutRef = useRef(null) - const lastValueRef = useRef(valueProp) - useEffect(() => { setValue(valueProp) }, [valueProp]) @@ -52,52 +42,14 @@ const Slider: React.FC = ({ setRatio(getRatio()) }, [value, min, max]) - const throttledUpdateValue = useCallback((newValue: number, dragEnd: boolean) => { - if (updateOnDragEnd !== dragEnd) return - if (!updateValue) return - - lastValueRef.current = newValue - - if (!throttle) { - // No throttling - updateValue(newValue) - return - } - - // Clear existing timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } - - // Set new timeout - timeoutRef.current = setTimeout(() => { - updateValue(lastValueRef.current) - timeoutRef.current = null - }, throttle) - }, [updateValue, updateOnDragEnd, throttle]) - - // Cleanup timeout on unmount - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - // Fire the last value immediately on cleanup - if (updateValue && lastValueRef.current !== undefined) { - updateValue(lastValueRef.current) - } - } - } - }, [updateValue]) - const fireValueUpdate = (dragEnd: boolean, v = value) => { - throttledUpdateValue(v, dragEnd) + if (updateOnDragEnd !== dragEnd) return + updateValue?.(v) } - const labelText = `${label}: ${valueDisplay ?? value} ${unit}` - return ( -
17 ? 'settings-text-container-long' : ''}`} style={{ width }} {...divProps}> +
= ({
diff --git a/src/react/TouchAreasControls.tsx b/src/react/TouchAreasControls.tsx index 469097bd..ec9d201b 100644 --- a/src/react/TouchAreasControls.tsx +++ b/src/react/TouchAreasControls.tsx @@ -1,6 +1,5 @@ import { CSSProperties, PointerEvent, useEffect, useRef, useState } from 'react' import { proxy, ref, useSnapshot } from 'valtio' -import activatableItemsMobile from 'mineflayer-mouse/dist/activatableItemsMobile' import { contro } from '../controls' import { options } from '../optionsStorage' import PixelartIcon from './PixelartIcon' @@ -73,12 +72,10 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) break: false, jump: bot?.getControlState('jump'), }[name] - const RIGHT_MOUSE_BUTTON = 2 - const LEFT_MOUSE_BUTTON = 0 const holdDown = { action () { if (!bot) return - document.dispatchEvent(new MouseEvent('mousedown', { button: RIGHT_MOUSE_BUTTON })) + document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) bot.mouse.update() }, sneak () { @@ -90,7 +87,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) }, break () { if (!bot) return - document.dispatchEvent(new MouseEvent('mousedown', { button: LEFT_MOUSE_BUTTON })) + document.dispatchEvent(new MouseEvent('mousedown', { button: 0 })) bot.mouse.update() active = true }, @@ -104,7 +101,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) } const holdUp = { action () { - document.dispatchEvent(new MouseEvent('mouseup', { button: RIGHT_MOUSE_BUTTON })) + document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) }, sneak () { void contro.emit('release', { @@ -115,7 +112,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props) }, break () { if (!bot) return - document.dispatchEvent(new MouseEvent('mouseup', { button: LEFT_MOUSE_BUTTON })) + document.dispatchEvent(new MouseEvent('mouseup', { button: 0 })) bot.mouse.update() active = false }, diff --git a/src/react/appStorageProvider.ts b/src/react/appStorageProvider.ts index fd469186..bce6feca 100644 --- a/src/react/appStorageProvider.ts +++ b/src/react/appStorageProvider.ts @@ -91,14 +91,6 @@ const setCookieValue = (key: string, value: string): boolean => { } document.cookie = cookie - - // Verify the cookie was actually saved by reading it back - const savedValue = getCookieValue(key) - if (savedValue !== value) { - console.warn(`Cookie verification failed for key '${key}'. Expected: ${value}, Got: ${savedValue}`) - return false - } - return true } catch (error) { console.error(`Failed to set cookie for key '${key}':`, error) @@ -237,19 +229,12 @@ export const getRandomUsername = (appConfig: AppConfig) => { export const appStorage = proxy({ ...defaultStorageData }) -// Track if cookies failed in this session -let cookiesFailedThisSession = false - // Check if cookie storage should be used (will be set by options) const shouldUseCookieStorage = () => { - // If cookies failed this session, don't try again - if (cookiesFailedThisSession) { - return false - } - + const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) const isSecureCookiesAvailable = () => { // either https or localhost - return window.location.protocol === 'https:' || (window.location.hostname === 'localhost') + return window.location.protocol === 'https:' || (window.location.hostname === 'localhost' && !isSafari) } if (!isSecureCookiesAvailable()) { return false @@ -360,10 +345,8 @@ const saveKey = (key: keyof StorageData) => { // Remove from localStorage if cookie save was successful markLocalStorageAsMigrated(key) } else { - // Cookie save failed, disable cookies for this session and fallback to localStorage - console.warn(`Cookie save failed for key '${key}', disabling cookies for this session`) - cookiesFailedThisSession = true - useLocalStorage = true + // Disabling for now so no confusing conflicts modal after page reload + // useLocalStorage = true } } } diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 6339686e..b15cb79d 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -66,9 +66,6 @@ import CreditsAboutModal from './react/CreditsAboutModal' import GlobalOverlayHints from './react/GlobalOverlayHints' import FullscreenTime from './react/FullscreenTime' import StorageConflictModal from './react/StorageConflictModal' -import FireRenderer from './react/FireRenderer' -import MonacoEditor from './react/MonacoEditor' -import OverlayModelViewer from './react/OverlayModelViewer' const isFirefox = ua.getBrowser().name === 'Firefox' if (isFirefox) { @@ -174,7 +171,6 @@ const InGameUi = () => { - {!disabledUiParts.includes('fire') && }
@@ -250,6 +246,7 @@ const App = () => { + @@ -260,8 +257,6 @@ const App = () => {
- - diff --git a/src/resourcePack.ts b/src/resourcePack.ts index ea6c73fd..e0f0fca4 100644 --- a/src/resourcePack.ts +++ b/src/resourcePack.ts @@ -486,6 +486,17 @@ const downloadAndUseResourcePack = async (url: string, progressReporter: Progres } } +const waitForGameEvent = async () => { + if (miscUiState.gameLoaded) return + await new Promise(resolve => { + const listener = () => resolve() + customEvents.once('gameLoaded', listener) + watchUnloadForCleanup(() => { + customEvents.removeListener('gameLoaded', listener) + }) + }) +} + export const onAppLoad = () => { customEvents.on('mineflayerBotCreated', () => { // todo also handle resourcePack diff --git a/src/screens.css b/src/screens.css index e503c305..f0040e2d 100644 --- a/src/screens.css +++ b/src/screens.css @@ -26,10 +26,6 @@ display: flex; justify-content: center; z-index: 12; - /* Account for GUI scaling */ - width: calc(100dvw / var(--guiScale, 1)); - height: calc(100dvh / var(--guiScale, 1)); - overflow: hidden; } .screen-content { diff --git a/src/shims/minecraftData.ts b/src/shims/minecraftData.ts index 33dff5fe..989aa698 100644 --- a/src/shims/minecraftData.ts +++ b/src/shims/minecraftData.ts @@ -19,20 +19,11 @@ const customResolver = () => { } } -let dataStatus = 'not-called' - const optimizedDataResolver = customResolver() window._MC_DATA_RESOLVER = optimizedDataResolver window._LOAD_MC_DATA = async () => { if (optimizedDataResolver.resolvedData) return - dataStatus = 'loading' - try { - optimizedDataResolver.resolve(await importLargeData('mcData')) - dataStatus = 'ready' - } catch (e) { - dataStatus = 'error' - throw e - } + optimizedDataResolver.resolve(await importLargeData('mcData')) } // 30 seconds @@ -48,7 +39,7 @@ const possiblyGetFromCache = (version: string) => { } const inner = () => { if (!optimizedDataResolver.resolvedData) { - throw new Error(`Minecraft data are not ready yet. Ensure you await window._LOAD_MC_DATA() before using it. Status: ${dataStatus}`) + throw new Error(`Data for ${version} is not ready yet`) } const dataTypes = Object.keys(optimizedDataResolver.resolvedData) const allRestored = {} diff --git a/src/sounds/botSoundSystem.ts b/src/sounds/botSoundSystem.ts index cb237072..72aa3da8 100644 --- a/src/sounds/botSoundSystem.ts +++ b/src/sounds/botSoundSystem.ts @@ -11,7 +11,6 @@ import { showNotification } from '../react/NotificationProvider' import { pixelartIcons } from '../react/PixelartIcon' import { createSoundMap, SoundMap } from './soundsMap' import { musicSystem } from './musicSystem' -import './customSoundSystem' let soundMap: SoundMap | undefined @@ -51,9 +50,8 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { appViewer.backend?.soundSystem?.playSound( position, soundData.url, - soundData.volume, - Math.max(Math.min(pitch ?? 1, 2), 0.5), - soundData.timeout ?? options.remoteSoundsLoadTimeout + soundData.volume * (options.volume / 100), + Math.max(Math.min(pitch ?? 1, 2), 0.5) ) } if (getDistance(bot.entity.position, position) < 4 * 16) { @@ -83,7 +81,7 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { } const randomMusicKey = musicKeys[Math.floor(Math.random() * musicKeys.length)] const soundData = await soundMap.getSoundUrl(randomMusicKey) - if (!soundData || !soundMap) return + if (!soundData) return await musicSystem.playMusic(soundData.url, soundData.volume) } @@ -111,9 +109,6 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { } bot.on('soundEffectHeard', async (soundId, position, volume, pitch) => { - if (/^https?:/.test(soundId.replace('minecraft:', ''))) { - return - } await playHardcodedSound(soundId, position, volume, pitch) }) diff --git a/src/sounds/customSoundSystem.ts b/src/sounds/customSoundSystem.ts deleted file mode 100644 index 1880aa70..00000000 --- a/src/sounds/customSoundSystem.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { loadOrPlaySound, stopAllSounds, stopSound } from '../basicSounds' -import { options } from '../optionsStorage' - -const customSoundSystem = () => { - bot._client.on('named_sound_effect', packet => { - if (!options.remoteSoundsSupport) return - let { soundName } = packet - let metadata = {} as { loadTimeout?: number, loop?: boolean } - - // Extract JSON metadata from parentheses at the end - const jsonMatch = /\(({.*})\)$/.exec(soundName) - if (jsonMatch) { - try { - metadata = JSON.parse(jsonMatch[1]) - soundName = soundName.slice(0, -jsonMatch[0].length) - } catch (e) { - console.warn('Failed to parse sound metadata:', jsonMatch[1]) - } - } - - if (/^https?:/.test(soundName.replace('minecraft:', ''))) { - const { loadTimeout, loop } = metadata - void loadOrPlaySound(soundName, packet.volume, loadTimeout, loop) - } - }) - - bot._client.on('stop_sound', packet => { - const { flags, source, sound } = packet - - if (flags === 0) { - // Stop all sounds - stopAllSounds() - } else if (sound) { - // Stop specific sound by name - stopSound(sound) - } - }) - - bot.on('end', () => { - stopAllSounds() - }) -} - -customEvents.on('mineflayerBotCreated', () => { - customSoundSystem() -}) diff --git a/src/sounds/musicSystem.ts b/src/sounds/musicSystem.ts index 8fad4c74..ecabf43e 100644 --- a/src/sounds/musicSystem.ts +++ b/src/sounds/musicSystem.ts @@ -5,10 +5,10 @@ class MusicSystem { private currentMusic: string | null = null async playMusic (url: string, musicVolume = 1) { - if (!options.enableMusic || this.currentMusic || options.musicVolume === 0) return + if (!options.enableMusic || this.currentMusic) return try { - const { onEnded } = await loadOrPlaySound(url, musicVolume, 5000, undefined, true) ?? {} + const { onEnded } = await loadOrPlaySound(url, 0.5 * musicVolume, 5000) ?? {} if (!onEnded) return diff --git a/src/sounds/soundsMap.ts b/src/sounds/soundsMap.ts index 47028971..1b0a0178 100644 --- a/src/sounds/soundsMap.ts +++ b/src/sounds/soundsMap.ts @@ -35,7 +35,6 @@ interface ResourcePackSoundEntry { name: string stream?: boolean volume?: number - timeout?: number } interface ResourcePackSound { @@ -141,7 +140,7 @@ export class SoundMap { await scan(soundsBasePath) } - async getSoundUrl (soundKey: string, volume = 1): Promise<{ url: string; volume: number, timeout?: number } | undefined> { + async getSoundUrl (soundKey: string, volume = 1): Promise<{ url: string; volume: number } | undefined> { // First check resource pack sounds.json if (this.activeResourcePackSoundsJson && soundKey in this.activeResourcePackSoundsJson) { const rpSound = this.activeResourcePackSoundsJson[soundKey] @@ -152,13 +151,6 @@ export class SoundMap { if (this.activeResourcePackBasePath) { const tryFormat = async (format: string) => { try { - if (sound.name.startsWith('http://') || sound.name.startsWith('https://')) { - return { - url: sound.name, - volume: soundVolume * Math.max(Math.min(volume, 1), 0), - timeout: sound.timeout - } - } const resourcePackPath = path.join(this.activeResourcePackBasePath!, `/assets/minecraft/sounds/${sound.name}.${format}`) const fileData = await fs.promises.readFile(resourcePackPath) return { diff --git a/src/watchOptions.ts b/src/watchOptions.ts index de7d30d3..478da4fb 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -3,7 +3,6 @@ import { subscribeKey } from 'valtio/utils' import { isMobile } from 'renderer/viewer/lib/simpleUtils' import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter' -import { setSkinsConfig } from 'renderer/viewer/lib/utils/skins' import { options, watchValue } from './optionsStorage' import { reloadChunks } from './utils' import { miscUiState } from './globalState' @@ -81,10 +80,6 @@ export const watchOptionsAfterViewerInit = () => { updateFpsLimit(o) }) - watchValue(options, o => { - appViewer.inWorldRenderingConfig.volume = Math.max(o.volume / 100, 0) - }) - watchValue(options, o => { appViewer.inWorldRenderingConfig.vrSupport = o.vrSupport appViewer.inWorldRenderingConfig.vrPageGameRendering = o.vrPageGameRendering @@ -98,8 +93,6 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks - - setSkinsConfig({ apiEnabled: o.loadPlayerSkins }) }) appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting @@ -119,10 +112,6 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.starfield = o.starfieldRendering }) - watchValue(options, o => { - appViewer.inWorldRenderingConfig.defaultSkybox = o.defaultSkybox - }) - watchValue(options, o => { // appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates }) @@ -135,6 +124,5 @@ export const watchOptionsAfterWorldViewInit = (worldView: WorldDataEmitter) => { appViewer.inWorldRenderingConfig.renderEars = o.renderEars appViewer.inWorldRenderingConfig.showHand = o.showHand appViewer.inWorldRenderingConfig.viewBobbing = o.viewBobbing - appViewer.inWorldRenderingConfig.dayCycle = o.dayCycleAndLighting }) }