diff --git a/.cursor/rules/vars-usage.mdc b/.cursor/rules/vars-usage.mdc
index 7120e7ae..233e0aba 100644
--- a/.cursor/rules/vars-usage.mdc
+++ b/.cursor/rules/vars-usage.mdc
@@ -14,3 +14,5 @@ Ask AI
- Some other global variables that can be used without window prefixes are listed in src/globals.d.ts
Rationale: This ensures a clean separation between the Mineflayer logic (server-side/game logic) and the renderer (client-side/view logic), making the renderer portable and testable, and maintains proper usage of global state.
+
+For more general project contributing guides see CONTRIBUTING.md on like how to setup the project. Use pnpm tsc if needed to validate result with typechecking the whole project.
diff --git a/.github/workflows/build-single-file.yml b/.github/workflows/build-single-file.yml
index 93b1b77f..5f9800db 100644
--- a/.github/workflows/build-single-file.yml
+++ b/.github/workflows/build-single-file.yml
@@ -23,6 +23,8 @@ jobs:
- name: Build single-file version - minecraft.html
run: pnpm build-single-file && mv dist/single/index.html minecraft.html
+ env:
+ LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Upload artifact
uses: actions/upload-artifact@v4
diff --git a/.github/workflows/build-zip.yml b/.github/workflows/build-zip.yml
index cc472476..76ca65ca 100644
--- a/.github/workflows/build-zip.yml
+++ b/.github/workflows/build-zip.yml
@@ -23,6 +23,8 @@ jobs:
- name: Build project
run: pnpm build
+ env:
+ LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Bundle server.js
run: |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cc344aad..8fc56ea9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,7 +33,7 @@ jobs:
cd package
zip -r ../self-host.zip .
- run: pnpm build-playground
- - run: pnpm build-storybook
+ # - run: pnpm build-storybook
- run: pnpm test-unit
- run: pnpm lint
diff --git a/.github/workflows/next-deploy.yml b/.github/workflows/next-deploy.yml
index 943727eb..75b39f6c 100644
--- a/.github/workflows/next-deploy.yml
+++ b/.github/workflows/next-deploy.yml
@@ -36,7 +36,7 @@ jobs:
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
CONFIG_JSON_SOURCE: BUNDLED
- - run: pnpm build-storybook
+ LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml
index f35e6a00..89fd6698 100644
--- a/.github/workflows/preview.yml
+++ b/.github/workflows/preview.yml
@@ -78,7 +78,7 @@ jobs:
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
env:
CONFIG_JSON_SOURCE: BUNDLED
- - run: pnpm build-storybook
+ LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 14c4b471..cbf52251 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,7 +34,7 @@ jobs:
- run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod
env:
CONFIG_JSON_SOURCE: BUNDLED
- - run: pnpm build-storybook
+ LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Copy playground files
run: |
mkdir -p .vercel/output/static/playground
@@ -48,12 +48,12 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: .vercel/output/static
force_orphan: true
-
+
- name: Change index.html title
run: |
# change
Minecraft Web Client to Minecraft Web Client — Free Online Browser Version
sed -i 's/Minecraft Web Client<\/title>/Minecraft Web Client — Free Online Browser Version<\/title>/' .vercel/output/static/index.html
-
+
- name: Deploy Project to Vercel
uses: mathiasvr/command-output@v2.0.0
with:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 06df61fa..a5a3482d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -177,8 +177,13 @@ New React components, improve UI (including mobile support).
## Updating Dependencies
-1. Ensure mineflayer fork is up to date with the latest version of mineflayer original repo
+1. Use `pnpm update-git-deps` to check and update git dependencies (like mineflayer fork, prismarine packages etc). The script will:
+ - Show which git dependencies have updates available
+ - Ask if you want to update them
+ - Skip dependencies listed in `pnpm.updateConfig.ignoreDependencies`
+
2. Update PrismarineJS dependencies to the latest version: `minecraft-data` (be sure to replace the version twice in the package.json), `mineflayer`, `minecraft-protocol`, `prismarine-block`, `prismarine-chunk`, `prismarine-item`, ...
+
3. If `minecraft-protocol` patch fails, do this:
1. Remove the patch from `patchedDependencies` in `package.json`
2. Run `pnpm patch minecraft-protocol`, open patch directory
diff --git a/README.MD b/README.MD
index 5f7a83f6..61a5b733 100644
--- a/README.MD
+++ b/README.MD
@@ -12,6 +12,9 @@ Don't confuse with [Eaglercraft](https://git.eaglercraft.rip/eaglercraft/eaglerc
For building the project yourself / contributing, see [Development, Debugging & Contributing](#development-debugging--contributing). For reference at what and how web technologies / frameworks are used, see [TECH.md](./TECH.md) (also for comparison with Eaglercraft).
+> **Note**: You can deploy it on your own server in less than a minute using a one-liner script from [Minecraft Everywhere repo](https://github.com/zardoy/minecraft-everywhere)
+
+
### Big Features
- Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely.
@@ -30,7 +33,7 @@ For building the project yourself / contributing, see [Development, Debugging &
- Support for custom rendering 3D engines. Modular architecture.
- even even more!
-All components that are in [Storybook](https://mcraft.fun/storybook) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react)
+All components that are in [Storybook](https://minimap.mcraft.fun/storybook/) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react)
### Recommended Settings
@@ -42,9 +45,12 @@ All components that are in [Storybook](https://mcraft.fun/storybook) are publish
### Browser Notes
-These browsers have issues with capturing pointer:
+This project is tested with BrowserStack. Special thanks to [BrowserStack](https://www.browserstack.com/) for providing testing infrastructure!
+
+Howerver, it's known that these browsers have issues:
**Opera Mini**: Disable *mouse gestures* in browsre settings to avoid opening new tab on right click hold
+
**Vivaldi**: Disable Controls -> *Raw Input* in game settings if experiencing issues
### Versions Support
@@ -124,7 +130,7 @@ Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can u
- `bot` - Mineflayer bot instance. See Mineflayer documentation for more.
- `viewer` - Three.js viewer instance, basically does all the rendering.
-- `viewer.world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group.
+- `world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group.
- `debugSceneChunks` - The same as above, but relative to current bot position (e.g. 0,0 is the current chunk).
- `debugChangedOptions` - See what options are changed. Don't change options here.
- `localServer`/`server` - Only for singleplayer mode/host. Flying Squid server instance, see it's documentation for more.
@@ -133,7 +139,7 @@ Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can u
- `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read.
-The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `viewer.camera.position` to see the camera position and so on.
+The most useful thing in devtools is the watch expression. You can add any expression there and it will be re-evaluated in real time. For example, you can add `camera.position` to see the camera position and so on.
diff --git a/assets/debug-inputs.html b/assets/debug-inputs.html
new file mode 100644
index 00000000..584fe4d7
--- /dev/null
+++ b/assets/debug-inputs.html
@@ -0,0 +1,237 @@
+
+
+
+
+
+ Web Input Debugger
+
+
+
+
+
+ Use keydown repeat mode (auto key-up after 150ms of no repeat)
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config.json b/config.json
index fe26a9e5..57b1c207 100644
--- a/config.json
+++ b/config.json
@@ -3,12 +3,16 @@
"defaultHost": "",
"defaultProxy": "https://proxy.mcraft.fun",
"mapsProvider": "https://maps.mcraft.fun/",
+ "skinTexturesProxy": "",
"peerJsServer": "",
"peerJsServerFallback": "https://p2p.mcraft.fun",
"promoteServers": [
{
"ip": "wss://play.mcraft.fun"
},
+ {
+ "ip": "wss://ws.fuchsmc.net"
+ },
{
"ip": "wss://play2.mcraft.fun"
},
diff --git a/config.mcraft-only.json b/config.mcraft-only.json
new file mode 100644
index 00000000..7d9a7b59
--- /dev/null
+++ b/config.mcraft-only.json
@@ -0,0 +1,4 @@
+{
+ "alwaysReconnectButton": true,
+ "reportBugButtonWithReconnect": true
+}
diff --git a/experiments/three.ts b/experiments/three.ts
index 7a629a13..9b158dec 100644
--- a/experiments/three.ts
+++ b/experiments/three.ts
@@ -1,101 +1,61 @@
import * as THREE from 'three'
-import * as tweenJs from '@tweenjs/tween.js'
-import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
-import * as THREE from 'three';
-import Jimp from 'jimp';
+import { loadThreeJsTextureFromBitmap } from '../renderer/viewer/lib/utils/skins'
+// Create scene, camera and renderer
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
-camera.position.set(0, 0, 5)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
-const controls = new OrbitControls(camera, renderer.domElement)
+// Position camera
+camera.position.z = 5
-const geometry = new THREE.BoxGeometry(1, 1, 1)
-const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
-const cube = new THREE.Mesh(geometry, material)
-cube.position.set(0.5, 0.5, 0.5);
-const group = new THREE.Group()
-group.add(cube)
-group.position.set(-0.5, -0.5, -0.5);
-const outerGroup = new THREE.Group()
-outerGroup.add(group)
-outerGroup.scale.set(0.2, 0.2, 0.2)
-outerGroup.position.set(1, 1, 0)
-scene.add(outerGroup)
+// Create a canvas with some content
+const canvas = document.createElement('canvas')
+canvas.width = 256
+canvas.height = 256
+const ctx = canvas.getContext('2d')
-// const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0x00_00_ff, transparent: true, opacity: 0.5 }))
-// mesh.position.set(0.5, 1, 0.5)
-// const group = new THREE.Group()
-// group.add(mesh)
-// group.position.set(-0.5, -1, -0.5)
-// const outerGroup = new THREE.Group()
-// outerGroup.add(group)
-// // outerGroup.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z)
-// scene.add(outerGroup)
+scene.background = new THREE.Color(0x444444)
- new tweenJs.Tween(group.rotation).to({ z: THREE.MathUtils.degToRad(90) }, 1000).yoyo(true).repeat(Infinity).start()
+// Draw something on the canvas
+ctx.fillStyle = '#444444'
+// ctx.fillRect(0, 0, 256, 256)
+ctx.fillStyle = 'red'
+ctx.font = '48px Arial'
+ctx.textAlign = 'center'
+ctx.textBaseline = 'middle'
+ctx.fillText('Hello!', 128, 128)
-const tweenGroup = new tweenJs.Group()
-function animate () {
- tweenGroup.update()
- requestAnimationFrame(animate)
-// cube.rotation.x += 0.01
-// cube.rotation.y += 0.01
- renderer.render(scene, camera)
+// Create bitmap and texture
+async function createTexturedBox() {
+ const canvas2 = new OffscreenCanvas(256, 256)
+ const ctx2 = canvas2.getContext('2d')!
+ ctx2.drawImage(canvas, 0, 0)
+ const texture = new THREE.Texture(canvas2)
+ texture.magFilter = THREE.NearestFilter
+ texture.minFilter = THREE.NearestFilter
+ texture.needsUpdate = true
+ texture.flipY = false
+
+ // Create box with texture
+ const geometry = new THREE.BoxGeometry(2, 2, 2)
+ const material = new THREE.MeshBasicMaterial({
+ map: texture,
+ side: THREE.DoubleSide,
+ premultipliedAlpha: false,
+ })
+ const cube = new THREE.Mesh(geometry, material)
+ scene.add(cube)
+}
+
+// Create the textured box
+createTexturedBox()
+
+// Animation loop
+function animate() {
+ requestAnimationFrame(animate)
+ renderer.render(scene, camera)
}
animate()
-
-// let animation
-
-window.animate = () => {
- // new Tween.Tween(group.position).to({ y: group.position.y - 1}, 1000 * 0.35/2).yoyo(true).repeat(1).start()
- new tweenJs.Tween(group.rotation, tweenGroup).to({ z: THREE.MathUtils.degToRad(90) }, 1000 * 0.35 / 2).yoyo(true).repeat(Infinity).start().onRepeat(() => {
- console.log('done')
- })
-}
-
-window.stop = () => {
- tweenGroup.removeAll()
-}
-
-
-function createGeometryFromImage() {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABEElEQVQ4jWNkIAPw2Zv9J0cfXPOSvx/+L/n74T+HqsJ/JlI1T9u3i6H91B7ybdY+vgZuO1majV+fppFmPnuz/+ihy2dv9t/49Wm8mlECkV1FHh5FfPZm/1XXTGX4cechA4eKPMNVq1CGH7cfMBJ0rlxX+X8OVYX/xq9P/5frKifoZ0Z0AwS8HRkYGBgYvt+8xyDXUUbQZgwJPnuz/+wq8gw/7zxk+PXsFUFno0h6mon+l5fgZFhwnYmBTUqMgYGBgaAhLMiaHQyFGOZvf8Lw49FXRgYGhv8MDAwwg/7jMoQFFury/C8Y5m9/wnADohnZVryJhoWBARJ9Cw69gtmMAgiFAcuvZ68Yfj17hU8NXgAATdKfkzbQhBEAAAAASUVORK5CYII='
- console.log('img.complete', img.complete)
- img.onload = () => {
- const canvas = document.createElement('canvas');
- canvas.width = img.width;
- canvas.height = img.height;
- const context = canvas.getContext('2d');
- context.drawImage(img, 0, 0, img.width, img.height);
- const imgData = context.getImageData(0, 0, img.width, img.height);
-
- const shape = new THREE.Shape();
- for (let y = 0; y < img.height; y++) {
- for (let x = 0; x < img.width; x++) {
- const index = (y * img.width + x) * 4;
- const alpha = imgData.data[index + 3];
- if (alpha !== 0) {
- shape.lineTo(x, y);
- }
- }
- }
-
- const geometry = new THREE.ShapeGeometry(shape);
- resolve(geometry);
- };
- img.onerror = reject;
- });
-}
-
-// Usage:
-const shapeGeomtry = createGeometryFromImage().then(geometry => {
- const material = new THREE.MeshBasicMaterial({ color: 0xffffff });
- const mesh = new THREE.Mesh(geometry, material);
- scene.add(mesh);
-})
diff --git a/index.html b/index.html
index 5a6318ec..b2fa3dbd 100644
--- a/index.html
+++ b/index.html
@@ -27,6 +27,7 @@
A true Minecraft client in your browser!
+ Only iOS 15+ is supported due to performance optimizations
`
@@ -36,6 +37,13 @@
if (!window.pageLoaded) {
document.documentElement.appendChild(loadingDivElem)
}
+
+ // iOS version detection
+ const getIOSVersion = () => {
+ const match = navigator.userAgent.match(/OS (\d+)_(\d+)_?(\d+)?/);
+ return match ? parseInt(match[1], 10) : null;
+ }
+
// load error handling
const onError = (errorOrMessage, log = false) => {
let message = errorOrMessage instanceof Error ? (errorOrMessage.stack ?? errorOrMessage.message) : errorOrMessage
@@ -46,12 +54,23 @@
const [errorMessage, ...errorStack] = message.split('\n')
document.querySelector('.initial-loader').querySelector('.subtitle').textContent = errorMessage
document.querySelector('.initial-loader').querySelector('.advanced-info').textContent = errorStack.join('\n')
+
+ // Show iOS warning if applicable
+ const iosVersion = getIOSVersion();
+ if (iosVersion !== null && iosVersion < 15) {
+ document.querySelector('.initial-loader').querySelector('.ios-warning').style.display = 'block';
+ }
+
if (window.navigator.maxTouchPoints > 1) window.location.hash = '#dev' // show eruda
// unregister all sw
- if (window.navigator.serviceWorker) {
+ if (window.navigator.serviceWorker && document.querySelector('.initial-loader').style.opacity !== 0) {
+ console.log('got worker')
window.navigator.serviceWorker.getRegistrations().then(registrations => {
registrations.forEach(registration => {
- registration.unregister()
+ console.log('got registration')
+ registration.unregister().then(() => {
+ console.log('worker unregistered')
+ })
})
})
}
diff --git a/package.json b/package.json
index 516d57f3..fe4adb16 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"dev-proxy": "node server.js",
"start": "run-p dev-proxy dev-rsbuild watch-mesher",
"start2": "run-p dev-rsbuild watch-mesher",
+ "start-metrics": "ENABLE_METRICS=true rsbuild dev",
"build": "pnpm build-other-workers && rsbuild build",
"build-analyze": "BUNDLE_ANALYZE=true rsbuild build && pnpm build-other-workers",
"build-single-file": "SINGLE_FILE_BUILD=true rsbuild build",
@@ -31,7 +32,9 @@
"run-playground": "run-p watch-mesher watch-other-workers watch-playground",
"run-all": "run-p start run-playground",
"build-playground": "rsbuild build --config renderer/rsbuild.config.ts",
- "watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts"
+ "watch-playground": "rsbuild dev --config renderer/rsbuild.config.ts",
+ "update-git-deps": "tsx scripts/updateGitDeps.ts",
+ "request-data": "tsx scripts/requestData.ts"
},
"keywords": [
"prismarine",
@@ -83,7 +86,7 @@
"jszip": "^3.10.1",
"lodash-es": "^4.17.21",
"mcraft-fun-mineflayer": "^0.1.23",
- "minecraft-data": "3.89.0",
+ "minecraft-data": "3.92.0",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
"mojangson": "^2.0.4",
@@ -150,10 +153,10 @@
"http-browserify": "^1.7.0",
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
- "mc-assets": "^0.2.59",
+ "mc-assets": "^0.2.62",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
- "mineflayer-mouse": "^0.1.10",
+ "mineflayer-mouse": "^0.1.11",
"mineflayer-pathfinder": "^2.4.4",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",
@@ -201,7 +204,7 @@
"diamond-square": "github:zardoy/diamond-square",
"prismarine-block": "github:zardoy/prismarine-block#next-era",
"prismarine-world": "github:zardoy/prismarine-world#next-era",
- "minecraft-data": "3.89.0",
+ "minecraft-data": "3.92.0",
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
"prismarine-physics": "github:zardoy/prismarine-physics",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
@@ -210,7 +213,10 @@
"prismarine-item": "latest"
},
"updateConfig": {
- "ignoreDependencies": []
+ "ignoreDependencies": [
+ "browserfs",
+ "google-drive-browserfs"
+ ]
},
"patchedDependencies": {
"pixelarticons@1.8.1": "patches/pixelarticons@1.8.1.patch",
diff --git a/patches/minecraft-protocol.patch b/patches/minecraft-protocol.patch
index a3a4a6a3..6a82d5cf 100644
--- a/patches/minecraft-protocol.patch
+++ b/patches/minecraft-protocol.patch
@@ -1,26 +1,26 @@
diff --git a/src/client/chat.js b/src/client/chat.js
-index f14269bea055d4329cd729271e7406ec4b344de7..00f5482eb6e3c911381ca9a728b1b4aae0d1d337 100644
+index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb1856fa582f9 100644
--- a/src/client/chat.js
+++ b/src/client/chat.js
-@@ -111,7 +111,7 @@ module.exports = function (client, options) {
- for (const player of packet.data) {
- if (!player.chatSession) continue
- client._players[player.UUID] = {
-- publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
-+ // publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
- publicKeyDER: player.chatSession.publicKey.keyBytes,
- sessionUuid: player.chatSession.uuid
- }
-@@ -127,7 +127,7 @@ module.exports = function (client, options) {
- for (const player of packet.data) {
- if (player.crypto) {
- client._players[player.UUID] = {
-- publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
-+ // publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
- publicKeyDER: player.crypto.publicKey,
- signature: player.crypto.signature,
- displayName: player.displayName || player.name
-@@ -198,7 +198,7 @@ module.exports = function (client, options) {
+@@ -109,7 +109,7 @@ module.exports = function (client, options) {
+ for (const player of packet.data) {
+ if (player.chatSession) {
+ client._players[player.uuid] = {
+- publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
++ // publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }),
+ publicKeyDER: player.chatSession.publicKey.keyBytes,
+ sessionUuid: player.chatSession.uuid
+ }
+@@ -119,7 +119,7 @@ module.exports = function (client, options) {
+
+ if (player.crypto) {
+ client._players[player.uuid] = {
+- publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
++ // publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }),
+ publicKeyDER: player.crypto.publicKey,
+ signature: player.crypto.signature,
+ displayName: player.displayName || player.name
+@@ -189,7 +189,7 @@ module.exports = function (client, options) {
if (mcData.supportFeature('useChatSessions')) {
const tsDelta = BigInt(Date.now()) - packet.timestamp
const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0
@@ -29,7 +29,7 @@ index f14269bea055d4329cd729271e7406ec4b344de7..00f5482eb6e3c911381ca9a728b1b4aa
if (verified) client._signatureCache.push(packet.signature)
client.emit('playerChat', {
plainMessage: packet.plainMessage,
-@@ -363,7 +363,7 @@ module.exports = function (client, options) {
+@@ -354,7 +354,7 @@ module.exports = function (client, options) {
}
}
@@ -38,7 +38,7 @@ index f14269bea055d4329cd729271e7406ec4b344de7..00f5482eb6e3c911381ca9a728b1b4aa
options.timestamp = options.timestamp || BigInt(Date.now())
options.salt = options.salt || 1n
-@@ -405,7 +405,7 @@ module.exports = function (client, options) {
+@@ -396,7 +396,7 @@ module.exports = function (client, options) {
message,
timestamp: options.timestamp,
salt: options.salt,
@@ -47,7 +47,7 @@ index f14269bea055d4329cd729271e7406ec4b344de7..00f5482eb6e3c911381ca9a728b1b4aa
offset: client._lastSeenMessages.pending,
acknowledged
})
-@@ -419,7 +419,7 @@ module.exports = function (client, options) {
+@@ -410,7 +410,7 @@ module.exports = function (client, options) {
message,
timestamp: options.timestamp,
salt: options.salt,
@@ -74,7 +74,7 @@ index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108
function onJoinServerResponse (err) {
diff --git a/src/client/play.js b/src/client/play.js
-index 6e06dc15291b38e1eeeec8d7102187b2a23d70a3..f67454942db9276cbb9eab99c281cfe182cb8a1f 100644
+index 559607f34e9a5b2b7809423f8ca4cd6746b60225..4dc1c3139438cc2729b05c57e57bd00252728f8a 100644
--- a/src/client/play.js
+++ b/src/client/play.js
@@ -53,7 +53,7 @@ module.exports = function (client, options) {
@@ -87,7 +87,7 @@ index 6e06dc15291b38e1eeeec8d7102187b2a23d70a3..f67454942db9276cbb9eab99c281cfe1
})
// 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
+index 5c7a62b013daa69be91ec9e763b1f48ffe96ffa6..174d42a77740a937afcb106e1f39a9ee824a24b9 100644
--- a/src/client.js
+++ b/src/client.js
@@ -89,10 +89,12 @@ class Client extends EventEmitter {
diff --git a/patches/pixelarticons@1.8.1.patch b/patches/pixelarticons@1.8.1.patch
index 10044536..b65b6f2b 100644
--- a/patches/pixelarticons@1.8.1.patch
+++ b/patches/pixelarticons@1.8.1.patch
@@ -1,5 +1,5 @@
diff --git a/fonts/pixelart-icons-font.css b/fonts/pixelart-icons-font.css
-index 3b2ebe839370d96bf93ef5ca94a827f07e49378d..103ab4d6b9f3b5c9f41d1407e3cbf4ac392fbf41 100644
+index 3b2ebe839370d96bf93ef5ca94a827f07e49378d..4f8d76be2ca6e4ddc43c68d0a6f0f69979165ab4 100644
--- a/fonts/pixelart-icons-font.css
+++ b/fonts/pixelart-icons-font.css
@@ -1,16 +1,13 @@
@@ -10,10 +10,11 @@ index 3b2ebe839370d96bf93ef5ca94a827f07e49378d..103ab4d6b9f3b5c9f41d1407e3cbf4ac
+ src:
url("pixelart-icons-font.woff2?t=1711815892278") format("woff2"),
url("pixelart-icons-font.woff?t=1711815892278") format("woff"),
- url('pixelart-icons-font.ttf?t=1711815892278') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
+- url('pixelart-icons-font.ttf?t=1711815892278') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
- url('pixelart-icons-font.svg?t=1711815892278#pixelart-icons-font') format('svg'); /* iOS 4.1- */
++ url('pixelart-icons-font.ttf?t=1711815892278') format('truetype'); /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
}
-
+
[class^="pixelart-icons-font-"], [class*=" pixelart-icons-font-"] {
font-family: 'pixelart-icons-font' !important;
- font-size:24px;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f954791a..c37e0510 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -12,7 +12,7 @@ overrides:
diamond-square: github:zardoy/diamond-square
prismarine-block: github:zardoy/prismarine-block#next-era
prismarine-world: github:zardoy/prismarine-world#next-era
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything
prismarine-physics: github:zardoy/prismarine-physics
minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master
@@ -22,13 +22,13 @@ overrides:
patchedDependencies:
minecraft-protocol:
- hash: 09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762
+ hash: a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0
path: patches/minecraft-protocol.patch
mineflayer-item-map-downloader@1.2.0:
hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad
path: patches/mineflayer-item-map-downloader@1.2.0.patch
pixelarticons@1.8.1:
- hash: d6a3d784047beba873565d1198bed425d9eb2de942e3fc8edac55f25473e4325
+ hash: 533230072bc402f425c86abd3d0356fe087b14cab2a254d93f419b083f2d8dfa
path: patches/pixelarticons@1.8.1.patch
importers:
@@ -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/0380bed150fe03db4ac37f0194e0cee98356647d(encoding@0.1.13))
+ version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13))
minecraft-data:
- specifier: 3.89.0
- version: 3.89.0
+ specifier: 3.92.0
+ version: 3.92.0
minecraft-protocol:
specifier: github:PrismarineJS/node-minecraft-protocol#master
- version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762)(encoding@0.1.13)
+ version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(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)
@@ -160,13 +160,13 @@ importers:
version: 1.5.4
pixelarticons:
specifier: ^1.8.1
- version: 1.8.1(patch_hash=d6a3d784047beba873565d1198bed425d9eb2de942e3fc8edac55f25473e4325)
+ version: 1.8.1(patch_hash=533230072bc402f425c86abd3d0356fe087b14cab2a254d93f419b083f2d8dfa)
pretty-bytes:
specifier: ^6.1.1
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.89.0)
+ version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
prosemirror-example-setup:
specifier: ^1.2.2
version: 1.2.3
@@ -334,17 +334,17 @@ importers:
specifier: ^1.0.0
version: 1.0.0
mc-assets:
- specifier: ^0.2.59
- version: 0.2.59
+ specifier: ^0.2.62
+ version: 0.2.62
minecraft-inventory-gui:
specifier: github:zardoy/minecraft-inventory-gui#next
- version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5494f356b1e59eddc876c3ae05ff395f12a46379(@types/react@18.3.18)(react@18.3.1)
+ version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@types/react@18.3.18)(react@18.3.1)
mineflayer:
specifier: github:zardoy/mineflayer#gen-the-master
- version: https://codeload.github.com/zardoy/mineflayer/tar.gz/0380bed150fe03db4ac37f0194e0cee98356647d(encoding@0.1.13)
+ version: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(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)
+ specifier: ^0.1.11
+ version: 0.1.11
mineflayer-pathfinder:
specifier: ^2.4.4
version: 2.4.5
@@ -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.89.0)
+ version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-schematic:
specifier: ^1.2.0
version: 1.2.3
@@ -3308,47 +3308,18 @@ packages:
'@vitest/expect@0.34.6':
resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==}
- '@vitest/expect@3.0.8':
- resolution: {integrity: sha512-Xu6TTIavTvSSS6LZaA3EebWFr6tsoXPetOWNMOlc7LO88QVVBwq2oQWBoDiLCN6YTvNYsGSjqOO8CAdjom5DCQ==}
-
- '@vitest/mocker@3.0.8':
- resolution: {integrity: sha512-n3LjS7fcW1BCoF+zWZxG7/5XvuYH+lsFg+BDwwAz0arIwHQJFUEsKBQ0BLU49fCxuM/2HSeBPHQD8WjgrxMfow==}
- peerDependencies:
- msw: ^2.4.9
- vite: ^5.0.0 || ^6.0.0
- peerDependenciesMeta:
- msw:
- optional: true
- vite:
- optional: true
-
- '@vitest/pretty-format@3.0.8':
- resolution: {integrity: sha512-BNqwbEyitFhzYMYHUVbIvepOyeQOSFA/NeJMIP9enMntkkxLgOcgABH6fjyXG85ipTgvero6noreavGIqfJcIg==}
-
'@vitest/runner@0.34.6':
resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==}
- '@vitest/runner@3.0.8':
- resolution: {integrity: sha512-c7UUw6gEcOzI8fih+uaAXS5DwjlBaCJUo7KJ4VvJcjL95+DSR1kova2hFuRt3w41KZEFcOEiq098KkyrjXeM5w==}
-
'@vitest/snapshot@0.34.6':
resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==}
- '@vitest/snapshot@3.0.8':
- resolution: {integrity: sha512-x8IlMGSEMugakInj44nUrLSILh/zy1f2/BgH0UeHpNyOocG18M9CWVIFBaXPt8TrqVZWmcPjwfG/ht5tnpba8A==}
-
'@vitest/spy@0.34.6':
resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==}
- '@vitest/spy@3.0.8':
- resolution: {integrity: sha512-MR+PzJa+22vFKYb934CejhR4BeRpMSoxkvNoDit68GQxRLSf11aT6CTj3XaqUU9rxgWJFnqicN/wxw6yBRkI1Q==}
-
'@vitest/utils@0.34.6':
resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==}
- '@vitest/utils@3.0.8':
- resolution: {integrity: sha512-nkBC3aEhfX2PdtQI/QwAWp8qZWwzASsU4Npbcd5RdMPBSSLCpkZp52P3xku3s3uA0HIEhGvEcF8rNkBsz9dQ4Q==}
-
'@xboxreplay/errors@0.1.0':
resolution: {integrity: sha512-Tgz1d/OIPDWPeyOvuL5+aai5VCcqObhPnlI3skQuf80GVF3k1I0lPCnGC+8Cm5PV9aLBT5m8qPcJoIUQ2U4y9g==}
@@ -3672,10 +3643,6 @@ packages:
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
- assertion-error@2.0.1:
- resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
- engines: {node: '>=12'}
-
assign-symbols@1.0.0:
resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==}
engines: {node: '>=0.10.0'}
@@ -4012,10 +3979,6 @@ packages:
resolution: {integrity: sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==}
engines: {node: '>=4'}
- chai@5.2.0:
- resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
- engines: {node: '>=12'}
-
chalk@2.4.2:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
@@ -4043,10 +4006,6 @@ packages:
check-error@1.0.3:
resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
- check-error@2.1.1:
- resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
- engines: {node: '>= 16'}
-
check-more-types@2.24.0:
resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==}
engines: {node: '>= 0.8.0'}
@@ -4472,10 +4431,6 @@ packages:
resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==}
engines: {node: '>=6'}
- deep-eql@5.0.2:
- resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
- engines: {node: '>=6'}
-
deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
@@ -4796,9 +4751,6 @@ packages:
es-module-lexer@0.9.3:
resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==}
- es-module-lexer@1.6.0:
- resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==}
-
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -5045,9 +4997,6 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
- estree-walker@3.0.3:
- resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
-
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -5103,10 +5052,6 @@ packages:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
- expect-type@1.2.0:
- resolution: {integrity: sha512-80F22aiJ3GLyVnS/B3HzgR6RelZVumzj9jkL0Rhz4h0xYbNW9PjlQz5h3J/SShErbXBc295vseR4/MIbVmUbeA==}
- engines: {node: '>=12.0.0'}
-
exponential-backoff@3.1.2:
resolution: {integrity: sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==}
@@ -6403,9 +6348,6 @@ packages:
loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
- loupe@3.1.3:
- resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==}
-
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
@@ -6487,8 +6429,8 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
- mc-assets@0.2.59:
- resolution: {integrity: sha512-HGdy6v09X5nks8+NuwrL3KQ763D+eWFeSpWLXx3+doWz6hSEeLjgBPOrB1stvQOjPDiQCzsIv5gaRB5sl6ng1A==}
+ mc-assets@0.2.62:
+ resolution: {integrity: sha512-RYZeD1+joNlPuUpi+tIWkbP0ieVJr+R6IFkI6/8juhSxx9zE4osoSmteybrfspGm8A6u+YbbY1epqRKEMwVR6Q==}
engines: {node: '>=18.0.0'}
mcraft-fun-mineflayer@0.1.23:
@@ -6699,19 +6641,19 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
- minecraft-data@3.89.0:
- resolution: {integrity: sha512-v6dUr1M7Pjc6N4ujanrBZu3IP4/HbSBpxSSXNbK6HVFVJqfaqKSMXN57G/JAlDcwqXYsVd9H4tbKFHCO+VmQpg==}
+ minecraft-data@3.92.0:
+ resolution: {integrity: sha512-CGfO50svzm+pSRa4Mbq4owsmRKbPCNkSZ3MCOyH+epC7yNjh+PUhPQFHWq72O51qsY7pAB5qM/bJn1ncwG1J5g==}
minecraft-folder-path@1.2.0:
resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==}
- minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5494f356b1e59eddc876c3ae05ff395f12a46379:
- resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5494f356b1e59eddc876c3ae05ff395f12a46379}
+ minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41:
+ resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41}
version: 1.0.1
- minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284:
- resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284}
- version: 1.57.0
+ minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176:
+ resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176}
+ version: 1.58.0
engines: {node: '>=22'}
minecraft-wrap@1.6.0:
@@ -6725,20 +6667,20 @@ packages:
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824}
version: 1.2.0
- mineflayer-mouse@0.1.10:
- resolution: {integrity: sha512-bxrBzOVQX2Ok5KOiJMaoOLYaHMDecMkJpLn5oGIdyLRqiTF3WAnZc9oYQq+/f3YNA88W1vBIjai3v3vZe6wyaQ==}
+ mineflayer-mouse@0.1.11:
+ resolution: {integrity: sha512-BL47pXZ1+92BA/7ym6KaJctEHKnL0up+tpuagVwSKJvAgibeqWQJJwDlNUWkOLvpnruRKDxMR5OB1hUXFoDNSg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
mineflayer-pathfinder@2.4.5:
resolution: {integrity: sha512-Jh3JnUgRLwhMh2Dugo4SPza68C41y+NPP5sdsgxRu35ydndo70i1JJGxauVWbXrpNwIxYNztUw78aFyb7icw8g==}
- mineflayer@4.27.0:
- resolution: {integrity: sha512-3bxph4jfbkBh5HpeouorxzrfSLNV+i+1gugNJ2jf52HW+rt+tW7eiiFPxrJEsOVkPT/3O/dEIW7j93LRlojMkQ==}
+ mineflayer@4.30.0:
+ resolution: {integrity: sha512-GtW4hkijyZbSu5LKYYD89xZu+XY7OoP7IkrCnNEn6EdPm0+vr2THoJgFGKrlze9/81+T+P3E4qvJXNFiU/zeJg==}
engines: {node: '>=22'}
- mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/0380bed150fe03db4ac37f0194e0cee98356647d:
- resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/0380bed150fe03db4ac37f0194e0cee98356647d}
- version: 4.27.0
+ mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980:
+ resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980}
+ version: 4.30.0
engines: {node: '>=22'}
minimalistic-assert@1.0.1:
@@ -7271,10 +7213,6 @@ packages:
pathval@1.1.1:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
- pathval@2.0.0:
- resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
- engines: {node: '>= 14.16'}
-
pause-stream@0.0.11:
resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
@@ -7436,7 +7374,7 @@ packages:
prismarine-biome@1.3.0:
resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==}
peerDependencies:
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
prismarine-registry: ^1.1.0
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
@@ -8716,22 +8654,10 @@ packages:
resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==}
engines: {node: '>=14.0.0'}
- tinypool@1.0.2:
- resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==}
- engines: {node: ^18.0.0 || >=20.0.0}
-
- tinyrainbow@2.0.0:
- resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
- engines: {node: '>=14.0.0'}
-
tinyspy@2.2.1:
resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==}
engines: {node: '>=14.0.0'}
- tinyspy@3.0.2:
- resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
- engines: {node: '>=14.0.0'}
-
title-case@3.0.3:
resolution: {integrity: sha512-e1zGYRvbffpcHIrnuqT0Dh+gEJtDaxDSoG4JAIpq4oDFyooziLBIiYQv0GBT4FUAnUop5uZ1hiIAj7oAF6sOCA==}
@@ -9218,11 +9144,6 @@ packages:
engines: {node: '>=v14.18.0'}
hasBin: true
- vite-node@3.0.8:
- resolution: {integrity: sha512-6PhR4H9VGlcwXZ+KWCdMqbtG649xCPZqfI9j2PsK1FcXgEzro5bGHcVKFCTqPLaNKZES8Evqv4LwvZARsq5qlg==}
- engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
- hasBin: true
-
vite@4.5.9:
resolution: {integrity: sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==}
engines: {node: ^14.18.0 || >=16.0.0}
@@ -9322,34 +9243,6 @@ packages:
webdriverio:
optional: true
- vitest@3.0.8:
- resolution: {integrity: sha512-dfqAsNqRGUc8hB9OVR2P0w8PZPEckti2+5rdZip0WIz9WW0MnImJ8XiR61QhqLa92EQzKP2uPkzenKOAHyEIbA==}
- engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
- hasBin: true
- peerDependencies:
- '@edge-runtime/vm': '*'
- '@types/debug': ^4.1.12
- '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
- '@vitest/browser': 3.0.8
- '@vitest/ui': 3.0.8
- happy-dom: '*'
- jsdom: '*'
- peerDependenciesMeta:
- '@edge-runtime/vm':
- optional: true
- '@types/debug':
- optional: true
- '@types/node':
- optional: true
- '@vitest/browser':
- optional: true
- '@vitest/ui':
- optional: true
- happy-dom:
- optional: true
- jsdom:
- optional: true
-
vm-browserify@1.1.2:
resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
@@ -11422,8 +11315,8 @@ snapshots:
'@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)':
dependencies:
'@nxg-org/mineflayer-util-plugin': 1.8.4
- minecraft-data: 3.89.0
- mineflayer: 4.27.0(encoding@0.1.13)
+ minecraft-data: 3.92.0
+ mineflayer: 4.30.0(encoding@0.1.13)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-item: 1.16.0
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
@@ -13086,68 +12979,28 @@ snapshots:
'@vitest/utils': 0.34.6
chai: 4.5.0
- '@vitest/expect@3.0.8':
- dependencies:
- '@vitest/spy': 3.0.8
- '@vitest/utils': 3.0.8
- chai: 5.2.0
- tinyrainbow: 2.0.0
-
- '@vitest/mocker@3.0.8(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))':
- dependencies:
- '@vitest/spy': 3.0.8
- estree-walker: 3.0.3
- magic-string: 0.30.17
- optionalDependencies:
- 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)
-
- '@vitest/pretty-format@3.0.8':
- dependencies:
- tinyrainbow: 2.0.0
-
'@vitest/runner@0.34.6':
dependencies:
'@vitest/utils': 0.34.6
p-limit: 4.0.0
pathe: 1.1.2
- '@vitest/runner@3.0.8':
- dependencies:
- '@vitest/utils': 3.0.8
- pathe: 2.0.3
-
'@vitest/snapshot@0.34.6':
dependencies:
magic-string: 0.30.17
pathe: 1.1.2
pretty-format: 29.7.0
- '@vitest/snapshot@3.0.8':
- dependencies:
- '@vitest/pretty-format': 3.0.8
- magic-string: 0.30.17
- pathe: 2.0.3
-
'@vitest/spy@0.34.6':
dependencies:
tinyspy: 2.2.1
- '@vitest/spy@3.0.8':
- dependencies:
- tinyspy: 3.0.2
-
'@vitest/utils@0.34.6':
dependencies:
diff-sequences: 29.6.3
loupe: 2.3.7
pretty-format: 29.7.0
- '@vitest/utils@3.0.8':
- dependencies:
- '@vitest/pretty-format': 3.0.8
- loupe: 3.1.3
- tinyrainbow: 2.0.0
-
'@xboxreplay/errors@0.1.0': {}
'@xboxreplay/xboxlive-auth@3.3.3(debug@4.4.0)':
@@ -13223,16 +13076,16 @@ snapshots:
exit-hook: 2.2.1
flatmap: 0.0.3
long: 5.3.1
- minecraft-data: 3.89.0
- minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762)(encoding@0.1.13)
+ minecraft-data: 3.92.0
+ minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(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.89.0)
+ prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-entity: 2.5.0
prismarine-item: 1.16.0
prismarine-nbt: 2.7.0
- prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.89.0)
+ prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
prismarine-windows: 2.9.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
rambda: 9.4.2
@@ -13259,16 +13112,16 @@ snapshots:
exit-hook: 2.2.1
flatmap: 0.0.3
long: 5.3.1
- minecraft-data: 3.89.0
- minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762)(encoding@0.1.13)
+ minecraft-data: 3.92.0
+ minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(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.89.0)
+ prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-entity: 2.5.0
prismarine-item: 1.16.0
prismarine-nbt: 2.7.0
- prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.89.0)
+ prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
prismarine-windows: 2.9.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
rambda: 9.4.2
@@ -13562,8 +13415,6 @@ snapshots:
assertion-error@1.1.0: {}
- assertion-error@2.0.1: {}
-
assign-symbols@1.0.0: {}
ast-types@0.16.1:
@@ -13995,14 +13846,6 @@ snapshots:
pathval: 1.1.1
type-detect: 4.1.0
- chai@5.2.0:
- dependencies:
- assertion-error: 2.0.1
- check-error: 2.1.1
- deep-eql: 5.0.2
- loupe: 3.1.3
- pathval: 2.0.0
-
chalk@2.4.2:
dependencies:
ansi-styles: 3.2.1
@@ -14041,8 +13884,6 @@ snapshots:
dependencies:
get-func-name: 2.0.2
- check-error@2.1.1: {}
-
check-more-types@2.24.0:
optional: true
@@ -14557,8 +14398,6 @@ snapshots:
dependencies:
type-detect: 4.1.0
- deep-eql@5.0.2: {}
-
deep-extend@0.6.0: {}
deep-is@0.1.4: {}
@@ -14673,8 +14512,8 @@ snapshots:
diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71:
dependencies:
- minecraft-data: 3.89.0
- prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
+ minecraft-data: 3.92.0
+ prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-registry: 1.11.0
random-seed: 0.3.0
vec3: 0.1.10
@@ -15056,8 +14895,6 @@ snapshots:
es-module-lexer@0.9.3: {}
- es-module-lexer@1.6.0: {}
-
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -15440,10 +15277,6 @@ snapshots:
estree-walker@2.0.2: {}
- estree-walker@3.0.3:
- dependencies:
- '@types/estree': 1.0.6
-
esutils@2.0.3: {}
etag@1.8.1: {}
@@ -15521,8 +15354,6 @@ snapshots:
expand-template@2.0.3: {}
- expect-type@1.2.0: {}
-
exponential-backoff@3.1.2:
optional: true
@@ -17024,8 +16855,6 @@ snapshots:
dependencies:
get-func-name: 2.0.2
- loupe@3.1.3: {}
-
lower-case@2.0.2:
dependencies:
tslib: 2.8.1
@@ -17122,17 +16951,17 @@ snapshots:
math-intrinsics@1.1.0: {}
- mc-assets@0.2.59:
+ mc-assets@0.2.62:
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/0380bed150fe03db4ac37f0194e0cee98356647d(encoding@0.1.13)):
+ mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(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=09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762)(encoding@0.1.13)
- mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/0380bed150fe03db4ac37f0194e0cee98356647d(encoding@0.1.13)
+ minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13)
+ mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)
prismarine-item: 1.16.0
ws: 8.18.1
transitivePeerDependencies:
@@ -17439,18 +17268,18 @@ snapshots:
min-indent@1.0.1: {}
- minecraft-data@3.89.0: {}
+ minecraft-data@3.92.0: {}
minecraft-folder-path@1.2.0: {}
- minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5494f356b1e59eddc876c3ae05ff395f12a46379(@types/react@18.3.18)(react@18.3.1):
+ minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@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=09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762)(encoding@0.1.13):
+ minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13):
dependencies:
'@types/node-rsa': 1.1.4
'@types/readable-stream': 4.0.18
@@ -17459,7 +17288,7 @@ snapshots:
debug: 4.4.0(supports-color@8.1.1)
endian-toggle: 0.0.0
lodash.merge: 4.6.2
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
minecraft-folder-path: 1.2.0
node-fetch: 2.7.0(encoding@0.1.13)
node-rsa: 0.4.2
@@ -17502,43 +17331,24 @@ snapshots:
mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13):
dependencies:
- mineflayer: 4.27.0(encoding@0.1.13)
+ mineflayer: 4.30.0(encoding@0.1.13)
sharp: 0.30.7
transitivePeerDependencies:
- encoding
- supports-color
- mineflayer-mouse@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):
+ mineflayer-mouse@0.1.11:
dependencies:
change-case: 5.4.4
- debug: 4.4.0(supports-color@8.1.1)
+ debug: 4.4.1
prismarine-item: 1.16.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
- vitest: 3.0.8(@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)
transitivePeerDependencies:
- - '@edge-runtime/vm'
- - '@types/debug'
- - '@types/node'
- - '@vitest/browser'
- - '@vitest/ui'
- - happy-dom
- - jiti
- - jsdom
- - less
- - lightningcss
- - msw
- - sass
- - sass-embedded
- - stylus
- - sugarss
- supports-color
- - terser
- - tsx
- - yaml
mineflayer-pathfinder@2.4.5:
dependencies:
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-entity: 2.5.0
prismarine-item: 1.16.0
@@ -17546,14 +17356,14 @@ snapshots:
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
vec3: 0.1.10
- mineflayer@4.27.0(encoding@0.1.13):
+ mineflayer@4.30.0(encoding@0.1.13):
dependencies:
- minecraft-data: 3.89.0
- minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(patch_hash=09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762)(encoding@0.1.13)
- prismarine-biome: 1.3.0(minecraft-data@3.89.0)(prismarine-registry@1.11.0)
+ minecraft-data: 3.92.0
+ minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13)
+ prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-chat: 1.11.0
- prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
+ prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-entity: 2.5.0
prismarine-item: 1.16.0
prismarine-nbt: 2.7.0
@@ -17569,15 +17379,15 @@ snapshots:
- encoding
- supports-color
- mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/0380bed150fe03db4ac37f0194e0cee98356647d(encoding@0.1.13):
+ mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13):
dependencies:
'@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=09def7a73311f10b6aa8ff9f9b76129589578fa154a8b846b06909ca748c4762)(encoding@0.1.13)
- prismarine-biome: 1.3.0(minecraft-data@3.89.0)(prismarine-registry@1.11.0)
+ minecraft-data: 3.92.0
+ minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13)
+ prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-chat: 1.11.0
- prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
+ prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-entity: 2.5.0
prismarine-item: 1.16.0
prismarine-nbt: 2.7.0
@@ -18202,8 +18012,6 @@ snapshots:
pathval@1.1.1: {}
- pathval@2.0.0: {}
-
pause-stream@0.0.11:
dependencies:
through: 2.3.8
@@ -18263,7 +18071,7 @@ snapshots:
pirates@4.0.6: {}
- pixelarticons@1.8.1(patch_hash=d6a3d784047beba873565d1198bed425d9eb2de942e3fc8edac55f25473e4325): {}
+ pixelarticons@1.8.1(patch_hash=533230072bc402f425c86abd3d0356fe087b14cab2a254d93f419b083f2d8dfa): {}
pixelmatch@4.0.2:
dependencies:
@@ -18363,15 +18171,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
- prismarine-biome@1.3.0(minecraft-data@3.89.0)(prismarine-registry@1.11.0):
+ prismarine-biome@1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0):
dependencies:
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
prismarine-registry: 1.11.0
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
dependencies:
- minecraft-data: 3.89.0
- prismarine-biome: 1.3.0(minecraft-data@3.89.0)(prismarine-registry@1.11.0)
+ minecraft-data: 3.92.0
+ prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
prismarine-chat: 1.11.0
prismarine-item: 1.16.0
prismarine-nbt: 2.7.0
@@ -18383,9 +18191,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.89.0):
+ prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0):
dependencies:
- prismarine-biome: 1.3.0(minecraft-data@3.89.0)(prismarine-registry@1.11.0)
+ prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-nbt: 2.7.0
prismarine-registry: 1.11.0
@@ -18414,14 +18222,14 @@ snapshots:
prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b:
dependencies:
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
prismarine-nbt: 2.7.0
vec3: 0.1.10
- prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.89.0):
+ prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0):
dependencies:
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
- prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.89.0)
+ prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
prismarine-nbt: 2.7.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
uint4: 0.1.2
@@ -18443,13 +18251,13 @@ snapshots:
prismarine-registry@1.11.0:
dependencies:
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-nbt: 2.7.0
prismarine-schematic@1.2.3:
dependencies:
- minecraft-data: 3.89.0
+ minecraft-data: 3.92.0
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
prismarine-nbt: 2.7.0
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
@@ -20064,14 +19872,8 @@ snapshots:
tinypool@0.7.0: {}
- tinypool@1.0.2: {}
-
- tinyrainbow@2.0.0: {}
-
tinyspy@2.2.1: {}
- tinyspy@3.0.2: {}
-
title-case@3.0.3:
dependencies:
tslib: 2.8.1
@@ -20560,27 +20362,6 @@ snapshots:
- supports-color
- terser
- 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.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)
- transitivePeerDependencies:
- - '@types/node'
- - jiti
- - less
- - lightningcss
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - supports-color
- - terser
- - tsx
- - yaml
-
vite@4.5.9(@types/node@22.13.9)(terser@5.39.0):
dependencies:
esbuild: 0.18.20
@@ -20639,45 +20420,6 @@ snapshots:
- supports-color
- terser
- vitest@3.0.8(@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):
- dependencies:
- '@vitest/expect': 3.0.8
- '@vitest/mocker': 3.0.8(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))
- '@vitest/pretty-format': 3.0.8
- '@vitest/runner': 3.0.8
- '@vitest/snapshot': 3.0.8
- '@vitest/spy': 3.0.8
- '@vitest/utils': 3.0.8
- chai: 5.2.0
- debug: 4.4.1
- expect-type: 1.2.0
- magic-string: 0.30.17
- pathe: 2.0.3
- std-env: 3.8.1
- tinybench: 2.9.0
- tinyexec: 0.3.2
- tinypool: 1.0.2
- tinyrainbow: 2.0.0
- 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)
- 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)
- why-is-node-running: 2.3.0
- optionalDependencies:
- '@types/debug': 4.1.12
- '@types/node': 22.13.9
- transitivePeerDependencies:
- - jiti
- - less
- - lightningcss
- - msw
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - supports-color
- - terser
- - tsx
- - yaml
-
vm-browserify@1.1.2: {}
w3c-keyname@2.2.8: {}
diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts
index f9e4b2c9..af5d9d06 100644
--- a/renderer/viewer/lib/basePlayerState.ts
+++ b/renderer/viewer/lib/basePlayerState.ts
@@ -1,5 +1,5 @@
import { ItemSelector } from 'mc-assets/dist/itemDefinitions'
-import { GameMode } from 'mineflayer'
+import { GameMode, Team } from 'mineflayer'
import { proxy } from 'valtio'
import type { HandItemBlock } from '../three/holdingBlock'
@@ -50,6 +50,8 @@ export const getInitialPlayerState = () => proxy({
perspective: 'first_person' as CameraPerspective,
cameraSpectatingEntity: undefined as number | undefined,
+
+ team: undefined as Team | undefined,
})
export const getPlayerStateUtils = (reactive: PlayerStateReactive) => ({
diff --git a/renderer/viewer/lib/mesher/models.ts b/renderer/viewer/lib/mesher/models.ts
index f83f8c25..e2333a03 100644
--- a/renderer/viewer/lib/mesher/models.ts
+++ b/renderer/viewer/lib/mesher/models.ts
@@ -765,7 +765,6 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W
heads: {},
signs: {},
// isFull: true,
- highestBlocks: new Map(),
hadErrors: false,
blocksCount: 0,
instancedBlocks: {}
@@ -776,12 +775,6 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W
for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) {
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.get(`${cursor.x},${cursor.z}`)
- if (!highest || highest.y < cursor.y) {
- attr.highestBlocks.set(`${cursor.x},${cursor.z}`, { y: cursor.y, stateId: block.stateId, biomeId: block.biome.id })
- }
- }
if (INVISIBLE_BLOCKS.has(block.name)) continue
if ((block.name.includes('_sign') || block.name === 'sign') && !world.config.disableSignsMapsSupport) {
const key = `${cursor.x},${cursor.y},${cursor.z}`
diff --git a/renderer/viewer/lib/mesher/shared.ts b/renderer/viewer/lib/mesher/shared.ts
index b2881e8d..97f9cf1a 100644
--- a/renderer/viewer/lib/mesher/shared.ts
+++ b/renderer/viewer/lib/mesher/shared.ts
@@ -67,7 +67,6 @@ export type MesherGeometryOutput = {
heads: Record,
signs: Record,
// isFull: boolean
- highestBlocks: Map
hadErrors: boolean
blocksCount: number
customBlockModels?: CustomBlockModels
diff --git a/renderer/viewer/lib/utils/skins.ts b/renderer/viewer/lib/utils/skins.ts
index e0c8c32e..9aa97340 100644
--- a/renderer/viewer/lib/utils/skins.ts
+++ b/renderer/viewer/lib/utils/skins.ts
@@ -3,6 +3,20 @@ import * as THREE from 'three'
import stevePng from 'mc-assets/dist/other-textures/latest/entity/player/wide/steve.png'
import { getLoadedImage } from 'mc-assets/dist/utils'
+const detectFullOffscreenCanvasSupport = () => {
+ if (typeof OffscreenCanvas === 'undefined') return false
+ try {
+ const canvas = new OffscreenCanvas(1, 1)
+ // Try to get a WebGL context - this will fail on iOS where only 2D is supported (iOS 16)
+ const gl = canvas.getContext('webgl2') || canvas.getContext('webgl')
+ return gl !== null
+ } catch (e) {
+ return false
+ }
+}
+
+const hasFullOffscreenCanvasSupport = detectFullOffscreenCanvasSupport()
+
export const loadThreeJsTextureFromUrlSync = (imageUrl: string) => {
const texture = new THREE.Texture()
const promise = getLoadedImage(imageUrl).then(image => {
@@ -16,12 +30,22 @@ export const loadThreeJsTextureFromUrlSync = (imageUrl: string) => {
}
}
+export const createCanvas = (width: number, height: number): OffscreenCanvas => {
+ if (hasFullOffscreenCanvasSupport) {
+ return new OffscreenCanvas(width, height)
+ }
+ const canvas = document.createElement('canvas')
+ canvas.width = width
+ canvas.height = height
+ return canvas as unknown as OffscreenCanvas // todo-low
+}
+
export const loadThreeJsTextureFromUrl = async (imageUrl: string) => {
const loaded = new THREE.TextureLoader().loadAsync(imageUrl)
return loaded
}
export const loadThreeJsTextureFromBitmap = (image: ImageBitmap) => {
- const canvas = new OffscreenCanvas(image.width, image.height)
+ const canvas = createCanvas(image.width, image.height)
const ctx = canvas.getContext('2d')!
ctx.drawImage(image, 0, 0)
const texture = new THREE.Texture(canvas)
@@ -83,7 +107,7 @@ export async function loadSkinImage (skinUrl: string): Promise<{ canvas: Offscre
}
const image = await loadImageFromUrl(skinUrl)
- const skinCanvas = new OffscreenCanvas(64, 64)
+ const skinCanvas = createCanvas(64, 64)
loadSkinToCanvas(skinCanvas, image)
return { canvas: skinCanvas, image }
}
diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts
index 78da96d9..e1ac2f24 100644
--- a/renderer/viewer/lib/worldDataEmitter.ts
+++ b/renderer/viewer/lib/worldDataEmitter.ts
@@ -104,6 +104,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter
handleResize = () => { }
highestBlocksByChunks = new Map()
- highestBlocksBySections = new Map()
blockEntities = {}
workersProcessAverageTime = 0
@@ -393,8 +393,6 @@ export abstract class WorldRendererCommon
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
- 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])))
}
@@ -692,7 +690,6 @@ export abstract class WorldRendererCommon
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}`]
- this.highestBlocksBySections.delete(`${x},${y},${z}`)
}
this.highestBlocksByChunks.delete(`${x},${z}`)
diff --git a/renderer/viewer/three/entities.ts b/renderer/viewer/three/entities.ts
index b4de4f1b..c05aac99 100644
--- a/renderer/viewer/three/entities.ts
+++ b/renderer/viewer/three/entities.ts
@@ -16,9 +16,10 @@ import { Item } from 'prismarine-item'
import { BlockModel } from 'mc-assets'
import { isEntityAttackable } from 'mineflayer-mouse/dist/attackableEntity'
import { Vec3 } from 'vec3'
+import { Team } from 'mineflayer'
import { EntityMetadataVersions } from '../../../src/mcDataTypes'
import { ItemSpecificContextProperties } from '../lib/basePlayerState'
-import { loadSkinImage, loadSkinFromUsername, stevePngUrl, steveTexture } from '../lib/utils/skins'
+import { loadSkinImage, loadSkinFromUsername, stevePngUrl, steveTexture, createCanvas } from '../lib/utils/skins'
import { loadTexture } from '../lib/utils'
import { getBlockMeshFromModel } from './holdingBlock'
import * as Entity from './entity/EntityMesh'
@@ -96,7 +97,7 @@ function getUsernameTexture ({
nameTagBackgroundColor = 'rgba(0, 0, 0, 0.3)',
nameTagTextOpacity = 255
}: any, { fontFamily = 'sans-serif' }: any) {
- const canvas = new OffscreenCanvas(64, 64)
+ const canvas = createCanvas(64, 64)
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Could not get 2d context')
@@ -209,7 +210,7 @@ export type SceneEntity = THREE.Object3D & {
username?: string
uuid?: string
additionalCleanup?: () => void
- originalEntity: import('prismarine-entity').Entity & { delete?; pos?, name }
+ originalEntity: import('prismarine-entity').Entity & { delete?; pos?, name, team?: Team }
}
export class Entities {
@@ -485,6 +486,7 @@ export class Entities {
.some(channel => channel !== 0)
}
+ // todo true/undefined doesnt reset the skin to the default one
// eslint-disable-next-line max-params
async updatePlayerSkin (entityId: string | number, username: string | undefined, uuidCache: string | undefined, skinUrl: string | true, capeUrl: string | true | undefined = undefined) {
if (uuidCache) {
@@ -550,7 +552,7 @@ export class Entities {
let skinCanvas: OffscreenCanvas
if (skinUrl === stevePngUrl) {
skinTexture = await steveTexture
- const canvas = new OffscreenCanvas(64, 64)
+ const canvas = createCanvas(64, 64)
const ctx = canvas.getContext('2d')
if (!ctx) throw new Error('Failed to get context')
ctx.drawImage(skinTexture.image, 0, 0)
@@ -900,6 +902,8 @@ export class Entities {
nameTag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3
nameTag.renderOrder = 1000
+ nameTag.name = 'nametag'
+
//@ts-expect-error
wrapper.add(nameTag)
}
@@ -1111,10 +1115,12 @@ export class Entities {
}
}
- if (entity.username) {
+ if (entity.username !== undefined) {
e.username = entity.username
}
+ this.updateNameTagVisibility(e)
+
this.updateEntityPosition(entity, justAdded, overrides)
}
@@ -1183,6 +1189,20 @@ export class Entities {
}
}
+ updateNameTagVisibility (entity: SceneEntity) {
+ const playerTeam = this.worldRenderer.playerStateReactive.team
+ const entityTeam = entity.originalEntity.team
+ const nameTagVisibility = entityTeam?.nameTagVisibility || 'always'
+ const showNameTag = nameTagVisibility === 'always' ||
+ (nameTagVisibility === 'hideForOwnTeam' && entityTeam?.team !== playerTeam?.team) ||
+ (nameTagVisibility === 'hideForOtherTeams' && (entityTeam?.team === playerTeam?.team || playerTeam === undefined))
+ entity.traverse(c => {
+ if (c.name === 'nametag') {
+ c.visible = showNameTag
+ }
+ })
+ }
+
addMapModel (entityMesh: THREE.Object3D, mapNumber: number, rotation: number) {
const imageData = this.cachedMapsImages?.[mapNumber]
let texture: THREE.Texture | null = null
@@ -1404,6 +1424,11 @@ function addArmorModel (worldRenderer: WorldRendererThree, entityMesh: THREE.Obj
if (textureData) {
const decodedData = JSON.parse(Buffer.from(textureData, 'base64').toString())
texturePath = decodedData.textures?.SKIN?.url
+ const { skinTexturesProxy } = this.worldRenderer.worldRendererConfig
+ if (skinTexturesProxy) {
+ texturePath = texturePath?.replace('http://textures.minecraft.net/', skinTexturesProxy)
+ .replace('https://textures.minecraft.net/', skinTexturesProxy)
+ }
}
} catch (err) {
console.error('Error decoding player head texture:', err)
diff --git a/renderer/viewer/three/renderSlot.ts b/renderer/viewer/three/renderSlot.ts
index fd1eae91..d82e58e3 100644
--- a/renderer/viewer/three/renderSlot.ts
+++ b/renderer/viewer/three/renderSlot.ts
@@ -14,7 +14,6 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res
scale?: number,
slice?: number[],
modelName?: string,
- image?: ImageBitmap
} | undefined => {
let itemModelName = model.modelName
const isItem = loadedData.itemsByName[itemModelName]
@@ -36,7 +35,6 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res
const y = item.v * atlas.height
return {
texture: 'gui',
- image: resourcesManager.currentResources!.guiAtlas!.image,
slice: [x, y, atlas.tileSize, atlas.tileSize],
scale: 0.25,
}
diff --git a/renderer/viewer/three/threeJsSound.ts b/renderer/viewer/three/threeJsSound.ts
index 627cabf8..46aefda9 100644
--- a/renderer/viewer/three/threeJsSound.ts
+++ b/renderer/viewer/three/threeJsSound.ts
@@ -39,7 +39,9 @@ export class ThreeJsSound implements SoundSystem {
sound.position.set(position.x, position.y, position.z)
sound.onEnded = () => {
this.worldRenderer.scene.remove(sound)
- sound.disconnect()
+ if (sound.source) {
+ sound.disconnect()
+ }
this.activeSounds.delete(sound)
audioLoader.manager.itemEnd(path)
}
@@ -51,7 +53,9 @@ export class ThreeJsSound implements SoundSystem {
// Stop and clean up all active sounds
for (const sound of this.activeSounds) {
sound.stop()
- sound.disconnect()
+ if (sound.source) {
+ sound.disconnect()
+ }
}
// Remove and cleanup audio listener
diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts
index 8de36887..82080b3f 100644
--- a/renderer/viewer/three/worldrendererThree.ts
+++ b/renderer/viewer/three/worldrendererThree.ts
@@ -775,7 +775,12 @@ export class WorldRendererThree extends WorldRendererCommon {
try {
const textureData = JSON.parse(Buffer.from(textures.Value, 'base64').toString())
- const skinUrl = textureData.textures?.SKIN?.url
+ let skinUrl = textureData.textures?.SKIN?.url
+ const { skinTexturesProxy } = this.worldRendererConfig
+ if (skinTexturesProxy) {
+ skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy)
+ .replace('https://textures.minecraft.net/', skinTexturesProxy)
+ }
const mesh = getMesh(this, skinUrl, armorModel.head)
const group = new THREE.Group()
diff --git a/rsbuild.config.ts b/rsbuild.config.ts
index 548be4e2..e264f6b7 100644
--- a/rsbuild.config.ts
+++ b/rsbuild.config.ts
@@ -1,3 +1,4 @@
+///
import { defineConfig, mergeRsbuildConfig, RsbuildPluginAPI } from '@rsbuild/core'
import { pluginReact } from '@rsbuild/plugin-react'
import { pluginTypedCSSModules } from '@rsbuild/plugin-typed-css-modules'
@@ -14,6 +15,7 @@ import { appAndRendererSharedConfig } from './renderer/rsbuildSharedConfig'
import { genLargeDataAliases } from './scripts/genLargeDataAliases'
import sharp from 'sharp'
import supportedVersions from './src/supportedVersions.mjs'
+import { startWsServer } from './scripts/wsServer'
const SINGLE_FILE_BUILD = process.env.SINGLE_FILE_BUILD === 'true'
@@ -48,7 +50,7 @@ if (fs.existsSync('./assets/release.json')) {
const configJson = JSON.parse(fs.readFileSync('./config.json', 'utf8'))
try {
- Object.assign(configJson, JSON.parse(fs.readFileSync('./config.local.json', 'utf8')))
+ Object.assign(configJson, JSON.parse(fs.readFileSync(process.env.LOCAL_CONFIG_FILE || './config.local.json', 'utf8')))
} catch (err) {}
if (dev) {
configJson.defaultProxy = ':8080'
@@ -58,6 +60,8 @@ const configSource = (SINGLE_FILE_BUILD ? 'BUNDLED' : (process.env.CONFIG_JSON_S
const faviconPath = 'favicon.png'
+const enableMetrics = process.env.ENABLE_METRICS === 'true'
+
// base options are in ./renderer/rsbuildSharedConfig.ts
const appConfig = defineConfig({
html: {
@@ -111,6 +115,22 @@ const appConfig = defineConfig({
js: 'source-map',
css: true,
},
+ minify: {
+ // js: false,
+ jsOptions: {
+ minimizerOptions: {
+ mangle: {
+ safari10: true,
+ keep_classnames: true,
+ keep_fnames: true,
+ keep_private_props: true,
+ },
+ compress: {
+ unused: true,
+ },
+ },
+ },
+ },
distPath: SINGLE_FILE_BUILD ? {
html: './single',
} : undefined,
@@ -141,6 +161,8 @@ const appConfig = defineConfig({
'process.env.DISABLE_SERVICE_WORKER': JSON.stringify(disableServiceWorker),
'process.env.INLINED_APP_CONFIG': JSON.stringify(configSource === 'BUNDLED' ? configJson : null),
'process.env.ENABLE_COOKIE_STORAGE': JSON.stringify(process.env.ENABLE_COOKIE_STORAGE || true),
+ 'process.env.COOKIE_STORAGE_PREFIX': JSON.stringify(process.env.COOKIE_STORAGE_PREFIX || ''),
+ 'process.env.WS_PORT': JSON.stringify(enableMetrics ? 8081 : false),
},
},
server: {
@@ -175,13 +197,14 @@ const appConfig = defineConfig({
fs.copyFileSync('./assets/playground.html', './dist/playground.html')
fs.copyFileSync('./assets/manifest.json', './dist/manifest.json')
fs.copyFileSync('./assets/config.html', './dist/config.html')
+ fs.copyFileSync('./assets/debug-inputs.html', './dist/debug-inputs.html')
fs.copyFileSync('./assets/loading-bg.jpg', './dist/loading-bg.jpg')
if (fs.existsSync('./assets/release.json')) {
fs.copyFileSync('./assets/release.json', './dist/release.json')
}
if (configSource === 'REMOTE') {
- fs.writeFileSync('./dist/config.json', JSON.stringify(configJson), 'utf8')
+ fs.writeFileSync('./dist/config.json', JSON.stringify(configJson, undefined, 2), 'utf8')
}
if (fs.existsSync('./generated/sounds.js')) {
fs.copyFileSync('./generated/sounds.js', './dist/sounds.js')
@@ -197,6 +220,12 @@ const appConfig = defineConfig({
await execAsync('pnpm run build-mesher')
}
fs.writeFileSync('./dist/version.txt', buildingVersion, 'utf-8')
+
+ // Start WebSocket server in development
+ if (dev && enableMetrics) {
+ await startWsServer(8081, false)
+ }
+
console.timeEnd('total-prep')
}
if (!dev) {
diff --git a/scripts/requestData.ts b/scripts/requestData.ts
new file mode 100644
index 00000000..dc866a1b
--- /dev/null
+++ b/scripts/requestData.ts
@@ -0,0 +1,42 @@
+import WebSocket from 'ws'
+
+function formatBytes(bytes: number) {
+ return `${(bytes).toFixed(2)} MB`
+}
+
+function formatTime(ms: number) {
+ return `${(ms / 1000).toFixed(2)}s`
+}
+
+const ws = new WebSocket('ws://localhost:8081')
+
+ws.on('open', () => {
+ console.log('Connected to metrics server, waiting for metrics...')
+})
+
+ws.on('message', (data) => {
+ try {
+ const metrics = JSON.parse(data.toString())
+ console.log('\nPerformance Metrics:')
+ console.log('------------------')
+ console.log(`Load Time: ${formatTime(metrics.loadTime)}`)
+ console.log(`Memory Usage: ${formatBytes(metrics.memoryUsage)}`)
+ console.log(`Timestamp: ${new Date(metrics.timestamp).toLocaleString()}`)
+ if (!process.argv.includes('-f')) { // follow mode
+ process.exit(0)
+ }
+ } catch (error) {
+ console.error('Error parsing metrics:', error)
+ }
+})
+
+ws.on('error', (error) => {
+ console.error('WebSocket error:', error)
+ process.exit(1)
+})
+
+// Exit if no metrics received after 5 seconds
+setTimeout(() => {
+ console.error('Timeout waiting for metrics')
+ process.exit(1)
+}, 5000)
diff --git a/scripts/updateGitDeps.ts b/scripts/updateGitDeps.ts
new file mode 100644
index 00000000..797aea8f
--- /dev/null
+++ b/scripts/updateGitDeps.ts
@@ -0,0 +1,160 @@
+import fs from 'fs'
+import path from 'path'
+import yaml from 'yaml'
+import { execSync } from 'child_process'
+import { createInterface } from 'readline'
+
+interface LockfilePackage {
+ specifier: string
+ version: string
+}
+
+interface Lockfile {
+ importers: {
+ '.': {
+ dependencies?: Record
+ devDependencies?: Record
+ }
+ }
+}
+
+interface PackageJson {
+ pnpm?: {
+ updateConfig?: {
+ ignoreDependencies?: string[]
+ }
+ }
+}
+
+async function prompt(question: string): Promise {
+ const rl = createInterface({
+ input: process.stdin,
+ output: process.stdout
+ })
+
+ return new Promise(resolve => {
+ rl.question(question, answer => {
+ rl.close()
+ resolve(answer.toLowerCase().trim())
+ })
+ })
+}
+
+async function getLatestCommit(owner: string, repo: string): Promise {
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/commits/HEAD`)
+ if (!response.ok) {
+ throw new Error(`Failed to fetch latest commit: ${response.statusText}`)
+ }
+ const data = await response.json()
+ return data.sha
+}
+
+function extractGitInfo(specifier: string): { owner: string; repo: string; branch: string } | null {
+ const match = specifier.match(/github:([^/]+)\/([^#]+)(?:#(.+))?/)
+ if (!match) return null
+ return {
+ owner: match[1],
+ repo: match[2],
+ branch: match[3] || 'master'
+ }
+}
+
+function extractCommitHash(version: string): string | null {
+ const match = version.match(/https:\/\/codeload\.github\.com\/[^/]+\/[^/]+\/tar\.gz\/([a-f0-9]+)/)
+ return match ? match[1] : null
+}
+
+function getIgnoredDependencies(): string[] {
+ try {
+ const packageJsonPath = path.join(process.cwd(), 'package.json')
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as PackageJson
+ return packageJson.pnpm?.updateConfig?.ignoreDependencies || []
+ } catch (error) {
+ console.warn('Failed to read package.json for ignored dependencies:', error)
+ return []
+ }
+}
+
+async function main() {
+ const lockfilePath = path.join(process.cwd(), 'pnpm-lock.yaml')
+ const lockfileContent = fs.readFileSync(lockfilePath, 'utf8')
+ const lockfile = yaml.parse(lockfileContent) as Lockfile
+
+ const ignoredDependencies = new Set(getIgnoredDependencies())
+ console.log('Ignoring dependencies:', Array.from(ignoredDependencies).join(', ') || 'none')
+
+ const dependencies = {
+ ...lockfile.importers['.'].dependencies,
+ ...lockfile.importers['.'].devDependencies
+ }
+
+ const updates: Array<{
+ name: string
+ currentHash: string
+ latestHash: string
+ gitInfo: ReturnType
+ }> = []
+
+ console.log('\nChecking git dependencies...')
+ for (const [name, pkg] of Object.entries(dependencies)) {
+ if (ignoredDependencies.has(name)) {
+ console.log(`Skipping ignored dependency: ${name}`)
+ continue
+ }
+
+ if (!pkg.specifier.startsWith('github:')) continue
+
+ const gitInfo = extractGitInfo(pkg.specifier)
+ if (!gitInfo) continue
+
+ const currentHash = extractCommitHash(pkg.version)
+ if (!currentHash) continue
+
+ try {
+ process.stdout.write(`Checking ${name}... `)
+ const latestHash = await getLatestCommit(gitInfo.owner, gitInfo.repo)
+ if (currentHash !== latestHash) {
+ console.log('update available')
+ updates.push({ name, currentHash, latestHash, gitInfo })
+ } else {
+ console.log('up to date')
+ }
+ } catch (error) {
+ console.log('failed')
+ console.error(`Error checking ${name}:`, error)
+ }
+ }
+
+ if (updates.length === 0) {
+ console.log('\nAll git dependencies are up to date!')
+ return
+ }
+
+ console.log('\nThe following git dependencies can be updated:')
+ for (const update of updates) {
+ console.log(`\n${update.name}:`)
+ console.log(` Current: ${update.currentHash}`)
+ console.log(` Latest: ${update.latestHash}`)
+ console.log(` Repo: ${update.gitInfo!.owner}/${update.gitInfo!.repo}`)
+ }
+
+ const answer = await prompt('\nWould you like to update these dependencies? (y/N): ')
+ if (answer === 'y' || answer === 'yes') {
+ let newLockfileContent = lockfileContent
+ for (const update of updates) {
+ newLockfileContent = newLockfileContent.replace(
+ new RegExp(update.currentHash, 'g'),
+ update.latestHash
+ )
+ }
+ fs.writeFileSync(lockfilePath, newLockfileContent)
+ console.log('\nUpdated pnpm-lock.yaml with new commit hashes')
+ // console.log('Running pnpm install to apply changes...')
+ // execSync('pnpm install', { stdio: 'inherit' })
+ console.log('Done!')
+ } else {
+ console.log('\nNo changes were made.')
+ }
+}
+
+main().catch(console.error)
diff --git a/scripts/wsServer.ts b/scripts/wsServer.ts
new file mode 100644
index 00000000..43035f52
--- /dev/null
+++ b/scripts/wsServer.ts
@@ -0,0 +1,45 @@
+import {WebSocketServer} from 'ws'
+
+export function startWsServer(port: number = 8081, tryOtherPort: boolean = true): Promise {
+ return new Promise((resolve, reject) => {
+ const tryPort = (currentPort: number) => {
+ const wss = new WebSocketServer({ port: currentPort })
+ .on('listening', () => {
+ console.log(`WebSocket server started on port ${currentPort}`)
+ resolve(currentPort)
+ })
+ .on('error', (err: any) => {
+ if (err.code === 'EADDRINUSE' && tryOtherPort) {
+ console.log(`Port ${currentPort} in use, trying ${currentPort + 1}`)
+ wss.close()
+ tryPort(currentPort + 1)
+ } else {
+ reject(err)
+ }
+ })
+
+ wss.on('connection', (ws) => {
+ console.log('Client connected')
+
+ ws.on('message', (message) => {
+ try {
+ // Simply relay the message to all connected clients except sender
+ wss.clients.forEach(client => {
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
+ client.send(message.toString())
+ }
+ })
+ } catch (error) {
+ console.error('Error processing message:', error)
+ }
+ })
+
+ ws.on('close', () => {
+ console.log('Client disconnected')
+ })
+ })
+ }
+
+ tryPort(port)
+ })
+}
diff --git a/src/appConfig.ts b/src/appConfig.ts
index 261ec7d3..92fde21a 100644
--- a/src/appConfig.ts
+++ b/src/appConfig.ts
@@ -24,6 +24,7 @@ export type MobileButtonConfig = {
readonly icon?: string
readonly action?: ActionType
readonly actionHold?: ActionType | ActionHoldConfig
+ readonly iconStyle?: React.CSSProperties
}
export type AppConfig = {
@@ -54,9 +55,14 @@ export type AppConfig = {
supportedLanguages?: string[]
showModsButton?: boolean
defaultUsername?: string
+ skinTexturesProxy?: string
+ alwaysReconnectButton?: boolean
+ reportBugButtonWithReconnect?: boolean
+ disabledCommands?: string[] // Array of command IDs to disable (e.g. ['general.jump', 'general.chat'])
}
export const loadAppConfig = (appConfig: AppConfig) => {
+
if (miscUiState.appConfig) {
Object.assign(miscUiState.appConfig, appConfig)
} else {
@@ -68,7 +74,7 @@ export const loadAppConfig = (appConfig: AppConfig) => {
if (value) {
disabledSettings.value.add(key)
// since the setting is forced, we need to set it to that value
- if (appConfig.defaultSettings?.[key] && !qsOptions[key]) {
+ if (appConfig.defaultSettings && key in appConfig.defaultSettings && !qsOptions[key]) {
options[key] = appConfig.defaultSettings[key]
}
} else {
@@ -76,12 +82,15 @@ export const loadAppConfig = (appConfig: AppConfig) => {
}
}
}
+ // todo apply defaultSettings to defaults even if not forced in case of remote config
if (appConfig.keybindings) {
Object.assign(customKeymaps, defaultsDeep(appConfig.keybindings, customKeymaps))
updateBinds(customKeymaps)
}
+ appViewer?.appConfigUdpate()
+
setStorageDataOnAppConfigLoad(appConfig)
}
diff --git a/src/appStatus.ts b/src/appStatus.ts
index 054975f3..101714f5 100644
--- a/src/appStatus.ts
+++ b/src/appStatus.ts
@@ -1,3 +1,4 @@
+import { resetStateAfterDisconnect } from './browserfs'
import { hideModal, activeModalStack, showModal, miscUiState } from './globalState'
import { appStatusState, resetAppStatusState } from './react/AppStatusProvider'
@@ -25,7 +26,6 @@ export const setLoadingScreenStatus = function (status: string | undefined | nul
}
showModal({ reactType: 'app-status' })
if (appStatusState.isError) {
- miscUiState.gameLoaded = false
return
}
appStatusState.hideDots = hideDots
@@ -33,5 +33,9 @@ export const setLoadingScreenStatus = function (status: string | undefined | nul
appStatusState.lastStatus = isError ? appStatusState.status : ''
appStatusState.status = status
appStatusState.minecraftJsonMessage = minecraftJsonMessage ?? null
+
+ if (isError && miscUiState.gameLoaded) {
+ resetStateAfterDisconnect()
+ }
}
globalThis.setLoadingScreenStatus = setLoadingScreenStatus
diff --git a/src/appViewer.ts b/src/appViewer.ts
index 0be6c2e8..05b1cf41 100644
--- a/src/appViewer.ts
+++ b/src/appViewer.ts
@@ -180,6 +180,12 @@ export class AppViewer {
this.worldView!.listenToBot(bot)
}
+ appConfigUdpate () {
+ if (miscUiState.appConfig) {
+ this.inWorldRenderingConfig.skinTexturesProxy = miscUiState.appConfig.skinTexturesProxy
+ }
+ }
+
async startWorld (world, renderDistance: number, playerStateSend: PlayerStateRenderer = this.playerState.reactive) {
if (this.currentDisplay === 'world') throw new Error('World already started')
this.currentDisplay = 'world'
@@ -187,6 +193,7 @@ export class AppViewer {
this.worldView = new WorldDataEmitter(world, renderDistance, startPosition)
window.worldView = this.worldView
watchOptionsAfterWorldViewInit(this.worldView)
+ this.appConfigUdpate()
const displayWorldOptions: DisplayWorldOptions = {
version: this.resourcesManager.currentConfig!.version,
diff --git a/src/controls.ts b/src/controls.ts
index 44de8f11..9430f9c1 100644
--- a/src/controls.ts
+++ b/src/controls.ts
@@ -116,6 +116,10 @@ export const contro = new ControMax({
window.controMax = contro
export type Command = CommandEventArgument['command']
+export const isCommandDisabled = (command: Command) => {
+ return miscUiState.appConfig?.disabledCommands?.includes(command)
+}
+
onControInit()
updateBinds(customKeymaps)
@@ -544,6 +548,8 @@ const customCommandsHandler = ({ command }) => {
contro.on('trigger', customCommandsHandler)
contro.on('trigger', ({ command }) => {
+ if (isCommandDisabled(command)) return
+
const willContinue = !isGameActive(true)
alwaysPressedHandledCommand(command)
if (willContinue) return
@@ -677,6 +683,8 @@ contro.on('trigger', ({ command }) => {
})
contro.on('release', ({ command }) => {
+ if (isCommandDisabled(command)) return
+
inModalCommand(command, false)
onTriggerOrReleased(command, false)
})
@@ -996,9 +1004,16 @@ export const handleMobileButtonCustomAction = (action: CustomAction) => {
}
}
+export const triggerCommand = (command: Command, isDown: boolean) => {
+ handleMobileButtonActionCommand(command, isDown)
+}
+
export const handleMobileButtonActionCommand = (command: ActionType | ActionHoldConfig, isDown: boolean) => {
const commandValue = typeof command === 'string' ? command : 'command' in command ? command.command : command
+ // Check if command is disabled before proceeding
+ if (typeof commandValue === 'string' && isCommandDisabled(commandValue as Command)) return
+
if (typeof commandValue === 'string' && !stringStartsWith(commandValue, 'custom')) {
const event: CommandEventArgument = {
command: commandValue as Command,
diff --git a/src/devtools.ts b/src/devtools.ts
index b9267127..8890fdea 100644
--- a/src/devtools.ts
+++ b/src/devtools.ts
@@ -209,3 +209,106 @@ setInterval(() => {
}, 1000)
// ---
+
+// Add type declaration for performance.memory
+declare global {
+ interface Performance {
+ memory?: {
+ usedJSHeapSize: number
+ totalJSHeapSize: number
+ jsHeapSizeLimit: number
+ }
+ }
+}
+
+// Performance metrics WebSocket client
+let ws: WebSocket | null = null
+let wsReconnectTimeout: NodeJS.Timeout | null = null
+let metricsInterval: NodeJS.Timeout | null = null
+
+// Start collecting metrics immediately
+const startTime = performance.now()
+
+function collectAndSendMetrics () {
+ if (!ws || ws.readyState !== WebSocket.OPEN) return
+
+ const metrics = {
+ loadTime: performance.now() - startTime,
+ memoryUsage: (performance.memory?.usedJSHeapSize ?? 0) / 1024 / 1024,
+ timestamp: Date.now()
+ }
+
+ ws.send(JSON.stringify(metrics))
+}
+
+function getWebSocketUrl () {
+ const wsPort = process.env.WS_PORT
+ if (!wsPort) return null
+
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
+ const { hostname } = window.location
+ return `${protocol}//${hostname}:${wsPort}`
+}
+
+function connectWebSocket () {
+ if (ws) return
+
+ const wsUrl = getWebSocketUrl()
+ if (!wsUrl) {
+ console.log('WebSocket server not configured')
+ return
+ }
+
+ ws = new WebSocket(wsUrl)
+
+ ws.onopen = () => {
+ console.log('Connected to metrics server')
+ if (wsReconnectTimeout) {
+ clearTimeout(wsReconnectTimeout)
+ wsReconnectTimeout = null
+ }
+
+ // Start sending metrics immediately after connection
+ collectAndSendMetrics()
+
+ // Clear existing interval if any
+ if (metricsInterval) {
+ clearInterval(metricsInterval)
+ }
+
+ // Set new interval
+ metricsInterval = setInterval(collectAndSendMetrics, 500)
+ }
+
+ ws.onclose = () => {
+ console.log('Disconnected from metrics server')
+ ws = null
+
+ // Clear metrics interval
+ if (metricsInterval) {
+ clearInterval(metricsInterval)
+ metricsInterval = null
+ }
+
+ // Try to reconnect after 3 seconds
+ wsReconnectTimeout = setTimeout(connectWebSocket, 3000)
+ }
+
+ ws.onerror = (error) => {
+ console.error('WebSocket error:', error)
+ }
+}
+
+// Connect immediately
+connectWebSocket()
+
+// Add command to request current metrics
+window.requestMetrics = () => {
+ const metrics = {
+ loadTime: performance.now() - startTime,
+ memoryUsage: (performance.memory?.usedJSHeapSize ?? 0) / 1024 / 1024,
+ timestamp: Date.now()
+ }
+ console.log('Current metrics:', metrics)
+ return metrics
+}
diff --git a/src/entities.ts b/src/entities.ts
index b9f7998e..79602aa5 100644
--- a/src/entities.ts
+++ b/src/entities.ts
@@ -4,6 +4,7 @@ import tracker from '@nxg-org/mineflayer-tracker'
import { loader as autoJumpPlugin } from '@nxg-org/mineflayer-auto-jump'
import { subscribeKey } from 'valtio/utils'
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
+import { Team } from 'mineflayer'
import { options, watchValue } from './optionsStorage'
import { gameAdditionalState, miscUiState } from './globalState'
import { EntityStatus } from './mineflayer/entityStatus'
@@ -197,35 +198,101 @@ customEvents.on('gameLoaded', () => {
}
})
- // Texture override from packet properties
- bot._client.on('player_info', (packet) => {
- for (const playerEntry of packet.data) {
- if (!playerEntry.player && !playerEntry.properties) continue
- let textureProperty = playerEntry.properties?.find(prop => prop?.name === 'textures')
- if (!textureProperty) {
- textureProperty = playerEntry.player?.properties?.find(prop => prop?.key === 'textures')
- }
- if (textureProperty) {
- try {
- const textureData = JSON.parse(Buffer.from(textureProperty.value, 'base64').toString())
- const skinUrl = textureData.textures?.SKIN?.url
- const capeUrl = textureData.textures?.CAPE?.url
+ const applySkinTexturesProxy = (url: string | undefined) => {
+ const { appConfig } = miscUiState
+ if (appConfig?.skinTexturesProxy) {
+ return url?.replace('http://textures.minecraft.net/', appConfig.skinTexturesProxy)
+ .replace('https://textures.minecraft.net/', appConfig.skinTexturesProxy)
+ }
+ return url
+ }
- // Find entity with matching UUID and update skin
- let entityId = ''
- for (const [entId, entity] of Object.entries(bot.entities)) {
- if (entity.uuid === playerEntry.uuid) {
- entityId = entId
- break
- }
- }
- // even if not found, still record to cache
- void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, playerEntry.player?.name, playerEntry.uuid, skinUrl, capeUrl)
- } catch (err) {
- console.error('Error decoding player texture:', err)
+ // Texture override from packet properties
+ const updateSkin = (player: import('mineflayer').Player) => {
+ if (!player.uuid || !player.username || !player.skinData) return
+
+ try {
+ const skinUrl = applySkinTexturesProxy(player.skinData.url)
+ const capeUrl = applySkinTexturesProxy((player.skinData as any).capeUrl)
+
+ // Find entity with matching UUID and update skin
+ let entityId = ''
+ for (const [entId, entity] of Object.entries(bot.entities)) {
+ if (entity.uuid === player.uuid) {
+ entityId = entId
+ break
+ }
+ }
+ // even if not found, still record to cache
+ void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl)
+ } catch (err) {
+ console.error('Error decoding player texture:', err)
+ }
+ }
+
+ bot.on('playerJoined', updateSkin)
+ bot.on('playerUpdated', updateSkin)
+
+ bot.on('teamUpdated', (team: Team) => {
+ for (const entity of Object.values(bot.entities)) {
+ if (entity.type === 'player' && entity.username && team.members.includes(entity.username) || entity.uuid && team.members.includes(entity.uuid)) {
+ bot.emit('entityUpdate', entity)
+ }
+ }
+ })
+
+ const updateEntityNameTags = (team: Team) => {
+ for (const entity of Object.values(bot.entities)) {
+ const entityTeam = entity.type === 'player' && entity.username ? bot.teamMap[entity.username] : entity.uuid ? bot.teamMap[entity.uuid] : undefined
+ if ((entityTeam?.nameTagVisibility === 'hideForOwnTeam' && entityTeam.name === team.name)
+ || (entityTeam?.nameTagVisibility === 'hideForOtherTeams' && entityTeam.name !== team.name)) {
+ bot.emit('entityUpdate', entity)
+ }
+ }
+ }
+
+ const doEntitiesNeedUpdating = (team: Team) => {
+ return team.nameTagVisibility === 'never'
+ || (team.nameTagVisibility === 'hideForOtherTeams' && appViewer.playerState.reactive.team?.team !== team.team)
+ || (team.nameTagVisibility === 'hideForOwnTeam' && appViewer.playerState.reactive.team?.team === team.team)
+ }
+
+ bot.on('teamMemberAdded', (team: Team, members: string[]) => {
+ if (members.includes(bot.username) && appViewer.playerState.reactive.team?.team !== team.team) {
+ appViewer.playerState.reactive.team = team
+ // Player was added to a team, need to check if any entities need updating
+ updateEntityNameTags(team)
+ } else if (doEntitiesNeedUpdating(team)) {
+ // Need to update all entities that were added
+ for (const entity of Object.values(bot.entities)) {
+ if (entity.type === 'player' && entity.username && members.includes(entity.username) || entity.uuid && members.includes(entity.uuid)) {
+ bot.emit('entityUpdate', entity)
}
}
}
-
})
+
+ bot.on('teamMemberRemoved', (team: Team, members: string[]) => {
+ if (members.includes(bot.username) && appViewer.playerState.reactive.team?.team === team.team) {
+ appViewer.playerState.reactive.team = undefined
+ // Player was removed from a team, need to check if any entities need updating
+ updateEntityNameTags(team)
+ } else if (doEntitiesNeedUpdating(team)) {
+ // Need to update all entities that were removed
+ for (const entity of Object.values(bot.entities)) {
+ if (entity.type === 'player' && entity.username && members.includes(entity.username) || entity.uuid && members.includes(entity.uuid)) {
+ bot.emit('entityUpdate', entity)
+ }
+ }
+ }
+ })
+
+ bot.on('teamRemoved', (team: Team) => {
+ if (appViewer.playerState.reactive.team?.team === team.team) {
+ appViewer.playerState.reactive.team = undefined
+ // Player's team was removed, need to update all entities that are in a team
+ updateEntityNameTags(team)
+ }
+ })
+
})
diff --git a/src/env.d.ts b/src/env.d.ts
new file mode 100644
index 00000000..9b3e9774
--- /dev/null
+++ b/src/env.d.ts
@@ -0,0 +1,31 @@
+declare namespace NodeJS {
+ interface ProcessEnv {
+ // Build configuration
+ NODE_ENV: 'development' | 'production'
+ SINGLE_FILE_BUILD?: string
+ WS_PORT?: string
+ DISABLE_SERVICE_WORKER?: string
+ CONFIG_JSON_SOURCE?: 'BUNDLED' | 'REMOTE'
+ LOCAL_CONFIG_FILE?: string
+ BUILD_VERSION?: string
+
+ // GitHub and Vercel related
+ GITHUB_REPOSITORY?: string
+ VERCEL_GIT_REPO_OWNER?: string
+ VERCEL_GIT_REPO_SLUG?: string
+
+ // UI and Features
+ MAIN_MENU_LINKS?: string
+ ENABLE_COOKIE_STORAGE?: string
+ COOKIE_STORAGE_PREFIX?: string
+
+ // Release information
+ RELEASE_TAG?: string
+ RELEASE_LINK?: string
+ RELEASE_CHANGELOG?: string
+
+ // Other configurations
+ DEPS_VERSIONS?: string
+ INLINED_APP_CONFIG?: string
+ }
+}
diff --git a/src/globals.js b/src/globals.js
index f9125c8c..11351555 100644
--- a/src/globals.js
+++ b/src/globals.js
@@ -5,7 +5,8 @@ window.bot = undefined
window.THREE = undefined
window.localServer = undefined
window.worldView = undefined
-window.viewer = undefined
+window.viewer = undefined // legacy
+window.appViewer = undefined
window.loadedData = undefined
window.customEvents = new EventEmitter()
window.customEvents.setMaxListeners(10_000)
diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts
index 01d1691d..c9f60d59 100644
--- a/src/inventoryWindows.ts
+++ b/src/inventoryWindows.ts
@@ -23,7 +23,7 @@ import { MessageFormatPart } from './chatUtils'
import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items'
import { playerState } from './mineflayer/playerState'
-const loadedImagesCache = new Map()
+const loadedImagesCache = new Map()
const cleanLoadedImagesCache = () => {
loadedImagesCache.delete('blocks')
loadedImagesCache.delete('items')
@@ -132,11 +132,12 @@ export const onGameLoad = () => {
}
}
-const getImageSrc = (path): string | HTMLImageElement => {
+const getImageSrc = (path): string | HTMLImageElement | ImageBitmap => {
switch (path) {
case 'gui/container/inventory': return appReplacableResources.latest_gui_container_inventory.content
case 'blocks': return appViewer.resourcesManager.blocksAtlasParser.latestImage
case 'items': return appViewer.resourcesManager.itemsAtlasParser.latestImage
+ case 'gui': return appViewer.resourcesManager.currentResources!.guiAtlas!.image
case 'gui/container/dispenser': return appReplacableResources.latest_gui_container_dispenser.content
case 'gui/container/furnace': return appReplacableResources.latest_gui_container_furnace.content
case 'gui/container/crafting_table': return appReplacableResources.latest_gui_container_crafting_table.content
@@ -167,6 +168,12 @@ const getImage = ({ path = undefined as string | undefined, texture = undefined
onLoad()
} else {
const imageSrc = getImageSrc(loadPath)
+ if (imageSrc instanceof ImageBitmap) {
+ onLoad()
+ loadedImagesCache.set(loadPath, imageSrc)
+ return imageSrc
+ }
+
let image: HTMLImageElement
if (imageSrc instanceof Image) {
image = imageSrc
@@ -277,6 +284,7 @@ const implementedContainersGuiMap = {
'minecraft:generic_3x3': 'DropDispenseWin',
'minecraft:furnace': 'FurnaceWin',
'minecraft:smoker': 'FurnaceWin',
+ 'minecraft:shulker_box': 'ChestWin',
'minecraft:blast_furnace': 'FurnaceWin',
'minecraft:crafting': 'CraftingWin',
'minecraft:crafting3x3': 'CraftingWin', // todo different result slot
@@ -367,7 +375,7 @@ const openWindow = (type: string | undefined) => {
lastWindow.destroy()
lastWindow = null as any
lastWindowType = null
- window.lastWindow = lastWindow
+ window.inventory = null
miscUiState.displaySearchInput = false
destroyFn()
skipClosePacketSending = false
@@ -375,6 +383,7 @@ const openWindow = (type: string | undefined) => {
cleanLoadedImagesCache()
const inv = openItemsCanvas(type)
inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch
+ window.inventory = inv
const title = bot.currentWindow?.title
const PrismarineChat = PrismarineChatLoader(bot.version)
try {
diff --git a/src/mineflayer/playerState.ts b/src/mineflayer/playerState.ts
index b7b4d2bd..b8919a14 100644
--- a/src/mineflayer/playerState.ts
+++ b/src/mineflayer/playerState.ts
@@ -99,6 +99,10 @@ export class PlayerStateControllerMain {
})
this.reactive.gameMode = bot.game?.gameMode
+ customEvents.on('gameLoaded', () => {
+ this.reactive.team = bot.teamMap[bot.username]
+ })
+
this.watchReactive()
}
diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx
index ba52e333..b03db37d 100644
--- a/src/optionsGuiScheme.tsx
+++ b/src/optionsGuiScheme.tsx
@@ -545,18 +545,6 @@ export const guiOptionsScheme: {
/>
}
},
- {
- custom () {
- const { cookieStorage } = useSnapshot(appStorage)
- return {
- appStorage.cookieStorage = !cookieStorage
- alert('Reload the page to apply this change')
- }}
- inScreen
- />
- }
- },
{
custom () {
return Server Connection
diff --git a/src/react/ArmorBar.css b/src/react/ArmorBar.css
index ca579a72..9718e553 100644
--- a/src/react/ArmorBar.css
+++ b/src/react/ArmorBar.css
@@ -8,7 +8,6 @@
--offset: calc(-1 * 25px);
--bg-x: calc(-1 * 16px);
--bg-y: calc(-1 * 9px);
- pointer-events: none;
image-rendering: pixelated;
}
diff --git a/src/react/BreathBar.css b/src/react/BreathBar.css
index e98cf7c2..bcf93303 100644
--- a/src/react/BreathBar.css
+++ b/src/react/BreathBar.css
@@ -9,7 +9,6 @@
--offset: calc(-1 * 16px);
--bg-x: calc(-1 * 16px);
--bg-y: calc(-1 * 18px);
- pointer-events: none;
image-rendering: pixelated;
}
diff --git a/src/react/Chat.css b/src/react/Chat.css
index 47394948..ff9f60b1 100644
--- a/src/react/Chat.css
+++ b/src/react/Chat.css
@@ -4,6 +4,12 @@ div.chat-wrapper {
/* z-index: 10; */
padding-left: calc(env(safe-area-inset-left) / 2);
padding-right: calc(env(safe-area-inset-right, 4px) / 2);
+ box-sizing: content-box;
+}
+
+/* Only apply overflow hidden when not in mobile mode */
+div.chat-wrapper:not(.display-mobile):not(.input-mobile) {
+ /* overflow: hidden; */
}
.chat-messages-wrapper {
@@ -11,12 +17,17 @@ div.chat-wrapper {
padding: 4px;
padding-left: 0;
max-height: var(--chatHeight);
- width: var(--chatWidth);
+ width: calc(var(--chatWidth) - 5px); /* Custom scrollbar width */
transform-origin: bottom left;
transform: scale(var(--chatScale));
pointer-events: none;
}
+/* Restore full width when chat is opened */
+.chat-messages-wrapper.chat-opened {
+ width: var(--chatWidth);
+}
+
.chat-input-wrapper {
bottom: 1px;
width: calc(100% - 3px);
@@ -62,7 +73,7 @@ div.chat-wrapper {
top: 100%;
padding-left: calc(env(safe-area-inset-left) / 2);
margin-top: 14px;
- margin-left: 20px;
+ margin-left: 40px;
/* input height */
}
@@ -109,6 +120,11 @@ div.chat-wrapper {
justify-content: flex-start;
}
+.input-mobile .chat-completions-items > div {
+ padding: 4px 0;
+ font-size: 10px;
+}
+
.input-mobile {
top: 15px;
position: absolute;
diff --git a/src/react/Chat.tsx b/src/react/Chat.tsx
index 7c1c8633..a45d7a69 100644
--- a/src/react/Chat.tsx
+++ b/src/react/Chat.tsx
@@ -18,6 +18,7 @@ const MessageLine = ({ message, currentPlayerName, chatOpened }: { message: Mess
const [fadeState, setFadeState] = useState<'visible' | 'fading' | 'faded'>('visible')
useEffect(() => {
+ if (window.debugStopChatFade) return
// Start fading after 5 seconds
const fadeTimeout = setTimeout(() => {
setFadeState('fading')
@@ -111,7 +112,9 @@ export default ({
const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]'))
const [isInputFocused, setIsInputFocused] = useState(false)
- const spellCheckEnabled = false
+ const [spellCheckEnabled, setSpellCheckEnabled] = useState(false)
+ const [preservedInputValue, setPreservedInputValue] = useState('')
+ const [inputKey, setInputKey] = useState(0)
const pingHistoryRef = useRef(JSON.parse(window.localStorage.pingHistory || '[]'))
const [completePadText, setCompletePadText] = useState('')
@@ -236,12 +239,16 @@ export default ({
if (opened) {
completeRequestValue.current = ''
resetCompletionItems()
+ } else {
+ setPreservedInputValue('')
}
}, [opened])
const onMainInputChange = () => {
const lastWord = chatInput.current.value.slice(0, chatInput.current.selectionEnd ?? chatInput.current.value.length).split(' ').at(-1)!
- if (lastWord.startsWith('@') && getPingComplete) {
+ const isCommand = chatInput.current.value.startsWith('/')
+
+ if (lastWord.startsWith('@') && getPingComplete && !isCommand) {
setCompletePadText(lastWord)
void fetchPingCompletions(true, lastWord.slice(1))
return
@@ -321,10 +328,33 @@ export default ({
return completeValue
}
+ const handleSlashCommand = () => {
+ remountInput('/')
+ }
+
+ const handleAcceptFirstCompletion = () => {
+ if (completionItems.length > 0) {
+ acceptComplete(completionItems[0])
+ }
+ }
+
+ const remountInput = (newValue?: string) => {
+ if (newValue !== undefined) {
+ setPreservedInputValue(newValue)
+ }
+ setInputKey(k => k + 1)
+ }
+
+ useEffect(() => {
+ if (preservedInputValue && chatInput.current) {
+ chatInput.current.focus()
+ }
+ }, [inputKey]) // Changed from spellCheckEnabled to inputKey
+
return (
<>
@@ -385,8 +415,53 @@ export default ({
- {/* close button */}
- {usingTouch &&
onClose?.()} />}
+ {usingTouch && (
+ <>
+ onClose?.()}
+ style={{
+ width: 20,
+ flexShrink: 0,
+ }}
+ />
+
+ {(chatInput.current?.value && !chatInput.current.value.startsWith('/')) ? (
+ // TOGGLE SPELL CHECK
+ {
+ setPreservedInputValue(chatInput.current?.value || '')
+ setSpellCheckEnabled(!spellCheckEnabled)
+ remountInput()
+ }}
+ />
+ ) : (
+ // SLASH COMMAND
+ {
+ const inputValue = chatInput.current.value
+ if (!inputValue) {
+ handleSlashCommand()
+ } else if (completionItems.length > 0) {
+ handleAcceptFirstCompletion()
+ }
+ }}
+ />
+ )}
+ >
+ )}
{isInputFocused && completionItems?.length ? (
@@ -430,9 +505,10 @@ export default ({
/>}
{
if (!options.debugContro) return null
return (
-
+
Keys: {[...pressedKeys].join(', ')}
Actions: {actions.join(', ')}
diff --git a/src/react/FoodBar.css b/src/react/FoodBar.css
index ca3629cd..83da3c25 100644
--- a/src/react/FoodBar.css
+++ b/src/react/FoodBar.css
@@ -11,7 +11,6 @@
--offset: calc(-1 * (52px + (9px * (4 * var(--kind) + var(--lightened) * 2))));
--bg-x: calc(-1 * (16px + 9px * var(--lightened)));
--bg-y: calc(-1 * 27px);
- pointer-events: none;
image-rendering: pixelated;
}
diff --git a/src/react/HealthBar.css b/src/react/HealthBar.css
index a6fefe09..72f5a1fe 100644
--- a/src/react/HealthBar.css
+++ b/src/react/HealthBar.css
@@ -11,7 +11,6 @@
--offset: calc(-1 * (52px + (9px * (4 * var(--kind) + var(--lightened) * 2)) ));
--bg-x: calc(-1 * (16px + 9px * var(--lightened)));
--bg-y: calc(-1 * var(--hardcore) * 45px);
- pointer-events: none;
image-rendering: pixelated;
}
diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx
index 80f0a789..ed1f42e6 100644
--- a/src/react/HotbarRenderApp.tsx
+++ b/src/react/HotbarRenderApp.tsx
@@ -2,15 +2,15 @@ import { useEffect, useRef, useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { createPortal } from 'react-dom'
import { subscribe, useSnapshot } from 'valtio'
-import { openItemsCanvas, openPlayerInventory, upInventoryItems } from '../inventoryWindows'
+import { openItemsCanvas, upInventoryItems } from '../inventoryWindows'
import { activeModalStack, isGameActive, miscUiState } from '../globalState'
import { currentScaling } from '../scaleInterface'
import { watchUnloadForCleanup } from '../gameUnload'
import { getItemNameRaw } from '../mineflayer/items'
import { isInRealGameSession } from '../utils'
+import { triggerCommand } from '../controls'
import MessageFormattedString from './MessageFormattedString'
import SharedHudVars from './SharedHudVars'
-import { packetsReplayState } from './state/packetsReplayState'
const ItemName = ({ itemKey }: { itemKey: string }) => {
@@ -75,6 +75,8 @@ const HotbarInner = () => {
const container = useRef
(null!)
const [itemKey, setItemKey] = useState('')
const hasModals = useSnapshot(activeModalStack).length
+ const { currentTouch, appConfig } = useSnapshot(miscUiState)
+ const mobileOpenInventory = currentTouch && !appConfig?.disabledCommands?.includes('general.inventory')
useEffect(() => {
const controller = new AbortController()
@@ -105,7 +107,7 @@ const HotbarInner = () => {
canvasManager.setScale(currentScaling.scale)
canvasManager.windowHeight = 25 * canvasManager.scale
- canvasManager.windowWidth = (210 - (inv.inventory.supportsOffhand ? 0 : 25) + (miscUiState.currentTouch ? 28 : 0)) * canvasManager.scale
+ canvasManager.windowWidth = (210 - (inv.inventory.supportsOffhand ? 0 : 25) + (mobileOpenInventory ? 28 : 0)) * canvasManager.scale
}
setSize()
watchUnloadForCleanup(subscribe(currentScaling, setSize))
@@ -119,8 +121,9 @@ const HotbarInner = () => {
canvasManager.canvas.onclick = (e) => {
if (!isGameActive(true)) return
const pos = inv.canvasManager.getMousePos(inv.canvas, e)
- if (canvasManager.canvas.width - pos.x < 35 * inv.canvasManager.scale) {
- openPlayerInventory()
+ if (canvasManager.canvas.width - pos.x < 35 * inv.canvasManager.scale && mobileOpenInventory) {
+ triggerCommand('general.inventory', true)
+ triggerCommand('general.inventory', false)
}
}
@@ -180,17 +183,8 @@ const HotbarInner = () => {
})
document.addEventListener('touchend', (e) => {
if (touchStart && (e.target as HTMLElement).closest('.hotbar') && Date.now() - touchStart > 700) {
- // drop item
- bot._client.write('block_dig', {
- 'status': 4,
- 'location': {
- 'x': 0,
- 'z': 0,
- 'y': 0
- },
- 'face': 0,
- sequence: 0
- })
+ triggerCommand('general.dropStack', true)
+ triggerCommand('general.dropStack', false)
}
touchStart = 0
})
diff --git a/src/react/HudBarsProvider.tsx b/src/react/HudBarsProvider.tsx
index db061500..42fa8378 100644
--- a/src/react/HudBarsProvider.tsx
+++ b/src/react/HudBarsProvider.tsx
@@ -1,5 +1,7 @@
import { useRef, useState, useMemo } from 'react'
import { GameMode } from 'mineflayer'
+import { useSnapshot } from 'valtio'
+import { options } from '../optionsStorage'
import { armor } from './armorValues'
import HealthBar from './HealthBar'
import FoodBar from './FoodBar'
@@ -8,6 +10,8 @@ import BreathBar from './BreathBar'
import './HealthBar.css'
export default () => {
+ const { disabledUiParts } = useSnapshot(options)
+
const [damaged, setDamaged] = useState(false)
const [healthValue, setHealthValue] = useState(bot.health)
const [food, setFood] = useState(bot.food)
@@ -91,7 +95,7 @@ export default () => {
}, [])
return
-
{
setEffectToAdd(null)
setEffectToRemove(null)
}}
- />
- }
+ {!disabledUiParts.includes('armor-bar') &&
- }
+ {!disabledUiParts.includes('food-bar') && {
setEffectToAdd(null)
setEffectToRemove(null)
}}
- />
- }
+ {!disabledUiParts.includes('breath-bar') &&
+ />}
}
diff --git a/src/react/PauseScreen.tsx b/src/react/PauseScreen.tsx
index 22e00ded..3036aa4b 100644
--- a/src/react/PauseScreen.tsx
+++ b/src/react/PauseScreen.tsx
@@ -32,7 +32,7 @@ import Screen from './Screen'
import styles from './PauseScreen.module.css'
import { DiscordButton } from './DiscordButton'
import { showNotification } from './NotificationProvider'
-import { appStatusState, reconnectReload } from './AppStatusProvider'
+import { appStatusState, lastConnectOptions, reconnectReload } from './AppStatusProvider'
import NetworkStatus from './NetworkStatus'
import PauseLinkButtons from './PauseLinkButtons'
import { pixelartIcons } from './PixelartIcon'
@@ -163,6 +163,7 @@ export default () => {
const { noConnection } = useSnapshot(gameAdditionalState)
const { active: packetsReplaceActive, hasRecordedPackets: packetsReplaceHasRecordedPackets } = useSnapshot(packetsRecordingState)
const { displayRecordButton: displayPacketsButtons } = useSnapshot(options)
+ const { appConfig } = useSnapshot(miscUiState)
const handlePointerLockChange = () => {
if (!pointerLock.hasPointerLock && activeModalStack.length === 0) {
@@ -264,7 +265,7 @@ export default () => {
Back to Game
-
openOptionsMenu('main')}>Options
+
openOptionsMenu('main')}>Options...
{singleplayer ? (
clickJoinLinkButton()}>
@@ -293,10 +294,54 @@ export default () => {
{fsState.inMemorySave && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect & Reset'}
>}
- {noConnection && (
-
- Reconnect
-
+ {(noConnection || appConfig?.alwaysReconnectButton) && (
+
+
+ Reconnect
+
+ {appConfig?.reportBugButtonWithReconnect && (
+ {
+ const platform = (navigator as any).userAgentData?.platform ?? navigator.platform
+ const body = `Version: ${window.location.hostname}\nServer: ${lastConnectOptions.value?.server ?? ''}\nPlatform: ${platform}\nWebsite: ${window.location.href}`
+ const currentHost = window.location.hostname
+ const options = [
+ 'GitHub (please use it if you can)',
+ 'Email',
+ ...((currentHost === 'mcraft.fun' || currentHost === 'ru.mcraft.fun') ? ['Try Beta Version'] : []),
+ // 'Use previous versions of client'
+ ]
+ const action = await showOptionsModal('Report client issue', options)
+ if (!action) return
+
+ switch (action) {
+ case 'GitHub (please use it if you can)':
+ openGithub(`/issues/new?body=${encodeURIComponent(body)}&title=${encodeURIComponent('[Bug Report] ')}&labels=bug`)
+ break
+ case 'Email': {
+ window.location.href = `mailto:support@mcraft.fun?body=${encodeURIComponent(body)}`
+ break
+ }
+ case 'Try Beta Version': {
+ if (currentHost === 'mcraft.fun') {
+ window.location.href = 'https://s.mcraft.fun'
+ } else if (currentHost === 'ru.mcraft.fun') {
+ window.location.href = 'https://s.pcm.gg'
+ }
+ break
+ }
+ case 'Use previous versions of client':
+ // TODO: Implement versions screen
+ void showOptionsModal('Previous versions', [])
+ break
+ }
+ }}
+ />
+ )}
+
)}
diff --git a/src/react/PlayerListOverlayProvider.tsx b/src/react/PlayerListOverlayProvider.tsx
index 3ff69c41..4d8a8ed7 100644
--- a/src/react/PlayerListOverlayProvider.tsx
+++ b/src/react/PlayerListOverlayProvider.tsx
@@ -5,6 +5,7 @@ import PlayerListOverlay from './PlayerListOverlay'
import './PlayerListOverlay.css'
import { lastConnectOptions } from './AppStatusProvider'
+const MAX_COLUMNS = 4
const MAX_ROWS_PER_COL = 10
type Players = typeof bot.players
@@ -56,21 +57,24 @@ export default () => {
}
}, [serverIp])
-
const playersArray = Object.values(players).sort((a, b) => {
if (a.username > b.username) return 1
if (a.username < b.username) return -1
return 0
})
+
+ // Calculate optimal column distribution
+ const totalPlayers = playersArray.length
+ const numColumns = Math.min(MAX_COLUMNS, Math.ceil(totalPlayers / MAX_ROWS_PER_COL))
+ const playersPerColumn = Math.ceil(totalPlayers / numColumns)
+
const lists = [] as Array
- let tempList = [] as typeof playersArray
- for (let i = 0; i < playersArray.length; i++) {
- tempList.push(playersArray[i])
-
- if ((i + 1) % MAX_ROWS_PER_COL === 0 || i + 1 === playersArray.length) {
- lists.push([...tempList])
- tempList = []
+ for (let i = 0; i < numColumns; i++) {
+ const startIdx = i * playersPerColumn
+ const endIdx = Math.min(startIdx + playersPerColumn, totalPlayers)
+ if (startIdx < totalPlayers) {
+ lists.push(playersArray.slice(startIdx, endIdx))
}
}
diff --git a/src/react/StorageConflictModal.tsx b/src/react/StorageConflictModal.tsx
index ac78d90a..e1d3299d 100644
--- a/src/react/StorageConflictModal.tsx
+++ b/src/react/StorageConflictModal.tsx
@@ -16,10 +16,15 @@ export default () => {
if (!isModalActive/* || conflicts.length === 0 */) return null
+ const clampText = (text: string) => {
+ if (typeof text !== 'string') text = JSON.stringify(text)
+ return text.length > 30 ? text.slice(0, 30) + '...' : text
+ }
+
const conflictText = conflicts.map(conflict => {
const localTime = formatTimestamp(conflict.localStorageTimestamp)
const cookieTime = formatTimestamp(conflict.cookieTimestamp)
- return `${conflict.key}: LocalStorage (${localTime}) vs Cookie (${cookieTime})`
+ return `${conflict.key}: LocalStorage (${localTime}, ${clampText(conflict.localStorageValue)}) vs Cookie (${cookieTime}, ${clampText(conflict.cookieValue)})`
}).join('\n')
return (
diff --git a/src/react/appStorageProvider.ts b/src/react/appStorageProvider.ts
index c65cc701..bce6feca 100644
--- a/src/react/appStorageProvider.ts
+++ b/src/react/appStorageProvider.ts
@@ -8,7 +8,7 @@ import type { BaseServerInfo } from './AddServerOrConnect'
// when opening html file locally in browser, localStorage is shared between all ever opened html files, so we try to avoid conflicts
const localStoragePrefix = process.env?.SINGLE_FILE_BUILD ? 'minecraft-web-client:' : ''
-const cookiePrefix = ''
+const cookiePrefix = process.env.COOKIE_STORAGE_PREFIX || ''
const { localStorage } = window
const migrateRemoveLocalStorage = false
@@ -134,12 +134,19 @@ const detectStorageConflicts = (): StorageConflict[] => {
delete localData.timestamp
delete cookieData.timestamp
- if (JSON.stringify(localData) !== JSON.stringify(cookieData)) {
+ const isDataEmpty = (data: any) => {
+ if (typeof data === 'object' && data !== null) {
+ return Object.keys(data).length === 0
+ }
+ return !data && data !== 0 && data !== false
+ }
+
+ if (JSON.stringify(localData) !== JSON.stringify(cookieData) && !isDataEmpty(localData) && !isDataEmpty(cookieData)) {
conflicts.push({
key,
localStorageValue: localData,
localStorageTimestamp: localTimestamp,
- cookieValue: cookieData,
+ cookieValue: (typeof cookieData === 'object' && cookieData !== null && 'data' in cookieData) ? cookieData.data : cookieData,
cookieTimestamp
})
}
@@ -224,9 +231,10 @@ export const appStorage = proxy({ ...defaultStorageData })
// Check if cookie storage should be used (will be set by options)
const shouldUseCookieStorage = () => {
+ const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
const isSecureCookiesAvailable = () => {
// either https or localhost
- return window.location.protocol === 'https:' || window.location.hostname === 'localhost'
+ return window.location.protocol === 'https:' || (window.location.hostname === 'localhost' && !isSafari)
}
if (!isSecureCookiesAvailable()) {
return false
@@ -395,6 +403,12 @@ export const resolveStorageConflicts = (useLocalStorage: boolean) => {
}
}
+ // forcefully set data again
+ for (const conflict of storageConflicts) {
+ appStorage[conflict.key] = useLocalStorage ? conflict.localStorageValue : conflict.cookieValue
+ saveKey(conflict.key as keyof StorageData)
+ }
+
// Clear conflicts and restore data
storageConflicts = []
restoreStorageData()
diff --git a/src/sounds/botSoundSystem.ts b/src/sounds/botSoundSystem.ts
index 0e23a98a..72aa3da8 100644
--- a/src/sounds/botSoundSystem.ts
+++ b/src/sounds/botSoundSystem.ts
@@ -71,7 +71,7 @@ subscribeKey(miscUiState, 'gameLoaded', async () => {
}
const musicStartCheck = async (force = false) => {
- if (!soundMap) return
+ if (!soundMap || !bot) return
// 20% chance to start music
if (Math.random() > 0.2 && !force && !options.enableMusic) return
diff --git a/src/utils.ts b/src/utils.ts
index d48fbbc3..3ccc7fc4 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -163,7 +163,7 @@ export const reloadChunks = async () => {
}
export const openGithub = (addUrl = '') => {
- window.open(`${process.env.GITHUB_URL}${addUrl}`, '_blank')
+ window.open(`${process.env.GITHUB_URL?.replace(/\/$/, '')}${addUrl}`, '_blank')
}
export const resolveTimeout = async (promise, timeout = 10_000) => {