diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fc56ea9d..037583182 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,6 @@ jobs: cp -r dist package/ cd package zip -r ../self-host.zip . - - run: pnpm build-playground # - run: pnpm build-storybook - run: pnpm test-unit - run: pnpm lint diff --git a/.github/workflows/next-deploy.yml b/.github/workflows/next-deploy.yml index 4fdce2b33..b5912cc5a 100644 --- a/.github/workflows/next-deploy.yml +++ b/.github/workflows/next-deploy.yml @@ -37,11 +37,11 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED LOCAL_CONFIG_FILE: config.mcraft-only.json - - name: Copy playground files - run: | - mkdir -p .vercel/output/static/playground - pnpm build-playground - cp -r renderer/dist/* .vercel/output/static/playground/ + # - name: Copy playground files + # run: | + # mkdir -p .vercel/output/static/playground + # pnpm build-playground + # cp -r renderer/dist/* .vercel/output/static/playground/ - name: Deploy Project Artifacts to Vercel uses: mathiasvr/command-output@v2.0.0 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca5f12ee9..57898744f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,11 +35,6 @@ jobs: env: CONFIG_JSON_SOURCE: BUNDLED LOCAL_CONFIG_FILE: config.mcraft-only.json - - name: Copy playground files - run: | - mkdir -p .vercel/output/static/playground - pnpm build-playground - cp -r renderer/dist/* .vercel/output/static/playground/ # publish to github - run: cp vercel.json .vercel/output/static/vercel.json diff --git a/README.MD b/README.MD index fa7b694cc..764d94319 100644 --- a/README.MD +++ b/README.MD @@ -18,7 +18,7 @@ Don't confuse this with [Eaglercraft](https://eagsrc.webmc.xyz) which is a REAL For building the project yourself / contributing, see [Development, Debugging & Contributing](#development-debugging--contributing). For reference at what and how web technologies / frameworks are used, see [TECH.md](./TECH.md) (also for comparison with Eaglercraft). -> **Note**: You can deploy it on your own server in less than a minute using a one-liner script from the [Minecraft Everywhere repo](https://github.com/zardoy/minecraft-everywhere). +> **Note**: See [Self-hosting & proxy](#servers--proxy) for Docker, proxy-only setups, and a one-liner VPS script with [Minecraft Everywhere](https://github.com/zardoy/minecraft-everywhere). ### Big Features @@ -78,15 +78,9 @@ Whatever offline mode you used (zip, folder, just singleplayer), you can always ### Servers & Proxy -You can play almost on any Java server, vanilla servers are fully supported. -See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for the list of supported versions (should support majority of versions). -There is a built-in proxy, but you can also host your own! Just clone the repo, run `pnpm i` (see CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. You can also deploy it to a cloud service: +You can play on almost any Java server; vanilla servers are fully supported. See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for supported protocol versions. -[![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 that use the TCP protocol. When you connect to a server with a proxy, a WebSocket connection is created between you (browser client) and the proxy server; then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid additional delays. That said all Minecraft protocol packets are processed by the client, right in your browser. +**How it works:** browsers speak WebSockets; Minecraft Java uses TCP. A **proxy** bridges the two. When you connect with a proxy URL enabled, your browser talks to the proxy over WebSocket **using the proxy's IP**; the proxy opens a TCP connection to the Minecraft server and forwards bytes without deserializing packets. All protocol handling still runs in the browser (Mineflayer in the client). ```mermaid graph LR @@ -101,12 +95,29 @@ So if the server is located in Europe and you are connecting from Europe, you wi Again, the proxy server is not a part of the client, it is a separate service that you can host yourself. -### Docker Files +#### Self-hosting + +| What you want | What to run | +|---------------|-------------| +| **Full stack** (your own full game copy + bundled proxy) | This repo: local dev with `pnpm i` and `pnpm prod-start` (see [CONTRIBUTING.md](./CONTRIBUTING.md)), or **Docker** with the [main Dockerfile](./Dockerfile) below. | +| **Proxy only** (keep using a public game client e.g. [mcraft.fun](https://mcraft.fun)) | Run **[mwc-proxy](https://github.com/zardoy/mwc-proxy)** somewhere that can reach your Minecraft server (often same host/VPC); follow that README for the rest. **Where to host:** your own PC or home network often behaves better than a cloud relay—some servers flag datacenter IPs like “VPN” traffic; for casual play, any reachable host is fine. **URL in the client:** paste your relay’s URL into the **proxy** field; you still have to make it reachable (reverse proxy **with SSL**, **[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/)**, temporary URL services like cloudflared are not recommended). **Note:** the Minecraft server sees **your relay’s public IP** on the TCP leg (the proxy → server connection). You do **not** need this web-client repo for proxy-only. | +| **One-liner deploy on a VPS** | [Minecraft Everywhere](https://github.com/zardoy/minecraft-everywhere) provides a **one-liner** script that can deploy these variants (your own full game copy, proxy, or combined setups)—pick what you need from that repo’s instructions. | + +Public deployments (e.g. GitHub Pages) ship without your private TCP endpoint; for your own server you still point the client at **your** proxy (e.g. mwc-proxy or a stack you deployed with the script above). + +**Koyeb (full app from this repo):** + +[![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) + +> **mwc-proxy vs this repo’s proxy:** [mwc-proxy](https://github.com/zardoy/mwc-proxy) is the lightweight standalone WebSocket→TCP bridge you run next to your world. This repo can embed a compatible proxy in `prod-start` / Docker. For “I only need a relay for the hosted client,” use mwc-proxy. + +### Docker (self-hosted) + +Use Docker when you want a reproducible install without local Node/pnpm setup (see [CONTRIBUTING.md](./CONTRIBUTING.md) only if you build from source). -- [Main Dockerfile](./Dockerfile) - for production build & offline/private usage. Includes full web-app + proxy server for connecting to Minecraft servers. -- [Proxy Dockerfile](./Dockerfile.proxy) - for proxy server only - that one you would be able to specify in the proxy field on the client (`docker build . -f Dockerfile.proxy -t minecraft-web-proxy`) +**[Dockerfile](./Dockerfile)** — production image: **your own full game copy (web client) + proxy** in one process. Good when you host the full stack yourself. -In case of using docker, you don't have to follow preparation steps from CONTRIBUTING.MD, like installing Node.js, pnpm, etc. +For **proxy only**, use **[mwc-proxy](https://github.com/zardoy/mwc-proxy)** (including its Docker options) instead of maintaining a second image in this repo. ### Rendering diff --git a/assets/generic_91.png b/assets/generic_91.png deleted file mode 100644 index 99e4d04ae..000000000 Binary files a/assets/generic_91.png and /dev/null differ diff --git a/assets/generic_92.png b/assets/generic_92.png deleted file mode 100644 index 299e081b3..000000000 Binary files a/assets/generic_92.png and /dev/null differ diff --git a/assets/generic_93.png b/assets/generic_93.png deleted file mode 100644 index 1b35f671d..000000000 Binary files a/assets/generic_93.png and /dev/null differ diff --git a/assets/generic_94.png b/assets/generic_94.png deleted file mode 100644 index 19dc788d5..000000000 Binary files a/assets/generic_94.png and /dev/null differ diff --git a/assets/generic_95.png b/assets/generic_95.png deleted file mode 100644 index 2286dbfc8..000000000 Binary files a/assets/generic_95.png and /dev/null differ diff --git a/package.json b/package.json index e1c42a558..2e1d8f166 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "watch-mesher": "pnpm build-mesher -w", "run-playground": "run-p watch-mesher watch-other-workers watch-playground", "run-all": "run-p start run-playground", - "build-playground": "rsbuild build --config renderer/rsbuild.config.ts", "watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts", "update-git-deps": "tsx scripts/updateGitDeps.ts", "request-data": "tsx scripts/requestData.ts" @@ -80,14 +79,15 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "filesize": "^10.0.12", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.118", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.119", "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.103.0", + "minecraft-data": "0.0.0", + "minecraft-inventory": "^0.1.37", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", @@ -155,9 +155,8 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "mc-assets": "^0.2.72", - "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", - "mineflayer": "github:zardoy/mineflayer#gen-the-master", - "mineflayer-mouse": "^0.1.26", + "mineflayer": "0.0.0", + "mineflayer-mouse": "0.1.28", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", @@ -205,7 +204,7 @@ "diamond-square": "github:zardoy/diamond-square", "prismarine-block": "github:zardoy/prismarine-block#next-era", "prismarine-world": "github:zardoy/prismarine-world#next-era", - "minecraft-data": "3.103.0", + "minecraft-data": "3.106.0", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "prismarine-physics": "github:zardoy/prismarine-physics", "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", @@ -222,7 +221,7 @@ "patchedDependencies": { "pixelarticons@1.8.1": "patches/pixelarticons@1.8.1.patch", "mineflayer-item-map-downloader@1.2.0": "patches/mineflayer-item-map-downloader@1.2.0.patch", - "minecraft-protocol": "patches/minecraft-protocol.patch" + "minecraft-protocol": "patches/minecraft-protocol@1.66.0.patch" }, "ignoredBuiltDependencies": [ "canvas", diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol@1.66.0.patch similarity index 92% rename from patches/minecraft-protocol.patch rename to patches/minecraft-protocol@1.66.0.patch index d71150e82..e115e8f8d 100644 --- a/patches/minecraft-protocol.patch +++ b/patches/minecraft-protocol@1.66.0.patch @@ -1,92 +1,5 @@ -diff --git a/src/client.js b/src/client.js -index e369e77..f6c408d 100644 ---- a/src/client.js -+++ b/src/client.js -@@ -75,7 +75,35 @@ class Client extends EventEmitter { - } - const deserializerDirection = this.isServer ? 'toServer' : 'toClient' - e.field = [this.protocolState, deserializerDirection].concat(parts).join('.') -- e.message = e.buffer ? `Parse error for ${e.field} (${e.buffer?.length} bytes, ${e.buffer?.toString('hex').slice(0, 6)}...) : ${e.message}` : `Parse error for ${e.field}: ${e.message}` -+ e.message = e.buffer -+ ? `Parse error for ${e.field} (${e.buffer?.length} bytes, ${e.buffer?.toString('hex').slice(0, 6)}...) : ${e.message}` -+ : `Parse error for ${e.field}: ${e.message}` -+ -+ // Non-fatal play-state parse error — e.g. Hypixel sending NBT tag IDs outside -+ // the standard 0-20 range (tag 71 = custom component data introduced in 1.21.4+). -+ // The deserializer stream is destroyed by Node.js on error, so we tear it down -+ // fully, rebuild it via setSerializer, and re-pipe it into the chain so packet -+ // processing continues normally. We emit 'warning' instead of 'error' so nothing -+ // upstream treats this as a fatal connection failure. -+ if (this.protocolState === states.PLAY) { -+ console.warn(`[minecraft-protocol] Non-fatal parse error (connection kept alive): ${e.message}`) -+ this.deserializer.removeAllListeners() -+ if (!this.compressor) { -+ this.splitter.unpipe(this.deserializer) -+ } else { -+ this.decompressor.unpipe(this.deserializer) -+ } -+ this.setSerializer(states.PLAY) -+ if (!this.compressor) { -+ this.splitter.pipe(this.deserializer) -+ } else { -+ this.decompressor.pipe(this.deserializer) -+ } -+ this.emit('warning', e) -+ return -+ } -+ -+ // All other parse errors are still fatal — re-pipe attempt then propagate. - if (!this.compressor) { this.splitter.pipe(this.deserializer) } else { this.decompressor.pipe(this.deserializer) } - this.emit('error', e) - }) -@@ -111,7 +139,13 @@ class Client extends EventEmitter { - this._hasBundlePacket = false - } - } else { -- emitPacket(parsed) -+ try { -+ emitPacket(parsed) -+ } catch (err) { -+ console.log('Client incorrectly handled packet ' + parsed.metadata.name) -+ console.error(err) -+ // todo investigate why it doesn't close the stream even if unhandled there -+ } - } - }) - } -@@ -169,7 +203,10 @@ class Client extends EventEmitter { - } - - const onFatalError = (err) => { -- this.emit('error', err) -+ // todo find out what is trying to write after client disconnect -+ if(err.code !== 'ECONNABORTED') { -+ this.emit('error', err) -+ } - endSocket() - } - -@@ -198,6 +235,10 @@ class Client extends EventEmitter { - serializer -> framer -> socket -> splitter -> deserializer */ - if (this.serializer) { - this.serializer.end() -+ setTimeout(() => { -+ this.socket?.end() -+ this.socket?.emit('end') -+ }, 2000) // allow the serializer to finish writing - } else { - if (this.socket) this.socket.end() - } -@@ -243,6 +284,7 @@ class Client extends EventEmitter { - debug('writing packet ' + this.state + '.' + name) - debug(params) - } -+ this.emit('writePacket', name, params) - this.serializer.write({ name, params }) - } - diff --git a/src/client/chat.js b/src/client/chat.js -index 0021870..a53fceb 100644 +index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b43cb102a 100644 --- a/src/client/chat.js +++ b/src/client/chat.js @@ -116,7 +116,7 @@ module.exports = function (client, options) { @@ -144,7 +57,7 @@ index 0021870..a53fceb 100644 previousMessages: client._lastSeenMessages.map((e) => ({ messageSender: e.sender, diff --git a/src/client/encrypt.js b/src/client/encrypt.js -index 63cc2bd..36df57d 100644 +index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f85410eaef 100644 --- a/src/client/encrypt.js +++ b/src/client/encrypt.js @@ -25,7 +25,11 @@ module.exports = function (client, options) { @@ -161,7 +74,7 @@ index 63cc2bd..36df57d 100644 function onJoinServerResponse (err) { diff --git a/src/client/play.js b/src/client/play.js -index 71ad739..7130b22 100644 +index 71ad739f118306b741908817851b0322db6f17e5..881abc9faf4dcf85127935b320e1a83786894330 100644 --- a/src/client/play.js +++ b/src/client/play.js @@ -31,11 +31,37 @@ module.exports = function (client, options) { @@ -195,8 +108,8 @@ index 71ad739..7130b22 100644 + locale: options.clientProfile?.locale ?? pickLocale(), + viewDistance: options.clientProfile?.viewDistance ?? 10, + mainHand: options.clientProfile?.mainHand !== undefined -+ ? options.clientProfile.mainHand -+ : (Math.random() < 0.75 ? 1 : 0) ++ ? options.clientProfile.mainHand ++ : (Math.random() < 0.75 ? 1 : 0) + } + if (mcData.supportFeature('hasConfigurationState')) { @@ -225,7 +138,7 @@ index 71ad739..7130b22 100644 client.write('select_known_packs', { packs: [] }) }) diff --git a/src/client/pluginChannels.js b/src/client/pluginChannels.js -index 671eb45..7f69f51 100644 +index 671eb452f31e6b5fcd57d715f1009d010160c65f..7f69f511c8fb97d431ec5125c851b49be8e2ab76 100644 --- a/src/client/pluginChannels.js +++ b/src/client/pluginChannels.js @@ -57,7 +57,7 @@ module.exports = function (client, options) { @@ -237,3 +150,90 @@ index 671eb45..7f69f51 100644 return } } +diff --git a/src/client.js b/src/client.js +index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..5395c9ac988d00a782d4ba98a73dc9d2e3fee666 100644 +--- a/src/client.js ++++ b/src/client.js +@@ -75,7 +75,35 @@ class Client extends EventEmitter { + } + const deserializerDirection = this.isServer ? 'toServer' : 'toClient' + e.field = [this.protocolState, deserializerDirection].concat(parts).join('.') +- e.message = e.buffer ? `Parse error for ${e.field} (${e.buffer?.length} bytes, ${e.buffer?.toString('hex').slice(0, 6)}...) : ${e.message}` : `Parse error for ${e.field}: ${e.message}` ++ e.message = e.buffer ++ ? `Parse error for ${e.field} (${e.buffer?.length} bytes, ${e.buffer?.toString('hex').slice(0, 6)}...) : ${e.message}` ++ : `Parse error for ${e.field}: ${e.message}` ++ ++ // Non-fatal play-state parse error — e.g. Hypixel sending NBT tag IDs outside ++ // the standard 0-20 range (tag 71 = custom component data introduced in 1.21.4+). ++ // The deserializer stream is destroyed by Node.js on error, so we tear it down ++ // fully, rebuild it via setSerializer, and re-pipe it into the chain so packet ++ // processing continues normally. We emit 'warning' instead of 'error' so nothing ++ // upstream treats this as a fatal connection failure. ++ if (this.protocolState === states.PLAY) { ++ console.warn(`[minecraft-protocol] Non-fatal parse error (connection kept alive): ${e.message}`) ++ this.deserializer.removeAllListeners() ++ if (!this.compressor) { ++ this.splitter.unpipe(this.deserializer) ++ } else { ++ this.decompressor.unpipe(this.deserializer) ++ } ++ this.setSerializer(states.PLAY) ++ if (!this.compressor) { ++ this.splitter.pipe(this.deserializer) ++ } else { ++ this.decompressor.pipe(this.deserializer) ++ } ++ this.emit('warning', e) ++ return ++ } ++ ++ // All other parse errors are still fatal — re-pipe attempt then propagate. + if (!this.compressor) { this.splitter.pipe(this.deserializer) } else { this.decompressor.pipe(this.deserializer) } + this.emit('error', e) + }) +@@ -111,7 +139,13 @@ class Client extends EventEmitter { + this._hasBundlePacket = false + } + } else { +- emitPacket(parsed) ++ try { ++ emitPacket(parsed) ++ } catch (err) { ++ console.log('Client incorrectly handled packet ' + parsed.metadata.name) ++ console.error(err) ++ // todo investigate why it doesn't close the stream even if unhandled there ++ } + } + }) + } +@@ -169,7 +203,10 @@ class Client extends EventEmitter { + } + + const onFatalError = (err) => { +- this.emit('error', err) ++ // todo find out what is trying to write after client disconnect ++ if (err.code !== 'ECONNABORTED') { ++ this.emit('error', err) ++ } + endSocket() + } + +@@ -198,6 +235,10 @@ class Client extends EventEmitter { + serializer -> framer -> socket -> splitter -> deserializer */ + if (this.serializer) { + this.serializer.end() ++ setTimeout(() => { ++ this.socket?.end() ++ this.socket?.emit('end') ++ }, 2000) // allow the serializer to finish writing + } else { + if (this.socket) this.socket.end() + } +@@ -243,6 +284,7 @@ class Client extends EventEmitter { + debug('writing packet ' + this.state + '.' + name) + debug(params) + } ++ this.emit('writePacket', name, params) + this.serializer.write({ name, params }) + } + diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 611773854..4989a5950 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.103.0 + minecraft-data: 3.106.0 prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything prismarine-physics: github:zardoy/prismarine-physics minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master @@ -23,8 +23,8 @@ overrides: patchedDependencies: minecraft-protocol: - hash: 8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24 - path: patches/minecraft-protocol.patch + hash: a0fc92234601e93b1e536d8d7cc9bf80a9df670d821e1d12746f261d15954fae + path: patches/minecraft-protocol@1.66.0.patch mineflayer-item-map-downloader@1.2.0: hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad path: patches/mineflayer-item-map-downloader@1.2.0.patch @@ -121,8 +121,8 @@ importers: specifier: ^10.0.12 version: 10.1.6 flying-squid: - specifier: npm:@zardoy/flying-squid@^0.0.118 - version: '@zardoy/flying-squid@0.0.118(encoding@0.1.13)' + specifier: npm:@zardoy/flying-squid@^0.0.119 + version: '@zardoy/flying-squid@0.0.119(encoding@0.1.13)' framer-motion: specifier: ^12.9.2 version: 12.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -140,13 +140,16 @@ 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/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13)) + version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b(encoding@0.1.13)) minecraft-data: - specifier: 3.103.0 - version: 3.103.0 + specifier: 3.106.0 + version: 3.106.0 + minecraft-inventory: + specifier: ^0.1.37 + version: 0.1.39(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) minecraft-protocol: specifier: github:PrismarineJS/node-minecraft-protocol#master - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=a0fc92234601e93b1e536d8d7cc9bf80a9df670d821e1d12746f261d15954fae)(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) @@ -170,7 +173,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.103.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.3 @@ -340,15 +343,12 @@ importers: mc-assets: specifier: ^0.2.72 version: 0.2.72 - minecraft-inventory-gui: - specifier: github:zardoy/minecraft-inventory-gui#next - version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db(@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/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b(encoding@0.1.13) mineflayer-mouse: - specifier: ^0.1.26 - version: 0.1.26 + specifier: 0.1.28 + version: 0.1.28 npm-run-all: specifier: ^4.1.5 version: 4.1.5 @@ -433,10 +433,10 @@ importers: version: 1.3.9 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-chunk: specifier: github:zardoy/prismarine-chunk#master - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -3413,8 +3413,8 @@ 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.118': - resolution: {integrity: sha512-FbpZfzuWaA8sUco5DNK7LfTVWk0KCHv4hb5G/7dmfzKTY93wM2M4iARBoyA2lAhLWt45bDEsHiEatODxNYykXw==} + '@zardoy/flying-squid@0.0.119': + resolution: {integrity: sha512-8IaWHgC4vlj53jGjlFEb0tdMF3+KYSAxmUI6D3lXwFqw0dPJSeEodyvew0qo9tbOhZ2tSY5UJwn35mnWFdYWUg==} engines: {node: '>=8'} hasBin: true @@ -4432,15 +4432,6 @@ packages: supports-color: optional: true - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -6492,7 +6483,7 @@ packages: resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: - minecraft-data: 3.103.0 + minecraft-data: 3.106.0 mcraft-fun-mineflayer@0.1.23: resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==} @@ -6702,19 +6693,27 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} - minecraft-data@3.103.0: - resolution: {integrity: sha512-laUAUiQ1OXPQPSzGFrx+v4M3LldcIiGuUjLAXzBmA3jZxjwwUKfgEr/7da8CvOeWlwkl+t/sMf5Fw7u7GNE2yg==} + minecraft-data@3.106.0: + resolution: {integrity: sha512-AE6d1WqU+BZED0foDWgXmpkBLYewxV49qfl4wwLM0qRqBpXXjB6FDgu3fM7uvcb2Q0x6jAMemvnsculLWU90og==} minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db: - resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db} - version: 1.0.1 + minecraft-inventory@0.1.39: + resolution: {integrity: sha512-m92NGCKsfqnTkNZ1J0s2PC5JSo+sHpT6nVHrkY16yOnLWauBu5mPR7v2MjVm7Hc9OL23XVKwAG8tMGy60ChtZw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + '@xmcl/text-component': '*' + mc-assets: '*' + react: ^18.2.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + mc-assets: + optional: true - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd: - resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd} - version: 1.64.0 + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77: + resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77} + version: 1.66.0 engines: {node: '>=22'} minecraft-wrap@1.6.0: @@ -6728,12 +6727,12 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824} version: 1.2.0 - mineflayer-mouse@0.1.26: - resolution: {integrity: sha512-fTHGUx+xy/43mA3of0cUQCSoXTQF6jvV7t8ALAIKKnvYCW5Y9Zcth0mZS5nUFszkJyYj47r7G44SjCHAS1sxvA==} + mineflayer-mouse@0.1.28: + resolution: {integrity: sha512-31r73CqU7eiv8cQSVA+NMR0WfA8Qs+p6of+Bd7ApWzR4a56W6fgfY3MExNmxxbb1/w9IbxTcMhDbcoKmE/5+nw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67: - resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b} version: 8.0.0 engines: {node: '>=22'} @@ -7431,26 +7430,26 @@ packages: prismarine-biome@1.3.0: resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==} peerDependencies: - minecraft-data: 3.103.0 + minecraft-data: 3.106.0 prismarine-registry: ^1.1.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9} - version: 1.21.0 + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad} + version: 1.23.0 prismarine-chat@1.11.0: resolution: {integrity: sha512-VJT/MWYB3qoiznUhrgvSQh76YFpzpCZpY85kJKxHLbd3UVoM0wsfs43Eg8dOltiZG92wc5/DTMLlT07TEeoa9w==} - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332} - version: 1.39.0 + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a} + version: 1.40.0 engines: {node: '>=14'} prismarine-entity@2.5.0: resolution: {integrity: sha512-nRPCawUwf9r3iKqi4I7mZRlir1Ix+DffWYdWq6p/KNnmiXve+xHE5zv8XCdhZlUmOshugHv5ONl9o6ORAkCNIA==} - prismarine-item@1.17.0: - resolution: {integrity: sha512-wN1OjP+f+Uvtjo3KzeCkVSy96CqZ8yG7cvuvlGwcYupQ6ct7LtNkubHp0AHuLMJ0vbbfAC0oZ2bWOgI1DYp8WA==} + prismarine-item@1.18.0: + resolution: {integrity: sha512-8pEq6YfcneVvarvUFnex09a3+MR8/4NCQVyawIKAa3kh/g9dHLexoEcpQEgM3cmpg4gbLmspSiARGwed5uGhlg==} prismarine-nbt@2.7.0: resolution: {integrity: sha512-Du9OLQAcCj3y29YtewOJbbV4ARaSUEJiTguw0PPQbPBy83f+eCyDRkyBpnXTi/KPyEpgYCzsjGzElevLpFoYGQ==} @@ -7459,15 +7458,15 @@ packages: resolution: {tarball: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b} version: 1.9.0 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7} - version: 2.8.0 + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9} + version: 2.13.0 prismarine-realms@1.3.2: resolution: {integrity: sha512-5apl9Ru8veTj5q2OozRc4GZOuSIcs3yY4UEtALiLKHstBe8bRw8vNlaz4Zla3jsQ8yP/ul1b1IJINTRbocuA6g==} - prismarine-recipe@1.3.1: - resolution: {integrity: sha512-xfa9E9ACoaDi+YzNQ+nk8kWSIqt5vSZOOCHIT+dTXscf/dng2HaJ/59uwe1D/jvOkAd2OvM6RRJM6fFe0q/LDA==} + prismarine-recipe@1.5.0: + resolution: {integrity: sha512-GRZHbsyBIUgVNF10vFRv2YWZj86vokCT5EWX6iK6gfx6h4FapgZT29V2DNkjv5+hmdzBCLZvfx1/RYr8VPeoGQ==} peerDependencies: prismarine-registry: ^1.4.0 @@ -10356,7 +10355,7 @@ snapshots: '@babel/parser': 7.26.9 '@babel/template': 7.26.9 '@babel/types': 7.26.9 - debug: 4.4.1 + debug: 4.4.3 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11389,10 +11388,10 @@ snapshots: '@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)': dependencies: '@nxg-org/mineflayer-util-plugin': 1.8.4 - minecraft-data: 3.103.0 - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-item: 1.17.0 + minecraft-data: 3.106.0 + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b(encoding@0.1.13) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad + prismarine-item: 1.18.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b vec3: 0.1.10 transitivePeerDependencies: @@ -12937,7 +12936,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.1.0(typescript@5.5.4) '@typescript-eslint/utils': 6.1.0(eslint@8.57.1)(typescript@5.5.4) - debug: 4.4.1 + debug: 4.4.3 eslint: 8.57.1 ts-api-utils: 1.4.3(typescript@5.5.4) optionalDependencies: @@ -12969,7 +12968,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.1 + debug: 4.4.3 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -12984,7 +12983,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.26.0 '@typescript-eslint/visitor-keys': 8.26.0 - debug: 4.4.1 + debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -13140,7 +13139,7 @@ snapshots: '@types/emscripten': 1.40.0 tslib: 1.14.1 - '@zardoy/flying-squid@0.0.118(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.119(encoding@0.1.13)': dependencies: '@tootallnate/once': 2.0.0 chalk: 5.4.1 @@ -13149,18 +13148,18 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - mc-bridge: 0.1.3(minecraft-data@3.103.0) - minecraft-data: 3.103.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + mc-bridge: 0.1.3(minecraft-data@3.106.0) + minecraft-data: 3.106.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=a0fc92234601e93b1e536d8d7cc9bf80a9df670d821e1d12746f261d15954fae)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.103.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -13187,16 +13186,16 @@ snapshots: exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.3.1 - minecraft-data: 3.103.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) + minecraft-data: 3.106.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=a0fc92234601e93b1e536d8d7cc9bf80a9df670d821e1d12746f261d15954fae)(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/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.103.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0) prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c rambda: 9.4.2 @@ -14446,10 +14445,6 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -14596,8 +14591,8 @@ snapshots: diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71: dependencies: - minecraft-data: 3.103.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) + minecraft-data: 3.106.0 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-registry: 1.11.0 random-seed: 0.3.0 vec3: 0.1.10 @@ -15016,7 +15011,7 @@ snapshots: esbuild-register@3.6.0(esbuild@0.18.20): dependencies: - debug: 4.4.1 + debug: 4.4.3 esbuild: 0.18.20 transitivePeerDependencies: - supports-color @@ -17048,17 +17043,17 @@ snapshots: maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.2 - mc-bridge@0.1.3(minecraft-data@3.103.0): + mc-bridge@0.1.3(minecraft-data@3.106.0): dependencies: - minecraft-data: 3.103.0 + minecraft-data: 3.106.0 - mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13)): + mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b(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/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) - mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) - prismarine-item: 1.17.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=a0fc92234601e93b1e536d8d7cc9bf80a9df670d821e1d12746f261d15954fae)(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b(encoding@0.1.13) + prismarine-item: 1.18.0 ws: 8.18.1 transitivePeerDependencies: - bufferutil @@ -17364,18 +17359,19 @@ snapshots: min-indent@1.0.1: {} - minecraft-data@3.103.0: {} + minecraft-data@3.106.0: {} minecraft-folder-path@1.2.0: {} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/ae382e30ad66c3ca40f51ff3376605e8c67413db(@types/react@18.3.18)(react@18.3.1): + minecraft-inventory@0.1.39(@xmcl/text-component@2.1.3)(mc-assets@0.2.72)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - valtio: 1.13.2(@types/react@18.3.18)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - react + '@xmcl/text-component': 2.1.3 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + mc-assets: 0.2.72 - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=a0fc92234601e93b1e536d8d7cc9bf80a9df670d821e1d12746f261d15954fae)(encoding@0.1.13): dependencies: '@types/node-rsa': 1.1.4 '@types/readable-stream': 4.0.18 @@ -17384,7 +17380,7 @@ snapshots: debug: 4.4.0(supports-color@8.1.1) endian-toggle: 0.0.0 lodash.merge: 4.6.2 - minecraft-data: 3.103.0 + minecraft-data: 3.106.0 minecraft-folder-path: 1.2.0 node-fetch: 2.7.0(encoding@0.1.13) node-rsa: 0.4.2 @@ -17427,35 +17423,36 @@ 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/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13) + mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b(encoding@0.1.13) sharp: 0.30.7 transitivePeerDependencies: - encoding - supports-color - mineflayer-mouse@0.1.26: + mineflayer-mouse@0.1.28: dependencies: change-case: 5.4.4 debug: 4.4.3 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c transitivePeerDependencies: - supports-color - mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/9442ffc3d82f8da410d4ab3d79cd9a63901e4c67(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/afebaa007ac86a02eba8e38bb2bdfb85487d9b8b(encoding@0.1.13): dependencies: '@nxg-org/mineflayer-physics-util': 1.8.19 - minecraft-data: 3.103.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/75399c59e103c8500357aa99b76d3600abea81fd(patch_hash=8a96528ea049e1239226caa349378cdf79f8b39939b5b502d226d643aac8ca24)(encoding@0.1.13) - prismarine-biome: 1.3.0(minecraft-data@3.103.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + minecraft-data: 3.106.0 + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3fabf16c61995b7a2c6d4d2902f7af99d0fe0a77(patch_hash=a0fc92234601e93b1e536d8d7cc9bf80a9df670d821e1d12746f261d15954fae)(encoding@0.1.13) + mojangson: 2.0.4 + prismarine-biome: 1.3.0(minecraft-data@3.106.0)(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-chat: 1.11.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-entity: 2.5.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b - prismarine-recipe: 1.3.1(prismarine-registry@1.11.0) + prismarine-recipe: 1.5.0(prismarine-registry@1.11.0) prismarine-registry: 1.11.0 prismarine-windows: 2.9.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c @@ -18237,17 +18234,17 @@ snapshots: transitivePeerDependencies: - supports-color - prismarine-biome@1.3.0(minecraft-data@3.103.0)(prismarine-registry@1.11.0): + prismarine-biome@1.3.0(minecraft-data@3.106.0)(prismarine-registry@1.11.0): dependencies: - minecraft-data: 3.103.0 + minecraft-data: 3.106.0 prismarine-registry: 1.11.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9: + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad: dependencies: - minecraft-data: 3.103.0 - prismarine-biome: 1.3.0(minecraft-data@3.103.0)(prismarine-registry@1.11.0) + minecraft-data: 3.106.0 + prismarine-biome: 1.3.0(minecraft-data@3.106.0)(prismarine-registry@1.11.0) prismarine-chat: 1.11.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18257,10 +18254,10 @@ snapshots: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0): dependencies: - prismarine-biome: 1.3.0(minecraft-data@3.103.0)(prismarine-registry@1.11.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + prismarine-biome: 1.3.0(minecraft-data@3.106.0)(prismarine-registry@1.11.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 smart-buffer: 4.2.0 @@ -18273,11 +18270,11 @@ snapshots: prismarine-entity@2.5.0: dependencies: prismarine-chat: 1.11.0 - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-registry: 1.11.0 vec3: 0.1.10 - prismarine-item@1.17.0: + prismarine-item@1.18.0: dependencies: prismarine-nbt: 2.7.0 prismarine-registry: 1.11.0 @@ -18288,16 +18285,15 @@ snapshots: prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b: dependencies: - minecraft-data: 3.103.0 + minecraft-data: 3.106.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.103.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/c4c47c0ffcc6cebcc85a414d73b1795cbfa2c6b9(minecraft-data@3.106.0): dependencies: - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/ec44d01fbe2e3a310c605761688a85119f056332(minecraft-data@3.103.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/1eb3a5d85dffe18b7b504c6e5871c4b36681df1a(minecraft-data@3.106.0) prismarine-nbt: 2.7.0 - prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c uint4: 0.1.2 vec3: 0.1.10 transitivePeerDependencies: @@ -18311,27 +18307,27 @@ snapshots: - encoding - supports-color - prismarine-recipe@1.3.1(prismarine-registry@1.11.0): + prismarine-recipe@1.5.0(prismarine-registry@1.11.0): dependencies: prismarine-registry: 1.11.0 prismarine-registry@1.11.0: dependencies: - minecraft-data: 3.103.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + minecraft-data: 3.106.0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-nbt: 2.7.0 prismarine-schematic@1.2.3: dependencies: - minecraft-data: 3.103.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9 + minecraft-data: 3.106.0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/401a296892fbde312ade34685fdc2db3d31668ad prismarine-nbt: 2.7.0 prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c vec3: 0.1.10 prismarine-windows@2.9.0: dependencies: - prismarine-item: 1.17.0 + prismarine-item: 1.18.0 prismarine-registry: 1.11.0 typed-emitter: 2.1.0 @@ -18522,7 +18518,7 @@ snapshots: puppeteer-core@2.1.1: dependencies: '@types/mime-types': 2.1.4 - debug: 4.4.1 + debug: 4.4.3 extract-zip: 1.7.0 https-proxy-agent: 4.0.0 mime: 2.6.0 diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index 9cf1350a5..a47121c7d 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -18,6 +18,7 @@ export const getInitialPlayerState = () => proxy({ backgroundColor: [0, 0, 0] as [number, number, number], ambientLight: 0, directionalLight: 0, + cardinalLight: 'default', eyeHeight: 0, gameMode: undefined as GameMode | undefined, lookingAtBlock: undefined as { diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts index 76025ad67..b7fc4f069 100644 --- a/renderer/viewer/lib/mesher/models.ts +++ b/renderer/viewer/lib/mesher/models.ts @@ -387,7 +387,16 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: const aos: number[] = [] const neighborPos = position.plus(new Vec3(...dir)) // 10% - const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15 + const { smoothLighting, shadingTheme, cardinalLight } = world.config + const faceLight = world.getLight(neighborPos, undefined, undefined, block.name) + const sideShading = (shadingTheme === 'high-contrast') ? + (0.8 + 0.5 * Math.max(0, 0.66 * dir[0] + 0.66 * dir[1] + 0.33 * dir[2])) : //old directional light behavior + ( + cardinalLight === 'nether' ? + (0.5 + Math.abs(0.1 * dir[0] + 0.4 * dir[1] + 0.3 * dir[2])) : + (0.75 + 0.25 * dir[1] + 0.05 * (Math.abs(dir[2]) - 3 * Math.abs(dir[0]))) + ) + const baseLight = sideShading * faceLight / 15 for (const pos of corners) { let vertex = [ (pos[0] ? maxx : minx), @@ -414,7 +423,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: } let light = 1 - const { smoothLighting } = world.config + // const smoothLighting = true if (doAO) { const dx = pos[0] * 2 - 1 @@ -427,7 +436,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: const side2 = world.getBlock(cursor.offset(...side2Dir)) const corner = world.getBlock(cursor.offset(...cornerDir)) - let cornerLightResult = baseLight * 15 + let cornerLightResult = faceLight if (smoothLighting) { const dirVec = new Vec3(...dir) @@ -444,7 +453,7 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: const cornerLightDir = getVec(new Vec3(...cornerDir)) const cornerLight = world.getLight(cursor.plus(cornerLightDir)) // interpolate - const lights = [side1Light, side2Light, cornerLight, baseLight * 15] + const lights = [side1Light, side2Light, cornerLight, faceLight] cornerLightResult = lights.reduce((acc, cur) => acc + cur, 0) / lights.length } @@ -455,8 +464,11 @@ function renderElement (world: World, cursor: Vec3, element: BlockElement, doAO: // TODO: correctly interpolate ao light based on pos (evaluate once for each corner of the block) const ao = (side1Block && side2Block) ? 0 : (3 - (side1Block + side2Block + cornerBlock)) + const ao_bias = (shadingTheme === 'high-contrast') ? 0.25 : 0.4 + const ao_scale = (shadingTheme === 'high-contrast') ? 0.25 : 0.2 + // todo light should go upper on lower blocks - light = (ao + 1) / 4 * (cornerLightResult / 15) + light = sideShading * (ao * ao_scale + ao_bias) * (cornerLightResult / 15) aos.push(ao) } diff --git a/renderer/viewer/lib/mesher/shared.ts b/renderer/viewer/lib/mesher/shared.ts index 4ea59b09a..b3defaf24 100644 --- a/renderer/viewer/lib/mesher/shared.ts +++ b/renderer/viewer/lib/mesher/shared.ts @@ -8,6 +8,8 @@ export const defaultMesherConfig = { enableLighting: true, skyLight: 15, smoothLighting: true, + shadingTheme: 'high-contrast', + cardinalLight: 'default', outputFormat: 'threeJs' as 'threeJs' | 'webgpu', // textureSize: 1024, // for testing debugModelVariant: undefined as undefined | number[], diff --git a/renderer/viewer/lib/mesher/test/tests.test.ts b/renderer/viewer/lib/mesher/test/tests.test.ts index 2c3dc6a53..6c96055c9 100644 --- a/renderer/viewer/lib/mesher/test/tests.test.ts +++ b/renderer/viewer/lib/mesher/test/tests.test.ts @@ -3,7 +3,8 @@ import supportedVersions from '../../../../../src/supportedVersions.mjs' import { INVISIBLE_BLOCKS } from '../worldConstants' import { setup } from './mesherTester' -const lastVersion = supportedVersions.at(-1) +// const lastVersion = supportedVersions.at(-1) +const lastVersion = '1.21' const addPositions = [ // [[0, 0, 0], 'diamond_block'], diff --git a/renderer/viewer/lib/worldrendererCommon.ts b/renderer/viewer/lib/worldrendererCommon.ts index f70b8f2ad..45633ff36 100644 --- a/renderer/viewer/lib/worldrendererCommon.ts +++ b/renderer/viewer/lib/worldrendererCommon.ts @@ -47,6 +47,8 @@ export const defaultWorldRendererConfig = { // Rendering engine settings dayCycle: true, smoothLighting: true, + shadingTheme: 'high-contrast', + cardinalLight: 'default', enableLighting: true, starfield: true, defaultSkybox: true, @@ -171,6 +173,7 @@ export abstract class WorldRendererCommon soundSystem: SoundSystem | undefined abstract changeBackgroundColor (color: [number, number, number]): void + abstract changeCardinalLight (string): void worldRendererConfig: WorldRendererConfig playerStateReactive: PlayerStateReactive @@ -344,6 +347,9 @@ export abstract class WorldRendererCommon this.onReactivePlayerStateUpdated('backgroundColor', (value) => { this.changeBackgroundColor(value) }) + this.onReactivePlayerStateUpdated('cardinalLight', (value) => { + this.changeCardinalLight(value) + }) } watchReactiveConfig () { @@ -572,6 +578,8 @@ export abstract class WorldRendererCommon enableLighting: this.worldRendererConfig.enableLighting, skyLight, smoothLighting: this.worldRendererConfig.smoothLighting, + shadingTheme: this.worldRendererConfig.shadingTheme, + cardinalLight: this.worldRendererConfig.cardinalLight, outputFormat: this.outputFormat, // textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width, debugModelVariant: this.worldRendererConfig.debugModelVariant, diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index d86fdeea1..933d362c3 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -46,7 +46,7 @@ export class WorldRendererThree extends WorldRendererCommon { directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5) entities = new Entities(this) cameraGroupVr?: THREE.Object3D - material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) + material = new THREE.MeshBasicMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 }) itemsTexture: THREE.Texture cursorBlock: CursorBlock onRender: Array<() => void> = [] @@ -272,6 +272,9 @@ export class WorldRendererThree extends WorldRendererCommon { changeBackgroundColor (color: [number, number, number]): void { this.scene.background = new THREE.Color(color[0], color[1], color[2]) } + changeCardinalLight (cardinalLight: string): void { + this.worldRendererConfig.cardinalLight = cardinalLight + } timeUpdated (newTime: number): void { const nightTime = 13_500 diff --git a/src/controls.ts b/src/controls.ts index 53375b080..c478258db 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -78,6 +78,7 @@ export const contro = new ControMax({ zoom: ['KeyC'], viewerConsole: ['Backquote'], togglePerspective: ['F5', 'Up'], + takeScreenshot: ['F2'], }, ui: { toggleFullscreen: ['F11'], @@ -146,6 +147,22 @@ const isSpectatingEntity = () => { return appViewer.playerState.utils.isSpectatingEntity() } +let lastScreenshotAt = 0 +const screenshotRepeatCooldownMs = 500 + +export const takeScreenshotAction = () => { + const now = Date.now() + if (now - lastScreenshotAt < screenshotRepeatCooldownMs) return + lastScreenshotAt = now + const canvas = document.getElementById('viewer-canvas') as HTMLCanvasElement | null + if (!canvas) return + const link = document.createElement('a') + link.href = canvas.toDataURL('image/png') + const date = new Date() + link.download = `screenshot ${date.toLocaleString().replaceAll('.', '-').replace(',', '')}.png` + link.click() +} + contro.on('movementUpdate', ({ vector, soleVector, gamepadIndex }) => { miscUiState.usingGamepadInput = gamepadIndex !== undefined if (!bot || !isGameActive(false) || isSpectatingEntity()) return @@ -489,6 +506,13 @@ const isCommandAvailableAfterDisconnect = (command: Command) => { contro.on('trigger', ({ command }) => { if (isCommandDisabled(command)) return + if (command === 'general.takeScreenshot') { + if (isGameActive(true)) { + takeScreenshotAction() + } + return + } + const willContinue = !isGameActive(true) alwaysPressedHandledCommand(command) if (willContinue && !isCommandAvailableAfterDisconnect(command)) return @@ -762,6 +786,7 @@ document.addEventListener('keydown', (e) => { if (contro.pressedKeys.has('F3')) { const keybind = f3Keybinds.find((v) => v.key === e.code) if (keybind && (keybind.enabled?.() ?? true)) { + e.preventDefault() // F4 etc. have browser defaults (e.g. F4 focuses URL bar) void keybind.action() e.stopPropagation() } @@ -868,18 +893,6 @@ window.addEventListener('keydown', (e) => { } }) -window.addEventListener('keydown', (e) => { - if (e.code !== 'F2' || e.repeat || !isGameActive(true)) return - e.preventDefault() - const canvas = document.getElementById('viewer-canvas') as HTMLCanvasElement - if (!canvas) return - const link = document.createElement('a') - link.href = canvas.toDataURL('image/png') - const date = new Date() - link.download = `screenshot ${date.toLocaleString().replaceAll('.', '-').replace(',', '')}.png` - link.click() -}) - window.addEventListener('keydown', (e) => { if (e.code !== 'F1' || e.repeat || !isGameActive(true)) return e.preventDefault() diff --git a/src/defaultOptions.ts b/src/defaultOptions.ts index f629afaa2..000d7df49 100644 --- a/src/defaultOptions.ts +++ b/src/defaultOptions.ts @@ -58,7 +58,10 @@ export const defaultOptions = { preciseMouseInput: false, // todo ui setting, maybe enable by default? waitForChunksRender: false as 'sp-only' | boolean, - jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>, + inventoryJei: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>, + inventoryNotes: true as boolean, + inventoryPlaceholders: false, + inventoryPlayerModel: true, modsSupport: false, modsAutoUpdate: 'check' as 'check' | 'never' | 'always', modsUpdatePeriodCheck: 24, // hours @@ -100,6 +103,7 @@ export const defaultOptions = { showCursorBlockInSpectator: false, renderEntities: true, smoothLighting: true, + vanillaLook: false, newVersionsLighting: false, chatSelect: true, autoJump: 'auto' as 'auto' | 'always' | 'never', diff --git a/src/index.ts b/src/index.ts index 365b89a4e..075dbc526 100644 --- a/src/index.ts +++ b/src/index.ts @@ -318,7 +318,11 @@ export async function connect (connectOptions: ConnectOptions) { signal: errorAbortController.signal }) window.addEventListener('error', (e) => { - handleError(e.message) + const statusAtError = appStatusState.status + setTimeout(() => { + if (appStatusState.status !== statusAtError || miscUiState.gameLoaded) return + handleError(e.message) + }, 10_000) }, { signal: errorAbortController.signal }) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 51db10ec2..e54bf11bc 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -1,126 +1,80 @@ import { proxy, subscribe } from 'valtio' -import { showInventory } from 'minecraft-inventory-gui/web/ext.mjs' -// import Dirt from 'mc-assets/dist/other-textures/latest/blocks/dirt.png' import { RecipeItem } from 'minecraft-data' -import { flat, fromFormattedString } from '@xmcl/text-component' +import { flat } from '@xmcl/text-component' import { splitEvery, equals } from 'rambda' import PItem, { Item } from 'prismarine-item' import { versionToNumber } from 'renderer/viewer/common/utils' -import { getRenamedData } from 'flying-squid/dist/blockRenames' -import PrismarineChatLoader from 'prismarine-chat' -import * as nbt from 'prismarine-nbt' -import { BlockModel } from 'mc-assets' +import { getInventoryType } from 'minecraft-inventory/src/registry' +import type { RecipeGuide, ItemStack as InventoryItemStack } from 'minecraft-inventory/src/types' +import type { JEIItem } from 'minecraft-inventory/src/components/JEI/JEI' 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' import { options } from './optionsStorage' -import { assertDefined, inGameError } from './utils' import { displayClientChat } from './botUtils' -import { currentScaling } from './scaleInterface' 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 = () => { - loadedImagesCache.delete('blocks') - loadedImagesCache.delete('items') -} +import { getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items' +import { clearInventoryCaches } from './react/inventory/Inventory' +import { buildBlockTexture, extractSpriteDataUrl } from './react/inventory/sharedConnectorSetup' -let lastWindow: ReturnType -let lastWindowType: string | null | undefined // null is inventory -/** bot version */ -let version: string let PrismarineItem: typeof Item 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, - followCursorCenter: { - x: 51, - y: 27, - }, - // 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(() => { - // Check if player inventory is still open before updating - if (lastWindowType === null) { - showInventoryPlayer() - } - }, 0) // todo patch instead and make reactive - remotePlayerSkin = a ?? '' - return remotePlayerSkin - }) - } -} +// ----- JEI items cache ----- +let jeiItemsCache: JEIItem[] | null = null +const clearJeiItemsCache = () => { jeiItemsCache = null } +subscribe(jeiCustomCategories, clearJeiItemsCache) export const onGameLoad = () => { - version = bot.version + PrismarineItem = PItem(bot.version) - PrismarineItem = PItem(version) - - const mapWindowType = (type: string, inventoryStart: number) => { + /** Maps mineflayer window type + inventoryStart to an exact key in the new library's registry */ + const mapWindowType = (type: string, inventoryStart: number): string => { + // minecraft:container size is determined by inventoryStart if (type === 'minecraft:container') { - if (inventoryStart === 45 - 9 * 4) return 'minecraft:generic_9x1' - if (inventoryStart === 45 - 9 * 3) return 'minecraft:generic_9x2' - if (inventoryStart === 45 - 9 * 2) return 'minecraft:generic_9x3' - if (inventoryStart === 45 - 9) return 'minecraft:generic_9x4' - if (inventoryStart === 45) return 'minecraft:generic_9x5' - if (inventoryStart === 45 + 9) return 'minecraft:generic_9x6' + if (inventoryStart === 45 - 9 * 4) return 'generic_9x1' + if (inventoryStart === 45 - 9 * 3) return 'generic_9x2' + if (inventoryStart === 45 - 9 * 2) return 'generic_9x3' + if (inventoryStart === 45 - 9) return 'generic_9x4' + if (inventoryStart === 45) return 'generic_9x5' + if (inventoryStart === 45 + 9) return 'generic_9x6' } - return type - } - - const maybeParseNbtJson = (data: any) => { - if (typeof data === 'string') { - try { - data = JSON.parse(data) - } catch (err) { - // ignore - } + // Version-specific smithing table layout + if (type === 'minecraft:smithing') { + return versionToNumber(bot.version) < versionToNumber('1.20') ? 'smithing_table_legacy' : 'smithing_table' } - return nbt.simplify(data) ?? data + // Strip minecraft: prefix then handle remaining aliases + const stripped = type.startsWith('minecraft:') ? type.slice(10) : type + const remap: Record = { + generic_5x1: 'hopper', + generic_3x3: 'dispenser', + crafting: 'crafting_table', + crafting3x3: 'crafting_table', + } + return remap[stripped] ?? stripped } bot.on('windowOpen', (win) => { - const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)] - if (implementedWindow) { - openWindow(implementedWindow, maybeParseNbtJson(win.title)) - } else if (options.unimplementedContainers) { - openWindow('ChestWin', maybeParseNbtJson(win.title)) + const mappedType = mapWindowType(win.type as string, win.inventoryStart) + const isImplemented = !!getInventoryType(mappedType) + + if (isImplemented || options.unimplementedContainers) { + if (activeModalStack.length && !miscUiState.disconnectedCleanup) { + hideCurrentModal() + } + showModal({ reactType: `player_win:${isImplemented ? mappedType : 'chest'}` }) } else { - // todo format - displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`) + displayClientChat(`[client error] cannot open unimplemented window ${(win as any).id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item as any)).filter(Boolean).join(', ')}`) + displayClientChat('You can help us fix it! Open a pull request adding support for it on https://github.com/zardoy/minecraft-inventory') bot.currentWindow?.['close']() } }) - // workaround: singleplayer player inventory crafting + // Workaround: singleplayer player-inventory crafting let skipUpdate = false bot.inventory.on('updateSlot', ((_oldSlot, oldItem, newItem) => { const currentSlot = _oldSlot as number @@ -149,441 +103,43 @@ export const onGameLoad = () => { }) } catch (err) { console.error(err) - // todo resolve the error! and why would we ever get here on every update? } }) as any) bot.on('windowClose', () => { - // todo hide up to the window itself! - if (lastWindow) { - hideCurrentModal() + const modal = activeModalStack.at(-1) + if (modal?.reactType?.startsWith('player_win:')) { + hideModal(undefined, undefined, { force: true }) } }) - bot.on('respawn', () => { // todo validate logic against native client (maybe login) - if (lastWindow) { - hideCurrentModal() + bot.on('respawn', () => { + const modal = activeModalStack.at(-1) + if (modal?.reactType?.startsWith('player_win:')) { + hideModal(undefined, undefined, { force: true }) } }) - customEvents.on('search', (q) => { - if (!lastWindow) return - upJei(q) - }) - if (!appViewer.resourcesManager['_inventoryChangeTracked']) { appViewer.resourcesManager['_inventoryChangeTracked'] = true - const texturesChanged = () => { - cleanLoadedImagesCache() - if (!lastWindow) return - upWindowItemsLocal() - upJei(lastJeiSearch) - } - appViewer.resourcesManager.on('assetsInventoryReady', () => texturesChanged()) - appViewer.resourcesManager.on('assetsTexturesUpdated', () => texturesChanged()) + appViewer.resourcesManager.on('assetsInventoryReady', () => { clearJeiItemsCache(); clearInventoryCaches() }) + appViewer.resourcesManager.on('assetsTexturesUpdated', () => { clearJeiItemsCache(); clearInventoryCaches() }) } } -const getImageSrc = (path): string | HTMLImageElement | ImageBitmap => { - switch (path) { - case 'gui/container/inventory': return appReplacableResources.latest_gui_container_inventory.content - case 'blocks': return appViewer.resourcesManager.blocksAtlasParser.latestImage - case 'items': return appViewer.resourcesManager.itemsAtlasParser.latestImage - case 'gui': return appViewer.resourcesManager.currentResources!.guiAtlas!.image - case 'gui/container/dispenser': return appReplacableResources.latest_gui_container_dispenser.content - case 'gui/container/furnace': return appReplacableResources.furnace_gui_texture.content - case 'gui/container/crafting_table': return appReplacableResources.latest_gui_container_crafting_table.content - case 'gui/container/shulker_box': return appReplacableResources.latest_gui_container_shulker_box.content - case 'gui/container/generic_54': return appReplacableResources.latest_gui_container_generic_54.content - case 'gui/container/generic_95': return Generic95 - case 'gui/container/hopper': return appReplacableResources.latest_gui_container_hopper.content - case 'gui/container/horse': return appReplacableResources.latest_gui_container_horse.content - case 'gui/container/villager2': return appReplacableResources.latest_gui_container_villager2.content - case 'gui/container/enchanting_table': return appReplacableResources.latest_gui_container_enchanting_table.content - case 'gui/container/anvil': return appReplacableResources.latest_gui_container_anvil.content - case 'gui/container/beacon': return appReplacableResources.latest_gui_container_beacon.content - case 'gui/container/smithing': - return versionToNumber(bot.version) < versionToNumber('1.20') - ? appReplacableResources._1_19_4_gui_container_smithing.content - : appReplacableResources.latest_gui_container_smithing.content - case 'gui/widgets': return appReplacableResources.other_textures_latest_gui_widgets.content - } - // empty texture - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=' -} - -const getImage = ({ path = undefined as string | undefined, texture = undefined as string | undefined, blockData = undefined as any, image = undefined as HTMLImageElement | undefined }, onLoad = () => { }) => { - if (image) { - return image - } - if (!path && !texture) { - throw new Error('Either pass path or texture') - } - const loadPath = (blockData ? 'blocks' : path ?? texture)! - if (loadedImagesCache.has(loadPath)) { - onLoad() - } else { - const imageSrc = getImageSrc(loadPath) - if (imageSrc instanceof ImageBitmap) { - onLoad() - loadedImagesCache.set(loadPath, imageSrc) - return imageSrc - } - - let image: HTMLImageElement - if (imageSrc instanceof Image) { - image = imageSrc - } else { - image = new Image() - image.src = imageSrc - } - image.onload = onLoad - loadedImagesCache.set(loadPath, image) - } - return loadedImagesCache.get(loadPath) -} - const getItemName = (slot: Item | RenderItem | null) => { const parsed = getItemNameRaw(slot, appViewer.resourcesManager) if (!parsed) return - // todo display full text renderer from sign renderer const text = flat(parsed as MessageFormatPart).map(x => (typeof x === 'string' ? x : x.text)) return text.join('') } -let lastMappedSlots = [] as any[] -const itemToVisualKey = (slot: RenderItem | Item | null) => { - if (!slot) return '' - const keys = [ - slot.name, - slot.durabilityUsed, - slot.maxDurability, - slot['count'], - slot['metadata'], - slot.nbt ? JSON.stringify(slot.nbt) : '', - slot['components'] ? JSON.stringify(slot['components']) : '', - appViewer.resourcesManager.currentResources!.guiAtlasVersion, - ].join('|') - return keys -} -const validateSlot = (slot: any, index: number) => { - if (!slot.texture) { - throw new Error(`Slot has no texture: ${index} ${slot.name}`) - } -} -const mapSlots = (slots: Array, isJei = false) => { - const newSlots = slots.map((slot, i) => { - if (!slot) return null - - if (!isJei) { - const oldKey = lastMappedSlots[i]?.cacheKey - const newKey = itemToVisualKey(slot) - slot['cacheKey'] = i + '|' + newKey - if (oldKey && oldKey === newKey) { - validateSlot(lastMappedSlots[i], i) - return lastMappedSlots[i] - } - } - - try { - if (slot.durabilityUsed && slot.maxDurability) slot.durabilityUsed = Math.min(slot.durabilityUsed, slot.maxDurability) - const debugIsQuickbar = !isJei && i === bot.inventory.hotbarStart + bot.quickBarSlot - const modelName = getItemModelName(slot, { 'minecraft:display_context': 'gui', }, appViewer.resourcesManager, appViewer.playerState.reactive) - const slotCustomProps = renderSlot({ modelName, originalItemName: slot.name }, appViewer.resourcesManager, debugIsQuickbar) - const itemCustomName = getItemName(slot) - Object.assign(slot, { ...slotCustomProps, displayName: itemCustomName ?? slot.displayName }) - //@ts-expect-error - slot.toJSON = () => { - // Allow to serialize slot to JSON as minecraft-inventory-gui creates icon property as cache (recursively) - //@ts-expect-error - const { icon, ...rest } = slot - return rest - } - validateSlot(slot, i) - } catch (err) { - inGameError(err) - } - return slot - }) - lastMappedSlots = JSON.parse(JSON.stringify(newSlots)) - return newSlots -} - -export const upInventoryItems = (isInventory: boolean, invWindow = lastWindow) => { - // inv.pwindow.inv.slots[2].displayName = 'test' - // inv.pwindow.inv.slots[2].blockData = getBlockData('dirt') - const customSlots = mapSlots((isInventory ? bot.inventory : bot.currentWindow)!.slots) - invWindow.pwindow.setSlots(customSlots) - return customSlots -} - -export const onModalClose = (callback: () => any) => { - const modal = activeModalStack.at(-1) - const unsubscribe = subscribe(activeModalStack, () => { - const newModal = activeModalStack.at(-1) - if (modal?.reactType !== newModal?.reactType) { - callback() - unsubscribe() - } - }, true) -} - -const implementedContainersGuiMap = { - // todo allow arbitrary size instead! - 'minecraft:generic_9x1': 'ChestWin', - 'minecraft:generic_9x2': 'ChestWin', - 'minecraft:generic_9x3': 'ChestWin', - 'minecraft:generic_9x4': 'Generic95Win', - 'minecraft:generic_9x5': 'Generic95Win', - // hopper - 'minecraft:hopper': 'HopperWin', - 'minecraft:generic_5x1': 'HopperWin', - 'minecraft:generic_9x6': 'LargeChestWin', - 'minecraft:generic_3x3': 'DropDispenseWin', - 'minecraft:furnace': 'FurnaceWin', - 'minecraft:smoker': 'FurnaceWin', - 'minecraft:blast_furnace': 'FurnaceWin', - 'minecraft:shulker_box': 'ChestWin', - 'minecraft:crafting': 'CraftingWin', - 'minecraft:smithing': 'will_be_patched_in_openWindow', - 'minecraft:crafting3x3': 'CraftingWin', // todo different result slot - 'minecraft:anvil': 'AnvilWin', - // enchant - 'minecraft:enchanting_table': 'EnchantingWin', - // horse - 'minecraft:horse': 'HorseWin', - // villager - 'minecraft:villager': 'VillagerWin', -} - -let lastJeiSearch = '' -const upJei = (search: string) => { - lastJeiSearch = search - search = search.toLowerCase() - // todo fix pre flat - const itemsArray = [ - ...jeiCustomCategories.value.flatMap(x => x.items).filter(x => x !== null), - ...loadedData.itemsArray.filter(x => x.displayName.toLowerCase().includes(search)).map(item => new PrismarineItem(item.id, 1)).filter(x => x !== null) - ] - const matchedSlots = itemsArray.map(x => { - x.displayName = getItemName(x) ?? x.displayName - if (!x.displayName.toLowerCase().includes(search)) return null - return x - }).filter(a => a !== null) - lastWindow.pwindow.win.jeiSlotsPage = 0 - lastWindow.pwindow.win.jeiSlots = mapSlots(matchedSlots, true) -} - -export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { - const inv = showInventory(type, getImage, {}, _bot); - (inv.canvasManager.children[0].callbacks as any).getItemRecipes = (item) => { - const allRecipes = getAllItemRecipes(item.name) - inv.canvasManager.children[0].messageDisplay = '' - const itemDescription = getItemDescription(item) - if (!allRecipes?.length && !itemDescription) { - inv.canvasManager.children[0].messageDisplay = `No recipes found for ${item.displayName}` - } - return [...allRecipes ?? [], ...itemDescription ? [ - [ - 'GenericDescription', - mapSlots([item], true)[0], - [], - itemDescription - ] - ] : []] - } - (inv.canvasManager.children[0].callbacks as any).getItemUsages = (item) => { - const allItemUsages = getAllItemUsages(item.name) - inv.canvasManager.children[0].messageDisplay = '' - if (!allItemUsages?.length) { - inv.canvasManager.children[0].messageDisplay = `No usages found for ${item.displayName}` - } - return allItemUsages - } - return inv -} - -const upWindowItemsLocal = () => { - void Promise.resolve().then(() => { - if (!lastWindow && bot.currentWindow) { - // edge case: might happen due to high ping, inventory should be closed soon! - // openWindow(implementedContainersGuiMap[bot.currentWindow.type]) - return - } - upInventoryItems(lastWindowType === null) - }) -} - -let skipClosePacketSending = false -const openWindow = (type: string | undefined, title: string | any = undefined) => { - // patch implementedContainersGuiMap - implementedContainersGuiMap['minecraft:smithing'] = versionToNumber(bot.version) < versionToNumber('1.20') ? 'SmithingTableLegacyWin' : 'SmithingTableWin' - - // if (activeModalStack.some(x => x.reactType?.includes?.('player_win:'))) { - if (activeModalStack.length && !miscUiState.disconnectedCleanup) { // game is not in foreground, don't close current modal - if (type) { - skipClosePacketSending = true - hideCurrentModal() - } else { - bot.currentWindow?.['close']() - return - } - } - lastWindowType = type ?? null - showModal({ - reactType: `player_win:${type}`, - }) - if (type === undefined) { - showInventoryPlayer() - } - cleanLoadedImagesCache() - const inv = openItemsCanvas(type) - inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch - window.inventory = inv - const PrismarineChat = PrismarineChatLoader(bot.version) - try { - inv.canvasManager.children[0].customTitleText = title ? - typeof title === 'string' ? - fromFormattedString(title).text : - new PrismarineChat(title).toString() : - undefined - } catch (err) { - reportError?.(err) - inv.canvasManager.children[0].customTitleText = undefined - } - // todo - inv.canvasManager.setScale(currentScaling.scale === 1 ? 1.5 : currentScaling.scale) - inv.canvas.style.zIndex = '10' - inv.canvas.style.position = 'fixed' - inv.canvas.style.inset = '0' - - inv.canvasManager.onClose = async () => { - await new Promise(resolve => { - setTimeout(resolve, 0) - }) - if (activeModalStack.at(-1)?.reactType?.includes('player_win:')) { - hideModal(undefined, undefined, { force: true }) - } - inv.canvasManager.destroy() - } - - lastWindow = inv - - onModalClose(() => { - // might be already closed (event fired) - if (type !== undefined && bot.currentWindow && !skipClosePacketSending) bot.currentWindow['close']() - lastWindow.destroy() - lastWindow = null as any - lastWindowType = undefined - window.inventory = null - miscUiState.displaySearchInput = false - destroyFn() - skipClosePacketSending = false - - modelViewerState.model = undefined - }) - - upWindowItemsLocal() - - lastWindow.pwindow.touch = miscUiState.currentTouch ?? false - const oldOnInventoryEvent = lastWindow.pwindow.onInventoryEvent.bind(lastWindow.pwindow) - lastWindow.pwindow.onInventoryEvent = (type, containing, windowIndex, inventoryIndex, item) => { - if (inv.canvasManager.children[0].currentGuide) { - const isRightClick = type === 'rightclick' - const isLeftClick = type === 'leftclick' - if (isLeftClick || isRightClick) { - modelViewerState.model = undefined - inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item) - } - } else { - oldOnInventoryEvent(type, containing, windowIndex, inventoryIndex, item) - } - } - lastWindow.pwindow.onJeiClick = (slotItem, _index, isRightclick) => { - if (!slotItem) { - console.warn('onJeiClick called with undefined slotItem') - return - } - if (versionToNumber(bot.version) < versionToNumber('1.13')) { - alert('Item give is broken on 1.12.2 and below, we are working on it!') - return - } - // slotItem is the slot from mapSlots - const itemId = loadedData.itemsByName[slotItem.name]?.id - if (!itemId) { - inGameError(`Item for block ${slotItem.name} not found`) - return - } - const item = PrismarineItem.fromNotch({ - ...slotItem, - itemId, - itemCount: isRightclick ? 64 : 1, - components: slotItem.components ?? [], - removeComponents: slotItem.removedComponents ?? [], - itemDamage: slotItem.metadata ?? 0, - nbt: slotItem.nbt, - }) - if (bot.game.gameMode === 'creative') { - const freeSlot = bot.inventory.firstEmptyInventorySlot() - if (freeSlot === null) { - console.warn('[JEI] no free inventory slot available') - return - } - bot._client.write('set_creative_slot', { - slot: freeSlot, - item: PrismarineItem.toNotch(item) - }) - //@ts-expect-error - bot._setSlot(freeSlot, item) - } else { - modelViewerState.model = undefined - inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0]) - } - } - - const isJeiEnabled = () => { - if (typeof options.jeiEnabled === 'boolean') return options.jeiEnabled - if (Array.isArray(options.jeiEnabled)) { - return options.jeiEnabled.includes(bot.game?.gameMode as any) - } - return false - } - - if (isJeiEnabled()) { - lastWindow.pwindow.win.jeiSlotsPage = 0 - // todo workaround so inventory opens immediately (though it still lags) - setTimeout(() => { - upJei('') - }) - miscUiState.displaySearchInput = true - } else { - lastWindow.pwindow.win.jeiSlots = [] - miscUiState.displaySearchInput = false - } - - if (type === undefined) { - // player inventory - bot.inventory.on('updateSlot', upWindowItemsLocal) - destroyFn = () => { - bot.inventory.off('updateSlot', upWindowItemsLocal) - } - } else { - //@ts-expect-error - bot.currentWindow.on('updateSlot', () => { - upWindowItemsLocal() - }) - } -} - -let destroyFn = () => { } - export const openPlayerInventory = () => { - openWindow(undefined) + showModal({ reactType: 'player_win:player' }) } const getResultingRecipe = (slots: Array, gridRows: number) => { const inputSlotsItems = slots.map(blockSlot => blockSlot?.type) let currentShape = splitEvery(gridRows, inputSlotsItems as Array) - // todo rewrite with candidates search if (currentShape.length > 1) { // eslint-disable-next-line @typescript-eslint/no-for-in-array for (const slotX in currentShape[0]) { @@ -598,7 +154,6 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { } currentShape = currentShape.map(arr => arr.filter(x => x !== undefined)).filter(x => x.length !== 0) - // todo rewrite // eslint-disable-next-line @typescript-eslint/require-array-sort-compare const slotsIngredients = [...inputSlotsItems].sort().filter(item => item !== undefined) type Result = RecipeItem | undefined @@ -618,82 +173,242 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { } const result = shapeResult ?? shapelessResult if (!result) return - const id = typeof result === 'number' ? result : Array.isArray(result) ? result[0] : result.id + const id = typeof result === 'number' ? result : Array.isArray(result) ? result[0] : (result as any).id if (!id) return - const count = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : result.count) ?? 1 - const metadata = typeof result === 'object' && !Array.isArray(result) ? result.metadata : undefined - const item = new PrismarineItem(id, count, metadata) + const count = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : (result as any).count) ?? 1 + const metadata = typeof result === 'object' && !Array.isArray(result) ? (result as any).metadata : undefined + const item = new PrismarineItem(id as number, count, metadata) return item } const ingredientToItem = (recipeItem) => (recipeItem === null ? null : new PrismarineItem(recipeItem, 1)) -const getAllItemRecipes = (itemName: string) => { +// Legacy format used by openItemsCanvas / HotbarRenderApp +// const getAllItemRecipesLegacy = (itemName: string) => { +// const item = loadedData.itemsByName[itemName] +// if (!item) return +// const itemId = item.id +// const recipes = loadedData.recipes?.[itemId] +// if (!recipes) return +// const results = [] as Array<{ +// result: Item, +// ingredients: Array, +// description?: string +// }> + +// for (const recipe of recipes) { +// const { result } = recipe +// if (!result) continue +// const resultId = typeof result === 'number' ? result : Array.isArray(result) ? result[0]! : (result as any).id +// const resultCount = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : (result as any).count) ?? 1 +// const resultMetadata = typeof result === 'object' && !Array.isArray(result) ? (result as any).metadata : undefined +// const resultItem = new PrismarineItem(resultId as number, resultCount, resultMetadata) +// if ('inShape' in recipe) { +// const ingredients = recipe.inShape +// if (!ingredients) continue +// const ingredientsItems = ingredients.flatMap(items => items.map(item => ingredientToItem(item))) +// results.push({ result: resultItem, ingredients: ingredientsItems }) +// } +// if ('ingredients' in recipe) { +// const { ingredients } = recipe +// if (!ingredients) continue +// const ingredientsItems = ingredients.map(item => ingredientToItem(item)) +// results.push({ result: resultItem, ingredients: ingredientsItems, description: 'Shapeless' }) +// } +// } +// return results.map(({ result, ingredients, description }) => { +// return [ +// 'CraftingTableGuide', +// mapSlots([result], true)[0], +// mapSlots(ingredients, true), +// description +// ] +// }) +// } + +// const getAllItemUsagesLegacy = (itemName: string) => { +// const item = loadedData.itemsByName[itemName] +// if (!item) return +// const foundRecipeIds = [] as string[] + +// for (const [id, recipes] of Object.entries(loadedData.recipes ?? {})) { +// for (const recipe of recipes) { +// if ('inShape' in recipe) { +// if (recipe.inShape.some(row => row.includes(item.id))) { +// foundRecipeIds.push(id) +// } +// } +// if ('ingredients' in recipe) { +// if (recipe.ingredients.includes(item.id)) { +// foundRecipeIds.push(id) +// } +// } +// } +// } + +// return foundRecipeIds.flatMap(id => { +// return getAllItemRecipesLegacy(loadedData.items[id].name) +// }) +// } + +// ----- New React inventory exports ----- + +/** Enrich a single item with texture data from the rendering pipeline (same as JEI enrichment) */ +const enrichItemTexture = (item: InventoryItemStack): void => { + if (!appViewer?.resourcesManager?.currentResources) return + const playerState = appViewer.playerState?.reactive + if (!playerState) return + try { + const modelName = getItemModelName( + { name: item.name ?? '', nbt: null }, + { 'minecraft:display_context': 'gui' }, + appViewer.resourcesManager, + playerState + ) + const slotProps = renderSlot({ modelName, originalItemName: item.name ?? '' }, appViewer.resourcesManager) + if (slotProps.blockData) { + item.blockTexture = buildBlockTexture(slotProps.blockData as Record) + } else if (slotProps.slice) { + item.texture = extractSpriteDataUrl(slotProps.texture, slotProps.slice) + } + } catch { /* skip items that fail enrichment */ } +} + +/** Helper: convert a minecraft-data item ID to a minimal ItemStack for recipe guides */ +const idToItemStack = (id: number | null | undefined): InventoryItemStack | null => { + if (!id) return null + const data = loadedData.items[id] + if (!data) return null + const stack: InventoryItemStack = { type: id, count: 1, name: data.name, displayName: data.displayName } + enrichItemTexture(stack) + return stack +} + +/** + * Returns recipes for the given item name in the new library's RecipeGuide format. + * Used by Inventory.tsx for JEI recipe lookup. + */ +export const getItemRecipes = (itemName: string): RecipeGuide[] => { + if (!PrismarineItem) return [] const item = loadedData.itemsByName[itemName] - if (!item) return - const itemId = item.id - const recipes = loadedData.recipes?.[itemId] - if (!recipes) return - const results = [] as Array<{ - result: Item, - ingredients: Array, - description?: string - }> - - // get recipes here + if (!item) return [] + const recipes = loadedData.recipes?.[item.id] + if (!recipes) { + const description = getItemDescription(item as any) + if (description) return [{ type: 'custom', title: item.displayName, description }] + return [] + } + + const guides: RecipeGuide[] = [] + for (const recipe of recipes) { const { result } = recipe if (!result) continue - const resultId = typeof result === 'number' ? result : Array.isArray(result) ? result[0]! : result.id - const resultCount = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : result.count) ?? 1 - const resultMetadata = typeof result === 'object' && !Array.isArray(result) ? result.metadata : undefined - const resultItem = new PrismarineItem(resultId!, resultCount, resultMetadata) - if ('inShape' in recipe) { - const ingredients = recipe.inShape - if (!ingredients) continue - - const ingredientsItems = ingredients.flatMap(items => items.map(item => ingredientToItem(item))) - results.push({ result: resultItem, ingredients: ingredientsItems }) + const resultId = typeof result === 'number' ? result : Array.isArray(result) ? result[0]! : (result as any).id + const resultCount = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : (result as any).count) ?? 1 + const resultData = resultId ? loadedData.items[resultId as number] : undefined + if (!resultData) continue + const resultStack: InventoryItemStack = { type: resultId, count: resultCount, name: resultData.name, displayName: resultData.displayName } + enrichItemTexture(resultStack) + + if ('inShape' in recipe && recipe.inShape) { + // Expand shaped recipe into a 9-element 3x3 grid (top-left aligned) + const grid: Array = Array.from({ length: 9 }, () => null) + for (const [rowIdx, row] of recipe.inShape.entries()) { + if (rowIdx >= 3) continue + for (const [colIdx, id] of row.entries()) { + if (colIdx >= 3) continue + grid[rowIdx * 3 + colIdx] = idToItemStack(id as number | null | undefined) + } + } + guides.push({ type: 'crafting', title: resultData.displayName, ingredients: grid, result: resultStack }) } - if ('ingredients' in recipe) { - const { ingredients } = recipe - if (!ingredients) continue - const ingredientsItems = ingredients.map(item => ingredientToItem(item)) - results.push({ result: resultItem, ingredients: ingredientsItems, description: 'Shapeless' }) + + if ('ingredients' in recipe && recipe.ingredients) { + const grid: Array = Array.from({ length: 9 }, () => null) + for (const [i, id] of recipe.ingredients.slice(0, 9).entries()) { grid[i] = idToItemStack(id as number | null | undefined) } + guides.push({ type: 'crafting', title: resultData.displayName, description: 'Shapeless', ingredients: grid, result: resultStack }) } } - return results.map(({ result, ingredients, description }) => { - return [ - 'CraftingTableGuide', - mapSlots([result], true)[0], - mapSlots(ingredients, true), - description - ] - }) + + const description = getItemDescription(item as any) + if (description) guides.push({ type: 'custom', title: item.displayName, description }) + + return guides } -const getAllItemUsages = (itemName: string) => { +/** + * Returns usages (recipes where this item is an ingredient) in RecipeGuide format. + * Used by Inventory.tsx for JEI usage lookup. + */ +export const getItemUsages = (itemName: string): RecipeGuide[] => { const item = loadedData.itemsByName[itemName] - if (!item) return - const foundRecipeIds = [] as string[] + if (!item) return [] + const foundItemIds = new Set() for (const [id, recipes] of Object.entries(loadedData.recipes ?? {})) { for (const recipe of recipes) { - if ('inShape' in recipe) { - if (recipe.inShape.some(row => row.includes(item.id))) { - foundRecipeIds.push(id) - } + if ('inShape' in recipe && recipe.inShape.some(row => row.includes(item.id))) { + foundItemIds.add(id) } - if ('ingredients' in recipe) { - if (recipe.ingredients.includes(item.id)) { - foundRecipeIds.push(id) - } + if ('ingredients' in recipe && recipe.ingredients.includes(item.id)) { + foundItemIds.add(id) } } } - return foundRecipeIds.flatMap(id => { - // todo should use exact match, not include all recipes! - return getAllItemRecipes(loadedData.items[id].name) - }) + return [...foundItemIds].flatMap(id => getItemRecipes(loadedData.items[id].name)) +} + +/** + * Returns all JEI items (custom categories + vanilla items) for the new inventory UI. + * Items are enriched with texture/blockTexture data from the rendering pipeline. + */ +export const getJeiItems = (): JEIItem[] => { + if (jeiItemsCache) return jeiItemsCache + if (!PrismarineItem) return [] + + const customItems: JEIItem[] = jeiCustomCategories.value.flatMap(cat => (cat.items).filter(Boolean).map(item => ({ + type: item.type ?? item.id ?? 0, + name: item.name ?? '', + displayName: getItemName(item) ?? item.displayName ?? item.name ?? '', + }))) + + const vanillaItems: JEIItem[] = loadedData.itemsArray.map(item => ({ + type: item.id, + name: item.name, + displayName: item.displayName, + })) + + const allItems = [...customItems, ...vanillaItems] + + // Enrich items with texture data if the rendering pipeline is available + if (!appViewer?.resourcesManager?.currentResources) return allItems + + const { resourcesManager } = appViewer + const playerState = appViewer.playerState?.reactive + if (!playerState) return allItems + + for (const item of allItems) { + try { + const modelName = getItemModelName( + { name: item.name, nbt: null }, + { 'minecraft:display_context': 'gui' }, + resourcesManager, + playerState + ) + const slotProps = renderSlot({ modelName, originalItemName: item.name }, resourcesManager) + + if (slotProps.blockData) { + item.blockTexture = buildBlockTexture(slotProps.blockData as Record) + } else if (slotProps.slice) { + item.texture = extractSpriteDataUrl(slotProps.texture, slotProps.slice) + } + } catch { + // Skip texture enrichment for items that fail — they'll fall back to CDN sprites + } + } + + jeiItemsCache = allItems + return allItems } diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts index 33f7af772..f472d4233 100644 --- a/src/mineflayer/playerState.ts +++ b/src/mineflayer/playerState.ts @@ -53,6 +53,14 @@ export class PlayerStateControllerMain { hasSkyLight = data.dimension.value.has_skylight.value } catch {} this.reactive.lightingDisabled = bot.game.dimension === 'the_nether' || bot.game.dimension === 'the_end' || !hasSkyLight + let cardinalLight = 'default' + try { + cardinalLight = data.dimension.value.effects.value === 'minecraft:the_nether' ? 'nether' : 'default' + } catch {} + try { + cardinalLight = data.dimension.value.cardinal_light.value //servers after 1.21.11, untested + } catch {} + this.reactive.cardinalLight = cardinalLight } bot._client.on('login', (packet) => { diff --git a/src/mineflayer/plugins/mouse.ts b/src/mineflayer/plugins/mouse.ts index 14e193455..1ca73ceb2 100644 --- a/src/mineflayer/plugins/mouse.ts +++ b/src/mineflayer/plugins/mouse.ts @@ -45,7 +45,9 @@ function cursorBlockDisplay (bot: Bot) { } export default (bot: Bot) => { - bot.loadPlugin(createMouse({})) + bot.loadPlugin(createMouse({ + useMineflayerInteractMethods: false, + })) domListeners(bot) cursorBlockDisplay(bot) diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 64189dbc4..d86c2e447 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -68,6 +68,9 @@ export const guiOptionsScheme: { activeRenderer: { text: 'Renderer', }, + vanillaLook: { + tooltip: 'On: Minecraft-style face shading. Off: client’s higher-contrast shading (default).', + }, }, { custom () { @@ -194,16 +197,16 @@ export const guiOptionsScheme: { return } if (choice === 'Uninstall') { - // todo make hidable + // todo make hidable setLoadingScreenStatus('Uninstalling texturepack') await uninstallResourcePack() setLoadingScreenStatus(undefined) } } else { - // if (!fsState.inMemorySave && isGameActive(false)) { - // alert('Unable to install resource pack in loaded save for now') - // return - // } + // if (!fsState.inMemorySave && isGameActive(false)) { + // alert('Unable to install resource pack in loaded save for now') + // return + // } openFilePicker('resourcepack') } }} @@ -255,6 +258,13 @@ export const guiOptionsScheme: { unit: '', delayApply: true, }, + }, + { + custom () { + return