Merge branch 'next' into renderer-debug
This commit is contained in:
commit
c151e414d9
74 changed files with 1492 additions and 418 deletions
|
|
@ -34,6 +34,7 @@ All components that are in [Storybook](https://mcraft.fun/storybook) are publish
|
|||
|
||||
- Controls -> **Touch Controls Type** -> **Joystick**
|
||||
- Controls -> **Auto Full Screen** -> **On** - To avoid ctrl+w issue
|
||||
- Interface -> **Enable Minimap** -> **Always** - To enable useful minimap (why not?)
|
||||
- Controls -> **Raw Input** -> **On** - This will make the controls more precise (UPD: already enabled by default)
|
||||
- Interface -> **Chat Select** -> **On** - To select chat messages (UPD: already enabled by default)
|
||||
|
||||
|
|
|
|||
36
config.json
36
config.json
|
|
@ -26,7 +26,8 @@
|
|||
}
|
||||
],
|
||||
"rightSideText": "A Minecraft client clone in the browser!",
|
||||
"splashText": "Gen is cooking!",
|
||||
"splashText": "The sunset is coming!",
|
||||
"splashTextFallback": "Welcome!",
|
||||
"pauseLinks": [
|
||||
[
|
||||
{
|
||||
|
|
@ -36,5 +37,38 @@
|
|||
"type": "discord"
|
||||
}
|
||||
]
|
||||
],
|
||||
"mobileButtons": [
|
||||
{
|
||||
"action": "general.drop",
|
||||
"actionHold": "general.dropStack",
|
||||
"label": "Q"
|
||||
},
|
||||
{
|
||||
"action": "general.selectItem",
|
||||
"actionHold": "",
|
||||
"label": "S"
|
||||
},
|
||||
{
|
||||
"action": "general.debugOverlay",
|
||||
"actionHold": "general.debugOverlayHelpMenu",
|
||||
"label": "F3"
|
||||
},
|
||||
{
|
||||
"action": "general.playersList",
|
||||
"actionHold": "",
|
||||
"icon": "pixelarticons:users",
|
||||
"label": "TAB"
|
||||
},
|
||||
{
|
||||
"action": "general.chat",
|
||||
"actionHold": "",
|
||||
"label": ""
|
||||
},
|
||||
{
|
||||
"action": "ui.pauseMenu",
|
||||
"actionHold": "",
|
||||
"label": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -51,8 +51,8 @@
|
|||
"dependencies": {
|
||||
"@dimaka/interface": "0.0.3-alpha.0",
|
||||
"@floating-ui/react": "^0.26.1",
|
||||
"@nxg-org/mineflayer-auto-jump": "^0.7.12",
|
||||
"@nxg-org/mineflayer-tracker": "1.2.1",
|
||||
"@nxg-org/mineflayer-auto-jump": "^0.7.18",
|
||||
"@nxg-org/mineflayer-tracker": "1.3.0",
|
||||
"@react-oauth/google": "^0.12.1",
|
||||
"@stylistic/eslint-plugin": "^2.6.1",
|
||||
"@types/gapi": "^0.0.47",
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
"jszip": "^3.10.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mcraft-fun-mineflayer": "^0.1.23",
|
||||
"minecraft-data": "3.83.1",
|
||||
"minecraft-data": "3.89.0",
|
||||
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
|
||||
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
|
||||
"mojangson": "^2.0.4",
|
||||
|
|
@ -140,7 +140,7 @@
|
|||
"browserify-zlib": "^0.2.0",
|
||||
"buffer": "^6.0.3",
|
||||
"constants-browserify": "^1.0.0",
|
||||
"contro-max": "^0.1.8",
|
||||
"contro-max": "^0.1.9",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"cypress-esbuild-preprocessor": "^1.0.2",
|
||||
"eslint": "^8.50.0",
|
||||
|
|
@ -150,7 +150,7 @@
|
|||
"http-browserify": "^1.7.0",
|
||||
"http-server": "^14.1.1",
|
||||
"https-browserify": "^1.0.0",
|
||||
"mc-assets": "^0.2.53",
|
||||
"mc-assets": "^0.2.54",
|
||||
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
||||
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
|
||||
"mineflayer-mouse": "^0.1.10",
|
||||
|
|
@ -194,14 +194,14 @@
|
|||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@nxg-org/mineflayer-physics-util": "1.8.7",
|
||||
"@nxg-org/mineflayer-physics-util": "1.8.10",
|
||||
"buffer": "^6.0.3",
|
||||
"vec3": "0.1.10",
|
||||
"three": "0.154.0",
|
||||
"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.83.1",
|
||||
"minecraft-data": "3.89.0",
|
||||
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
|
||||
"prismarine-physics": "github:zardoy/prismarine-physics",
|
||||
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
diff --git a/README.md b/README.md
|
||||
deleted file mode 100644
|
||||
index fbcaa43667323a58b8110a4495938c2c6d2d6f83..0000000000000000000000000000000000000000
|
||||
diff --git a/src/client/chat.js b/src/client/chat.js
|
||||
index f14269bea055d4329cd729271e7406ec4b344de7..00f5482eb6e3c911381ca9a728b1b4aae0d1d337 100644
|
||||
--- a/src/client/chat.js
|
||||
|
|
@ -73,6 +76,19 @@ index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108
|
|||
}
|
||||
|
||||
function onJoinServerResponse (err) {
|
||||
diff --git a/src/client/play.js b/src/client/play.js
|
||||
index 6e06dc15291b38e1eeeec8d7102187b2a23d70a3..f67454942db9276cbb9eab99c281cfe182cb8a1f 100644
|
||||
--- a/src/client/play.js
|
||||
+++ b/src/client/play.js
|
||||
@@ -53,7 +53,7 @@ module.exports = function (client, options) {
|
||||
client.write('configuration_acknowledged', {})
|
||||
}
|
||||
client.state = states.CONFIGURATION
|
||||
- client.on('select_known_packs', () => {
|
||||
+ client.once('select_known_packs', () => {
|
||||
client.write('select_known_packs', { packs: [] })
|
||||
})
|
||||
// Server should send finish_configuration on its own right after sending the client a dimension codec
|
||||
diff --git a/src/client.js b/src/client.js
|
||||
index 74749698f8cee05b5dc749c271544f78d06645b0..e77e0a3f41c1ee780c3abbd54b0801d248c2a07c 100644
|
||||
--- a/src/client.js
|
||||
|
|
|
|||
194
pnpm-lock.yaml
generated
194
pnpm-lock.yaml
generated
|
|
@ -5,14 +5,14 @@ settings:
|
|||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
'@nxg-org/mineflayer-physics-util': 1.8.7
|
||||
'@nxg-org/mineflayer-physics-util': 1.8.10
|
||||
buffer: ^6.0.3
|
||||
vec3: 0.1.10
|
||||
three: 0.154.0
|
||||
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.83.1
|
||||
minecraft-data: 3.89.0
|
||||
prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything
|
||||
prismarine-physics: github:zardoy/prismarine-physics
|
||||
minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master
|
||||
|
|
@ -22,7 +22,7 @@ overrides:
|
|||
|
||||
patchedDependencies:
|
||||
minecraft-protocol:
|
||||
hash: 3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab
|
||||
hash: 1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37
|
||||
path: patches/minecraft-protocol.patch
|
||||
mineflayer-item-map-downloader@1.2.0:
|
||||
hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad
|
||||
|
|
@ -42,11 +42,11 @@ importers:
|
|||
specifier: ^0.26.1
|
||||
version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nxg-org/mineflayer-auto-jump':
|
||||
specifier: ^0.7.12
|
||||
version: 0.7.12
|
||||
specifier: ^0.7.18
|
||||
version: 0.7.18
|
||||
'@nxg-org/mineflayer-tracker':
|
||||
specifier: 1.2.1
|
||||
version: 1.2.1(encoding@0.1.13)
|
||||
specifier: 1.3.0
|
||||
version: 1.3.0(encoding@0.1.13)
|
||||
'@react-oauth/google':
|
||||
specifier: ^0.12.1
|
||||
version: 0.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
|
|
@ -136,13 +136,13 @@ importers:
|
|||
version: 4.17.21
|
||||
mcraft-fun-mineflayer:
|
||||
specifier: ^0.1.23
|
||||
version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13))
|
||||
version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/e4818de9e1ba2783a930b4358499ac4033e6383d(encoding@0.1.13))
|
||||
minecraft-data:
|
||||
specifier: 3.83.1
|
||||
version: 3.83.1
|
||||
specifier: 3.89.0
|
||||
version: 3.89.0
|
||||
minecraft-protocol:
|
||||
specifier: github:PrismarineJS/node-minecraft-protocol#master
|
||||
version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13)
|
||||
version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(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)
|
||||
|
|
@ -166,7 +166,7 @@ importers:
|
|||
version: 6.1.1
|
||||
prismarine-provider-anvil:
|
||||
specifier: github:zardoy/prismarine-provider-anvil#everything
|
||||
version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1)
|
||||
version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.89.0)
|
||||
prosemirror-example-setup:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.3
|
||||
|
|
@ -304,8 +304,8 @@ importers:
|
|||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
contro-max:
|
||||
specifier: ^0.1.8
|
||||
version: 0.1.8(typescript@5.5.4)
|
||||
specifier: ^0.1.9
|
||||
version: 0.1.9(typescript@5.5.4)
|
||||
crypto-browserify:
|
||||
specifier: ^3.12.0
|
||||
version: 3.12.1
|
||||
|
|
@ -334,14 +334,14 @@ importers:
|
|||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
mc-assets:
|
||||
specifier: ^0.2.53
|
||||
version: 0.2.53
|
||||
specifier: ^0.2.54
|
||||
version: 0.2.54
|
||||
minecraft-inventory-gui:
|
||||
specifier: github:zardoy/minecraft-inventory-gui#next
|
||||
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4(@types/react@18.3.18)(react@18.3.1)
|
||||
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f425e6a2db796d42cd2c09cf715dad841dec643d(@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/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13)
|
||||
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/e4818de9e1ba2783a930b4358499ac4033e6383d(encoding@0.1.13)
|
||||
mineflayer-mouse:
|
||||
specifier: ^0.1.10
|
||||
version: 0.1.10(@types/debug@4.1.12)(@types/node@22.13.9)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
|
|
@ -435,7 +435,7 @@ importers:
|
|||
version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-chunk:
|
||||
specifier: github:zardoy/prismarine-chunk#master
|
||||
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1)
|
||||
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
|
||||
prismarine-schematic:
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.3
|
||||
|
|
@ -2019,14 +2019,14 @@ packages:
|
|||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||
deprecated: This functionality has been moved to @npmcli/fs
|
||||
|
||||
'@nxg-org/mineflayer-auto-jump@0.7.12':
|
||||
resolution: {integrity: sha512-F5vX/lerlWx/5HVlkDNbvrtQ19PL6iG8i4ItPTIRtjGiFzusDefP7DI226zSFR8Wlaw45qHv0jn814p/4/qVdQ==}
|
||||
'@nxg-org/mineflayer-auto-jump@0.7.18':
|
||||
resolution: {integrity: sha512-O/nRCyWrRwFpcCXXSJhmt844c4a8KhkK4OJPAOKSc63tExIIQU/sipHgjgpy0B+gVDjSmLMPYXe71CN0W327Wg==}
|
||||
|
||||
'@nxg-org/mineflayer-physics-util@1.8.7':
|
||||
resolution: {integrity: sha512-wtLYvHqoEFr/j0ny2lyogwjbMvwpFuG2aWI8sI14+EAiGFRpL5+cog2ujSDsnRTZruO7tUXMTiPc1kebjXwfJg==}
|
||||
'@nxg-org/mineflayer-physics-util@1.8.10':
|
||||
resolution: {integrity: sha512-JGIJEPauVmqoBFQ0I8ZtbaYo3mKn2N00srnDrWkCEt1qozyZWie4sYR0khjjwYubFCljMoWtoEA0+DLsHZLNFg==}
|
||||
|
||||
'@nxg-org/mineflayer-tracker@1.2.1':
|
||||
resolution: {integrity: sha512-SI1ffF8zvg3/ZNE021Ja2W0FZPN+WbQDZf8yFqOcXtPRXAtM9W6HvoACdzXep8BZid7WYgYLIgjKpB+9RqvCNQ==}
|
||||
'@nxg-org/mineflayer-tracker@1.3.0':
|
||||
resolution: {integrity: sha512-HINrv51l2aZ/lDrcL77gSWDvf3Z3trd6kdiifXitCMDNdBT0FpWnXq9bi5Fr7yPpFGQ3fqGUIq5DQYYY84E9IA==}
|
||||
|
||||
'@nxg-org/mineflayer-trajectories@1.2.0':
|
||||
resolution: {integrity: sha512-yTDHn96fyWLKwdHdOGIrnt8nss4SJmxXwJn101o7aNI4sgdnUmwaX4FoNbmrEa9eZn6IwxaXIxDf+fJmKj9RIw==}
|
||||
|
|
@ -4237,8 +4237,8 @@ packages:
|
|||
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
contro-max@0.1.8:
|
||||
resolution: {integrity: sha512-5SoeudO8Zzfj/gbFTDrMRFJny02+MY1lBtb2NyCNiBLtHAfvhWZxZs/Z3yJvKL2rY/qKUZs9gTQOIDygBcBrdw==}
|
||||
contro-max@0.1.9:
|
||||
resolution: {integrity: sha512-zH9FB60EzhHKublD92d11QuarYRTdYci5rvDgwDr5XXwUqae5mr6IgzXGcr78T2odnO/Aeqmrf32RDwJIl5GfQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
convert-source-map@1.9.0:
|
||||
|
|
@ -4428,6 +4428,15 @@ 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
|
||||
|
||||
decamelize-keys@1.1.1:
|
||||
resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
@ -6466,8 +6475,8 @@ packages:
|
|||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
mc-assets@0.2.53:
|
||||
resolution: {integrity: sha512-Ucsu2pDLr/cs8bxbxU9KTszdf/vPTLphYgEHUEWxuYlMkPQUCpsQwkn3YgyykJ7RXaca7zZGlZXaTPXBAqJT6A==}
|
||||
mc-assets@0.2.54:
|
||||
resolution: {integrity: sha512-ZEaa9IcqfOt4cFGLVJVkZMemKPfbjQskvIxuDepkXWkJb9T+xQ+Hj86zDMh1Ah8WZWNeGx2x26CuXt8QNr6pcw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
mcraft-fun-mineflayer@0.1.23:
|
||||
|
|
@ -6678,14 +6687,14 @@ packages:
|
|||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
minecraft-data@3.83.1:
|
||||
resolution: {integrity: sha512-5K26za9k5WV1OnfkGexA77lBhfGZeFw3rT3NM7/rbFXRZC65prCx7Tk2BQvC9UfzgxxvmxHfxM5y8G1U+Oxgfg==}
|
||||
minecraft-data@3.89.0:
|
||||
resolution: {integrity: sha512-v6dUr1M7Pjc6N4ujanrBZu3IP4/HbSBpxSSXNbK6HVFVJqfaqKSMXN57G/JAlDcwqXYsVd9H4tbKFHCO+VmQpg==}
|
||||
|
||||
minecraft-folder-path@1.2.0:
|
||||
resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==}
|
||||
|
||||
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4:
|
||||
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4}
|
||||
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f425e6a2db796d42cd2c09cf715dad841dec643d:
|
||||
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f425e6a2db796d42cd2c09cf715dad841dec643d}
|
||||
version: 1.0.1
|
||||
|
||||
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284:
|
||||
|
|
@ -6715,8 +6724,8 @@ packages:
|
|||
resolution: {integrity: sha512-3bxph4jfbkBh5HpeouorxzrfSLNV+i+1gugNJ2jf52HW+rt+tW7eiiFPxrJEsOVkPT/3O/dEIW7j93LRlojMkQ==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6:
|
||||
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6}
|
||||
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/e4818de9e1ba2783a930b4358499ac4033e6383d:
|
||||
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/e4818de9e1ba2783a930b4358499ac4033e6383d}
|
||||
version: 4.27.0
|
||||
engines: {node: '>=22'}
|
||||
|
||||
|
|
@ -7415,7 +7424,7 @@ packages:
|
|||
prismarine-biome@1.3.0:
|
||||
resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==}
|
||||
peerDependencies:
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-data: 3.89.0
|
||||
prismarine-registry: ^1.1.0
|
||||
|
||||
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
|
||||
|
|
@ -9742,7 +9751,7 @@ snapshots:
|
|||
'@babel/core': 7.26.9
|
||||
'@babel/helper-compilation-targets': 7.26.5
|
||||
'@babel/helper-plugin-utils': 7.26.5
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.10
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -11368,17 +11377,18 @@ snapshots:
|
|||
rimraf: 3.0.2
|
||||
optional: true
|
||||
|
||||
'@nxg-org/mineflayer-auto-jump@0.7.12':
|
||||
'@nxg-org/mineflayer-auto-jump@0.7.18':
|
||||
dependencies:
|
||||
'@nxg-org/mineflayer-physics-util': 1.8.7
|
||||
'@nxg-org/mineflayer-physics-util': 1.8.10
|
||||
strict-event-emitter-types: 2.0.0
|
||||
|
||||
'@nxg-org/mineflayer-physics-util@1.8.7':
|
||||
'@nxg-org/mineflayer-physics-util@1.8.10':
|
||||
dependencies:
|
||||
'@nxg-org/mineflayer-util-plugin': 1.8.4
|
||||
|
||||
'@nxg-org/mineflayer-tracker@1.2.1(encoding@0.1.13)':
|
||||
'@nxg-org/mineflayer-tracker@1.3.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@nxg-org/mineflayer-physics-util': 1.8.10
|
||||
'@nxg-org/mineflayer-trajectories': 1.2.0(encoding@0.1.13)
|
||||
'@nxg-org/mineflayer-util-plugin': 1.8.4
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -11388,7 +11398,7 @@ snapshots:
|
|||
'@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@nxg-org/mineflayer-util-plugin': 1.8.4
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-data: 3.89.0
|
||||
mineflayer: 4.27.0(encoding@0.1.13)
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-item: 1.16.0
|
||||
|
|
@ -13189,16 +13199,16 @@ snapshots:
|
|||
exit-hook: 2.2.1
|
||||
flatmap: 0.0.3
|
||||
long: 5.3.1
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13)
|
||||
minecraft-data: 3.89.0
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(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.83.1)
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
|
||||
prismarine-entity: 2.5.0
|
||||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.7.0
|
||||
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1)
|
||||
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.89.0)
|
||||
prismarine-windows: 2.9.0
|
||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||
rambda: 9.4.2
|
||||
|
|
@ -13225,16 +13235,16 @@ snapshots:
|
|||
exit-hook: 2.2.1
|
||||
flatmap: 0.0.3
|
||||
long: 5.3.1
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13)
|
||||
minecraft-data: 3.89.0
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(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.83.1)
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
|
||||
prismarine-entity: 2.5.0
|
||||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.7.0
|
||||
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1)
|
||||
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.89.0)
|
||||
prismarine-windows: 2.9.0
|
||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||
rambda: 9.4.2
|
||||
|
|
@ -13312,7 +13322,7 @@ snapshots:
|
|||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
|
@ -14215,7 +14225,7 @@ snapshots:
|
|||
|
||||
content-type@1.0.5: {}
|
||||
|
||||
contro-max@0.1.8(typescript@5.5.4):
|
||||
contro-max@0.1.9(typescript@5.5.4):
|
||||
dependencies:
|
||||
events: 3.3.0
|
||||
lodash-es: 4.17.21
|
||||
|
|
@ -14480,6 +14490,10 @@ snapshots:
|
|||
optionalDependencies:
|
||||
supports-color: 8.1.1
|
||||
|
||||
debug@4.4.1:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize-keys@1.1.1:
|
||||
dependencies:
|
||||
decamelize: 1.2.0
|
||||
|
|
@ -14624,8 +14638,8 @@ snapshots:
|
|||
|
||||
diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.83.1)
|
||||
minecraft-data: 3.89.0
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
|
||||
prismarine-registry: 1.11.0
|
||||
random-seed: 0.3.0
|
||||
vec3: 0.1.10
|
||||
|
|
@ -16128,7 +16142,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@tootallnate/once': 2.0.0
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
optional: true
|
||||
|
|
@ -17014,17 +17028,17 @@ snapshots:
|
|||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mc-assets@0.2.53:
|
||||
mc-assets@0.2.54:
|
||||
dependencies:
|
||||
maxrects-packer: '@zardoy/maxrects-packer@2.7.4'
|
||||
zod: 3.24.2
|
||||
|
||||
mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13)):
|
||||
mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/e4818de9e1ba2783a930b4358499ac4033e6383d(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/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13)
|
||||
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13)
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13)
|
||||
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/e4818de9e1ba2783a930b4358499ac4033e6383d(encoding@0.1.13)
|
||||
prismarine-item: 1.16.0
|
||||
ws: 8.18.1
|
||||
transitivePeerDependencies:
|
||||
|
|
@ -17254,7 +17268,7 @@ snapshots:
|
|||
micromark@4.0.2:
|
||||
dependencies:
|
||||
'@types/debug': 4.1.12
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
decode-named-character-reference: 1.1.0
|
||||
devlop: 1.1.0
|
||||
micromark-core-commonmark: 2.0.3
|
||||
|
|
@ -17331,18 +17345,18 @@ snapshots:
|
|||
|
||||
min-indent@1.0.1: {}
|
||||
|
||||
minecraft-data@3.83.1: {}
|
||||
minecraft-data@3.89.0: {}
|
||||
|
||||
minecraft-folder-path@1.2.0: {}
|
||||
|
||||
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/98bc5bb8ee6da8b4b771c05b404cee796318ccd4(@types/react@18.3.18)(react@18.3.1):
|
||||
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f425e6a2db796d42cd2c09cf715dad841dec643d(@types/react@18.3.18)(react@18.3.1):
|
||||
dependencies:
|
||||
valtio: 1.13.2(@types/react@18.3.18)(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- react
|
||||
|
||||
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13):
|
||||
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@types/node-rsa': 1.1.4
|
||||
'@types/readable-stream': 4.0.18
|
||||
|
|
@ -17351,7 +17365,7 @@ snapshots:
|
|||
debug: 4.4.0(supports-color@8.1.1)
|
||||
endian-toggle: 0.0.0
|
||||
lodash.merge: 4.6.2
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-data: 3.89.0
|
||||
minecraft-folder-path: 1.2.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
node-rsa: 0.4.2
|
||||
|
|
@ -17430,7 +17444,7 @@ snapshots:
|
|||
|
||||
mineflayer-pathfinder@2.4.5:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-data: 3.89.0
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-entity: 2.5.0
|
||||
prismarine-item: 1.16.0
|
||||
|
|
@ -17440,12 +17454,12 @@ snapshots:
|
|||
|
||||
mineflayer@4.27.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13)
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
minecraft-data: 3.89.0
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13)
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.89.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.83.1)
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
|
||||
prismarine-entity: 2.5.0
|
||||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.7.0
|
||||
|
|
@ -17461,15 +17475,15 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/5602eca4174c0aff079e60234d7c68327eeb6fe6(encoding@0.1.13):
|
||||
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/e4818de9e1ba2783a930b4358499ac4033e6383d(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@nxg-org/mineflayer-physics-util': 1.8.7
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=3a55a278c417cc34ff3172cd1de8e22852935cba0586875cbd0635f1ffdaa5ab)(encoding@0.1.13)
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
'@nxg-org/mineflayer-physics-util': 1.8.10
|
||||
minecraft-data: 3.89.0
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=1546deaf50efae3d6564fcc9f08da99d3ae8096ac98f420b87b48b8c9501fd37)(encoding@0.1.13)
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.89.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.83.1)
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
|
||||
prismarine-entity: 2.5.0
|
||||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.7.0
|
||||
|
|
@ -18255,15 +18269,15 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
prismarine-biome@1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0):
|
||||
prismarine-biome@1.3.0(minecraft-data@3.89.0)(prismarine-registry@1.11.0):
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-data: 3.89.0
|
||||
prismarine-registry: 1.11.0
|
||||
|
||||
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
minecraft-data: 3.89.0
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.89.0)(prismarine-registry@1.11.0)
|
||||
prismarine-chat: 1.11.0
|
||||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.7.0
|
||||
|
|
@ -18275,9 +18289,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.83.1):
|
||||
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0):
|
||||
dependencies:
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.89.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
|
||||
|
|
@ -18306,14 +18320,14 @@ snapshots:
|
|||
|
||||
prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-data: 3.89.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.83.1):
|
||||
prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.89.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.83.1)
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
|
||||
prismarine-nbt: 2.7.0
|
||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||
uint4: 0.1.2
|
||||
|
|
@ -18335,13 +18349,13 @@ snapshots:
|
|||
|
||||
prismarine-registry@1.11.0:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-data: 3.89.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.83.1
|
||||
minecraft-data: 3.89.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
|
||||
|
|
@ -19180,7 +19194,7 @@ snapshots:
|
|||
|
||||
send@1.2.0:
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
encodeurl: 2.0.0
|
||||
escape-html: 1.0.3
|
||||
etag: 1.8.1
|
||||
|
|
@ -19535,7 +19549,7 @@ snapshots:
|
|||
socks-proxy-agent@7.0.0:
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
socks: 2.8.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -20448,7 +20462,7 @@ snapshots:
|
|||
vite-node@3.0.8(@types/node@22.13.9)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
es-module-lexer: 1.6.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.2.1(@types/node@22.13.9)(jiti@2.4.2)(terser@5.39.0)(tsx@4.19.3)(yaml@2.7.0)
|
||||
|
|
@ -20534,7 +20548,7 @@ snapshots:
|
|||
'@vitest/spy': 3.0.8
|
||||
'@vitest/utils': 3.0.8
|
||||
chai: 5.2.0
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.1
|
||||
expect-type: 1.2.0
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { RendererReactiveState } from '../../src/appViewer'
|
|||
export const getDefaultRendererState = (): RendererReactiveState => {
|
||||
return {
|
||||
world: {
|
||||
chunksLoaded: [],
|
||||
chunksLoaded: new Set(),
|
||||
heightmaps: new Map(),
|
||||
chunksTotalNumber: 0,
|
||||
allChunksLoaded: true,
|
||||
mesherWork: false,
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ export interface IPlayerState {
|
|||
getHeldItem?(isLeftHand: boolean): HandItemBlock | undefined
|
||||
username?: string
|
||||
onlineMode?: boolean
|
||||
lightingDisabled?: boolean
|
||||
shouldHideHand?: boolean
|
||||
|
||||
events: TypedEmitter<PlayerStateEvents>
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Vec3 } from 'vec3'
|
|||
import { World } from './world'
|
||||
import { getSectionGeometry, setBlockStatesData as setMesherData } from './models'
|
||||
import { BlockStateModelInfo } from './shared'
|
||||
import { INVISIBLE_BLOCKS } from './worldConstants'
|
||||
|
||||
globalThis.structuredClone ??= (value) => JSON.parse(JSON.stringify(value))
|
||||
|
||||
|
|
@ -148,6 +149,30 @@ const handleMessage = data => {
|
|||
global.postMessage({ type: 'customBlockModel', chunkKey, customBlockModel })
|
||||
break
|
||||
}
|
||||
case 'getHeightmap': {
|
||||
const heightmap = new Uint8Array(256)
|
||||
|
||||
const blockPos = new Vec3(0, 0, 0)
|
||||
for (let z = 0; z < 16; z++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const blockX = x + data.x
|
||||
const blockZ = z + data.z
|
||||
blockPos.x = blockX
|
||||
blockPos.z = blockZ
|
||||
blockPos.y = world.config.worldMaxY
|
||||
let block = world.getBlock(blockPos)
|
||||
while (block && INVISIBLE_BLOCKS.has(block.name) && blockPos.y > world.config.worldMinY) {
|
||||
blockPos.y -= 1
|
||||
block = world.getBlock(blockPos)
|
||||
}
|
||||
const index = z * 16 + x
|
||||
heightmap[index] = block ? blockPos.y : 0
|
||||
}
|
||||
}
|
||||
postMessage({ type: 'heightmap', key: `${Math.floor(data.x / 16)},${Math.floor(data.z / 16)}`, heightmap })
|
||||
|
||||
break
|
||||
}
|
||||
// No default
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,13 @@ const isCube = (block: Block) => {
|
|||
}))
|
||||
}
|
||||
|
||||
const getVec = (v: Vec3, dir: Vec3) => {
|
||||
for (const coord of ['x', 'y', 'z']) {
|
||||
if (Math.abs(dir[coord]) > 0) v[coord] = 0
|
||||
}
|
||||
return v.plus(dir)
|
||||
}
|
||||
|
||||
function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: Record<string, any>, isRealWater: boolean) {
|
||||
const heights: number[] = []
|
||||
for (let z = -1; z <= 1; z++) {
|
||||
|
|
@ -142,7 +149,7 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ
|
|||
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const face in elemFaces) {
|
||||
const { dir, corners } = elemFaces[face]
|
||||
const { dir, corners, mask1, mask2 } = elemFaces[face]
|
||||
const isUp = dir[1] === 1
|
||||
|
||||
const neighborPos = cursor.offset(...dir as [number, number, number])
|
||||
|
|
@ -180,6 +187,9 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ
|
|||
const { su } = texture
|
||||
const { sv } = texture
|
||||
|
||||
// Get base light value for the face
|
||||
const baseLight = world.getLight(neighborPos, undefined, undefined, water ? 'water' : 'lava') / 15
|
||||
|
||||
for (const pos of corners) {
|
||||
const height = cornerHeights[pos[2] * 2 + pos[0]]
|
||||
attr.t_positions.push(
|
||||
|
|
@ -189,7 +199,31 @@ function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, typ
|
|||
)
|
||||
attr.t_normals.push(...dir)
|
||||
attr.t_uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v)
|
||||
attr.t_colors.push(tint[0], tint[1], tint[2])
|
||||
|
||||
let cornerLightResult = baseLight
|
||||
if (world.config.smoothLighting) {
|
||||
const dx = pos[0] * 2 - 1
|
||||
const dy = pos[1] * 2 - 1
|
||||
const dz = pos[2] * 2 - 1
|
||||
const cornerDir: [number, number, number] = [dx, dy, dz]
|
||||
const side1Dir: [number, number, number] = [dx * mask1[0], dy * mask1[1], dz * mask1[2]]
|
||||
const side2Dir: [number, number, number] = [dx * mask2[0], dy * mask2[1], dz * mask2[2]]
|
||||
|
||||
const dirVec = new Vec3(...dir as [number, number, number])
|
||||
|
||||
const side1LightDir = getVec(new Vec3(...side1Dir), dirVec)
|
||||
const side1Light = world.getLight(cursor.plus(side1LightDir)) / 15
|
||||
const side2DirLight = getVec(new Vec3(...side2Dir), dirVec)
|
||||
const side2Light = world.getLight(cursor.plus(side2DirLight)) / 15
|
||||
const cornerLightDir = getVec(new Vec3(...cornerDir), dirVec)
|
||||
const cornerLight = world.getLight(cursor.plus(cornerLightDir)) / 15
|
||||
// interpolate
|
||||
const lights = [side1Light, side2Light, cornerLight, baseLight]
|
||||
cornerLightResult = lights.reduce((acc, cur) => acc + cur, 0) / lights.length
|
||||
}
|
||||
|
||||
// Apply light value to tint
|
||||
attr.t_colors.push(tint[0] * cornerLightResult, tint[1] * cornerLightResult, tint[2] * cornerLightResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -476,7 +510,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
|||
heads: {},
|
||||
signs: {},
|
||||
// isFull: true,
|
||||
highestBlocks: {},
|
||||
highestBlocks: new Map(),
|
||||
hadErrors: false,
|
||||
blocksCount: 0
|
||||
}
|
||||
|
|
@ -487,9 +521,9 @@ export function getSectionGeometry (sx, sy, sz, world: World) {
|
|||
for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) {
|
||||
let block = world.getBlock(cursor, blockProvider, attr)!
|
||||
if (!INVISIBLE_BLOCKS.has(block.name)) {
|
||||
const highest = attr.highestBlocks[`${cursor.x},${cursor.z}`]
|
||||
const highest = attr.highestBlocks.get(`${cursor.x},${cursor.z}`)
|
||||
if (!highest || highest.y < cursor.y) {
|
||||
attr.highestBlocks[`${cursor.x},${cursor.z}`] = { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id }
|
||||
attr.highestBlocks.set(`${cursor.x},${cursor.z}`, { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id })
|
||||
}
|
||||
}
|
||||
if (INVISIBLE_BLOCKS.has(block.name)) continue
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { BlockType } from '../../../playground/shared'
|
|||
// only here for easier testing
|
||||
export const defaultMesherConfig = {
|
||||
version: '',
|
||||
worldMaxY: 256,
|
||||
worldMinY: 0,
|
||||
enableLighting: true,
|
||||
skyLight: 15,
|
||||
smoothLighting: true,
|
||||
|
|
@ -40,12 +42,21 @@ export type MesherGeometryOutput = {
|
|||
heads: Record<string, any>,
|
||||
signs: Record<string, any>,
|
||||
// isFull: boolean
|
||||
highestBlocks: Record<string, HighestBlockInfo>
|
||||
highestBlocks: Map<string, HighestBlockInfo>
|
||||
hadErrors: boolean
|
||||
blocksCount: number
|
||||
customBlockModels?: CustomBlockModels
|
||||
}
|
||||
|
||||
export interface MesherMainEvents {
|
||||
geometry: { type: 'geometry'; key: string; geometry: MesherGeometryOutput; workerIndex: number };
|
||||
sectionFinished: { type: 'sectionFinished'; key: string; workerIndex: number; processTime?: number };
|
||||
blockStateModelInfo: { type: 'blockStateModelInfo'; info: Record<string, BlockStateModelInfo> };
|
||||
heightmap: { type: 'heightmap'; key: string; heightmap: Uint8Array };
|
||||
}
|
||||
|
||||
export type MesherMainEvent = MesherMainEvents[keyof MesherMainEvents]
|
||||
|
||||
export type HighestBlockInfo = { y: number, stateId: number | undefined, biomeId: number | undefined }
|
||||
|
||||
export type BlockStateModelInfo = {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ test('Known blocks are not rendered', () => {
|
|||
// TODO resolve creaking_heart issue (1.21.3)
|
||||
expect(missingBlocks).toMatchInlineSnapshot(`
|
||||
{
|
||||
"creaking_heart": true,
|
||||
"end_gateway": true,
|
||||
"end_portal": true,
|
||||
"structure_void": true,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { ResourcesManager } from '../../../src/resourcesManager'
|
|||
import { DisplayWorldOptions, GraphicsInitOptions, RendererReactiveState } from '../../../src/appViewer'
|
||||
import { SoundSystem } from '../three/threeJsSound'
|
||||
import { buildCleanupDecorator } from './cleanupDecorator'
|
||||
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig } from './mesher/shared'
|
||||
import { HighestBlockInfo, MesherGeometryOutput, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from './mesher/shared'
|
||||
import { chunkPos } from './simpleUtils'
|
||||
import { addNewStat, removeAllStats, removeStat, updatePanesVisibility, updateStatText } from './ui/newStats'
|
||||
import { WorldDataEmitter } from './worldDataEmitter'
|
||||
|
|
@ -44,6 +44,7 @@ export const defaultWorldRendererConfig = {
|
|||
starfield: true,
|
||||
addChunksBatchWaitTime: 200,
|
||||
vrSupport: true,
|
||||
vrPageGameRendering: true,
|
||||
renderEntities: true,
|
||||
fov: 75,
|
||||
fetchPlayerSkins: true,
|
||||
|
|
@ -97,6 +98,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
dirty (pos: Vec3, value: boolean): void
|
||||
update (/* pos: Vec3, value: boolean */): void
|
||||
chunkFinished (key: string): void
|
||||
heightmap (key: string, heightmap: Uint8Array): void
|
||||
}>
|
||||
customTexturesDataUrl = undefined as string | undefined
|
||||
workers: any[] = []
|
||||
|
|
@ -115,8 +117,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
ONMESSAGE_TIME_LIMIT = 30 // ms
|
||||
|
||||
handleResize = () => { }
|
||||
highestBlocksByChunks = {} as Record<string, { [chunkKey: string]: HighestBlockInfo }>
|
||||
highestBlocksBySections = {} as Record<string, { [sectionKey: string]: HighestBlockInfo }>
|
||||
highestBlocksByChunks = new Map<string, { [chunkKey: string]: HighestBlockInfo }>()
|
||||
highestBlocksBySections = new Map<string, { [sectionKey: string]: HighestBlockInfo }>()
|
||||
blockEntities = {}
|
||||
|
||||
workersProcessAverageTime = 0
|
||||
|
|
@ -259,7 +261,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
}
|
||||
|
||||
async getHighestBlocks (chunkKey: string) {
|
||||
return this.highestBlocksByChunks[chunkKey]
|
||||
return this.highestBlocksByChunks.get(chunkKey)
|
||||
}
|
||||
|
||||
updateCustomBlock (chunkKey: string, blockPos: string, model: string) {
|
||||
|
|
@ -382,19 +384,20 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.isProcessingQueue = false
|
||||
}
|
||||
|
||||
handleMessage (data) {
|
||||
handleMessage (rawData: any) {
|
||||
const data = rawData as MesherMainEvent
|
||||
if (!this.active) return
|
||||
this.mesherLogReader?.workerMessageReceived(data.type, data)
|
||||
if (data.type !== 'geometry' || !this.debugStopGeometryUpdate) {
|
||||
const start = performance.now()
|
||||
this.handleWorkerMessage(data)
|
||||
this.handleWorkerMessage(data as WorkerReceive)
|
||||
this.workerCustomHandleTime += performance.now() - start
|
||||
}
|
||||
if (data.type === 'geometry') {
|
||||
this.logWorkerWork(() => `-> ${data.workerIndex} geometry ${data.key} ${JSON.stringify({ dataSize: JSON.stringify(data).length })}`)
|
||||
this.geometryReceiveCount[data.workerIndex] ??= 0
|
||||
this.geometryReceiveCount[data.workerIndex]++
|
||||
const geometry = data.geometry as MesherGeometryOutput
|
||||
const { geometry } = data
|
||||
this.highestBlocksBySections[data.key] = geometry.highestBlocks
|
||||
const chunkCoords = data.key.split(',').map(Number)
|
||||
this.lastChunkDistance = Math.max(...this.getDistance(new Vec3(chunkCoords[0], 0, chunkCoords[2])))
|
||||
|
|
@ -421,6 +424,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
if (loaded) {
|
||||
// CHUNK FINISHED
|
||||
this.finishedChunks[chunkKey] = true
|
||||
this.reactiveState.world.chunksLoaded.add(`${Math.floor(chunkCoords[0] / 16)},${Math.floor(chunkCoords[2] / 16)}`)
|
||||
this.renderUpdateEmitter.emit(`chunkFinished`, `${chunkCoords[0]},${chunkCoords[2]}`)
|
||||
this.checkAllFinished()
|
||||
// merge highest blocks by sections into highest blocks by chunks
|
||||
|
|
@ -459,6 +463,10 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.blockStateModelInfo.set(cacheKey, info)
|
||||
}
|
||||
}
|
||||
|
||||
if (data.type === 'heightmap') {
|
||||
appViewer.rendererState.world.heightmaps.set(data.key, new Uint8Array(data.heightmap))
|
||||
}
|
||||
}
|
||||
|
||||
downloadMesherLog () {
|
||||
|
|
@ -569,7 +577,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width,
|
||||
debugModelVariant: undefined,
|
||||
clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY,
|
||||
disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers
|
||||
disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers,
|
||||
worldMinY: this.worldMinYRender,
|
||||
worldMaxY: this.worldMinYRender + this.worldSizeParams.worldHeight,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -616,7 +626,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
|
||||
updateChunksStats () {
|
||||
const loadedChunks = Object.keys(this.finishedChunks)
|
||||
this.displayOptions.nonReactiveState.world.chunksLoaded = loadedChunks
|
||||
this.displayOptions.nonReactiveState.world.chunksLoaded = new Set(loadedChunks)
|
||||
this.displayOptions.nonReactiveState.world.chunksTotalNumber = this.chunksLength
|
||||
this.reactiveState.world.allChunksLoaded = this.allChunksFinished
|
||||
|
||||
|
|
@ -645,6 +655,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
customBlockModels: customBlockModels || undefined
|
||||
})
|
||||
}
|
||||
this.workers[0].postMessage({
|
||||
type: 'getHeightmap',
|
||||
x,
|
||||
z,
|
||||
})
|
||||
this.logWorkerWork(() => `-> chunk ${JSON.stringify({ x, z, chunkLength: chunk.length, customBlockModelsLength: customBlockModels ? Object.keys(customBlockModels).length : 0 })}`)
|
||||
this.mesherLogReader?.chunkReceived(x, z, chunk.length)
|
||||
for (let y = this.worldMinYRender; y < this.worldSizeParams.worldHeight; y += 16) {
|
||||
|
|
@ -681,9 +696,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
|
||||
this.setSectionDirty(new Vec3(x, y, z), false)
|
||||
delete this.finishedSections[`${x},${y},${z}`]
|
||||
delete this.highestBlocksBySections[`${x},${y},${z}`]
|
||||
this.highestBlocksBySections.delete(`${x},${y},${z}`)
|
||||
}
|
||||
delete this.highestBlocksByChunks[`${x},${z}`]
|
||||
this.highestBlocksByChunks.delete(`${x},${z}`)
|
||||
|
||||
this.updateChunksStats()
|
||||
|
||||
|
|
@ -1009,7 +1024,6 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.active = false
|
||||
|
||||
this.renderUpdateEmitter.removeAllListeners()
|
||||
this.displayOptions.worldView.removeAllListeners() // todo
|
||||
this.abortController.abort()
|
||||
removeAllStats()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import * as THREE from 'three'
|
||||
import { WorldRendererThree } from './worldrendererThree'
|
||||
|
||||
export class CameraShake {
|
||||
private rollAngle = 0
|
||||
|
|
@ -8,7 +9,7 @@ export class CameraShake {
|
|||
private basePitch = 0
|
||||
private baseYaw = 0
|
||||
|
||||
constructor (public camera: THREE.Camera, public onRenderCallbacks: Array<() => void>) {
|
||||
constructor (public worldRenderer: WorldRendererThree, public onRenderCallbacks: Array<() => void>) {
|
||||
onRenderCallbacks.push(() => {
|
||||
this.update()
|
||||
})
|
||||
|
|
@ -62,14 +63,21 @@ export class CameraShake {
|
|||
}
|
||||
}
|
||||
|
||||
// Create rotation quaternions
|
||||
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))
|
||||
const camera = this.worldRenderer.cameraGroupVr || this.worldRenderer.camera
|
||||
|
||||
// Combine rotations in the correct order: pitch -> yaw -> roll
|
||||
const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat)
|
||||
this.camera.setRotationFromQuaternion(finalQuat)
|
||||
if (this.worldRenderer.cameraGroupVr) {
|
||||
// For VR camera, only apply yaw rotation
|
||||
const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw)
|
||||
camera.setRotationFromQuaternion(yawQuat)
|
||||
} else {
|
||||
// For regular camera, apply all rotations
|
||||
const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.basePitch)
|
||||
const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw)
|
||||
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)
|
||||
camera.setRotationFromQuaternion(finalQuat)
|
||||
}
|
||||
}
|
||||
|
||||
private easeOut (t: number): number {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Stats from 'stats.js'
|
|||
import StatsGl from 'stats-gl'
|
||||
import * as tween from '@tweenjs/tween.js'
|
||||
import { GraphicsBackendConfig, GraphicsInitOptions } from '../../../src/appViewer'
|
||||
import { WorldRendererConfig } from '../lib/worldrendererCommon'
|
||||
|
||||
export class DocumentRenderer {
|
||||
readonly canvas = document.createElement('canvas')
|
||||
|
|
@ -23,6 +24,7 @@ export class DocumentRenderer {
|
|||
droppedFpsPercentage: number
|
||||
config: GraphicsBackendConfig
|
||||
onRender = [] as Array<(sizeChanged: boolean) => void>
|
||||
inWorldRenderingConfig: WorldRendererConfig | undefined
|
||||
|
||||
constructor (initOptions: GraphicsInitOptions) {
|
||||
this.config = initOptions.config
|
||||
|
|
@ -94,7 +96,7 @@ export class DocumentRenderer {
|
|||
if (this.disconnected) return
|
||||
this.animationFrameId = requestAnimationFrame(animate)
|
||||
|
||||
if (this.paused) return
|
||||
if (this.paused || (this.renderer.xr.isPresenting && !this.inWorldRenderingConfig?.vrPageGameRendering)) return
|
||||
|
||||
// Handle FPS limiting
|
||||
if (this.config.fpsLimit) {
|
||||
|
|
@ -117,18 +119,7 @@ export class DocumentRenderer {
|
|||
sizeChanged = true
|
||||
}
|
||||
|
||||
this.preRender()
|
||||
this.stats.markStart()
|
||||
tween.update()
|
||||
if (!window.freezeRender) {
|
||||
this.render(sizeChanged)
|
||||
}
|
||||
for (const fn of this.onRender) {
|
||||
fn(sizeChanged)
|
||||
}
|
||||
this.renderedFps++
|
||||
this.stats.markEnd()
|
||||
this.postRender()
|
||||
this.frameRender(sizeChanged)
|
||||
|
||||
// Update stats visibility each frame
|
||||
if (this.config.statsVisible !== undefined) {
|
||||
|
|
@ -139,6 +130,21 @@ export class DocumentRenderer {
|
|||
animate()
|
||||
}
|
||||
|
||||
frameRender (sizeChanged: boolean) {
|
||||
this.preRender()
|
||||
this.stats.markStart()
|
||||
tween.update()
|
||||
if (!window.freezeRender) {
|
||||
this.render(sizeChanged)
|
||||
}
|
||||
for (const fn of this.onRender) {
|
||||
fn(sizeChanged)
|
||||
}
|
||||
this.renderedFps++
|
||||
this.stats.markEnd()
|
||||
this.postRender()
|
||||
}
|
||||
|
||||
setPaused (paused: boolean) {
|
||||
this.paused = paused
|
||||
}
|
||||
|
|
|
|||
|
|
@ -292,6 +292,9 @@ export class Entities {
|
|||
playerObject.animation.update(playerObject, dt)
|
||||
}
|
||||
|
||||
// Update armor positions
|
||||
this.syncArmorPositions(entity)
|
||||
|
||||
// Update visibility based on distance and chunk load status
|
||||
if (botPos && entity.position) {
|
||||
const dx = entity.position.x - botPos.x
|
||||
|
|
@ -312,6 +315,79 @@ export class Entities {
|
|||
}
|
||||
}
|
||||
|
||||
private syncArmorPositions (entity: SceneEntity) {
|
||||
if (!entity.playerObject) return
|
||||
|
||||
// todo-low use property access for less loop iterations (small performance gain)
|
||||
entity.traverse((armor) => {
|
||||
if (!armor.name.startsWith('geometry_armor_')) return
|
||||
|
||||
const { skin } = entity.playerObject!
|
||||
|
||||
switch (armor.name) {
|
||||
case 'geometry_armor_head':
|
||||
// Head armor sync
|
||||
if (armor.children[0]?.children[0]) {
|
||||
armor.children[0].children[0].rotation.set(
|
||||
-skin.head.rotation.x,
|
||||
skin.head.rotation.y,
|
||||
skin.head.rotation.z,
|
||||
skin.head.rotation.order
|
||||
)
|
||||
}
|
||||
break
|
||||
|
||||
case 'geometry_armor_legs':
|
||||
// Legs armor sync
|
||||
if (armor.children[0]) {
|
||||
// Left leg
|
||||
if (armor.children[0].children[2]) {
|
||||
armor.children[0].children[2].rotation.set(
|
||||
-skin.leftLeg.rotation.x,
|
||||
skin.leftLeg.rotation.y,
|
||||
skin.leftLeg.rotation.z,
|
||||
skin.leftLeg.rotation.order
|
||||
)
|
||||
}
|
||||
// Right leg
|
||||
if (armor.children[0].children[1]) {
|
||||
armor.children[0].children[1].rotation.set(
|
||||
-skin.rightLeg.rotation.x,
|
||||
skin.rightLeg.rotation.y,
|
||||
skin.rightLeg.rotation.z,
|
||||
skin.rightLeg.rotation.order
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'geometry_armor_feet':
|
||||
// Boots armor sync
|
||||
if (armor.children[0]) {
|
||||
// Right boot
|
||||
if (armor.children[0].children[0]) {
|
||||
armor.children[0].children[0].rotation.set(
|
||||
-skin.rightLeg.rotation.x,
|
||||
skin.rightLeg.rotation.y,
|
||||
skin.rightLeg.rotation.z,
|
||||
skin.rightLeg.rotation.order
|
||||
)
|
||||
}
|
||||
// Left boot (reversed Z rotation)
|
||||
if (armor.children[0].children[1]) {
|
||||
armor.children[0].children[1].rotation.set(
|
||||
-skin.leftLeg.rotation.x,
|
||||
skin.leftLeg.rotation.y,
|
||||
-skin.leftLeg.rotation.z,
|
||||
skin.leftLeg.rotation.order
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getPlayerObject (entityId: string | number) {
|
||||
const playerObject = this.entities[entityId]?.playerObject
|
||||
return playerObject
|
||||
|
|
@ -1203,6 +1279,16 @@ function addArmorModel (worldRenderer: WorldRendererThree, entityMesh: THREE.Obj
|
|||
})
|
||||
} else {
|
||||
mesh = getMesh(worldRenderer, texturePath, armorModel[slotType])
|
||||
// // enable debug mode to see the mesh
|
||||
// mesh.traverse(c => {
|
||||
// if (c instanceof THREE.Mesh) {
|
||||
// c.material.wireframe = true
|
||||
// }
|
||||
// })
|
||||
if (slotType === 'head') {
|
||||
// avoid z-fighting with the head
|
||||
mesh.children[0].position.y += 0.01
|
||||
}
|
||||
mesh.name = meshName
|
||||
material = mesh.material
|
||||
if (!isPlayerHead) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import supportedVersions from '../../../src/supportedVersions.mjs'
|
|||
import { WorldRendererThree } from './worldrendererThree'
|
||||
import { DocumentRenderer } from './documentRenderer'
|
||||
import { PanoramaRenderer } from './panorama'
|
||||
import { initVR } from './world/vr'
|
||||
|
||||
// https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791
|
||||
THREE.ColorManagement.enabled = false
|
||||
|
|
@ -87,10 +88,12 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO
|
|||
panoramaRenderer = null
|
||||
}
|
||||
worldRenderer = new WorldRendererThree(documentRenderer.renderer, initOptions, displayOptions)
|
||||
void initVR(worldRenderer, documentRenderer)
|
||||
await worldRenderer.worldReadyPromise
|
||||
documentRenderer.render = (sizeChanged: boolean) => {
|
||||
worldRenderer?.render(sizeChanged)
|
||||
}
|
||||
documentRenderer.inWorldRenderingConfig = displayOptions.inWorldRenderingConfig
|
||||
window.world = worldRenderer
|
||||
callModsMethod('worldReady', worldRenderer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import * as THREE from 'three'
|
||||
import * as tweenJs from '@tweenjs/tween.js'
|
||||
import PrismarineItem from 'prismarine-item'
|
||||
import worldBlockProvider, { WorldBlockProvider } from 'mc-assets/dist/worldBlockProvider'
|
||||
import { BlockModel } from 'mc-assets'
|
||||
import { getThreeBlockModelGroup, renderBlockThree, setBlockPosition } from '../lib/mesher/standaloneRenderer'
|
||||
|
|
@ -293,6 +294,7 @@ export default class HoldingBlock {
|
|||
}
|
||||
|
||||
isDifferentItem (block: HandItemBlock | undefined) {
|
||||
const Item = PrismarineItem(this.worldRenderer.version)
|
||||
if (!this.lastHeldItem) {
|
||||
return true
|
||||
}
|
||||
|
|
@ -300,7 +302,7 @@ export default class HoldingBlock {
|
|||
return true
|
||||
}
|
||||
// eslint-disable-next-line sonarjs/prefer-single-boolean-return
|
||||
if (JSON.stringify(this.lastHeldItem.fullItem) !== JSON.stringify(block?.fullItem ?? '{}')) {
|
||||
if (!Item.equal(this.lastHeldItem.fullItem, block?.fullItem ?? {}) || JSON.stringify(this.lastHeldItem.fullItem.components) !== JSON.stringify(block?.fullItem?.components)) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerM
|
|||
import { buttonMap as standardButtonsMap } from 'contro-max/build/gamepad'
|
||||
import * as THREE from 'three'
|
||||
import { WorldRendererThree } from '../worldrendererThree'
|
||||
import { DocumentRenderer } from '../documentRenderer'
|
||||
|
||||
export async function initVR (worldRenderer: WorldRendererThree) {
|
||||
export async function initVR (worldRenderer: WorldRendererThree, documentRenderer: DocumentRenderer) {
|
||||
if (!('xr' in navigator) || !worldRenderer.worldRendererConfig.vrSupport) return
|
||||
const { renderer } = worldRenderer
|
||||
|
||||
|
|
@ -26,12 +27,13 @@ export async function initVR (worldRenderer: WorldRendererThree) {
|
|||
|
||||
function enableVr () {
|
||||
renderer.xr.enabled = true
|
||||
// renderer.xr.setReferenceSpaceType('local-floor')
|
||||
worldRenderer.reactiveState.preventEscapeMenu = true
|
||||
}
|
||||
|
||||
function disableVr () {
|
||||
renderer.xr.enabled = false
|
||||
worldRenderer.cameraObjectOverride = undefined
|
||||
worldRenderer.cameraGroupVr = undefined
|
||||
worldRenderer.reactiveState.preventEscapeMenu = false
|
||||
worldRenderer.scene.remove(user)
|
||||
vrButtonContainer.hidden = true
|
||||
|
|
@ -189,7 +191,7 @@ export async function initVR (worldRenderer: WorldRendererThree) {
|
|||
}
|
||||
|
||||
// appViewer.backend?.updateCamera(null, yawOffset, 0)
|
||||
worldRenderer.updateCamera(null, bot.entity.yaw, bot.entity.pitch)
|
||||
// worldRenderer.updateCamera(null, bot.entity.yaw, bot.entity.pitch)
|
||||
|
||||
// todo restore this logic (need to preserve ability to move camera)
|
||||
// const xrCamera = renderer.xr.getCamera()
|
||||
|
|
@ -197,16 +199,13 @@ export async function initVR (worldRenderer: WorldRendererThree) {
|
|||
// bot.entity.yaw = Math.atan2(-d.x, -d.z)
|
||||
// bot.entity.pitch = Math.asin(d.y)
|
||||
|
||||
// todo ?
|
||||
// bot.physics.stepHeight = 1
|
||||
|
||||
worldRenderer.render()
|
||||
documentRenderer.frameRender(false)
|
||||
})
|
||||
renderer.xr.addEventListener('sessionstart', () => {
|
||||
worldRenderer.cameraObjectOverride = user
|
||||
worldRenderer.cameraGroupVr = user
|
||||
})
|
||||
renderer.xr.addEventListener('sessionend', () => {
|
||||
worldRenderer.cameraObjectOverride = undefined
|
||||
worldRenderer.cameraGroupVr = undefined
|
||||
})
|
||||
|
||||
worldRenderer.abortController.signal.addEventListener('abort', disableVr)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import { armorModel } from './entity/armorModels'
|
|||
import { disposeObject } from './threeJsUtils'
|
||||
import { CursorBlock } from './world/cursorBlock'
|
||||
import { getItemUv } from './appShared'
|
||||
import { initVR } from './world/vr'
|
||||
import { Entities } from './entities'
|
||||
import { ThreeJsSound } from './threeJsSound'
|
||||
import { CameraShake } from './cameraShake'
|
||||
|
|
@ -42,7 +41,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
ambientLight = new THREE.AmbientLight(0xcc_cc_cc)
|
||||
directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.5)
|
||||
entities = new Entities(this)
|
||||
cameraObjectOverride?: THREE.Object3D // for xr
|
||||
cameraGroupVr?: THREE.Object3D
|
||||
material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.1 })
|
||||
itemsTexture: THREE.Texture
|
||||
cursorBlock = new CursorBlock(this)
|
||||
|
|
@ -91,10 +90,9 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
this.addDebugOverlay()
|
||||
this.resetScene()
|
||||
void this.init()
|
||||
void initVR(this)
|
||||
|
||||
this.soundSystem = new ThreeJsSound(this)
|
||||
this.cameraShake = new CameraShake(this.camera, this.onRender)
|
||||
this.cameraShake = new CameraShake(this, this.onRender)
|
||||
this.media = new ThreeJsMedia(this)
|
||||
// this.fountain = new Fountain(this.scene, this.scene, {
|
||||
// position: new THREE.Vector3(0, 10, 0),
|
||||
|
|
@ -106,6 +104,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
this.worldSwitchActions()
|
||||
}
|
||||
|
||||
get cameraObject () {
|
||||
return this.cameraGroupVr || this.camera
|
||||
}
|
||||
|
||||
worldSwitchActions () {
|
||||
this.onWorldSwitched.push(() => {
|
||||
// clear custom blocks
|
||||
|
|
@ -301,7 +303,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
|
||||
updateViewerPosition (pos: Vec3): void {
|
||||
this.viewerPosition = pos
|
||||
const cameraPos = this.camera.position.toArray().map(x => Math.floor(x / 16)) as [number, number, number]
|
||||
const cameraPos = this.cameraObject.position.toArray().map(x => Math.floor(x / 16)) as [number, number, number]
|
||||
this.cameraSectionPos = new Vec3(...cameraPos)
|
||||
// eslint-disable-next-line guard-for-in
|
||||
for (const key in this.sectionObjects) {
|
||||
|
|
@ -429,10 +431,8 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
}
|
||||
|
||||
setFirstPersonCamera (pos: Vec3 | null, yaw: number, pitch: number) {
|
||||
const cam = this.cameraObjectOverride || this.camera
|
||||
const yOffset = this.displayOptions.playerState.getEyeHeight()
|
||||
|
||||
this.camera = cam as THREE.PerspectiveCamera
|
||||
this.updateCamera(pos?.offset(0, yOffset, 0) ?? null, yaw, pitch)
|
||||
this.media.tryIntersectMedia()
|
||||
}
|
||||
|
|
@ -445,7 +445,11 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
// }
|
||||
|
||||
if (pos) {
|
||||
new tweenJs.Tween(this.camera.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
|
||||
if (this.renderer.xr.isPresenting) {
|
||||
pos.y -= this.camera.position.y // Fix Y position of camera in world
|
||||
}
|
||||
|
||||
new tweenJs.Tween(this.cameraObject.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start()
|
||||
// this.freeFlyState.position = pos
|
||||
}
|
||||
this.cameraShake.setBaseRotation(pitch, yaw)
|
||||
|
|
@ -495,10 +499,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style
|
||||
const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
|
||||
const cam = this.cameraGroupVr instanceof THREE.Group ? this.cameraGroupVr.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
|
||||
this.renderer.render(this.scene, cam)
|
||||
|
||||
if (this.displayOptions.inWorldRenderingConfig.showHand/* && !this.freeFlyMode */) {
|
||||
if (this.displayOptions.inWorldRenderingConfig.showHand && !this.playerState.shouldHideHand /* && !this.freeFlyMode */ && !this.renderer.xr.isPresenting) {
|
||||
this.holdingBlock.render(this.camera, this.renderer, this.ambientLight, this.directionalLight)
|
||||
this.holdingBlockLeft.render(this.camera, this.renderer, this.ambientLight, this.directionalLight)
|
||||
}
|
||||
|
|
|
|||
16
server.js
16
server.js
|
|
@ -16,9 +16,23 @@ try {
|
|||
const app = express()
|
||||
|
||||
const isProd = process.argv.includes('--prod') || process.env.NODE_ENV === 'production'
|
||||
const timeoutIndex = process.argv.indexOf('--timeout')
|
||||
let timeout = timeoutIndex > -1 && timeoutIndex + 1 < process.argv.length
|
||||
? parseInt(process.argv[timeoutIndex + 1])
|
||||
: process.env.TIMEOUT
|
||||
? parseInt(process.env.TIMEOUT)
|
||||
: 10000
|
||||
if (isNaN(timeout) || timeout < 0) {
|
||||
console.warn('Invalid timeout value provided, using default of 10000ms')
|
||||
timeout = 10000
|
||||
}
|
||||
app.use(compression())
|
||||
app.use(cors())
|
||||
app.use(netApi({ allowOrigin: '*' }))
|
||||
app.use(netApi({
|
||||
allowOrigin: '*',
|
||||
log: process.argv.includes('--log') || process.env.LOG === 'true',
|
||||
timeout
|
||||
}))
|
||||
if (!isProd) {
|
||||
app.use('/sounds', express.static(path.join(__dirname, './generated/sounds/')))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,27 @@ import { setLoadingScreenStatus } from './appStatus'
|
|||
import { setStorageDataOnAppConfigLoad } from './react/appStorageProvider'
|
||||
import { customKeymaps, updateBinds } from './controls'
|
||||
|
||||
export type CustomAction = {
|
||||
readonly type: string
|
||||
readonly input: readonly any[]
|
||||
}
|
||||
|
||||
export type ActionType = string | CustomAction
|
||||
|
||||
export type ActionHoldConfig = {
|
||||
readonly command: ActionType
|
||||
readonly longPressAction?: ActionType
|
||||
readonly duration?: number
|
||||
readonly threshold?: number
|
||||
}
|
||||
|
||||
export type MobileButtonConfig = {
|
||||
readonly label?: string
|
||||
readonly icon?: string
|
||||
readonly action?: ActionType
|
||||
readonly actionHold?: ActionType | ActionHoldConfig
|
||||
}
|
||||
|
||||
export type AppConfig = {
|
||||
// defaultHost?: string
|
||||
// defaultHostSave?: string
|
||||
|
|
@ -24,7 +45,9 @@ export type AppConfig = {
|
|||
// hideSettings?: Record<string, boolean>
|
||||
allowAutoConnect?: boolean
|
||||
splashText?: string
|
||||
splashTextFallback?: string
|
||||
pauseLinks?: Array<Array<Record<string, any>>>
|
||||
mobileButtons?: MobileButtonConfig[]
|
||||
keybindings?: Record<string, any>
|
||||
defaultLanguage?: string
|
||||
displayLanguageSelector?: boolean
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import { watchOptionsAfterWorldViewInit } from './watchOptions'
|
|||
|
||||
export interface RendererReactiveState {
|
||||
world: {
|
||||
chunksLoaded: string[]
|
||||
chunksLoaded: Set<string>
|
||||
heightmaps: Map<string, Uint8Array>
|
||||
chunksTotalNumber: number
|
||||
allChunksLoaded: boolean
|
||||
mesherWork: boolean
|
||||
|
|
@ -28,7 +29,7 @@ export interface RendererReactiveState {
|
|||
}
|
||||
export interface NonReactiveState {
|
||||
world: {
|
||||
chunksLoaded: string[]
|
||||
chunksLoaded: Set<string>
|
||||
chunksTotalNumber: number
|
||||
allChunksLoaded: boolean
|
||||
mesherWork: boolean
|
||||
|
|
@ -198,7 +199,7 @@ export class AppViewer {
|
|||
resetBackend (cleanState = false) {
|
||||
this.disconnectBackend(cleanState)
|
||||
if (this.backendLoader) {
|
||||
this.loadBackend(this.backendLoader)
|
||||
void this.loadBackend(this.backendLoader)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,8 +74,6 @@ export const onControInit = () => {
|
|||
}
|
||||
|
||||
function pointerLockChangeCallback () {
|
||||
hideNotification('pointerlockchange')
|
||||
|
||||
if (appViewer.rendererState.preventEscapeMenu) return
|
||||
if (!pointerLock.hasPointerLock && activeModalStack.length === 0 && miscUiState.gameLoaded) {
|
||||
showModal({ reactType: 'pause-screen' })
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export type ConnectOptions = {
|
|||
peerId?: string
|
||||
ignoreQs?: boolean
|
||||
onSuccessfulPlay?: () => void
|
||||
autoLoginPassword?: string
|
||||
serverIndex?: string
|
||||
authenticatedAccount?: AuthenticatedAccount | true
|
||||
peerOptions?: any
|
||||
|
|
|
|||
131
src/controls.ts
131
src/controls.ts
|
|
@ -27,7 +27,8 @@ import { onCameraMove, onControInit } from './cameraRotationControls'
|
|||
import { createNotificationProgressReporter } from './core/progressReporter'
|
||||
import { appStorage } from './react/appStorageProvider'
|
||||
import { switchGameMode } from './packetsReplay/replayPackets'
|
||||
|
||||
import { tabListState } from './react/PlayerListOverlayProvider'
|
||||
import { type ActionType, type ActionHoldConfig, type CustomAction } from './appConfig'
|
||||
|
||||
export const customKeymaps = proxy(appStorage.keybindings)
|
||||
subscribe(customKeymaps, () => {
|
||||
|
|
@ -45,6 +46,7 @@ export const contro = new ControMax({
|
|||
jump: ['Space', 'A'],
|
||||
inventory: ['KeyE', 'X'],
|
||||
drop: ['KeyQ', 'B'],
|
||||
dropStack: [null],
|
||||
sneak: ['ShiftLeft'],
|
||||
toggleSneakOrDown: [null, 'Right Stick'],
|
||||
sprint: ['ControlLeft', 'Left Stick'],
|
||||
|
|
@ -54,7 +56,7 @@ export const contro = new ControMax({
|
|||
attackDestroy: [null, 'Right Trigger'],
|
||||
interactPlace: [null, 'Left Trigger'],
|
||||
swapHands: ['KeyF'],
|
||||
selectItem: ['KeyH'], // default will be removed
|
||||
selectItem: ['KeyH'],
|
||||
rotateCameraLeft: [null],
|
||||
rotateCameraRight: [null],
|
||||
rotateCameraUp: [null],
|
||||
|
|
@ -62,6 +64,9 @@ export const contro = new ControMax({
|
|||
// ui?
|
||||
chat: [['KeyT', 'Enter']],
|
||||
command: ['Slash'],
|
||||
playersList: ['Tab'],
|
||||
debugOverlay: ['F3'],
|
||||
debugOverlayHelpMenu: [null],
|
||||
// client side
|
||||
zoom: ['KeyC'],
|
||||
viewerConsole: ['Backquote'],
|
||||
|
|
@ -76,7 +81,7 @@ export const contro = new ControMax({
|
|||
pauseMenu: [null, 'Start']
|
||||
},
|
||||
communication: {
|
||||
toggleMicrophone: ['KeyK'],
|
||||
toggleMicrophone: ['KeyM'],
|
||||
},
|
||||
advanced: {
|
||||
lockUrl: ['KeyY'],
|
||||
|
|
@ -235,6 +240,10 @@ const inModalCommand = (command: Command, pressed: boolean) => {
|
|||
if (command === 'ui.back') {
|
||||
hideCurrentModal()
|
||||
}
|
||||
if (command === 'ui.pauseMenu') {
|
||||
// hide all modals
|
||||
hideAllModals()
|
||||
}
|
||||
if (command === 'ui.leftClick' || command === 'ui.rightClick') {
|
||||
// in percent
|
||||
const { x, y } = gamepadUiCursorState
|
||||
|
|
@ -399,12 +408,45 @@ const onTriggerOrReleased = (command: Command, pressed: boolean) => {
|
|||
case 'general.zoom':
|
||||
gameAdditionalState.isZooming = pressed
|
||||
break
|
||||
case 'general.debugOverlay':
|
||||
if (pressed) {
|
||||
miscUiState.showDebugHud = !miscUiState.showDebugHud
|
||||
}
|
||||
break
|
||||
case 'general.debugOverlayHelpMenu':
|
||||
if (pressed) {
|
||||
void onF3LongPress()
|
||||
}
|
||||
break
|
||||
case 'general.rotateCameraLeft':
|
||||
case 'general.rotateCameraRight':
|
||||
case 'general.rotateCameraUp':
|
||||
case 'general.rotateCameraDown':
|
||||
cameraRotationControls.handleCommand(command, pressed)
|
||||
break
|
||||
case 'general.playersList':
|
||||
tabListState.isOpen = pressed
|
||||
break
|
||||
}
|
||||
} else if (stringStartsWith(command, 'ui')) {
|
||||
switch (command) {
|
||||
case 'ui.pauseMenu':
|
||||
if (pressed) {
|
||||
if (activeModalStack.length) {
|
||||
hideCurrentModal()
|
||||
} else {
|
||||
showModal({ reactType: 'pause-screen' })
|
||||
}
|
||||
}
|
||||
break
|
||||
case 'ui.back':
|
||||
case 'ui.toggleFullscreen':
|
||||
case 'ui.toggleMap':
|
||||
case 'ui.leftClick':
|
||||
case 'ui.rightClick':
|
||||
case 'ui.speedupCursor':
|
||||
// These are handled elsewhere
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -421,6 +463,9 @@ const alwaysPressedHandledCommand = (command: Command) => {
|
|||
if (command === 'advanced.lockUrl') {
|
||||
lockUrl()
|
||||
}
|
||||
if (command === 'communication.toggleMicrophone') {
|
||||
toggleMicrophoneMuted?.()
|
||||
}
|
||||
}
|
||||
|
||||
export function lockUrl () {
|
||||
|
|
@ -494,6 +539,9 @@ contro.on('trigger', ({ command }) => {
|
|||
case 'general.rotateCameraRight':
|
||||
case 'general.rotateCameraUp':
|
||||
case 'general.rotateCameraDown':
|
||||
case 'general.debugOverlay':
|
||||
case 'general.debugOverlayHelpMenu':
|
||||
case 'general.playersList':
|
||||
// no-op
|
||||
break
|
||||
case 'general.swapHands': {
|
||||
|
|
@ -531,6 +579,12 @@ contro.on('trigger', ({ command }) => {
|
|||
}
|
||||
break
|
||||
}
|
||||
case 'general.dropStack': {
|
||||
if (bot.heldItem) {
|
||||
void bot.tossStack(bot.heldItem)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'general.chat':
|
||||
showModal({ reactType: 'chat' })
|
||||
break
|
||||
|
|
@ -557,14 +611,6 @@ contro.on('trigger', ({ command }) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (command === 'communication.toggleMicrophone') {
|
||||
// toggleMicrophoneMuted()
|
||||
}
|
||||
|
||||
if (command === 'ui.pauseMenu') {
|
||||
showModal({ reactType: 'pause-screen' })
|
||||
}
|
||||
|
||||
if (command === 'ui.toggleFullscreen') {
|
||||
void goFullscreen(true)
|
||||
}
|
||||
|
|
@ -714,30 +760,18 @@ export const f3Keybinds: Array<{
|
|||
}
|
||||
]
|
||||
|
||||
const hardcodedPressedKeys = new Set<string>()
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!isGameActive(false)) return
|
||||
if (hardcodedPressedKeys.has('F3')) {
|
||||
if (contro.pressedKeys.has('F3')) {
|
||||
const keybind = f3Keybinds.find((v) => v.key === e.code)
|
||||
if (keybind && (keybind.enabled?.() ?? true)) {
|
||||
void keybind.action()
|
||||
e.stopPropagation()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
hardcodedPressedKeys.add(e.code)
|
||||
}, {
|
||||
capture: true,
|
||||
})
|
||||
document.addEventListener('keyup', (e) => {
|
||||
hardcodedPressedKeys.delete(e.code)
|
||||
})
|
||||
document.addEventListener('visibilitychange', (e) => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
hardcodedPressedKeys.clear()
|
||||
}
|
||||
})
|
||||
|
||||
const isFlying = () => (bot.entity as any).flying
|
||||
|
||||
|
|
@ -893,3 +927,52 @@ export function updateBinds (commands: any) {
|
|||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export const onF3LongPress = async () => {
|
||||
const select = await showOptionsModal('', f3Keybinds.filter(f3Keybind => {
|
||||
return f3Keybind.mobileTitle && (f3Keybind.enabled?.() ?? true)
|
||||
}).map(f3Keybind => {
|
||||
return `${f3Keybind.mobileTitle}${f3Keybind.key ? ` (F3+${f3Keybind.key})` : ''}`
|
||||
}))
|
||||
if (!select) return
|
||||
const f3Keybind = f3Keybinds.find(f3Keybind => f3Keybind.mobileTitle === select)
|
||||
if (f3Keybind) void f3Keybind.action()
|
||||
}
|
||||
|
||||
export const handleMobileButtonCustomAction = (action: CustomAction) => {
|
||||
const handler = customCommandsConfig[action.type]?.handler
|
||||
if (handler) {
|
||||
handler([...action.input])
|
||||
}
|
||||
}
|
||||
|
||||
export const handleMobileButtonActionCommand = (command: ActionType | ActionHoldConfig, isDown: boolean) => {
|
||||
const commandValue = typeof command === 'string' ? command : 'command' in command ? command.command : command
|
||||
|
||||
if (typeof commandValue === 'string' && !stringStartsWith(commandValue, 'custom')) {
|
||||
const event: CommandEventArgument<typeof contro['_commandsRaw']> = {
|
||||
command: commandValue as Command,
|
||||
schema: {
|
||||
keys: [],
|
||||
gamepad: []
|
||||
}
|
||||
}
|
||||
if (isDown) {
|
||||
contro.emit('trigger', event)
|
||||
} else {
|
||||
contro.emit('release', event)
|
||||
}
|
||||
} else if (typeof commandValue === 'object') {
|
||||
if (isDown) {
|
||||
handleMobileButtonCustomAction(commandValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const handleMobileButtonLongPress = (actionHold: ActionHoldConfig) => {
|
||||
if (typeof actionHold.longPressAction === 'string' && actionHold.longPressAction === 'general.debugOverlayHelpMenu') {
|
||||
void onF3LongPress()
|
||||
} else if (actionHold.longPressAction) {
|
||||
handleMobileButtonActionCommand(actionHold.longPressAction, true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ const updateAutoJump = () => {
|
|||
if (!bot?.autoJumper) return
|
||||
const autoJump = options.autoParkour || (options.autoJump === 'auto' ? miscUiState.currentTouch && !miscUiState.usingGamepadInput : options.autoJump === 'always')
|
||||
bot.autoJumper.setOpts({
|
||||
jumpIntoWater: options.autoParkour,
|
||||
// jumpIntoWater: options.autoParkour,
|
||||
jumpOnAllEdges: options.autoParkour,
|
||||
// strictBlockCollision: true,
|
||||
})
|
||||
|
|
@ -61,7 +61,7 @@ customEvents.on('gameLoaded', () => {
|
|||
if (!tracking) continue
|
||||
const e = bot.entities[id]
|
||||
if (!e) continue
|
||||
const speed = info.avgSpeed
|
||||
const speed = info.avgVel
|
||||
const WALKING_SPEED = 0.03
|
||||
const SPRINTING_SPEED = 0.18
|
||||
const isCrouched = e['crouching']
|
||||
|
|
|
|||
|
|
@ -18,9 +18,10 @@ export function nameToMcOfflineUUID (name) {
|
|||
}
|
||||
|
||||
export async function savePlayers (autoSave: boolean) {
|
||||
if (!localServer?.players[0]) return
|
||||
if (autoSave && new URL(location.href).searchParams.get('noSave') === 'true') return
|
||||
//@ts-expect-error TODO
|
||||
await localServer!.savePlayersSingleplayer()
|
||||
await localServer.savePlayersSingleplayer()
|
||||
}
|
||||
|
||||
// todo flying squid should expose save function instead
|
||||
|
|
|
|||
|
|
@ -35,3 +35,12 @@ window.addEventListener('beforeunload', (event) => {
|
|||
event.returnValue = '' // Required for some browsers
|
||||
return 'The game is running. Are you sure you want to close this page?'
|
||||
})
|
||||
|
||||
window.addEventListener('contextmenu', (e) => {
|
||||
const ALLOW_TAGS = ['INPUT', 'TEXTAREA', 'A']
|
||||
// allow if target is in ALLOW_TAGS or has selection text
|
||||
if (ALLOW_TAGS.includes((e.target as HTMLElement)?.tagName) || window.getSelection()?.toString()) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export const miscUiState = proxy({
|
|||
/** wether game hud is shown (in playing state) */
|
||||
gameLoaded: false,
|
||||
showUI: true,
|
||||
showDebugHud: false,
|
||||
loadedServerIndex: '',
|
||||
/** currently trying to load or loaded mc version, after all data is loaded */
|
||||
loadedDataVersion: null as string | null,
|
||||
|
|
|
|||
6
src/globals.d.ts
vendored
6
src/globals.d.ts
vendored
|
|
@ -27,8 +27,14 @@ declare const customEvents: import('typed-emitter').default<{
|
|||
search (q: string): void
|
||||
activateItem (item: Item, slot: number, offhand: boolean): void
|
||||
hurtAnimation (yaw?: number): void
|
||||
customChannelRegister (channel: string, parser: any): void
|
||||
}>
|
||||
declare const beforeRenderFrame: Array<() => void>
|
||||
declare const translate: <T extends string | undefined>(key: T) => T
|
||||
|
||||
// API LAYER
|
||||
declare const toggleMicrophoneMuted: undefined | (() => void)
|
||||
declare const translateText: undefined | ((text: string) => string)
|
||||
|
||||
declare interface Document {
|
||||
exitPointerLock?(): void
|
||||
|
|
|
|||
|
|
@ -9,3 +9,7 @@ window.viewer = undefined
|
|||
window.loadedData = undefined
|
||||
window.customEvents = new EventEmitter()
|
||||
window.customEvents.setMaxListeners(10_000)
|
||||
window.translate = (key) => {
|
||||
if (typeof key !== 'string') return key
|
||||
return window.translateText?.(key) ?? key
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ import './water'
|
|||
import { ConnectOptions, getVersionAutoSelect, downloadOtherGameData, downloadAllMinecraftData } from './connect'
|
||||
import { ref, subscribe } from 'valtio'
|
||||
import { signInMessageState } from './react/SignInMessageProvider'
|
||||
import { updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage'
|
||||
import { findServerPassword, updateAuthenticatedAccountData, updateLoadedServerData, updateServerConnectionHistory } from './react/serversStorage'
|
||||
import { mainMenuState } from './react/MainMenuRenderApp'
|
||||
import './mobileShim'
|
||||
import { parseFormattedMessagePacket } from './botUtils'
|
||||
|
|
@ -708,7 +708,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
resolve()
|
||||
unsub()
|
||||
} else {
|
||||
const perc = Math.round(appViewer.rendererState.world.chunksLoaded.length / appViewer.rendererState.world.chunksTotalNumber * 100)
|
||||
const perc = Math.round(appViewer.rendererState.world.chunksLoaded.size / appViewer.rendererState.world.chunksTotalNumber * 100)
|
||||
progress?.reportProgress('chunks', perc / 100)
|
||||
}
|
||||
})
|
||||
|
|
@ -754,9 +754,10 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
}
|
||||
connectOptions.onSuccessfulPlay?.()
|
||||
updateDataAfterJoin()
|
||||
if (connectOptions.autoLoginPassword) {
|
||||
const password = findServerPassword()
|
||||
if (password) {
|
||||
setTimeout(() => {
|
||||
bot.chat(`/login ${connectOptions.autoLoginPassword}`)
|
||||
bot.chat(`/login ${password}`)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1318,55 +1318,47 @@
|
|||
13
|
||||
],
|
||||
"lever": {
|
||||
"face=ceiling,facing=east": [
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
12,
|
||||
6,
|
||||
11
|
||||
],
|
||||
"face=ceiling,facing=north": [
|
||||
5,
|
||||
0,
|
||||
4,
|
||||
11,
|
||||
6,
|
||||
12
|
||||
],
|
||||
"face=ceiling,facing=south": [
|
||||
5,
|
||||
0,
|
||||
4,
|
||||
11,
|
||||
6,
|
||||
12
|
||||
],
|
||||
"face=ceiling,facing=west": [
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
12,
|
||||
6,
|
||||
11
|
||||
],
|
||||
"face=floor,facing=east": [
|
||||
4,
|
||||
10,
|
||||
0,
|
||||
5,
|
||||
12,
|
||||
16,
|
||||
6,
|
||||
11
|
||||
],
|
||||
"face=floor,facing=north": [
|
||||
5,
|
||||
10,
|
||||
0,
|
||||
4,
|
||||
11,
|
||||
16,
|
||||
6,
|
||||
12
|
||||
],
|
||||
"face=floor,facing=south": [
|
||||
5,
|
||||
0,
|
||||
4,
|
||||
11,
|
||||
6,
|
||||
12
|
||||
],
|
||||
"face=floor,facing=west": [
|
||||
4,
|
||||
0,
|
||||
5,
|
||||
12,
|
||||
6,
|
||||
11
|
||||
],
|
||||
"face=ceiling,facing=east": [
|
||||
4,
|
||||
10,
|
||||
5,
|
||||
12,
|
||||
16,
|
||||
11
|
||||
],
|
||||
"face=ceiling,facing=north": [
|
||||
5,
|
||||
10,
|
||||
4,
|
||||
|
|
@ -1374,7 +1366,15 @@
|
|||
16,
|
||||
12
|
||||
],
|
||||
"face=floor,facing=west": [
|
||||
"face=ceiling,facing=south": [
|
||||
5,
|
||||
10,
|
||||
4,
|
||||
11,
|
||||
16,
|
||||
12
|
||||
],
|
||||
"face=ceiling,facing=west": [
|
||||
4,
|
||||
10,
|
||||
5,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, Re
|
|||
const loadedImagesCache = new Map<string, HTMLImageElement>()
|
||||
const cleanLoadedImagesCache = () => {
|
||||
loadedImagesCache.delete('blocks')
|
||||
loadedImagesCache.delete('items')
|
||||
}
|
||||
|
||||
let lastWindow: ReturnType<typeof showInventory>
|
||||
|
|
@ -120,6 +121,7 @@ export const onGameLoad = () => {
|
|||
if (!appViewer.resourcesManager['_inventoryChangeTracked']) {
|
||||
appViewer.resourcesManager['_inventoryChangeTracked'] = true
|
||||
const texturesChanged = () => {
|
||||
cleanLoadedImagesCache()
|
||||
if (!lastWindow) return
|
||||
upWindowItemsLocal()
|
||||
upJei(lastJeiSearch)
|
||||
|
|
@ -189,7 +191,6 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal
|
|||
image?: HTMLImageElement
|
||||
} | undefined => {
|
||||
let itemModelName = model.modelName
|
||||
const originalItemName = itemModelName
|
||||
const isItem = loadedData.itemsByName[itemModelName]
|
||||
|
||||
// #region normalize item name
|
||||
|
|
@ -225,7 +226,7 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal
|
|||
?? (model.originalItemName ? appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture(model.originalItemName, {}, false, fullBlockModelSupport) : undefined)
|
||||
?? appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture('item/missing_texture')!
|
||||
} catch (err) {
|
||||
inGameError(`Failed to render item ${itemModelName} (original: ${originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`)
|
||||
inGameError(`Failed to render item ${itemModelName} (original: ${model.originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`)
|
||||
itemTexture = blockToTopTexture(appViewer.resourcesManager.currentResources!.itemsRenderer.getItemTexture('errored')!)
|
||||
}
|
||||
|
||||
|
|
@ -339,6 +340,7 @@ const implementedContainersGuiMap = {
|
|||
'minecraft:generic_3x3': 'DropDispenseWin',
|
||||
'minecraft:furnace': 'FurnaceWin',
|
||||
'minecraft:smoker': 'FurnaceWin',
|
||||
'minecraft:blast_furnace': 'FurnaceWin',
|
||||
'minecraft:crafting': 'CraftingWin',
|
||||
'minecraft:crafting3x3': 'CraftingWin', // todo different result slot
|
||||
'minecraft:anvil': 'AnvilWin',
|
||||
|
|
|
|||
|
|
@ -92,7 +92,8 @@ export const getItemMetadata = (item: GeneralInputItem, resourcesManager: Resour
|
|||
|
||||
|
||||
export const getItemNameRaw = (item: Pick<import('prismarine-item').Item, 'nbt'> | null, resourcesManager: ResourcesManager) => {
|
||||
const { customText } = getItemMetadata(item as any, resourcesManager)
|
||||
if (!item) return ''
|
||||
const { customText } = getItemMetadata(item as GeneralInputItem, resourcesManager)
|
||||
if (!customText) return
|
||||
try {
|
||||
if (typeof customText === 'object') {
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ import clientAutoVersion from 'minecraft-protocol/src/client/autoVersion'
|
|||
|
||||
export const pingServerVersion = async (ip: string, port?: number, mergeOptions: Record<string, any> = {}) => {
|
||||
const fakeClient = new EventEmitter() as any
|
||||
fakeClient.on('error', (err) => {
|
||||
throw new Error(err.message ?? err)
|
||||
})
|
||||
const options = {
|
||||
host: ip,
|
||||
port,
|
||||
noPongTimeout: Infinity, // disable timeout
|
||||
noPongTimeout: 10_000,
|
||||
closeTimeout: 20_000,
|
||||
...mergeOptions,
|
||||
}
|
||||
let latency = 0
|
||||
|
|
@ -19,12 +17,26 @@ export const pingServerVersion = async (ip: string, port?: number, mergeOptions:
|
|||
fullInfo = res
|
||||
}]
|
||||
|
||||
// TODO! use client.socket.destroy() instead of client.end() for faster cleanup
|
||||
await clientAutoVersion(fakeClient, options)
|
||||
// TODO use client.socket.destroy() instead of client.end() for faster cleanup
|
||||
clientAutoVersion(fakeClient, options)
|
||||
await Promise.race([
|
||||
new Promise<void>((resolve, reject) => {
|
||||
fakeClient.once('connect_allowed', () => {
|
||||
resolve()
|
||||
})
|
||||
}),
|
||||
new Promise<void>((resolve, reject) => {
|
||||
fakeClient.on('error', (err) => {
|
||||
reject(new Error(err.message ?? err))
|
||||
})
|
||||
if (mergeOptions.stream) {
|
||||
mergeOptions.stream.on('end', (err) => {
|
||||
reject(new Error('Connection closed'))
|
||||
})
|
||||
}
|
||||
})
|
||||
])
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
fakeClient.once('connect_allowed', resolve)
|
||||
})
|
||||
return {
|
||||
version: fakeClient.version,
|
||||
latency,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ export class PlayerStateManager implements IPlayerState {
|
|||
private itemUsageTicks = 0
|
||||
private isUsingItem = false
|
||||
private ready = false
|
||||
public lightingDisabled = false
|
||||
onlineMode = false
|
||||
get username () {
|
||||
return bot.username ?? ''
|
||||
|
|
@ -51,6 +52,21 @@ export class PlayerStateManager implements IPlayerState {
|
|||
}
|
||||
|
||||
private botCreated () {
|
||||
const handleDimensionData = (data) => {
|
||||
let hasSkyLight = 1
|
||||
try {
|
||||
hasSkyLight = data.dimension.value.has_skylight.value
|
||||
} catch {}
|
||||
this.lightingDisabled = bot.game.dimension === 'the_nether' || bot.game.dimension === 'the_end' || !hasSkyLight
|
||||
}
|
||||
|
||||
bot._client.on('login', (packet) => {
|
||||
handleDimensionData(packet)
|
||||
})
|
||||
bot._client.on('respawn', (packet) => {
|
||||
handleDimensionData(packet)
|
||||
})
|
||||
|
||||
// Movement tracking
|
||||
bot.on('move', this.updateState)
|
||||
|
||||
|
|
@ -75,6 +91,10 @@ export class PlayerStateManager implements IPlayerState {
|
|||
this.reactive.gameMode = bot.game?.gameMode
|
||||
}
|
||||
|
||||
get shouldHideHand () {
|
||||
return this.reactive.gameMode === 'spectator'
|
||||
}
|
||||
|
||||
// #region Movement and Physics State
|
||||
private updateState () {
|
||||
if (!bot?.entity || this.disableStateUpdates) return
|
||||
|
|
@ -118,7 +138,7 @@ export class PlayerStateManager implements IPlayerState {
|
|||
}
|
||||
|
||||
getEyeHeight (): number {
|
||||
return bot.controlState.sneak ? 1.27 : 1.62
|
||||
return bot.controlState.sneak && !this.isFlying() ? 1.27 : 1.62
|
||||
}
|
||||
|
||||
isOnGround (): boolean {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { sendVideoInteraction, videoCursorInteraction } from '../../customChanne
|
|||
|
||||
function cursorBlockDisplay (bot: Bot) {
|
||||
const updateCursorBlock = (data?: { block: Block }) => {
|
||||
if (!data?.block) {
|
||||
if (!data?.block || bot.game.gameMode === 'spectator') {
|
||||
playerState.reactive.lookingAtBlock = undefined
|
||||
return
|
||||
}
|
||||
|
|
@ -27,6 +27,10 @@ function cursorBlockDisplay (bot: Bot) {
|
|||
}
|
||||
|
||||
bot.on('highlightCursorBlock', updateCursorBlock)
|
||||
bot.on('game', () => {
|
||||
const block = bot.mouse.getCursorState().cursorBlock
|
||||
updateCursorBlock(block ? { block } : undefined)
|
||||
})
|
||||
|
||||
bot.on('blockBreakProgressStage', (block, stage) => {
|
||||
const mergedShape = bot.mouse.getMergedCursorShape(block)
|
||||
|
|
|
|||
|
|
@ -33,10 +33,14 @@ export const getWebsocketStream = async (host: string) => {
|
|||
ws.addEventListener('close', () => {
|
||||
console.log('ws closed')
|
||||
clientDuplex.end()
|
||||
setTimeout(() => {
|
||||
clientDuplex.emit('end', 'Connection lost')
|
||||
}, 500)
|
||||
})
|
||||
|
||||
ws.addEventListener('error', err => {
|
||||
console.log('ws error', err)
|
||||
clientDuplex.emit('error', err)
|
||||
})
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
|
|
|
|||
|
|
@ -90,8 +90,7 @@ export const guiOptionsScheme: {
|
|||
},
|
||||
lowMemoryMode: {
|
||||
text: 'Low Memory Mode',
|
||||
enableWarning: 'Enabling it will make chunks load ~4x slower',
|
||||
disabledDuringGame: true
|
||||
enableWarning: 'Enabling it will make chunks load ~4x slower. When in the game, app needs to be reloaded to apply this setting.',
|
||||
},
|
||||
starfieldRendering: {},
|
||||
renderEntities: {},
|
||||
|
|
@ -286,6 +285,20 @@ export const guiOptionsScheme: {
|
|||
chatSelect: {
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Category>Map</Category>
|
||||
},
|
||||
showMinimap: {
|
||||
text: 'Enable Minimap',
|
||||
enableWarning: 'App reload is required to apply this setting',
|
||||
values: [
|
||||
'always',
|
||||
'singleplayer',
|
||||
'never'
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Category>World</Category>
|
||||
|
|
@ -321,19 +334,6 @@ export const guiOptionsScheme: {
|
|||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Category>Map</Category>
|
||||
},
|
||||
showMinimap: {
|
||||
text: 'Enable Minimap',
|
||||
values: [
|
||||
'always',
|
||||
'singleplayer',
|
||||
'never'
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
return <Category>Experimental</Category>
|
||||
|
|
@ -488,7 +488,11 @@ export const guiOptionsScheme: {
|
|||
</>
|
||||
)
|
||||
},
|
||||
vrSupport: {}
|
||||
vrSupport: {},
|
||||
vrPageGameRendering: {
|
||||
text: 'Page Game Rendering',
|
||||
tooltip: 'Wether to continue rendering page even when vr is active.',
|
||||
}
|
||||
},
|
||||
],
|
||||
advanced: [
|
||||
|
|
@ -555,6 +559,7 @@ export const guiOptionsScheme: {
|
|||
{
|
||||
preventBackgroundTimeoutKick: {},
|
||||
preventSleep: {
|
||||
text: 'Prevent Device Sleep',
|
||||
disabledReason: navigator.wakeLock ? undefined : 'Your browser does not support wake lock API',
|
||||
enableWarning: 'When connected to a server, prevent PC from sleeping or screen dimming. Useful for purpusely staying AFK for long time. Some events might still prevent this like loosing tab focus or going low power mode.',
|
||||
},
|
||||
|
|
@ -605,6 +610,10 @@ export const guiOptionsScheme: {
|
|||
debugResponseTimeIndicator: {
|
||||
text: 'Debug Input Lag',
|
||||
},
|
||||
},
|
||||
{
|
||||
debugChatScroll: {
|
||||
},
|
||||
}
|
||||
],
|
||||
'export-import': [
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ const defaultOptions = {
|
|||
preventBackgroundTimeoutKick: false,
|
||||
preventSleep: false,
|
||||
debugContro: false,
|
||||
debugChatScroll: false,
|
||||
chatVanillaRestrictions: true,
|
||||
debugResponseTimeIndicator: false,
|
||||
// antiAliasing: false,
|
||||
|
|
@ -103,7 +104,8 @@ const defaultOptions = {
|
|||
autoJump: 'auto' as 'auto' | 'always' | 'never',
|
||||
autoParkour: false,
|
||||
vrSupport: true, // doesn't directly affect the VR mode, should only disable the button which is annoying to android users
|
||||
renderDebug: (isDev ? 'advanced' : 'basic') as 'none' | 'advanced' | 'basic',
|
||||
vrPageGameRendering: false,
|
||||
renderDebug: 'basic' as 'none' | 'advanced' | 'basic',
|
||||
|
||||
// advanced bot options
|
||||
autoRespawn: false,
|
||||
|
|
|
|||
|
|
@ -46,8 +46,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
|||
const parsedInitialIp = parseServerAddress(initialData?.ip)
|
||||
|
||||
const [serverName, setServerName] = React.useState(initialData?.name ?? qsParamName ?? '')
|
||||
const [serverIp, setServerIp] = React.useState(parsedQsIp.host || parsedInitialIp.host || '')
|
||||
const [serverPort, setServerPort] = React.useState(parsedQsIp.port || parsedInitialIp.port || '')
|
||||
const [serverIp, setServerIp] = React.useState(parsedQsIp.serverIpFull || parsedInitialIp.serverIpFull || '')
|
||||
const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? /* legacy */ initialData?.['version'] ?? qsParamVersion ?? '')
|
||||
const [proxyOverride, setProxyOverride] = React.useState(initialData?.proxyOverride ?? qsParamProxy ?? '')
|
||||
const [usernameOverride, setUsernameOverride] = React.useState(initialData?.usernameOverride ?? qsParamUsername ?? '')
|
||||
|
|
@ -61,7 +60,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
|||
const noAccountSelected = accountIndex === -1
|
||||
const authenticatedAccountOverride = noAccountSelected ? undefined : freshAccount ? true : accounts?.[accountIndex]
|
||||
|
||||
let ipFinal = serverIp.includes(':') ? serverIp : `${serverIp}${serverPort ? `:${serverPort}` : ''}`
|
||||
let ipFinal = serverIp
|
||||
ipFinal = ipFinal.replace(/:$/, '')
|
||||
const commonUseOptions: BaseServerInfo = {
|
||||
name: serverName,
|
||||
|
|
@ -126,6 +125,9 @@ 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']
|
||||
// pick random example
|
||||
const example = serverExamples[Math.floor(Math.random() * serverExamples.length)]
|
||||
|
||||
return <Screen title={qsParamIp ? 'Connect to Server' : title} backdrop>
|
||||
<form
|
||||
|
|
@ -149,11 +151,6 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
|||
})
|
||||
}}
|
||||
>
|
||||
{!lockConnect && <>
|
||||
<div style={{ gridColumn: smallWidth ? '' : 'span 2', display: 'flex', justifyContent: 'center' }}>
|
||||
<InputWithLabel label="Server Name" value={serverName} onChange={({ target: { value } }) => setServerName(value)} placeholder='Defaults to IP' />
|
||||
</div>
|
||||
</>}
|
||||
<InputWithLabel
|
||||
required
|
||||
label="Server IP"
|
||||
|
|
@ -165,8 +162,13 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
|||
setServerOnline(false)
|
||||
}}
|
||||
validateInput={serverOnline === null || fetchedServerInfoIp !== serverIp ? undefined : validateServerIp}
|
||||
placeholder={example}
|
||||
/>
|
||||
<InputWithLabel label="Server Port" value={serverPort} disabled={lockConnect && parsedQsIp.port !== null} onChange={({ target: { value } }) => setServerPort(value)} placeholder={serverIp.startsWith('ws://') || serverIp.startsWith('wss://') ? '' : '25565'} />
|
||||
{!lockConnect && <>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<InputWithLabel label="Server Name" value={serverName} onChange={({ target: { value } }) => setServerName(value)} placeholder='Defaults to IP' />
|
||||
</div>
|
||||
</>}
|
||||
{isSmallHeight ? <div style={{ gridColumn: 'span 2', marginTop: 10, }} /> : <div style={{ gridColumn: smallWidth ? '' : 'span 2' }}>Overrides:</div>}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ type Props = {
|
|||
inputDisabled?: string
|
||||
placeholder?: string
|
||||
chatVanillaRestrictions?: boolean
|
||||
debugChatScroll?: boolean
|
||||
}
|
||||
|
||||
export const chatInputValueGlobal = proxy({
|
||||
|
|
@ -69,7 +70,8 @@ export default ({
|
|||
allowSelection,
|
||||
inputDisabled,
|
||||
placeholder,
|
||||
chatVanillaRestrictions
|
||||
chatVanillaRestrictions,
|
||||
debugChatScroll
|
||||
}: Props) => {
|
||||
const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]'))
|
||||
const [isInputFocused, setIsInputFocused] = useState(false)
|
||||
|
|
@ -86,7 +88,16 @@ export default ({
|
|||
const chatHistoryPos = useRef(sendHistoryRef.current.length)
|
||||
const inputCurrentlyEnteredValue = useRef('')
|
||||
|
||||
const { scrollToBottom } = useScrollBehavior(chatMessages, { messages, opened })
|
||||
const { scrollToBottom, isAtBottom, wasAtBottom, currentlyAtBottom } = useScrollBehavior(chatMessages, { messages, opened })
|
||||
const [rightNowAtBottom, setRightNowAtBottom] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!debugChatScroll) return
|
||||
const interval = setInterval(() => {
|
||||
setRightNowAtBottom(isAtBottom())
|
||||
}, 50)
|
||||
return () => clearInterval(interval)
|
||||
}, [debugChatScroll])
|
||||
|
||||
const setSendHistory = (newHistory: string[]) => {
|
||||
sendHistoryRef.current = newHistory
|
||||
|
|
@ -252,6 +263,55 @@ export default ({
|
|||
}}
|
||||
>
|
||||
{opacity && <div ref={chatMessages} className={`chat ${opened ? 'opened' : ''}`} id="chat-messages" style={{ opacity }}>
|
||||
{debugChatScroll && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 5,
|
||||
left: 5,
|
||||
display: 'flex',
|
||||
gap: 4,
|
||||
zIndex: 100,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
title="Right now is at bottom (updated every 50ms)"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: rightNowAtBottom ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
title="Currently at bottom"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: currentlyAtBottom ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
title="Was at bottom"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: wasAtBottom() ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
title="Chat opened"
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
backgroundColor: opened ? '#00ff00' : '#ff0000',
|
||||
border: '1px solid #fff',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{messages.map((m) => (
|
||||
<MessageLine key={reactKeyForMessage(m)} message={m} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { viewerVersionState } from '../viewerConnector'
|
|||
import Chat, { Message, fadeMessage } from './Chat'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import { hideNotification, notificationProxy, showNotification } from './NotificationProvider'
|
||||
import { updateLoadedServerData } from './serversStorage'
|
||||
import { getServerIndex, updateLoadedServerData } from './serversStorage'
|
||||
import { lastConnectOptions } from './AppStatusProvider'
|
||||
import { showOptionsModal } from './SelectOption'
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ export default () => {
|
|||
const isChatActive = useIsModalActive('chat')
|
||||
const lastMessageId = useRef(0)
|
||||
const usingTouch = useSnapshot(miscUiState).currentTouch
|
||||
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions } = useSnapshot(options)
|
||||
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions, debugChatScroll } = useSnapshot(options)
|
||||
const isUsingMicrosoftAuth = useMemo(() => !!lastConnectOptions.value?.authenticatedAccount, [])
|
||||
const { forwardChat } = useSnapshot(viewerVersionState)
|
||||
const { viewerConnection } = useSnapshot(gameAdditionalState)
|
||||
|
|
@ -48,6 +48,7 @@ export default () => {
|
|||
|
||||
return <Chat
|
||||
chatVanillaRestrictions={chatVanillaRestrictions}
|
||||
debugChatScroll={debugChatScroll}
|
||||
allowSelection={chatSelect}
|
||||
usingTouch={!!usingTouch}
|
||||
opacity={(isChatActive ? chatOpacityOpened : chatOpacity) / 100}
|
||||
|
|
@ -56,13 +57,13 @@ export default () => {
|
|||
placeholder={forwardChat || !viewerConnection ? undefined : 'Chat forwarding is not enabled in the plugin settings'}
|
||||
sendMessage={async (message) => {
|
||||
const builtinHandled = tryHandleBuiltinCommand(message)
|
||||
if (miscUiState.loadedServerIndex && (message.startsWith('/login') || message.startsWith('/register'))) {
|
||||
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]
|
||||
server.autoLogin[bot.username] = password
|
||||
return server
|
||||
return { ...server }
|
||||
})
|
||||
hideNotification()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const Inner = () => {
|
|||
state,
|
||||
lines: [String(chunk?.loads.length ?? 0)],
|
||||
sidebarLines: [
|
||||
`loads: ${chunk.loads.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`,
|
||||
`loads: ${chunk.loads?.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`,
|
||||
// `blockUpdates: ${chunk.blockUpdates}`,
|
||||
],
|
||||
}
|
||||
|
|
|
|||
84
src/react/CreditsAboutModal.module.css
Normal file
84
src/react/CreditsAboutModal.module.css
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
.modalScreen {
|
||||
margin-top: -15px;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
background-color: #eee0c3;
|
||||
border: 5px solid #7A5C3E;
|
||||
padding: 15px;
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
color: #3F2A14;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.contentWrapper {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 6px;
|
||||
margin-bottom: 4px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
font-size: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
.listItem {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #0000AA;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
margin-top: 7px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.closeButton {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.closeButton:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.closeButton:focus-visible {
|
||||
outline: 1px dashed #7A5C3E;
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
color: #3F2A14;
|
||||
}
|
||||
18
src/react/CreditsAboutModal.module.css.d.ts
vendored
Normal file
18
src/react/CreditsAboutModal.module.css.d.ts
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
closeButton: string;
|
||||
closeIcon: string;
|
||||
container: string;
|
||||
contentWrapper: string;
|
||||
link: string;
|
||||
list: string;
|
||||
listItem: string;
|
||||
modalScreen: string;
|
||||
paragraph: string;
|
||||
sectionTitle: string;
|
||||
subtitle: string;
|
||||
title: string;
|
||||
}
|
||||
declare const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
57
src/react/CreditsAboutModal.tsx
Normal file
57
src/react/CreditsAboutModal.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { hideCurrentModal } from '../globalState'
|
||||
import { useIsModalActive } from './utilsApp'
|
||||
import Screen from './Screen'
|
||||
import PixelartIcon, { pixelartIcons } from './PixelartIcon'
|
||||
import styles from './CreditsAboutModal.module.css'
|
||||
|
||||
export default () => {
|
||||
const isModalActive = useIsModalActive('credits-about')
|
||||
|
||||
if (!isModalActive) return null
|
||||
|
||||
return (
|
||||
<Screen
|
||||
title=""
|
||||
backdrop
|
||||
className={styles.modalScreen}>
|
||||
<div className={styles.container}>
|
||||
<h2 className={styles.title}>Minecraft Open Source JS Edition</h2>
|
||||
|
||||
<div className={styles.contentWrapper}>
|
||||
<small className={styles.subtitle}><i>What if Minecraft was an online game?</i></small>
|
||||
<p className={styles.paragraph}>
|
||||
Hey! You are on the safest modern Minecraft clone rewritten in JavaScript. A huge amount of work has gone into this project to make it fast and complete, and many features would not be possible without these awesome people and projects:
|
||||
</p>
|
||||
<ul className={styles.list}>
|
||||
<li className={styles.listItem}>- Everyone who provided awesome mods for the game</li>
|
||||
<li className={styles.listItem}>- <span><a className={styles.link} href="https://discord.com/users/gen6442" target="_blank" rel="noopener noreferrer">[Gen]</a></span> for rewriting the physics engine to be Grim-compliant</li>
|
||||
<li className={styles.listItem}>- <span><a className={styles.link} href="https://viaversion.com/" target="_blank" rel="noopener noreferrer">[ViaVersion]</a></span> for providing reliable sound id mappings</li>
|
||||
<li className={styles.listItem}>- <span><a className={styles.link} href="https://github.com/BlueMap-Minecraft/BlueMap" target="_blank" rel="noopener noreferrer">[Bluemap]</a></span> for providing block entity models like chest</li>
|
||||
<li className={styles.listItem}>- <span><a className={styles.link} href="https://github.com/misode/deepslate" target="_blank" rel="noopener noreferrer">[Deepslate]</a></span> for rendering 3d blocks in GUI (inventory)</li>
|
||||
<li className={styles.listItem}>- <span><a className={styles.link} href="https://www.npmjs.com/package/skinview3d" target="_blank" rel="noopener noreferrer">[skinview3d]</a></span> for rendering skins & player geometry</li>
|
||||
<li className={styles.listItem}>- <span><a className={styles.link} href="https://github.com/atxi/Polymer" target="_blank" rel="noopener noreferrer">[Polymer]</a></span> (c++ project) for providing fast & accurate server light implementation</li>
|
||||
</ul>
|
||||
|
||||
<h3 className={styles.sectionTitle}>Current contributors:</h3>
|
||||
<ul className={styles.list}>
|
||||
<li className={styles.listItem}>- <span className={styles.link}><a className={styles.link} href="https://github.com/mgDentist" target="_blank" rel="noopener noreferrer">Maxim Grigorev</a></span> - React UI & Core Developer Maintainer</li>
|
||||
<li className={styles.listItem}>- And many more community contributors!</li>
|
||||
</ul>
|
||||
|
||||
<button
|
||||
className={styles.closeButton}
|
||||
onClick={() => hideCurrentModal()}
|
||||
aria-label="Close"
|
||||
type="button"
|
||||
>
|
||||
<PixelartIcon
|
||||
iconName={pixelartIcons.close}
|
||||
width={12}
|
||||
className={styles.closeIcon}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Screen>
|
||||
)
|
||||
}
|
||||
27
src/react/CreditsBookButton.module.css
Normal file
27
src/react/CreditsBookButton.module.css
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
.creditsButton {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: -30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.creditsButton:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.creditsButton:focus:not(:hover) {
|
||||
outline: 1px solid #fff;
|
||||
}
|
||||
|
||||
.creditsButton svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
7
src/react/CreditsBookButton.module.css.d.ts
vendored
Normal file
7
src/react/CreditsBookButton.module.css.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
creditsButton: string;
|
||||
}
|
||||
declare const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
22
src/react/CreditsBookButton.tsx
Normal file
22
src/react/CreditsBookButton.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { showModal } from '../globalState'
|
||||
import styles from './CreditsBookButton.module.css'
|
||||
|
||||
export default () => {
|
||||
const handleClick = () => {
|
||||
showModal({ reactType: 'credits-about' })
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={styles.creditsButton}
|
||||
onClick={handleClick}
|
||||
aria-label="Credits"
|
||||
title="Credits"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.4375 15.1238L15.625 15.3113V16.8113L15.4375 16.9988H14.6875L14.5 16.8113V16.4363L14.3125 16.2488H12.8125L12.625 16.0613V15.3113L12.4375 15.1238H10.9375L10.75 14.9363V14.1863L10.5625 13.9988H7.9375L7.75 14.1863V14.9363L7.5625 15.1238H6.0625L5.875 15.3113V16.0613L5.6875 16.2488H4.1875L4 16.4363V16.8113L3.8125 16.9988H3.0625L2.875 16.8113V15.3113L3.0625 15.1238H3.4375L3.625 14.9363V13.8113L3.8125 13.6238H4.1875L4.375 13.4363V11.9363L4.5625 11.7488H4.9375L5.125 11.5613V10.0613L4.9375 9.87384H4.1875L4 9.68634V8.93634L3.8125 8.74884H2.6875L2.5 8.56134V7.81134L2.3125 7.62384H1.1875L1 7.43634V6.68634L1.1875 6.49884H6.63738L6.82488 6.31134V5.61543V4.60586L7.01238 4.41836H7.5625L7.75 4.23086V2.6875L7.9375 2.5H8.43022L8.61772 2.3125V1.1875L8.80522 1H9.66735L9.85485 1.1875V2.3125L10.0424 2.5H10.5625L10.75 2.6875V4.23086L10.9375 4.41836H11.4375L11.625 4.60586V6.31134L11.8125 6.49884H17.3125L17.5 6.68634V7.43634L17.3125 7.62384H16.1875L16 7.81134V8.56134L15.8125 8.74884H14.6875L14.5 8.93634V9.68634L14.3125 9.87384H13.5625L13.375 10.0613V11.5613L13.5625 11.7488H13.9375L14.125 11.9363V13.4363L14.3125 13.6238H14.6875L14.875 13.8113V14.9363L15.0625 15.1238H15.4375Z" fill="#FFD700" />
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ export default ({ dieReasonMessage, respawnCallback, disconnectCallback }: Props
|
|||
}}
|
||||
/>
|
||||
<Button
|
||||
label="Disconnnect" onClick={() => {
|
||||
label="Disconnect" onClick={() => {
|
||||
disconnectCallback()
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import type { Block } from 'prismarine-block'
|
||||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
||||
import { miscUiState } from '../globalState'
|
||||
import { getFixedFilesize } from '../downloadAndOpenFile'
|
||||
import { options } from '../optionsStorage'
|
||||
import { BlockStateModelInfo } from '../../renderer/viewer/lib/mesher/shared'
|
||||
|
|
@ -28,7 +30,7 @@ export default () => {
|
|||
window.packetsCountByName = packetsCountByName
|
||||
const ignoredPackets = useRef(new Set([] as any[]))
|
||||
const [packetsString, setPacketsString] = useState('')
|
||||
const [showDebug, setShowDebug] = useState(false)
|
||||
const { showDebugHud } = useSnapshot(miscUiState)
|
||||
const [pos, setPos] = useState<{ x: number, y: number, z: number }>({ x: 0, y: 0, z: 0 })
|
||||
const [skyL, setSkyL] = useState(0)
|
||||
const [blockL, setBlockL] = useState(0)
|
||||
|
|
@ -53,13 +55,6 @@ export default () => {
|
|||
|
||||
const viewDegToMinecraft = (yaw) => yaw % 360 - 180 * (yaw < 0 ? -1 : 1)
|
||||
|
||||
const handleF3 = (e) => {
|
||||
if (e.code === 'F3') {
|
||||
setShowDebug(prev => !prev)
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const readPacket = (data, { name }, _buf, fullBuffer) => {
|
||||
if (fullBuffer) {
|
||||
const size = fullBuffer.byteLength
|
||||
|
|
@ -105,7 +100,6 @@ export default () => {
|
|||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleF3)
|
||||
let update = 0
|
||||
const packetsUpdateInterval = setInterval(() => {
|
||||
setPacketsString(`↓ ${received.current.count} (${(received.current.size / 1024).toFixed(2)} KB/s, ${getFixedFilesize(receivedTotal.current)}) ↑ ${sent.current.count}`)
|
||||
|
|
@ -161,7 +155,6 @@ export default () => {
|
|||
})
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleF3)
|
||||
clearInterval(packetsUpdateInterval)
|
||||
clearInterval(freqUpdateInterval)
|
||||
clearInterval(notFrequentUpdateInterval)
|
||||
|
|
@ -174,7 +167,7 @@ export default () => {
|
|||
minecraftQuad.current = Math.floor(((minecraftYaw.current + 180) / 90 + 0.5) % 4)
|
||||
}, [bot.entity.yaw])
|
||||
|
||||
if (!showDebug) return null
|
||||
if (!showDebugHud) return null
|
||||
|
||||
return <>
|
||||
<div className={`debug-left-side ${styles['debug-left-side']}`}>
|
||||
|
|
|
|||
56
src/react/GlobalOverlayHints.tsx
Normal file
56
src/react/GlobalOverlayHints.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { useEffect } from 'react'
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
import { isInRealGameSession, pointerLock } from '../utils'
|
||||
import { activeModalStack, miscUiState } from '../globalState'
|
||||
import PixelartIcon, { pixelartIcons } from './PixelartIcon'
|
||||
import { useUsingTouch } from './utilsApp'
|
||||
|
||||
export const displayHintsState = proxy({
|
||||
captureMouseHint: false
|
||||
})
|
||||
|
||||
export default () => {
|
||||
const { captureMouseHint } = useSnapshot(displayHintsState)
|
||||
const { usingGamepadInput } = useSnapshot(miscUiState)
|
||||
const usingTouch = useUsingTouch()
|
||||
const acitveModals = useSnapshot(activeModalStack).length > 0
|
||||
|
||||
const inRealGameSession = isInRealGameSession()
|
||||
|
||||
useEffect(() => {
|
||||
const listener = () => {
|
||||
if (pointerLock.hasPointerLock) {
|
||||
displayHintsState.captureMouseHint = false
|
||||
}
|
||||
}
|
||||
document.addEventListener('pointerlockchange', listener)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('pointerlockchange', listener)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <div style={{
|
||||
// below crosshair that is in center of screen
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
color: 'white',
|
||||
fontSize: '10px',
|
||||
whiteSpace: 'nowrap',
|
||||
fontWeight: 'bold',
|
||||
marginTop: '-16px',
|
||||
pointerEvents: 'none',
|
||||
textShadow: '0 0 1px black'
|
||||
}}>
|
||||
{captureMouseHint && !usingTouch && !usingGamepadInput && !acitveModals && inRealGameSession && <div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '10px',
|
||||
}}>
|
||||
<PixelartIcon iconName={pixelartIcons['sun-alt']} />
|
||||
<div>{translate('Click to capture mouse')}</div>
|
||||
</div>}
|
||||
</div>
|
||||
}
|
||||
|
|
@ -80,6 +80,9 @@ const HotbarInner = () => {
|
|||
const controller = new AbortController()
|
||||
|
||||
const inv = openItemsCanvas('HotbarWin', {
|
||||
_client: {
|
||||
write () {}
|
||||
},
|
||||
clickWindow (slot, mouseButton, mode) {
|
||||
if (mouseButton === 1) {
|
||||
console.log('right click')
|
||||
|
|
|
|||
|
|
@ -1,15 +1,24 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect, useMemo } from 'react'
|
||||
import { openURL } from 'renderer/viewer/lib/simpleUtils'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { haveDirectoryPicker } from '../utils'
|
||||
import { ConnectOptions } from '../connect'
|
||||
import { miscUiState } from '../globalState'
|
||||
import {
|
||||
isRemoteSplashText,
|
||||
loadRemoteSplashText,
|
||||
getCachedSplashText,
|
||||
cacheSplashText,
|
||||
cacheSourceUrl,
|
||||
clearSplashCache
|
||||
} from '../utils/splashText'
|
||||
import styles from './mainMenu.module.css'
|
||||
import Button from './Button'
|
||||
import ButtonWithTooltip from './ButtonWithTooltip'
|
||||
import { pixelartIcons } from './PixelartIcon'
|
||||
import useLongPress from './useLongPress'
|
||||
import PauseLinkButtons from './PauseLinkButtons'
|
||||
import CreditsBookButton from './CreditsBookButton'
|
||||
|
||||
type Action = (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
|
||||
|
|
@ -44,10 +53,48 @@ export default ({
|
|||
versionTitle,
|
||||
onVersionStatusClick,
|
||||
bottomRightLinks,
|
||||
singleplayerAvailable = true
|
||||
singleplayerAvailable = true,
|
||||
}: Props) => {
|
||||
const { appConfig } = useSnapshot(miscUiState)
|
||||
|
||||
const splashText = useMemo(() => {
|
||||
const cachedText = getCachedSplashText()
|
||||
|
||||
const configSplashFromApp = appConfig?.splashText
|
||||
const isRemote = configSplashFromApp && isRemoteSplashText(configSplashFromApp)
|
||||
const sourceKey = isRemote ? configSplashFromApp : (configSplashFromApp || '')
|
||||
const storedSourceKey = localStorage.getItem('minecraft_splash_url')
|
||||
|
||||
if (storedSourceKey !== sourceKey) {
|
||||
clearSplashCache()
|
||||
cacheSourceUrl(sourceKey)
|
||||
} else if (cachedText) {
|
||||
return cachedText
|
||||
}
|
||||
|
||||
if (!isRemote && configSplashFromApp && configSplashFromApp.trim() !== '') {
|
||||
cacheSplashText(configSplashFromApp)
|
||||
return configSplashFromApp
|
||||
}
|
||||
|
||||
return appConfig?.splashTextFallback || ''
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const configSplashFromApp = appConfig?.splashText
|
||||
if (configSplashFromApp && isRemoteSplashText(configSplashFromApp)) {
|
||||
loadRemoteSplashText(configSplashFromApp)
|
||||
.then(fetchedText => {
|
||||
if (fetchedText && fetchedText.trim() !== '' && !fetchedText.includes('Failed to load')) {
|
||||
cacheSplashText(fetchedText)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to preload splash text for next session:', error)
|
||||
})
|
||||
}
|
||||
}, [appConfig?.splashText])
|
||||
|
||||
if (!bottomRightLinks?.trim()) bottomRightLinks = undefined
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const linksParsed = bottomRightLinks?.split(/;|\n/g).map(l => {
|
||||
|
|
@ -75,7 +122,7 @@ export default ({
|
|||
const connectToServerLongPress = useLongPress(
|
||||
() => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Connect to <origin>:25565
|
||||
// Connect to <origin>:25565
|
||||
const origin = window.location.hostname
|
||||
const connectOptions: ConnectOptions = {
|
||||
server: `${origin}:25565`,
|
||||
|
|
@ -93,7 +140,7 @@ export default ({
|
|||
<div className={styles['game-title']}>
|
||||
<div className={styles.minecraft}>
|
||||
<div className={styles.edition} />
|
||||
<span className={styles.splash}>{appConfig?.splashText}</span>
|
||||
<span className={styles.splash}>{splashText}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -149,6 +196,7 @@ export default ({
|
|||
<div className={styles['menu-row']}>
|
||||
<PauseLinkButtons />
|
||||
</div>
|
||||
<CreditsBookButton />
|
||||
</div>
|
||||
|
||||
<div className={styles['bottom-info']}>
|
||||
|
|
|
|||
|
|
@ -22,8 +22,14 @@ export default (
|
|||
const canvasTick = useRef(0)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const [position, setPosition] = useState({ x: 0, y: 0, z: 0 })
|
||||
const lastUpdate = useRef(0)
|
||||
const THROTTLE_MS = 50 // 20fps
|
||||
|
||||
const updateMap = () => {
|
||||
const now = Date.now()
|
||||
if (now - lastUpdate.current < THROTTLE_MS) return
|
||||
lastUpdate.current = now
|
||||
|
||||
setPosition({ x: adapter.playerPosition.x, y: adapter.playerPosition.y, z: adapter.playerPosition.z })
|
||||
if (adapter.mapDrawer) {
|
||||
if (!full.current) {
|
||||
|
|
@ -85,6 +91,7 @@ export default (
|
|||
top: '0px',
|
||||
padding: '5px 5px 0px 0px',
|
||||
textAlign: 'center',
|
||||
zIndex: 7,
|
||||
}}
|
||||
onClick={() => {
|
||||
toggleFullMap?.()
|
||||
|
|
@ -106,7 +113,7 @@ export default (
|
|||
textShadow: '0.1em 0 black, 0 0.1em black, -0.1em 0 black, 0 -0.1em black, -0.1em -0.1em black, -0.1em 0.1em black, 0.1em -0.1em black, 0.1em 0.1em black'
|
||||
}}
|
||||
>
|
||||
{position.x.toFixed(2)} {position.y.toFixed(2)} {position.z.toFixed(2)}
|
||||
{Math.round(position.x)} {Math.round(position.y)} {Math.round(position.z)}
|
||||
</div>
|
||||
</div> : null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Chunk } from 'prismarine-world/types/world'
|
|||
import { Block } from 'prismarine-block'
|
||||
import { INVISIBLE_BLOCKS } from 'renderer/viewer/lib/mesher/worldConstants'
|
||||
import { getRenamedData } from 'flying-squid/dist/blockRenames'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useSnapshot, subscribe } from 'valtio'
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
||||
import BlockData from '../../renderer/viewer/lib/moreBlockDataGenerated.json'
|
||||
|
|
@ -42,10 +42,10 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
|
|||
yaw: number
|
||||
world: string
|
||||
warps: WorldWarp[] = gameAdditionalState.warps
|
||||
chunksStore = new Map<string, undefined | null | 'requested' | ChunkInfo >()
|
||||
chunksStore = new Map<string, undefined | null | 'requested' | ChunkInfo>()
|
||||
loadingChunksQueue = new Set<string>()
|
||||
loadChunk: (key: string) => Promise<void> = this.loadChunkMinimap
|
||||
mapDrawer = new MinimapDrawer(this.loadChunk, this.warps, this.loadingChunksQueue, this.chunksStore)
|
||||
mapDrawer = new MinimapDrawer(this.loadChunk.bind(this), this.warps, this.loadingChunksQueue, this.chunksStore)
|
||||
currChunk: PCChunk | undefined
|
||||
currChunkPos: { x: number, z: number } = { x: 0, z: 0 }
|
||||
isOldVersion: boolean
|
||||
|
|
@ -119,9 +119,9 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
|
|||
this.blockData.set(renamedKey, BlockData.colors[blockKey])
|
||||
}
|
||||
|
||||
subscribeKey(appViewer.rendererState, 'world', () => {
|
||||
subscribe(appViewer.rendererState.world, () => {
|
||||
for (const key of this.loadingChunksQueue) {
|
||||
if (appViewer.rendererState.world.chunksLoaded.includes(key)) {
|
||||
if (appViewer.rendererState.world.chunksLoaded.has(key)) {
|
||||
this.loadingChunksQueue.delete(key)
|
||||
void this.loadChunk(key)
|
||||
}
|
||||
|
|
@ -205,33 +205,31 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
|
|||
const [chunkX, chunkZ] = key.split(',').map(Number)
|
||||
const chunkWorldX = chunkX * 16
|
||||
const chunkWorldZ = chunkZ * 16
|
||||
if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) {
|
||||
const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks(`${chunkWorldX},${chunkWorldZ}`)
|
||||
if (!highestBlocks) return undefined
|
||||
const heightmap = new Uint8Array(256)
|
||||
if (appViewer.rendererState.world.chunksLoaded.has(key)) {
|
||||
// console.log('[MinimapProvider] loading chunk for minimap', key)
|
||||
const heightmap = appViewer.rendererState.world.heightmaps.get(key)
|
||||
if (heightmap) {
|
||||
// console.log('[MinimapProvider] did get highest blocks')
|
||||
} else {
|
||||
console.warn('[MinimapProvider] no highestBlocks from renderMethods')
|
||||
return undefined
|
||||
}
|
||||
const colors = Array.from({ length: 256 }).fill('') as string[]
|
||||
// avoid creating new object every time
|
||||
const blockPos = new Vec3(0, 0, 0)
|
||||
// filling up colors and heightmap
|
||||
// filling up colors
|
||||
for (let z = 0; z < 16; z += 1) {
|
||||
for (let x = 0; x < 16; x += 1) {
|
||||
const blockX = chunkWorldX + x
|
||||
const blockZ = chunkWorldZ + z
|
||||
const hBlock = highestBlocks[`${blockX},${blockZ}`]
|
||||
blockPos.x = blockX; blockPos.z = blockZ; blockPos.y = hBlock?.y ?? 0
|
||||
let block = bot.world.getBlock(blockPos)
|
||||
while (block?.name.includes('air')) {
|
||||
blockPos.y -= 1
|
||||
block = bot.world.getBlock(blockPos)
|
||||
}
|
||||
const index = z * 16 + x
|
||||
blockPos.x = blockX; blockPos.z = blockZ; blockPos.y = heightmap[index]
|
||||
const block = bot.world.getBlock(blockPos)
|
||||
// blocks which are not set are shown as half transparent
|
||||
if (!block || !hBlock) {
|
||||
heightmap[index] = 0
|
||||
if (!block) {
|
||||
colors[index] = 'rgba(0, 0, 0, 0.5)'
|
||||
continue
|
||||
}
|
||||
heightmap[index] = block.position.y
|
||||
colors[index] = this.setColor(block)
|
||||
}
|
||||
}
|
||||
|
|
@ -242,6 +240,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
|
|||
} else {
|
||||
this.loadingChunksQueue.add(`${chunkX},${chunkZ}`)
|
||||
this.chunksStore.set(key, 'requested')
|
||||
// console.log('[MinimapProvider] requested new chunk', key)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -339,7 +338,7 @@ export class DrawerAdapterImpl extends TypedEventEmitter<MapUpdates> implements
|
|||
const chunkWorldX = chunkX * 16
|
||||
const chunkWorldZ = chunkZ * 16
|
||||
const highestBlocks = await getThreeJsRendererMethods()?.getHighestBlocks(`${chunkWorldX},${chunkWorldZ}`)
|
||||
if (appViewer.rendererState.world.chunksLoaded.includes(`${chunkWorldX},${chunkWorldZ}`)) {
|
||||
if (appViewer.rendererState.world.chunksLoaded.has(`${chunkWorldX},${chunkWorldZ}`)) {
|
||||
const heightmap = new Uint8Array(256)
|
||||
const colors = Array.from({ length: 256 }).fill('') as string[]
|
||||
if (!highestBlocks) return null
|
||||
|
|
|
|||
|
|
@ -35,3 +35,17 @@
|
|||
padding: 4px 6px;
|
||||
outline: 0.5px solid white;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
color: #fff;
|
||||
background: #9c8c86;
|
||||
outline: .5px solid #fff;
|
||||
padding: 4px 6px;
|
||||
font-family: pixelarticons, mojangles, monospace;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.tab-btn:has(> div) {
|
||||
padding: 3px 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
|
|
|||
2
src/react/MobileTopButtons.module.css.d.ts
vendored
2
src/react/MobileTopButtons.module.css.d.ts
vendored
|
|
@ -9,6 +9,8 @@ interface CssExports {
|
|||
mobileTopBtns: string;
|
||||
'pause-btn': string;
|
||||
pauseBtn: string;
|
||||
'tab-btn': string;
|
||||
tabBtn: string;
|
||||
}
|
||||
declare const cssExports: CssExports;
|
||||
export default cssExports;
|
||||
|
|
|
|||
|
|
@ -1,91 +1,162 @@
|
|||
import { useEffect, useRef } from 'react'
|
||||
import { f3Keybinds } from '../controls'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { handleMobileButtonActionCommand, handleMobileButtonLongPress } from '../controls'
|
||||
import { watchValue } from '../optionsStorage'
|
||||
import { showModal, miscUiState, activeModalStack, hideCurrentModal } from '../globalState'
|
||||
import { showOptionsModal } from './SelectOption'
|
||||
import useLongPress from './useLongPress'
|
||||
import { type MobileButtonConfig, type ActionHoldConfig, type ActionType, type CustomAction } from '../appConfig'
|
||||
import { miscUiState } from '../globalState'
|
||||
import PixelartIcon from './PixelartIcon'
|
||||
import styles from './MobileTopButtons.module.css'
|
||||
|
||||
|
||||
export default () => {
|
||||
const elRef = useRef<HTMLDivElement | null>(null)
|
||||
const { appConfig } = useSnapshot(miscUiState)
|
||||
const mobileButtonsConfig = appConfig?.mobileButtons
|
||||
|
||||
const showMobileControls = (bl) => {
|
||||
if (elRef.current) elRef.current.style.display = bl ? 'flex' : 'none'
|
||||
const longPressTimerIdRef = useRef<number | null>(null)
|
||||
const actionToShortPressRef = useRef<ActionType | null>(null)
|
||||
|
||||
const showMobileControls = (visible: boolean) => {
|
||||
if (elRef.current) {
|
||||
elRef.current.style.display = visible ? 'flex' : 'none'
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
watchValue(miscUiState, o => {
|
||||
showMobileControls(o.currentTouch)
|
||||
showMobileControls(Boolean(o.currentTouch))
|
||||
})
|
||||
}, [])
|
||||
|
||||
const onLongPress = async () => {
|
||||
const select = await showOptionsModal('', f3Keybinds.filter(f3Keybind => {
|
||||
return f3Keybind.mobileTitle && (f3Keybind.enabled?.() ?? true)
|
||||
}).map(f3Keybind => {
|
||||
return `${f3Keybind.mobileTitle}${f3Keybind.key ? ` (F3+${f3Keybind.key})` : ''}`
|
||||
}))
|
||||
if (!select) return
|
||||
const f3Keybind = f3Keybinds.find(f3Keybind => f3Keybind.mobileTitle === select)
|
||||
if (f3Keybind) void f3Keybind.action()
|
||||
}
|
||||
const getButtonClassName = (button: MobileButtonConfig): string => {
|
||||
const actionForStyle = button.action || (button.actionHold && typeof button.actionHold === 'object' && 'command' in button.actionHold ? button.actionHold.command : undefined)
|
||||
|
||||
const defaultOptions = {
|
||||
shouldPreventDefault: true,
|
||||
delay: 500,
|
||||
}
|
||||
const longPressEvent = useLongPress(onLongPress, () => {}, defaultOptions)
|
||||
|
||||
|
||||
const onChatLongPress = () => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab' }))
|
||||
}
|
||||
|
||||
const onChatClick = () => {
|
||||
if (activeModalStack.at(-1)?.reactType === 'chat') {
|
||||
hideCurrentModal()
|
||||
} else {
|
||||
showModal({ reactType: 'chat' })
|
||||
if (typeof actionForStyle === 'string') {
|
||||
switch (actionForStyle) {
|
||||
case 'general.chat':
|
||||
return styles['chat-btn']
|
||||
case 'ui.pauseMenu':
|
||||
return styles['pause-btn']
|
||||
case 'general.playersList':
|
||||
return styles['tab-btn']
|
||||
default:
|
||||
return styles['debug-btn']
|
||||
}
|
||||
}
|
||||
return styles['debug-btn']
|
||||
}
|
||||
|
||||
const chatLongPressEvent = useLongPress(
|
||||
onChatLongPress,
|
||||
onChatClick,
|
||||
{
|
||||
shouldPreventDefault: true,
|
||||
delay: 300,
|
||||
}
|
||||
)
|
||||
const renderConfigButtons = () => {
|
||||
return mobileButtonsConfig?.map((button, index) => {
|
||||
const className = getButtonClassName(button)
|
||||
let label: string | JSX.Element = button.icon || button.label || ''
|
||||
|
||||
if (typeof label === 'string' && label.startsWith('pixelarticons:')) {
|
||||
const iconName = label.replace('pixelarticons:', '')
|
||||
label = <PixelartIcon iconName={iconName} />
|
||||
}
|
||||
|
||||
const onPointerDown = (e: React.PointerEvent) => {
|
||||
const elem = e.currentTarget as HTMLElement
|
||||
elem.setPointerCapture(e.pointerId)
|
||||
|
||||
if (longPressTimerIdRef.current) {
|
||||
clearTimeout(longPressTimerIdRef.current)
|
||||
longPressTimerIdRef.current = null
|
||||
}
|
||||
actionToShortPressRef.current = null
|
||||
|
||||
const { actionHold, action } = button
|
||||
|
||||
if (actionHold) {
|
||||
if (typeof actionHold === 'object' && 'command' in actionHold) {
|
||||
const config = actionHold
|
||||
if (config.longPressAction) {
|
||||
actionToShortPressRef.current = config.command
|
||||
longPressTimerIdRef.current = window.setTimeout(() => {
|
||||
handleMobileButtonLongPress(config)
|
||||
actionToShortPressRef.current = null
|
||||
longPressTimerIdRef.current = null
|
||||
}, config.duration || 500)
|
||||
} else {
|
||||
handleMobileButtonActionCommand(config.command, true)
|
||||
}
|
||||
} else if (action) {
|
||||
actionToShortPressRef.current = action
|
||||
longPressTimerIdRef.current = window.setTimeout(() => {
|
||||
handleMobileButtonActionCommand(actionHold, true)
|
||||
actionToShortPressRef.current = null
|
||||
longPressTimerIdRef.current = null
|
||||
}, 500)
|
||||
} else {
|
||||
handleMobileButtonActionCommand(actionHold, true)
|
||||
}
|
||||
} else if (action) {
|
||||
handleMobileButtonActionCommand(action, true)
|
||||
}
|
||||
}
|
||||
|
||||
const onPointerUp = (e: React.PointerEvent) => {
|
||||
const elem = e.currentTarget as HTMLElement
|
||||
elem.releasePointerCapture(e.pointerId)
|
||||
|
||||
const { actionHold, action } = button
|
||||
let wasShortPressHandled = false
|
||||
|
||||
if (longPressTimerIdRef.current) {
|
||||
clearTimeout(longPressTimerIdRef.current)
|
||||
longPressTimerIdRef.current = null
|
||||
if (actionToShortPressRef.current) {
|
||||
handleMobileButtonActionCommand(actionToShortPressRef.current, true)
|
||||
handleMobileButtonActionCommand(actionToShortPressRef.current, false)
|
||||
wasShortPressHandled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!wasShortPressHandled) {
|
||||
if (actionHold) {
|
||||
if (typeof actionHold === 'object' && 'command' in actionHold) {
|
||||
const config = actionHold
|
||||
if (config.longPressAction) {
|
||||
if (actionToShortPressRef.current === null) {
|
||||
if (typeof config.longPressAction === 'string') {
|
||||
handleMobileButtonActionCommand(config.longPressAction, false)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handleMobileButtonActionCommand(config.command, false)
|
||||
}
|
||||
} else if (action) {
|
||||
if (actionToShortPressRef.current === null) {
|
||||
handleMobileButtonActionCommand(actionHold, false)
|
||||
}
|
||||
} else {
|
||||
handleMobileButtonActionCommand(actionHold, false)
|
||||
}
|
||||
} else if (action) {
|
||||
handleMobileButtonActionCommand(action, false)
|
||||
}
|
||||
}
|
||||
actionToShortPressRef.current = null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={className}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerUp={onPointerUp}
|
||||
onLostPointerCapture={onPointerUp}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// ios note: just don't use <button>
|
||||
return <div ref={elRef} className={styles['mobile-top-btns']} id="mobile-top">
|
||||
<div
|
||||
className={styles['debug-btn']} onPointerDown={(e) => {
|
||||
window.dispatchEvent(new MouseEvent('mousedown', { button: 1 }))
|
||||
}}
|
||||
>S
|
||||
return (
|
||||
<div ref={elRef} className={styles['mobile-top-btns']} id="mobile-top">
|
||||
{mobileButtonsConfig && mobileButtonsConfig.length > 0 ? renderConfigButtons() : null}
|
||||
</div>
|
||||
<div
|
||||
className={styles['debug-btn']} onPointerDown={(e) => {
|
||||
document.dispatchEvent(new KeyboardEvent('keydown', { code: 'F3' }))
|
||||
document.dispatchEvent(new KeyboardEvent('keyup', { code: 'F3' }))
|
||||
}} {...longPressEvent}
|
||||
>F3
|
||||
</div>
|
||||
<div
|
||||
className={styles['chat-btn']}
|
||||
{...chatLongPressEvent}
|
||||
onPointerUp={(e) => {
|
||||
document.dispatchEvent(new KeyboardEvent('keyup', { key: 'Tab' }))
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
className={styles['pause-btn']} onPointerDown={(e) => {
|
||||
e.stopPropagation()
|
||||
showModal({ reactType: 'pause-screen' })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const Primary: Story = {
|
|||
{ username: 'Player 1', ping: 10, uuid: '1' },
|
||||
{ username: 'Player 2', ping: 20, uuid: '2' },
|
||||
{ username: 'Player 3', ping: 30, uuid: '3' },
|
||||
]
|
||||
] as any
|
||||
],
|
||||
clientId: '2',
|
||||
tablistHeader: 'Header',
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import MessageFormattedString from './MessageFormattedString'
|
|||
import './PlayerListOverlay.css'
|
||||
|
||||
|
||||
type PlayersLists = Array<Array<Pick<import('mineflayer').Player, 'uuid' | 'username' | 'ping'>>>
|
||||
type PlayersLists = Array<Array<Pick<import('mineflayer').Player, 'uuid' | 'displayName' | 'username' | 'ping'>>>
|
||||
|
||||
type PlayerListOverlayProps = {
|
||||
playersLists: PlayersLists,
|
||||
|
|
@ -25,7 +25,7 @@ export default ({ playersLists, clientId, tablistHeader, tablistFooter, serverIP
|
|||
<div key={index} className="player-list">
|
||||
{list.map(player => (
|
||||
<div key={player.uuid ?? player.username} className={`playerlist-entry${clientId === player.uuid ? ' active-player' : ''}`} id={`plist-player-${player.uuid}`}>
|
||||
<MessageFormattedString message={player.username} />
|
||||
<MessageFormattedString message={player.displayName ?? player.username} />
|
||||
<div className="playerlist-ping">
|
||||
<p className="playerlist-ping-value">{player.ping}</p>
|
||||
<p className="playerlist-ping-label">ms</p>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { useSnapshot } from 'valtio'
|
||||
import { proxy, useSnapshot } from 'valtio'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { isGameActive } from '../globalState'
|
||||
import PlayerListOverlay from './PlayerListOverlay'
|
||||
|
|
@ -9,26 +9,17 @@ const MAX_ROWS_PER_COL = 10
|
|||
|
||||
type Players = typeof bot.players
|
||||
|
||||
export const tabListState = proxy({
|
||||
isOpen: false,
|
||||
})
|
||||
|
||||
export default () => {
|
||||
const { isOpen } = useSnapshot(tabListState)
|
||||
|
||||
const serverIp = lastConnectOptions.value?.server
|
||||
const [clientId, setClientId] = useState(bot._client.uuid)
|
||||
const [players, setPlayers] = useState<Players>({})
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (!isGameActive(true)) return
|
||||
if (e.key === 'Tab') {
|
||||
setIsOpen(prev => true)
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyUp = (e) => {
|
||||
if (e.key === 'Tab') {
|
||||
setIsOpen(prev => false)
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
const [counter, setCounter] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
function requestUpdate () {
|
||||
|
|
@ -57,12 +48,11 @@ export default () => {
|
|||
})
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
document.addEventListener('keyup', handleKeyUp)
|
||||
const playerlistHeader = () => setCounter(prev => prev + 1)
|
||||
bot._client.on('playerlist_header', playerlistHeader)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
document.removeEventListener('keyup', handleKeyUp)
|
||||
bot?._client.removeListener('playerlist_header', playerlistHeader)
|
||||
}
|
||||
}, [serverIp])
|
||||
|
||||
|
|
|
|||
|
|
@ -287,7 +287,6 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
|
|||
proxy: overrides.proxyOverride || getCurrentProxy(),
|
||||
botVersion: overrides.versionOverride ?? /* legacy */ overrides['version'],
|
||||
ignoreQs: true,
|
||||
autoLoginPassword: server?.autoLogin?.[username],
|
||||
authenticatedAccount,
|
||||
saveServerToHistory: shouldSave,
|
||||
onSuccessfulPlay () {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
.hint_container {
|
||||
position: fixed;
|
||||
top: 20%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: calc(var(--safe-area-inset-bottom) + 55px);
|
||||
margin: 0 auto;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
pointer-events: none;
|
||||
pointer-events: auto;
|
||||
z-index: 1000;
|
||||
text-shadow: 1px 1px 8px rgba(0, 0, 0, 1);
|
||||
}
|
||||
|
||||
.hint_container > button {
|
||||
width: auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.hint_text {
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
|
|
|
|||
|
|
@ -3,28 +3,33 @@ import { useSnapshot } from 'valtio'
|
|||
import { options } from '../optionsStorage'
|
||||
import { activeModalStack } from '../globalState'
|
||||
import { videoCursorInteraction } from '../customChannels'
|
||||
import PixelartIcon, { pixelartIcons } from './PixelartIcon'
|
||||
// import PixelartIcon, { pixelartIcons } from './PixelartIcon'
|
||||
import styles from './TouchInteractionHint.module.css'
|
||||
import { useUsingTouch } from './utilsApp'
|
||||
import Button from './Button'
|
||||
|
||||
export default () => {
|
||||
const usingTouch = useUsingTouch()
|
||||
const modalStack = useSnapshot(activeModalStack)
|
||||
const { touchInteractionType } = useSnapshot(options)
|
||||
const [hintText, setHintText] = useState<string | null>(null)
|
||||
const [entityName, setEntityName] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const update = () => {
|
||||
const videoInteraction = videoCursorInteraction()
|
||||
if (videoInteraction) {
|
||||
setHintText(`Interact with video`)
|
||||
setEntityName(null)
|
||||
} else {
|
||||
const cursorState = bot.mouse.getCursorState()
|
||||
if (cursorState.entity) {
|
||||
const entityName = cursorState.entity.displayName ?? cursorState.entity.name
|
||||
setHintText(`Attack ${entityName}`)
|
||||
const name = cursorState.entity.displayName ?? cursorState.entity.name ?? 'Entity'
|
||||
setHintText(`Attack ${name}`)
|
||||
setEntityName(name)
|
||||
} else {
|
||||
setHintText(null)
|
||||
setEntityName(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,13 +45,33 @@ export default () => {
|
|||
}
|
||||
}, [])
|
||||
|
||||
const handleUseButtonClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
document.dispatchEvent(new MouseEvent('mousedown', { button: 2 }))
|
||||
bot.mouse.update()
|
||||
document.dispatchEvent(new MouseEvent('mouseup', { button: 2 }))
|
||||
}
|
||||
|
||||
if (!usingTouch || touchInteractionType !== 'classic' || modalStack.length > 0) return null
|
||||
if (!hintText) return null
|
||||
if (!hintText && !entityName) return null
|
||||
|
||||
// need to hide "Use" button if there isn't an entity name, but there is a hint text
|
||||
if (!entityName) return null
|
||||
|
||||
return (
|
||||
<div className={`${styles.hint_container} interaction-hint`}>
|
||||
<PixelartIcon iconName={pixelartIcons['sun-alt']} width={14} />
|
||||
<span className={styles.hint_text}>{hintText}</span>
|
||||
<div
|
||||
className={`${styles.hint_container} interaction-hint`}
|
||||
>
|
||||
{/* temporary hide hint indicator and text */}
|
||||
{/* <PixelartIcon iconName={pixelartIcons['sun-alt']} width={14} />
|
||||
<span className={styles.hint_text}>{hintText || 'Attack entity'}</span> */}
|
||||
<Button
|
||||
onClick={handleUseButtonClick}
|
||||
>
|
||||
{`Use ${entityName}`}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
--txrV: 66px;
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: calc(20px * var(--scale));
|
||||
min-height: calc(20px * var(--scale));
|
||||
font-family: minecraft, mojangles, monospace;
|
||||
font-size: 10px;
|
||||
color: white;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { RefObject, useEffect, useLayoutEffect, useRef } from 'react'
|
||||
import { RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
||||
import { pixelartIcons } from '../PixelartIcon'
|
||||
|
||||
export const useScrollBehavior = (
|
||||
|
|
@ -12,6 +12,8 @@ export const useScrollBehavior = (
|
|||
}
|
||||
) => {
|
||||
const openedWasAtBottom = useRef(true) // before new messages
|
||||
const [currentlyAtBottom, setCurrentlyAtBottom] = useState(true)
|
||||
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null)
|
||||
|
||||
const isAtBottom = () => {
|
||||
if (!elementRef.current) return true
|
||||
|
|
@ -20,17 +22,30 @@ export const useScrollBehavior = (
|
|||
return distanceFromBottom < 1
|
||||
}
|
||||
|
||||
const scrollToBottom = () => {
|
||||
if (elementRef.current) {
|
||||
elementRef.current.scrollTop = elementRef.current.scrollHeight
|
||||
setTimeout(() => {
|
||||
if (!elementRef.current) return
|
||||
elementRef.current.scrollTo({
|
||||
top: elementRef.current.scrollHeight,
|
||||
behavior: 'instant'
|
||||
})
|
||||
}, 0)
|
||||
const scrollToBottom = (behavior: ScrollBehavior = 'instant') => {
|
||||
if (!elementRef.current) return
|
||||
|
||||
// Clear any existing scroll timeout
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current)
|
||||
}
|
||||
|
||||
const el = elementRef.current
|
||||
|
||||
// Immediate scroll
|
||||
el.scrollTop = el.scrollHeight
|
||||
|
||||
// Double-check after a short delay to ensure we're really at the bottom
|
||||
scrollTimeoutRef.current = setTimeout(() => {
|
||||
if (!elementRef.current) return
|
||||
const el = elementRef.current
|
||||
el.scrollTo({
|
||||
top: el.scrollHeight,
|
||||
behavior
|
||||
})
|
||||
setCurrentlyAtBottom(true)
|
||||
openedWasAtBottom.current = true
|
||||
}, 5)
|
||||
}
|
||||
|
||||
// Handle scroll position tracking
|
||||
|
|
@ -39,18 +54,28 @@ export const useScrollBehavior = (
|
|||
if (!element) return
|
||||
|
||||
const handleScroll = () => {
|
||||
openedWasAtBottom.current = isAtBottom()
|
||||
const atBottom = isAtBottom()
|
||||
openedWasAtBottom.current = atBottom
|
||||
setCurrentlyAtBottom(atBottom)
|
||||
}
|
||||
|
||||
element.addEventListener('scroll', handleScroll)
|
||||
return () => element.removeEventListener('scroll', handleScroll)
|
||||
return () => {
|
||||
element.removeEventListener('scroll', handleScroll)
|
||||
if (scrollTimeoutRef.current) {
|
||||
clearTimeout(scrollTimeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Handle opened state changes
|
||||
useLayoutEffect(() => {
|
||||
if (opened) {
|
||||
openedWasAtBottom.current = true
|
||||
} else {
|
||||
// Wait a frame before scrolling to ensure DOM has updated
|
||||
requestAnimationFrame(() => {
|
||||
scrollToBottom()
|
||||
})
|
||||
} else if (elementRef.current) {
|
||||
scrollToBottom()
|
||||
}
|
||||
}, [opened])
|
||||
|
|
@ -64,6 +89,8 @@ export const useScrollBehavior = (
|
|||
|
||||
return {
|
||||
scrollToBottom,
|
||||
isAtBottom
|
||||
isAtBottom,
|
||||
wasAtBottom: () => openedWasAtBottom.current,
|
||||
currentlyAtBottom
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { appQueryParams } from '../appParams'
|
||||
import { miscUiState } from '../globalState'
|
||||
import { BaseServerInfo } from './AddServerOrConnect'
|
||||
import { lastConnectOptions } from './AppStatusProvider'
|
||||
import { appStorage, StoreServerItem } from './appStorageProvider'
|
||||
|
||||
const serversListQs = appQueryParams.serversList
|
||||
|
|
@ -43,9 +44,34 @@ export function updateServerConnectionHistory (ip: string, version?: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export const updateLoadedServerData = (callback: (data: StoreServerItem) => StoreServerItem, index = miscUiState.loadedServerIndex) => {
|
||||
if (index === undefined) index = miscUiState.loadedServerIndex
|
||||
export const getServerIndex = () => {
|
||||
const lastConnectedIp = lastConnectOptions.value?.server
|
||||
const index = miscUiState.loadedServerIndex
|
||||
if (index !== undefined) return index
|
||||
if (lastConnectedIp) {
|
||||
const idx = appStorage.serversList?.findIndex(s => s.ip === lastConnectedIp).toString()
|
||||
if (idx === '-1') return undefined
|
||||
return idx
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const findServerPassword = () => {
|
||||
const { username } = bot
|
||||
const index = getServerIndex()
|
||||
if (index === undefined) return
|
||||
const pswd = appStorage.serversList?.[index]?.autoLogin?.[username]
|
||||
if (pswd) return pswd
|
||||
// try other servers with same host
|
||||
return appStorage.serversList?.find(s => s.ip === lastConnectOptions.value?.server && s.autoLogin?.[username])?.autoLogin?.[username]
|
||||
}
|
||||
|
||||
export const updateLoadedServerData = (callback: (data: StoreServerItem) => StoreServerItem, index = miscUiState.loadedServerIndex) => {
|
||||
if (index === undefined) {
|
||||
const idx = getServerIndex()
|
||||
if (idx === undefined) return
|
||||
index = idx
|
||||
}
|
||||
|
||||
const servers = [...(appStorage.serversList ?? [])]
|
||||
const server = servers[index]
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ import ChunksDebug from './react/ChunksDebug'
|
|||
import ChunksDebugScreen from './react/ChunksDebugScreen'
|
||||
import DebugResponseTimeIndicator from './react/debugs/DebugResponseTimeIndicator'
|
||||
import RendererDebugMenu from './react/RendererDebugMenu'
|
||||
import CreditsAboutModal from './react/CreditsAboutModal'
|
||||
import GlobalOverlayHints from './react/GlobalOverlayHints'
|
||||
|
||||
const isFirefox = ua.getBrowser().name === 'Firefox'
|
||||
if (isFirefox) {
|
||||
|
|
@ -175,6 +177,7 @@ const InGameUi = () => {
|
|||
<MineflayerPluginHud />
|
||||
<MineflayerPluginConsole />
|
||||
{showUI && <TouchInteractionHint />}
|
||||
<GlobalOverlayHints />
|
||||
<div style={{ display: showUI ? 'block' : 'none' }}>
|
||||
{!disabledUiParts.includes('xp-bar') && <XPBarProvider />}
|
||||
{!disabledUiParts.includes('hud-bars') && <HudBarsProvider />}
|
||||
|
|
@ -241,7 +244,7 @@ const App = () => {
|
|||
<ModsPage />
|
||||
|
||||
<SelectOption />
|
||||
|
||||
<CreditsAboutModal />
|
||||
<NoModalFoundProvider />
|
||||
</RobustPortal>
|
||||
<RobustPortal to={document.body}>
|
||||
|
|
|
|||
17
src/utils.ts
17
src/utils.ts
|
|
@ -1,5 +1,6 @@
|
|||
import { gameAdditionalState, isGameActive, miscUiState } from './globalState'
|
||||
import { options } from './optionsStorage'
|
||||
import { displayHintsState } from './react/GlobalOverlayHints'
|
||||
import { notificationProxy, showNotification } from './react/NotificationProvider'
|
||||
import { packetsReplayState } from './react/state/packetsReplayState'
|
||||
|
||||
|
|
@ -38,13 +39,14 @@ export const pointerLock = {
|
|||
if (options.autoFullScreen) {
|
||||
void goFullscreen()
|
||||
}
|
||||
const displayBrowserProblem = () => {
|
||||
if (notificationProxy.id === 'auto-login') return // prevent notification hide
|
||||
showNotification('Browser Delay Limitation', navigator['keyboard'] ? 'Click on screen, enable Auto Fullscreen or F11' : 'Click on screen or use fullscreen in Chrome')
|
||||
notificationProxy.id = 'pointerlockchange'
|
||||
const displayMouseCaptureFailure = () => {
|
||||
// if (notificationProxy.id === 'auto-login') return // prevent notification hide
|
||||
// showNotification('Browser Delay Limitation', navigator['keyboard'] ? 'Click on screen, enable Auto Fullscreen or F11' : 'Click on screen or use fullscreen in Chrome')
|
||||
// notificationProxy.id = 'pointerlockchange'
|
||||
displayHintsState.captureMouseHint = true
|
||||
}
|
||||
if (!(document.fullscreenElement && navigator['keyboard']) && this.justHitEscape) {
|
||||
displayBrowserProblem()
|
||||
displayMouseCaptureFailure()
|
||||
} else {
|
||||
//@ts-expect-error
|
||||
const promise: any = document.documentElement.requestPointerLock({
|
||||
|
|
@ -56,9 +58,10 @@ export const pointerLock = {
|
|||
document.documentElement.requestPointerLock()
|
||||
} else if (error.name === 'SecurityError') {
|
||||
// cause: https://discourse.threejs.org/t/how-to-avoid-pointerlockcontrols-error/33017/4
|
||||
displayBrowserProblem()
|
||||
displayMouseCaptureFailure()
|
||||
} else {
|
||||
console.error(error)
|
||||
displayMouseCaptureFailure()
|
||||
console.warn('Failed to request pointer lock:', error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
67
src/utils/splashText.ts
Normal file
67
src/utils/splashText.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
const MAX_WORDS = 5
|
||||
const HTTPS_REGEX = /^https?:\/\//
|
||||
const TIMEOUT_MS = 5000
|
||||
const SPLASH_CACHE_KEY = 'minecraft_splash_text_cache'
|
||||
const SPLASH_URL_KEY = 'minecraft_splash_url'
|
||||
|
||||
const limitWords = (text: string): string => {
|
||||
const words = text.split(/\s+/)
|
||||
if (words.length <= MAX_WORDS) {
|
||||
return text
|
||||
}
|
||||
return words.slice(0, MAX_WORDS).join(' ') + '...'
|
||||
}
|
||||
|
||||
export const isRemoteSplashText = (text: string): boolean => {
|
||||
if (!text) return false
|
||||
return HTTPS_REGEX.test(text)
|
||||
}
|
||||
|
||||
export const loadRemoteSplashText = async (url: string): Promise<string> => {
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS)
|
||||
const response = await fetch(url, { signal: controller.signal })
|
||||
clearTimeout(timeoutId)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch splash text: ${response.statusText}`)
|
||||
}
|
||||
|
||||
const clonedResponse = response.clone()
|
||||
try {
|
||||
const json = await response.json()
|
||||
|
||||
if (typeof json === 'object' && json !== null) {
|
||||
if (json.title) return limitWords(json.title)
|
||||
if (json.text) return limitWords(json.text)
|
||||
if (json.message) return limitWords(json.message)
|
||||
|
||||
return limitWords(JSON.stringify(json))
|
||||
}
|
||||
|
||||
return limitWords(String(json))
|
||||
} catch (jsonError) {
|
||||
const text = await clonedResponse.text()
|
||||
return limitWords(text.trim())
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading remote splash text:', error)
|
||||
return 'Failed to load splash text!'
|
||||
}
|
||||
}
|
||||
|
||||
export const cacheSourceUrl = (url: string): void => {
|
||||
localStorage.setItem(SPLASH_URL_KEY, url)
|
||||
}
|
||||
|
||||
export const clearSplashCache = (): void => {
|
||||
localStorage.removeItem(SPLASH_CACHE_KEY)
|
||||
}
|
||||
|
||||
export const getCachedSplashText = (): string | null => {
|
||||
return localStorage.getItem(SPLASH_CACHE_KEY)
|
||||
}
|
||||
|
||||
export const cacheSplashText = (text: string): void => {
|
||||
localStorage.setItem(SPLASH_CACHE_KEY, text)
|
||||
}
|
||||
|
|
@ -80,6 +80,11 @@ export const watchOptionsAfterViewerInit = () => {
|
|||
updateFpsLimit(o)
|
||||
})
|
||||
|
||||
watchValue(options, o => {
|
||||
appViewer.inWorldRenderingConfig.vrSupport = o.vrSupport
|
||||
appViewer.inWorldRenderingConfig.vrPageGameRendering = o.vrPageGameRendering
|
||||
})
|
||||
|
||||
watchValue(options, (o, isChanged) => {
|
||||
appViewer.inWorldRenderingConfig.clipWorldBelowY = o.clipWorldBelowY
|
||||
appViewer.inWorldRenderingConfig.extraBlockRenderers = !o.disableSignsMapsSupport
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue