diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f913b9b6..e80b7100 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -26,7 +26,7 @@ jobs: uses: pnpm/action-setup@v4 - uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 22 cache: "pnpm" - name: Move Cypress to dependencies run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cbf52251..3e8c4136 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,20 @@ jobs: publish_dir: .vercel/output/static force_orphan: true + # Create CNAME file for custom domain + - name: Create CNAME file + run: echo "github.mcraft.fun" > .vercel/output/static/CNAME + + - name: Deploy to mwc-mcraft-pages repository + uses: peaceiris/actions-gh-pages@v3 + with: + personal_token: ${{ secrets.MCW_MCRAFT_PAGE_DEPLOY_TOKEN }} + external_repository: ${{ github.repository_owner }}/mwc-mcraft-pages + publish_dir: .vercel/output/static + publish_branch: main + destination_dir: docs + force_orphan: true + - name: Change index.html title run: | # change Minecraft Web Client to Minecraft Web Client — Free Online Browser Version diff --git a/README.MD b/README.MD index 7978cee5..018784e3 100644 --- a/README.MD +++ b/README.MD @@ -78,6 +78,8 @@ There is a builtin proxy, but you can also host your one! Just clone the repo, r [![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F) +> **Note**: If you want to make **your own** Minecraft server accessible to web clients (without our proxies), you can use [mwc-proxy](https://github.com/zardoy/mwc-proxy) - a lightweight JS WebSocket proxy that runs on the same server as your Minecraft server, allowing players to connect directly via `wss://play.example.com`. `?client_mcraft` is added to the URL, so the proxy will know that it's this client. + Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser. ```mermaid @@ -176,6 +178,7 @@ Server specific: - `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes. - `?autoConnect=true` - Only works then `ip` and `version` parameters are set and `allowAutoConnect` is `true` in config.json! Directly connects to the specified server. Useful for integrates iframes. - `?serversList=` - `` can be a list of servers in the format `ip:version,ip` or a url to a json file with the same format (array) or a txt file with line-delimited list of server IPs. +- `?addPing=` - Add a latency to both sides of the connection. Useful for testing ping issues. For example `?addPing=100` will add 200ms to your ping. Single player specific: @@ -232,3 +235,4 @@ Only during development: - [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true) - [https://m.eaglercraft.com/](EaglerCraft) - Eaglercraft runnable on mobile (real Minecraft in the browser) +- [js-minecraft](https://github.com/LabyStudio/js-minecraft) - An insanely well done clone from the graphical side that inspired many features here diff --git a/config.json b/config.json index 940fb738..2bfa9cfe 100644 --- a/config.json +++ b/config.json @@ -10,6 +10,10 @@ { "ip": "wss://play.mcraft.fun" }, + { + "ip": "wss://play.webmc.fun", + "name": "WebMC" + }, { "ip": "wss://ws.fuchsmc.net" }, diff --git a/package.json b/package.json index 1d9dfc0c..ff673726 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "dependencies": { "@dimaka/interface": "0.0.3-alpha.0", "@floating-ui/react": "^0.26.1", + "@monaco-editor/react": "^4.7.0", "@nxg-org/mineflayer-auto-jump": "^0.7.18", "@nxg-org/mineflayer-tracker": "1.3.0", "@react-oauth/google": "^0.12.1", @@ -79,14 +80,14 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "filesize": "^10.0.12", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.62", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.104", "framer-motion": "^12.9.2", "fs-extra": "^11.1.1", "google-drive-browserfs": "github:zardoy/browserfs#google-drive", "jszip": "^3.10.1", "lodash-es": "^4.17.21", "mcraft-fun-mineflayer": "^0.1.23", - "minecraft-data": "3.92.0", + "minecraft-data": "3.98.0", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -156,7 +157,7 @@ "mc-assets": "^0.2.62", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.17", + "mineflayer-mouse": "^0.1.21", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", @@ -204,7 +205,7 @@ "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", "prismarine-world": "github:zardoy/prismarine-world#next-era", - "minecraft-data": "3.92.0", + "minecraft-data": "3.98.0", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "prismarine-physics": "github:zardoy/prismarine-physics", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7e1d1b9..5bcd74a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ overrides: diamond-square: github:zardoy/diamond-square prismarine-block: github:zardoy/prismarine-block#next-era prismarine-world: github:zardoy/prismarine-world#next-era - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything prismarine-physics: github:zardoy/prismarine-physics minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master @@ -42,6 +42,9 @@ importers: '@floating-ui/react': specifier: ^0.26.1 version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@nxg-org/mineflayer-auto-jump': specifier: ^0.7.18 version: 0.7.18 @@ -118,8 +121,8 @@ importers: specifier: ^10.0.12 version: 10.1.6 flying-squid: - specifier: npm:@zardoy/flying-squid@^0.0.62 - version: '@zardoy/flying-squid@0.0.62(encoding@0.1.13)' + specifier: npm:@zardoy/flying-squid@^0.0.104 + version: '@zardoy/flying-squid@0.0.104(encoding@0.1.13)' framer-motion: specifier: ^12.9.2 version: 12.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -137,13 +140,13 @@ importers: version: 4.17.21 mcraft-fun-mineflayer: specifier: ^0.1.23 - version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)) minecraft-data: - specifier: 3.92.0 - version: 3.92.0 + specifier: 3.98.0 + version: 3.98.0 minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13) @@ -152,7 +155,7 @@ importers: version: 2.0.4 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5 + version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618 node-gzip: specifier: ^1.1.2 version: 1.1.2 @@ -167,7 +170,7 @@ importers: version: 6.1.1 prismarine-provider-anvil: specifier: github:zardoy/prismarine-provider-anvil#everything - version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.3 @@ -342,10 +345,10 @@ importers: version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1) mineflayer: specifier: github:zardoy/mineflayer#gen-the-master - version: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.17 - version: 0.1.17 + specifier: ^0.1.21 + version: 0.1.21 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -430,13 +433,13 @@ importers: version: 1.3.9 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-schematic: specifier: ^1.2.0 - version: 1.2.3(prismarine-registry@1.11.0) + version: 1.2.3 process: specifier: ^0.11.10 version: 0.11.10 @@ -1989,6 +1992,16 @@ packages: '@module-federation/webpack-bundler-runtime@0.11.2': resolution: {integrity: sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==} + '@monaco-editor/loader@1.5.0': + resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^18.2.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@msgpack/msgpack@2.8.0': resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==} engines: {node: '>= 10'} @@ -3374,13 +3387,13 @@ packages: resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - '@zardoy/flying-squid@0.0.49': - resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==} + '@zardoy/flying-squid@0.0.104': + resolution: {integrity: sha512-jGhQ7fn7o8UN+mUwZbt9674D37YLuBi+Au4TwKcopCA6huIQdHTFNl2e+0ZSTI5mnhN+NpyVoR3vmtH6L58vHQ==} engines: {node: '>=8'} hasBin: true - '@zardoy/flying-squid@0.0.62': - resolution: {integrity: sha512-M6icydO/yrmwevBhmgKcqEPC63AhWfU/Es9N/uadVrmKaxGm2FQMMLcybbutRYm1xZ6qsdxDUOUZnN56PsVwfQ==} + '@zardoy/flying-squid@0.0.49': + resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==} engines: {node: '>=8'} hasBin: true @@ -6431,6 +6444,12 @@ packages: resolution: {integrity: sha512-RYZeD1+joNlPuUpi+tIWkbP0ieVJr+R6IFkI6/8juhSxx9zE4osoSmteybrfspGm8A6u+YbbY1epqRKEMwVR6Q==} engines: {node: '>=18.0.0'} + mc-bridge@0.1.3: + resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + minecraft-data: 3.98.0 + mcraft-fun-mineflayer@0.1.23: resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==} version: 0.1.23 @@ -6639,8 +6658,8 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minecraft-data@3.92.0: - resolution: {integrity: sha512-CGfO50svzm+pSRa4Mbq4owsmRKbPCNkSZ3MCOyH+epC7yNjh+PUhPQFHWq72O51qsY7pAB5qM/bJn1ncwG1J5g==} + minecraft-data@3.98.0: + resolution: {integrity: sha512-JAPqJ/TZoxMUlAPPdWUh1v5wdqvYGFSZ4rW9bUtmaKBkGpomDSjw4V02ocBqbxKJvcTtmc5nM/LfN9/0DDqHrQ==} minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} @@ -6649,9 +6668,9 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41} version: 1.0.1 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074} - version: 1.61.0 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9} + version: 1.62.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6665,12 +6684,12 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.17: - resolution: {integrity: sha512-0eCR8pnGb42Qd9QmAxOjl0PhA5Fa+9+6H1G/YsbsO5rg5mDf94Tusqp/8NAGLPQCPVDzbarLskXdjR3h0E0bEQ==} + mineflayer-mouse@0.1.21: + resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659} version: 8.0.0 engines: {node: '>=22'} @@ -6769,6 +6788,9 @@ packages: mojangson@2.0.4: resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==} + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} + moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} @@ -6833,8 +6855,8 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5} + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618} version: 0.2.4 nice-try@1.0.5: @@ -7365,7 +7387,7 @@ packages: prismarine-biome@1.3.0: resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==} peerDependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-registry: ^1.1.0 prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: @@ -8373,6 +8395,9 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + static-extend@0.1.2: resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==} engines: {node: '>=0.10.0'} @@ -11254,6 +11279,17 @@ snapshots: '@module-federation/runtime': 0.11.2 '@module-federation/sdk': 0.11.2 + '@monaco-editor/loader@1.5.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.5.0 + monaco-editor: 0.52.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@msgpack/msgpack@2.8.0': {} '@ndelangen/get-tarball@3.0.9': @@ -11307,9 +11343,9 @@ snapshots: '@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 - minecraft-data: 3.92.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + minecraft-data: 3.98.0 + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-item: 1.17.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 @@ -13058,7 +13094,7 @@ snapshots: '@types/emscripten': 1.40.0 tslib: 1.14.1 - '@zardoy/flying-squid@0.0.49(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.104(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 chalk: 5.4.1 @@ -13068,16 +13104,18 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + mc-bridge: 0.1.3(minecraft-data@3.98.0) + minecraft-data: 3.98.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -13094,7 +13132,7 @@ snapshots: - encoding - supports-color - '@zardoy/flying-squid@0.0.62(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.49(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 chalk: 5.4.1 @@ -13104,16 +13142,16 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + minecraft-data: 3.98.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -14504,8 +14542,8 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: - minecraft-data: 3.92.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + minecraft-data: 3.98.0 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -16948,12 +16986,16 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)): + mc-bridge@0.1.3(minecraft-data@3.98.0): + dependencies: + minecraft-data: 3.98.0 + + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)): dependencies: '@zardoy/flying-squid': 0.0.49(encoding@0.1.13) exit-hook: 2.2.1 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) prismarine-item: 1.17.0 ws: 8.18.1 transitivePeerDependencies: @@ -17260,7 +17302,7 @@ snapshots: min-indent@1.0.1: {} - minecraft-data@3.92.0: {} + minecraft-data@3.98.0: {} minecraft-folder-path@1.2.0: {} @@ -17271,7 +17313,7 @@ snapshots: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17280,7 +17322,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) endian-toggle: 0.0.0 lodash.merge: 4.6.2 - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 minecraft-folder-path: 1.2.0 node-fetch: 2.7.0(encoding@0.1.13) node-rsa: 0.4.2 @@ -17323,13 +17365,13 @@ snapshots: mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13): dependencies: - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding - supports-color - mineflayer-mouse@0.1.17: + mineflayer-mouse@0.1.21: dependencies: change-case: 5.4.4 debug: 4.4.1 @@ -17338,15 +17380,15 @@ snapshots: transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.10 - minecraft-data: 3.92.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + minecraft-data: 3.98.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13) + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-entity: 2.5.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 @@ -17461,6 +17503,8 @@ snapshots: dependencies: nearley: 2.20.1 + monaco-editor@0.52.2: {} + moo@0.5.2: {} morgan@1.10.0: @@ -17542,7 +17586,7 @@ snapshots: neo-async@2.6.2: {} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5: + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618: dependencies: body-parser: 1.20.3 express: 4.21.2 @@ -18130,21 +18174,19 @@ snapshots: transitivePeerDependencies: - supports-color - prismarine-biome@1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0): + prismarine-biome@1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0): dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0): + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: dependencies: - minecraft-data: 3.92.0 - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) + minecraft-data: 3.98.0 + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 prismarine-item: 1.17.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - transitivePeerDependencies: - - prismarine-registry prismarine-chat@1.11.0: dependencies: @@ -18152,10 +18194,10 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0): dependencies: - prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18183,14 +18225,14 @@ snapshots: prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b: dependencies: - minecraft-data: 3.92.0 + minecraft-data: 3.98.0 prismarine-nbt: 2.7.0 vec3: 0.1.10 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0) prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 @@ -18212,19 +18254,17 @@ snapshots: prismarine-registry@1.11.0: dependencies: - minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + minecraft-data: 3.98.0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 - prismarine-schematic@1.2.3(prismarine-registry@1.11.0): + prismarine-schematic@1.2.3: dependencies: - minecraft-data: 3.92.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0) + minecraft-data: 3.98.0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 - transitivePeerDependencies: - - prismarine-registry prismarine-windows@2.9.0: dependencies: @@ -19521,6 +19561,8 @@ snapshots: stack-generator: 2.0.10 stacktrace-gps: 3.1.2 + state-local@1.0.7: {} + static-extend@0.1.2: dependencies: define-property: 0.2.5 diff --git a/renderer/viewer/lib/createPlayerObject.ts b/renderer/viewer/lib/createPlayerObject.ts new file mode 100644 index 00000000..836c8062 --- /dev/null +++ b/renderer/viewer/lib/createPlayerObject.ts @@ -0,0 +1,55 @@ +import { PlayerObject, PlayerAnimation } from 'skinview3d' +import * as THREE from 'three' +import { WalkingGeneralSwing } from '../three/entity/animations' +import { loadSkinImage, stevePngUrl } from './utils/skins' + +export type PlayerObjectType = PlayerObject & { + animation?: PlayerAnimation + realPlayerUuid: string + realUsername: string +} + +export function createPlayerObject (options: { + username?: string + uuid?: string + scale?: number +}): { + playerObject: PlayerObjectType + wrapper: THREE.Group + } { + const wrapper = new THREE.Group() + const playerObject = new PlayerObject() as PlayerObjectType + + playerObject.realPlayerUuid = options.uuid ?? '' + playerObject.realUsername = options.username ?? '' + playerObject.position.set(0, 16, 0) + + // fix issues with starfield + playerObject.traverse((obj) => { + if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshStandardMaterial) { + obj.material.transparent = true + } + }) + + wrapper.add(playerObject as any) + const scale = options.scale ?? (1 / 16) + wrapper.scale.set(scale, scale, scale) + wrapper.rotation.set(0, Math.PI, 0) + + // Set up animation + playerObject.animation = new WalkingGeneralSwing() + ;(playerObject.animation as WalkingGeneralSwing).isMoving = false + playerObject.animation.update(playerObject, 0) + + return { playerObject, wrapper } +} + +export const applySkinToPlayerObject = async (playerObject: PlayerObjectType, skinUrl: string) => { + return loadSkinImage(skinUrl || stevePngUrl).then(({ canvas }) => { + const skinTexture = new THREE.CanvasTexture(canvas) + skinTexture.magFilter = THREE.NearestFilter + skinTexture.minFilter = THREE.NearestFilter + skinTexture.needsUpdate = true + playerObject.skin.map = skinTexture as any + }).catch(console.error) +} diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 86a85f77..dfbdb35c 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -7,6 +7,7 @@ import { Vec3 } from 'vec3' import { BotEvents } from 'mineflayer' import { proxy } from 'valtio' import TypedEmitter from 'typed-emitter' +import { Biome } from 'minecraft-data' import { delayedIterator } from '../../playground/shared' import { chunkPos } from './simpleUtils' @@ -28,6 +29,8 @@ export type WorldDataEmitterEvents = { updateLight: (data: { pos: Vec3 }) => void onWorldSwitch: () => void end: () => void + biomeUpdate: (data: { biome: Biome }) => void + biomeReset: () => void } export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmitter) { @@ -360,8 +363,37 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { export const worldCleanup = buildCleanupDecorator('resetWorld') export const defaultWorldRendererConfig = { + // Debug settings showChunkBorders: false, + enableDebugOverlay: false, + + // Performance settings mesherWorkers: 4, - isPlayground: false, - renderEars: true, - skinTexturesProxy: undefined as string | undefined, - // game renderer setting actually - showHand: false, - viewBobbing: false, - extraBlockRenderers: true, - clipWorldBelowY: undefined as number | undefined, + addChunksBatchWaitTime: 200, + _experimentalSmoothChunkLoading: true, + _renderByChunks: false, + + // Rendering engine settings + dayCycle: true, smoothLighting: true, enableLighting: true, starfield: true, - addChunksBatchWaitTime: 200, + defaultSkybox: true, + renderEntities: true, + extraBlockRenderers: true, + foreground: true, + fov: 75, + volume: 1, + + // Camera visual related settings + showHand: false, + viewBobbing: false, + renderEars: true, + highlightBlockColor: 'blue', + + // Player models + fetchPlayerSkins: true, + skinTexturesProxy: undefined as string | undefined, + + // VR settings vrSupport: true, vrPageGameRendering: true, - renderEntities: true, - fov: 75, - fetchPlayerSkins: true, - highlightBlockColor: 'blue', - foreground: true, - enableDebugOverlay: false, - _experimentalSmoothChunkLoading: true, - _renderByChunks: false, - volume: 1 + + // World settings + clipWorldBelowY: undefined as number | undefined, + isPlayground: false } export type WorldRendererConfig = typeof defaultWorldRendererConfig @@ -496,6 +510,10 @@ export abstract class WorldRendererCommon timeUpdated? (newTime: number): void + biomeUpdated? (biome: any): void + + biomeReset? (): void + updateViewerPosition (pos: Vec3) { this.viewerChunkPosition = pos for (const [key, value] of Object.entries(this.loadedChunks)) { @@ -817,12 +835,9 @@ export abstract class WorldRendererCommon }) worldEmitter.on('time', (timeOfDay) => { + if (!this.worldRendererConfig.dayCycle) return this.timeUpdated?.(timeOfDay) - if (timeOfDay < 0 || timeOfDay > 24_000) { - throw new Error('Invalid time of day. It should be between 0 and 24000.') - } - this.timeOfTheDay = timeOfDay // if (this.worldRendererConfig.skyLight === skyLight) return @@ -831,6 +846,14 @@ export abstract class WorldRendererCommon // (this).rerenderAllChunks?.() // } }) + + worldEmitter.on('biomeUpdate', ({ biome }) => { + this.biomeUpdated?.(biome) + }) + + worldEmitter.on('biomeReset', () => { + this.biomeReset?.() + }) } setBlockStateIdInner (pos: Vec3, stateId: number | undefined, needAoRecalculation = true) { diff --git a/renderer/viewer/three/cameraShake.ts b/renderer/viewer/three/cameraShake.ts index 593b4628..7b159509 100644 --- a/renderer/viewer/three/cameraShake.ts +++ b/renderer/viewer/three/cameraShake.ts @@ -80,8 +80,12 @@ export class CameraShake { camera.setRotationFromQuaternion(yawQuat) } else { // For regular camera, apply all rotations - const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.basePitch) - const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw) + // Add tiny offsets to prevent z-fighting at ideal angles (90, 180, 270 degrees) + const pitchOffset = this.addAntiZfightingOffset(this.basePitch) + const yawOffset = this.addAntiZfightingOffset(this.baseYaw) + + const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset) + const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset) const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle)) // Combine rotations in the correct order: pitch -> yaw -> roll const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat) @@ -96,4 +100,21 @@ export class CameraShake { private easeInOut (t: number): number { return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2 } + + private addAntiZfightingOffset (angle: number): number { + const offset = 0.001 // Very small offset in radians (about 0.057 degrees) + + // Check if the angle is close to ideal angles (0, π/2, π, 3π/2) + const normalizedAngle = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2) + const tolerance = 0.01 // Tolerance for considering an angle "ideal" + + if (Math.abs(normalizedAngle) < tolerance || + Math.abs(normalizedAngle - Math.PI / 2) < tolerance || + Math.abs(normalizedAngle - Math.PI) < tolerance || + Math.abs(normalizedAngle - 3 * Math.PI / 2) < tolerance) { + return angle + offset + } + + return angle + } } diff --git a/renderer/viewer/three/entities.ts b/renderer/viewer/three/entities.ts index 7849686b..fad30182 100644 --- a/renderer/viewer/three/entities.ts +++ b/renderer/viewer/three/entities.ts @@ -20,6 +20,7 @@ import { ItemSpecificContextProperties } from '../lib/basePlayerState' import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins' import { renderComponent } from '../sign-renderer' import { createCanvas } from '../lib/utils' +import { PlayerObjectType } from '../lib/createPlayerObject' import { getBlockMeshFromModel } from './holdingBlock' import { createItemMesh } from './itemMesh' import * as Entity from './entity/EntityMesh' @@ -33,12 +34,6 @@ export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl) export const TWEEN_DURATION = 120 -type PlayerObjectType = PlayerObject & { - animation?: PlayerAnimation - realPlayerUuid: string - realUsername: string -} - function convert2sComplementToHex (complement: number) { if (complement < 0) { complement = (0xFF_FF_FF_FF + complement + 1) >>> 0 diff --git a/renderer/viewer/three/skyboxRenderer.ts b/renderer/viewer/three/skyboxRenderer.ts index 294c72aa..fb9edae6 100644 --- a/renderer/viewer/three/skyboxRenderer.ts +++ b/renderer/viewer/three/skyboxRenderer.ts @@ -1,10 +1,51 @@ import * as THREE from 'three' +import { DebugGui } from '../lib/DebugGui' + +export const DEFAULT_TEMPERATURE = 0.75 export class SkyboxRenderer { private texture: THREE.Texture | null = null private mesh: THREE.Mesh | null = null + private skyMesh: THREE.Mesh | null = null + private voidMesh: THREE.Mesh | null = null - constructor (private readonly scene: THREE.Scene, public initialImage: string | null) {} + // World state + private worldTime = 0 + private partialTicks = 0 + private viewDistance = 4 + private temperature = DEFAULT_TEMPERATURE + private inWater = false + private waterBreathing = false + private fogBrightness = 0 + private prevFogBrightness = 0 + private readonly fogOrangeness = 0 // Debug property to control sky color orangeness + private readonly distanceFactor = 2.7 + + private readonly brightnessAtPosition = 1 + debugGui: DebugGui + + constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) { + this.debugGui = new DebugGui('skybox_renderer', this, [ + 'temperature', + 'worldTime', + 'inWater', + 'waterBreathing', + 'fogOrangeness', + 'brightnessAtPosition', + 'distanceFactor' + ], { + brightnessAtPosition: { min: 0, max: 1, step: 0.01 }, + temperature: { min: 0, max: 1, step: 0.01 }, + worldTime: { min: 0, max: 24_000, step: 1 }, + fogOrangeness: { min: -1, max: 1, step: 0.01 }, + distanceFactor: { min: 0, max: 5, step: 0.01 }, + }) + + if (!initialImage) { + this.createGradientSky() + } + // this.debugGui.activate() + } async init () { if (this.initialImage) { @@ -58,10 +99,288 @@ export class SkyboxRenderer { } } - update (cameraPosition: THREE.Vector3) { - if (this.mesh) { - this.mesh.position.copy(cameraPosition) + update (cameraPosition: THREE.Vector3, newViewDistance: number) { + if (newViewDistance !== this.viewDistance) { + this.viewDistance = newViewDistance + this.updateSkyColors() } + + if (this.mesh) { + // Update skybox position + this.mesh.position.copy(cameraPosition) + } else if (this.skyMesh) { + // Update gradient sky position + this.skyMesh.position.copy(cameraPosition) + this.voidMesh?.position.copy(cameraPosition) + this.updateSkyColors() // Update colors based on time of day + } + } + + // Update world time + updateTime (timeOfDay: number, partialTicks = 0) { + if (this.debugGui.visible) return + this.worldTime = timeOfDay + this.partialTicks = partialTicks + this.updateSkyColors() + } + + // Update view distance + updateViewDistance (viewDistance: number) { + this.viewDistance = viewDistance + this.updateSkyColors() + } + + // Update temperature (for biome support) + updateTemperature (temperature: number) { + if (this.debugGui.visible) return + this.temperature = temperature + this.updateSkyColors() + } + + // Update water state + updateWaterState (inWater: boolean, waterBreathing: boolean) { + if (this.debugGui.visible) return + this.inWater = inWater + this.waterBreathing = waterBreathing + this.updateSkyColors() + } + + // Update default skybox setting + updateDefaultSkybox (defaultSkybox: boolean) { + if (this.debugGui.visible) return + this.defaultSkybox = defaultSkybox + this.updateSkyColors() + } + + private createGradientSky () { + const size = 64 + const scale = 256 / size + 2 + + { + const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2) + geometry.rotateX(-Math.PI / 2) + geometry.translate(0, 16, 0) + + const material = new THREE.MeshBasicMaterial({ + color: 0xff_ff_ff, + side: THREE.DoubleSide, + depthTest: false + }) + + this.skyMesh = new THREE.Mesh(geometry, material) + this.scene.add(this.skyMesh) + } + + { + const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2) + geometry.rotateX(-Math.PI / 2) + geometry.translate(0, -16, 0) + + const material = new THREE.MeshBasicMaterial({ + color: 0xff_ff_ff, + side: THREE.DoubleSide, + depthTest: false + }) + + this.voidMesh = new THREE.Mesh(geometry, material) + this.scene.add(this.voidMesh) + } + + this.updateSkyColors() + } + + private getFogColor (partialTicks = 0): THREE.Vector3 { + const angle = this.getCelestialAngle(partialTicks) + let rotation = Math.cos(angle * Math.PI * 2) * 2 + 0.5 + rotation = Math.max(0, Math.min(1, rotation)) + + let x = 0.752_941_2 + let y = 0.847_058_83 + let z = 1 + + x *= (rotation * 0.94 + 0.06) + y *= (rotation * 0.94 + 0.06) + z *= (rotation * 0.91 + 0.09) + + return new THREE.Vector3(x, y, z) + } + + private getSkyColor (x = 0, z = 0, partialTicks = 0): THREE.Vector3 { + const angle = this.getCelestialAngle(partialTicks) + let brightness = Math.cos(angle * 3.141_593 * 2) * 2 + 0.5 + + if (brightness < 0) brightness = 0 + if (brightness > 1) brightness = 1 + + const temperature = this.getTemperature(x, z) + const rgb = this.getSkyColorByTemp(temperature) + + const red = ((rgb >> 16) & 0xff) / 255 + const green = ((rgb >> 8) & 0xff) / 255 + const blue = (rgb & 0xff) / 255 + + return new THREE.Vector3( + red * brightness, + green * brightness, + blue * brightness + ) + } + + private calculateCelestialAngle (time: number, partialTicks: number): number { + const modTime = (time % 24_000) + let angle = (modTime + partialTicks) / 24_000 - 0.25 + + if (angle < 0) { + angle++ + } + if (angle > 1) { + angle-- + } + + angle = 1 - ((Math.cos(angle * Math.PI) + 1) / 2) + angle += (angle - angle) / 3 + + return angle + } + + private getCelestialAngle (partialTicks: number): number { + return this.calculateCelestialAngle(this.worldTime, partialTicks) + } + + private getTemperature (x: number, z: number): number { + return this.temperature + } + + private getSkyColorByTemp (temperature: number): number { + temperature /= 3 + if (temperature < -1) temperature = -1 + if (temperature > 1) temperature = 1 + + // Apply debug fog orangeness to hue - positive values make it more orange, negative make it less orange + const baseHue = 0.622_222_2 - temperature * 0.05 + // Orange is around hue 0.08-0.15, so we need to shift from blue-purple (0.62) toward orange + // Use a more dramatic shift and also increase saturation for more noticeable effect + const orangeHue = 0.12 // Orange hue value + const hue = this.fogOrangeness > 0 + ? baseHue + (orangeHue - baseHue) * this.fogOrangeness * 0.8 // Blend toward orange + : baseHue + this.fogOrangeness * 0.1 // Subtle shift for negative values + const saturation = 0.5 + temperature * 0.1 + Math.abs(this.fogOrangeness) * 0.3 // Increase saturation with orangeness + const brightness = 1 + + return this.hsbToRgb(hue, saturation, brightness) + } + + private hsbToRgb (hue: number, saturation: number, brightness: number): number { + let r = 0; let g = 0; let b = 0 + if (saturation === 0) { + r = g = b = Math.floor(brightness * 255 + 0.5) + } else { + const h = (hue - Math.floor(hue)) * 6 + const f = h - Math.floor(h) + const p = brightness * (1 - saturation) + const q = brightness * (1 - saturation * f) + const t = brightness * (1 - (saturation * (1 - f))) + switch (Math.floor(h)) { + case 0: + r = Math.floor(brightness * 255 + 0.5) + g = Math.floor(t * 255 + 0.5) + b = Math.floor(p * 255 + 0.5) + break + case 1: + r = Math.floor(q * 255 + 0.5) + g = Math.floor(brightness * 255 + 0.5) + b = Math.floor(p * 255 + 0.5) + break + case 2: + r = Math.floor(p * 255 + 0.5) + g = Math.floor(brightness * 255 + 0.5) + b = Math.floor(t * 255 + 0.5) + break + case 3: + r = Math.floor(p * 255 + 0.5) + g = Math.floor(q * 255 + 0.5) + b = Math.floor(brightness * 255 + 0.5) + break + case 4: + r = Math.floor(t * 255 + 0.5) + g = Math.floor(p * 255 + 0.5) + b = Math.floor(brightness * 255 + 0.5) + break + case 5: + r = Math.floor(brightness * 255 + 0.5) + g = Math.floor(p * 255 + 0.5) + b = Math.floor(q * 255 + 0.5) + break + } + } + return 0xff_00_00_00 | (r << 16) | (g << 8) | (Math.trunc(b)) + } + + private updateSkyColors () { + if (!this.skyMesh || !this.voidMesh) return + + // If default skybox is disabled, hide the skybox meshes + if (!this.defaultSkybox) { + this.skyMesh.visible = false + this.voidMesh.visible = false + if (this.mesh) { + this.mesh.visible = false + } + return + } + + // Show skybox meshes when default skybox is enabled + this.skyMesh.visible = true + this.voidMesh.visible = true + if (this.mesh) { + this.mesh.visible = true + } + + // Update fog brightness with smooth transition + this.prevFogBrightness = this.fogBrightness + const renderDistance = this.viewDistance / 32 + const targetBrightness = this.brightnessAtPosition * (1 - renderDistance) + renderDistance + this.fogBrightness += (targetBrightness - this.fogBrightness) * 0.1 + + // Handle water fog + if (this.inWater) { + const waterViewDistance = this.waterBreathing ? 100 : 5 + this.scene.fog = new THREE.Fog(new THREE.Color(0, 0, 1), 0.0025, waterViewDistance) + this.scene.background = new THREE.Color(0, 0, 1) + + // Update sky and void colors for underwater effect + ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 1)) + ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 0.6)) + return + } + + // Normal sky colors + const viewDistance = this.viewDistance * 16 + const viewFactor = 1 - (0.25 + 0.75 * this.viewDistance / 32) ** 0.25 + + const angle = this.getCelestialAngle(this.partialTicks) + const skyColor = this.getSkyColor(0, 0, this.partialTicks) + const fogColor = this.getFogColor(this.partialTicks) + + const brightness = Math.cos(angle * Math.PI * 2) * 2 + 0.5 + const clampedBrightness = Math.max(0, Math.min(1, brightness)) + + // Interpolate fog brightness + const interpolatedBrightness = this.prevFogBrightness + (this.fogBrightness - this.prevFogBrightness) * this.partialTicks + + const red = (fogColor.x + (skyColor.x - fogColor.x) * viewFactor) * clampedBrightness * interpolatedBrightness + const green = (fogColor.y + (skyColor.y - fogColor.y) * viewFactor) * clampedBrightness * interpolatedBrightness + const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness + + this.scene.background = new THREE.Color(red, green, blue) + this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * this.distanceFactor) + + ;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z)) + ;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color( + skyColor.x * 0.2 + 0.04, + skyColor.y * 0.2 + 0.04, + skyColor.z * 0.6 + 0.1 + )) } dispose () { @@ -73,5 +392,15 @@ export class SkyboxRenderer { ;(this.mesh.material as THREE.Material).dispose() this.scene.remove(this.mesh) } + if (this.skyMesh) { + this.skyMesh.geometry.dispose() + ;(this.skyMesh.material as THREE.Material).dispose() + this.scene.remove(this.skyMesh) + } + if (this.voidMesh) { + this.voidMesh.geometry.dispose() + ;(this.voidMesh.material as THREE.Material).dispose() + this.scene.remove(this.voidMesh) + } } } diff --git a/renderer/viewer/three/waypointSprite.ts b/renderer/viewer/three/waypointSprite.ts index 7c8cf1f6..6a30e6db 100644 --- a/renderer/viewer/three/waypointSprite.ts +++ b/renderer/viewer/three/waypointSprite.ts @@ -16,7 +16,7 @@ export const WAYPOINT_CONFIG = { CANVAS_SCALE: 2, ARROW: { enabledDefault: false, - pixelSize: 30, + pixelSize: 50, paddingPx: 50, }, } @@ -50,6 +50,7 @@ export function createWaypointSprite (options: { depthTest?: boolean, // Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this) labelYOffset?: number, + metadata?: any, }): WaypointSprite { const color = options.color ?? 0xFF_00_00 const depthTest = options.depthTest ?? false @@ -131,16 +132,22 @@ export function createWaypointSprite (options: { canvas.height = size const ctx = canvas.getContext('2d')! ctx.clearRect(0, 0, size, size) + + // Draw arrow shape ctx.beginPath() - ctx.moveTo(size * 0.2, size * 0.5) - ctx.lineTo(size * 0.8, size * 0.5) - ctx.lineTo(size * 0.5, size * 0.2) + ctx.moveTo(size * 0.15, size * 0.5) + ctx.lineTo(size * 0.85, size * 0.5) + ctx.lineTo(size * 0.5, size * 0.15) ctx.closePath() - ctx.lineWidth = 4 + + // Use waypoint color for arrow + const colorHex = `#${color.toString(16).padStart(6, '0')}` + ctx.lineWidth = 6 ctx.strokeStyle = 'black' ctx.stroke() - ctx.fillStyle = 'white' + ctx.fillStyle = colorHex ctx.fill() + const texture = new THREE.CanvasTexture(canvas) const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false }) arrowSprite = new THREE.Sprite(material) @@ -169,6 +176,9 @@ export function createWaypointSprite (options: { ensureArrow() if (!arrowSprite) return true + // Check if onlyLeftRight is enabled in metadata + const onlyLeftRight = options.metadata?.onlyLeftRight === true + // Build camera basis using camera.up to respect custom orientations const forward = new THREE.Vector3() camera.getWorldDirection(forward) // camera look direction @@ -213,6 +223,20 @@ export function createWaypointSprite (options: { } } + // Apply onlyLeftRight logic - restrict arrows to left/right edges only + if (onlyLeftRight) { + // Force the arrow to appear only on left or right edges + if (Math.abs(rx) > Math.abs(ry)) { + // Horizontal direction is dominant, keep it + ry = 0 + } else { + // Vertical direction is dominant, but we want only left/right + // So choose left or right based on the sign of rx + rx = rx >= 0 ? 1 : -1 + ry = 0 + } + } + // Place on the rectangle border [-1,1]x[-1,1] const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1 let ndcX = rx / s diff --git a/renderer/viewer/three/waypoints.ts b/renderer/viewer/three/waypoints.ts index cebd779a..256ca6df 100644 --- a/renderer/viewer/three/waypoints.ts +++ b/renderer/viewer/three/waypoints.ts @@ -17,6 +17,7 @@ interface WaypointOptions { color?: number label?: string minDistance?: number + metadata?: any } export class WaypointsRenderer { @@ -71,13 +72,14 @@ export class WaypointsRenderer { this.removeWaypoint(id) const color = options.color ?? 0xFF_00_00 - const { label } = options + const { label, metadata } = options const minDistance = options.minDistance ?? 0 const sprite = createWaypointSprite({ position: new THREE.Vector3(x, y, z), color, label: (label || id), + metadata, }) sprite.enableOffscreenArrow(true) sprite.setArrowParent(this.waypointScene) diff --git a/renderer/viewer/three/world/cursorBlock.ts b/renderer/viewer/three/world/cursorBlock.ts index b71c1b8d..a03a6999 100644 --- a/renderer/viewer/three/world/cursorBlock.ts +++ b/renderer/viewer/three/world/cursorBlock.ts @@ -28,7 +28,7 @@ export class CursorBlock { } cursorLineMaterial: LineMaterial - interactionLines: null | { blockPos: Vec3, mesh: THREE.Group } = null + interactionLines: null | { blockPos: Vec3, mesh: THREE.Group, shapePositions: BlocksShapes | undefined } = null prevColor: string | undefined blockBreakMesh: THREE.Mesh breakTextures: THREE.Texture[] = [] @@ -62,6 +62,13 @@ export class CursorBlock { this.worldRenderer.onReactivePlayerStateUpdated('gameMode', () => { this.updateLineMaterial() }) + // todo figure out why otherwise fog from skybox breaks it + setTimeout(() => { + this.updateLineMaterial() + if (this.interactionLines) { + this.setHighlightCursorBlock(this.interactionLines.blockPos, this.interactionLines.shapePositions, true) + } + }) } // Update functions @@ -69,6 +76,9 @@ export class CursorBlock { const inCreative = this.worldRenderer.playerStateReactive.gameMode === 'creative' const pixelRatio = this.worldRenderer.renderer.getPixelRatio() + if (this.cursorLineMaterial) { + this.cursorLineMaterial.dispose() + } this.cursorLineMaterial = new LineMaterial({ color: (() => { switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) { @@ -115,8 +125,8 @@ export class CursorBlock { } } - setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes): void { - if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) { + setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes, force = false): void { + if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos) && !force) { return } if (this.interactionLines !== null) { @@ -140,7 +150,7 @@ export class CursorBlock { } this.worldRenderer.scene.add(group) group.visible = !this.cursorLinesHidden - this.interactionLines = { blockPos, mesh: group } + this.interactionLines = { blockPos, mesh: group, shapePositions } } render () { diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index fb6c8e11..1b4e6152 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -3,6 +3,7 @@ import { Vec3 } from 'vec3' import nbt from 'prismarine-nbt' import PrismarineChatLoader from 'prismarine-chat' import * as tweenJs from '@tweenjs/tween.js' +import { Biome } from 'minecraft-data' import { renderSign } from '../sign-renderer' import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer' import { chunkPos, sectionPos } from '../lib/simpleUtils' @@ -24,7 +25,7 @@ import { CameraShake } from './cameraShake' import { ThreeJsMedia } from './threeJsMedia' import { Fountain } from './threeJsParticles' import { WaypointsRenderer } from './waypoints' -import { SkyboxRenderer } from './skyboxRenderer' +import { DEFAULT_TEMPERATURE, SkyboxRenderer } from './skyboxRenderer' type SectionKey = string @@ -97,7 +98,7 @@ export class WorldRendererThree extends WorldRendererCommon { this.holdingBlockLeft = new HoldingBlock(this, true) // Initialize skybox renderer - this.skyboxRenderer = new SkyboxRenderer(this.scene, null) + this.skyboxRenderer = new SkyboxRenderer(this.scene, this.worldRendererConfig.defaultSkybox, null) void this.skyboxRenderer.init() this.addDebugOverlay() @@ -173,7 +174,10 @@ export class WorldRendererThree extends WorldRendererCommon { override watchReactivePlayerState () { super.watchReactivePlayerState() this.onReactivePlayerStateUpdated('inWater', (value) => { - this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, this.playerStateReactive.waterBreathing ? 100 : 20) : null + this.skyboxRenderer.updateWaterState(value, this.playerStateReactive.waterBreathing) + }) + this.onReactivePlayerStateUpdated('waterBreathing', (value) => { + this.skyboxRenderer.updateWaterState(this.playerStateReactive.inWater, value) }) this.onReactivePlayerStateUpdated('ambientLight', (value) => { if (!value) return @@ -202,6 +206,9 @@ export class WorldRendererThree extends WorldRendererCommon { this.onReactiveConfigUpdated('showChunkBorders', (value) => { this.updateShowChunksBorder(value) }) + this.onReactiveConfigUpdated('defaultSkybox', (value) => { + this.skyboxRenderer.updateDefaultSkybox(value) + }) } changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) { @@ -264,6 +271,19 @@ export class WorldRendererThree extends WorldRendererCommon { } else { this.starField.remove() } + + this.skyboxRenderer.updateTime(newTime) + } + + biomeUpdated (biome: Biome): void { + if (biome?.temperature !== undefined) { + this.skyboxRenderer.updateTemperature(biome.temperature) + } + } + + biomeReset (): void { + // Reset to default temperature when biome is unknown + this.skyboxRenderer.updateTemperature(DEFAULT_TEMPERATURE) } getItemRenderData (item: Record, specificProps: ItemSpecificContextProperties) { @@ -716,7 +736,7 @@ export class WorldRendererThree extends WorldRendererCommon { // Update skybox position to follow camera const cameraPos = this.getCameraPosition() - this.skyboxRenderer.update(cameraPos) + this.skyboxRenderer.update(cameraPos, this.viewDistance) const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov if (sizeOrFovChanged) { @@ -767,12 +787,17 @@ export class WorldRendererThree extends WorldRendererCommon { } renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) { - const textures = blockEntity.SkullOwner?.Properties?.textures[0] - if (!textures) return + let textureData: string + if (blockEntity.SkullOwner) { + textureData = blockEntity.SkullOwner.Properties?.textures?.[0]?.Value + } else { + textureData = blockEntity.profile?.properties?.find(p => p.name === 'textures')?.value + } + if (!textureData) return try { - const textureData = JSON.parse(Buffer.from(textures.Value, 'base64').toString()) - let skinUrl = textureData.textures?.SKIN?.url + const decodedData = JSON.parse(Buffer.from(textureData, 'base64').toString()) + let skinUrl = decodedData.textures?.SKIN?.url const { skinTexturesProxy } = this.worldRendererConfig if (skinTexturesProxy) { skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy) diff --git a/scripts/makeOptimizedMcData.mjs b/scripts/makeOptimizedMcData.mjs index 76e0f1c2..a572d067 100644 --- a/scripts/makeOptimizedMcData.mjs +++ b/scripts/makeOptimizedMcData.mjs @@ -371,6 +371,7 @@ console.log('size', fs.lstatSync(filePath).size / 1000 / 1000, gzipSizeFromFileS const { defaultVersion } = MCProtocol const data = MinecraftData(defaultVersion) +console.log('defaultVersion', defaultVersion, !!data) const initialMcData = { [defaultVersion]: { version: data.version, diff --git a/src/appConfig.ts b/src/appConfig.ts index 92fde21a..c29d74e8 100644 --- a/src/appConfig.ts +++ b/src/appConfig.ts @@ -35,7 +35,7 @@ export type AppConfig = { // defaultVersion?: string peerJsServer?: string peerJsServerFallback?: string - promoteServers?: Array<{ ip, description, version? }> + promoteServers?: Array<{ ip, description, name?, version?, }> mapsProvider?: string appParams?: Record // query string params diff --git a/src/appParams.ts b/src/appParams.ts index 8d487f8d..4c8ca186 100644 --- a/src/appParams.ts +++ b/src/appParams.ts @@ -47,6 +47,7 @@ export type AppQsParams = { connectText?: string freezeSettings?: string testIosCrash?: string + addPing?: string // Replay params replayFilter?: string diff --git a/src/basicSounds.ts b/src/basicSounds.ts index 37f8dccd..54af0d35 100644 --- a/src/basicSounds.ts +++ b/src/basicSounds.ts @@ -7,7 +7,12 @@ let audioContext: AudioContext const sounds: Record = {} // Track currently playing sounds and their gain nodes -const activeSounds: Array<{ source: AudioBufferSourceNode; gainNode: GainNode; volumeMultiplier: number }> = [] +const activeSounds: Array<{ + source: AudioBufferSourceNode; + gainNode: GainNode; + volumeMultiplier: number; + isMusic: boolean; +}> = [] window.activeSounds = activeSounds // load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded @@ -43,7 +48,7 @@ export async function loadSound (path: string, contents = path) { } } -export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false) => { +export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false, isMusic = false) => { const soundBuffer = sounds[url] if (!soundBuffer) { const start = Date.now() @@ -51,11 +56,11 @@ export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = option if (cancelled || Date.now() - start > loadTimeout) return } - return playSound(url, soundVolume, loop) + return playSound(url, soundVolume, loop, isMusic) } -export async function playSound (url, soundVolume = 1, loop = false) { - const volume = soundVolume * (options.volume / 100) +export async function playSound (url, soundVolume = 1, loop = false, isMusic = false) { + const volume = soundVolume * (options.volume / 100) * (isMusic ? options.musicVolume / 100 : 1) if (!volume) return @@ -82,7 +87,7 @@ export async function playSound (url, soundVolume = 1, loop = false) { source.start(0) // Add to active sounds - activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume }) + activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume, isMusic }) const callbacks = [] as Array<() => void> source.onended = () => { @@ -110,6 +115,7 @@ export async function playSound (url, soundVolume = 1, loop = false) { console.warn('Failed to stop sound:', err) } }, + gainNode, } } @@ -137,11 +143,11 @@ export function stopSound (url: string) { } } -export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { +export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusicVolume: number) { const normalizedVolume = newVolume / 100 - for (const { gainNode, volumeMultiplier } of activeSounds) { + for (const { gainNode, volumeMultiplier, isMusic } of activeSounds) { try { - gainNode.gain.value = normalizedVolume * volumeMultiplier + gainNode.gain.value = normalizedVolume * volumeMultiplier * (isMusic ? newMusicVolume / 100 : 1) } catch (err) { console.warn('Failed to change sound volume:', err) } @@ -149,5 +155,9 @@ export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) { } subscribeKey(options, 'volume', () => { - changeVolumeOfCurrentlyPlayingSounds(options.volume) + changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) +}) + +subscribeKey(options, 'musicVolume', () => { + changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume) }) diff --git a/src/chatUtils.ts b/src/chatUtils.ts index 88437bc3..849d5847 100644 --- a/src/chatUtils.ts +++ b/src/chatUtils.ts @@ -118,6 +118,14 @@ export const formatMessage = (message: MessageInput, mcData: IndexedData = globa return msglist } +export const messageToString = (message: MessageInput | string) => { + if (typeof message === 'string') { + return message + } + const msglist = formatMessage(message) + return msglist.map(msg => msg.text).join('') +} + const blockToItemRemaps = { water: 'water_bucket', lava: 'lava_bucket', diff --git a/src/core/ideChannels.ts b/src/core/ideChannels.ts new file mode 100644 index 00000000..a9c517f7 --- /dev/null +++ b/src/core/ideChannels.ts @@ -0,0 +1,106 @@ +import { proxy } from 'valtio' + +export const ideState = proxy({ + id: '', + contents: '', + line: 0, + column: 0, + language: 'typescript', + title: '', +}) +globalThis.ideState = ideState + +export const registerIdeChannels = () => { + registerIdeOpenChannel() + registerIdeSaveChannel() +} + +const registerIdeOpenChannel = () => { + const CHANNEL_NAME = 'minecraft-web-client:ide-open' + + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'language', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'contents', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'line', + type: 'i32' + }, + { + name: 'column', + type: 'i32' + }, + { + name: 'title', + type: ['pstring', { countType: 'i16' }] + } + ] + ] + + bot._client.registerChannel(CHANNEL_NAME, packetStructure, true) + + bot._client.on(CHANNEL_NAME as any, (data) => { + const { id, language, contents, line, column, title } = data + + ideState.contents = contents + ideState.line = line + ideState.column = column + ideState.id = id + ideState.language = language || 'typescript' + ideState.title = title + }) + + console.debug(`registered custom channel ${CHANNEL_NAME} channel`) +} +const IDE_SAVE_CHANNEL_NAME = 'minecraft-web-client:ide-save' +const registerIdeSaveChannel = () => { + + const packetStructure = [ + 'container', + [ + { + name: 'id', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'contents', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'language', + type: ['pstring', { countType: 'i16' }] + }, + { + name: 'line', + type: 'i32' + }, + { + name: 'column', + type: 'i32' + }, + ] + ] + bot._client.registerChannel(IDE_SAVE_CHANNEL_NAME, packetStructure, true) +} + +export const saveIde = () => { + bot._client.writeChannel(IDE_SAVE_CHANNEL_NAME, { + id: ideState.id, + contents: ideState.contents, + language: ideState.language, + // todo: reflect updated + line: ideState.line, + column: ideState.column, + }) +} diff --git a/src/customChannels.ts b/src/customChannels.ts index 6d3aa7e9..506ea776 100644 --- a/src/customChannels.ts +++ b/src/customChannels.ts @@ -2,21 +2,20 @@ import PItem from 'prismarine-item' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' import { options } from './optionsStorage' import { jeiCustomCategories } from './inventoryWindows' +import { registerIdeChannels } from './core/ideChannels' export default () => { customEvents.on('mineflayerBotCreated', async () => { if (!options.customChannels) return - await new Promise(resolve => { - bot.once('login', () => { - resolve(true) - }) + bot.once('login', () => { + registerBlockModelsChannel() + registerMediaChannels() + registerSectionAnimationChannels() + registeredJeiChannel() + registerBlockInteractionsCustomizationChannel() + registerWaypointChannels() + registerIdeChannels() }) - registerBlockModelsChannel() - registerMediaChannels() - registerSectionAnimationChannels() - registeredJeiChannel() - registerBlockInteractionsCustomizationChannel() - registerWaypointChannels() }) } @@ -48,19 +47,7 @@ const registerBlockInteractionsCustomizationChannel = () => { registerChannel(CHANNEL_NAME, packetStructure, (data) => { const config = JSON.parse(data.newConfiguration) - if (config.customBreakTime !== undefined && Object.values(config.customBreakTime).every(x => typeof x === 'number')) { - bot.mouse.customBreakTime = config.customBreakTime - } - if (config.customBreakTimeToolAllowance !== undefined) { - bot.mouse.customBreakTimeToolAllowance = new Set(config.customBreakTimeToolAllowance) - } - - if (config.blockPlacePrediction !== undefined) { - bot.mouse.settings.blockPlacePrediction = config.blockPlacePrediction - } - if (config.blockPlacePredictionDelay !== undefined) { - bot.mouse.settings.blockPlacePredictionDelay = config.blockPlacePredictionDelay - } + bot.mouse.setConfigFromPacket(config) }, true) } @@ -95,15 +82,30 @@ const registerWaypointChannels = () => { { name: 'color', type: 'i32' + }, + { + name: 'metadataJson', + type: ['pstring', { countType: 'i16' }] } ] ] registerChannel('minecraft-web-client:waypoint-add', packetStructure, (data) => { + // Parse metadata if provided + let metadata: any = {} + if (data.metadataJson && data.metadataJson.trim() !== '') { + try { + metadata = JSON.parse(data.metadataJson) + } catch (error) { + console.warn('Failed to parse waypoint metadataJson:', error) + } + } + getThreeJsRendererMethods()?.addWaypoint(data.id, data.x, data.y, data.z, { minDistance: data.minDistance, label: data.label || undefined, - color: data.color || undefined + color: data.color || undefined, + metadata }) }) diff --git a/src/dayCycle.ts b/src/dayCycle.ts deleted file mode 100644 index 50e63a21..00000000 --- a/src/dayCycle.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { options } from './optionsStorage' -import { assertDefined } from './utils' -import { updateBackground } from './water' - -export default () => { - const timeUpdated = () => { - // 0 morning - const dayTotal = 24_000 - const evening = 11_500 - const night = 13_500 - const morningStart = 23_000 - const morningEnd = 23_961 - const timeProgress = options.dayCycleAndLighting ? bot.time.timeOfDay : 0 - - // todo check actual colors - const dayColorRainy = { r: 111 / 255, g: 156 / 255, b: 236 / 255 } - // todo yes, we should make animations (and rain) - // eslint-disable-next-line unicorn/numeric-separators-style - const dayColor = bot.isRaining ? dayColorRainy : { r: 0.6784313725490196, g: 0.8470588235294118, b: 0.9019607843137255 } // lightblue - // let newColor = dayColor - let int = 1 - if (timeProgress < evening) { - // stay dayily - } else if (timeProgress < night) { - const progressNorm = timeProgress - evening - const progressMax = night - evening - int = 1 - progressNorm / progressMax - } else if (timeProgress < morningStart) { - int = 0 - } else if (timeProgress < morningEnd) { - const progressNorm = timeProgress - morningStart - const progressMax = night - morningEnd - int = progressNorm / progressMax - } - // todo need to think wisely how to set these values & also move directional light around! - const colorInt = Math.max(int, 0.1) - updateBackground({ r: dayColor.r * colorInt, g: dayColor.g * colorInt, b: dayColor.b * colorInt }) - if (!options.newVersionsLighting && bot.supportFeature('blockStateId')) { - appViewer.playerState.reactive.ambientLight = Math.max(int, 0.25) - appViewer.playerState.reactive.directionalLight = Math.min(int, 0.5) - } - } - - bot.on('time', timeUpdated) - timeUpdated() -} diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index 6045e70b..48c1cfad 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -16,7 +16,8 @@ export const defaultOptions = { chatOpacityOpened: 100, messagesLimit: 200, volume: 50, - enableMusic: false, + enableMusic: true, + musicVolume: 50, // fov: 70, fov: 75, defaultPerspective: 'first_person' as 'first_person' | 'third_person_back' | 'third_person_front', @@ -41,6 +42,7 @@ export const defaultOptions = { renderEars: true, lowMemoryMode: false, starfieldRendering: true, + defaultSkybox: true, enabledResourcepack: null as string | null, useVersionsTextures: 'latest', serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never', @@ -83,6 +85,7 @@ export const defaultOptions = { localServerOptions: { gameMode: 1 } as any, + saveLoginPassword: 'prompt' as 'prompt' | 'never' | 'always', preferLoadReadonly: false, experimentalClientSelfReload: false, remoteSoundsSupport: false, diff --git a/src/devtools.ts b/src/devtools.ts index 6c47f73d..1f8ef8e8 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -5,6 +5,17 @@ import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree' import { enable, disable, enabled } from 'debug' import { Vec3 } from 'vec3' +customEvents.on('mineflayerBotCreated', () => { + window.debugServerPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toClient.types).map(name => { + name = name.replace('packet_', '') + return [name, name] + })) + window.debugClientPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toServer.types).map(name => { + name = name.replace('packet_', '') + return [name, name] + })) +}) + window.Vec3 = Vec3 window.cursorBlockRel = (x = 0, y = 0, z = 0) => { const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z) diff --git a/src/entities.ts b/src/entities.ts index dcec6143..674f91ef 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -246,22 +246,29 @@ customEvents.on('gameLoaded', () => { } } // even if not found, still record to cache - void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) + void getThreeJsRendererMethods()!.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl) } catch (err) { - console.error('Error decoding player texture:', err) + reportError(new Error('Error applying skin texture:', { cause: err })) } } bot.on('playerJoined', updateSkin) bot.on('playerUpdated', updateSkin) + for (const entity of Object.values(bot.players)) { + updateSkin(entity) + } - bot.on('teamUpdated', (team: Team) => { + const teamUpdated = (team: Team) => { for (const entity of Object.values(bot.entities)) { if (entity.type === 'player' && entity.username && team.members.includes(entity.username) || entity.uuid && team.members.includes(entity.uuid)) { bot.emit('entityUpdate', entity) } } - }) + } + bot.on('teamUpdated', teamUpdated) + for (const team of Object.values(bot.teams)) { + teamUpdated(team) + } const updateEntityNameTags = (team: Team) => { for (const entity of Object.values(bot.entities)) { diff --git a/src/index.ts b/src/index.ts index 4a118cee..7764188f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,7 +56,6 @@ import { isCypress } from './standaloneUtils' import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer' import defaultServerOptions from './defaultLocalServerOptions' -import dayCycle from './dayCycle' import { onAppLoad, resourcepackReload, resourcePackState } from './resourcePack' import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer' @@ -305,7 +304,7 @@ export async function connect (connectOptions: ConnectOptions) { if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) { console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`) - net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` } }) + net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` }, artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined }) } const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance @@ -794,7 +793,6 @@ export async function connect (connectOptions: ConnectOptions) { } initMotionTracking() - dayCycle() // Bot position callback const botPosition = () => { diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index a9f89d1b..d40260df 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -12,6 +12,7 @@ import PrismarineChatLoader from 'prismarine-chat' import * as nbt from 'prismarine-nbt' import { BlockModel } from 'mc-assets' import { renderSlot } from 'renderer/viewer/three/renderSlot' +import { loadSkinFromUsername } from 'renderer/viewer/lib/utils/skins' import Generic95 from '../assets/generic_95.png' import { appReplacableResources } from './generated/resources' import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' @@ -23,6 +24,7 @@ import { getItemDescription } from './itemsDescriptions' import { MessageFormatPart } from './chatUtils' import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' import { playerState } from './mineflayer/playerState' +import { modelViewerState } from './react/OverlayModelViewer' const loadedImagesCache = new Map() const cleanLoadedImagesCache = () => { @@ -40,6 +42,34 @@ export const jeiCustomCategories = proxy({ value: [] as Array<{ id: string, categoryTitle: string, items: any[] }> }) +let remotePlayerSkin: string | undefined | Promise + +export const showInventoryPlayer = () => { + modelViewerState.model = { + positioning: { + windowWidth: 176, + windowHeight: 166, + x: 25, + y: 8, + width: 50, + height: 70, + scaled: true, + onlyInitialScale: true, + followCursor: true, + }, + // models: ['https://bucket.mcraft.fun/sitarbuckss.glb'], + // debug: true, + steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''), + } + if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) { + remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => { + setTimeout(() => { showInventoryPlayer() }, 0) // todo patch instead and make reactive + remotePlayerSkin = a ?? '' + return remotePlayerSkin + }) + } +} + export const onGameLoad = () => { version = bot.version @@ -57,12 +87,23 @@ export const onGameLoad = () => { return type } + const maybeParseNbtJson = (data: any) => { + if (typeof data === 'string') { + try { + data = JSON.parse(data) + } catch (err) { + // ignore + } + } + return nbt.simplify(data) ?? data + } + bot.on('windowOpen', (win) => { const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)] if (implementedWindow) { - openWindow(implementedWindow, nbt.simplify(win.title as any)) + openWindow(implementedWindow, maybeParseNbtJson(win.title)) } else if (options.unimplementedContainers) { - openWindow('ChestWin', nbt.simplify(win.title as any)) + openWindow('ChestWin', maybeParseNbtJson(win.title)) } else { // todo format displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`) @@ -381,7 +422,12 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = miscUiState.displaySearchInput = false destroyFn() skipClosePacketSending = false + + modelViewerState.model = undefined }) + if (type === undefined) { + showInventoryPlayer() + } cleanLoadedImagesCache() const inv = openItemsCanvas(type) inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch @@ -424,6 +470,7 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = const isRightClick = type === 'rightclick' const isLeftClick = type === 'leftclick' if (isLeftClick || isRightClick) { + modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item) } } else { @@ -455,6 +502,7 @@ const openWindow = (type: string | undefined, title: string | any = undefined) = if (freeSlot === null) return void bot.creative.setInventorySlot(freeSlot, item) } else { + modelViewerState.model = undefined inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0]) } } diff --git a/src/mineflayer/mc-protocol.ts b/src/mineflayer/mc-protocol.ts index 0171387a..cd21d01f 100644 --- a/src/mineflayer/mc-protocol.ts +++ b/src/mineflayer/mc-protocol.ts @@ -130,7 +130,8 @@ export const setProxy = (proxyParams: ProxyParams) => { net['setProxy']({ hostname: proxy.host, port: proxy.port, - headers: proxyParams.headers + headers: proxyParams.headers, + artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined }) return { proxy diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index fc1ce0fd..14e19345 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -110,7 +110,7 @@ const domListeners = (bot: Bot) => { }, { signal: abortController.signal }) bot.mouse.beforeUpdateChecks = () => { - if (!document.hasFocus()) { + if (!document.hasFocus() || !isGameActive(true)) { // deactive all buttons bot.mouse.buttons.fill(false) } diff --git a/src/mineflayer/websocket-core.ts b/src/mineflayer/websocket-core.ts index 0edd2497..f8163102 100644 --- a/src/mineflayer/websocket-core.ts +++ b/src/mineflayer/websocket-core.ts @@ -15,9 +15,12 @@ class CustomDuplex extends Duplex { } export const getWebsocketStream = async (host: string) => { - const baseProtocol = location.protocol === 'https:' ? 'wss' : host.startsWith('ws://') ? 'ws' : 'wss' + const baseProtocol = host.startsWith('ws://') ? 'ws' : 'wss' const hostClean = host.replace('ws://', '').replace('wss://', '') - const ws = new WebSocket(`${baseProtocol}://${hostClean}`) + const hostURL = new URL(`${baseProtocol}://${hostClean}`) + const hostParams = hostURL.searchParams + hostParams.append('client_mcraft', '') + const ws = new WebSocket(`${baseProtocol}://${hostURL.host}${hostURL.pathname}?${hostParams.toString()}`) const clientDuplex = new CustomDuplex(undefined, data => { ws.send(data) }) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index b03db37d..0cb0fe1e 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -480,6 +480,24 @@ export const guiOptionsScheme: { ], sound: [ { volume: {} }, + { + custom () { + return { + options.musicVolume = value + }} + item={{ + type: 'slider', + id: 'musicVolume', + text: 'Music Volume', + min: 0, + max: 100, + unit: '%', + }} + /> + }, + }, { custom () { return