Merge branch 'next' into instancing

This commit is contained in:
Vitaly Turovsky 2025-07-09 17:19:31 +03:00
commit 7ec9d10787
58 changed files with 1323 additions and 613 deletions

View file

@ -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.

View file

@ -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

View file

@ -23,6 +23,8 @@ jobs:
- name: Build project
run: pnpm build
env:
LOCAL_CONFIG_FILE: config.mcraft-only.json
- name: Bundle server.js
run: |

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <title>Minecraft Web Client</title> to <title>Minecraft Web Client — Free Online Browser Version</title>
sed -i 's/<title>Minecraft Web Client<\/title>/<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:

View file

@ -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

View file

@ -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.
<img src="./docs-assets/watch-expr.png" alt="Watch expression" width="480"/>

237
assets/debug-inputs.html Normal file
View file

@ -0,0 +1,237 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Input Debugger</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background: #f0f0f0;
}
.key-container {
display: grid;
grid-template-columns: repeat(3, 60px);
gap: 5px;
margin: 20px 0;
}
.key {
width: 60px;
height: 60px;
border: 2px solid #333;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
background: white;
position: relative;
user-select: none;
}
.key.pressed {
background: #90EE90;
}
.key .duration {
position: absolute;
bottom: 2px;
font-size: 10px;
}
.key .count {
position: absolute;
top: 2px;
right: 2px;
font-size: 10px;
}
.controls {
margin: 20px 0;
padding: 10px;
background: white;
border-radius: 5px;
}
.wasd-container {
position: relative;
width: 190px;
height: 130px;
}
#KeyW {
position: absolute;
left: 65px;
top: 0;
}
#KeyA {
position: absolute;
left: 0;
top: 65px;
}
#KeyS {
position: absolute;
left: 65px;
top: 65px;
}
#KeyD {
position: absolute;
left: 130px;
top: 65px;
}
.space-container {
margin-top: 20px;
}
#Space {
width: 190px;
}
</style>
</head>
<body>
<div class="controls">
<label>
<input type="checkbox" id="repeatMode"> Use keydown repeat mode (auto key-up after 150ms of no repeat)
</label>
</div>
<div class="wasd-container">
<div id="KeyW" class="key" data-code="KeyW">W</div>
<div id="KeyA" class="key" data-code="KeyA">A</div>
<div id="KeyS" class="key" data-code="KeyS">S</div>
<div id="KeyD" class="key" data-code="KeyD">D</div>
</div>
<div class="key-container">
<div id="ControlLeft" class="key" data-code="ControlLeft">Ctrl</div>
</div>
<div class="space-container">
<div id="Space" class="key" data-code="Space">Space</div>
</div>
<script>
const keys = {};
const keyStats = {};
const pressStartTimes = {};
const keyTimeouts = {};
function initKeyStats(code) {
if (!keyStats[code]) {
keyStats[code] = {
pressCount: 0,
duration: 0,
startTime: 0
};
}
}
function updateKeyVisuals(code) {
const element = document.getElementById(code);
if (!element) return;
const stats = keyStats[code];
if (keys[code]) {
element.classList.add('pressed');
const currentDuration = ((Date.now() - stats.startTime) / 1000).toFixed(1);
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="duration">${currentDuration}s</span><span class="count">${stats.pressCount}</span>`;
} else {
element.classList.remove('pressed');
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="count">${stats.pressCount}</span>`;
}
}
function releaseKey(code) {
keys[code] = false;
if (pressStartTimes[code]) {
keyStats[code].duration += (Date.now() - pressStartTimes[code]) / 1000;
delete pressStartTimes[code];
}
updateKeyVisuals(code);
}
function handleKeyDown(event) {
const code = event.code;
const isRepeatMode = document.getElementById('repeatMode').checked;
initKeyStats(code);
// Clear any existing timeout for this key
if (keyTimeouts[code]) {
clearTimeout(keyTimeouts[code]);
delete keyTimeouts[code];
}
if (isRepeatMode) {
// In repeat mode, always handle the keydown
if (!keys[code] || event.repeat) {
keys[code] = true;
if (!event.repeat) {
// Only increment count on initial press, not repeats
keyStats[code].pressCount++;
keyStats[code].startTime = Date.now();
pressStartTimes[code] = Date.now();
}
}
// Set timeout to release key if no repeat events come
keyTimeouts[code] = setTimeout(() => {
releaseKey(code);
}, 150);
} else {
// In normal mode, only handle keydown if key is not already pressed
if (!keys[code]) {
keys[code] = true;
keyStats[code].pressCount++;
keyStats[code].startTime = Date.now();
pressStartTimes[code] = Date.now();
}
}
updateKeyVisuals(code);
event.preventDefault();
}
function handleKeyUp(event) {
const code = event.code;
const isRepeatMode = document.getElementById('repeatMode').checked;
if (!isRepeatMode) {
releaseKey(code);
}
event.preventDefault();
}
// Initialize all monitored keys
const monitoredKeys = ['KeyW', 'KeyA', 'KeyS', 'KeyD', 'ControlLeft', 'Space'];
monitoredKeys.forEach(code => {
initKeyStats(code);
const element = document.getElementById(code);
if (element) {
element.innerHTML = `${element.getAttribute('data-code').replace('Key', '').replace('Left', '')}<span class="count">0</span>`;
}
});
// Start visual updates
setInterval(() => {
monitoredKeys.forEach(code => {
if (keys[code]) {
updateKeyVisuals(code);
}
});
}, 100);
// Event listeners
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
// Handle mode changes
document.getElementById('repeatMode').addEventListener('change', () => {
// Release all keys when switching modes
monitoredKeys.forEach(code => {
if (keys[code]) {
releaseKey(code);
}
if (keyTimeouts[code]) {
clearTimeout(keyTimeouts[code]);
delete keyTimeouts[code];
}
});
});
</script>
</body>
</html>

View file

@ -3,12 +3,16 @@
"defaultHost": "<from-proxy>",
"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"
},

4
config.mcraft-only.json Normal file
View file

@ -0,0 +1,4 @@
{
"alwaysReconnectButton": true,
"reportBugButtonWithReconnect": true
}

View file

@ -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<THREE.ShapeGeometry>((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);
})

View file

@ -27,6 +27,7 @@
<div style="font-size: var(--font-size);color: rgb(176, 176, 176);margin-top: 3px;text-align: center" class="subtitle">A true Minecraft client in your browser!</div>
<!-- small text pre -->
<div style="font-size: calc(var(--font-size) * 0.6);color: rgb(150, 150, 150);margin-top: 3px;text-align: center;white-space: pre-line;" class="advanced-info"></div>
<div style="font-size: calc(var(--font-size) * 0.6);color: rgb(255, 100, 100);margin-top: 10px;text-align: center;display: none;" class="ios-warning">Only iOS 15+ is supported due to performance optimizations</div>
</div>
</div>
`
@ -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')
})
})
})
}

View file

@ -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",

View file

@ -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 {

View file

@ -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;

416
pnpm-lock.yaml generated
View file

@ -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: {}

View file

@ -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) => ({

View file

@ -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}`

View file

@ -67,7 +67,6 @@ export type MesherGeometryOutput = {
heads: Record<string, any>,
signs: Record<string, any>,
// isFull: boolean
highestBlocks: Map<string, HighestBlockInfo>
hadErrors: boolean
blocksCount: number
customBlockModels?: CustomBlockModels

View file

@ -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 }
}

View file

@ -104,6 +104,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
...e,
pos: e.position,
username: e.username,
team: bot.teamMap[e.username] || bot.teamMap[e.uuid],
// set debugTree (obj) {
// e.debugTree = obj
// }

View file

@ -36,6 +36,7 @@ export const defaultWorldRendererConfig = {
mesherWorkers: 4,
isPlayground: false,
renderEars: true,
skinTexturesProxy: undefined as string | undefined,
// game renderer setting actually
showHand: false,
viewBobbing: false,
@ -127,7 +128,6 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
handleResize = () => { }
highestBlocksByChunks = new Map<string, { [chunkKey: string]: HighestBlockInfo }>()
highestBlocksBySections = new Map<string, { [sectionKey: string]: HighestBlockInfo }>()
blockEntities = {}
workersProcessAverageTime = 0
@ -393,8 +393,6 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
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<WorkerSend = any, WorkerReceive = any>
for (let y = this.worldSizeParams.minY; y < this.worldSizeParams.worldHeight; y += 16) {
this.setSectionDirty(new Vec3(x, y, z), false)
delete this.finishedSections[`${x},${y},${z}`]
this.highestBlocksBySections.delete(`${x},${y},${z}`)
}
this.highestBlocksByChunks.delete(`${x},${z}`)

View file

@ -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)

View file

@ -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,
}

View file

@ -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

View file

@ -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()

View file

@ -1,3 +1,4 @@
/// <reference types="./src/env" />
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) {

42
scripts/requestData.ts Normal file
View file

@ -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)

160
scripts/updateGitDeps.ts Normal file
View file

@ -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<string, LockfilePackage>
devDependencies?: Record<string, LockfilePackage>
}
}
}
interface PackageJson {
pnpm?: {
updateConfig?: {
ignoreDependencies?: string[]
}
}
}
async function prompt(question: string): Promise<string> {
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<string> {
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<typeof extractGitInfo>
}> = []
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)

45
scripts/wsServer.ts Normal file
View file

@ -0,0 +1,45 @@
import {WebSocketServer} from 'ws'
export function startWsServer(port: number = 8081, tryOtherPort: boolean = true): Promise<number> {
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)
})
}

View file

@ -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)
}

View file

@ -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

View file

@ -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,

View file

@ -116,6 +116,10 @@ export const contro = new ControMax({
window.controMax = contro
export type Command = CommandEventArgument<typeof contro['_commandsRaw']>['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<typeof contro['_commandsRaw']> = {
command: commandValue as Command,

View file

@ -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
}

View file

@ -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)
}
})
})

31
src/env.d.ts vendored Normal file
View file

@ -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
}
}

View file

@ -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)

View file

@ -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<string, HTMLImageElement>()
const loadedImagesCache = new Map<string, HTMLImageElement | ImageBitmap>()
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 {

View file

@ -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()
}

View file

@ -545,18 +545,6 @@ export const guiOptionsScheme: {
/>
}
},
{
custom () {
const { cookieStorage } = useSnapshot(appStorage)
return <Button
label={`Storage: ${cookieStorage ? 'Synced Cookies' : 'Local Storage'}`} onClick={() => {
appStorage.cookieStorage = !cookieStorage
alert('Reload the page to apply this change')
}}
inScreen
/>
}
},
{
custom () {
return <Category>Server Connection</Category>

View file

@ -8,7 +8,6 @@
--offset: calc(-1 * 25px);
--bg-x: calc(-1 * 16px);
--bg-y: calc(-1 * 9px);
pointer-events: none;
image-rendering: pixelated;
}

View file

@ -9,7 +9,6 @@
--offset: calc(-1 * 16px);
--bg-x: calc(-1 * 16px);
--bg-y: calc(-1 * 18px);
pointer-events: none;
image-rendering: pixelated;
}

View file

@ -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;

View file

@ -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 (
<>
<div
className={`chat-wrapper chat-messages-wrapper ${usingTouch ? 'display-mobile' : ''}`} style={{
className={`chat-wrapper chat-messages-wrapper ${usingTouch ? 'display-mobile' : ''} ${opened ? 'chat-opened' : ''}`} style={{
userSelect: opened && allowSelection ? 'text' : undefined,
}}
>
@ -385,8 +415,53 @@ export default ({
</div>
<div className={`chat-wrapper chat-input-wrapper ${usingTouch ? 'input-mobile' : ''}`} hidden={!opened}>
{/* close button */}
{usingTouch && <Button icon={pixelartIcons.close} onClick={() => onClose?.()} />}
{usingTouch && (
<>
<Button
icon={pixelartIcons.close}
onClick={() => onClose?.()}
style={{
width: 20,
flexShrink: 0,
}}
/>
{(chatInput.current?.value && !chatInput.current.value.startsWith('/')) ? (
// TOGGLE SPELL CHECK
<Button
style={{
width: 20,
flexShrink: 0,
}}
overlayColor={spellCheckEnabled ? '#00ff00' : '#ff0000'}
icon={pixelartIcons['text-wrap']}
onClick={() => {
setPreservedInputValue(chatInput.current?.value || '')
setSpellCheckEnabled(!spellCheckEnabled)
remountInput()
}}
/>
) : (
// SLASH COMMAND
<Button
style={{
width: 20,
flexShrink: 0,
}}
label={chatInput.current?.value ? undefined : '/'}
icon={chatInput.current?.value ? pixelartIcons['arrow-right'] : undefined}
onClick={() => {
const inputValue = chatInput.current.value
if (!inputValue) {
handleSlashCommand()
} else if (completionItems.length > 0) {
handleAcceptFirstCompletion()
}
}}
/>
)}
</>
)}
<div className="chat-input">
{isInputFocused && completionItems?.length ? (
<div className="chat-completions">
@ -430,9 +505,10 @@ export default ({
/>}
<input
maxLength={chatVanillaRestrictions ? 256 : undefined}
defaultValue=''
defaultValue={preservedInputValue}
// ios doesn't support toggling autoCorrect on the fly so we need to re-create the input
key={spellCheckEnabled ? 'true' : 'false'}
key={`${inputKey}`}
autoCapitalize={preservedInputValue ? 'off' : 'on'}
ref={chatInput}
type="text"
className="chat-input"

View file

@ -50,20 +50,24 @@ export default () => {
if (!options.debugContro) return null
return (
<div style={{
position: 'fixed',
right: 0,
top: '50%',
transform: 'translateY(-50%)',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
padding: '8px',
fontFamily: 'monospace',
fontSize: '8px',
color: 'white',
display: 'flex',
flexDirection: 'column',
gap: '4px'
}}>
<div
className='debug-contro'
style={{
position: 'fixed',
right: 0,
top: '50%',
transform: 'translateY(-50%)',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
padding: '8px',
fontFamily: 'monospace',
fontSize: '8px',
color: 'white',
display: 'flex',
flexDirection: 'column',
gap: '4px',
zIndex: 2,
}}
>
<div>Keys: {[...pressedKeys].join(', ')}</div>
<div style={{ color: 'limegreen' }}>Actions: {actions.join(', ')}</div>
</div>

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<HTMLDivElement>(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
})

View file

@ -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 <div className='hud-bars-container'>
<HealthBar
{!disabledUiParts.includes('health-bar') && <HealthBar
gameMode={gameMode}
isHardcore={isHardcore}
damaged={damaged}
@ -102,12 +106,12 @@ export default () => {
setEffectToAdd(null)
setEffectToRemove(null)
}}
/>
<ArmorBar
/>}
{!disabledUiParts.includes('armor-bar') && <ArmorBar
armorValue={armorValue}
style={gameMode !== 'survival' && gameMode !== 'adventure' ? { display: 'none' } : { display: 'flex' }}
/>
<FoodBar
/>}
{!disabledUiParts.includes('food-bar') && <FoodBar
gameMode={gameMode}
food={food}
effectToAdd={effectToAdd}
@ -116,9 +120,9 @@ export default () => {
setEffectToAdd(null)
setEffectToRemove(null)
}}
/>
<BreathBar
/>}
{!disabledUiParts.includes('breath-bar') && <BreathBar
oxygen={gameMode !== 'survival' && gameMode !== 'adventure' ? 0 : oxygen}
/>
/>}
</div>
}

View file

@ -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 () => {
<div className={styles.pause_container}>
<Button className="button" style={{ width: '204px' }} onClick={onReturnPress}>Back to Game</Button>
<PauseLinkButtons />
<Button className="button" style={{ width: '204px' }} onClick={() => openOptionsMenu('main')}>Options</Button>
<Button className="button" style={{ width: '204px' }} onClick={() => openOptionsMenu('main')}>Options...</Button>
{singleplayer ? (
<div className={styles.row}>
<Button className="button" style={{ width: '170px' }} onClick={async () => clickJoinLinkButton()}>
@ -293,10 +294,54 @@ export default () => {
{fsState.inMemorySave && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect & Reset'}
</Button>
</>}
{noConnection && (
<Button className="button" style={{ width: '204px' }} onClick={reconnectReload}>
Reconnect
</Button>
{(noConnection || appConfig?.alwaysReconnectButton) && (
<div className={styles.row}>
<Button className="button" style={{ width: appConfig?.reportBugButtonWithReconnect ? '98px' : '204px' }} onClick={reconnectReload}>
Reconnect
</Button>
{appConfig?.reportBugButtonWithReconnect && (
<Button
label="Report Problem"
className="button"
style={{ width: '98px' }}
onClick={async () => {
const platform = (navigator as any).userAgentData?.platform ?? navigator.platform
const body = `Version: ${window.location.hostname}\nServer: ${lastConnectOptions.value?.server ?? '<not a 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] <describe your issue here>')}&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
}
}}
/>
)}
</div>
)}
</div>
<LoadingTimer />

View file

@ -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<typeof playersArray>
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))
}
}

View file

@ -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 (

View file

@ -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()

View file

@ -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

View file

@ -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) => {