diff --git a/.eslintrc.json b/.eslintrc.json index 05983c703..58df91c97 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,9 +1,11 @@ { "extends": "zardoy", "ignorePatterns": [ - "!*.js" + "!*.js", + "prismarine-viewer/" ], "rules": { + "space-infix-ops": "error", "no-multi-spaces": "error", "space-in-parens": [ "error", @@ -86,6 +88,7 @@ "unicorn/no-empty-file": "off", "unicorn/prefer-event-target": "off", // needs to be fixed actually + "complexity": "off", "@typescript-eslint/no-floating-promises": "warn", "no-async-promise-executor": "off", "no-bitwise": "off", diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index b3c4f51eb..528240047 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -27,11 +27,15 @@ jobs: run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} - name: Build Project Artifacts run: vercel build --token=${{ secrets.VERCEL_TOKEN }} + - name: Copy playground files + run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js - name: Deploy Project Artifacts to Vercel uses: mathiasvr/command-output@v2.0.0 with: run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} id: deploy + - name: Set deployment alias + run: vercel alias set ${{ steps.deploy.outputs.stdout }} ${{ secrets.TEST_PREVIEW_DOMAIN }} --token=${{ secrets.VERCEL_TOKEN }} - uses: mshick/add-pr-comment@v2 with: message: | diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0a6378a93..2b84b90c4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,6 +19,8 @@ jobs: - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} # will install + build to .vercel/output/static - run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod + - name: Copy playground files + run: node prismarine-viewer/esbuild.mjs && cp prismarine-viewer/public/index.html .vercel/output/static/playground.html && cp prismarine-viewer/public/playground.js .vercel/output/static/playground.js - name: Deploy Project to Vercel uses: mathiasvr/command-output@v2.0.0 with: diff --git a/.vscode/launch.json b/.vscode/launch.json index 87e66901e..6bbd4198e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,12 +15,12 @@ "outFiles": [ "${workspaceFolder}/dist/**/*.js", // "!${workspaceFolder}/dist/**/*vendors*", - "!${workspaceFolder}/dist/**/*minecraftData*", + "!${workspaceFolder}/dist/**/*mc-data*", "!**/node_modules/**" ], "skipFiles": [ // "/**/*vendors*" - "/**/*minecraftData*" + "/**/*mc-data*" ], "port": 9222, }, @@ -36,12 +36,12 @@ "outFiles": [ "${workspaceFolder}/dist/**/*.js", // "!${workspaceFolder}/dist/**/*vendors*", - "!${workspaceFolder}/dist/**/*minecraftData*", + "!${workspaceFolder}/dist/**/*mc-data*", "!**/node_modules/**" ], "skipFiles": [ // "/**/*vendors*" - "/**/*minecraftData*" + "/**/*mc-data*" ], }, { @@ -54,7 +54,7 @@ "webRoot": "${workspaceFolder}/", "skipFiles": [ // "/**/*vendors*" - "/**/*minecraftData*" + "/**/*mc-data*" ], }, ] diff --git a/README.MD b/README.MD new file mode 100644 index 000000000..ca70dfc14 --- /dev/null +++ b/README.MD @@ -0,0 +1,80 @@ +# Minecraft Web Client + +A true Minecraft client running in your browser! A port of the original game to the web, written in JavaScript using modern web technologies. + +This project is a work in progress, but I consider it to be usable. If you encounter any bugs or usability issues, please report them! + +### Big Features + +- Connect to any offline server* (it's possible because of proxy servers, see below) +- Open any zip world file or even folder in read-write mode! +- Singleplayer mode with simple world generation +- Works offline +- Play with friends over global network! (P2P is powered by Peer.js servers) +- First-class touch (mobile) & controller support +- Resource pack support +- even even more! + +There are a lot + +### World Loading + +Zip files and folders are supported. Just drag and drop them into the browser window. You can open folders in readonly and read-write mode. New chunks may be generated incorrectly for now. +In case of opening zip files they are stored in your ram entirely, so there is a ~300mb file limit on IOS. +Whatever offline mode you used (zip, folder, just single player), you can always export world with the `/export` command typed in the game chat. + +### Servers + +You can play almost on any server, supporting offline connections. +See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for the list of supported versions (should support majority of versions). +There is a builtin proxy, but you can also host a your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. +MS account authentication will be supported soon. + + + +### Things that are not planned yet + +- Mods, plugins (basically JARs) support, shaders - since they all are related to specific game pipelines + +### Advanced Settings + +There are many many settings, that are not exposed in the UI yet. You can find or change them by opening the browser console and typing `options`. You can also change them by typing `options. = `. + +### Console + +To open the console, press `F12`, or if you are on mobile, you can type `#debug` in the URL (browser address bar), it wont't reload the page, but you will see a button to open the console. This way you can change advanced settings and see all errors or warnings. Also this way you can access global variables (described below). + +### Debugging + +It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info. + +However, there are many things that can be done in online version. You can access some global variables in the console and useful examples: + +- `localStorage.debug = '*'` - Enables all debug messages! + +- `bot` - Mineflayer bot instance. See Mineflayer documentation for more. +- `viewer` - Three.js viewer instance, basically does all the rendering. +- `viewer.world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group. +- `localServer` - Only for singleplayer mode/host. Flying Squid server instance, see it's documentation for more. +- `localServer.overworld.storageProvider.regions` - See ALL LOADED region files with all raw data. + +- `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read. + +You can also drag and drop any .dat file into the browser window to see it's contents in the console. + +### F3 Keybindings + +- `F3` - Toggle debug overlay +- `F3 + A` - Reload all chunks (these that are loaded from the server) + +- `F3 + G` - Toggle chunk sections (geometries) border visibility (aka Three.js geometry helpers) - most probably need to reload chunks after toggling + +### Notable Things that Power this Project + +- [Mineflayer](https://github.com/PrismarineJS/mineflayer) - Handles all client-side communications with the server (including the builtin one) - forked +- [Flying Squid](https://github.com/prismarineJS/flying-squid) - The builtin server that makes single player possible! Here forked version is used. +- [Prismarine Provider Anvil](https://github.com/PrismarineJS/prismarine-provider-anvil) - Handles world loading (region format) +- [Prismarine Physics](https://github.com/PrismarineJS/prismarine-physics) - Does all the physics calculations +- [Minecraft Protocol](https://github.com/PrismarineJS/node-minecraft-protocol) - Makes connections to servers possible +- [Peer.js](https://peerjs.com/) - P2P networking (when you open to wan) +- [Three.js](https://threejs.org/) - Helping in 3D rendering diff --git a/esbuild.mjs b/esbuild.mjs index 78ee7173c..36a981d22 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -6,6 +6,7 @@ import server from './server.js' import { clients, plugins } from './scripts/esbuildPlugins.mjs' import { generateSW } from 'workbox-build' import { getSwAdditionalEntries } from './scripts/build.js' +import { build } from 'esbuild' //@ts-ignore try { await import('./localSettings.mjs') } catch { } @@ -26,7 +27,8 @@ const banner = [ const buildingVersion = new Date().toISOString().split(':')[0] -const ctx = await esbuild.context({ +/** @type {import('esbuild').BuildOptions} */ +const buildOptions = { bundle: true, entryPoints: ['src/index.ts'], target: ['es2020'], @@ -77,9 +79,10 @@ const ctx = await esbuild.context({ write: false, // todo would be better to enable? // preserveSymlinks: true, -}) +} if (watch) { + const ctx = await esbuild.context(buildOptions) await ctx.watch() server.app.get('/esbuild', (req, res, next) => { res.writeHead(200, { @@ -103,7 +106,7 @@ if (watch) { }) }) } else { - const result = await ctx.rebuild() + const result = await build(buildOptions) // console.log(await esbuild.analyzeMetafile(result.metafile)) if (prod) { @@ -119,6 +122,4 @@ if (watch) { swDest: 'dist/service-worker.js', }) } - - await ctx.dispose() } diff --git a/experiments/pointers.html b/experiments/pointers.html new file mode 100644 index 000000000..1d2d15dce --- /dev/null +++ b/experiments/pointers.html @@ -0,0 +1,39 @@ + + + + + + Document + + +
+ + + diff --git a/package.json b/package.json index 015d90360..9d610edf7 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "test:e2e": "start-test http-get://localhost:8080 test:cypress", "prod-start": "node server.js", "postinstall": "node scripts/gen-texturepack-files.mjs", + "test-mc-server": "tsx cypress/minecraft-server.mjs", "lint": "eslint \"{src,cypress}/**/*.{ts,js,jsx,tsx}\"" }, "keywords": [ @@ -18,7 +19,6 @@ "web", "client" ], - "bin": "./server.js", "author": "PrismarineJS", "license": "MIT", "dependencies": { @@ -60,6 +60,7 @@ "@types/lodash-es": "^4.17.9", "@types/stats.js": "^0.17.1", "@types/three": "0.128.0", + "@xmcl/installer": "^5.1.0", "assert": "^2.0.0", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", @@ -90,6 +91,7 @@ "timers-browserify": "^2.0.12", "typescript": "^5.2.2", "use-typed-event-listener": "^4.0.2", + "vitest": "^0.34.6", "yaml": "^2.3.2" }, "pnpm": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 003560478..010215f1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,7 +62,7 @@ importers: version: 4.18.2 flying-squid: specifier: github:zardoy/space-squid#everything - version: github.com/zardoy/space-squid/eec886b7a881eb40dd8efc6d48fa52e638e07693 + version: github.com/zardoy/space-squid/458eee79e4ff20fccdff8027d3aae16161b9fb1c fs-extra: specifier: ^11.1.1 version: 11.1.1 @@ -86,7 +86,7 @@ importers: version: 3.45.0 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: github.com/zardoy/prismarinejs-net-browserify/d63bd1df82186cf2f0baa161a83754037d537282 + version: github.com/zardoy/prismarinejs-net-browserify/f88123ad4f4407ac21fc435b11c561742825d0a7 peerjs: specifier: ^1.5.0 version: 1.5.0 @@ -124,6 +124,9 @@ importers: '@types/three': specifier: 0.128.0 version: 0.128.0 + '@xmcl/installer': + specifier: ^5.1.0 + version: 5.1.0 assert: specifier: ^2.0.0 version: 2.0.0 @@ -214,6 +217,9 @@ importers: use-typed-event-listener: specifier: ^4.0.2 version: 4.0.2(react@18.2.0)(typescript@5.2.2) + vitest: + specifier: ^0.34.6 + version: 0.34.6 yaml: specifier: ^2.3.2 version: 2.3.2 @@ -250,6 +256,9 @@ importers: minecrafthawkeye: specifier: ^1.3.6 version: 1.3.6 + node-canvas-webgl: + specifier: ^0.3.0 + version: 0.3.0 prismarine-block: specifier: github:zardoy/prismarine-block#next-era version: github.com/zardoy/prismarine-block/753cf1fe507f7647063c69d5c124d40f85b29cc2 @@ -271,13 +280,28 @@ importers: socket.io-client: specifier: ^4.0.0 version: 4.7.2 + three-stdlib: + specifier: ^2.26.11 + version: 2.26.11(three@0.128.0) three.meshline: specifier: ^1.3.0 version: 1.4.0 + tsx: + specifier: ^3.13.0 + version: 3.13.0 vec3: specifier: ^0.1.7 version: 0.1.8 + prismarine-viewer/viewer/sign-renderer: + dependencies: + '@xmcl/text-component': + specifier: ^2.1.2 + version: 2.1.2 + vite: + specifier: ^4.4.9 + version: 4.4.10(@types/node@20.8.0) + packages: /@aashutoshrathi/word-wrap@1.2.6: @@ -1651,6 +1675,14 @@ packages: resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} dev: false + /@esbuild/android-arm64@0.18.20: + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-arm64@0.19.3: resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} engines: {node: '>=12'} @@ -1660,6 +1692,14 @@ packages: dev: false optional: true + /@esbuild/android-arm@0.18.20: + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-arm@0.19.3: resolution: {integrity: sha512-Lemgw4io4VZl9GHJmjiBGzQ7ONXRfRPHcUEerndjwiSkbxzrpq0Uggku5MxxrXdwJ+pTj1qyw4jwTu7hkPsgIA==} engines: {node: '>=12'} @@ -1669,6 +1709,14 @@ packages: dev: false optional: true + /@esbuild/android-x64@0.18.20: + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-x64@0.19.3: resolution: {integrity: sha512-FKQJKkK5MXcBHoNZMDNUAg1+WcZlV/cuXrWCoGF/TvdRiYS4znA0m5Il5idUwfxrE20bG/vU1Cr5e1AD6IEIjQ==} engines: {node: '>=12'} @@ -1678,6 +1726,14 @@ packages: dev: false optional: true + /@esbuild/darwin-arm64@0.18.20: + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/darwin-arm64@0.19.3: resolution: {integrity: sha512-kw7e3FXU+VsJSSSl2nMKvACYlwtvZB8RUIeVShIEY6PVnuZ3c9+L9lWB2nWeeKWNNYDdtL19foCQ0ZyUL7nqGw==} engines: {node: '>=12'} @@ -1687,6 +1743,14 @@ packages: dev: false optional: true + /@esbuild/darwin-x64@0.18.20: + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/darwin-x64@0.19.3: resolution: {integrity: sha512-tPfZiwF9rO0jW6Jh9ipi58N5ZLoSjdxXeSrAYypy4psA2Yl1dAMhM71KxVfmjZhJmxRjSnb29YlRXXhh3GqzYw==} engines: {node: '>=12'} @@ -1696,6 +1760,14 @@ packages: dev: false optional: true + /@esbuild/freebsd-arm64@0.18.20: + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/freebsd-arm64@0.19.3: resolution: {integrity: sha512-ERDyjOgYeKe0Vrlr1iLrqTByB026YLPzTytDTz1DRCYM+JI92Dw2dbpRHYmdqn6VBnQ9Bor6J8ZlNwdZdxjlSg==} engines: {node: '>=12'} @@ -1705,6 +1777,14 @@ packages: dev: false optional: true + /@esbuild/freebsd-x64@0.18.20: + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/freebsd-x64@0.19.3: resolution: {integrity: sha512-nXesBZ2Ad1qL+Rm3crN7NmEVJ5uvfLFPLJev3x1j3feCQXfAhoYrojC681RhpdOph8NsvKBBwpYZHR7W0ifTTA==} engines: {node: '>=12'} @@ -1714,6 +1794,14 @@ packages: dev: false optional: true + /@esbuild/linux-arm64@0.18.20: + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-arm64@0.19.3: resolution: {integrity: sha512-qXvYKmXj8GcJgWq3aGvxL/JG1ZM3UR272SdPU4QSTzD0eymrM7leiZH77pvY3UetCy0k1xuXZ+VPvoJNdtrsWQ==} engines: {node: '>=12'} @@ -1723,6 +1811,14 @@ packages: dev: false optional: true + /@esbuild/linux-arm@0.18.20: + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-arm@0.19.3: resolution: {integrity: sha512-zr48Cg/8zkzZCzDHNxXO/89bf9e+r4HtzNUPoz4GmgAkF1gFAFmfgOdCbR8zMbzFDGb1FqBBhdXUpcTQRYS1cQ==} engines: {node: '>=12'} @@ -1732,6 +1828,14 @@ packages: dev: false optional: true + /@esbuild/linux-ia32@0.18.20: + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ia32@0.19.3: resolution: {integrity: sha512-7XlCKCA0nWcbvYpusARWkFjRQNWNGlt45S+Q18UeS///K6Aw8bB2FKYe9mhVWy/XLShvCweOLZPrnMswIaDXQA==} engines: {node: '>=12'} @@ -1741,6 +1845,14 @@ packages: dev: false optional: true + /@esbuild/linux-loong64@0.18.20: + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-loong64@0.19.3: resolution: {integrity: sha512-qGTgjweER5xqweiWtUIDl9OKz338EQqCwbS9c2Bh5jgEH19xQ1yhgGPNesugmDFq+UUSDtWgZ264st26b3de8A==} engines: {node: '>=12'} @@ -1750,6 +1862,14 @@ packages: dev: false optional: true + /@esbuild/linux-mips64el@0.18.20: + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-mips64el@0.19.3: resolution: {integrity: sha512-gy1bFskwEyxVMFRNYSvBauDIWNggD6pyxUksc0MV9UOBD138dKTzr8XnM2R4mBsHwVzeuIH8X5JhmNs2Pzrx+A==} engines: {node: '>=12'} @@ -1759,6 +1879,14 @@ packages: dev: false optional: true + /@esbuild/linux-ppc64@0.18.20: + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ppc64@0.19.3: resolution: {integrity: sha512-UrYLFu62x1MmmIe85rpR3qou92wB9lEXluwMB/STDzPF9k8mi/9UvNsG07Tt9AqwPQXluMQ6bZbTzYt01+Ue5g==} engines: {node: '>=12'} @@ -1768,6 +1896,14 @@ packages: dev: false optional: true + /@esbuild/linux-riscv64@0.18.20: + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-riscv64@0.19.3: resolution: {integrity: sha512-9E73TfyMCbE+1AwFOg3glnzZ5fBAFK4aawssvuMgCRqCYzE0ylVxxzjEfut8xjmKkR320BEoMui4o/t9KA96gA==} engines: {node: '>=12'} @@ -1777,6 +1913,14 @@ packages: dev: false optional: true + /@esbuild/linux-s390x@0.18.20: + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-s390x@0.19.3: resolution: {integrity: sha512-LlmsbuBdm1/D66TJ3HW6URY8wO6IlYHf+ChOUz8SUAjVTuaisfuwCOAgcxo3Zsu3BZGxmI7yt//yGOxV+lHcEA==} engines: {node: '>=12'} @@ -1786,6 +1930,14 @@ packages: dev: false optional: true + /@esbuild/linux-x64@0.18.20: + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-x64@0.19.3: resolution: {integrity: sha512-ogV0+GwEmvwg/8ZbsyfkYGaLACBQWDvO0Kkh8LKBGKj9Ru8VM39zssrnu9Sxn1wbapA2qNS6BiLdwJZGouyCwQ==} engines: {node: '>=12'} @@ -1795,6 +1947,14 @@ packages: dev: false optional: true + /@esbuild/netbsd-x64@0.18.20: + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + /@esbuild/netbsd-x64@0.19.3: resolution: {integrity: sha512-o1jLNe4uzQv2DKXMlmEzf66Wd8MoIhLNO2nlQBHLtWyh2MitDG7sMpfCO3NTcoTMuqHjfufgUQDFRI5C+xsXQw==} engines: {node: '>=12'} @@ -1804,6 +1964,14 @@ packages: dev: false optional: true + /@esbuild/openbsd-x64@0.18.20: + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + /@esbuild/openbsd-x64@0.19.3: resolution: {integrity: sha512-AZJCnr5CZgZOdhouLcfRdnk9Zv6HbaBxjcyhq0StNcvAdVZJSKIdOiPB9az2zc06ywl0ePYJz60CjdKsQacp5Q==} engines: {node: '>=12'} @@ -1813,6 +1981,14 @@ packages: dev: false optional: true + /@esbuild/sunos-x64@0.18.20: + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + /@esbuild/sunos-x64@0.19.3: resolution: {integrity: sha512-Acsujgeqg9InR4glTRvLKGZ+1HMtDm94ehTIHKhJjFpgVzZG9/pIcWW/HA/DoMfEyXmANLDuDZ2sNrWcjq1lxw==} engines: {node: '>=12'} @@ -1822,6 +1998,14 @@ packages: dev: false optional: true + /@esbuild/win32-arm64@0.18.20: + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-arm64@0.19.3: resolution: {integrity: sha512-FSrAfjVVy7TifFgYgliiJOyYynhQmqgPj15pzLyJk8BUsnlWNwP/IAy6GAiB1LqtoivowRgidZsfpoYLZH586A==} engines: {node: '>=12'} @@ -1831,6 +2015,14 @@ packages: dev: false optional: true + /@esbuild/win32-ia32@0.18.20: + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-ia32@0.19.3: resolution: {integrity: sha512-xTScXYi12xLOWZ/sc5RBmMN99BcXp/eEf7scUC0oeiRoiT5Vvo9AycuqCp+xdpDyAU+LkrCqEpUS9fCSZF8J3Q==} engines: {node: '>=12'} @@ -1840,6 +2032,14 @@ packages: dev: false optional: true + /@esbuild/win32-x64@0.18.20: + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-x64@0.19.3: resolution: {integrity: sha512-FbUN+0ZRXsypPyWE2IwIkVjDkDnJoMJARWOcFZn4KPPli+QnKqF0z1anvfaYe3ev5HFCpRDLLBDHyOALLppWHw==} engines: {node: '>=12'} @@ -1886,6 +2086,11 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@fastify/busboy@2.0.0: + resolution: {integrity: sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==} + engines: {node: '>=14'} + dev: true + /@humanwhocodes/config-array@0.11.11: resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} @@ -1920,6 +2125,12 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 + + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 dev: true /@jimp/bmp@0.10.3(@jimp/custom@0.10.3): @@ -2355,7 +2566,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: false /@jridgewell/trace-mapping@0.3.19: resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} @@ -2426,11 +2636,17 @@ packages: fastq: 1.15.0 dev: true + /@npmcli/fs@3.1.0: + resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + semver: 7.5.4 + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@popperjs/core@2.11.8: @@ -2495,6 +2711,10 @@ packages: resolution: {integrity: sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==} dev: true + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + /@socket.io/component-emitter@3.1.0: resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} dev: false @@ -2508,10 +2728,25 @@ packages: string.prototype.matchall: 4.0.10 dev: false + /@tootallnate/once@2.0.0: + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + dev: false + /@tweenjs/tween.js@20.0.3: resolution: {integrity: sha512-SYUe1UgY5HM05EB4+0B4arq2IPjvyzKXoklXKxSYrc2IFxGm1cBrqg5XbiB5uwbs0xY5j+rj986NAJMM0KZaUw==} dev: false + /@types/chai-subset@1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.6 + dev: true + + /@types/chai@4.3.6: + resolution: {integrity: sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==} + dev: true + /@types/cookie@0.4.1: resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} dev: false @@ -2522,10 +2757,18 @@ packages: '@types/node': 20.8.0 dev: false + /@types/draco3d@1.4.6: + resolution: {integrity: sha512-tAyEGmnz6qcPqSWoHtO3tTobQCDW0tW36gVdDKyN0jkT2S2w6LABe0+DdVkfVDwNzTwR7cE7LQGiGJiAsdSNKg==} + dev: false + /@types/estree@0.0.39: resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} dev: false + /@types/http-cache-semantics@4.0.2: + resolution: {integrity: sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw==} + dev: true + /@types/js-cookie@2.2.7: resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} dev: false @@ -2562,6 +2805,10 @@ packages: resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} dev: true + /@types/offscreencanvas@2019.7.1: + resolution: {integrity: sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==} + dev: false + /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: false @@ -2627,6 +2874,10 @@ packages: resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==} dev: false + /@types/webxr@0.5.5: + resolution: {integrity: sha512-HVOsSRTQYx3zpVl0c0FBmmmcY/60BkQLzVnpE9M1aG4f2Z0aKlBWfj4XZ2zr++XNBfkQWYcwhGlmuu44RJPDqg==} + dev: false + /@types/wicg-file-system-access@2020.9.6: resolution: {integrity: sha512-6hogE75Hl2Ov/jgp8ZhDaGmIF/q3J07GtXf8nCJCwKTHq7971po5+DId7grft09zG7plBwpF6ZU0yx9Du4/e1A==} dev: false @@ -2636,7 +2887,6 @@ packages: requiresBuild: true dependencies: '@types/node': 20.8.0 - optional: true /@typescript-eslint/eslint-plugin@6.1.0(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)(typescript@5.2.2): resolution: {integrity: sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==} @@ -2812,6 +3062,44 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@vitest/expect@0.34.6: + resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} + dependencies: + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + chai: 4.3.10 + dev: true + + /@vitest/runner@0.34.6: + resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} + dependencies: + '@vitest/utils': 0.34.6 + p-limit: 4.0.0 + pathe: 1.1.1 + dev: true + + /@vitest/snapshot@0.34.6: + resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} + dependencies: + magic-string: 0.30.4 + pathe: 1.1.1 + pretty-format: 29.7.0 + dev: true + + /@vitest/spy@0.34.6: + resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} + dependencies: + tinyspy: 2.2.0 + dev: true + + /@vitest/utils@0.34.6: + resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} + dependencies: + diff-sequences: 29.6.3 + loupe: 2.3.6 + pretty-format: 29.7.0 + dev: true + /@xboxreplay/errors@0.1.0: resolution: {integrity: sha512-Tgz1d/OIPDWPeyOvuL5+aai5VCcqObhPnlI3skQuf80GVF3k1I0lPCnGC+8Cm5PV9aLBT5m8qPcJoIUQ2U4y9g==} @@ -2823,6 +3111,69 @@ packages: transitivePeerDependencies: - debug + /@xmcl/asm@1.0.1: + resolution: {integrity: sha512-7vCVgm1E1IZ2cujiitFk9550Vgu2XAOn1ff90di638fMmTK0XkFMXKsSR/nGZmYKt+XiTMI/0B3TvreqbVjOug==} + engines: {node: '>=16'} + dev: true + + /@xmcl/core@2.12.0(yauzl@2.10.0): + resolution: {integrity: sha512-rcxy29i2fjGFpe6sEvaJxYHFGlfYMcJwElbk6TIUCrKNOiMhyLZeBtCJSk38hvaYF0kiROA3c4EJeWNGHf2zVw==} + engines: {node: '>=16.0'} + dependencies: + '@xmcl/unzip': 2.1.2(yauzl@2.10.0) + transitivePeerDependencies: + - yauzl + dev: true + + /@xmcl/file-transfer@1.0.3: + resolution: {integrity: sha512-p5JsUQpNShqW4VpqaKXcQzYSO9o/9UGjXZfb5aNt/24Ty+j9qepQyPo4P1CFwfzgFuf9GGhH5cPUi6nShlKS+g==} + engines: {node: '>=16.0'} + dependencies: + '@types/http-cache-semantics': 4.0.2 + http-cache-semantics: 4.1.1 + undici: 5.25.4 + dev: true + + /@xmcl/forge-site-parser@2.0.9: + resolution: {integrity: sha512-OHKG2KYE+F6TSeOQmymuGoqEifxbJb3w3X/hmxMNeqtewiYukJldPmKO559ZFnZnOuMQEnr+X0dMbTQwWs5dFg==} + engines: {node: '>=16'} + dependencies: + node-html-parser: 6.1.10 + dev: true + + /@xmcl/installer@5.1.0: + resolution: {integrity: sha512-KpoxpfYdUWH4U4Yat7RifS1JZajArOdfgsJ/LdU90y6Fc3hKhHSJsiRudk1VxASTyMXKCM0lWVxH5JzywRHBDw==} + engines: {node: '>=16.0'} + dependencies: + '@xmcl/asm': 1.0.1 + '@xmcl/core': 2.12.0(yauzl@2.10.0) + '@xmcl/file-transfer': 1.0.3 + '@xmcl/forge-site-parser': 2.0.9 + '@xmcl/task': 4.0.6 + '@xmcl/unzip': 2.1.2(yauzl@2.10.0) + undici: 5.25.4 + yauzl: 2.10.0 + dev: true + + /@xmcl/task@4.0.6: + resolution: {integrity: sha512-h0AR7DQm6xxBgROPnRi0EY8DlaDQwuGzPA5lFRMD4EsnpHJ/3fPdzwbMLb81ZxKJlLSCn3hVR2yI0mSKIm5Heg==} + dev: true + + /@xmcl/text-component@2.1.2: + resolution: {integrity: sha512-Xbzo5+F2Z3O+Q5eVRKE1JJ/+oPtdLkSPazJ/efWJ5jR4lrCUdt5QmdJo+rSrfeGD6eH1u80JlEGBTA5w5fNpow==} + engines: {node: '>=16'} + dev: false + + /@xmcl/unzip@2.1.2(yauzl@2.10.0): + resolution: {integrity: sha512-Lm/eg/e0/p+sfj/RT2QDpsBAf39DZqQ3+XvX1JXZPb64wnjwOf8CGU1WPv6BseEcJ5CMOpm0s2NyrEQD04y0UQ==} + engines: {node: '>=16'} + peerDependencies: + yauzl: ^2.10.0 + dependencies: + '@types/yauzl': 2.10.1 + yauzl: 2.10.0 + dev: true + /@xobotyi/scrollbar-width@1.9.5: resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} dev: false @@ -2879,6 +3230,11 @@ packages: acorn: 8.10.0 dev: true + /acorn-walk@8.2.0: + resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + engines: {node: '>=0.4.0'} + dev: true + /acorn@8.10.0: resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} engines: {node: '>=0.4.0'} @@ -2900,6 +3256,13 @@ packages: - supports-color dev: false + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -2945,7 +3308,6 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -2959,10 +3321,14 @@ packages: dependencies: color-convert: 2.0.1 + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true + /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /any-base@1.1.0: resolution: {integrity: sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==} @@ -2987,6 +3353,14 @@ packages: readable-stream: 3.6.2 dev: false + /are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + dev: false + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true @@ -3101,6 +3475,10 @@ packages: object-is: 1.1.5 util: 0.12.5 + /assertion-error@1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -3224,6 +3602,16 @@ packages: dependencies: tweetnacl: 0.14.5 + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + dependencies: + file-uri-to-path: 1.0.0 + dev: false + + /bit-twiddle@1.0.2: + resolution: {integrity: sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==} + dev: false + /bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: @@ -3294,6 +3682,10 @@ packages: - supports-color dev: false + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -3431,6 +3823,29 @@ packages: engines: {node: '>= 0.8'} dev: false + /cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + + /cacache@17.1.4: + resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + '@npmcli/fs': 3.1.0 + fs-minipass: 3.0.3 + glob: 10.3.3 + lru-cache: 7.18.3 + minipass: 7.0.3 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 4.0.0 + ssri: 10.0.5 + tar: 6.2.0 + unique-filename: 3.0.0 + dev: false + /cachedir@2.4.0: resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} engines: {node: '>=6'} @@ -3517,6 +3932,19 @@ packages: cbor-extract: 2.1.1 dev: false + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.3 + deep-eql: 4.1.3 + get-func-name: 2.0.2 + loupe: 2.3.6 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -3549,6 +3977,12 @@ packages: tslib: 2.6.2 dev: false + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 + dev: true + /check-more-types@2.24.0: resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} engines: {node: '>= 0.8.0'} @@ -3918,6 +4352,16 @@ packages: hyphenate-style-name: 1.0.4 dev: false + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + dev: true + /css-tree@1.1.3: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} engines: {node: '>=8.0.0'} @@ -3926,6 +4370,11 @@ packages: source-map: 0.6.1 dev: false + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} @@ -4112,6 +4561,13 @@ packages: mimic-response: 3.1.0 dev: false + /deep-eql@4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -4196,6 +4652,11 @@ packages: vec3: 0.1.8 dev: false + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /diff2html@2.12.2: resolution: {integrity: sha512-G/Zn1KyG/OeC+67N/P26WHsQpjrjUiRyWGvg29ypy3MxSsBmF0bzsU/Irq70i2UAg+f/MzmLx4v/Nkt01TOU3g==} engines: {node: '>=4'} @@ -4248,10 +4709,37 @@ packages: esutils: 2.0.3 dev: true + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: true + /dom-walk@0.1.2: resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} dev: false + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: true + /dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} dependencies: @@ -4259,9 +4747,12 @@ packages: tslib: 2.6.2 dev: false + /draco3d@1.5.6: + resolution: {integrity: sha512-+3NaRjWktb5r61ZFoDejlykPEFKT5N/LkbXsaddlw6xNSXBanUYpFc2AXXpbJDilPHazcSreU/DpQIaxfX0NfQ==} + dev: false + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -4317,13 +4808,20 @@ packages: /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} dev: false + /encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + requiresBuild: true + dependencies: + iconv-lite: 0.6.3 + dev: false + optional: true + /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: @@ -4424,6 +4922,20 @@ packages: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + /entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + dev: true + + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: false + + /err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + dev: false + /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -4547,6 +5059,35 @@ packages: import-meta-resolve: 3.0.0 dev: false + /esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + /esbuild@0.19.3: resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==} engines: {node: '>=12'} @@ -5026,6 +5567,10 @@ packages: engines: {node: '>=6'} dev: false + /exponential-backoff@3.1.1: + resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + dev: false + /express-ws@4.0.0(express@4.18.2): resolution: {integrity: sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw==} engines: {node: '>=4.5.0'} @@ -5142,6 +5687,10 @@ packages: dependencies: pend: 1.2.0 + /fflate@0.6.10: + resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} + dev: false + /figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -5160,6 +5709,10 @@ packages: engines: {node: '>=6'} dev: false + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + dev: false + /filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: @@ -5251,7 +5804,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} @@ -5321,6 +5873,13 @@ packages: minipass: 3.3.6 dev: false + /fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 7.0.3 + dev: false + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -5329,7 +5888,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: false optional: true /function-bind@1.1.1: @@ -5362,6 +5920,20 @@ packages: wide-align: 1.1.5 dev: false + /gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + dev: false + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -5372,6 +5944,10 @@ packages: engines: {node: 6.* || 8.* || >= 10.*} dev: false + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} + dev: true + /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} dependencies: @@ -5397,6 +5973,12 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.1 + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: false + /getos@3.2.1: resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} dependencies: @@ -5411,6 +5993,22 @@ packages: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} dev: false + /gl@6.0.2: + resolution: {integrity: sha512-yBbfpChOtFvg5D+KtMaBFvj6yt3vUnheNAH+UrQH2TfDB8kr0tERdL0Tjhe0W7xJ6jR6ftQBluTZR9jXUnKe8g==} + engines: {node: '>=14.0.0'} + requiresBuild: true + dependencies: + bindings: 1.5.0 + bit-twiddle: 1.0.2 + glsl-tokenizer: 2.1.5 + nan: 2.18.0 + node-abi: 3.47.0 + node-gyp: 9.4.0 + prebuild-install: 7.1.1 + transitivePeerDependencies: + - supports-color + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -5435,7 +6033,6 @@ packages: minimatch: 9.0.3 minipass: 7.0.3 path-scurry: 1.10.1 - dev: true /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} @@ -5490,6 +6087,12 @@ packages: slash: 3.0.0 dev: true + /glsl-tokenizer@2.1.5: + resolution: {integrity: sha512-XSZEJ/i4dmz3Pmbnpsy3cKh7cotvFlBiZnDOwnj/05EwNp2XrhQ4XKJxT7/pDt4kp4YcpRSKz8eTV7S+mwV6MA==} + dependencies: + through2: 0.6.5 + dev: false + /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -5626,6 +6229,9 @@ packages: inherits: 2.0.4 dev: true + /http-cache-semantics@4.1.1: + resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} + /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -5637,6 +6243,17 @@ packages: toidentifier: 1.0.1 dev: false + /http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.3.4(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + dev: false + /http-proxy@1.18.1(debug@4.3.4): resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} @@ -5697,6 +6314,12 @@ packages: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + /hyphenate-style-name@1.0.4: resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==} dev: false @@ -5719,7 +6342,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - dev: true /idb@7.1.1: resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} @@ -5757,7 +6379,6 @@ packages: /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - dev: true /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} @@ -5804,6 +6425,10 @@ packages: has: 1.0.3 side-channel: 1.0.4 + /ip@2.0.0: + resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} + dev: false + /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -5916,6 +6541,10 @@ packages: global-dirs: 3.0.1 is-path-inside: 3.0.3 + /is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + dev: false + /is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} dev: true @@ -6026,6 +6655,10 @@ packages: get-intrinsic: 1.2.1 dev: true + /isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} + dev: false + /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: false @@ -6060,7 +6693,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jake@10.8.7: resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} @@ -6183,6 +6815,10 @@ packages: hasBin: true dev: false + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: @@ -6268,6 +6904,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /ktx-parse@0.4.5: + resolution: {integrity: sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==} + dev: false + /lazy-ass@1.6.0: resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} engines: {node: '> 0.8'} @@ -6362,6 +7002,11 @@ packages: strip-bom: 3.0.0 dev: true + /local-pkg@0.4.3: + resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} + engines: {node: '>=14'} + dev: true + /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -6463,6 +7108,12 @@ packages: dependencies: js-tokens: 4.0.0 + /loupe@2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + dependencies: + get-func-name: 2.0.2 + dev: true + /lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: @@ -6472,7 +7123,6 @@ packages: /lru-cache@10.0.1: resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} engines: {node: 14 || >=16.14} - dev: true /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -6486,6 +7136,11 @@ packages: dependencies: yallist: 4.0.0 + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: false + /macaddress@0.5.3: resolution: {integrity: sha512-vGBKTA+jwM4KgjGZ+S/8/Mkj9rWzePyGY6jManXPGhiWu63RYwW8dKPyk5koP+8qNVhPhHgFa1y/MJ4wrjsNrg==} @@ -6495,6 +7150,13 @@ packages: sourcemap-codec: 1.4.8 dev: false + /magic-string@0.30.4: + resolution: {integrity: sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + dev: true + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -6502,6 +7164,29 @@ packages: semver: 6.3.1 dev: false + /make-fetch-happen@11.1.1: + resolution: {integrity: sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + agentkeepalive: 4.5.0 + cacache: 17.1.4 + http-cache-semantics: 4.1.1 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 5.0.0 + minipass-fetch: 3.0.4 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 10.0.5 + transitivePeerDependencies: + - supports-color + dev: false + /map-obj@1.0.1: resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} engines: {node: '>=0.10.0'} @@ -6710,7 +7395,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -6724,6 +7408,45 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + /minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: false + + /minipass-fetch@3.0.4: + resolution: {integrity: sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 7.0.3 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + dev: false + + /minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + dependencies: + minipass: 3.3.6 + dev: false + + /minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + dependencies: + minipass: 3.3.6 + dev: false + + /minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + dependencies: + minipass: 3.3.6 + dev: false + /minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} @@ -6739,7 +7462,6 @@ packages: /minipass@7.0.3: resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==} engines: {node: '>=16 || 14 >=14.17'} - dev: true /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -6770,6 +7492,19 @@ packages: hasBin: true dev: false + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} + dependencies: + acorn: 8.10.0 + pathe: 1.1.1 + pkg-types: 1.0.3 + ufo: 1.3.1 + dev: true + + /mmd-parser@1.0.4: + resolution: {integrity: sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==} + dev: false + /mojangson@2.0.4: resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==} dependencies: @@ -6822,6 +7557,11 @@ packages: stylis: 4.2.0 dev: false + /nanoid@3.3.6: + resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + /napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} dev: false @@ -6886,6 +7626,16 @@ packages: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} dev: false + /node-canvas-webgl@0.3.0: + resolution: {integrity: sha512-wDRCTEE2TCqKDeSef5cNgtLv8kgjRlnFN3aYgB9yni0kHRNnKBSSP93lT8VIV5GuMWiy5KNXBU0ilyWL5wTvFg==} + dependencies: + canvas: 2.11.2 + gl: 6.0.2 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -6904,10 +7654,37 @@ packages: dev: false optional: true + /node-gyp@9.4.0: + resolution: {integrity: sha512-dMXsYP6gc9rRbejLXmTbVRYjAHw7ppswsKyMxuxJxxOHzluIO1rGp9TOQgjFJ+2MCqcOcQTOPB/8Xwhr+7s4Eg==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 11.1.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.5.4 + tar: 6.2.0 + which: 2.0.2 + transitivePeerDependencies: + - supports-color + dev: false + /node-gzip@1.1.2: resolution: {integrity: sha512-ZB6zWpfZHGtxZnPMrJSKHVPrRjURoUzaDbLFj3VO70mpLTW5np96vXyHwft4Id0o+PYIzgDkBUjIzaNHhQ8srw==} dev: false + /node-html-parser@6.1.10: + resolution: {integrity: sha512-6/uWdWxjQWQ7tMcFK2wWlrflsQUzh1HsEzlIf2j5+TtzfhT2yUvg3DwZYAmjEHeR3uX74ko7exjHW69J0tOzIg==} + dependencies: + css-select: 5.1.0 + he: 1.2.0 + dev: true + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: false @@ -6932,6 +7709,14 @@ packages: abbrev: 1.1.1 dev: false + /nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + dependencies: + abbrev: 1.1.1 + dev: false + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -6982,6 +7767,22 @@ packages: set-blocking: 2.0.0 dev: false + /npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + dev: false + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -7108,6 +7909,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -7273,7 +8081,6 @@ packages: dependencies: lru-cache: 10.0.1 minipass: 7.0.3 - dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -7290,6 +8097,14 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: true + + /pathval@1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -7329,7 +8144,6 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: false /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -7357,6 +8171,14 @@ packages: pngjs: 3.4.0 dev: false + /pkg-types@1.0.3: + resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.4.2 + pathe: 1.1.1 + dev: true + /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -7382,6 +8204,18 @@ packages: - supports-color dev: true + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.6 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /potpack@1.0.2: + resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} + dev: false + /prebuild-install@7.1.1: resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} engines: {node: '>=10'} @@ -7421,6 +8255,15 @@ packages: engines: {node: ^14.13.1 || >=16.0.0} dev: false + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.2.0 + dev: true + /prismarine-auth@2.3.0: resolution: {integrity: sha512-giKZiHwuQdpMJ7KX94UncOJqM3u+yqKIR2UI/rqmdmFUuQilV9vhlz/zehpVkvo7FE8gmZsuUMCUPhI+gtgd3A==} dependencies: @@ -7547,6 +8390,14 @@ packages: engines: {node: '>= 0.8.0'} dev: false + /promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + dev: false + /promise@5.0.0: resolution: {integrity: sha512-N2BfLz0Sigf7rsm5NnItRwTNqEDUF2ephwEXTcOAf2cO9NwZ9TnIjOmnQNtC0r70CV0S1+uc9mSMmFH7gxk87Q==} dependencies: @@ -7753,6 +8604,10 @@ packages: /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: true + /react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} peerDependencies: @@ -7886,6 +8741,15 @@ packages: type-fest: 1.4.0 dev: true + /readable-stream@1.0.34: + resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 0.0.1 + string_decoder: 0.10.31 + dev: false + /readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: @@ -8034,6 +8898,10 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: false + /resolve@1.22.4: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true @@ -8062,6 +8930,11 @@ packages: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} + /retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + dev: false + /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -8119,6 +8992,13 @@ packages: fsevents: 2.3.3 dev: false + /rollup@3.29.4: + resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.3 + /rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} dependencies: @@ -8338,13 +9218,16 @@ packages: get-intrinsic: 1.2.1 object-inspect: 1.12.3 + /siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -8514,6 +9397,29 @@ packages: - utf-8-validate dev: false + /socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.4(supports-color@8.1.1) + socks: 2.7.1 + transitivePeerDependencies: + - supports-color + dev: false + + /socks@2.7.1: + resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} + engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} + dependencies: + ip: 2.0.0 + smart-buffer: 4.2.0 + dev: false + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: @@ -8570,10 +9476,6 @@ packages: resolution: {integrity: sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==} dev: true - /spiralloop@1.0.2: - resolution: {integrity: sha512-arrGOnli7tXoLWMqK8xXdV0qXLj3zd0OtgeME5SL/Nub0ssSYD27m9SlEsYcJGNi+VvEzVQIoBnAHOOhdZqc5Q==} - dev: false - /sshpk@1.17.0: resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} engines: {node: '>=0.10.0'} @@ -8589,12 +9491,23 @@ packages: safer-buffer: 2.1.2 tweetnacl: 0.14.5 + /ssri@10.0.5: + resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + minipass: 7.0.3 + dev: false + /stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} dependencies: stackframe: 1.3.4 dev: false + /stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + /stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} dev: false @@ -8627,6 +9540,10 @@ packages: engines: {node: '>= 0.8'} dev: false + /std-env@3.4.3: + resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} + dev: true + /stream-browserify@3.0.0: resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} dependencies: @@ -8649,7 +9566,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true /string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} @@ -8695,6 +9611,10 @@ packages: define-properties: 1.2.1 es-abstract: 1.22.2 + /string_decoder@0.10.31: + resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} + dev: false + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: @@ -8726,7 +9646,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} @@ -8766,6 +9685,12 @@ packages: engines: {node: '>=8'} dev: true + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} + dependencies: + acorn: 8.10.0 + dev: true + /stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: false @@ -8867,13 +9792,29 @@ packages: any-promise: 1.3.0 dev: false + /three-stdlib@2.26.11(three@0.128.0): + resolution: {integrity: sha512-/LxvLQIXAku2peNJCCFpCq/P7jGpLKFeAHKdSpHFqJjbElKTa+7EQg60NdL73lhegLtLz8oAsFONS9R2wb3RQg==} + peerDependencies: + three: '>=0.128.0' + dependencies: + '@types/draco3d': 1.4.6 + '@types/offscreencanvas': 2019.7.1 + '@types/webxr': 0.5.5 + draco3d: 1.5.6 + fflate: 0.6.10 + ktx-parse: 0.4.5 + mmd-parser: 1.0.4 + potpack: 1.0.2 + three: 0.128.0 + zstddec: 0.0.2 + dev: false + /three.meshline@1.4.0: resolution: {integrity: sha512-A8IsiMrWP8zmHisGDAJ76ZD7t/dOF/oCe/FUKNE6Bu01ZYEx8N6IlU/1Plb2aOZtAuWM2A8s8qS3hvY0OFuvOw==} dev: false /three@0.128.0: resolution: {integrity: sha512-i0ap/E+OaSfzw7bD1TtYnPo3VEplkl70WX5fZqZnfZsE3k3aSFudqrrC9ldFZfYFkn1zwDmBcdGfiIm/hnbyZA==} - dev: true /throttle-debounce@3.0.1: resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} @@ -8883,6 +9824,13 @@ packages: /throttleit@1.0.0: resolution: {integrity: sha512-rkTVqu6IjfQ/6+uNuuc3sZek4CEYxTJom3IktzgdSxcZqdARuebbA/f4QmAxMQIxqq9ZLEUkSYqvuk1I6VKq4g==} + /through2@0.6.5: + resolution: {integrity: sha512-RkK/CCESdTKQZHdmKICijdKKsCRVHs5KsLZ6pACAmF/1GPUQhonHSXWNERctxEp7RmvjdNbZTL5z9V7nSCXKcg==} + dependencies: + readable-stream: 1.0.34 + xtend: 4.0.2 + dev: false + /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -8897,10 +9845,24 @@ packages: resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==} dev: false + /tinybench@2.5.1: + resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} + dev: true + /tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} dev: false + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} + engines: {node: '>=14.0.0'} + dev: true + /tmp@0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} engines: {node: '>=8.17.0'} @@ -8986,6 +9948,17 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + /tsx@3.13.0: + resolution: {integrity: sha512-rjmRpTu3as/5fjNq/kOkOtihgLxuIz6pbKdj9xwP4J5jOLkBxw/rjN5ANw+KyrrOXV5uB7HC8+SrrSJxT65y+A==} + hasBin: true + dependencies: + esbuild: 0.18.20 + get-tsconfig: 4.7.2 + source-map-support: 0.5.21 + optionalDependencies: + fsevents: 2.3.3 + dev: false + /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -9001,6 +9974,11 @@ packages: prelude-ls: 1.2.1 dev: true + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true + /type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -9092,6 +10070,10 @@ packages: hasBin: true dev: true + /ufo@1.3.1: + resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} + dev: true + /uint4@0.1.2: resolution: {integrity: sha512-lhEx78gdTwFWG+mt6cWAZD/R6qrIj0TTBeH5xwyuDJyswLNlGe+KVlUPQ6+mx5Ld332pS0AMUTo9hIly7YsWxQ==} @@ -9103,6 +10085,13 @@ packages: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + /undici@5.25.4: + resolution: {integrity: sha512-450yJxT29qKMf3aoudzFpIciqpx6Pji3hEWaXqXmanbXF58LTAGCKxcJjxMXWu3iG+Mudgo3ZUfDB6YDFd/dAw==} + engines: {node: '>=14.0'} + dependencies: + '@fastify/busboy': 2.0.0 + dev: true + /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -9139,6 +10128,20 @@ packages: qs: 6.11.2 dev: true + /unique-filename@3.0.0: + resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + unique-slug: 4.0.0 + dev: false + + /unique-slug@4.0.0: + resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + dependencies: + imurmurhash: 0.1.4 + dev: false + /unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -9346,6 +10349,128 @@ packages: core-util-is: 1.0.2 extsprintf: 1.3.0 + /vite-node@0.34.6(@types/node@20.8.0): + resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} + engines: {node: '>=v14.18.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@8.1.1) + mlly: 1.4.2 + pathe: 1.1.1 + picocolors: 1.0.0 + vite: 4.4.10(@types/node@20.8.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite@4.4.10(@types/node@20.8.0): + resolution: {integrity: sha512-TzIjiqx9BEXF8yzYdF2NTf1kFFbjMjUSV0LFZ3HyHoI3SGSPLnnFUKiIQtL3gl2AjHvMrprOvQ3amzaHgQlAxw==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.8.0 + esbuild: 0.18.20 + postcss: 8.4.31 + rollup: 3.29.4 + optionalDependencies: + fsevents: 2.3.3 + + /vitest@0.34.6: + resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} + engines: {node: '>=v14.18.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + playwright: '*' + safaridriver: '*' + webdriverio: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + dependencies: + '@types/chai': 4.3.6 + '@types/chai-subset': 1.3.3 + '@types/node': 20.8.0 + '@vitest/expect': 0.34.6 + '@vitest/runner': 0.34.6 + '@vitest/snapshot': 0.34.6 + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + acorn: 8.10.0 + acorn-walk: 8.2.0 + cac: 6.7.14 + chai: 4.3.10 + debug: 4.3.4(supports-color@8.1.1) + local-pkg: 0.4.3 + magic-string: 0.30.4 + pathe: 1.1.1 + picocolors: 1.0.0 + std-env: 3.4.3 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.7.0 + vite: 4.4.10(@types/node@20.8.0) + vite-node: 0.34.6(@types/node@20.8.0) + why-is-node-running: 2.2.2 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + /warning@4.0.3: resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} dependencies: @@ -9451,6 +10576,15 @@ packages: dependencies: isexe: 2.0.0 + /why-is-node-running@2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: @@ -9626,7 +10760,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -9783,6 +10916,15 @@ packages: engines: {node: '>=10'} dev: true + /yocto-queue@1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + + /zstddec@0.0.2: + resolution: {integrity: sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==} + dev: false + /zustand@3.6.5(react@18.2.0): resolution: {integrity: sha512-/WfLJuXiEJimt61KGMHebrFBwckkCHGhAgVXTgPQHl6IMzjqm6MREb1OnDSnCRiSmRdhgdFCctceg6tSm79hiw==} engines: {node: '>=12.7.0'} @@ -9912,8 +11054,8 @@ packages: dependencies: vec3: 0.1.8 - github.com/zardoy/prismarinejs-net-browserify/d63bd1df82186cf2f0baa161a83754037d537282: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/d63bd1df82186cf2f0baa161a83754037d537282} + github.com/zardoy/prismarinejs-net-browserify/f88123ad4f4407ac21fc435b11c561742825d0a7: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/f88123ad4f4407ac21fc435b11c561742825d0a7} name: net-browserify version: 0.2.4 dependencies: @@ -9926,8 +11068,8 @@ packages: - utf-8-validate dev: false - github.com/zardoy/space-squid/eec886b7a881eb40dd8efc6d48fa52e638e07693: - resolution: {tarball: https://codeload.github.com/zardoy/space-squid/tar.gz/eec886b7a881eb40dd8efc6d48fa52e638e07693} + github.com/zardoy/space-squid/458eee79e4ff20fccdff8027d3aae16161b9fb1c: + resolution: {tarball: https://codeload.github.com/zardoy/space-squid/tar.gz/458eee79e4ff20fccdff8027d3aae16161b9fb1c} name: flying-squid version: 1.5.0 engines: {node: '>=8'} @@ -9956,7 +11098,6 @@ packages: random-seed: 0.3.0 range: 0.0.3 readline: 1.3.0 - spiralloop: 1.0.2 uuid-1345: 1.0.2 vec3: 0.1.8 yargs: 17.7.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8fc6b39ce..131aadfec 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: - "." - "prismarine-viewer" + - "prismarine-viewer/viewer/sign-renderer/" diff --git a/prismarine-viewer/buildWorker.mjs b/prismarine-viewer/buildWorker.mjs index d0501e9d9..291af4bab 100644 --- a/prismarine-viewer/buildWorker.mjs +++ b/prismarine-viewer/buildWorker.mjs @@ -24,9 +24,10 @@ const buildOptions = { drop: [ 'debugger' ], - sourcemap: true, + sourcemap: 'linked', write: false, metafile: true, + outdir: path.join(__dirname, './public'), plugins: [ { name: 'external-json', @@ -101,15 +102,14 @@ const buildOptions = { build.onEnd(({metafile, outputFiles}) => { if (!metafile) return fs.writeFileSync(path.join(__dirname, './public/metafile.json'), JSON.stringify(metafile)) - for (const outPath of ['../dist/worker.js', './public/worker.js']) { + for (const outDir of ['../dist/', './public/']) { for (const outputFile of outputFiles) { - if (outPath === '../dist/worker.js' && outputFile.path.endsWith('.map')) { - // skip writing & browser loading sourcemap there, debugging should be done in playground + if (outDir === '../dist/' && outputFile.path.endsWith('.map')) { + // skip writing & browser loading sourcemap there, worker debugging should be done in playground continue } - const savePath = path.join(__dirname, outPath); - fs.mkdirSync(path.dirname(savePath), { recursive: true }) - fs.writeFileSync(savePath, outputFile.text) + fs.mkdirSync(outDir, { recursive: true }) + fs.writeFileSync(path.join(__dirname, outDir, path.basename(outputFile.path)), outputFile.text) } } }) diff --git a/prismarine-viewer/esbuild.mjs b/prismarine-viewer/esbuild.mjs index 725349c30..ee6ffc145 100644 --- a/prismarine-viewer/esbuild.mjs +++ b/prismarine-viewer/esbuild.mjs @@ -1,4 +1,5 @@ import * as fs from 'fs' +import fsExtra from 'fs-extra' //@ts-check import * as esbuild from 'esbuild' @@ -6,10 +7,21 @@ import { polyfillNode } from 'esbuild-plugin-polyfill-node' import path, { dirname, join } from 'path' import { fileURLToPath } from 'url' -const dev = !process.argv.includes('-p') +const dev = process.argv.includes('-w') const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url))) +const mcDataPath = join(__dirname, '../dist/mc-data') +if (!fs.existsSync(mcDataPath)) { + // shouldn't it be in the viewer instead? + await import('../scripts/prepareData.mjs') +} + +fs.mkdirSync(join(__dirname, 'public'), { recursive: true }) +fs.copyFileSync(join(__dirname, 'playground.html'), join(__dirname, 'public/index.html')) +fsExtra.copySync(mcDataPath, join(__dirname, 'public/mc-data')) +const availableVersions = fs.readdirSync(mcDataPath).map(ver => ver.replace('.js', '')) + /** @type {import('esbuild').BuildOptions} */ const buildOptions = { bundle: true, @@ -19,13 +31,14 @@ const buildOptions = { logLevel: 'info', platform: 'browser', sourcemap: dev ? 'inline' : false, - outfile: join(__dirname, 'public/index.js'), + minify: !dev, + outfile: join(__dirname, 'public/playground.js'), mainFields: [ 'browser', 'module', 'main' ], keepNames: true, banner: { - js: 'globalThis.global = globalThis;', + js: `globalThis.global = globalThis;globalThis.includedVersions = ${JSON.stringify(availableVersions)};`, }, alias: { events: 'events', @@ -39,60 +52,20 @@ const buildOptions = { metafile: true, plugins: [ { - name: 'data-handler', + name: 'minecraft-data', setup (build) { - const customMcDataNs = 'custom-mc' - build.onResolve({ - filter: /.*/, - }, ({ path, ...rest }) => { - if (join(rest.resolveDir, path).replaceAll('\\', '/').endsWith('minecraft-data/data.js')) { - return { - path, - namespace: customMcDataNs, - } - } - return undefined - }) build.onLoad({ - filter: /.*/, - namespace: customMcDataNs, - }, async ({ path, ...rest }) => { - const resolvedPath = await build.resolve('minecraft-data/minecraft-data/data/dataPaths.json', { kind: 'require-call', resolveDir: process.cwd() }) - const dataPaths = JSON.parse(await fs.promises.readFile(resolvedPath.path, 'utf8')) - - // bedrock unsupported - delete dataPaths.bedrock - - const allowOnlyList = process.env.ONLY_MC_DATA?.split(',') ?? [] - - const includeVersions = ['1.20.1', '1.18.1'] - - const includedVersions = [] - let contents = `module.exports =\n{\n` - for (const platform of Object.keys(dataPaths)) { - contents += ` '${platform}': {\n` - for (const version of Object.keys(dataPaths[platform])) { - if (allowOnlyList.length && !allowOnlyList.includes(version)) continue - if (!includeVersions.includes(version)) continue - - includedVersions.push(version) - contents += ` '${version}': {\n` - for (const dataType of Object.keys(dataPaths[platform][version])) { - const loc = `minecraft-data/data/${dataPaths[platform][version][dataType]}/` - contents += ` get ${dataType} () { return require("./${loc}${dataType}.json") },\n` - } - contents += ' },\n' - } - contents += ' },\n' - } - contents += `}\n;globalThis.includedVersions = ${JSON.stringify(includedVersions)};` - + filter: /minecraft-data[\/\\]data.js$/, + }, () => { + const defaultVersionsObj = {} return { - contents, + contents: `window.mcData ??= ${JSON.stringify(defaultVersionsObj)};module.exports = { pc: window.mcData }`, loader: 'js', - resolveDir: join(dirname(resolvedPath.path), '../..'), } }) + build.onEnd((e) => { + fs.writeFileSync(join(__dirname, 'public/metafile.json'), JSON.stringify(e.metafile), 'utf8') + }) } }, polyfillNode({ @@ -109,7 +82,7 @@ const buildOptions = { }) ], } -if (process.argv.includes('-w')) { +if (dev) { (await esbuild.context(buildOptions)).watch() } else { await esbuild.build(buildOptions) diff --git a/prismarine-viewer/examples/playground.js b/prismarine-viewer/examples/playground.js index fbb2308f0..73a341eec 100644 --- a/prismarine-viewer/examples/playground.js +++ b/prismarine-viewer/examples/playground.js @@ -1,6 +1,7 @@ //@ts-check /* global THREE, fetch */ -const { WorldView, Viewer, MapControls } = require('../viewer') +const _ = require('lodash') +const { WorldDataEmitter, Viewer, MapControls } = require('../viewer') const { Vec3 } = require('vec3') const { Schematic } = require('prismarine-schematic') const BlockLoader = require('prismarine-block') @@ -9,17 +10,27 @@ const BlockLoader = require('prismarine-block') const ChunkLoader = require('prismarine-chunk') /** @type {import('prismarine-world')['default']} */ //@ts-ignore -const WorldLoader = require('prismarine-world'); +const WorldLoader = require('prismarine-world') const THREE = require('three') -const {GUI} = require('lil-gui') -global.THREE = THREE +const { GUI } = require('lil-gui') +const { toMajor } = require('../viewer/lib/version') +const { loadScript } = require('../viewer/lib/utils') +globalThis.THREE = THREE +//@ts-ignore +require('three/examples/js/controls/OrbitControls') const gui = new GUI() // initial values const params = { skip: '', - version: globalThis.includedVersions[0], + version: globalThis.includedVersions.sort((a, b) => { + const s = (x) => { + const parts = x.split('.') + return +parts[0] + (+parts[1]) + } + return s(a) - s(b) + }).at(-1), block: '', metadata: 0, supportBlock: false, @@ -28,6 +39,7 @@ const params = { this.entity = '' }, entityRotate: false, + camera: '' } const qs = new URLSearchParams(window.location.search) @@ -47,8 +59,24 @@ const setQs = () => { async function main () { const { version } = params + // temporary solution until web worker is here, cache data for faster reloads + const globalMcData = window['mcData'] + if (!globalMcData['version']) { + const major = toMajor(version) + const sessionKey = `mcData-${major}` + if (sessionStorage[sessionKey]) { + Object.assign(globalMcData, JSON.parse(sessionStorage[sessionKey])) + } else { + if (sessionStorage.length > 1) sessionStorage.clear() + await loadScript(`./mc-data/${major}.js`) + try { + sessionStorage[sessionKey] = JSON.stringify(Object.fromEntries(Object.entries(globalMcData).filter(([ver]) => ver.startsWith(major)))) + } catch { } + } + } + const mcData = require('minecraft-data')(version) - window['mcData'] = mcData + window['loadedData'] = mcData gui.add(params, 'version', globalThis.includedVersions) gui.add(params, 'block', mcData.blocksArray.map(b => b.name)) @@ -66,21 +94,31 @@ async function main () { // const data = await fetch('smallhouse1.schem').then(r => r.arrayBuffer()) // const schem = await Schematic.read(Buffer.from(data), version) - const viewDistance = 1 + const viewDistance = 2 const center = new Vec3(0, 90, 0) const World = WorldLoader(version) // const diamondSquare = require('diamond-square')({ version, seed: Math.floor(Math.random() * Math.pow(2, 31)) }) - const targetBlockPos = center + + const targetPos = center + //@ts-ignore + const chunk1 = new Chunk() + //@ts-ignore + const chunk2 = new Chunk() + chunk1.setBlockStateId(center, 34) + chunk2.setBlockStateId(center.offset(1, 0, 0), 34) const world = new World((chunkX, chunkZ) => { + // if (chunkX === 0 && chunkZ === 0) return chunk1 + // if (chunkX === 1 && chunkZ === 0) return chunk2 //@ts-ignore - return new Chunk() + const chunk = new Chunk() + return chunk }) // await schem.paste(world, new Vec3(0, 60, 0)) - const worldView = new WorldView(world, viewDistance, center) + const worldView = new WorldDataEmitter(world, viewDistance, center) // Create three.js context, add to page const renderer = new THREE.WebGLRenderer() @@ -91,29 +129,34 @@ async function main () { // Create viewer const viewer = new Viewer(renderer) viewer.setVersion(version) + viewer.listen(worldView) - // Initialize viewer, load chunks + // Load chunks worldView.init(center) window['worldView'] = worldView window['viewer'] = viewer - // const controls = new MapControls(viewer.camera, renderer.domElement) - // controls.update() + //@ts-ignore + const controls = new THREE.OrbitControls(viewer.camera, renderer.domElement) + controls.target.set(center.x + 0.5, center.y + 0.5, center.z + 0.5) const cameraPos = center.offset(2, 2, 2) const pitch = THREE.MathUtils.degToRad(-45) const yaw = THREE.MathUtils.degToRad(45) viewer.camera.rotation.set(pitch, yaw, 0, 'ZYX') + viewer.camera.lookAt(center.x + 0.5, center.y + 0.5, center.z + 0.5) viewer.camera.position.set(cameraPos.x + 0.5, cameraPos.y + 0.5, cameraPos.z + 0.5) + controls.update() let blockProps = {} - const getBlock = () => { + const getBlock = () => { return mcData.blocksByName[params.block || 'air'] } const onUpdate = { - block() { - const {states} = mcData.blocksByStateId[getBlock()?.minStateId] ?? {} + block () { + //@ts-ignore + const { states } = mcData.blocksByStateId[getBlock()?.minStateId] ?? {} folder.destroy() if (!states) { return @@ -124,16 +167,16 @@ async function main () { switch (state.type) { case 'enum': defaultValue = state.values[0] - break; + break case 'bool': defaultValue = false - break; + break case 'int': defaultValue = 0 - break; + break case 'direction': defaultValue = 'north' - break; + break default: continue @@ -151,25 +194,42 @@ async function main () { viewer.entities.clear() if (!params.entity) return worldView.emit('entity', { - id: 'id', name: params.entity, pos: targetBlockPos.offset(0, 1, 0), width: 1, height: 1, username: 'username' + id: 'id', name: params.entity, pos: targetPos.offset(0, 1, 0), width: 1, height: 1, username: 'username' }) } } - const applyChanges = () => { + const applyChanges = (metadataUpdate = false) => { + const blockId = getBlock()?.id + /** @type {BlockLoader.Block} */ + let block + if (metadataUpdate) { + block = new Block(blockId, 0, params.metadata) + Object.assign(blockProps, block.getProperties()) + for (const _child of folder.children) { + /** @type {import('lil-gui').Controller} */ + //@ts-ignore + const child = _child + child.updateDisplay() + } + } else { + //@ts-ignore + block = Block.fromProperties(blockId ?? -1, blockProps, 0) + } + //@ts-ignore - const block = Block.fromProperties(getBlock()?.id ?? -1, blockProps, 0) - viewer.setBlockStateId(targetBlockPos, block.stateId) + viewer.setBlockStateId(targetPos, block.stateId) console.log('up', block.stateId) params.metadata = block.metadata metadataGui.updateDisplay() - viewer.setBlockStateId(targetBlockPos.offset(0, -1, 0), params.supportBlock ? 1 : 0) + viewer.setBlockStateId(targetPos.offset(0, -1, 0), params.supportBlock ? 1 : 0) setQs() } - gui.onChange(({property}) => { + gui.onChange(({ property }) => { + if (property === 'camera') return onUpdate[property]?.() - applyChanges() + applyChanges(property === 'metadata') }) viewer.waitForChunksToRender().then(async () => { await new Promise(resolve => { @@ -178,7 +238,7 @@ async function main () { for (const update of Object.values(onUpdate)) { update() } - applyChanges() + applyChanges(true) gui.openAnimated() }) @@ -187,7 +247,7 @@ async function main () { // if (controls) controls.update() // worldView.updatePosition(controls.target) viewer.update() - renderer.render(viewer.scene, viewer.camera) + viewer.render() // window.requestAnimationFrame(animate) } viewer.world.renderUpdateEmitter.addListener('update', () => { @@ -195,6 +255,39 @@ async function main () { }) animate() + // #region camera rotation param + if (params.camera) { + const [x, y] = params.camera.split(',') + viewer.camera.rotation.set(parseFloat(x), parseFloat(y), 0, 'ZYX') + controls.update() + console.log(viewer.camera.rotation.x, parseFloat(x)) + } + const throttledCamQsUpdate = _.throttle(() => { + const { camera } = viewer + // params.camera = `${camera.rotation.x.toFixed(2)},${camera.rotation.y.toFixed(2)}` + setQs() + }, 200) + controls.addEventListener('change', () => { + throttledCamQsUpdate() + animate() + }) + // #endregion + + window.onresize = () => { + // const vec3 = new THREE.Vector3() + // vec3.set(-1, -1, -1).unproject(viewer.camera) + // console.log(vec3) + // box.position.set(vec3.x, vec3.y, vec3.z-1) + + const { camera } = viewer + viewer.camera.aspect = window.innerWidth / window.innerHeight + viewer.camera.updateProjectionMatrix() + renderer.setSize(window.innerWidth, window.innerHeight) + + animate() + } + window.dispatchEvent(new Event('resize')) + setTimeout(() => { // worldView.emit('entity', { // id: 'id', name: 'player', pos: center.offset(1, -2, 0), width: 1, height: 1, username: 'username' diff --git a/prismarine-viewer/index.d.ts b/prismarine-viewer/index.d.ts index 4a39dc757..543fa225d 100644 --- a/prismarine-viewer/index.d.ts +++ b/prismarine-viewer/index.d.ts @@ -28,7 +28,7 @@ export function headless(bot: Bot, settings: { export const viewer: { Viewer: any; - WorldView: any; + WorldDataEmitter: any; MapControls: any; Entitiy: any; getBufferFromStream: (stream: any) => Promise; diff --git a/prismarine-viewer/lib/headless.js b/prismarine-viewer/lib/headless.js index 609426728..5c1631007 100644 --- a/prismarine-viewer/lib/headless.js +++ b/prismarine-viewer/lib/headless.js @@ -12,7 +12,7 @@ global.THREE = require('three') global.Worker = require('worker_threads').Worker const { createCanvas } = safeRequire('node-canvas-webgl/lib') -const { WorldView, Viewer, getBufferFromStream } = require('../viewer') +const { WorldDataEmitter, Viewer, getBufferFromStream } = require('../viewer') module.exports = (bot, { viewDistance = 6, output = 'output.mp4', frames = -1, width = 512, height = 512, logFFMPEG = false, jpegOptions }) => { const canvas = createCanvas(width, height) @@ -23,7 +23,7 @@ module.exports = (bot, { viewDistance = 6, output = 'output.mp4', frames = -1, w viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) // Load world - const worldView = new WorldView(bot.world, viewDistance, bot.entity.position) + const worldView = new WorldDataEmitter(bot.world, viewDistance, bot.entity.position) viewer.listen(worldView) worldView.init(bot.entity.position) diff --git a/prismarine-viewer/lib/mineflayer.js b/prismarine-viewer/lib/mineflayer.js index 06634bf64..1735371e8 100644 --- a/prismarine-viewer/lib/mineflayer.js +++ b/prismarine-viewer/lib/mineflayer.js @@ -1,5 +1,5 @@ const EventEmitter = require('events') -const { WorldView } = require('../viewer') +const { WorldDataEmitter } = require('../viewer') module.exports = (bot, { viewDistance = 6, firstPerson = false, port = 3000, prefix = '' }) => { const express = require('express') @@ -49,7 +49,7 @@ module.exports = (bot, { viewDistance = 6, firstPerson = false, port = 3000, pre socket.emit('version', bot.version) sockets.push(socket) - const worldView = new WorldView(bot.world, viewDistance, bot.entity.position, socket) + const worldView = new WorldDataEmitter(bot.world, viewDistance, bot.entity.position, socket) worldView.init(bot.entity.position) worldView.on('blockClicked', (block, face, button) => { diff --git a/prismarine-viewer/package.json b/prismarine-viewer/package.json index 6fb400750..1d6d3b1c0 100644 --- a/prismarine-viewer/package.json +++ b/prismarine-viewer/package.json @@ -8,7 +8,8 @@ "pretest": "npm run lint", "lint": "standard", "fix": "standard --fix", - "postinstall": "node viewer/generateTextures.js && node buildWorker.mjs" + "postinstall": "pnpm generate-textures && node buildWorker.mjs", + "generate-textures": "tsx viewer/prepare/generateTextures.ts" }, "author": "PrismarineJS", "license": "MIT", @@ -22,6 +23,8 @@ }, "dependencies": { "@tweenjs/tween.js": "^20.0.3", + "three-stdlib": "^2.26.11", + "tsx": "^3.13.0", "assert": "^2.0.0", "buffer": "^6.0.3", "canvas": "^2.11.2", @@ -39,6 +42,7 @@ "socket.io": "^4.0.0", "socket.io-client": "^4.0.0", "three.meshline": "^1.3.0", - "vec3": "^0.1.7" + "vec3": "^0.1.7", + "node-canvas-webgl": "^0.3.0" } } diff --git a/prismarine-viewer/playground.html b/prismarine-viewer/playground.html new file mode 100644 index 000000000..dfd2d701c --- /dev/null +++ b/prismarine-viewer/playground.html @@ -0,0 +1,34 @@ + + + + Prismarine Viewer + + + + + + diff --git a/prismarine-viewer/viewer/generateTextures.js b/prismarine-viewer/viewer/generateTextures.js deleted file mode 100644 index 796c56527..000000000 --- a/prismarine-viewer/viewer/generateTextures.js +++ /dev/null @@ -1,31 +0,0 @@ -const path = require('path') -const { makeTextureAtlas } = require('./lib/atlas') -const { prepareBlocksStates } = require('./lib/modelsBuilder') -const mcAssets = require('minecraft-assets') -const fs = require('fs-extra') - -const texturesPath = path.resolve(__dirname, '../public/textures') -if (fs.existsSync(texturesPath) && !process.argv.includes('-f')) { - console.log('textures folder already exists, skipping...') - process.exit(0) -} -fs.mkdirSync(texturesPath, { recursive: true }) - -const blockStatesPath = path.resolve(__dirname, '../public/blocksStates') -fs.mkdirSync(blockStatesPath, { recursive: true }) - -for (const version of mcAssets.versions) { - const assets = mcAssets(version) - const atlas = makeTextureAtlas(assets) - const out = fs.createWriteStream(path.resolve(texturesPath, version + '.png')) - const stream = atlas.canvas.pngStream() - stream.on('data', (chunk) => out.write(chunk)) - stream.on('end', () => console.log('Generated textures/' + version + '.png')) - - const blocksStates = JSON.stringify(prepareBlocksStates(assets, atlas)) - fs.writeFileSync(path.resolve(blockStatesPath, version + '.json'), blocksStates) - - fs.copySync(assets.directory, path.resolve(texturesPath, version), { overwrite: true }) -} - -fs.writeFileSync(path.resolve(__dirname, '../public/supportedVersions.json'), '[' + mcAssets.versions.map(v => `"${v}"`).toString() + ']') diff --git a/prismarine-viewer/viewer/index.js b/prismarine-viewer/viewer/index.js index 091166426..36cfd94b2 100644 --- a/prismarine-viewer/viewer/index.js +++ b/prismarine-viewer/viewer/index.js @@ -1,6 +1,6 @@ module.exports = { Viewer: require('./lib/viewer').Viewer, - WorldView: require('./lib/worldView').WorldView, + WorldDataEmitter: require('./lib/worldDataEmitter').WorldDataEmitter, MapControls: require('./lib/controls').MapControls, Entity: require('./lib/entity/Entity'), getBufferFromStream: require('./lib/simpleUtils').getBufferFromStream diff --git a/prismarine-viewer/viewer/lib/atlas.js b/prismarine-viewer/viewer/lib/atlas.js deleted file mode 100644 index d36ccca30..000000000 --- a/prismarine-viewer/viewer/lib/atlas.js +++ /dev/null @@ -1,56 +0,0 @@ -const fs = require('fs') -const { Canvas, Image } = require('canvas') -const path = require('path') - -function nextPowerOfTwo (n) { - if (n === 0) return 1 - n-- - n |= n >> 1 - n |= n >> 2 - n |= n >> 4 - n |= n >> 8 - n |= n >> 16 - return n + 1 -} - -function readTexture (basePath, name) { - if (name === 'missing_texture.png') { - // grab ./missing_texture.png - basePath = __dirname - } - return fs.readFileSync(path.join(basePath, name), 'base64') -} - -function makeTextureAtlas (mcAssets) { - const blocksTexturePath = path.join(mcAssets.directory, '/blocks') - const textureFiles = fs.readdirSync(blocksTexturePath).filter(file => file.endsWith('.png')) - textureFiles.unshift('missing_texture.png') - - const texSize = nextPowerOfTwo(Math.ceil(Math.sqrt(textureFiles.length))) - const tileSize = 16 - - const imgSize = texSize * tileSize - const canvas = new Canvas(imgSize, imgSize, 'png') - const g = canvas.getContext('2d') - - const texturesIndex = {} - - for (const i in textureFiles) { - const x = (i % texSize) * tileSize - const y = Math.floor(i / texSize) * tileSize - - const name = textureFiles[i].split('.')[0] - - texturesIndex[name] = { u: x / imgSize, v: y / imgSize, su: tileSize / imgSize, sv: tileSize / imgSize } - - const img = new Image() - img.src = 'data:image/png;base64,' + readTexture(blocksTexturePath, textureFiles[i]) - g.drawImage(img, 0, 0, 16, 16, x, y, 16, 16) - } - - return { image: canvas.toBuffer(), canvas, json: { size: tileSize / imgSize, textures: texturesIndex } } -} - -module.exports = { - makeTextureAtlas -} diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index 9c2c0029c..ed9459d90 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -5,26 +5,26 @@ const Entity = require('./entity/Entity') const { dispose3 } = require('./dispose') function getUsernameTexture(username, { fontFamily = 'sans-serif' }) { -const canvas = document.createElement('canvas'); -const ctx = canvas.getContext('2d'); + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') -const fontSize = 50; -const padding = 5 -ctx.font = `${fontSize}px ${fontFamily}`; + const fontSize = 50 + const padding = 5 + ctx.font = `${fontSize}px ${fontFamily}` -const textWidth = ctx.measureText(username).width + padding * 2; + const textWidth = ctx.measureText(username).width + padding * 2 -canvas.width = textWidth; -canvas.height = fontSize + padding * 2; + canvas.width = textWidth + canvas.height = fontSize + padding * 2 -ctx.fillStyle = 'rgba(0, 0, 0, 0.3)'; -ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = 'rgba(0, 0, 0, 0.3)' + ctx.fillRect(0, 0, canvas.width, canvas.height) -ctx.font = `${fontSize}px ${fontFamily}`; -ctx.fillStyle = 'white' -ctx.fillText(username, padding, fontSize); + ctx.font = `${fontSize}px ${fontFamily}` + ctx.fillStyle = 'white' + ctx.fillText(username, padding, fontSize) -return canvas; + return canvas } function getEntityMesh (entity, scene, options) { @@ -33,7 +33,7 @@ function getEntityMesh (entity, scene, options) { const e = new Entity('1.16.4', entity.name, scene) if (entity.username !== undefined) { - const canvas = getUsernameTexture(entity.username, options); + const canvas = getUsernameTexture(entity.username, options) const tex = new THREE.Texture(canvas) tex.needsUpdate = true const spriteMat = new THREE.SpriteMaterial({ map: tex }) @@ -51,7 +51,7 @@ function getEntityMesh (entity, scene, options) { const geometry = new THREE.BoxGeometry(entity.width, entity.height, entity.width) geometry.translate(0, entity.height / 2, 0) - const material = new THREE.MeshBasicMaterial({ color: 0xff00ff }) + const material = new THREE.MeshBasicMaterial({ color: 0xff_00_ff }) const cube = new THREE.Mesh(geometry, material) return cube } diff --git a/prismarine-viewer/viewer/lib/models.js b/prismarine-viewer/viewer/lib/models.ts similarity index 91% rename from prismarine-viewer/viewer/lib/models.js rename to prismarine-viewer/viewer/lib/models.ts index 0f6e2f077..f931ebd33 100644 --- a/prismarine-viewer/viewer/lib/models.js +++ b/prismarine-viewer/viewer/lib/models.ts @@ -1,6 +1,10 @@ -const { Vec3 } = require('vec3') +//@ts-nocheck +import { Vec3 } from 'vec3' +import { BlockStatesOutput } from '../prepare/modelsBuilder' +import { World } from './world' const tints = {} +let blockStates: BlockStatesOutput const tintsData = require('esbuild-data').tints for (const key of Object.keys(tintsData)) { @@ -131,7 +135,6 @@ function renderLiquid (world, cursor, texture, type, biome, water, attr) { if (!neighbor) continue if (neighbor.type === type) continue if ((neighbor.isCube && !isUp) || neighbor.material === 'plant' || neighbor.getProperties().waterlogged) continue - if (neighbor.position.y < 0) continue let tint = [1, 1, 1] if (water) { @@ -239,10 +242,10 @@ function renderElement (world, cursor, element, doAO, attr, globalMatrix, global if (eFace.cullface) { const neighbor = world.getBlock(cursor.plus(new Vec3(...dir))) - if (!neighbor) continue - if (cullIfIdentical && neighbor.type === block.type) continue - if (!neighbor.transparent && neighbor.isCube) continue - if (neighbor.position.y < 0) continue + if (neighbor) { + if (cullIfIdentical && neighbor.type === block.type) continue + if (!neighbor.transparent && neighbor.isCube) continue + } } const minx = element.from[0] @@ -363,7 +366,7 @@ function renderElement (world, cursor, element, doAO, attr, globalMatrix, global } } -function getSectionGeometry (sx, sy, sz, world, blocksStates) { +export function getSectionGeometry (sx, sy, sz, world: World) { const attr = { sx: sx + 8, sy: sy + 8, @@ -376,7 +379,9 @@ function getSectionGeometry (sx, sy, sz, world, blocksStates) { t_normals: [], t_colors: [], t_uvs: [], - indices: [] + indices: [], + // todo this can be removed here + signs: {} } const cursor = new Vec3(0, 0, 0) @@ -384,9 +389,24 @@ function getSectionGeometry (sx, sy, sz, world, blocksStates) { for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) { for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) { const block = world.getBlock(cursor) + if (block.name.includes('sign')) { + const key = `${cursor.x},${cursor.y},${cursor.z}` + const props = block.getProperties(); + const facingRotationMap = { + "north": 2, + "south": 0, + "west": 1, + "east": 3 + } + const isWall = block.name.endsWith('wall_sign') || block.name.endsWith('hanging_sign'); + attr.signs[key] = { + isWall, + rotation: isWall ? facingRotationMap[props.facing] : +props.rotation + } + } const biome = block.biome.name if (block.variant === undefined) { - block.variant = getModelVariants(block, blocksStates) + block.variant = getModelVariants(block) } for (const variant of block.variant) { @@ -478,9 +498,9 @@ function matchProperties (block, properties) { return true } -function getModelVariants (block, blockStates) { +function getModelVariants (block) { // air, cave_air, void_air and so on... - if (block.name.includes('air')) return [] + if (block.name === 'air' || block.name.endsWith('_air')) return [] const state = blockStates[block.name] ?? blockStates.missing_texture if (!state) return [] if (state.variants) { @@ -494,11 +514,7 @@ function getModelVariants (block, blockStates) { const parts = state.multipart.filter(multipart => matchProperties(block, multipart.when)) let variants = [] for (const part of parts) { - if (part.apply instanceof Array) { - variants = [...variants, ...part.apply] - } else { - variants = [...variants, part.apply] - } + variants = [...variants, ...Array.isArray(part.apply) ? part.apply : [part.apply]]; } return variants @@ -507,4 +523,6 @@ function getModelVariants (block, blockStates) { return [] } -module.exports = { getSectionGeometry } +export const setBlockStates = (_blockStates: BlockStatesOutput | null) => { + blockStates = _blockStates! +} diff --git a/prismarine-viewer/viewer/lib/modelsBuilder.js b/prismarine-viewer/viewer/lib/modelsBuilder.js deleted file mode 100644 index 58aca167a..000000000 --- a/prismarine-viewer/viewer/lib/modelsBuilder.js +++ /dev/null @@ -1,139 +0,0 @@ -function cleanupBlockName (name) { - if (name.startsWith('block') || name.startsWith('minecraft:block')) return name.split('/')[1] - return name -} - -function getModel (name, blocksModels) { - name = cleanupBlockName(name) - const data = blocksModels[name] - if (!data) { - return null - } - - let model = { textures: {}, elements: [], ao: true } - - for (const axis in ['x', 'y', 'z']) { - if (axis in data) { - model[axis] = data[axis] - } - } - - if (data.parent) { - model = getModel(data.parent, blocksModels) - } - if (data.textures) { - Object.assign(model.textures, JSON.parse(JSON.stringify(data.textures))) - } - if (data.elements) { - model.elements = JSON.parse(JSON.stringify(data.elements)) - } - if (data.ambientocclusion !== undefined) { - model.ao = data.ambientocclusion - } - return model -} - -function prepareModel (model, texturesJson) { - // resolve texture names eg west: #all -> blocks/stone - for (const tex in model.textures) { - let root = model.textures[tex] - while (root.charAt(0) === '#') { - root = model.textures[root.substr(1)] - } - model.textures[tex] = root - } - for (const tex in model.textures) { - let name = model.textures[tex] - name = cleanupBlockName(name) - model.textures[tex] = texturesJson[name] - } - for (const elem of model.elements) { - for (const sideName of Object.keys(elem.faces)) { - const face = elem.faces[sideName] - - if (face.texture.charAt(0) === '#') { - face.texture = JSON.parse(JSON.stringify(model.textures[face.texture.substr(1)])) - } else { - let name = face.texture - name = cleanupBlockName(name) - face.texture = JSON.parse(JSON.stringify(texturesJson[name])) - } - - let uv = face.uv - if (!uv) { - const _from = elem.from - const _to = elem.to - - // taken from https://github.com/DragonDev1906/Minecraft-Overviewer/ - uv = { - north: [_to[0], 16 - _to[1], _from[0], 16 - _from[1]], - east: [_from[2], 16 - _to[1], _to[2], 16 - _from[1]], - south: [_from[0], 16 - _to[1], _to[0], 16 - _from[1]], - west: [_from[2], 16 - _to[1], _to[2], 16 - _from[1]], - up: [_from[0], _from[2], _to[0], _to[2]], - down: [_to[0], _from[2], _from[0], _to[2]] - }[sideName] - } - - const su = (uv[2] - uv[0]) * face.texture.su / 16 - const sv = (uv[3] - uv[1]) * face.texture.sv / 16 - face.texture.bu = face.texture.u + 0.5 * face.texture.su - face.texture.bv = face.texture.v + 0.5 * face.texture.sv - face.texture.u += uv[0] * face.texture.su / 16 - face.texture.v += uv[1] * face.texture.sv / 16 - face.texture.su = su - face.texture.sv = sv - } - } -} - -function resolveModel (name, blocksModels, texturesJson) { - const model = getModel(name, blocksModels) - prepareModel(model, texturesJson.textures) - return model -} - -function prepareBlocksStates (mcAssets, atlas) { - const blocksStates = mcAssets.blocksStates - mcAssets.blocksStates["missing_texture"] = { - "variants": { - "normal": { - "model": "missing_texture" - } - } - }, - mcAssets.blocksModels["missing_texture"] = { - "parent": "block/cube_all", - "textures": { - "all": "blocks/missing_texture" - } - } - for (const block of Object.values(blocksStates)) { - if (!block) continue - if (block.variants) { - for (const variant of Object.values(block.variants)) { - if (variant instanceof Array) { - for (const v of variant) { - v.model = resolveModel(v.model, mcAssets.blocksModels, atlas.json) - } - } else { - variant.model = resolveModel(variant.model, mcAssets.blocksModels, atlas.json) - } - } - } - if (block.multipart) { - for (const variant of block.multipart) { - if (variant.apply instanceof Array) { - for (const v of variant.apply) { - v.model = resolveModel(v.model, mcAssets.blocksModels, atlas.json) - } - } else { - variant.apply.model = resolveModel(variant.apply.model, mcAssets.blocksModels, atlas.json) - } - } - } - } - return blocksStates -} - -module.exports = { prepareBlocksStates } diff --git a/prismarine-viewer/viewer/lib/simpleUtils.js b/prismarine-viewer/viewer/lib/simpleUtils.js deleted file mode 100644 index 684f489b9..000000000 --- a/prismarine-viewer/viewer/lib/simpleUtils.js +++ /dev/null @@ -1,55 +0,0 @@ -function getBufferFromStream (stream) { - return new Promise( - (resolve, reject) => { - let buffer = Buffer.from([]) - stream.on('data', buf => { - buffer = Buffer.concat([buffer, buf]) - }) - stream.on('end', () => resolve(buffer)) - stream.on('error', reject) - } - ) -} - -function spiral (X, Y, fun) { // TODO: move that to spiralloop package - let x = 0 - let y = 0 - let dx = 0 - let dy = -1 - const N = Math.max(X, Y) * Math.max(X, Y) - const hX = X / 2 - const hY = Y / 2 - for (let i = 0; i < N; i++) { - if (-hX < x && x <= hX && -hY < y && y <= hY) { - fun(x, y) - } - if (x === y || (x < 0 && x === -y) || (x > 0 && x === 1 - y)) { - const tmp = dx - dx = -dy - dy = tmp - } - x += dx - y += dy - } -} - -class ViewRect { - constructor (cx, cz, viewDistance) { - this.x0 = cx - viewDistance - this.x1 = cx + viewDistance - this.z0 = cz - viewDistance - this.z1 = cz + viewDistance - } - - contains (x, z) { - return this.x0 < x && x <= this.x1 && this.z0 < z && z <= this.z1 - } -} - -function chunkPos (pos) { - const x = Math.floor(pos.x / 16) - const z = Math.floor(pos.z / 16) - return [x, z] -} - -module.exports = { getBufferFromStream, spiral, ViewRect, chunkPos } diff --git a/prismarine-viewer/viewer/lib/simpleUtils.ts b/prismarine-viewer/viewer/lib/simpleUtils.ts new file mode 100644 index 000000000..b46f96120 --- /dev/null +++ b/prismarine-viewer/viewer/lib/simpleUtils.ts @@ -0,0 +1,18 @@ +export function getBufferFromStream (stream) { + return new Promise( + (resolve, reject) => { + let buffer = Buffer.from([]) + stream.on('data', buf => { + buffer = Buffer.concat([buffer, buf]) + }) + stream.on('end', () => resolve(buffer)) + stream.on('error', reject) + } + ) +} + +export function chunkPos (pos: { x: number, z: number }) { + const x = Math.floor(pos.x / 16) + const z = Math.floor(pos.z / 16) + return [x, z] +} diff --git a/prismarine-viewer/viewer/lib/utils.js b/prismarine-viewer/viewer/lib/utils.js index 382c05739..34e36de20 100644 --- a/prismarine-viewer/viewer/lib/utils.js +++ b/prismarine-viewer/viewer/lib/utils.js @@ -6,12 +6,12 @@ function safeRequire (path) { } } const { loadImage } = safeRequire('node-canvas-webgl/lib') -const THREE = require('three') const path = require('path') +const THREE = require('three') const textureCache = {} // todo not ideal, export different functions for browser and node -function loadTexture (texture, cb) { +export function loadTexture (texture, cb) { if (process.platform === 'browser') { return require('./utils.web').loadTexture(texture, cb) } @@ -26,11 +26,31 @@ function loadTexture (texture, cb) { } } -function loadJSON (json, cb) { +export function loadJSON (json, cb) { if (process.platform === 'browser') { return require('./utils.web').loadJSON(json, cb) } cb(require(path.resolve(__dirname, '../../public/' + json))) } -module.exports = { loadTexture, loadJSON } +export const loadScript = async function (/** @type {string} */scriptSrc) { + if (document.querySelector(`script[src="${scriptSrc}"]`)) { + return + } + + return new Promise((resolve, reject) => { + const scriptElement = document.createElement('script') + scriptElement.src = scriptSrc + scriptElement.async = true + + scriptElement.addEventListener('load', () => { + resolve(scriptElement) + }) + + scriptElement.onerror = (error) => { + reject(error) + } + + document.head.appendChild(scriptElement) + }) +} diff --git a/prismarine-viewer/viewer/lib/viewer.js b/prismarine-viewer/viewer/lib/viewer.ts similarity index 57% rename from prismarine-viewer/viewer/lib/viewer.js rename to prismarine-viewer/viewer/lib/viewer.ts index 58d07144a..c1faf3d96 100644 --- a/prismarine-viewer/viewer/lib/viewer.js +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -1,21 +1,34 @@ - -const THREE = require('three') -const TWEEN = require('@tweenjs/tween.js') -const { WorldRenderer } = require('./worldrenderer') -const { Entities } = require('./entities') -const { Primitives } = require('./primitives') -const { getVersion } = require('./version') -const { Vec3 } = require('vec3') - -class Viewer { - constructor (renderer, numWorkers = undefined) { +import * as THREE from 'three' +import * as tweenJs from '@tweenjs/tween.js' +import { Vec3 } from 'vec3' +import { WorldRenderer } from './worldrenderer' +import { Entities } from './entities' +import { Primitives } from './primitives' +import { getVersion } from './version' + +// new THREE.Points(new THREE.BufferGeometry(), new THREE.PointsMaterial()) + +export class Viewer { + scene: THREE.Scene + ambientLight: THREE.AmbientLight + directionalLight: THREE.DirectionalLight + camera: THREE.PerspectiveCamera + world: WorldRenderer + entities: Entities + primitives: Primitives + domElement: HTMLCanvasElement + playerHeight: number + isSneaking: boolean + version: string + + constructor (public renderer: THREE.WebGLRenderer, numWorkers = undefined) { this.scene = new THREE.Scene() this.scene.background = new THREE.Color('lightblue') - this.ambientLight = new THREE.AmbientLight(0xcccccc) + this.ambientLight = new THREE.AmbientLight(0xcc_cc_cc) this.scene.add(this.ambientLight) - this.directionalLight = new THREE.DirectionalLight(0xffffff, 0.5) + this.directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5) this.directionalLight.position.set(1, 1, 0.5).normalize() this.directionalLight.castShadow = true this.scene.add(this.directionalLight) @@ -38,9 +51,9 @@ class Viewer { this.primitives.clear() } - setVersion (userVersion) { + setVersion (userVersion: string) { const texturesVersion = getVersion(userVersion) - console.log('Using version:', userVersion, 'textures:', texturesVersion) + console.log('[viewer] Using version:', userVersion, 'textures:', texturesVersion) this.version = userVersion this.world.setVersion(userVersion, texturesVersion) this.entities.clear() @@ -51,11 +64,11 @@ class Viewer { this.world.addColumn(x, z, chunk) } - removeColumn (x, z) { + removeColumn (x: string, z: string) { this.world.removeColumn(x, z) } - setBlockStateId (pos, stateId) { + setBlockStateId (pos: Vec3, stateId: number) { this.world.setBlockStateId(pos, stateId) } @@ -67,15 +80,16 @@ class Viewer { this.primitives.update(p) } - setFirstPersonCamera (pos, yaw, pitch) { + setFirstPersonCamera (pos: Vec3, yaw: number, pitch: number, roll = 0) { if (pos) { let y = pos.y + this.playerHeight if (this.isSneaking) y -= 0.3 - new TWEEN.Tween(this.camera.position).to({ x: pos.x, y, z: pos.z }, 50).start() + new tweenJs.Tween(this.camera.position).to({ x: pos.x, y, z: pos.z }, 50).start() } - this.camera.rotation.set(pitch, yaw, 0, 'ZYX') + this.camera.rotation.set(pitch, yaw, roll, 'ZYX') } + // todo type listen (emitter) { emitter.on('entity', (e) => { this.updateEntity(e) @@ -85,9 +99,14 @@ class Viewer { this.updatePrimitive(p) }) - emitter.on('loadChunk', ({ x, z, chunk }) => { + emitter.on('loadChunk', ({ x, z, chunk, worldConfig }) => { + this.world.worldConfig = worldConfig this.addColumn(x, z, chunk) }) + // todo remove and use other architecture instead so data flow is clear + emitter.on('blockEntities', (blockEntities) => { + this.world.blockEntities = blockEntities + }) emitter.on('unloadChunk', ({ x, z }) => { this.removeColumn(x, z) @@ -97,24 +116,28 @@ class Viewer { this.setBlockStateId(new Vec3(pos.x, pos.y, pos.z), stateId) }) + emitter.emit('listening') + this.domElement.addEventListener('pointerdown', (evt) => { const raycaster = new THREE.Raycaster() const mouse = new THREE.Vector2() mouse.x = (evt.clientX / this.domElement.clientWidth) * 2 - 1 mouse.y = -(evt.clientY / this.domElement.clientHeight) * 2 + 1 raycaster.setFromCamera(mouse, this.camera) - const ray = raycaster.ray + const { ray } = raycaster emitter.emit('mouseClick', { origin: ray.origin, direction: ray.direction, button: evt.button }) }) } update () { - TWEEN.update() + tweenJs.update() + } + + render () { + this.renderer.render(this.scene, this.camera) } async waitForChunksToRender () { await this.world.waitForChunksToRender() } } - -module.exports = { Viewer } diff --git a/prismarine-viewer/viewer/lib/worker.js b/prismarine-viewer/viewer/lib/worker.js index e1bb170bf..e1ef45c33 100644 --- a/prismarine-viewer/viewer/lib/worker.js +++ b/prismarine-viewer/viewer/lib/worker.js @@ -12,11 +12,11 @@ if (!global.self) { const { Vec3 } = require('vec3') const { World } = require('./world') -const { getSectionGeometry } = require('./models') +const { getSectionGeometry, setBlockStates } = require('./models') -let blocksStates = null let world = null let dirtySections = {} +let blockStatesReady = false function sectionKey (x, y, z) { return `${x},${y},${z}` @@ -31,7 +31,7 @@ function setSectionDirty (pos, value = true) { if (!value) { delete dirtySections[key] postMessage({ type: 'sectionFinished', key }) - } else if (chunk && chunk.sections[Math.floor(y / 16)]) { + } else if (chunk?.getSection(pos)) { dirtySections[key] = value } else { postMessage({ type: 'sectionFinished', key }) @@ -43,7 +43,8 @@ self.onmessage = ({ data }) => { globalThis.mcData = data.mcData world = new World(data.version) } else if (data.type === 'blockStates') { - blocksStates = data.json + setBlockStates(data.json) + blockStatesReady = true } else if (data.type === 'dirty') { const loc = new Vec3(data.x, data.y, data.z) setSectionDirty(loc, data.value) @@ -60,11 +61,12 @@ self.onmessage = ({ data }) => { dirtySections = {} // todo also remove cached globalThis.mcData = null + blockStatesReady = false } } setInterval(() => { - if (world === null || blocksStates === null) return + if (world === null || !blockStatesReady) return const sections = Object.keys(dirtySections) if (sections.length === 0) return @@ -77,9 +79,9 @@ setInterval(() => { y = parseInt(y, 10) z = parseInt(z, 10) const chunk = world.getColumn(x, z) - if (chunk && chunk.sections[Math.floor(y / 16)]) { + if (chunk?.getSection(new Vec3(x, y, z))) { delete dirtySections[key] - const geometry = getSectionGeometry(x, y, z, world, blocksStates) + const geometry = getSectionGeometry(x, y, z, world) const transferable = [geometry.positions.buffer, geometry.normals.buffer, geometry.colors.buffer, geometry.uvs.buffer] postMessage({ type: 'geometry', key, geometry }, transferable) } diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts new file mode 100644 index 000000000..d770e2064 --- /dev/null +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -0,0 +1,165 @@ +import { chunkPos } from './simpleUtils' + +// todo refactor into its own commons module +import { generateSpiralMatrix, ViewRect } from 'flying-squid/src/utils' +import { Vec3 } from 'vec3' +import { EventEmitter } from 'events' + +export type ChunkPosKey = string +type ChunkPos = { x: number, z: number } + +/** + * Usually connects to mineflayer bot and emits world data (chunks, entities) + * It's up to the consumer to serialize the data if needed + */ +export class WorldDataEmitter extends EventEmitter { + private loadedChunks: Record + private lastPos: Vec3 + private eventListeners: Record = {}; + private emitter: WorldDataEmitter + + constructor (public world: import('prismarine-world').world.World | typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { + super() + this.loadedChunks = {} + this.lastPos = new Vec3(0, 0, 0).update(position) + // todo + this.emitter = this + + this.emitter.on('mouseClick', async (click) => { + const ori = new Vec3(click.origin.x, click.origin.y, click.origin.z) + const dir = new Vec3(click.direction.x, click.direction.y, click.direction.z) + const block = this.world.raycast(ori, dir, 256) + if (!block) return + //@ts-ignore + this.emit('blockClicked', block, block.face, click.button) + }) + } + + listenToBot (bot: typeof __type_bot) { + this.eventListeners[bot.username] = { + // 'move': botPosition, + entitySpawn: (e: any) => { + if (e === bot.entity) return + this.emitter.emit('entity', { id: e.id, name: e.name, pos: e.position, width: e.width, height: e.height, username: e.username }) + }, + entityMoved: (e: any) => { + this.emitter.emit('entity', { id: e.id, pos: e.position, pitch: e.pitch, yaw: e.yaw }) + }, + entityGone: (e: any) => { + this.emitter.emit('entity', { id: e.id, delete: true }) + }, + chunkColumnLoad: (pos: Vec3) => { + this.loadChunk(pos) + }, + blockUpdate: (oldBlock: any, newBlock: any) => { + const stateId = newBlock.stateId ? newBlock.stateId : ((newBlock.type << 4) | newBlock.metadata) + this.emitter.emit('blockUpdate', { pos: oldBlock.position, stateId }) + } + } + + this.emitter.on('listening', () => { + this.emitter.emit('blockEntities', new Proxy({}, { + get (_target, posKey, receiver) { + if (typeof posKey !== 'string') return + const [x, y, z] = posKey.split(',').map(Number) + return bot.world.getBlock(new Vec3(x, y, z)).entity + }, + })) + }) + // node.js stream data event pattern + if (this.emitter.listenerCount('blockEntities')) { + this.emitter.emit('listening') + } + + for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + bot.on(evt as any, listener) + } + + for (const id in bot.entities) { + const e = bot.entities[id] + if (e && e !== bot.entity) { + this.emitter.emit('entity', { id: e.id, name: e.name, pos: e.position, width: e.width, height: e.height, username: e.username }) + } + } + } + + removeListenersFromBot (bot: import('mineflayer').Bot) { + for (const [evt, listener] of Object.entries(this.eventListeners[bot.username])) { + bot.removeListener(evt as any, listener) + } + delete this.eventListeners[bot.username] + } + + async init (pos: Vec3) { + const [botX, botZ] = chunkPos(pos) + + const positions = generateSpiralMatrix(this.viewDistance).map(([x, z]) => new Vec3((botX + x) * 16, 0, (botZ + z) * 16)) + + this.lastPos.update(pos) + await this._loadChunks(positions) + } + + async _loadChunks (positions: Vec3[], sliceSize = 5, waitTime = 0) { + for (let i = 0; i < positions.length; i += sliceSize) { + await new Promise((resolve) => setTimeout(resolve, waitTime)) + await Promise.all(positions.slice(i, i + sliceSize).map((p) => this.loadChunk(p))) + } + } + + async loadChunk (pos: ChunkPos) { + const [botX, botZ] = chunkPos(this.lastPos) + const dx = Math.abs(botX - Math.floor(pos.x / 16)) + const dz = Math.abs(botZ - Math.floor(pos.z / 16)) + if (dx < this.viewDistance && dz < this.viewDistance) { + const column = await this.world.getColumnAt(pos['y'] ? pos as Vec3 : new Vec3(pos.x, 0, pos.z)) + if (column) { + // todo optimize toJson data, make it clear why it is used + const chunk = column.toJson() + // TODO: blockEntities + const worldConfig = { + minY: column['minY'] ?? 0, + worldHeight: column['worldHeight'] ?? 256, + } + //@ts-ignore + this.emitter.emit('loadChunk', { x: pos.x, z: pos.z, chunk, blockEntities: column.blockEntities, worldConfig }) + this.loadedChunks[`${pos.x},${pos.z}`] = true + } + } + } + + unloadChunk (pos: ChunkPos) { + this.emitter.emit('unloadChunk', { x: pos.x, z: pos.z }) + delete this.loadedChunks[`${pos.x},${pos.z}`] + } + + async updatePosition (pos: Vec3, force = false) { + const [lastX, lastZ] = chunkPos(this.lastPos) + const [botX, botZ] = chunkPos(pos) + if (lastX !== botX || lastZ !== botZ || force) { + const newView = new ViewRect(botX, botZ, this.viewDistance) + const chunksToUnload: Vec3[] = [] + for (const coords of Object.keys(this.loadedChunks)) { + const x = parseInt(coords.split(',')[0]) + const z = parseInt(coords.split(',')[1]) + const p = new Vec3(x, 0, z) + const [chunkX, chunkZ] = chunkPos(p) + if (!newView.contains(chunkX, chunkZ)) { + chunksToUnload.push(p) + } + } + // todo @sa2urami + console.log('unloading', chunksToUnload.length, 'total now', Object.keys(this.loadedChunks).length) + for (const p of chunksToUnload) { + this.unloadChunk(p) + } + const positions = generateSpiralMatrix(this.viewDistance).map(([x, z]) => { + const pos = new Vec3((botX + x) * 16, 0, (botZ + z) * 16) + if (!this.loadedChunks[`${pos.x},${pos.z}`]) return pos + }).filter(Boolean) + this.lastPos.update(pos) + await this._loadChunks(positions) + } else { + this.lastPos.update(pos) + } + } +} diff --git a/prismarine-viewer/viewer/lib/worldView.js b/prismarine-viewer/viewer/lib/worldView.js deleted file mode 100644 index 90ffe3046..000000000 --- a/prismarine-viewer/viewer/lib/worldView.js +++ /dev/null @@ -1,134 +0,0 @@ -const { spiral, ViewRect, chunkPos } = require('./simpleUtils') -const { Vec3 } = require('vec3') -const EventEmitter = require('events') - -class WorldView extends EventEmitter { - constructor (world, viewDistance, position = new Vec3(0, 0, 0), emitter = null) { - super() - this.world = world - this.viewDistance = viewDistance - this.loadedChunks = {} - this.lastPos = new Vec3(0, 0, 0).update(position) - this.emitter = emitter || this - - this.listeners = {} - this.emitter.on('mouseClick', async (click) => { - const ori = new Vec3(click.origin.x, click.origin.y, click.origin.z) - const dir = new Vec3(click.direction.x, click.direction.y, click.direction.z) - const block = this.world.raycast(ori, dir, 256) - if (!block) return - this.emit('blockClicked', block, block.face, click.button) - }) - } - - listenToBot (bot) { - const worldView = this - this.listeners[bot.username] = { - // 'move': botPosition, - entitySpawn: function (e) { - if (e === bot.entity) return - worldView.emitter.emit('entity', { id: e.id, name: e.name, pos: e.position, width: e.width, height: e.height, username: e.username }) - }, - entityMoved: function (e) { - worldView.emitter.emit('entity', { id: e.id, pos: e.position, pitch: e.pitch, yaw: e.yaw }) - }, - entityGone: function (e) { - worldView.emitter.emit('entity', { id: e.id, delete: true }) - }, - chunkColumnLoad: function (pos) { - worldView.loadChunk(pos) - }, - blockUpdate: function (oldBlock, newBlock) { - const stateId = newBlock.stateId ? newBlock.stateId : ((newBlock.type << 4) | newBlock.metadata) - worldView.emitter.emit('blockUpdate', { pos: oldBlock.position, stateId }) - } - } - - for (const [evt, listener] of Object.entries(this.listeners[bot.username])) { - bot.on(evt, listener) - } - - for (const id in bot.entities) { - const e = bot.entities[id] - if (e && e !== bot.entity) { - this.emitter.emit('entity', { id: e.id, name: e.name, pos: e.position, width: e.width, height: e.height, username: e.username }) - } - } - } - - removeListenersFromBot (bot) { - for (const [evt, listener] of Object.entries(this.listeners[bot.username])) { - bot.removeListener(evt, listener) - } - delete this.listeners[bot.username] - } - - async init (pos) { - const [botX, botZ] = chunkPos(pos) - - const positions = [] - spiral(this.viewDistance * 2, this.viewDistance * 2, (x, z) => { - const p = new Vec3((botX + x) * 16, 0, (botZ + z) * 16) - positions.push(p) - }) - - this.lastPos.update(pos) - await this._loadChunks(positions) - } - - async _loadChunks (positions, sliceSize = 5, waitTime = 0) { - for (let i = 0; i < positions.length; i += sliceSize) { - await new Promise((resolve) => setTimeout(resolve, waitTime)) - await Promise.all(positions.slice(i, i + sliceSize).map(p => this.loadChunk(p))) - } - } - - async loadChunk (pos) { - const [botX, botZ] = chunkPos(this.lastPos) - const dx = Math.abs(botX - Math.floor(pos.x / 16)) - const dz = Math.abs(botZ - Math.floor(pos.z / 16)) - if (dx < this.viewDistance && dz < this.viewDistance) { - const column = await this.world.getColumnAt(pos) - if (column) { - // todo optimize toJson data, make it clear why it is used - const chunk = column.toJson() - this.emitter.emit('loadChunk', { x: pos.x, z: pos.z, chunk }) - this.loadedChunks[`${pos.x},${pos.z}`] = true - } - } - } - - unloadChunk (pos) { - this.emitter.emit('unloadChunk', { x: pos.x, z: pos.z }) - delete this.loadedChunks[`${pos.x},${pos.z}`] - } - - async updatePosition (pos, force = false) { - const [lastX, lastZ] = chunkPos(this.lastPos) - const [botX, botZ] = chunkPos(pos) - if (lastX !== botX || lastZ !== botZ || force) { - const newView = new ViewRect(botX, botZ, this.viewDistance) - for (const coords of Object.keys(this.loadedChunks)) { - const x = parseInt(coords.split(',')[0]) - const z = parseInt(coords.split(',')[1]) - const p = new Vec3(x, 0, z) - if (!newView.contains(Math.floor(x / 16), Math.floor(z / 16))) { - this.unloadChunk(p) - } - } - const positions = [] - spiral(this.viewDistance * 2, this.viewDistance * 2, (x, z) => { - const p = new Vec3((botX + x) * 16, 0, (botZ + z) * 16) - if (!this.loadedChunks[`${p.x},${p.z}`]) { - positions.push(p) - } - }) - this.lastPos.update(pos) - await this._loadChunks(positions) - } else { - this.lastPos.update(pos) - } - } -} - -module.exports = { WorldView } diff --git a/prismarine-viewer/viewer/lib/worldrenderer.js b/prismarine-viewer/viewer/lib/worldrenderer.js index 1d8176eda..60c52921c 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.js +++ b/prismarine-viewer/viewer/lib/worldrenderer.js @@ -1,13 +1,15 @@ //@ts-check -/* global Worker */ const THREE = require('three') -const Vec3 = require('vec3').Vec3 +const { Vec3 } = require('vec3') const { loadTexture, loadJSON } = globalThis.isElectron ? require('./utils.electron.js') : require('./utils') const { EventEmitter } = require('events') -const { dispose3 } = require('./dispose') -const { dynamicMcDataFiles } = require('../../buildWorkerConfig.mjs') const mcDataRaw = require('minecraft-data/data.js') +const nbt = require('prismarine-nbt') +const { dynamicMcDataFiles } = require('../../buildWorkerConfig.mjs') +const { dispose3 } = require('./dispose') const { toMajor } = require('./version.js') +const PrismarineChatLoader = require('prismarine-chat') +const { renderSign } = require('../sign-renderer/') function mod (x, n) { return ((x % n) + n) % n @@ -15,9 +17,13 @@ function mod (x, n) { class WorldRenderer { constructor (scene, numWorkers = 4) { - this.sectionMeshs = {} + this.blockEntities = {} + this.worldConfig = { minY: 0, worldHeight: 256 } + this.sectionObjects = {} + this.showChunkBorders = false this.active = false this.version = undefined + /** @type {THREE.Scene} */ this.scene = scene this.loadedChunks = {} this.sectionsOutstanding = new Set() @@ -29,25 +35,29 @@ class WorldRenderer { this.workers = [] for (let i = 0; i < numWorkers; i++) { - // Node environement needs an absolute path, but browser needs the url of the file + // Node environment needs an absolute path, but browser needs the url of the file let src = __dirname - if (typeof window !== 'undefined') src = 'worker.js' - else src += '/worker.js' + if (typeof window === 'undefined') src += '/worker.js' + else src = 'worker.js' /** @type {any} */ const worker = new Worker(src) - worker.onmessage = ({ data }) => { + worker.onmessage = async ({ data }) => { if (!this.active) return + await new Promise(resolve => { + setTimeout(resolve, 0) + }) if (data.type === 'geometry') { - let mesh = this.sectionMeshs[data.key] - if (mesh) { - this.scene.remove(mesh) - dispose3(mesh) - delete this.sectionMeshs[data.key] + /** @type {THREE.Object3D} */ + let object = this.sectionObjects[data.key] + if (object) { + this.scene.remove(object) + dispose3(object) + delete this.sectionObjects[data.key] } const chunkCoords = data.key.split(',') - if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]]) return + if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length) return const geometry = new THREE.BufferGeometry() geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3)) @@ -56,10 +66,25 @@ class WorldRenderer { geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2)) geometry.setIndex(data.geometry.indices) - mesh = new THREE.Mesh(geometry, this.material) + const mesh = new THREE.Mesh(geometry, this.material) mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) - this.sectionMeshs[data.key] = mesh - this.scene.add(mesh) + object = new THREE.Group() + object.add(mesh) + if (this.showChunkBorders) { + const boxHelper = new THREE.BoxHelper(mesh, 0xffff00) + object.add(boxHelper) + } + // should not it compute once + if (Object.keys(data.geometry.signs).length) { + for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.signs)) { + const [x, y, z] = posKey.split(',') + const signBlockEntity = this.blockEntities[posKey] + if (!signBlockEntity) continue + object.add(this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity))) + } + } + this.sectionObjects[data.key] = object + this.scene.add(object) } else if (data.type === 'sectionFinished') { this.sectionsOutstanding.delete(data.key) this.renderUpdateEmitter.emit('update') @@ -70,12 +95,43 @@ class WorldRenderer { } } + renderSign (/** @type {Vec3} */position, /** @type {number} */rotation, isWall, blockEntity) { + // @ts-ignore + const PrismarineChat = PrismarineChatLoader(this.version) + const canvas = renderSign(blockEntity, PrismarineChat) + const tex = new THREE.Texture(canvas) + tex.magFilter = THREE.NearestFilter + tex.minFilter = THREE.NearestFilter + tex.needsUpdate = true + const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true, })) + mesh.renderOrder = 999 + + // todo @sa2urami shouldnt all this be done in worker? + mesh.scale.set(1, 7 / 16, 1) + if (isWall) { + mesh.position.set(0, 0, -(8 - 1.5) / 16 + 0.001) + } else { + // standing + const faceEnd = 8.75 + mesh.position.set(0, 0, (faceEnd - 16 / 2) / 16 + 0.001) + } + + const group = new THREE.Group() + group.rotation.set(0, -THREE.MathUtils.degToRad( + rotation * (isWall ? 90 : 45 / 2) + ), 0) + group.add(mesh) + const y = isWall ? 4.5 / 16 + mesh.scale.y / 2 : (1 - (mesh.scale.y / 2)) + group.position.set(position.x + 0.5, position.y + y, position.z + 0.5) + return group + } + resetWorld () { this.active = false - for (const mesh of Object.values(this.sectionMeshs)) { + for (const mesh of Object.values(this.sectionObjects)) { this.scene.remove(mesh) } - this.sectionMeshs = {} + this.sectionObjects = {} this.loadedChunks = {} this.sectionsOutstanding = new Set() for (const worker of this.workers) { @@ -93,7 +149,7 @@ class WorldRenderer { for (const worker of this.workers) { const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) mcData.version = JSON.parse(JSON.stringify(mcData.version)) - worker.postMessage({ type: 'mcData', mcData, version: this.version, time: Date.now() }) + worker.postMessage({ type: 'mcData', mcData, version: this.version }) } this.updateTexturesData() @@ -107,7 +163,7 @@ class WorldRenderer { this.material.map = texture }) - const loadBlockStates = () => { + const loadBlockStates = async () => { return new Promise(resolve => { if (this.blockStatesData) return resolve(this.blockStatesData) return loadJSON(`blocksStates/${this.texturesVersion}.json`, resolve) @@ -125,7 +181,7 @@ class WorldRenderer { for (const worker of this.workers) { worker.postMessage({ type: 'chunk', x, z, chunk }) } - for (let y = 0; y < 256; y += 16) { + for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { const loc = new Vec3(x, y, z) this.setSectionDirty(loc) this.setSectionDirty(loc.offset(-16, 0, 0)) @@ -140,15 +196,15 @@ class WorldRenderer { for (const worker of this.workers) { worker.postMessage({ type: 'unloadChunk', x, z }) } - for (let y = 0; y < 256; y += 16) { + for (let y = this.worldConfig.minY; y < this.worldConfig.worldHeight; y += 16) { this.setSectionDirty(new Vec3(x, y, z), false) const key = `${x},${y},${z}` - const mesh = this.sectionMeshs[key] + const mesh = this.sectionObjects[key] if (mesh) { this.scene.remove(mesh) dispose3(mesh) } - delete this.sectionMeshs[key] + delete this.sectionObjects[key] } } @@ -176,9 +232,9 @@ class WorldRenderer { // Listen for chunk rendering updates emitted if a worker finished a render and resolve if the number // of sections not rendered are 0 - waitForChunksToRender () { + async waitForChunksToRender () { return new Promise((resolve, reject) => { - if (Array.from(this.sectionsOutstanding).length === 0) { + if ([...this.sectionsOutstanding].length === 0) { resolve() return } diff --git a/prismarine-viewer/viewer/prepare/atlas.ts b/prismarine-viewer/viewer/prepare/atlas.ts new file mode 100644 index 000000000..3a8eed60f --- /dev/null +++ b/prismarine-viewer/viewer/prepare/atlas.ts @@ -0,0 +1,69 @@ +import fs from 'fs' +import path from 'path' +import { Canvas, Image } from 'canvas' +import { getAdditionalTextures } from './moreGeneratedBlocks' + +function nextPowerOfTwo (n) { + if (n === 0) return 1 + n-- + n |= n >> 1 + n |= n >> 2 + n |= n >> 4 + n |= n >> 8 + n |= n >> 16 + return n + 1 +} + +const localTextures = ['missing_texture.png'] + +function readTexture (basePath, name) { + if (localTextures.includes(name)) { + // grab ./missing_texture.png + basePath = __dirname + } + return fs.readFileSync(path.join(basePath, name), 'base64') +} + +export function makeTextureAtlas (mcAssets) { + const blocksTexturePath = path.join(mcAssets.directory, '/blocks') + const textureFiles = fs.readdirSync(blocksTexturePath).filter(file => file.endsWith('.png')) + textureFiles.unshift(...localTextures) + + const { generated: additionalTextures, twoBlockTextures } = getAdditionalTextures() + textureFiles.push(...Object.keys(additionalTextures)) + + const texSize = nextPowerOfTwo(Math.ceil(Math.sqrt(textureFiles.length + twoBlockTextures.length))) + const tileSize = 16 + + const imgSize = texSize * tileSize + const canvas = new Canvas(imgSize, imgSize, 'png' as any) + const g = canvas.getContext('2d') + + const texturesIndex = {} + + let offset = 0 + for (const i in textureFiles) { + const pos = +i + offset + const x = (pos % texSize) * tileSize + const y = Math.floor(pos / texSize) * tileSize + + const name = textureFiles[i].split('.')[0] + + const img = new Image() + if (additionalTextures[name]) { + img.src = additionalTextures[name] + } else { + img.src = 'data:image/png;base64,' + readTexture(blocksTexturePath, textureFiles[i]) + } + const twoTileWidth = twoBlockTextures.includes(name) + if (twoTileWidth) { + offset++ + } + const renderWidth = twoTileWidth ? tileSize * 2 : tileSize + g.drawImage(img, 0, 0, renderWidth, tileSize, x, y, renderWidth, tileSize) + + texturesIndex[name] = { u: x / imgSize, v: y / imgSize, su: tileSize / imgSize, sv: tileSize / imgSize } + } + + return { image: canvas.toBuffer(), canvas, json: { size: tileSize / imgSize, textures: texturesIndex } } +} diff --git a/prismarine-viewer/viewer/prepare/blockStates/chest.json b/prismarine-viewer/viewer/prepare/blockStates/chest.json new file mode 100644 index 000000000..11053c9d5 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/blockStates/chest.json @@ -0,0 +1,49 @@ +{ + "variants": { + "facing=north,type=single": { + "model": "chest" + }, + "facing=east,type=single": { + "model": "chest", + "y": 90 + }, + "facing=south,type=single": { + "model": "chest", + "y": 180 + }, + "facing=west,type=single": { + "model": "chest", + "y": 270 + }, + "facing=north,type=left": { + "model": "chest_left" + }, + "facing=east,type=left": { + "model": "chest_left", + "y": 90 + }, + "facing=south,type=left": { + "model": "chest_left", + "y": 180 + }, + "facing=west,type=left": { + "model": "chest_left", + "y": 270 + }, + "facing=north,type=right": { + "model": "chest_right" + }, + "facing=east,type=right": { + "model": "chest_right", + "y": 90 + }, + "facing=south,type=right": { + "model": "chest_right", + "y": 180 + }, + "facing=west,type=right": { + "model": "chest_right", + "y": 270 + } + } +} diff --git a/prismarine-viewer/viewer/prepare/blockStates/ender_chest.json b/prismarine-viewer/viewer/prepare/blockStates/ender_chest.json new file mode 100644 index 000000000..7d81b878d --- /dev/null +++ b/prismarine-viewer/viewer/prepare/blockStates/ender_chest.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=north": { + "model": "chest" + }, + "facing=east": { + "model": "chest", + "y": 90 + }, + "facing=south": { + "model": "chest", + "y": 180 + }, + "facing=west": { + "model": "chest", + "y": 270 + } + } +} diff --git a/prismarine-viewer/viewer/prepare/blockStates/trapped_chest.json b/prismarine-viewer/viewer/prepare/blockStates/trapped_chest.json new file mode 100644 index 000000000..11053c9d5 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/blockStates/trapped_chest.json @@ -0,0 +1,49 @@ +{ + "variants": { + "facing=north,type=single": { + "model": "chest" + }, + "facing=east,type=single": { + "model": "chest", + "y": 90 + }, + "facing=south,type=single": { + "model": "chest", + "y": 180 + }, + "facing=west,type=single": { + "model": "chest", + "y": 270 + }, + "facing=north,type=left": { + "model": "chest_left" + }, + "facing=east,type=left": { + "model": "chest_left", + "y": 90 + }, + "facing=south,type=left": { + "model": "chest_left", + "y": 180 + }, + "facing=west,type=left": { + "model": "chest_left", + "y": 270 + }, + "facing=north,type=right": { + "model": "chest_right" + }, + "facing=east,type=right": { + "model": "chest_right", + "y": 90 + }, + "facing=south,type=right": { + "model": "chest_right", + "y": 180 + }, + "facing=west,type=right": { + "model": "chest_right", + "y": 270 + } + } +} diff --git a/prismarine-viewer/viewer/prepare/generateTextures.ts b/prismarine-viewer/viewer/prepare/generateTextures.ts new file mode 100644 index 000000000..380ac96ac --- /dev/null +++ b/prismarine-viewer/viewer/prepare/generateTextures.ts @@ -0,0 +1,48 @@ +import path from 'path' +import { makeTextureAtlas } from './atlas' +import { McAssets, prepareBlocksStates } from './modelsBuilder' +import mcAssets from 'minecraft-assets' +import fs from 'fs-extra' +import { prepareMoreGeneratedBlocks } from './moreGeneratedBlocks' + +const publicPath = path.resolve(__dirname, '../../public') + +const texturesPath = path.join(publicPath, 'textures') +if (fs.existsSync(texturesPath) && !process.argv.includes('-f')) { + console.log('textures folder already exists, skipping...') + process.exit(0) +} +fs.mkdirSync(texturesPath, { recursive: true }) + +const blockStatesPath = path.join(publicPath, 'blocksStates') +fs.mkdirSync(blockStatesPath, { recursive: true }) + +const warnings = new Set() +Promise.resolve().then(async () => { + console.time('generateTextures') + for (const version of mcAssets.versions as typeof mcAssets['versions']) { + // for debugging (e.g. when above is overridden) + if (!mcAssets.versions.includes(version)) { + throw new Error(`Version ${version} is not supported by minecraft-assets, skipping...`) + } + const assets = mcAssets(version) + const { warnings: _warnings } = await prepareMoreGeneratedBlocks(assets) + _warnings.forEach(x => warnings.add(x)) + // #region texture atlas + const atlas = makeTextureAtlas(assets) + const out = fs.createWriteStream(path.resolve(texturesPath, version + '.png')) + const stream = (atlas.canvas as any).pngStream() + stream.on('data', (chunk) => out.write(chunk)) + stream.on('end', () => console.log('Generated textures/' + version + '.png')) + // #endregion + + const blocksStates = JSON.stringify(prepareBlocksStates(assets, atlas)) + fs.writeFileSync(path.resolve(blockStatesPath, version + '.json'), blocksStates) + + fs.copySync(assets.directory, path.resolve(texturesPath, version), { overwrite: true }) + } + + fs.writeFileSync(path.join(publicPath, 'supportedVersions.json'), '[' + mcAssets.versions.map(v => `"${v}"`).toString() + ']') + warnings.forEach(x => console.warn(x)) + console.timeEnd('generateTextures') +}) diff --git a/prismarine-viewer/viewer/lib/missing_texture.png b/prismarine-viewer/viewer/prepare/missing_texture.png similarity index 100% rename from prismarine-viewer/viewer/lib/missing_texture.png rename to prismarine-viewer/viewer/prepare/missing_texture.png diff --git a/prismarine-viewer/viewer/prepare/modelsBuilder.ts b/prismarine-viewer/viewer/prepare/modelsBuilder.ts new file mode 100644 index 000000000..e32903fed --- /dev/null +++ b/prismarine-viewer/viewer/prepare/modelsBuilder.ts @@ -0,0 +1,257 @@ +type ModelBasic = { + model: string + x?: number + y?: number + uvlock?: boolean +} + +type BlockApplyModel = ModelBasic | (ModelBasic & { weight })[] + +type BlockStateCondition = { + [name: string]: string | number +} + +type BlockState = { + variants?: { + [name: string | ""]: BlockApplyModel + } + multipart?: { + when: { + [name: string]: string | number + } & { + OR?: BlockStateCondition[] + } + apply: BlockApplyModel + }[] +} + +type BlockModel = { + parent?: string + textures?: { + [name: string]: string + } + elements?: { + from: number[] + to: number[] + faces: { + [name: string]: { + texture: string + uv?: number[] + cullface?: string + } + } + }[] + ambientocclusion?: boolean + x?: number + y?: number + z?: number + ao?: boolean +} + +export type McAssets = { + blocksStates: { + [x: string]: BlockState + } + blocksModels: { + [x: string]: BlockModel + } + directory: string + version: string +} + +export type BlockStatesOutput = { + // states: { + // [blockName: string]: ResolvedModel + // } + // defaults: { + // su: number + // sv: number + // } +} + +export type ResolvedModel = { + textures: { + [name: string]: { + u: number + v: number + su: number + sv: number + bu: number + bv: number + } + } + elements: { + from: number[] + to: number[] + faces: { + [name: string]: { + texture: { + u: number + v: number + su: number + sv: number + bu: number + bv: number + } + } + } + }[] + ao: boolean + x?: number + y?: number + z?: number +} + +export const addBlockAllModel = (mcAssets: McAssets, name: string, texture = name) => { + mcAssets.blocksStates[name] = { + "variants": { + "": { + "model": name + } + } + } + mcAssets.blocksModels[name] = { + "parent": "block/cube_all", + "textures": { + "all": `blocks/${texture}` + } + } +} + +function cleanupBlockName (name: string) { + if (name.startsWith('block') || name.startsWith('minecraft:block')) return name.split('/')[1] + return name +} + +const objectAssignStrict = > (target: T, source: Partial) => Object.assign(target, source) + +function getFinalModel (name: string, blocksModels: { [x: string]: BlockModel }) { + name = cleanupBlockName(name) + const input = blocksModels[name] + if (!input) { + return null + } + + let out: BlockModel | null = { + textures: {}, + elements: [], + ao: true, + x: input.x, + y: input.y, + z: input.z, + } + + if (input.parent) { + out = getFinalModel(input.parent, blocksModels) + if (!out) return null + } + if (input.textures) { + Object.assign(out.textures!, deepCopy(input.textures)) + } + if (input.elements) out.elements = deepCopy(input.elements) + if (input.ao !== undefined) out.ao = input.ao + return out +} + +const deepCopy = (obj) => JSON.parse(JSON.stringify(obj)) + +const workerUsedTextures = ['particle'] +function prepareModel (model: BlockModel, texturesJson) { + const newModel = {} + + const getFinalTexture = (originalBlockName) => { + // texture name e.g. blocks/anvil_base + const cleanBlockName = cleanupBlockName(originalBlockName); + return { ...texturesJson[cleanBlockName], __debugName: cleanBlockName } + } + + const finalTextures = [] + + // resolve texture names eg west: #all -> blocks/stone + for (const side in model.textures) { + let texture = model.textures[side] + + while (texture.charAt(0) === '#') { + const textureName = texture.slice(1) + texture = model.textures[textureName] + if (texture === undefined) throw new Error(`Texture ${textureName} in ${JSON.stringify(model.textures)} not found`) + } + + finalTextures[side] = getFinalTexture(texture) + if (workerUsedTextures.includes(side)) { + model.textures[side] = finalTextures[side] + } + } + + for (const elem of model.elements!) { + for (const sideName of Object.keys(elem.faces)) { + const face = elem.faces[sideName] + + const finalTexture = deepCopy( + face.texture.charAt(0) === '#' + ? finalTextures![face.texture.slice(1)] + : getFinalTexture(face.texture) + ) + + const _from = elem.from + const _to = elem.to + // taken from https://github.com/DragonDev1906/Minecraft-Overviewer/ + const uv = face.uv || { + // default UVs + // format: [u1, v1, u2, v2] (u = x, v = y) + north: [_to[0], 16 - _to[1], _from[0], 16 - _from[1]], + east: [_from[2], 16 - _to[1], _to[2], 16 - _from[1]], + south: [_from[0], 16 - _to[1], _to[0], 16 - _from[1]], + west: [_from[2], 16 - _to[1], _to[2], 16 - _from[1]], + up: [_from[0], _from[2], _to[0], _to[2]], + down: [_to[0], _from[2], _from[0], _to[2]] + }[sideName]! + + const su = (uv[2] - uv[0]) / 16 * finalTexture.su + const sv = (uv[3] - uv[1]) / 16 * finalTexture.sv + finalTexture.u += uv[0] / 16 * finalTexture.su + finalTexture.v += uv[1] / 16 * finalTexture.sv + finalTexture.su = su + finalTexture.sv = sv + face.texture = finalTexture + } + } + return model +} + +function resolveModel (name, blocksModels, texturesJson) { + const model = getFinalModel(name, blocksModels) + return prepareModel(model, texturesJson.textures) +} + +export function prepareBlocksStates (mcAssets: McAssets, atlas: { json: any }) { + addBlockAllModel(mcAssets, 'missing_texture') + + const blocksStates = mcAssets.blocksStates + for (const block of Object.values(blocksStates)) { + if (!block) continue + if (block.variants) { + for (const variant of Object.values(block.variants)) { + if (variant instanceof Array) { + for (const v of variant) { + v.model = resolveModel(v.model, mcAssets.blocksModels, atlas.json) as any + } + } else { + variant.model = resolveModel(variant.model, mcAssets.blocksModels, atlas.json) as any + } + } + } + if (block.multipart) { + for (const variant of block.multipart) { + if (variant.apply instanceof Array) { + for (const v of variant.apply) { + v.model = resolveModel(v.model, mcAssets.blocksModels, atlas.json) as any + } + } else { + variant.apply.model = resolveModel(variant.apply.model, mcAssets.blocksModels, atlas.json) as any + } + } + } + } + return blocksStates +} diff --git a/prismarine-viewer/viewer/prepare/moreGeneratedBlocks.ts b/prismarine-viewer/viewer/prepare/moreGeneratedBlocks.ts new file mode 100644 index 000000000..67b024cef --- /dev/null +++ b/prismarine-viewer/viewer/prepare/moreGeneratedBlocks.ts @@ -0,0 +1,476 @@ +import Jimp from 'jimp' +import minecraftData from 'minecraft-data' +import prismarineRegistry from 'prismarine-registry' +import { McAssets } from './modelsBuilder' +import path from 'path' +import fs from 'fs' +import { fileURLToPath } from 'url' + +// todo refactor +const twoBlockTextures: string[] = [] +let currentImage: Jimp +let currentBlockName: string +let currentMcAssets: McAssets +let isPreFlattening = false +const postFlatenningRegistry = prismarineRegistry('1.13') +const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url))) + +type SidesType = { + "up": string + "north": string + "east": string + "south": string + "west": string + "down": string +} + +const getBlockStates = (name: string, postFlatenningName = name) => { + const mcData = isPreFlattening ? postFlatenningRegistry : minecraftData(currentMcAssets.version) + return mcData.blocksByName[isPreFlattening ? postFlatenningName : name]?.states +} + +export const addBlockCustomSidesModel = (name: string, sides: SidesType) => { + currentMcAssets.blocksStates[name] = { + "variants": { + "": { + "model": name + } + } + } + currentMcAssets.blocksModels[name] = { + "parent": "block/cube", + "textures": sides + } +} + +type TextureMap = [ + x: number, + y: number, + width?: number, + height?: number, +] + +const justCropUV = (x: number, y: number, x1, y1) => { + // input: 0-16, output: 0-currentImage.getWidth() + const width = Math.abs(x1 - x) + const height = Math.abs(y1 - y) + return currentImage.clone().crop( + x / 16 * currentImage.getWidth(), + y / 16 * currentImage.getHeight(), + width / 16 * currentImage.getWidth(), + height / 16 * currentImage.getHeight(), + ) +} +const justCrop = (x: number, y: number, width = 16, height = 16) => { + return currentImage.clone().crop(x, y, width, height) +} + +const combineTextures = (locations: TextureMap[]) => { + const resized: Jimp[] = [] + for (const [x, y, height = 16, width = 16] of locations) { + resized.push(justCrop(x, y, width, height)) + } + + const combinedImage = new Jimp(locations[0]![2] ?? 16, locations[0]![3] ?? 16) + for (const image of resized) { + combinedImage.blit(image, 0, 0) + } + return combinedImage +} + +const generatedImageTextures: { [blockName: string]: /* base64 */string } = {} + +const getBlockTexturesFromJimp = async > (sides: T, withUv = false, textureNameBase = currentBlockName): Promise> => { + const sidesTextures = {} as any + for (const [side, jimp] of Object.entries(sides)) { + const textureName = `${textureNameBase}_${side}` + const sideTexture = withUv ? { uv: [0, 0, jimp.getWidth(), jimp.getHeight()], texture: textureName } : textureName + const base64 = await jimp.getBase64Async(jimp.getMIME()) + if (side === 'side') { + sidesTextures['north'] = sideTexture + sidesTextures['east'] = sideTexture + sidesTextures['south'] = sideTexture + sidesTextures['west'] = sideTexture + } else { + sidesTextures[side] = sideTexture + } + generatedImageTextures[textureName] = base64 + } + + return sidesTextures +} + +const addSimpleCubeWithSides = async (sides: Record) => { + const sidesTextures = await getBlockTexturesFromJimp(sides) + + addBlockCustomSidesModel(currentBlockName, sidesTextures as any) +} + +const handleShulkerBox = async (dataBase: string, match: RegExpExecArray) => { + const [, shulkerColor = ''] = match + currentImage = await Jimp.read(dataBase + `entity/shulker/shulker${shulkerColor && `_${shulkerColor}`}.png`) + + const shulkerBoxTextures = { + // todo do all sides + side: combineTextures([ + [0, 16], // top + [0, 36], // bottom + ]), + up: justCrop(16, 0), + down: justCrop(32, 28) + } + + await addSimpleCubeWithSides(shulkerBoxTextures) +} + +const handleSign = async (dataBase: string, match: RegExpExecArray) => { + const states = getBlockStates(currentBlockName, currentBlockName === 'wall_sign' ? 'wall_sign' : 'sign') + if (!states) return + + const [, signMaterial = ''] = match + currentImage = await Jimp.read(`${dataBase}entity/${signMaterial ? `signs/${signMaterial}` : 'sign'}.png`) + // todo cache + const signTextures = { + // todo correct mapping + // todo alg to fit to the side + signboard_side: justCrop(0, 2, 2, 12), + face: justCrop(2, 2, 24, 12), + up: justCrop(2, 0, 24, 2), + support: justCrop(0, 16, 2, 14) + } + const blockTextures = await getBlockTexturesFromJimp(signTextures, true) + + const isWall = currentBlockName.includes('wall_') + const isHanging = currentBlockName.includes('hanging_') + const rotationState = states.find(state => state.name === 'rotation') + const faceTexture = { texture: blockTextures.face.texture, uv: blockTextures.face.uv } + if (isWall || isHanging) { + // todo isHanging + if (!isHanging) { + const facingState = states.find(state => state.name === 'facing')! + const facingMap = { + south: 0, + west: 90, + north: 180, + east: 270 + } + + currentMcAssets.blocksStates[currentBlockName] = { + "variants": Object.fromEntries( + facingState.values!.map((_val, i) => { + const val = _val as string + return [`facing=${val}`, { + "model": currentBlockName, + y: facingMap[val], + }] + }) + ) + } + currentMcAssets.blocksModels[currentBlockName] = { + elements: [ + { + // signboard + "from": [0, 4.5, 0], + "to": [16, 11.5, 1.5], + faces: { + south: faceTexture, + east: blockTextures.signboard_side, + west: blockTextures.signboard_side, + up: blockTextures.up, + down: blockTextures.up, + }, + } + ], + } + } + } else if (rotationState) { + currentMcAssets.blocksStates[currentBlockName] = { + "variants": Object.fromEntries( + Array.from({ length: 16 }).map((_val, i) => { + return [`rotation=${i}`, { + "model": currentBlockName, + y: i * (45 / 2), + }] + }) + ) + } + + const supportTexture = blockTextures.support + // TODO fix models.ts, apply textures for signs correctly! + // const supportTexture = { texture: supportTextureImg, uv: [0, 0, 16, 16] } + currentMcAssets.blocksModels[currentBlockName] = { + elements: [ + { + // support post + "from": [7.5, 0, 7.5], + "to": [8.5, 9, 8.5], + faces: { + // todo 14 + north: supportTexture, + east: supportTexture, + south: supportTexture, + west: supportTexture, + } + }, + { + // signboard + "from": [0, 9, 7.25], + "to": [16, 16, 8.75], + faces: { + north: faceTexture, + south: faceTexture, + east: blockTextures.signboard_side, + west: blockTextures.signboard_side, + up: blockTextures.up, + down: blockTextures.up, + }, + } + ], + } + } + twoBlockTextures.push(blockTextures.face.texture) + twoBlockTextures.push(blockTextures.up.texture) +} + +const chestModels = { + chest: { + "parent": "block/block", + "textures": { + "particle": "#particles" + }, + "elements": [ + { + "from": [1, 0, 1], + "to": [15, 10, 15], + "faces": { + "down": { "texture": "#chest", "uv": [3.5, 4.75, 7, 8.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [10.5, 8.25, 14, 10.75], "rotation": 180 }, + "east": { "texture": "#chest", "uv": [0, 8.25, 3.5, 10.75], "rotation": 180 }, + "south": { "texture": "#chest", "uv": [3.5, 8.25, 7, 10.75], "rotation": 180 }, + "west": { "texture": "#chest", "uv": [7, 8.25, 10.5, 10.75], "rotation": 180 } + }, + }, + { + "from": [1, 10, 1], + "to": [15, 14, 15], + "faces": { + "up": { "texture": "#chest", "uv": [3.5, 4.75, 7, 8.25] }, + "north": { "texture": "#chest", "uv": [10.5, 3.75, 14, 4.75], "rotation": 180 }, + "east": { "texture": "#chest", "uv": [0, 3.75, 3.5, 4.75], "rotation": 180 }, + "south": { "texture": "#chest", "uv": [3.5, 3.75, 7, 4.75], "rotation": 180 }, + "west": { "texture": "#chest", "uv": [7, 3.75, 10.5, 4.75], "rotation": 180 } + } + }, + { + "from": [7, 7, 0], + "to": [9, 11, 1], + "faces": { + "down": { "texture": "#chest", "uv": [0.25, 0, 0.75, 0.25], "rotation": 180 }, + "up": { "texture": "#chest", "uv": [0.75, 0, 1.25, 0.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [1, 0.25, 1.5, 1.25], "rotation": 180 }, + "west": { "texture": "#chest", "uv": [0.75, 0.25, 1, 1.25], "rotation": 180 }, + "east": { "texture": "#chest", "uv": [0, 0.25, 0.25, 1.25], "rotation": 180 } + } + } + ] + }, + chest_left: { + "parent": "block/block", + "textures": { + "particle": "#particles" + }, + "elements": [ + { + "from": [1, 0, 1], + "to": [16, 10, 15], + "faces": { + "down": { "texture": "#chest", "uv": [3.5, 4.75, 7.25, 8.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [10.75, 8.25, 14.5, 10.75], "rotation": 180 }, + "south": { "texture": "#chest", "uv": [3.5, 8.25, 7.25, 10.75], "rotation": 180 }, + "west": { "texture": "#chest", "uv": [7.25, 8.25, 10.75, 10.75], "rotation": 180 } + } + }, + { + "from": [1, 10, 1], + "to": [16, 14, 15], + "faces": { + "up": { "texture": "#chest", "uv": [3.5, 4.75, 7.25, 8.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [10.75, 3.75, 14.5, 4.75], "rotation": 180 }, + "south": { "texture": "#chest", "uv": [3.5, 3.75, 7.25, 4.75], "rotation": 180 }, + "west": { "texture": "#chest", "uv": [7.25, 3.75, 10.75, 4.75], "rotation": 180 } + } + }, + { + "from": [15, 7, 0], + "to": [16, 11, 1], + "faces": { + "down": { "texture": "#chest", "uv": [0.25, 0, 0.5, 0.25], "rotation": 180 }, + "up": { "texture": "#chest", "uv": [0.5, 0, 0.75, 0.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [0.75, 0.25, 1, 1.25], "rotation": 180 }, + "west": { "texture": "#chest", "uv": [0.5, 0.25, 0.75, 1.25], "rotation": 180 } + } + } + ] + }, + chest_right: { + "parent": "block/block", + "textures": { + "particle": "#particles" + }, + "elements": [ + { + "from": [0, 0, 1], + "to": [15, 10, 15], + "faces": { + "down": { "texture": "#chest", "uv": [3.5, 4.75, 7.25, 8.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [10.75, 8.25, 14.5, 10.75], "rotation": 180 }, + "east": { "texture": "#chest", "uv": [0, 8.25, 3.5, 10.75], "rotation": 180 }, + "south": { "texture": "#chest", "uv": [3.5, 8.25, 7.25, 10.75], "rotation": 180 } + } + }, + { + "from": [0, 10, 1], + "to": [15, 14, 15], + "faces": { + "up": { "texture": "#chest", "uv": [3.5, 4.75, 7.25, 8.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [10.75, 3.75, 14.5, 4.75], "rotation": 180 }, + "east": { "texture": "#chest", "uv": [0, 3.75, 3.5, 4.75], "rotation": 180 }, + "south": { "texture": "#chest", "uv": [3.5, 3.75, 7.25, 4.75], "rotation": 180 } + } + }, + { + "from": [0, 7, 0], + "to": [1, 11, 1], + "faces": { + "down": { "texture": "#chest", "uv": [0.25, 0, 0.5, 0.25], "rotation": 180 }, + "up": { "texture": "#chest", "uv": [0.5, 0, 0.75, 0.25], "rotation": 180 }, + "north": { "texture": "#chest", "uv": [0.75, 0.25, 1, 1.25], "rotation": 180 }, + "east": { "texture": "#chest", "uv": [0.0, 0.25, 0.25, 1.25], "rotation": 180 } + } + } + ] + } +} + +// these blockStates / models copied from https://github.com/FakeDomi/FastChest/blob/master/src/main/resources/assets/minecraft/blockstates/ +const chestBlockStatesMap = { + chest: JSON.parse(fs.readFileSync(path.join(__dirname, 'blockStates/chest.json'), 'utf-8')), + trapped_chest: JSON.parse(fs.readFileSync(path.join(__dirname, 'blockStates/trapped_chest.json'), 'utf-8')), + ender_chest: JSON.parse(fs.readFileSync(path.join(__dirname, 'blockStates/ender_chest.json'), 'utf-8')), +} +const handleChest = async (dataBase: string, match: RegExpExecArray) => { + const blockStates = structuredClone(chestBlockStatesMap[currentBlockName]) + + const particle = match[1] === 'ender' ? 'obsidian' : 'oak_planks' + + const blockStatesVariants = Object.values(blockStates.variants) as { model }[] + const neededModels = [...new Set(blockStatesVariants.map((x) => x.model))] + + for (const modelName of neededModels) { + let chestTextureName = { + chest: 'normal', + trapped_chest: 'trapped', + ender_chest: 'ender', + }[currentBlockName] + if (modelName.endsWith('_left')) chestTextureName = `${chestTextureName}_left` + if (modelName.endsWith('_right')) chestTextureName = `${chestTextureName}_right` + + // reading latest version since the texture wasn't changed, but in pre-flatenning need custom mapping for doubled_chest + const texture = path.join(currentMcAssets.directory, `../1.19.1/entity/chest/${chestTextureName}.png`) + + currentImage = await Jimp.read(texture) + + const model = structuredClone(chestModels[modelName]) + // todo < 1.9 + if (currentMcAssets.version === '1.8.8') { + // doesn't have definition of block yet + model.parent = undefined + } + model.textures.particle = particle + const newModelName = `${currentBlockName}_${modelName}` + for (const variant of blockStatesVariants) { + if (variant.model !== modelName) continue + variant.model = newModelName + } + for (const [i, { faces }] of model.elements.entries()) { + for (const [faceName, face] of Object.entries(faces) as any) { + const { uv } = face + //@ts-ignore + const jimp = justCropUV(...uv) + const key = `${chestTextureName}_${modelName}_${i}_${faceName}` + const texture = await getBlockTexturesFromJimp({ + [key]: jimp + }, true, key).then(a => a[key]) + face.texture = texture.texture + face.uv = texture.uv + } + } + currentMcAssets.blocksModels[newModelName] = model + } + currentMcAssets.blocksStates[currentBlockName] = blockStates +} + +const handlers = [ + [/(.+)_shulker_box$/, handleShulkerBox], + [/^shulker_box$/, handleShulkerBox], + [/^sign$/, handleSign], + [/^standing_sign$/, handleSign], + [/^wall_sign$/, handleSign], + [/(.+)_wall_sign$/, handleSign], + [/(.+)_sign$/, handleSign], + [/^(?:(ender|trapped)_)?chest$/, handleChest], + // no-op just suppress warning + [/(^light|^moving_piston$)/, true], +] as const + +export const tryHandleBlockEntity = async (dataBase, blockName) => { + currentBlockName = blockName + for (const [regex, handler] of handlers) { + const match = regex.exec(blockName) + if (!match) continue + if (handler !== true) { + await handler(dataBase, match) + } + return true + } +} + +export const prepareMoreGeneratedBlocks = async (mcAssets: McAssets) => { + const mcData = minecraftData(mcAssets.version) + //@ts-expect-error + isPreFlattening = !mcData.supportFeature('blockStateId') + const allTheBlocks = mcData.blocksArray.map(x => x.name) + + currentMcAssets = mcAssets + const handledBlocks = ['water', 'lava', 'barrier'] + // todo + const ignoredBlocks = ['skull', 'structure_void', 'banner', 'bed', 'end_portal'] + + for (const theBlock of allTheBlocks) { + try { + if (await tryHandleBlockEntity(mcAssets.directory, theBlock)) { + handledBlocks.push(theBlock) + } + } catch (err) { + // todo remove when all warnings are resolved + console.warn(`[${mcAssets.version}] failed to generate block ${theBlock}`) + } + } + + const warnings: string[] = [] + for (const [name, model] of Object.entries(mcAssets.blocksModels)) { + if (Object.keys(model).length === 1 && model.textures) { + const keys = Object.keys(model.textures) + if (keys.length === 1 && keys[0] === 'particle') { + if (handledBlocks.includes(name) || ignoredBlocks.includes(name)) continue + warnings.push(`unhandled block ${name}`) + } + } + } + + return { warnings } +} + +export const getAdditionalTextures = () => { + return { generated: generatedImageTextures, twoBlockTextures } +} diff --git a/prismarine-viewer/viewer/sign-renderer/index.html b/prismarine-viewer/viewer/sign-renderer/index.html new file mode 100644 index 000000000..671ad05f2 --- /dev/null +++ b/prismarine-viewer/viewer/sign-renderer/index.html @@ -0,0 +1,21 @@ + + + + + + + %VITE_NAME% + + + + +
test
+ + + + diff --git a/prismarine-viewer/viewer/sign-renderer/index.ts b/prismarine-viewer/viewer/sign-renderer/index.ts new file mode 100644 index 000000000..164c01a84 --- /dev/null +++ b/prismarine-viewer/viewer/sign-renderer/index.ts @@ -0,0 +1,124 @@ +import { fromFormattedString, render, RenderNode, TextComponent } from '@xmcl/text-component' +import type { ChatMessage } from 'prismarine-chat' + +type SignBlockEntity = { + Color?: string + GlowingText?: 0 | 1 + Text1?: string + Text2?: string + Text3?: string + Text4?: string +} | { + // todo + is_waxed: 0 | 1 + front_text: { + // todo + // has_glowing_text: 0 | 1 + color: string + messages: string[] + } + // todo + // back_text: {} +} + +type JsonEncodedType = string | null | Record + +const parseSafe = (text: string, task: string) => { + try { + return JSON.parse(text) + } catch (e) { + console.warn(`Failed to parse ${task}`, e) + return null + } +} + +export const renderSign = (blockEntity: SignBlockEntity, PrismarineChat: typeof ChatMessage, ctxHook = (ctx) => { }) => { + const canvas = document.createElement('canvas') + + const factor = 50 + const signboardY = [16, 9] + const heightOffset = signboardY[0] - signboardY[1] + const heightScalar = heightOffset / 16 + + canvas.width = 16 * factor + canvas.height = heightOffset * factor + + const ctx = canvas.getContext('2d')! + ctx.imageSmoothingEnabled = false + + ctxHook(ctx) + + const texts = 'is_waxed' in blockEntity ? /* > 1.20 */ blockEntity.front_text.messages : [ + blockEntity.Text1, + blockEntity.Text2, + blockEntity.Text3, + blockEntity.Text4 + ] + const defaultColor = ('front_text' in blockEntity ? blockEntity.front_text.color : blockEntity.Color) || 'black' + for (let [lineNum, text] of texts.slice(0, 4).entries()) { + // todo test mojangson parsing + const parsed = parseSafe(text ?? '""', 'sign text') + if (!parsed || (typeof parsed !== 'object' && typeof parsed !== 'string')) continue + // todo fix type + const message = typeof parsed === 'string' ? fromFormattedString(parsed) : new PrismarineChat(parsed) as never + const patchExtra = ({ extra }: TextComponent) => { + if (!extra) return + for (const child of extra) { + if (child.color) { + child.color = child.color === 'dark_green' ? child.color.toUpperCase() : child.color.toLowerCase() + } + patchExtra(child) + } + } + patchExtra(message) + const rendered = render(message) + + const toRenderCanvas: { + fontStyle: string + fillStyle: string + underlineStyle: boolean + strikeStyle: boolean + text: string + }[] = [] + let plainText = '' + const MAX_LENGTH = 15 // avoid abusing the signboard + const renderText = (node: RenderNode) => { + const { component } = node + let { text } = component + if (plainText.length + text.length > MAX_LENGTH) { + text = text.slice(0, MAX_LENGTH - plainText.length) + if (!text) return false + } + plainText += text + toRenderCanvas.push({ + fontStyle: `${component.bold ? 'bold' : ''} ${component.italic ? 'italic' : ''}`, + fillStyle: node.style['color'] || defaultColor, + underlineStyle: component.underlined ?? false, + strikeStyle: component.strikethrough ?? false, + text + }) + for (const child of node.children) { + const stop = renderText(child) === false + if (stop) return false + } + } + renderText(rendered) + + const fontSize = 1.6 * factor; + ctx.font = `${fontSize}px mojangles` + const textWidth = ctx.measureText(plainText).width + + let renderedWidth = 0 + for (const { fillStyle, fontStyle, strikeStyle, text, underlineStyle } of toRenderCanvas) { + // todo strikeStyle, underlineStyle + ctx.fillStyle = fillStyle + ctx.font = `${fontStyle} ${fontSize}px mojangles` + ctx.fillText(text, (canvas.width - textWidth) / 2 + renderedWidth, fontSize * (lineNum + 1)) + renderedWidth += ctx.measureText(text).width // todo isn't the font is monospace? + } + } + // ctx.fillStyle = 'red' + // ctx.fillRect(0, 0, canvas.width, canvas.height) + + return canvas +} diff --git a/prismarine-viewer/viewer/sign-renderer/noop.js b/prismarine-viewer/viewer/sign-renderer/noop.js new file mode 100644 index 000000000..4ba52ba2c --- /dev/null +++ b/prismarine-viewer/viewer/sign-renderer/noop.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/prismarine-viewer/viewer/sign-renderer/package.json b/prismarine-viewer/viewer/sign-renderer/package.json new file mode 100644 index 000000000..135782e20 --- /dev/null +++ b/prismarine-viewer/viewer/sign-renderer/package.json @@ -0,0 +1,14 @@ +{ + "name": "sign-renderer", + "version": "0.0.0", + "private": true, + "license": "MIT", + "main": "index.ts", + "scripts": { + "start": "vite" + }, + "dependencies": { + "@xmcl/text-component": "^2.1.2", + "vite": "^4.4.9" + } +} diff --git a/prismarine-viewer/viewer/sign-renderer/playground.ts b/prismarine-viewer/viewer/sign-renderer/playground.ts new file mode 100644 index 000000000..f8c239b25 --- /dev/null +++ b/prismarine-viewer/viewer/sign-renderer/playground.ts @@ -0,0 +1,26 @@ +import { renderSign } from '.' +import PrismarineChatLoader from 'prismarine-chat' + +const PrismarineChat = PrismarineChatLoader({ language: {} } as any) + +const img = new Image() +img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAMCAYAAAB4MH11AAABbElEQVR4AY3BQY6cMBBA0Q+yQZZVi+ndcJVcKGfMgegdvShKVtuokzGSWwwiUd7rfv388Vst0UgMXCobmgsSA5VaQmKgUks0EgNHji8SA9W8GJCQwVNpLhzJ4KFs4B1HEgPVvBiQkMFTaS44tYTEQDXdIkfiHbuyobmguaDPFzIWGrWExEA13SJH4h1uzS/WbPyvroM1v6jWbFRrNv7GfX5EdmXjzTvUEjJ4zjQXjiQGdmXjzTvUEjJ4HF/UEt/kQqW5UEkMzIshY08jg6dRS3yTC5XmgpsXY7pFztQSEgPNJCNv3lGpJVSfTLfImVpCYsB1HdwfxpU1G9eeNF0H94dxZc2G+/yI7MoG3vEv82LI2NNIDLyVDbzjzFE2mnkxZOy5IoNnkpFGc2FXNpp5MWTsOXJ4h1qikrGnkhjYlY1m1icy9lQSA+TCzjvUEpWMPZXEwK5suPvDOFuzcdZ1sOYX1ZqNas3GlTUbzR+jQbEAcs8ZQAAAAABJRU5ErkJggg==' + +await new Promise(resolve => { + img.onload = () => resolve() +}) + +const blockEntity = { + "GlowingText": 0, + "Color": "black", + "Text4": "{\"text\":\"\"}", + "Text3": "{\"text\":\"\"}", + "Text2": "{\"text\":\"\"}", + "Text1": "{\"extra\":[{\"color\":\"dark_green\",\"text\":\"Minecraft \"},{\"text\":\"Tools\"}],\"text\":\"\"}" +} as const + +const canvas = renderSign(blockEntity, PrismarineChat, (ctx) => { + ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height) +}) + +document.body.appendChild(canvas) diff --git a/prismarine-viewer/viewer/sign-renderer/vite.config.ts b/prismarine-viewer/viewer/sign-renderer/vite.config.ts new file mode 100644 index 000000000..896ac8651 --- /dev/null +++ b/prismarine-viewer/viewer/sign-renderer/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' + +export default defineConfig({ + resolve: { + alias: { + 'prismarine-registry': "./noop.js", + 'prismarine-nbt': "./noop.js" + }, + }, +}) diff --git a/scripts/build.js b/scripts/build.js index 90cc36c4f..f73a2a60c 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -17,15 +17,17 @@ const filesToCopy = [ { from: `${prismarineViewerBase}/public/textures/1.16.4/entity`, to: 'dist/textures/1.16.4/entity' }, ] exports.filesToCopy = filesToCopy -exports.copyFiles = () => { +exports.copyFiles = (dev = false) => { console.time('copy files') - // copy glob - const cwd = `${prismarineViewerBase}/public/textures/` - const files = glob.sync('*.png', { cwd: cwd, nodir: true, }) - for (const file of files) { - const copyDest = path.join('dist/textures/', file) - fs.mkdirSync(path.dirname(copyDest), { recursive: true, }) - fs.copyFileSync(path.join(cwd, file), copyDest) + if (!dev) { + // copy glob + const cwd = `${prismarineViewerBase}/public/textures/` + const files = glob.sync('*.png', { cwd: cwd, nodir: true, }) + for (const file of files) { + const copyDest = path.join('dist/textures/', file) + fs.mkdirSync(path.dirname(copyDest), { recursive: true, }) + fs.copyFileSync(path.join(cwd, file), copyDest) + } } filesToCopy.forEach(file => { diff --git a/server.js b/server.js index d89018c9d..a542dd6e4 100644 --- a/server.js +++ b/server.js @@ -11,29 +11,24 @@ const fs = require('fs') // Create our app const app = express() +const isProd = process.argv.includes('--prod') app.use(compression()) app.use(netApi({ allowOrigin: '*' })) -if (process.argv[3] === 'dev') { - // https://webpack.js.org/guides/development/#using-webpack-dev-middleware - const webpackDevMiddleware = require('webpack-dev-middleware') - const config = require('./webpack.dev.js') - const webpack = require('webpack') - const compiler = webpack(config) - - app.use( - webpackDevMiddleware(compiler, { - publicPath: config.output.publicPath - }) - ) -} else { - app.use(express.static(path.join(__dirname, './dist'))) +let lastVersion = '' +app.post('/lastVersion', (req, res) => { + res.send(lastVersion.toString()) +}) +if (!isProd) { + app.use('/blocksStates', express.static(path.join(__dirname, './prismarine-viewer/public/blocksStates'))) + app.use('/textures', express.static(path.join(__dirname, './prismarine-viewer/public/textures'))) } +app.use(express.static(path.join(__dirname, './dist'))) const portArg = process.argv.indexOf('--port') const port = (require.main === module ? process.argv[2] : portArg !== -1 ? process.argv[portArg + 1] : undefined) || 8080 // Start the server -const server = process.argv.includes('--prod') ? +const server = isProd ? undefined : app.listen(port, function () { console.log('Server listening on port ' + server.address().port) diff --git a/src/blockInteraction.js b/src/blockInteraction.js index d8aa0dc82..a88a8cb2e 100644 --- a/src/blockInteraction.js +++ b/src/blockInteraction.js @@ -127,7 +127,6 @@ class BlockInteraction { } // todo this shouldnt be done in the render loop, migrate the code to dom events to avoid delays on lags - // eslint-disable-next-line complexity update () { const cursorBlock = bot.blockAtCursor(5) let cursorBlockDiggable = cursorBlock diff --git a/src/chat.js b/src/chat.js index ed99c876f..aa7b362e2 100644 --- a/src/chat.js +++ b/src/chat.js @@ -360,7 +360,10 @@ class ChatBox extends LitElement { this.completePadText = completeValue === '/' ? '' : completeValue if (this.completeRequestValue === completeValue) { const lastWord = chatInput.value.split(' ').at(-1) - this.completionItems = this.completionItemsSource.filter(i => i.startsWith(lastWord)) + this.completionItems = this.completionItemsSource.filter(i => { + const compareableParts = i.split(/[_:]/) + return compareableParts.some(compareablePart => compareablePart.startsWith(lastWord)) + }) return } this.completeRequestValue = '' diff --git a/src/controls.ts b/src/controls.ts index fe49a55dd..e0d8ad28a 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -8,6 +8,7 @@ import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types import { stringStartsWith } from 'contro-max/build/stringUtils' import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCurrentModal } from './globalState' import { reloadChunks } from './utils' +import { options } from './optionsStorage' // doesnt seem to work for now const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) @@ -48,12 +49,12 @@ export const contro = new ControMax({ }, }, { target: document, - captureEvents() { + captureEvents () { return bot && isGameActive(false) }, storeProvider: { load: () => customKeymaps, - save() { }, + save () { }, }, gamepadPollingInterval: 10 }) @@ -103,10 +104,10 @@ let lastCommandTrigger = null as { command: string, time: number } | null const secondActionActivationTimeout = 300 const secondActionCommands = { - 'general.jump'() { + 'general.jump' () { toggleFly() }, - 'general.forward'() { + 'general.forward' () { setSprinting(true) } } @@ -228,16 +229,28 @@ document.addEventListener('keydown', (e) => { for (const [x, z] of loadedChunks) { worldView.unloadChunk({ x, z }) } + if (localServer) { + localServer.players[0].world.columns = {} + } reloadChunks() } + if (e.code === 'KeyG') { + // todo make it work without reload + options.showChunkBorders = !options.showChunkBorders + } + return } - if (hardcodedPressedKeys.has(e.code)) return hardcodedPressedKeys.add(e.code) }) document.addEventListener('keyup', (e) => { hardcodedPressedKeys.delete(e.code) }) +document.addEventListener('visibilitychange', (e) => { + if (document.visibilityState === 'hidden') { + hardcodedPressedKeys.clear() + } +}) // #region creative fly // these controls are more like for gamemode 3 diff --git a/src/createLocalServer.ts b/src/createLocalServer.ts index 0ea0e31c2..80874bbfd 100644 --- a/src/createLocalServer.ts +++ b/src/createLocalServer.ts @@ -8,6 +8,7 @@ export const startLocalServer = () => { const server = mcServer.createMCServer(passOptions) server.formatMessage = (message) => `[server] ${message}` server.options = passOptions + server.looseProtocolMode = true return server } diff --git a/src/globals.d.ts b/src/globals.d.ts index 84a2ade58..ee7ff5818 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -2,9 +2,10 @@ declare const THREE: typeof import('three') // todo make optional -declare const bot: import('mineflayer').Bot +declare const bot: Omit & { world: import('prismarine-world').world.WorldSync } +declare const __type_bot: typeof bot declare const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer | undefined -declare const worldView: import('prismarine-viewer/viewer/lib/worldView').WorldView | undefined +declare const worldView: import('prismarine-viewer/viewer/lib/worldDataEmitter').WorldDataEmitter | undefined declare const localServer: any declare interface Document { diff --git a/src/index.ts b/src/index.ts index 3857ea020..dedeab59b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,12 +33,12 @@ import { contro } from './controls' import './dragndrop' import './browserfs' import './eruda' -import './watchOptions' +import { watchOptionsAfterViewerInit } from './watchOptions' import downloadAndOpenFile from './downloadAndOpenFile' import net from 'net' import mineflayer from 'mineflayer' -import { WorldView, Viewer } from 'prismarine-viewer/viewer' +import { WorldDataEmitter, Viewer } from 'prismarine-viewer/viewer' import pathfinder from 'mineflayer-pathfinder' import { Vec3 } from 'vec3' @@ -62,7 +62,6 @@ import { import { pointerLock, goFullscreen, isCypress, - loadScript, toMajorVersion, setLoadingScreenStatus, setRenderDistance @@ -85,22 +84,16 @@ import { genTexturePackTextures, watchTexturepackInViewer } from './texturePack' import { connectToPeer } from './localServerMultiplayer' import CustomChannelClient from './customClient' import debug from 'debug' +import { loadScript } from 'prismarine-viewer/viewer/lib/utils' +import { registerServiceWorker } from './serviceWorker' window.debug = debug window.THREE = THREE -if ('serviceWorker' in navigator && !isCypress() && process.env.NODE_ENV !== 'development') { - window.addEventListener('load', () => { - navigator.serviceWorker.register('./service-worker.js').then(registration => { - console.log('SW registered:', registration) - }).catch(registrationError => { - console.log('SW registration failed:', registrationError) - }) - }) -} - // ACTUAL CODE +void registerServiceWorker() + // Create three.js context, add to page const renderer = new THREE.WebGLRenderer({ powerPreference: options.highPerformanceGpu ? 'high-performance' : 'default', @@ -115,6 +108,10 @@ document.body.appendChild(renderer.domElement) // Create viewer const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer, options.numWorkers) window.viewer = viewer +viewer.entities.entitiesOptions = { + fontFamily: 'mojangles' +} +watchOptionsAfterViewerInit() initPanoramaOptions(viewer) watchTexturepackInViewer(viewer) @@ -167,7 +164,7 @@ const updateCursor = () => { debugMenu ??= hud.shadowRoot.querySelector('#debug-overlay') debugMenu.cursorBlock = blockInteraction.cursorBlock } -function onCameraMove(e) { +function onCameraMove (e) { if (e.type !== 'touchmove' && !pointerLock.hasPointerLock) return e.stopPropagation?.() const now = performance.now() @@ -180,18 +177,17 @@ function onCameraMove(e) { x: e.movementX * mouseSensX * 0.0001, y: e.movementY * mouseSensY * 0.0001 }) - // todo do it also on every block update within radius 5 updateCursor() } window.addEventListener('mousemove', onCameraMove, { capture: true }) -function hideCurrentScreens() { +function hideCurrentScreens () { activeModalStacks['main-menu'] = [...activeModalStack] insertActiveModalStack('', []) } -async function main() { +async function main () { const menu = document.getElementById('play-screen') menu.addEventListener('connect', e => { const options = e.detail @@ -239,7 +235,7 @@ const cleanConnectIp = (host: string | undefined, defaultPort: string | undefine } } -async function connect(connectOptions: { +async function connect (connectOptions: { server?: string; singleplayer?: any; username?: string; password?: any; proxy?: any; botVersion?: any; serverOverrides?; peerId?: string }) { document.getElementById('play-screen').style = 'display: none;' @@ -260,24 +256,21 @@ async function connect(connectOptions: { setLoadingScreenStatus('Logging in') let ended = false - let bot: mineflayer.Bot + let bot: typeof __type_bot const destroyAll = () => { if (ended) return ended = true - // ensure bot cleanup viewer.resetAll() window.localServer = undefined - // simple variant, still buggy postRenderFrameFn = () => { } if (bot) { bot.end() + // ensure mineflayer plugins receive this even for cleanup bot.emit('end', '') bot.removeAllListeners() bot._client.removeAllListeners() bot._client = undefined - // for debugging - window._botDisconnected = undefined window.bot = bot = undefined } removeAllListeners() @@ -319,7 +312,7 @@ async function connect(connectOptions: { }) if (proxy) { - console.log(`using proxy ${proxy.host}:${proxy.port}`) + console.log(`using proxy ${proxy.host}${proxy.port && `:${proxy.port}`}`) net['setProxy']({ hostname: proxy.host, port: proxy.port }) } @@ -334,12 +327,13 @@ async function connect(connectOptions: { await genTexturePackTextures(version) } catch (err) { console.error(err) - const doContinue = prompt('Failed to apply texture pack. See errors in the console. Continue?') + const doContinue = confirm('Failed to apply texture pack. See errors in the console. Continue?') if (!doContinue) { - setLoadingScreenStatus(undefined) + throw err } } await loadScript(`./mc-data/${toMajorVersion(version)}.js`) + viewer.setVersion(version) } const downloadVersion = connectOptions.botVersion || (singeplayer ? serverOptions.version : undefined) @@ -371,7 +365,7 @@ async function connect(connectOptions: { } } - setLoadingScreenStatus('Creating mineflayer bot') + setLoadingScreenStatus('Connecting to server') bot = mineflayer.createBot({ host: server.host, port: +server.port, @@ -384,7 +378,7 @@ async function connect(connectOptions: { } : {}, ...singeplayer ? { version: serverOptions.version, - connect() { }, + connect () { }, Client: CustomChannelClient as any, } : {}, username, @@ -394,7 +388,7 @@ async function connect(connectOptions: { noPongTimeout: 240 * 1000, closeTimeout: 240 * 1000, respawn: options.autoRespawn, - async versionSelectedHook(client) { + async versionSelectedHook (client) { // todo keep in sync with esbuild preload, expose cache ideally if (client.version === '1.20.1') { // ignore cache hit @@ -403,7 +397,7 @@ async function connect(connectOptions: { await downloadMcData(client.version) setLoadingScreenStatus('Connecting to server') } - }) + }) as unknown as typeof __type_bot window.bot = bot if (singeplayer || p2pMultiplayer) { // p2pMultiplayer still uses the same flying-squid server @@ -465,9 +459,10 @@ async function connect(connectOptions: { }) bot.on('end', (endReason) => { + if (ended) return console.log('disconnected for', endReason) - destroyAll() setLoadingScreenStatus(`You have been disconnected from the server. End reason: ${endReason}`, true) + destroyAll() if (isCypress()) throw new Error(`disconnected: ${endReason}`) }) @@ -493,8 +488,7 @@ async function connect(connectOptions: { const center = bot.entity.position - const worldView: import('prismarine-viewer/viewer/lib/worldView').WorldView = new WorldView(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center) - window.worldView = worldView + const worldView = window.worldView = new WorldDataEmitter(bot.world, singeplayer ? renderDistance : Math.min(renderDistance, maxMultiplayerRenderDistance), center) setRenderDistance() const updateFov = () => { @@ -519,7 +513,6 @@ async function connect(connectOptions: { }) bot.on('physicsTick', () => updateCursor()) - viewer.setVersion(version) const debugMenu = hud.shadowRoot.querySelector('#debug-overlay') @@ -542,7 +535,7 @@ async function connect(connectOptions: { debugMenu.rendererDevice = '???' } - // Link WorldView and Viewer + // Link WorldDataEmitter and Viewer viewer.listen(worldView) worldView.listenToBot(bot) worldView.init(bot.entity.position) @@ -550,7 +543,7 @@ async function connect(connectOptions: { dayCycle() // Bot position callback - function botPosition() { + function botPosition () { // this might cause lag, but not sure viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) worldView.updatePosition(bot.entity.position) @@ -568,7 +561,7 @@ async function connect(connectOptions: { bot.entity.yaw -= x } - function changeCallback() { + function changeCallback () { notification.show = false if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { showModal(pauseMenu) @@ -655,7 +648,7 @@ async function connect(connectOptions: { onCameraMove({ movementX: x * 10, movementY: z * 10, type: 'touchmove' }) }) - registerListener(document, 'lostpointercapture', (e) => { + const pointerUpHandler = (e: PointerEvent) => { if (e.pointerId === undefined || e.pointerId !== capturedPointer?.id) return clearTimeout(virtualClickTimeout) virtualClickTimeout = undefined @@ -670,15 +663,11 @@ async function connect(connectOptions: { document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) } capturedPointer = undefined - }, { passive: false }) - - registerListener(document, 'pointerup', (e) => { - const clickedEl = e.composedPath()[0] - if (!isGameActive(true) || !miscUiState.currentTouch || clickedEl !== cameraControlEl || e.pointerId === undefined) { - return - } screenTouches-- - }) + } + registerListener(document, 'pointerup', pointerUpHandler) + registerListener(document, 'pointercancel', pointerUpHandler) + registerListener(document, 'lostpointercapture', pointerUpHandler) registerListener(document, 'contextmenu', (e) => e.preventDefault(), false) @@ -686,7 +675,6 @@ async function connect(connectOptions: { bot.clearControlStates() }, false) - setLoadingScreenStatus('Done!') console.log('Done!') hud.init(renderer, bot, server.host) @@ -694,16 +682,13 @@ async function connect(connectOptions: { blockInteraction.init() errorAbortController.abort() - setTimeout(() => { - if (loadingScreen.hasError) return - // remove loading screen, wait a second to make sure a frame has properly rendered - setLoadingScreenStatus(undefined) - void viewer.waitForChunksToRender().then(() => { - console.log('All done and ready!') - document.dispatchEvent(new Event('cypress-world-ready')) - }) - miscUiState.gameLoaded = true - }, singeplayer ? 0 : 2500) + if (loadingScreen.hasError) return + setLoadingScreenStatus(undefined) + miscUiState.gameLoaded = true + void viewer.waitForChunksToRender().then(() => { + console.log('All done and ready!') + document.dispatchEvent(new Event('cypress-world-ready')) + }) }) } diff --git a/src/loadSave.ts b/src/loadSave.ts index c0260279c..6c06cad7e 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -20,7 +20,6 @@ export const fsState = proxy({ const PROPOSE_BACKUP = true -// eslint-disable-next-line complexity export const loadSave = async (root = '/world') => { const disablePrompts = options.disableLoadPrompts diff --git a/src/menus/components/breath_bar.js b/src/menus/components/breath_bar.js index 407fee12b..b6fcfd9c5 100644 --- a/src/menus/components/breath_bar.js +++ b/src/menus/components/breath_bar.js @@ -36,8 +36,8 @@ class BreathBar extends LitElement { ` } - gameModeChanged (gamemode) { - this.shadowRoot.querySelector('#breathbar').classList.toggle('creative', gamemode === 1) + gameModeChanged () { + this.shadowRoot.querySelector('#breathbar').classList.toggle('creative', bot.game.gameMode === 'creative' || bot.game.gameMode === 'spectator') } updateOxygen (hValue) { diff --git a/src/menus/components/health_bar.js b/src/menus/components/health_bar.js index 4c0306716..8b9947ada 100644 --- a/src/menus/components/health_bar.js +++ b/src/menus/components/health_bar.js @@ -110,7 +110,7 @@ class HealthBar extends LitElement { } gameModeChanged (gamemode, hardcore) { - this.shadowRoot.querySelector('#health').classList.toggle('creative', gamemode === 1) + this.shadowRoot.querySelector('#health').classList.toggle('creative', bot.game.gameMode === 'creative' || bot.game.gameMode === 'spectator') this.shadowRoot.querySelector('#health').classList.toggle('hardcore', hardcore) } diff --git a/src/menus/hud.js b/src/menus/hud.js index 1a536ff26..eb13b7f46 100644 --- a/src/menus/hud.js +++ b/src/menus/hud.js @@ -264,7 +264,8 @@ class Hud extends LitElement { healthbar.gameModeChanged(gamemode, bot.game.hardcore) foodbar.gameModeChanged(gamemode) // breathbar.gameModeChanged(gamemode) - this.shadowRoot.querySelector('#xp-bar-bg').style.display = gamemode === 1 ? 'none' : 'block' + const creativeLike = gamemode === 1 || gamemode === 3 + this.shadowRoot.querySelector('#xp-bar-bg').style.display = creativeLike ? 'none' : 'block' } bot.on('game', onGameModeChange) onGameModeChange() diff --git a/src/menus/play_screen.js b/src/menus/play_screen.js index 8dec17439..9f12d8687 100644 --- a/src/menus/play_screen.js +++ b/src/menus/play_screen.js @@ -178,7 +178,7 @@ class PlayScreen extends LitElement { pmui-inputmode="decimal" state="${this.version && (fullySupporedVersions.includes(/** @type {any} */(this.version)) ? '' : Object.keys(versionsByMinecraftVersion.pc).includes(this.version) ? 'warning' : 'invalid')}" .autocompleteValues=${fullySupporedVersions} - @input=${e => { this.version = e.target.value }} + @input=${e => { this.version = e.target.value = e.target.value.replaceAll(',', '.') }} >

Leave blank and it will be chosen automatically

diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index d6428a5e2..427c5f3ed 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -27,6 +27,7 @@ const defaultOptions = { touchButtonsSize: 40, highPerformanceGpu: false, + showChunkBorders: false, frameLimit: false as number | false, alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null, alwaysShowMobileControls: false, @@ -61,7 +62,7 @@ type WatchValue = >(proxy: T, callback: (p: T) => export const watchValue: WatchValue = (proxy, callback) => { const watchedProps = new Set() callback(new Proxy(proxy, { - get(target, p, receiver) { + get (target, p, receiver) { watchedProps.add(p.toString()) return Reflect.get(target, p, receiver) }, diff --git a/src/serviceWorker.ts b/src/serviceWorker.ts new file mode 100644 index 000000000..2fe00bee0 --- /dev/null +++ b/src/serviceWorker.ts @@ -0,0 +1,23 @@ +import { isCypress } from './utils' + +export const registerServiceWorker = async () => { + if (!('serviceWorker' in navigator)) return + if (!isCypress() && process.env.NODE_ENV !== 'development') { + window.addEventListener('load', () => { + navigator.serviceWorker.register('./service-worker.js').then(registration => { + console.log('SW registered:', registration) + }).catch(registrationError => { + console.log('SW registration failed:', registrationError) + }) + }) + } else { + // force unregister service worker in development mode + const registrations = await navigator.serviceWorker.getRegistrations() + for (const registration of registrations) { + await registration.unregister() // eslint-disable-line no-await-in-loop + } + if (registrations.length) { + location.reload() + } + } +} diff --git a/src/texturePack.ts b/src/texturePack.ts index 83a783186..77420d401 100644 --- a/src/texturePack.ts +++ b/src/texturePack.ts @@ -200,7 +200,7 @@ export const genTexturePackTextures = async (version: string) => { } // we get the size of image from the first block file, which is not ideal but works in 99% cases - const tileSize = await getSizeFromImage(join(blocksBasePath, firstBlockFile)) + const tileSize = Math.max(originalTileSize, await getSizeFromImage(join(blocksBasePath, firstBlockFile))) const imgSize = texSize * tileSize diff --git a/src/utils.ts b/src/utils.ts index 9213cdb68..389970360 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -161,34 +161,11 @@ export const disconnect = async () => { } else { // workaround bot.end doesn't end the socket and emit end event bot.end() - bot._client.socket?.end?.() } bot._client.emit('end', 'You left the server') miscUiState.gameLoaded = false } -export const loadScript = async function (scriptSrc: string) { - if (document.querySelector(`script[src="${scriptSrc}"]`)) { - return - } - - return new Promise((resolve, reject) => { - const scriptElement = document.createElement('script') - scriptElement.src = scriptSrc - scriptElement.async = true - - scriptElement.addEventListener('load', () => { - resolve(scriptElement) - }) - - scriptElement.onerror = (error) => { - reject(error) - } - - document.head.appendChild(scriptElement) - }) -} - // doesn't support snapshots export const toMajorVersion = (version) => { const [a, b] = (String(version)).split('.') diff --git a/src/watchOptions.ts b/src/watchOptions.ts index de5450330..026c43cb2 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -1,7 +1,13 @@ // not all options are watched here import { subscribeKey } from 'valtio/utils' -import { options } from './optionsStorage' +import { options, watchValue } from './optionsStorage' import { reloadChunks } from './utils' subscribeKey(options, 'renderDistance', reloadChunks) + +export const watchOptionsAfterViewerInit = () => { + watchValue(options, o => { + viewer.world.showChunkBorders = o.showChunkBorders + }) +}