Compare commits

..

1 commit

Author SHA1 Message Date
Vitaly Turovsky
9a25d60991 a little less fog 2025-09-05 21:35:16 +03:00
34 changed files with 127 additions and 1035 deletions

View file

@ -78,8 +78,6 @@ There is a builtin proxy, but you can also host your one! Just clone the repo, r
[![Deploy to Koyeb](https://www.koyeb.com/static/images/deploy/button.svg)](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F)
> **Note**: If you want to make **your own** Minecraft server accessible to web clients (without our proxies), you can use [mwc-proxy](https://github.com/zardoy/mwc-proxy) - a lightweight JS WebSocket proxy that runs on the same server as your Minecraft server, allowing players to connect directly via `wss://play.example.com`. `?client_mcraft` is added to the URL, so the proxy will know that it's this client.
Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser.
```mermaid

View file

@ -10,10 +10,6 @@
{
"ip": "wss://play.mcraft.fun"
},
{
"ip": "wss://play.webmc.fun",
"name": "WebMC"
},
{
"ip": "wss://ws.fuchsmc.net"
},

View file

@ -87,7 +87,7 @@
"jszip": "^3.10.1",
"lodash-es": "^4.17.21",
"mcraft-fun-mineflayer": "^0.1.23",
"minecraft-data": "3.98.0",
"minecraft-data": "3.92.0",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
"mojangson": "^2.0.4",
@ -205,7 +205,7 @@
"diamond-square": "github:zardoy/diamond-square",
"prismarine-block": "github:zardoy/prismarine-block#next-era",
"prismarine-world": "github:zardoy/prismarine-world#next-era",
"minecraft-data": "3.98.0",
"minecraft-data": "3.92.0",
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"prismarine-physics": "github:zardoy/prismarine-physics",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",

110
pnpm-lock.yaml generated
View file

@ -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.98.0
minecraft-data: 3.92.0
prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything
prismarine-physics: github:zardoy/prismarine-physics
minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master
@ -140,13 +140,13 @@ importers:
version: 4.17.21
mcraft-fun-mineflayer:
specifier: ^0.1.23
version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13))
version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13))
minecraft-data:
specifier: 3.98.0
version: 3.98.0
specifier: 3.92.0
version: 3.92.0
minecraft-protocol:
specifier: github:PrismarineJS/node-minecraft-protocol#master
version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
mineflayer-item-map-downloader:
specifier: github:zardoy/mineflayer-item-map-downloader
version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13)
@ -170,7 +170,7 @@ importers:
version: 6.1.1
prismarine-provider-anvil:
specifier: github:zardoy/prismarine-provider-anvil#everything
version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0)
version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
prosemirror-example-setup:
specifier: ^1.2.2
version: 1.2.3
@ -345,7 +345,7 @@ importers:
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1)
mineflayer:
specifier: github:zardoy/mineflayer#gen-the-master
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)
mineflayer-mouse:
specifier: ^0.1.21
version: 0.1.21
@ -436,7 +436,7 @@ importers:
version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-chunk:
specifier: github:zardoy/prismarine-chunk#master
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-schematic:
specifier: ^1.2.0
version: 1.2.3
@ -6448,7 +6448,7 @@ packages:
resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
minecraft-data: 3.98.0
minecraft-data: 3.92.0
mcraft-fun-mineflayer@0.1.23:
resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==}
@ -6658,8 +6658,8 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
minecraft-data@3.98.0:
resolution: {integrity: sha512-JAPqJ/TZoxMUlAPPdWUh1v5wdqvYGFSZ4rW9bUtmaKBkGpomDSjw4V02ocBqbxKJvcTtmc5nM/LfN9/0DDqHrQ==}
minecraft-data@3.92.0:
resolution: {integrity: sha512-CGfO50svzm+pSRa4Mbq4owsmRKbPCNkSZ3MCOyH+epC7yNjh+PUhPQFHWq72O51qsY7pAB5qM/bJn1ncwG1J5g==}
minecraft-folder-path@1.2.0:
resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==}
@ -6668,9 +6668,9 @@ packages:
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41}
version: 1.0.1
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9:
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9}
version: 1.62.0
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074:
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074}
version: 1.61.0
engines: {node: '>=22'}
minecraft-wrap@1.6.0:
@ -6688,8 +6688,8 @@ packages:
resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659:
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659}
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8:
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8}
version: 8.0.0
engines: {node: '>=22'}
@ -7387,7 +7387,7 @@ packages:
prismarine-biome@1.3.0:
resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==}
peerDependencies:
minecraft-data: 3.98.0
minecraft-data: 3.92.0
prismarine-registry: ^1.1.0
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
@ -11343,8 +11343,8 @@ snapshots:
'@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)':
dependencies:
'@nxg-org/mineflayer-util-plugin': 1.8.4
minecraft-data: 3.98.0
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
minecraft-data: 3.92.0
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-item: 1.17.0
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
@ -13104,18 +13104,18 @@ snapshots:
exit-hook: 2.2.1
flatmap: 0.0.3
long: 5.3.1
mc-bridge: 0.1.3(minecraft-data@3.98.0)
minecraft-data: 3.98.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
mc-bridge: 0.1.3(minecraft-data@3.92.0)
minecraft-data: 3.92.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
mkdirp: 2.1.6
node-gzip: 1.1.2
node-rsa: 1.1.1
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-entity: 2.5.0
prismarine-item: 1.17.0
prismarine-nbt: 2.7.0
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0)
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
prismarine-windows: 2.9.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
rambda: 9.4.2
@ -13142,16 +13142,16 @@ snapshots:
exit-hook: 2.2.1
flatmap: 0.0.3
long: 5.3.1
minecraft-data: 3.98.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
minecraft-data: 3.92.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
mkdirp: 2.1.6
node-gzip: 1.1.2
node-rsa: 1.1.1
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-entity: 2.5.0
prismarine-item: 1.17.0
prismarine-nbt: 2.7.0
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0)
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
prismarine-windows: 2.9.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
rambda: 9.4.2
@ -14542,8 +14542,8 @@ snapshots:
diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71:
dependencies:
minecraft-data: 3.98.0
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
minecraft-data: 3.92.0
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-registry: 1.11.0
random-seed: 0.3.0
vec3: 0.1.10
@ -16986,16 +16986,16 @@ snapshots:
maxrects-packer: '@zardoy/maxrects-packer@2.7.4'
zod: 3.24.2
mc-bridge@0.1.3(minecraft-data@3.98.0):
mc-bridge@0.1.3(minecraft-data@3.92.0):
dependencies:
minecraft-data: 3.98.0
minecraft-data: 3.92.0
mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)):
mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)):
dependencies:
'@zardoy/flying-squid': 0.0.49(encoding@0.1.13)
exit-hook: 2.2.1
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)
prismarine-item: 1.17.0
ws: 8.18.1
transitivePeerDependencies:
@ -17302,7 +17302,7 @@ snapshots:
min-indent@1.0.1: {}
minecraft-data@3.98.0: {}
minecraft-data@3.92.0: {}
minecraft-folder-path@1.2.0: {}
@ -17313,7 +17313,7 @@ snapshots:
- '@types/react'
- react
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13):
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13):
dependencies:
'@types/node-rsa': 1.1.4
'@types/readable-stream': 4.0.18
@ -17322,7 +17322,7 @@ snapshots:
debug: 4.4.0(supports-color@8.1.1)
endian-toggle: 0.0.0
lodash.merge: 4.6.2
minecraft-data: 3.98.0
minecraft-data: 3.92.0
minecraft-folder-path: 1.2.0
node-fetch: 2.7.0(encoding@0.1.13)
node-rsa: 0.4.2
@ -17365,7 +17365,7 @@ snapshots:
mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13):
dependencies:
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13)
sharp: 0.30.7
transitivePeerDependencies:
- encoding
@ -17380,15 +17380,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13):
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/86e65631e79c490021afc63c80091a7bb6019fa8(encoding@0.1.13):
dependencies:
'@nxg-org/mineflayer-physics-util': 1.8.10
minecraft-data: 3.98.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0)
minecraft-data: 3.92.0
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/c561917bf7e7966911321512c2a6895a3f9da074(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-chat: 1.11.0
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-entity: 2.5.0
prismarine-item: 1.17.0
prismarine-nbt: 2.7.0
@ -18174,15 +18174,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
prismarine-biome@1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0):
prismarine-biome@1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0):
dependencies:
minecraft-data: 3.98.0
minecraft-data: 3.92.0
prismarine-registry: 1.11.0
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
dependencies:
minecraft-data: 3.98.0
prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0)
minecraft-data: 3.92.0
prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
prismarine-chat: 1.11.0
prismarine-item: 1.17.0
prismarine-nbt: 2.7.0
@ -18194,9 +18194,9 @@ snapshots:
prismarine-nbt: 2.7.0
prismarine-registry: 1.11.0
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0):
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0):
dependencies:
prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0)
prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-nbt: 2.7.0
prismarine-registry: 1.11.0
@ -18225,14 +18225,14 @@ snapshots:
prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b:
dependencies:
minecraft-data: 3.98.0
minecraft-data: 3.92.0
prismarine-nbt: 2.7.0
vec3: 0.1.10
prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0):
prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0):
dependencies:
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-nbt: 2.7.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
uint4: 0.1.2
@ -18254,13 +18254,13 @@ snapshots:
prismarine-registry@1.11.0:
dependencies:
minecraft-data: 3.98.0
minecraft-data: 3.92.0
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-nbt: 2.7.0
prismarine-schematic@1.2.3:
dependencies:
minecraft-data: 3.98.0
minecraft-data: 3.92.0
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-nbt: 2.7.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c

View file

@ -1,55 +0,0 @@
import { PlayerObject, PlayerAnimation } from 'skinview3d'
import * as THREE from 'three'
import { WalkingGeneralSwing } from '../three/entity/animations'
import { loadSkinImage, stevePngUrl } from './utils/skins'
export type PlayerObjectType = PlayerObject & {
animation?: PlayerAnimation
realPlayerUuid: string
realUsername: string
}
export function createPlayerObject (options: {
username?: string
uuid?: string
scale?: number
}): {
playerObject: PlayerObjectType
wrapper: THREE.Group
} {
const wrapper = new THREE.Group()
const playerObject = new PlayerObject() as PlayerObjectType
playerObject.realPlayerUuid = options.uuid ?? ''
playerObject.realUsername = options.username ?? ''
playerObject.position.set(0, 16, 0)
// fix issues with starfield
playerObject.traverse((obj) => {
if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshStandardMaterial) {
obj.material.transparent = true
}
})
wrapper.add(playerObject as any)
const scale = options.scale ?? (1 / 16)
wrapper.scale.set(scale, scale, scale)
wrapper.rotation.set(0, Math.PI, 0)
// Set up animation
playerObject.animation = new WalkingGeneralSwing()
;(playerObject.animation as WalkingGeneralSwing).isMoving = false
playerObject.animation.update(playerObject, 0)
return { playerObject, wrapper }
}
export const applySkinToPlayerObject = async (playerObject: PlayerObjectType, skinUrl: string) => {
return loadSkinImage(skinUrl || stevePngUrl).then(({ canvas }) => {
const skinTexture = new THREE.CanvasTexture(canvas)
skinTexture.magFilter = THREE.NearestFilter
skinTexture.minFilter = THREE.NearestFilter
skinTexture.needsUpdate = true
playerObject.skin.map = skinTexture as any
}).catch(console.error)
}

View file

@ -47,7 +47,6 @@ export const defaultWorldRendererConfig = {
smoothLighting: true,
enableLighting: true,
starfield: true,
defaultSkybox: true,
renderEntities: true,
extraBlockRenderers: true,
foreground: true,

View file

@ -80,12 +80,8 @@ export class CameraShake {
camera.setRotationFromQuaternion(yawQuat)
} else {
// For regular camera, apply all rotations
// Add tiny offsets to prevent z-fighting at ideal angles (90, 180, 270 degrees)
const pitchOffset = this.addAntiZfightingOffset(this.basePitch)
const yawOffset = this.addAntiZfightingOffset(this.baseYaw)
const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset)
const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset)
const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.basePitch)
const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw)
const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle))
// Combine rotations in the correct order: pitch -> yaw -> roll
const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat)
@ -100,21 +96,4 @@ export class CameraShake {
private easeInOut (t: number): number {
return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2
}
private addAntiZfightingOffset (angle: number): number {
const offset = 0.001 // Very small offset in radians (about 0.057 degrees)
// Check if the angle is close to ideal angles (0, π/2, π, 3π/2)
const normalizedAngle = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2)
const tolerance = 0.01 // Tolerance for considering an angle "ideal"
if (Math.abs(normalizedAngle) < tolerance ||
Math.abs(normalizedAngle - Math.PI / 2) < tolerance ||
Math.abs(normalizedAngle - Math.PI) < tolerance ||
Math.abs(normalizedAngle - 3 * Math.PI / 2) < tolerance) {
return angle + offset
}
return angle
}
}

View file

@ -20,7 +20,6 @@ import { ItemSpecificContextProperties } from '../lib/basePlayerState'
import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins'
import { renderComponent } from '../sign-renderer'
import { createCanvas } from '../lib/utils'
import { PlayerObjectType } from '../lib/createPlayerObject'
import { getBlockMeshFromModel } from './holdingBlock'
import { createItemMesh } from './itemMesh'
import * as Entity from './entity/EntityMesh'
@ -34,6 +33,12 @@ export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl)
export const TWEEN_DURATION = 120
type PlayerObjectType = PlayerObject & {
animation?: PlayerAnimation
realPlayerUuid: string
realUsername: string
}
function convert2sComplementToHex (complement: number) {
if (complement < 0) {
complement = (0xFF_FF_FF_FF + complement + 1) >>> 0

View file

@ -1,5 +1,4 @@
import * as THREE from 'three'
import { DebugGui } from '../lib/DebugGui'
export const DEFAULT_TEMPERATURE = 0.75
@ -18,33 +17,11 @@ export class SkyboxRenderer {
private waterBreathing = false
private fogBrightness = 0
private prevFogBrightness = 0
private readonly fogOrangeness = 0 // Debug property to control sky color orangeness
private readonly distanceFactor = 2.7
private readonly brightnessAtPosition = 1
debugGui: DebugGui
constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) {
this.debugGui = new DebugGui('skybox_renderer', this, [
'temperature',
'worldTime',
'inWater',
'waterBreathing',
'fogOrangeness',
'brightnessAtPosition',
'distanceFactor'
], {
brightnessAtPosition: { min: 0, max: 1, step: 0.01 },
temperature: { min: 0, max: 1, step: 0.01 },
worldTime: { min: 0, max: 24_000, step: 1 },
fogOrangeness: { min: -1, max: 1, step: 0.01 },
distanceFactor: { min: 0, max: 5, step: 0.01 },
})
constructor (private readonly scene: THREE.Scene, public initialImage: string | null) {
if (!initialImage) {
this.createGradientSky()
}
// this.debugGui.activate()
}
async init () {
@ -99,12 +76,14 @@ export class SkyboxRenderer {
}
}
update (cameraPosition: THREE.Vector3, newViewDistance: number) {
update (cameraPosition: THREE.Vector3, newViewDistance: number, partialTicks = 0) {
if (newViewDistance !== this.viewDistance) {
this.viewDistance = newViewDistance
this.updateSkyColors()
}
// Update partial ticks for interpolation
this.partialTicks = partialTicks
if (this.mesh) {
// Update skybox position
this.mesh.position.copy(cameraPosition)
@ -112,13 +91,12 @@ export class SkyboxRenderer {
// Update gradient sky position
this.skyMesh.position.copy(cameraPosition)
this.voidMesh?.position.copy(cameraPosition)
this.updateSkyColors() // Update colors based on time of day
this.updateSkyColors()
}
}
// Update world time
updateTime (timeOfDay: number, partialTicks = 0) {
if (this.debugGui.visible) return
this.worldTime = timeOfDay
this.partialTicks = partialTicks
this.updateSkyColors()
@ -132,26 +110,17 @@ export class SkyboxRenderer {
// Update temperature (for biome support)
updateTemperature (temperature: number) {
if (this.debugGui.visible) return
this.temperature = temperature
this.updateSkyColors()
}
// Update water state
updateWaterState (inWater: boolean, waterBreathing: boolean) {
if (this.debugGui.visible) return
this.inWater = inWater
this.waterBreathing = waterBreathing
this.updateSkyColors()
}
// Update default skybox setting
updateDefaultSkybox (defaultSkybox: boolean) {
if (this.debugGui.visible) return
this.defaultSkybox = defaultSkybox
this.updateSkyColors()
}
private createGradientSky () {
const size = 64
const scale = 256 / size + 2
@ -189,8 +158,8 @@ export class SkyboxRenderer {
this.updateSkyColors()
}
private getFogColor (partialTicks = 0): THREE.Vector3 {
const angle = this.getCelestialAngle(partialTicks)
private getFogColor (): THREE.Vector3 {
const angle = this.getCelestialAngle()
let rotation = Math.cos(angle * Math.PI * 2) * 2 + 0.5
rotation = Math.max(0, Math.min(1, rotation))
@ -205,8 +174,8 @@ export class SkyboxRenderer {
return new THREE.Vector3(x, y, z)
}
private getSkyColor (x = 0, z = 0, partialTicks = 0): THREE.Vector3 {
const angle = this.getCelestialAngle(partialTicks)
private getSkyColor (x = 0, z = 0): THREE.Vector3 {
const angle = this.getCelestialAngle()
let brightness = Math.cos(angle * 3.141_593 * 2) * 2 + 0.5
if (brightness < 0) brightness = 0
@ -243,8 +212,8 @@ export class SkyboxRenderer {
return angle
}
private getCelestialAngle (partialTicks: number): number {
return this.calculateCelestialAngle(this.worldTime, partialTicks)
private getCelestialAngle (): number {
return this.calculateCelestialAngle(this.worldTime, this.partialTicks)
}
private getTemperature (x: number, z: number): number {
@ -256,15 +225,8 @@ export class SkyboxRenderer {
if (temperature < -1) temperature = -1
if (temperature > 1) temperature = 1
// Apply debug fog orangeness to hue - positive values make it more orange, negative make it less orange
const baseHue = 0.622_222_2 - temperature * 0.05
// Orange is around hue 0.08-0.15, so we need to shift from blue-purple (0.62) toward orange
// Use a more dramatic shift and also increase saturation for more noticeable effect
const orangeHue = 0.12 // Orange hue value
const hue = this.fogOrangeness > 0
? baseHue + (orangeHue - baseHue) * this.fogOrangeness * 0.8 // Blend toward orange
: baseHue + this.fogOrangeness * 0.1 // Subtle shift for negative values
const saturation = 0.5 + temperature * 0.1 + Math.abs(this.fogOrangeness) * 0.3 // Increase saturation with orangeness
const hue = 0.622_222_2 - temperature * 0.05
const saturation = 0.5 + temperature * 0.1
const brightness = 1
return this.hsbToRgb(hue, saturation, brightness)
@ -319,27 +281,11 @@ export class SkyboxRenderer {
private updateSkyColors () {
if (!this.skyMesh || !this.voidMesh) return
// If default skybox is disabled, hide the skybox meshes
if (!this.defaultSkybox) {
this.skyMesh.visible = false
this.voidMesh.visible = false
if (this.mesh) {
this.mesh.visible = false
}
return
}
// Show skybox meshes when default skybox is enabled
this.skyMesh.visible = true
this.voidMesh.visible = true
if (this.mesh) {
this.mesh.visible = true
}
// Update fog brightness with smooth transition
this.prevFogBrightness = this.fogBrightness
const renderDistance = this.viewDistance / 32
const targetBrightness = this.brightnessAtPosition * (1 - renderDistance) + renderDistance
const brightnessAtPosition = 1 // Could be affected by light level in future
const targetBrightness = brightnessAtPosition * (1 - renderDistance) + renderDistance
this.fogBrightness += (targetBrightness - this.fogBrightness) * 0.1
// Handle water fog
@ -358,9 +304,9 @@ export class SkyboxRenderer {
const viewDistance = this.viewDistance * 16
const viewFactor = 1 - (0.25 + 0.75 * this.viewDistance / 32) ** 0.25
const angle = this.getCelestialAngle(this.partialTicks)
const skyColor = this.getSkyColor(0, 0, this.partialTicks)
const fogColor = this.getFogColor(this.partialTicks)
const angle = this.getCelestialAngle()
const skyColor = this.getSkyColor(0, 0)
const fogColor = this.getFogColor()
const brightness = Math.cos(angle * Math.PI * 2) * 2 + 0.5
const clampedBrightness = Math.max(0, Math.min(1, brightness))
@ -373,7 +319,7 @@ export class SkyboxRenderer {
const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness
this.scene.background = new THREE.Color(red, green, blue)
this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * this.distanceFactor)
this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * 2.5)
;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z))
;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(

View file

@ -16,7 +16,7 @@ export const WAYPOINT_CONFIG = {
CANVAS_SCALE: 2,
ARROW: {
enabledDefault: false,
pixelSize: 50,
pixelSize: 30,
paddingPx: 50,
},
}
@ -50,7 +50,6 @@ export function createWaypointSprite (options: {
depthTest?: boolean,
// Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this)
labelYOffset?: number,
metadata?: any,
}): WaypointSprite {
const color = options.color ?? 0xFF_00_00
const depthTest = options.depthTest ?? false
@ -132,22 +131,16 @@ export function createWaypointSprite (options: {
canvas.height = size
const ctx = canvas.getContext('2d')!
ctx.clearRect(0, 0, size, size)
// Draw arrow shape
ctx.beginPath()
ctx.moveTo(size * 0.15, size * 0.5)
ctx.lineTo(size * 0.85, size * 0.5)
ctx.lineTo(size * 0.5, size * 0.15)
ctx.moveTo(size * 0.2, size * 0.5)
ctx.lineTo(size * 0.8, size * 0.5)
ctx.lineTo(size * 0.5, size * 0.2)
ctx.closePath()
// Use waypoint color for arrow
const colorHex = `#${color.toString(16).padStart(6, '0')}`
ctx.lineWidth = 6
ctx.lineWidth = 4
ctx.strokeStyle = 'black'
ctx.stroke()
ctx.fillStyle = colorHex
ctx.fillStyle = 'white'
ctx.fill()
const texture = new THREE.CanvasTexture(canvas)
const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })
arrowSprite = new THREE.Sprite(material)
@ -176,9 +169,6 @@ export function createWaypointSprite (options: {
ensureArrow()
if (!arrowSprite) return true
// Check if onlyLeftRight is enabled in metadata
const onlyLeftRight = options.metadata?.onlyLeftRight === true
// Build camera basis using camera.up to respect custom orientations
const forward = new THREE.Vector3()
camera.getWorldDirection(forward) // camera look direction
@ -223,20 +213,6 @@ export function createWaypointSprite (options: {
}
}
// Apply onlyLeftRight logic - restrict arrows to left/right edges only
if (onlyLeftRight) {
// Force the arrow to appear only on left or right edges
if (Math.abs(rx) > Math.abs(ry)) {
// Horizontal direction is dominant, keep it
ry = 0
} else {
// Vertical direction is dominant, but we want only left/right
// So choose left or right based on the sign of rx
rx = rx >= 0 ? 1 : -1
ry = 0
}
}
// Place on the rectangle border [-1,1]x[-1,1]
const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1
let ndcX = rx / s

View file

@ -17,7 +17,6 @@ interface WaypointOptions {
color?: number
label?: string
minDistance?: number
metadata?: any
}
export class WaypointsRenderer {
@ -72,14 +71,13 @@ export class WaypointsRenderer {
this.removeWaypoint(id)
const color = options.color ?? 0xFF_00_00
const { label, metadata } = options
const { label } = options
const minDistance = options.minDistance ?? 0
const sprite = createWaypointSprite({
position: new THREE.Vector3(x, y, z),
color,
label: (label || id),
metadata,
})
sprite.enableOffscreenArrow(true)
sprite.setArrowParent(this.waypointScene)

View file

@ -28,7 +28,7 @@ export class CursorBlock {
}
cursorLineMaterial: LineMaterial
interactionLines: null | { blockPos: Vec3, mesh: THREE.Group, shapePositions: BlocksShapes | undefined } = null
interactionLines: null | { blockPos: Vec3, mesh: THREE.Group } = null
prevColor: string | undefined
blockBreakMesh: THREE.Mesh
breakTextures: THREE.Texture[] = []
@ -62,13 +62,6 @@ export class CursorBlock {
this.worldRenderer.onReactivePlayerStateUpdated('gameMode', () => {
this.updateLineMaterial()
})
// todo figure out why otherwise fog from skybox breaks it
setTimeout(() => {
this.updateLineMaterial()
if (this.interactionLines) {
this.setHighlightCursorBlock(this.interactionLines.blockPos, this.interactionLines.shapePositions, true)
}
})
}
// Update functions
@ -76,9 +69,6 @@ export class CursorBlock {
const inCreative = this.worldRenderer.playerStateReactive.gameMode === 'creative'
const pixelRatio = this.worldRenderer.renderer.getPixelRatio()
if (this.cursorLineMaterial) {
this.cursorLineMaterial.dispose()
}
this.cursorLineMaterial = new LineMaterial({
color: (() => {
switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) {
@ -125,8 +115,8 @@ export class CursorBlock {
}
}
setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes, force = false): void {
if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos) && !force) {
setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes): void {
if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) {
return
}
if (this.interactionLines !== null) {
@ -150,7 +140,7 @@ export class CursorBlock {
}
this.worldRenderer.scene.add(group)
group.visible = !this.cursorLinesHidden
this.interactionLines = { blockPos, mesh: group, shapePositions }
this.interactionLines = { blockPos, mesh: group }
}
render () {

View file

@ -98,7 +98,7 @@ export class WorldRendererThree extends WorldRendererCommon {
this.holdingBlockLeft = new HoldingBlock(this, true)
// Initialize skybox renderer
this.skyboxRenderer = new SkyboxRenderer(this.scene, this.worldRendererConfig.defaultSkybox, null)
this.skyboxRenderer = new SkyboxRenderer(this.scene, null)
void this.skyboxRenderer.init()
this.addDebugOverlay()
@ -206,9 +206,6 @@ export class WorldRendererThree extends WorldRendererCommon {
this.onReactiveConfigUpdated('showChunkBorders', (value) => {
this.updateShowChunksBorder(value)
})
this.onReactiveConfigUpdated('defaultSkybox', (value) => {
this.skyboxRenderer.updateDefaultSkybox(value)
})
}
changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) {

View file

@ -371,7 +371,6 @@ console.log('size', fs.lstatSync(filePath).size / 1000 / 1000, gzipSizeFromFileS
const { defaultVersion } = MCProtocol
const data = MinecraftData(defaultVersion)
console.log('defaultVersion', defaultVersion, !!data)
const initialMcData = {
[defaultVersion]: {
version: data.version,

View file

@ -35,7 +35,7 @@ export type AppConfig = {
// defaultVersion?: string
peerJsServer?: string
peerJsServerFallback?: string
promoteServers?: Array<{ ip, description, name?, version?, }>
promoteServers?: Array<{ ip, description, version? }>
mapsProvider?: string
appParams?: Record<string, any> // query string params

View file

@ -7,12 +7,7 @@ let audioContext: AudioContext
const sounds: Record<string, any> = {}
// Track currently playing sounds and their gain nodes
const activeSounds: Array<{
source: AudioBufferSourceNode;
gainNode: GainNode;
volumeMultiplier: number;
isMusic: boolean;
}> = []
const activeSounds: Array<{ source: AudioBufferSourceNode; gainNode: GainNode; volumeMultiplier: number }> = []
window.activeSounds = activeSounds
// load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded
@ -48,7 +43,7 @@ export async function loadSound (path: string, contents = path) {
}
}
export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false, isMusic = false) => {
export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false) => {
const soundBuffer = sounds[url]
if (!soundBuffer) {
const start = Date.now()
@ -56,11 +51,11 @@ export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = option
if (cancelled || Date.now() - start > loadTimeout) return
}
return playSound(url, soundVolume, loop, isMusic)
return playSound(url, soundVolume, loop)
}
export async function playSound (url, soundVolume = 1, loop = false, isMusic = false) {
const volume = soundVolume * (options.volume / 100) * (isMusic ? options.musicVolume / 100 : 1)
export async function playSound (url, soundVolume = 1, loop = false) {
const volume = soundVolume * (options.volume / 100)
if (!volume) return
@ -87,7 +82,7 @@ export async function playSound (url, soundVolume = 1, loop = false, isMusic = f
source.start(0)
// Add to active sounds
activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume, isMusic })
activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume })
const callbacks = [] as Array<() => void>
source.onended = () => {
@ -115,7 +110,6 @@ export async function playSound (url, soundVolume = 1, loop = false, isMusic = f
console.warn('Failed to stop sound:', err)
}
},
gainNode,
}
}
@ -143,11 +137,11 @@ export function stopSound (url: string) {
}
}
export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusicVolume: number) {
export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) {
const normalizedVolume = newVolume / 100
for (const { gainNode, volumeMultiplier, isMusic } of activeSounds) {
for (const { gainNode, volumeMultiplier } of activeSounds) {
try {
gainNode.gain.value = normalizedVolume * volumeMultiplier * (isMusic ? newMusicVolume / 100 : 1)
gainNode.gain.value = normalizedVolume * volumeMultiplier
} catch (err) {
console.warn('Failed to change sound volume:', err)
}
@ -155,9 +149,5 @@ export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusi
}
subscribeKey(options, 'volume', () => {
changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume)
})
subscribeKey(options, 'musicVolume', () => {
changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume)
changeVolumeOfCurrentlyPlayingSounds(options.volume)
})

View file

@ -82,30 +82,15 @@ const registerWaypointChannels = () => {
{
name: 'color',
type: 'i32'
},
{
name: 'metadataJson',
type: ['pstring', { countType: 'i16' }]
}
]
]
registerChannel('minecraft-web-client:waypoint-add', packetStructure, (data) => {
// Parse metadata if provided
let metadata: any = {}
if (data.metadataJson && data.metadataJson.trim() !== '') {
try {
metadata = JSON.parse(data.metadataJson)
} catch (error) {
console.warn('Failed to parse waypoint metadataJson:', error)
}
}
getThreeJsRendererMethods()?.addWaypoint(data.id, data.x, data.y, data.z, {
minDistance: data.minDistance,
label: data.label || undefined,
color: data.color || undefined,
metadata
color: data.color || undefined
})
})

View file

@ -16,8 +16,7 @@ export const defaultOptions = {
chatOpacityOpened: 100,
messagesLimit: 200,
volume: 50,
enableMusic: true,
musicVolume: 50,
enableMusic: false,
// fov: 70,
fov: 75,
defaultPerspective: 'first_person' as 'first_person' | 'third_person_back' | 'third_person_front',
@ -42,7 +41,6 @@ export const defaultOptions = {
renderEars: true,
lowMemoryMode: false,
starfieldRendering: true,
defaultSkybox: true,
enabledResourcepack: null as string | null,
useVersionsTextures: 'latest',
serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never',
@ -85,7 +83,6 @@ export const defaultOptions = {
localServerOptions: {
gameMode: 1
} as any,
saveLoginPassword: 'prompt' as 'prompt' | 'never' | 'always',
preferLoadReadonly: false,
experimentalClientSelfReload: false,
remoteSoundsSupport: false,

View file

@ -5,17 +5,6 @@ import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree'
import { enable, disable, enabled } from 'debug'
import { Vec3 } from 'vec3'
customEvents.on('mineflayerBotCreated', () => {
window.debugServerPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toClient.types).map(name => {
name = name.replace('packet_', '')
return [name, name]
}))
window.debugClientPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toServer.types).map(name => {
name = name.replace('packet_', '')
return [name, name]
}))
})
window.Vec3 = Vec3
window.cursorBlockRel = (x = 0, y = 0, z = 0) => {
const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z)

View file

@ -246,29 +246,22 @@ customEvents.on('gameLoaded', () => {
}
}
// even if not found, still record to cache
void getThreeJsRendererMethods()!.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl)
void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl)
} catch (err) {
reportError(new Error('Error applying skin texture:', { cause: err }))
console.error('Error decoding player texture:', err)
}
}
bot.on('playerJoined', updateSkin)
bot.on('playerUpdated', updateSkin)
for (const entity of Object.values(bot.players)) {
updateSkin(entity)
}
const teamUpdated = (team: Team) => {
bot.on('teamUpdated', (team: Team) => {
for (const entity of Object.values(bot.entities)) {
if (entity.type === 'player' && entity.username && team.members.includes(entity.username) || entity.uuid && team.members.includes(entity.uuid)) {
bot.emit('entityUpdate', entity)
}
}
}
bot.on('teamUpdated', teamUpdated)
for (const team of Object.values(bot.teams)) {
teamUpdated(team)
}
})
const updateEntityNameTags = (team: Team) => {
for (const entity of Object.values(bot.entities)) {

View file

@ -12,7 +12,6 @@ import PrismarineChatLoader from 'prismarine-chat'
import * as nbt from 'prismarine-nbt'
import { BlockModel } from 'mc-assets'
import { renderSlot } from 'renderer/viewer/three/renderSlot'
import { loadSkinFromUsername } from 'renderer/viewer/lib/utils/skins'
import Generic95 from '../assets/generic_95.png'
import { appReplacableResources } from './generated/resources'
import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState'
@ -24,7 +23,6 @@ import { getItemDescription } from './itemsDescriptions'
import { MessageFormatPart } from './chatUtils'
import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items'
import { playerState } from './mineflayer/playerState'
import { modelViewerState } from './react/OverlayModelViewer'
const loadedImagesCache = new Map<string, HTMLImageElement | ImageBitmap>()
const cleanLoadedImagesCache = () => {
@ -42,34 +40,6 @@ export const jeiCustomCategories = proxy({
value: [] as Array<{ id: string, categoryTitle: string, items: any[] }>
})
let remotePlayerSkin: string | undefined | Promise<string>
export const showInventoryPlayer = () => {
modelViewerState.model = {
positioning: {
windowWidth: 176,
windowHeight: 166,
x: 25,
y: 8,
width: 50,
height: 70,
scaled: true,
onlyInitialScale: true,
followCursor: true,
},
// models: ['https://bucket.mcraft.fun/sitarbuckss.glb'],
// debug: true,
steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''),
}
if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) {
remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => {
setTimeout(() => { showInventoryPlayer() }, 0) // todo patch instead and make reactive
remotePlayerSkin = a ?? ''
return remotePlayerSkin
})
}
}
export const onGameLoad = () => {
version = bot.version
@ -422,12 +392,7 @@ const openWindow = (type: string | undefined, title: string | any = undefined) =
miscUiState.displaySearchInput = false
destroyFn()
skipClosePacketSending = false
modelViewerState.model = undefined
})
if (type === undefined) {
showInventoryPlayer()
}
cleanLoadedImagesCache()
const inv = openItemsCanvas(type)
inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch
@ -470,7 +435,6 @@ const openWindow = (type: string | undefined, title: string | any = undefined) =
const isRightClick = type === 'rightclick'
const isLeftClick = type === 'leftclick'
if (isLeftClick || isRightClick) {
modelViewerState.model = undefined
inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item)
}
} else {
@ -502,7 +466,6 @@ const openWindow = (type: string | undefined, title: string | any = undefined) =
if (freeSlot === null) return
void bot.creative.setInventorySlot(freeSlot, item)
} else {
modelViewerState.model = undefined
inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0])
}
}

View file

@ -15,12 +15,9 @@ class CustomDuplex extends Duplex {
}
export const getWebsocketStream = async (host: string) => {
const baseProtocol = host.startsWith('ws://') ? 'ws' : 'wss'
const baseProtocol = location.protocol === 'https:' ? 'wss' : host.startsWith('ws://') ? 'ws' : 'wss'
const hostClean = host.replace('ws://', '').replace('wss://', '')
const hostURL = new URL(`${baseProtocol}://${hostClean}`)
const hostParams = hostURL.searchParams
hostParams.append('client_mcraft', '')
const ws = new WebSocket(`${baseProtocol}://${hostURL.host}${hostURL.pathname}?${hostParams.toString()}`)
const ws = new WebSocket(`${baseProtocol}://${hostClean}`)
const clientDuplex = new CustomDuplex(undefined, data => {
ws.send(data)
})

View file

@ -480,24 +480,6 @@ export const guiOptionsScheme: {
],
sound: [
{ volume: {} },
{
custom () {
return <OptionSlider
valueOverride={options.enableMusic ? undefined : 0}
onChange={(value) => {
options.musicVolume = value
}}
item={{
type: 'slider',
id: 'musicVolume',
text: 'Music Volume',
min: 0,
max: 100,
unit: '%',
}}
/>
},
},
{
custom () {
return <Button label='Sound Muffler' onClick={() => showModal({ reactType: 'sound-muffler' })} inScreen />
@ -568,16 +550,6 @@ export const guiOptionsScheme: {
return <Category>Server Connection</Category>
},
},
{
saveLoginPassword: {
tooltip: 'Controls whether to save login passwords for servers in this browser memory.',
values: [
'prompt',
'always',
'never'
]
},
},
{
custom () {
const { serversAutoVersionSelect } = useSnapshot(options)

View file

@ -59,7 +59,6 @@ export const startLocalReplayServer = (contents: string) => {
const server = createServer({
Server: LocalServer as any,
version: header.minecraftVersion,
keepAlive: false,
'online-mode': false
})

View file

@ -117,7 +117,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
}
const displayConnectButton = qsParamIp
const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg', 'wss://play.webmc.fun']
const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg']
// pick random example
const example = serverExamples[Math.floor(Math.random() * serverExamples.length)]

View file

@ -125,9 +125,7 @@ export default ({
const chatInput = useRef<HTMLInputElement>(null!)
const chatMessages = useRef<HTMLDivElement>(null)
const chatHistoryPos = useRef(sendHistoryRef.current.length)
const commandHistoryPos = useRef(0)
const inputCurrentlyEnteredValue = useRef('')
const commandHistoryRef = useRef(sendHistoryRef.current.filter((msg: string) => msg.startsWith('/')))
const { scrollToBottom, isAtBottom, wasAtBottom, currentlyAtBottom } = useScrollBehavior(chatMessages, { messages, opened })
const [rightNowAtBottom, setRightNowAtBottom] = useState(false)
@ -144,9 +142,6 @@ export default ({
sendHistoryRef.current = newHistory
window.sessionStorage.chatHistory = JSON.stringify(newHistory)
chatHistoryPos.current = newHistory.length
// Update command history (only messages starting with /)
commandHistoryRef.current = newHistory.filter((msg: string) => msg.startsWith('/'))
commandHistoryPos.current = commandHistoryRef.current.length
}
const acceptComplete = (item: string) => {
@ -185,21 +180,6 @@ export default ({
updateInputValue(sendHistoryRef.current[chatHistoryPos.current] || inputCurrentlyEnteredValue.current || '')
}
const handleCommandArrowUp = () => {
if (commandHistoryPos.current === 0 || commandHistoryRef.current.length === 0) return
if (commandHistoryPos.current === commandHistoryRef.current.length) { // started navigating command history
inputCurrentlyEnteredValue.current = chatInput.current.value
}
commandHistoryPos.current--
updateInputValue(commandHistoryRef.current[commandHistoryPos.current] || '')
}
const handleCommandArrowDown = () => {
if (commandHistoryPos.current === commandHistoryRef.current.length) return
commandHistoryPos.current++
updateInputValue(commandHistoryRef.current[commandHistoryPos.current] || inputCurrentlyEnteredValue.current || '')
}
const auxInputFocus = (direction: 'up' | 'down') => {
chatInput.current.focus()
if (direction === 'up') {
@ -223,7 +203,6 @@ export default ({
updateInputValue(chatInputValueGlobal.value)
chatInputValueGlobal.value = ''
chatHistoryPos.current = sendHistoryRef.current.length
commandHistoryPos.current = commandHistoryRef.current.length
if (!usingTouch) {
chatInput.current.focus()
}
@ -545,19 +524,9 @@ export default ({
onBlur={() => setIsInputFocused(false)}
onKeyDown={(e) => {
if (e.code === 'ArrowUp') {
if (e.altKey) {
handleCommandArrowUp()
e.preventDefault()
} else {
handleArrowUp()
}
handleArrowUp()
} else if (e.code === 'ArrowDown') {
if (e.altKey) {
handleCommandArrowDown()
e.preventDefault()
} else {
handleArrowDown()
}
handleArrowDown()
}
if (e.code === 'Tab') {
if (completionItemsSource.length) {

View file

@ -73,28 +73,16 @@ export default () => {
}
const builtinHandled = tryHandleBuiltinCommand(message)
if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register')) && options.saveLoginPassword !== 'never') {
const savePassword = () => {
let hadPassword = false
if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register'))) {
showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, () => {
updateLoadedServerData((server) => {
server.autoLogin ??= {}
const password = message.split(' ')[1]
hadPassword = !!server.autoLogin[bot.username]
server.autoLogin[bot.username] = password
return { ...server }
})
if (options.saveLoginPassword === 'always') {
const message = hadPassword ? 'Password updated in browser for auto-login' : 'Password saved in browser for auto-login'
showNotification(message, undefined, false, undefined)
} else {
hideNotification()
}
}
if (options.saveLoginPassword === 'prompt') {
showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, savePassword)
} else {
savePassword()
}
hideNotification()
})
notificationProxy.id = 'auto-login'
const listener = () => {
hideNotification()

View file

@ -161,15 +161,7 @@ export const OptionButton = ({ item, onClick, valueText, cacheKey }: {
/>
}
export const OptionSlider = ({
item,
onChange,
valueOverride
}: {
item: Extract<OptionMeta, { type: 'slider' }>
onChange?: (value: number) => void
valueOverride?: number
}) => {
export const OptionSlider = ({ item }: { item: Extract<OptionMeta, { type: 'slider' }> }) => {
const { disabledBecauseOfSetting } = useCommonComponentsProps(item)
const optionValue = useSnapshot(options)[item.id!]
@ -182,7 +174,7 @@ export const OptionSlider = ({
return (
<Slider
label={item.text!}
value={valueOverride ?? options[item.id!]}
value={options[item.id!]}
data-setting={item.id}
disabledReason={isLocked(item) ? 'qs' : disabledBecauseOfSetting ? `Disabled because ${item.disableIf![0]} is ${item.disableIf![1]}` : item.disabledReason}
min={item.min}
@ -192,7 +184,6 @@ export const OptionSlider = ({
updateOnDragEnd={item.delayApply}
updateValue={(value) => {
options[item.id!] = value
onChange?.(value)
}}
/>
)

View file

@ -1,554 +0,0 @@
import { proxy, useSnapshot, subscribe } from 'valtio'
import { useEffect, useMemo, useRef } from 'react'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { applySkinToPlayerObject, createPlayerObject, PlayerObjectType } from '../../renderer/viewer/lib/createPlayerObject'
import { currentScaling } from '../scaleInterface'
import { activeModalStack } from '../globalState'
THREE.ColorManagement.enabled = false
export const modelViewerState = proxy({
model: undefined as undefined | {
models?: string[] // Array of model URLs (URL itself is the cache key)
steveModelSkin?: string
debug?: boolean
// absolute positioning
positioning: {
windowWidth: number
windowHeight: number
x: number
y: number
width: number
height: number
scaled?: boolean
onlyInitialScale?: boolean
followCursor?: boolean
}
modelCustomization?: { [modelUrl: string]: { color?: string, opacity?: number, metalness?: number, roughness?: number } }
resetRotationOnReleae?: boolean
continiousRender?: boolean
alwaysRender?: boolean
}
})
globalThis.modelViewerState = modelViewerState
// Global debug function to get camera and model values
globalThis.getModelViewerValues = () => {
const scene = globalThis.sceneRef?.current
if (!scene) return null
const { camera, playerObject } = scene
if (!playerObject) return null
const wrapper = playerObject.parent
if (!wrapper) return null
const box = new THREE.Box3().setFromObject(wrapper)
const size = box.getSize(new THREE.Vector3())
const center = box.getCenter(new THREE.Vector3())
return {
camera: {
position: camera.position.clone(),
fov: camera.fov,
aspect: camera.aspect
},
model: {
position: wrapper.position.clone(),
rotation: wrapper.rotation.clone(),
scale: wrapper.scale.clone(),
size,
center
},
cursor: {
position: globalThis.cursorPosition || { x: 0, y: 0 },
normalized: globalThis.cursorPosition ? {
x: globalThis.cursorPosition.x * 2 - 1,
y: globalThis.cursorPosition.y * 2 - 1
} : { x: 0, y: 0 }
},
visibleArea: {
height: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z,
width: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z * camera.aspect
}
}
}
subscribe(activeModalStack, () => {
if (!modelViewerState.model || !modelViewerState.model?.alwaysRender) {
return
}
if (activeModalStack.length === 0) {
modelViewerState.model = undefined
}
})
export default () => {
const { model } = useSnapshot(modelViewerState)
const containerRef = useRef<HTMLDivElement>(null)
const sceneRef = useRef<{
scene: THREE.Scene
camera: THREE.PerspectiveCamera
renderer: THREE.WebGLRenderer
controls: OrbitControls
playerObject?: PlayerObjectType
dispose: () => void
}>()
const initialScale = useMemo(() => {
return currentScaling.scale
}, [])
globalThis.sceneRef = sceneRef
// Cursor following state
const cursorPosition = useRef({ x: 0, y: 0 })
const isFollowingCursor = useRef(false)
// Model management state
const loadedModels = useRef<Map<string, THREE.Object3D>>(new Map())
const modelLoaders = useRef<Map<string, GLTFLoader | OBJLoader>>(new Map())
// Model management functions
const loadModel = (modelUrl: string) => {
if (loadedModels.current.has(modelUrl)) return // Already loaded
const isGLTF = modelUrl.toLowerCase().endsWith('.gltf') || modelUrl.toLowerCase().endsWith('.glb')
const loader = isGLTF ? new GLTFLoader() : new OBJLoader()
modelLoaders.current.set(modelUrl, loader)
const onLoad = (object: THREE.Object3D) => {
// Apply customization if available and enable shadows
const customization = model?.modelCustomization?.[modelUrl]
object.traverse((child) => {
if (child instanceof THREE.Mesh) {
// Enable shadow casting and receiving for all meshes
child.castShadow = true
child.receiveShadow = true
if (child.material && customization) {
const material = child.material as THREE.MeshStandardMaterial
if (customization.color) {
material.color.setHex(parseInt(customization.color.replace('#', ''), 16))
}
if (customization.opacity !== undefined) {
material.opacity = customization.opacity
material.transparent = customization.opacity < 1
}
if (customization.metalness !== undefined) {
material.metalness = customization.metalness
}
if (customization.roughness !== undefined) {
material.roughness = customization.roughness
}
}
}
})
// Center and scale model
const box = new THREE.Box3().setFromObject(object)
const center = box.getCenter(new THREE.Vector3())
const size = box.getSize(new THREE.Vector3())
const maxDim = Math.max(size.x, size.y, size.z)
const scale = 2 / maxDim
object.scale.setScalar(scale)
object.position.sub(center.multiplyScalar(scale))
// Store the model using URL as key
loadedModels.current.set(modelUrl, object)
sceneRef.current?.scene.add(object)
// Trigger render
if (sceneRef.current) {
setTimeout(() => {
const render = () => sceneRef.current?.renderer.render(sceneRef.current.scene, sceneRef.current.camera)
render()
}, 0)
}
}
if (isGLTF) {
(loader as GLTFLoader).load(modelUrl, (gltf) => {
onLoad(gltf.scene)
})
} else {
(loader as OBJLoader).load(modelUrl, onLoad)
}
}
const removeModel = (modelUrl: string) => {
const model = loadedModels.current.get(modelUrl)
if (model) {
sceneRef.current?.scene.remove(model)
model.traverse((child) => {
if (child instanceof THREE.Mesh) {
if (child.material) {
if (Array.isArray(child.material)) {
for (const mat of child.material) {
mat.dispose()
}
} else {
child.material.dispose()
}
}
if (child.geometry) {
child.geometry.dispose()
}
}
})
loadedModels.current.delete(modelUrl)
}
modelLoaders.current.delete(modelUrl)
}
// Subscribe to model changes
useEffect(() => {
if (!modelViewerState.model?.models) return
const modelsChanged = () => {
const currentModels = modelViewerState.model?.models || []
const currentModelUrls = new Set(currentModels)
const loadedModelUrls = new Set(loadedModels.current.keys())
// Remove models that are no longer in the state
for (const modelUrl of loadedModelUrls) {
if (!currentModelUrls.has(modelUrl)) {
removeModel(modelUrl)
}
}
// Add new models
for (const modelUrl of currentModels) {
if (!loadedModelUrls.has(modelUrl)) {
loadModel(modelUrl)
}
}
}
const unsubscribe = subscribe(modelViewerState.model.models, modelsChanged)
let unmounted = false
setTimeout(() => {
if (unmounted) return
modelsChanged()
})
return () => {
unmounted = true
unsubscribe?.()
}
}, [model?.models])
useEffect(() => {
if (!model || !containerRef.current) return
// Setup scene
const scene = new THREE.Scene()
scene.background = null // Transparent background
// Setup camera with optimal settings for player model viewing
const camera = new THREE.PerspectiveCamera(
50, // Reduced FOV for better model viewing
model.positioning.width / model.positioning.height,
0.1,
1000
)
camera.position.set(0, 0, 3) // Position camera to view player model optimally
// Setup renderer with pixel density awareness
const renderer = new THREE.WebGLRenderer({ alpha: true })
let scale = window.devicePixelRatio || 1
if (modelViewerState.model?.positioning.scaled) {
scale *= currentScaling.scale
}
renderer.setPixelRatio(scale)
renderer.setSize(model.positioning.width, model.positioning.height)
// Enable shadow rendering for depth and realism
renderer.shadowMap.enabled = true
renderer.shadowMap.type = THREE.PCFSoftShadowMap // Soft shadows for better quality
renderer.shadowMap.autoUpdate = true
containerRef.current.appendChild(renderer.domElement)
// Setup controls
const controls = new OrbitControls(camera, renderer.domElement)
// controls.enableZoom = false
// controls.enablePan = false
controls.minPolarAngle = Math.PI / 2 // Lock vertical rotation
controls.maxPolarAngle = Math.PI / 2
controls.enableDamping = true
controls.dampingFactor = 0.05
// Add ambient light for overall illumination
const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 0.4) // Reduced intensity to allow shadows
scene.add(ambientLight)
// Add directional light for shadows and depth (similar to Minecraft inventory lighting)
const directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.6)
directionalLight.position.set(2, 2, 2) // Position light from top-right-front
directionalLight.target.position.set(0, 0, 0) // Point towards center of scene
// Configure shadow properties for optimal quality
directionalLight.castShadow = true
directionalLight.shadow.mapSize.width = 2048 // High resolution shadow map
directionalLight.shadow.mapSize.height = 2048
directionalLight.shadow.camera.near = 0.1
directionalLight.shadow.camera.far = 10
directionalLight.shadow.camera.left = -3
directionalLight.shadow.camera.right = 3
directionalLight.shadow.camera.top = 3
directionalLight.shadow.camera.bottom = -3
directionalLight.shadow.bias = -0.0001 // Reduce shadow acne
scene.add(directionalLight)
scene.add(directionalLight.target)
// Cursor following function
const updatePlayerLookAt = () => {
if (!isFollowingCursor.current || !sceneRef.current?.playerObject) return
const { playerObject } = sceneRef.current
const { x, y } = cursorPosition.current
// Convert 0-1 cursor position to normalized coordinates (-1 to 1)
const normalizedX = x * 2 - 1
const normalizedY = y * 2 - 1 // Inverted: top of screen = negative pitch, bottom = positive pitch
// Calculate head rotation based on cursor position
// Limit head movement to realistic angles
const maxHeadYaw = Math.PI / 3 // 60 degrees
const maxHeadPitch = Math.PI / 4 // 45 degrees
const headYaw = normalizedX * maxHeadYaw
const headPitch = normalizedY * maxHeadPitch
// Apply head rotation with smooth interpolation
const lerpFactor = 0.1 // Smooth interpolation factor
playerObject.skin.head.rotation.y = THREE.MathUtils.lerp(
playerObject.skin.head.rotation.y,
headYaw,
lerpFactor
)
playerObject.skin.head.rotation.x = THREE.MathUtils.lerp(
playerObject.skin.head.rotation.x,
headPitch,
lerpFactor
)
// Apply slight body rotation for more natural movement
const bodyYaw = headYaw * 0.3 // Body follows head but with less rotation
playerObject.rotation.y = THREE.MathUtils.lerp(
playerObject.rotation.y,
bodyYaw,
lerpFactor * 0.5 // Slower body movement
)
render()
}
// Render function
const render = () => {
renderer.render(scene, camera)
}
// Setup animation/render strategy
if (model.continiousRender) {
// Continuous animation loop
const animate = () => {
requestAnimationFrame(animate)
render()
}
animate()
} else {
// Render only on camera movement
controls.addEventListener('change', render)
// Initial render
render()
// Render after model loads
if (model.steveModelSkin !== undefined) {
// Create player model
const { playerObject, wrapper } = createPlayerObject({
scale: 1 // Start with base scale, will adjust below
})
// Enable shadows for player object
wrapper.traverse((child) => {
if (child instanceof THREE.Mesh) {
child.castShadow = true
child.receiveShadow = true
}
})
// Calculate proper scale and positioning for camera view
const box = new THREE.Box3().setFromObject(wrapper)
const size = box.getSize(new THREE.Vector3())
const center = box.getCenter(new THREE.Vector3())
// Calculate scale to fit within camera view (considering FOV and distance)
const cameraDistance = camera.position.z
const fov = camera.fov * Math.PI / 180 // Convert to radians
const visibleHeight = 2 * Math.tan(fov / 2) * cameraDistance
const visibleWidth = visibleHeight * (model.positioning.width / model.positioning.height)
const scaleFactor = Math.min(
(visibleHeight) / size.y,
(visibleWidth) / size.x
)
wrapper.scale.multiplyScalar(scaleFactor)
// Center the player object
wrapper.position.sub(center.multiplyScalar(scaleFactor))
// Rotate to face camera (remove the default 180° rotation)
wrapper.rotation.set(0, 0, 0)
scene.add(wrapper)
sceneRef.current = {
...sceneRef.current!,
playerObject
}
void applySkinToPlayerObject(playerObject, model.steveModelSkin).then(() => {
setTimeout(render, 0)
})
// Set up cursor following if enabled
if (model.positioning.followCursor) {
isFollowingCursor.current = true
}
}
}
// Window cursor tracking for followCursor
let lastCursorUpdate = 0
let waitingRender = false
const handleWindowPointerMove = (event: PointerEvent) => {
if (!model.positioning.followCursor) return
// Track cursor position as 0-1 across the entire window
const newPosition = {
x: event.clientX / window.innerWidth,
y: event.clientY / window.innerHeight
}
cursorPosition.current = newPosition
globalThis.cursorPosition = newPosition // Expose for debug
lastCursorUpdate = Date.now()
updatePlayerLookAt()
if (!waitingRender) {
requestAnimationFrame(() => {
render()
waitingRender = false
})
waitingRender = true
}
}
// Add window event listeners
if (model.positioning.followCursor) {
window.addEventListener('pointermove', handleWindowPointerMove)
isFollowingCursor.current = true
}
// Store refs for cleanup
sceneRef.current = {
...sceneRef.current!,
scene,
camera,
renderer,
controls,
dispose () {
if (!model.continiousRender) {
controls.removeEventListener('change', render)
}
if (model.positioning.followCursor) {
window.removeEventListener('pointermove', handleWindowPointerMove)
}
// Clean up loaded models
for (const [modelUrl, model] of loadedModels.current) {
scene.remove(model)
model.traverse((child) => {
if (child instanceof THREE.Mesh) {
if (child.material) {
if (Array.isArray(child.material)) {
for (const mat of child.material) {
mat.dispose()
}
} else {
child.material.dispose()
}
}
if (child.geometry) {
child.geometry.dispose()
}
}
})
}
loadedModels.current.clear()
modelLoaders.current.clear()
const playerObject = sceneRef.current?.playerObject
if (playerObject?.skin.map) {
(playerObject.skin.map as unknown as THREE.Texture).dispose()
}
renderer.dispose()
renderer.domElement?.remove()
}
}
return () => {
sceneRef.current?.dispose()
}
}, [model])
if (!model) return null
const { x, y, width, height, scaled, onlyInitialScale } = model.positioning
const { windowWidth } = model.positioning
const { windowHeight } = model.positioning
const scaleValue = onlyInitialScale ? initialScale : 'var(--guiScale)'
return (
<div
className='overlay-model-viewer-container'
style={{
zIndex: 100,
position: 'fixed',
inset: 0,
width: '100dvw',
height: '100dvh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
transform: scaled ? `scale(${scaleValue})` : 'none',
pointerEvents: 'none',
}}
>
<div
className='overlay-model-viewer-window'
style={{
width: windowWidth,
height: windowHeight,
position: 'relative',
pointerEvents: 'none',
}}
>
<div
ref={containerRef}
className='overlay-model-viewer'
style={{
position: 'absolute',
left: x,
top: y,
width,
height,
pointerEvents: 'auto',
backgroundColor: model.debug ? 'red' : undefined,
}}
/>
</div>
</div>
)
}

View file

@ -119,7 +119,6 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
...serversListProvided,
...(customServersList ? [] : (miscUiState.appConfig?.promoteServers ?? [])).map((server): StoreServerItem => ({
ip: server.ip,
name: server.name,
versionOverride: server.version,
description: server.description,
isRecommended: true
@ -168,7 +167,6 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
console.log('pingResult.fullInfo.description', pingResult.fullInfo.description)
data = {
formattedText: pingResult.fullInfo.description,
icon: pingResult.fullInfo.favicon,
textNameRight: `ws ${pingResult.latency}ms`,
textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`,
offline: false

View file

@ -68,7 +68,6 @@ import FullscreenTime from './react/FullscreenTime'
import StorageConflictModal from './react/StorageConflictModal'
import FireRenderer from './react/FireRenderer'
import MonacoEditor from './react/MonacoEditor'
import OverlayModelViewer from './react/OverlayModelViewer'
const isFirefox = ua.getBrowser().name === 'Firefox'
if (isFirefox) {
@ -260,7 +259,6 @@ const App = () => {
</div>
<div />
<DebugEdges />
<OverlayModelViewer />
<MonacoEditor />
<DebugResponseTimeIndicator />
</RobustPortal>

View file

@ -26,10 +26,6 @@
display: flex;
justify-content: center;
z-index: 12;
/* Account for GUI scaling */
width: calc(100dvw / var(--guiScale, 1));
height: calc(100dvh / var(--guiScale, 1));
overflow: hidden;
}
.screen-content {

View file

@ -5,10 +5,10 @@ class MusicSystem {
private currentMusic: string | null = null
async playMusic (url: string, musicVolume = 1) {
if (!options.enableMusic || this.currentMusic || options.musicVolume === 0) return
if (!options.enableMusic || this.currentMusic) return
try {
const { onEnded } = await loadOrPlaySound(url, musicVolume, 5000, undefined, true) ?? {}
const { onEnded } = await loadOrPlaySound(url, 0.5 * musicVolume, 5000) ?? {}
if (!onEnded) return

View file

@ -3,7 +3,6 @@
import { subscribeKey } from 'valtio/utils'
import { isMobile } from 'renderer/viewer/lib/simpleUtils'
import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter'
import { setSkinsConfig } from 'renderer/viewer/lib/utils/skins'
import { options, watchValue } from './optionsStorage'
import { reloadChunks } from './utils'
import { miscUiState } from './globalState'
@ -98,8 +97,6 @@ export const watchOptionsAfterViewerInit = () => {
appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor
appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading
appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks
setSkinsConfig({ apiEnabled: o.loadPlayerSkins })
})
appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting
@ -119,10 +116,6 @@ export const watchOptionsAfterViewerInit = () => {
appViewer.inWorldRenderingConfig.starfield = o.starfieldRendering
})
watchValue(options, o => {
appViewer.inWorldRenderingConfig.defaultSkybox = o.defaultSkybox
})
watchValue(options, o => {
// appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates
})