Compare commits
35 commits
next
...
light-engi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a8d15b638 | ||
|
|
b8c8f8ab62 | ||
|
|
56aee16737 |
||
|
|
6be3c5c687 | ||
|
|
f185df993f | ||
|
|
90de0d0be1 | ||
|
|
e95f84e92c | ||
|
|
7dba526ad8 | ||
|
|
5720cfaf34 | ||
|
|
ddf08107f2 | ||
|
|
d6f394fe20 | ||
|
|
7d224fb7ef | ||
|
|
c4b9c33a3b |
||
|
|
c97c7e0cc0 | ||
|
|
f4f5eddce0 | ||
|
|
27c55b1afc | ||
|
|
79f0fdd86e | ||
|
|
f4eab39f7f | ||
|
|
2f6191a425 | ||
|
|
5a57d29919 |
||
|
|
1f5b682bee | ||
|
|
b4c72dbb36 | ||
|
|
1918c68efb | ||
|
|
3cd1ac3666 | ||
|
|
b1ba2cd470 | ||
|
|
0fa66e295e | ||
|
|
e10f610898 | ||
|
|
f18b3a17b3 | ||
|
|
9f505f81d6 | ||
|
|
ec6b2494c8 | ||
|
|
ace45a9f87 | ||
|
|
037e297473 | ||
|
|
48ead547e3 | ||
|
|
d5c61d8320 | ||
|
|
245300ff84 |
96 changed files with 1135 additions and 4519 deletions
|
|
@ -23,7 +23,6 @@
|
||||||
// ],
|
// ],
|
||||||
"@stylistic/arrow-spacing": "error",
|
"@stylistic/arrow-spacing": "error",
|
||||||
"@stylistic/block-spacing": "error",
|
"@stylistic/block-spacing": "error",
|
||||||
"@typescript-eslint/no-this-alias": "off",
|
|
||||||
"@stylistic/brace-style": [
|
"@stylistic/brace-style": [
|
||||||
"error",
|
"error",
|
||||||
"1tbs",
|
"1tbs",
|
||||||
|
|
|
||||||
2
.github/workflows/benchmark.yml
vendored
2
.github/workflows/benchmark.yml
vendored
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 18
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
- name: Move Cypress to dependencies
|
- name: Move Cypress to dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
|
|
@ -49,20 +49,6 @@ jobs:
|
||||||
publish_dir: .vercel/output/static
|
publish_dir: .vercel/output/static
|
||||||
force_orphan: true
|
force_orphan: true
|
||||||
|
|
||||||
# Create CNAME file for custom domain
|
|
||||||
- name: Create CNAME file
|
|
||||||
run: echo "github.mcraft.fun" > .vercel/output/static/CNAME
|
|
||||||
|
|
||||||
- name: Deploy to mwc-mcraft-pages repository
|
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
|
||||||
with:
|
|
||||||
personal_token: ${{ secrets.MCW_MCRAFT_PAGE_DEPLOY_TOKEN }}
|
|
||||||
external_repository: ${{ github.repository_owner }}/mwc-mcraft-pages
|
|
||||||
publish_dir: .vercel/output/static
|
|
||||||
publish_branch: main
|
|
||||||
destination_dir: docs
|
|
||||||
force_orphan: true
|
|
||||||
|
|
||||||
- name: Change index.html title
|
- name: Change index.html title
|
||||||
run: |
|
run: |
|
||||||
# change <title>Minecraft Web Client</title> to <title>Minecraft Web Client — Free Online Browser Version</title>
|
# change <title>Minecraft Web Client</title> to <title>Minecraft Web Client — Free Online Browser Version</title>
|
||||||
|
|
|
||||||
17
README.MD
17
README.MD
|
|
@ -14,11 +14,13 @@ For building the project yourself / contributing, see [Development, Debugging &
|
||||||
|
|
||||||
> **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)
|
> **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
|
### Big Features
|
||||||
|
|
||||||
|
- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below)
|
||||||
|
- Combined Lighting System - Server Parsing + Client Side Engine for block updates
|
||||||
- Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely.
|
- Official Mineflayer [plugin integration](https://github.com/zardoy/mcraft-fun-mineflayer-plugin)! View / Control your bot remotely.
|
||||||
- Open any zip world file or even folder in read-write mode!
|
- Open any zip world file or even folder in read-write mode!
|
||||||
- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below)
|
|
||||||
- Integrated JS server clone capable of opening Java world saves in any way (folders, zip, web chunks streaming, etc)
|
- Integrated JS server clone capable of opening Java world saves in any way (folders, zip, web chunks streaming, etc)
|
||||||
- Singleplayer mode with simple world generations!
|
- Singleplayer mode with simple world generations!
|
||||||
- Works offline
|
- Works offline
|
||||||
|
|
@ -54,9 +56,8 @@ Howerver, it's known that these browsers have issues:
|
||||||
|
|
||||||
### Versions Support
|
### Versions Support
|
||||||
|
|
||||||
Server versions 1.8 - 1.21.5 are supported.
|
Server versions 1.8 - 1.21.4 are supported.
|
||||||
First class versions (most of the features are tested on these versions):
|
First class versions (most of the features are tested on these versions):
|
||||||
|
|
||||||
- 1.19.4
|
- 1.19.4
|
||||||
- 1.21.4
|
- 1.21.4
|
||||||
|
|
||||||
|
|
@ -78,8 +79,6 @@ There is a builtin proxy, but you can also host your one! Just clone the repo, r
|
||||||
|
|
||||||
[](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F)
|
[](https://app.koyeb.com/deploy?name=minecraft-web-client&type=git&repository=zardoy%2Fminecraft-web-client&branch=next&builder=dockerfile&env%5B%5D=&ports=8080%3Bhttp%3B%2F)
|
||||||
|
|
||||||
> **Note**: If you want to make **your own** Minecraft server accessible to web clients (without our proxies), you can use [mwc-proxy](https://github.com/zardoy/mwc-proxy) - a lightweight JS WebSocket proxy that runs on the same server as your Minecraft server, allowing players to connect directly via `wss://play.example.com`. `?client_mcraft` is added to the URL, so the proxy will know that it's this client.
|
|
||||||
|
|
||||||
Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser.
|
Proxy servers are used to connect to Minecraft servers which use TCP protocol. When you connect connect to a server with a proxy, websocket connection is created between you (browser client) and the proxy server located in Europe, then the proxy connects to the Minecraft server and sends the data to the client (you) without any packet deserialization to avoid any additional delays. That said all the Minecraft protocol packets are processed by the client, right in your browser.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|
@ -127,11 +126,11 @@ There is world renderer playground ([link](https://mcon.vercel.app/playground/))
|
||||||
|
|
||||||
However, there are many things that can be done in online production version (like debugging actual source code). Also you can access some global variables in the console and there are a few useful examples:
|
However, there are many things that can be done in online production version (like debugging actual source code). Also you can access some global variables in the console and there are a few useful examples:
|
||||||
|
|
||||||
- If you type `debugToggle`, press enter in console - It will enables all debug messages! Warning: this will start all packets spam.
|
- `localStorage.debug = '*'` - Enables all debug messages! Warning: this will start all packets spam.
|
||||||
Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can use `debugTopPackets` (with JSON.stringify) to see what packets were received/sent by name
|
Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can use `debugTopPackets` (with JSON.stringify) to see what packets were received/sent by name
|
||||||
|
|
||||||
- `bot` - Mineflayer bot instance. See Mineflayer documentation for more.
|
- `bot` - Mineflayer bot instance. See Mineflayer documentation for more.
|
||||||
- `world` - Three.js world instance, basically does all the rendering (part of renderer backend).
|
- `viewer` - Three.js viewer instance, basically does all the rendering.
|
||||||
- `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).
|
- `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.
|
- `debugChangedOptions` - See what options are changed. Don't change options here.
|
||||||
|
|
@ -141,7 +140,7 @@ Instead I recommend setting `options.debugLogNotFrequentPackets`. Also you can u
|
||||||
|
|
||||||
- `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read.
|
- `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 `world.getCameraPosition()` 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"/>
|
<img src="./docs-assets/watch-expr.png" alt="Watch expression" width="480"/>
|
||||||
|
|
||||||
|
|
@ -178,7 +177,6 @@ Server specific:
|
||||||
- `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes.
|
- `?lockConnect=true` - Only works then `ip` parameter is set. Disables cancel/save buttons and all inputs in the connect screen already set as parameters. Useful for integrates iframes.
|
||||||
- `?autoConnect=true` - Only works then `ip` and `version` parameters are set and `allowAutoConnect` is `true` in config.json! Directly connects to the specified server. Useful for integrates iframes.
|
- `?autoConnect=true` - Only works then `ip` and `version` parameters are set and `allowAutoConnect` is `true` in config.json! Directly connects to the specified server. Useful for integrates iframes.
|
||||||
- `?serversList=<list_or_url>` - `<list_or_url>` can be a list of servers in the format `ip:version,ip` or a url to a json file with the same format (array) or a txt file with line-delimited list of server IPs.
|
- `?serversList=<list_or_url>` - `<list_or_url>` can be a list of servers in the format `ip:version,ip` or a url to a json file with the same format (array) or a txt file with line-delimited list of server IPs.
|
||||||
- `?addPing=<ping>` - Add a latency to both sides of the connection. Useful for testing ping issues. For example `?addPing=100` will add 200ms to your ping.
|
|
||||||
|
|
||||||
Single player specific:
|
Single player specific:
|
||||||
|
|
||||||
|
|
@ -235,4 +233,3 @@ Only during development:
|
||||||
|
|
||||||
- [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true)
|
- [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true)
|
||||||
- [https://m.eaglercraft.com/](EaglerCraft) - Eaglercraft runnable on mobile (real Minecraft in the browser)
|
- [https://m.eaglercraft.com/](EaglerCraft) - Eaglercraft runnable on mobile (real Minecraft in the browser)
|
||||||
- [js-minecraft](https://github.com/LabyStudio/js-minecraft) - An insanely well done clone from the graphical side that inspired many features here
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
here you can place custom textures for bundled files (blocks/items) e.g. blocks/stone.png
|
|
||||||
get file names from here (blocks/items) https://zardoy.github.io/mc-assets/
|
|
||||||
|
|
@ -10,10 +10,6 @@
|
||||||
{
|
{
|
||||||
"ip": "wss://play.mcraft.fun"
|
"ip": "wss://play.mcraft.fun"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ip": "wss://play.webmc.fun",
|
|
||||||
"name": "WebMC"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ip": "wss://ws.fuchsmc.net"
|
"ip": "wss://ws.fuchsmc.net"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{
|
{
|
||||||
"alwaysReconnectButton": true,
|
"alwaysReconnectButton": true,
|
||||||
"reportBugButtonWithReconnect": true,
|
"reportBugButtonWithReconnect": true
|
||||||
"allowAutoConnect": true
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Minecraft Item Viewer</title>
|
|
||||||
<style>
|
|
||||||
body { margin: 0; overflow: hidden; }
|
|
||||||
canvas { display: block; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script type="module" src="./three-item.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
import * as THREE from 'three'
|
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
|
||||||
import itemsAtlas from 'mc-assets/dist/itemsAtlasLegacy.png'
|
|
||||||
import { createItemMeshFromCanvas, createItemMesh } from '../renderer/viewer/three/itemMesh'
|
|
||||||
|
|
||||||
// Create scene, camera and renderer
|
|
||||||
const scene = new THREE.Scene()
|
|
||||||
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true })
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
||||||
document.body.appendChild(renderer.domElement)
|
|
||||||
|
|
||||||
// Setup camera and controls
|
|
||||||
camera.position.set(0, 0, 3)
|
|
||||||
const controls = new OrbitControls(camera, renderer.domElement)
|
|
||||||
controls.enableDamping = true
|
|
||||||
|
|
||||||
// Background and lights
|
|
||||||
scene.background = new THREE.Color(0x333333)
|
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 0.7)
|
|
||||||
scene.add(ambientLight)
|
|
||||||
|
|
||||||
// Animation loop
|
|
||||||
function animate () {
|
|
||||||
requestAnimationFrame(animate)
|
|
||||||
controls.update()
|
|
||||||
renderer.render(scene, camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setupItemMesh () {
|
|
||||||
try {
|
|
||||||
const loader = new THREE.TextureLoader()
|
|
||||||
const atlasTexture = await loader.loadAsync(itemsAtlas)
|
|
||||||
|
|
||||||
// Pixel-art configuration
|
|
||||||
atlasTexture.magFilter = THREE.NearestFilter
|
|
||||||
atlasTexture.minFilter = THREE.NearestFilter
|
|
||||||
atlasTexture.generateMipmaps = false
|
|
||||||
atlasTexture.wrapS = atlasTexture.wrapT = THREE.ClampToEdgeWrapping
|
|
||||||
|
|
||||||
// Extract the tile at x=2, y=0 (16x16)
|
|
||||||
const tileSize = 16
|
|
||||||
const tileX = 2
|
|
||||||
const tileY = 0
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
canvas.width = tileSize
|
|
||||||
canvas.height = tileSize
|
|
||||||
const ctx = canvas.getContext('2d')!
|
|
||||||
|
|
||||||
ctx.imageSmoothingEnabled = false
|
|
||||||
ctx.drawImage(
|
|
||||||
atlasTexture.image,
|
|
||||||
tileX * tileSize,
|
|
||||||
tileY * tileSize,
|
|
||||||
tileSize,
|
|
||||||
tileSize,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
tileSize,
|
|
||||||
tileSize
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test both approaches - working manual extraction:
|
|
||||||
const meshOld = createItemMeshFromCanvas(canvas, { depth: 0.1 })
|
|
||||||
meshOld.position.x = -1
|
|
||||||
meshOld.rotation.x = -Math.PI / 12
|
|
||||||
meshOld.rotation.y = Math.PI / 12
|
|
||||||
scene.add(meshOld)
|
|
||||||
|
|
||||||
// And new unified function:
|
|
||||||
const atlasWidth = atlasTexture.image.width
|
|
||||||
const atlasHeight = atlasTexture.image.height
|
|
||||||
const u = (tileX * tileSize) / atlasWidth
|
|
||||||
const v = (tileY * tileSize) / atlasHeight
|
|
||||||
const sizeX = tileSize / atlasWidth
|
|
||||||
const sizeY = tileSize / atlasHeight
|
|
||||||
|
|
||||||
console.log('Debug texture coords:', {u, v, sizeX, sizeY, atlasWidth, atlasHeight})
|
|
||||||
|
|
||||||
const resultNew = createItemMesh(atlasTexture, {
|
|
||||||
u, v, sizeX, sizeY
|
|
||||||
}, {
|
|
||||||
faceCamera: false,
|
|
||||||
use3D: true,
|
|
||||||
depth: 0.1
|
|
||||||
})
|
|
||||||
|
|
||||||
resultNew.mesh.position.x = 1
|
|
||||||
resultNew.mesh.rotation.x = -Math.PI / 12
|
|
||||||
resultNew.mesh.rotation.y = Math.PI / 12
|
|
||||||
scene.add(resultNew.mesh)
|
|
||||||
|
|
||||||
animate()
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to create item mesh:', err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle window resize
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight
|
|
||||||
camera.updateProjectionMatrix()
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start
|
|
||||||
setupItemMesh()
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
<script type="module" src="three-labels.ts"></script>
|
|
||||||
<style>
|
|
||||||
body { margin: 0; }
|
|
||||||
canvas { display: block; }
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
import * as THREE from 'three'
|
|
||||||
import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js'
|
|
||||||
import { createWaypointSprite, WAYPOINT_CONFIG } from '../renderer/viewer/three/waypointSprite'
|
|
||||||
|
|
||||||
// Create scene, camera and renderer
|
|
||||||
const scene = new THREE.Scene()
|
|
||||||
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true })
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
||||||
document.body.appendChild(renderer.domElement)
|
|
||||||
|
|
||||||
// Add FirstPersonControls
|
|
||||||
const controls = new FirstPersonControls(camera, renderer.domElement)
|
|
||||||
controls.lookSpeed = 0.1
|
|
||||||
controls.movementSpeed = 10
|
|
||||||
controls.lookVertical = true
|
|
||||||
controls.constrainVertical = true
|
|
||||||
controls.verticalMin = 0.1
|
|
||||||
controls.verticalMax = Math.PI - 0.1
|
|
||||||
|
|
||||||
// Position camera
|
|
||||||
camera.position.y = 1.6 // Typical eye height
|
|
||||||
camera.lookAt(0, 1.6, -1)
|
|
||||||
|
|
||||||
// Create a helper grid and axes
|
|
||||||
const grid = new THREE.GridHelper(20, 20)
|
|
||||||
scene.add(grid)
|
|
||||||
const axes = new THREE.AxesHelper(5)
|
|
||||||
scene.add(axes)
|
|
||||||
|
|
||||||
// Create waypoint sprite via utility
|
|
||||||
const waypoint = createWaypointSprite({
|
|
||||||
position: new THREE.Vector3(0, 0, -5),
|
|
||||||
color: 0xff0000,
|
|
||||||
label: 'Target',
|
|
||||||
})
|
|
||||||
scene.add(waypoint.group)
|
|
||||||
|
|
||||||
// Use built-in offscreen arrow from utils
|
|
||||||
waypoint.enableOffscreenArrow(true)
|
|
||||||
waypoint.setArrowParent(scene)
|
|
||||||
|
|
||||||
// Animation loop
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate)
|
|
||||||
|
|
||||||
const delta = Math.min(clock.getDelta(), 0.1)
|
|
||||||
controls.update(delta)
|
|
||||||
|
|
||||||
// Unified camera update (size, distance text, arrow, visibility)
|
|
||||||
const sizeVec = renderer.getSize(new THREE.Vector2())
|
|
||||||
waypoint.updateForCamera(camera.position, camera, sizeVec.width, sizeVec.height)
|
|
||||||
|
|
||||||
renderer.render(scene, camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle window resize
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight
|
|
||||||
camera.updateProjectionMatrix()
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add clock for controls
|
|
||||||
const clock = new THREE.Clock()
|
|
||||||
|
|
||||||
animate()
|
|
||||||
12
package.json
12
package.json
|
|
@ -54,7 +54,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dimaka/interface": "0.0.3-alpha.0",
|
"@dimaka/interface": "0.0.3-alpha.0",
|
||||||
"@floating-ui/react": "^0.26.1",
|
"@floating-ui/react": "^0.26.1",
|
||||||
"@monaco-editor/react": "^4.7.0",
|
|
||||||
"@nxg-org/mineflayer-auto-jump": "^0.7.18",
|
"@nxg-org/mineflayer-auto-jump": "^0.7.18",
|
||||||
"@nxg-org/mineflayer-tracker": "1.3.0",
|
"@nxg-org/mineflayer-tracker": "1.3.0",
|
||||||
"@react-oauth/google": "^0.12.1",
|
"@react-oauth/google": "^0.12.1",
|
||||||
|
|
@ -80,14 +79,14 @@
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"filesize": "^10.0.12",
|
"filesize": "^10.0.12",
|
||||||
"flying-squid": "npm:@zardoy/flying-squid@^0.0.104",
|
"flying-squid": "npm:@zardoy/flying-squid@^0.0.62",
|
||||||
"framer-motion": "^12.9.2",
|
"framer-motion": "^12.9.2",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
|
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mcraft-fun-mineflayer": "^0.1.23",
|
"mcraft-fun-mineflayer": "^0.1.23",
|
||||||
"minecraft-data": "3.98.0",
|
"minecraft-data": "3.92.0",
|
||||||
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
|
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
|
||||||
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
|
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
|
||||||
"mojangson": "^2.0.4",
|
"mojangson": "^2.0.4",
|
||||||
|
|
@ -156,8 +155,10 @@
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
"mc-assets": "^0.2.62",
|
"mc-assets": "^0.2.62",
|
||||||
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
||||||
|
"minecraft-lighting": "^0.0.10",
|
||||||
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
|
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
|
||||||
"mineflayer-mouse": "^0.1.21",
|
"mineflayer-mouse": "^0.1.11",
|
||||||
|
"mineflayer-pathfinder": "^2.4.4",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
|
@ -197,7 +198,6 @@
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
|
|
||||||
"@nxg-org/mineflayer-physics-util": "1.8.10",
|
"@nxg-org/mineflayer-physics-util": "1.8.10",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"vec3": "0.1.10",
|
"vec3": "0.1.10",
|
||||||
|
|
@ -205,7 +205,7 @@
|
||||||
"diamond-square": "github:zardoy/diamond-square",
|
"diamond-square": "github:zardoy/diamond-square",
|
||||||
"prismarine-block": "github:zardoy/prismarine-block#next-era",
|
"prismarine-block": "github:zardoy/prismarine-block#next-era",
|
||||||
"prismarine-world": "github:zardoy/prismarine-world#next-era",
|
"prismarine-world": "github:zardoy/prismarine-world#next-era",
|
||||||
"minecraft-data": "3.98.0",
|
"minecraft-data": "3.92.0",
|
||||||
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
|
"prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything",
|
||||||
"prismarine-physics": "github:zardoy/prismarine-physics",
|
"prismarine-physics": "github:zardoy/prismarine-physics",
|
||||||
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
|
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
diff --git a/src/client/chat.js b/src/client/chat.js
|
diff --git a/src/client/chat.js b/src/client/chat.js
|
||||||
index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b43cb102a 100644
|
index 8d0869b150681574ad19292a026cce9f67a137ee..2efa2e6600f017b566155974cb9fb1856fa582f9 100644
|
||||||
--- a/src/client/chat.js
|
--- a/src/client/chat.js
|
||||||
+++ b/src/client/chat.js
|
+++ b/src/client/chat.js
|
||||||
@@ -116,7 +116,7 @@ module.exports = function (client, options) {
|
@@ -109,7 +109,7 @@ module.exports = function (client, options) {
|
||||||
for (const player of packet.data) {
|
for (const player of packet.data) {
|
||||||
if (player.chatSession) {
|
if (player.chatSession) {
|
||||||
client._players[player.uuid] = {
|
client._players[player.uuid] = {
|
||||||
|
|
@ -11,7 +11,7 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b
|
||||||
publicKeyDER: player.chatSession.publicKey.keyBytes,
|
publicKeyDER: player.chatSession.publicKey.keyBytes,
|
||||||
sessionUuid: player.chatSession.uuid
|
sessionUuid: player.chatSession.uuid
|
||||||
}
|
}
|
||||||
@@ -126,7 +126,7 @@ module.exports = function (client, options) {
|
@@ -119,7 +119,7 @@ module.exports = function (client, options) {
|
||||||
|
|
||||||
if (player.crypto) {
|
if (player.crypto) {
|
||||||
client._players[player.uuid] = {
|
client._players[player.uuid] = {
|
||||||
|
|
@ -20,7 +20,7 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b
|
||||||
publicKeyDER: player.crypto.publicKey,
|
publicKeyDER: player.crypto.publicKey,
|
||||||
signature: player.crypto.signature,
|
signature: player.crypto.signature,
|
||||||
displayName: player.displayName || player.name
|
displayName: player.displayName || player.name
|
||||||
@@ -196,7 +196,7 @@ module.exports = function (client, options) {
|
@@ -189,7 +189,7 @@ module.exports = function (client, options) {
|
||||||
if (mcData.supportFeature('useChatSessions')) {
|
if (mcData.supportFeature('useChatSessions')) {
|
||||||
const tsDelta = BigInt(Date.now()) - packet.timestamp
|
const tsDelta = BigInt(Date.now()) - packet.timestamp
|
||||||
const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0
|
const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0
|
||||||
|
|
@ -28,8 +28,8 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b
|
||||||
+ const verified = false && !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired
|
+ const verified = false && !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired
|
||||||
if (verified) client._signatureCache.push(packet.signature)
|
if (verified) client._signatureCache.push(packet.signature)
|
||||||
client.emit('playerChat', {
|
client.emit('playerChat', {
|
||||||
globalIndex: packet.globalIndex,
|
plainMessage: packet.plainMessage,
|
||||||
@@ -362,7 +362,7 @@ module.exports = function (client, options) {
|
@@ -354,7 +354,7 @@ module.exports = function (client, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,16 +38,16 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b
|
||||||
options.timestamp = options.timestamp || BigInt(Date.now())
|
options.timestamp = options.timestamp || BigInt(Date.now())
|
||||||
options.salt = options.salt || 1n
|
options.salt = options.salt || 1n
|
||||||
|
|
||||||
@@ -407,7 +407,7 @@ module.exports = function (client, options) {
|
@@ -396,7 +396,7 @@ module.exports = function (client, options) {
|
||||||
message,
|
message,
|
||||||
timestamp: options.timestamp,
|
timestamp: options.timestamp,
|
||||||
salt: options.salt,
|
salt: options.salt,
|
||||||
- signature: (client.profileKeys && client._session) ? client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
|
- signature: (client.profileKeys && client._session) ? client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
|
||||||
+ signature: (client.profileKeys && client._session) ? await client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
|
+ signature: (client.profileKeys && client._session) ? await client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined,
|
||||||
offset: client._lastSeenMessages.pending,
|
offset: client._lastSeenMessages.pending,
|
||||||
checksum: computeChatChecksum(client._lastSeenMessages), // 1.21.5+
|
|
||||||
acknowledged
|
acknowledged
|
||||||
@@ -422,7 +422,7 @@ module.exports = function (client, options) {
|
})
|
||||||
|
@@ -410,7 +410,7 @@ module.exports = function (client, options) {
|
||||||
message,
|
message,
|
||||||
timestamp: options.timestamp,
|
timestamp: options.timestamp,
|
||||||
salt: options.salt,
|
salt: options.salt,
|
||||||
|
|
@ -57,7 +57,7 @@ index 0021870994fc59a82f0ac8aba0a65a8be43ef2f4..a53fceb843105ea2a1d88722b3fc7c3b
|
||||||
previousMessages: client._lastSeenMessages.map((e) => ({
|
previousMessages: client._lastSeenMessages.map((e) => ({
|
||||||
messageSender: e.sender,
|
messageSender: e.sender,
|
||||||
diff --git a/src/client/encrypt.js b/src/client/encrypt.js
|
diff --git a/src/client/encrypt.js b/src/client/encrypt.js
|
||||||
index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f85410eaef 100644
|
index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108f4c63536 100644
|
||||||
--- a/src/client/encrypt.js
|
--- a/src/client/encrypt.js
|
||||||
+++ b/src/client/encrypt.js
|
+++ b/src/client/encrypt.js
|
||||||
@@ -25,7 +25,11 @@ module.exports = function (client, options) {
|
@@ -25,7 +25,11 @@ module.exports = function (client, options) {
|
||||||
|
|
@ -73,24 +73,41 @@ index 63cc2bd9615100bd2fd63dfe14c094aa6b8cd1c9..36df57d1196af9761d920fa285ac48f8
|
||||||
}
|
}
|
||||||
|
|
||||||
function onJoinServerResponse (err) {
|
function onJoinServerResponse (err) {
|
||||||
diff --git a/src/client/pluginChannels.js b/src/client/pluginChannels.js
|
diff --git a/src/client/play.js b/src/client/play.js
|
||||||
index 671eb452f31e6b5fcd57d715f1009d010160c65f..7f69f511c8fb97d431ec5125c851b49be8e2ab76 100644
|
index 559607f34e9a5b2b7809423f8ca4cd6746b60225..4dc1c3139438cc2729b05c57e57bd00252728f8a 100644
|
||||||
--- a/src/client/pluginChannels.js
|
--- a/src/client/play.js
|
||||||
+++ b/src/client/pluginChannels.js
|
+++ b/src/client/play.js
|
||||||
@@ -57,7 +57,7 @@ module.exports = function (client, options) {
|
@@ -53,7 +53,7 @@ module.exports = function (client, options) {
|
||||||
try {
|
client.write('configuration_acknowledged', {})
|
||||||
packet.data = proto.parsePacketBuffer(channel, packet.data).data
|
|
||||||
} catch (error) {
|
|
||||||
- client.emit('error', error)
|
|
||||||
+ client.emit('error', error, { customPayload: packet })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
client.state = states.CONFIGURATION
|
||||||
|
- client.on('select_known_packs', () => {
|
||||||
|
+ client.once('select_known_packs', () => {
|
||||||
|
client.write('select_known_packs', { packs: [] })
|
||||||
|
})
|
||||||
|
// Server should send finish_configuration on its own right after sending the client a dimension codec
|
||||||
diff --git a/src/client.js b/src/client.js
|
diff --git a/src/client.js b/src/client.js
|
||||||
index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875989cdf0c 100644
|
index 5c7a62b013daa69be91ec9e763b1f48ffe96ffa6..174d42a77740a937afcb106e1f39a9ee824a24b9 100644
|
||||||
--- a/src/client.js
|
--- a/src/client.js
|
||||||
+++ b/src/client.js
|
+++ b/src/client.js
|
||||||
@@ -111,7 +111,13 @@ class Client extends EventEmitter {
|
@@ -89,10 +89,12 @@ class Client extends EventEmitter {
|
||||||
|
parsed.metadata.name = parsed.data.name
|
||||||
|
parsed.data = parsed.data.params
|
||||||
|
parsed.metadata.state = state
|
||||||
|
- debug('read packet ' + state + '.' + parsed.metadata.name)
|
||||||
|
- if (debug.enabled) {
|
||||||
|
- const s = JSON.stringify(parsed.data, null, 2)
|
||||||
|
- debug(s && s.length > 10000 ? parsed.data : s)
|
||||||
|
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(parsed.metadata.name)) {
|
||||||
|
+ debug('read packet ' + state + '.' + parsed.metadata.name)
|
||||||
|
+ if (debug.enabled) {
|
||||||
|
+ const s = JSON.stringify(parsed.data, null, 2)
|
||||||
|
+ debug(s && s.length > 10000 ? parsed.data : s)
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') {
|
||||||
|
if (this._mcBundle.length) { // End bundle
|
||||||
|
@@ -110,7 +112,13 @@ class Client extends EventEmitter {
|
||||||
this._hasBundlePacket = false
|
this._hasBundlePacket = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -105,7 +122,7 @@ index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -169,7 +175,10 @@ class Client extends EventEmitter {
|
@@ -168,7 +176,10 @@ class Client extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onFatalError = (err) => {
|
const onFatalError = (err) => {
|
||||||
|
|
@ -117,21 +134,25 @@ index e369e77d055ba919e8f9da7b8e8b5dc879c74cf4..54bb9e6644388e9b6bd42b3012951875
|
||||||
endSocket()
|
endSocket()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +207,10 @@ class Client extends EventEmitter {
|
@@ -197,6 +208,8 @@ class Client extends EventEmitter {
|
||||||
serializer -> framer -> socket -> splitter -> deserializer */
|
serializer -> framer -> socket -> splitter -> deserializer */
|
||||||
if (this.serializer) {
|
if (this.serializer) {
|
||||||
this.serializer.end()
|
this.serializer.end()
|
||||||
+ setTimeout(() => {
|
+ this.socket?.end()
|
||||||
+ this.socket?.end()
|
+ this.socket?.emit('end')
|
||||||
+ this.socket?.emit('end')
|
|
||||||
+ }, 2000) // allow the serializer to finish writing
|
|
||||||
} else {
|
} else {
|
||||||
if (this.socket) this.socket.end()
|
if (this.socket) this.socket.end()
|
||||||
}
|
}
|
||||||
@@ -243,6 +256,7 @@ class Client extends EventEmitter {
|
@@ -238,8 +251,11 @@ class Client extends EventEmitter {
|
||||||
debug('writing packet ' + this.state + '.' + name)
|
|
||||||
debug(params)
|
write (name, params) {
|
||||||
}
|
if (!this.serializer.writable) { return }
|
||||||
|
- debug('writing packet ' + this.state + '.' + name)
|
||||||
|
- debug(params)
|
||||||
|
+ if (!globalThis.excludeCommunicationDebugEvents?.includes(name)) {
|
||||||
|
+ debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name)
|
||||||
|
+ debug(params)
|
||||||
|
+ }
|
||||||
+ this.emit('writePacket', name, params)
|
+ this.emit('writePacket', name, params)
|
||||||
this.serializer.write({ name, params })
|
this.serializer.write({ name, params })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
288
pnpm-lock.yaml
generated
288
pnpm-lock.yaml
generated
|
|
@ -5,7 +5,6 @@ settings:
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
overrides:
|
overrides:
|
||||||
mineflayer: github:zardoy/mineflayer#gen-the-master
|
|
||||||
'@nxg-org/mineflayer-physics-util': 1.8.10
|
'@nxg-org/mineflayer-physics-util': 1.8.10
|
||||||
buffer: ^6.0.3
|
buffer: ^6.0.3
|
||||||
vec3: 0.1.10
|
vec3: 0.1.10
|
||||||
|
|
@ -13,7 +12,7 @@ overrides:
|
||||||
diamond-square: github:zardoy/diamond-square
|
diamond-square: github:zardoy/diamond-square
|
||||||
prismarine-block: github:zardoy/prismarine-block#next-era
|
prismarine-block: github:zardoy/prismarine-block#next-era
|
||||||
prismarine-world: github:zardoy/prismarine-world#next-era
|
prismarine-world: github:zardoy/prismarine-world#next-era
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything
|
prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything
|
||||||
prismarine-physics: github:zardoy/prismarine-physics
|
prismarine-physics: github:zardoy/prismarine-physics
|
||||||
minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master
|
minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master
|
||||||
|
|
@ -23,7 +22,7 @@ overrides:
|
||||||
|
|
||||||
patchedDependencies:
|
patchedDependencies:
|
||||||
minecraft-protocol:
|
minecraft-protocol:
|
||||||
hash: 4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b
|
hash: a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0
|
||||||
path: patches/minecraft-protocol.patch
|
path: patches/minecraft-protocol.patch
|
||||||
mineflayer-item-map-downloader@1.2.0:
|
mineflayer-item-map-downloader@1.2.0:
|
||||||
hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad
|
hash: a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad
|
||||||
|
|
@ -42,9 +41,6 @@ importers:
|
||||||
'@floating-ui/react':
|
'@floating-ui/react':
|
||||||
specifier: ^0.26.1
|
specifier: ^0.26.1
|
||||||
version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@monaco-editor/react':
|
|
||||||
specifier: ^4.7.0
|
|
||||||
version: 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
|
||||||
'@nxg-org/mineflayer-auto-jump':
|
'@nxg-org/mineflayer-auto-jump':
|
||||||
specifier: ^0.7.18
|
specifier: ^0.7.18
|
||||||
version: 0.7.18
|
version: 0.7.18
|
||||||
|
|
@ -121,8 +117,8 @@ importers:
|
||||||
specifier: ^10.0.12
|
specifier: ^10.0.12
|
||||||
version: 10.1.6
|
version: 10.1.6
|
||||||
flying-squid:
|
flying-squid:
|
||||||
specifier: npm:@zardoy/flying-squid@^0.0.104
|
specifier: npm:@zardoy/flying-squid@^0.0.62
|
||||||
version: '@zardoy/flying-squid@0.0.104(encoding@0.1.13)'
|
version: '@zardoy/flying-squid@0.0.62(encoding@0.1.13)'
|
||||||
framer-motion:
|
framer-motion:
|
||||||
specifier: ^12.9.2
|
specifier: ^12.9.2
|
||||||
version: 12.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 12.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
@ -140,13 +136,13 @@ importers:
|
||||||
version: 4.17.21
|
version: 4.17.21
|
||||||
mcraft-fun-mineflayer:
|
mcraft-fun-mineflayer:
|
||||||
specifier: ^0.1.23
|
specifier: ^0.1.23
|
||||||
version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13))
|
version: 0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13))
|
||||||
minecraft-data:
|
minecraft-data:
|
||||||
specifier: 3.98.0
|
specifier: 3.92.0
|
||||||
version: 3.98.0
|
version: 3.92.0
|
||||||
minecraft-protocol:
|
minecraft-protocol:
|
||||||
specifier: github:PrismarineJS/node-minecraft-protocol#master
|
specifier: github:PrismarineJS/node-minecraft-protocol#master
|
||||||
version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
|
version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13)
|
||||||
mineflayer-item-map-downloader:
|
mineflayer-item-map-downloader:
|
||||||
specifier: github:zardoy/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)
|
version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13)
|
||||||
|
|
@ -155,7 +151,7 @@ importers:
|
||||||
version: 2.0.4
|
version: 2.0.4
|
||||||
net-browserify:
|
net-browserify:
|
||||||
specifier: github:zardoy/prismarinejs-net-browserify
|
specifier: github:zardoy/prismarinejs-net-browserify
|
||||||
version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618
|
version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5
|
||||||
node-gzip:
|
node-gzip:
|
||||||
specifier: ^1.1.2
|
specifier: ^1.1.2
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
|
|
@ -170,7 +166,7 @@ importers:
|
||||||
version: 6.1.1
|
version: 6.1.1
|
||||||
prismarine-provider-anvil:
|
prismarine-provider-anvil:
|
||||||
specifier: github:zardoy/prismarine-provider-anvil#everything
|
specifier: github:zardoy/prismarine-provider-anvil#everything
|
||||||
version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0)
|
version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
|
||||||
prosemirror-example-setup:
|
prosemirror-example-setup:
|
||||||
specifier: ^1.2.2
|
specifier: ^1.2.2
|
||||||
version: 1.2.3
|
version: 1.2.3
|
||||||
|
|
@ -343,12 +339,18 @@ importers:
|
||||||
minecraft-inventory-gui:
|
minecraft-inventory-gui:
|
||||||
specifier: github:zardoy/minecraft-inventory-gui#next
|
specifier: github:zardoy/minecraft-inventory-gui#next
|
||||||
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41(@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)
|
||||||
|
minecraft-lighting:
|
||||||
|
specifier: ^0.0.10
|
||||||
|
version: 0.0.10
|
||||||
mineflayer:
|
mineflayer:
|
||||||
specifier: github:zardoy/mineflayer#gen-the-master
|
specifier: github:zardoy/mineflayer#gen-the-master
|
||||||
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
|
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)
|
||||||
mineflayer-mouse:
|
mineflayer-mouse:
|
||||||
specifier: ^0.1.21
|
specifier: ^0.1.11
|
||||||
version: 0.1.21
|
version: 0.1.11
|
||||||
|
mineflayer-pathfinder:
|
||||||
|
specifier: ^2.4.4
|
||||||
|
version: 2.4.5
|
||||||
npm-run-all:
|
npm-run-all:
|
||||||
specifier: ^4.1.5
|
specifier: ^4.1.5
|
||||||
version: 4.1.5
|
version: 4.1.5
|
||||||
|
|
@ -436,7 +438,7 @@ importers:
|
||||||
version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
prismarine-chunk:
|
prismarine-chunk:
|
||||||
specifier: github:zardoy/prismarine-chunk#master
|
specifier: github:zardoy/prismarine-chunk#master
|
||||||
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
|
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
|
||||||
prismarine-schematic:
|
prismarine-schematic:
|
||||||
specifier: ^1.2.0
|
specifier: ^1.2.0
|
||||||
version: 1.2.3
|
version: 1.2.3
|
||||||
|
|
@ -1992,16 +1994,6 @@ packages:
|
||||||
'@module-federation/webpack-bundler-runtime@0.11.2':
|
'@module-federation/webpack-bundler-runtime@0.11.2':
|
||||||
resolution: {integrity: sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==}
|
resolution: {integrity: sha512-WdwIE6QF+MKs/PdVu0cKPETF743JB9PZ62/qf7Uo3gU4fjsUMc37RnbJZ/qB60EaHHfjwp1v6NnhZw1r4eVsnw==}
|
||||||
|
|
||||||
'@monaco-editor/loader@1.5.0':
|
|
||||||
resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==}
|
|
||||||
|
|
||||||
'@monaco-editor/react@4.7.0':
|
|
||||||
resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==}
|
|
||||||
peerDependencies:
|
|
||||||
monaco-editor: '>= 0.25.0 < 1'
|
|
||||||
react: ^18.2.0
|
|
||||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
|
||||||
|
|
||||||
'@msgpack/msgpack@2.8.0':
|
'@msgpack/msgpack@2.8.0':
|
||||||
resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==}
|
resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
@ -3387,13 +3379,13 @@ packages:
|
||||||
resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==}
|
resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==}
|
||||||
engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'}
|
engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'}
|
||||||
|
|
||||||
'@zardoy/flying-squid@0.0.104':
|
'@zardoy/flying-squid@0.0.49':
|
||||||
resolution: {integrity: sha512-jGhQ7fn7o8UN+mUwZbt9674D37YLuBi+Au4TwKcopCA6huIQdHTFNl2e+0ZSTI5mnhN+NpyVoR3vmtH6L58vHQ==}
|
resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@zardoy/flying-squid@0.0.49':
|
'@zardoy/flying-squid@0.0.62':
|
||||||
resolution: {integrity: sha512-Kt4wr5/R+44tcLU9gjuNG2an9weWeKEpIoKXfsgJN2GGQqdnbd5nBpxfGDdgZ9aMdFugsVW8BsyPZNhj9vbMXA==}
|
resolution: {integrity: sha512-M6icydO/yrmwevBhmgKcqEPC63AhWfU/Es9N/uadVrmKaxGm2FQMMLcybbutRYm1xZ6qsdxDUOUZnN56PsVwfQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
|
@ -6444,12 +6436,6 @@ packages:
|
||||||
resolution: {integrity: sha512-RYZeD1+joNlPuUpi+tIWkbP0ieVJr+R6IFkI6/8juhSxx9zE4osoSmteybrfspGm8A6u+YbbY1epqRKEMwVR6Q==}
|
resolution: {integrity: sha512-RYZeD1+joNlPuUpi+tIWkbP0ieVJr+R6IFkI6/8juhSxx9zE4osoSmteybrfspGm8A6u+YbbY1epqRKEMwVR6Q==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
mc-bridge@0.1.3:
|
|
||||||
resolution: {integrity: sha512-H9jPt2xEU77itC27dSz3qazHYqN9qVsv4HgMPozg7RqQ1uwgXmEa+ojKIlDtXf/TLJsG6Kv4EbzGa8a1Wh72uA==}
|
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
|
||||||
peerDependencies:
|
|
||||||
minecraft-data: 3.98.0
|
|
||||||
|
|
||||||
mcraft-fun-mineflayer@0.1.23:
|
mcraft-fun-mineflayer@0.1.23:
|
||||||
resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==}
|
resolution: {integrity: sha512-qmI1rQQ0Ro5zJdi99rClWLF+mS9JZffgNX2vyWWesS3Hsk3Xblp/8swYTJKHSaFpNgzkVfXV92fEIrBqeH6iKA==}
|
||||||
version: 0.1.23
|
version: 0.1.23
|
||||||
|
|
@ -6658,8 +6644,8 @@ packages:
|
||||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
minecraft-data@3.98.0:
|
minecraft-data@3.92.0:
|
||||||
resolution: {integrity: sha512-JAPqJ/TZoxMUlAPPdWUh1v5wdqvYGFSZ4rW9bUtmaKBkGpomDSjw4V02ocBqbxKJvcTtmc5nM/LfN9/0DDqHrQ==}
|
resolution: {integrity: sha512-CGfO50svzm+pSRa4Mbq4owsmRKbPCNkSZ3MCOyH+epC7yNjh+PUhPQFHWq72O51qsY7pAB5qM/bJn1ncwG1J5g==}
|
||||||
|
|
||||||
minecraft-folder-path@1.2.0:
|
minecraft-folder-path@1.2.0:
|
||||||
resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==}
|
resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==}
|
||||||
|
|
@ -6668,9 +6654,13 @@ packages:
|
||||||
resolution: {tarball: 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
|
version: 1.0.1
|
||||||
|
|
||||||
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9:
|
minecraft-lighting@0.0.10:
|
||||||
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9}
|
resolution: {integrity: sha512-m3RNe5opaibquxyO0ly1FpKdehapvp9hRRY37RccKY4bio2LGnN3nCZ3PrOXy0C596YpxBsG1OCYg0dqtPzehg==}
|
||||||
version: 1.62.0
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.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'}
|
engines: {node: '>=22'}
|
||||||
|
|
||||||
minecraft-wrap@1.6.0:
|
minecraft-wrap@1.6.0:
|
||||||
|
|
@ -6684,13 +6674,20 @@ packages:
|
||||||
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824}
|
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824}
|
||||||
version: 1.2.0
|
version: 1.2.0
|
||||||
|
|
||||||
mineflayer-mouse@0.1.21:
|
mineflayer-mouse@0.1.11:
|
||||||
resolution: {integrity: sha512-1XTVuw3twIrEcqQ1QRSB8NcStIUEZ+tbxiAG6rOrN/9M4thhtlS5PTJzFdmdrcYgWEBLvuOdJszaKE5zFfiXhg==}
|
resolution: {integrity: sha512-BL47pXZ1+92BA/7ym6KaJctEHKnL0up+tpuagVwSKJvAgibeqWQJJwDlNUWkOLvpnruRKDxMR5OB1hUXFoDNSg==}
|
||||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||||
|
|
||||||
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659:
|
mineflayer-pathfinder@2.4.5:
|
||||||
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659}
|
resolution: {integrity: sha512-Jh3JnUgRLwhMh2Dugo4SPza68C41y+NPP5sdsgxRu35ydndo70i1JJGxauVWbXrpNwIxYNztUw78aFyb7icw8g==}
|
||||||
version: 8.0.0
|
|
||||||
|
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/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980:
|
||||||
|
resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980}
|
||||||
|
version: 4.30.0
|
||||||
engines: {node: '>=22'}
|
engines: {node: '>=22'}
|
||||||
|
|
||||||
minimalistic-assert@1.0.1:
|
minimalistic-assert@1.0.1:
|
||||||
|
|
@ -6788,9 +6785,6 @@ packages:
|
||||||
mojangson@2.0.4:
|
mojangson@2.0.4:
|
||||||
resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==}
|
resolution: {integrity: sha512-HYmhgDjr1gzF7trGgvcC/huIg2L8FsVbi/KacRe6r1AswbboGVZDS47SOZlomPuMWvZLas8m9vuHHucdZMwTmQ==}
|
||||||
|
|
||||||
monaco-editor@0.52.2:
|
|
||||||
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
|
|
||||||
|
|
||||||
moo@0.5.2:
|
moo@0.5.2:
|
||||||
resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==}
|
resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==}
|
||||||
|
|
||||||
|
|
@ -6855,8 +6849,8 @@ packages:
|
||||||
neo-async@2.6.2:
|
neo-async@2.6.2:
|
||||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||||
|
|
||||||
net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618:
|
net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5:
|
||||||
resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618}
|
resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5}
|
||||||
version: 0.2.4
|
version: 0.2.4
|
||||||
|
|
||||||
nice-try@1.0.5:
|
nice-try@1.0.5:
|
||||||
|
|
@ -7387,7 +7381,7 @@ packages:
|
||||||
prismarine-biome@1.3.0:
|
prismarine-biome@1.3.0:
|
||||||
resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==}
|
resolution: {integrity: sha512-GY6nZxq93mTErT7jD7jt8YS1aPrOakbJHh39seYsJFXvueIOdHAmW16kYQVrTVMW5MlWLQVxV/EquRwOgr4MnQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-registry: ^1.1.0
|
prismarine-registry: ^1.1.0
|
||||||
|
|
||||||
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
|
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
|
||||||
|
|
@ -7405,8 +7399,8 @@ packages:
|
||||||
prismarine-entity@2.5.0:
|
prismarine-entity@2.5.0:
|
||||||
resolution: {integrity: sha512-nRPCawUwf9r3iKqi4I7mZRlir1Ix+DffWYdWq6p/KNnmiXve+xHE5zv8XCdhZlUmOshugHv5ONl9o6ORAkCNIA==}
|
resolution: {integrity: sha512-nRPCawUwf9r3iKqi4I7mZRlir1Ix+DffWYdWq6p/KNnmiXve+xHE5zv8XCdhZlUmOshugHv5ONl9o6ORAkCNIA==}
|
||||||
|
|
||||||
prismarine-item@1.17.0:
|
prismarine-item@1.16.0:
|
||||||
resolution: {integrity: sha512-wN1OjP+f+Uvtjo3KzeCkVSy96CqZ8yG7cvuvlGwcYupQ6ct7LtNkubHp0AHuLMJ0vbbfAC0oZ2bWOgI1DYp8WA==}
|
resolution: {integrity: sha512-88Tz+/6HquYIsDuseae5G3IbqLeMews2L+ba2gX+p6K6soU9nuFhCfbwN56QuB7d/jZFcWrCYAPE5+UhwWh67w==}
|
||||||
|
|
||||||
prismarine-nbt@2.7.0:
|
prismarine-nbt@2.7.0:
|
||||||
resolution: {integrity: sha512-Du9OLQAcCj3y29YtewOJbbV4ARaSUEJiTguw0PPQbPBy83f+eCyDRkyBpnXTi/KPyEpgYCzsjGzElevLpFoYGQ==}
|
resolution: {integrity: sha512-Du9OLQAcCj3y29YtewOJbbV4ARaSUEJiTguw0PPQbPBy83f+eCyDRkyBpnXTi/KPyEpgYCzsjGzElevLpFoYGQ==}
|
||||||
|
|
@ -8337,7 +8331,6 @@ packages:
|
||||||
source-map@0.8.0-beta.0:
|
source-map@0.8.0-beta.0:
|
||||||
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
|
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
deprecated: The work that was done in this beta branch won't be included in future versions
|
|
||||||
|
|
||||||
sourcemap-codec@1.4.8:
|
sourcemap-codec@1.4.8:
|
||||||
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
|
||||||
|
|
@ -8395,9 +8388,6 @@ packages:
|
||||||
stacktrace-js@2.0.2:
|
stacktrace-js@2.0.2:
|
||||||
resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==}
|
resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==}
|
||||||
|
|
||||||
state-local@1.0.7:
|
|
||||||
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
|
|
||||||
|
|
||||||
static-extend@0.1.2:
|
static-extend@0.1.2:
|
||||||
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
|
resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
@ -9685,7 +9675,7 @@ snapshots:
|
||||||
'@babel/core': 7.26.9
|
'@babel/core': 7.26.9
|
||||||
'@babel/helper-compilation-targets': 7.26.5
|
'@babel/helper-compilation-targets': 7.26.5
|
||||||
'@babel/helper-plugin-utils': 7.26.5
|
'@babel/helper-plugin-utils': 7.26.5
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
lodash.debounce: 4.0.8
|
lodash.debounce: 4.0.8
|
||||||
resolve: 1.22.10
|
resolve: 1.22.10
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
|
@ -10310,7 +10300,7 @@ snapshots:
|
||||||
'@babel/parser': 7.26.9
|
'@babel/parser': 7.26.9
|
||||||
'@babel/template': 7.26.9
|
'@babel/template': 7.26.9
|
||||||
'@babel/types': 7.26.9
|
'@babel/types': 7.26.9
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
globals: 11.12.0
|
globals: 11.12.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
@ -11279,17 +11269,6 @@ snapshots:
|
||||||
'@module-federation/runtime': 0.11.2
|
'@module-federation/runtime': 0.11.2
|
||||||
'@module-federation/sdk': 0.11.2
|
'@module-federation/sdk': 0.11.2
|
||||||
|
|
||||||
'@monaco-editor/loader@1.5.0':
|
|
||||||
dependencies:
|
|
||||||
state-local: 1.0.7
|
|
||||||
|
|
||||||
'@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
|
||||||
dependencies:
|
|
||||||
'@monaco-editor/loader': 1.5.0
|
|
||||||
monaco-editor: 0.52.2
|
|
||||||
react: 18.3.1
|
|
||||||
react-dom: 18.3.1(react@18.3.1)
|
|
||||||
|
|
||||||
'@msgpack/msgpack@2.8.0': {}
|
'@msgpack/msgpack@2.8.0': {}
|
||||||
|
|
||||||
'@ndelangen/get-tarball@3.0.9':
|
'@ndelangen/get-tarball@3.0.9':
|
||||||
|
|
@ -11343,10 +11322,10 @@ snapshots:
|
||||||
'@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)':
|
'@nxg-org/mineflayer-trajectories@1.2.0(encoding@0.1.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nxg-org/mineflayer-util-plugin': 1.8.4
|
'@nxg-org/mineflayer-util-plugin': 1.8.4
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
|
mineflayer: 4.30.0(encoding@0.1.13)
|
||||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
|
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
|
||||||
vec3: 0.1.10
|
vec3: 0.1.10
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
|
|
@ -12891,7 +12870,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/typescript-estree': 6.1.0(typescript@5.5.4)
|
'@typescript-eslint/typescript-estree': 6.1.0(typescript@5.5.4)
|
||||||
'@typescript-eslint/utils': 6.1.0(eslint@8.57.1)(typescript@5.5.4)
|
'@typescript-eslint/utils': 6.1.0(eslint@8.57.1)(typescript@5.5.4)
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
eslint: 8.57.1
|
eslint: 8.57.1
|
||||||
ts-api-utils: 1.4.3(typescript@5.5.4)
|
ts-api-utils: 1.4.3(typescript@5.5.4)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|
@ -12909,7 +12888,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 6.1.0
|
'@typescript-eslint/types': 6.1.0
|
||||||
'@typescript-eslint/visitor-keys': 6.1.0
|
'@typescript-eslint/visitor-keys': 6.1.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
semver: 7.7.1
|
semver: 7.7.1
|
||||||
|
|
@ -12923,7 +12902,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 6.21.0
|
'@typescript-eslint/types': 6.21.0
|
||||||
'@typescript-eslint/visitor-keys': 6.21.0
|
'@typescript-eslint/visitor-keys': 6.21.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
globby: 11.1.0
|
globby: 11.1.0
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 9.0.3
|
minimatch: 9.0.3
|
||||||
|
|
@ -12938,7 +12917,7 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/types': 8.26.0
|
'@typescript-eslint/types': 8.26.0
|
||||||
'@typescript-eslint/visitor-keys': 8.26.0
|
'@typescript-eslint/visitor-keys': 8.26.0
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
|
|
@ -13094,7 +13073,7 @@ snapshots:
|
||||||
'@types/emscripten': 1.40.0
|
'@types/emscripten': 1.40.0
|
||||||
tslib: 1.14.1
|
tslib: 1.14.1
|
||||||
|
|
||||||
'@zardoy/flying-squid@0.0.104(encoding@0.1.13)':
|
'@zardoy/flying-squid@0.0.49(encoding@0.1.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tootallnate/once': 2.0.0
|
'@tootallnate/once': 2.0.0
|
||||||
chalk: 5.4.1
|
chalk: 5.4.1
|
||||||
|
|
@ -13104,18 +13083,16 @@ snapshots:
|
||||||
exit-hook: 2.2.1
|
exit-hook: 2.2.1
|
||||||
flatmap: 0.0.3
|
flatmap: 0.0.3
|
||||||
long: 5.3.1
|
long: 5.3.1
|
||||||
mc-bridge: 0.1.3(minecraft-data@3.98.0)
|
minecraft-data: 3.92.0
|
||||||
minecraft-data: 3.98.0
|
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13)
|
||||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
|
|
||||||
mkdirp: 2.1.6
|
mkdirp: 2.1.6
|
||||||
node-gzip: 1.1.2
|
node-gzip: 1.1.2
|
||||||
node-rsa: 1.1.1
|
node-rsa: 1.1.1
|
||||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
|
|
||||||
prismarine-entity: 2.5.0
|
prismarine-entity: 2.5.0
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0)
|
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
|
||||||
prismarine-windows: 2.9.0
|
prismarine-windows: 2.9.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
rambda: 9.4.2
|
rambda: 9.4.2
|
||||||
|
|
@ -13132,7 +13109,7 @@ snapshots:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@zardoy/flying-squid@0.0.49(encoding@0.1.13)':
|
'@zardoy/flying-squid@0.0.62(encoding@0.1.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tootallnate/once': 2.0.0
|
'@tootallnate/once': 2.0.0
|
||||||
chalk: 5.4.1
|
chalk: 5.4.1
|
||||||
|
|
@ -13142,16 +13119,16 @@ snapshots:
|
||||||
exit-hook: 2.2.1
|
exit-hook: 2.2.1
|
||||||
flatmap: 0.0.3
|
flatmap: 0.0.3
|
||||||
long: 5.3.1
|
long: 5.3.1
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
|
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13)
|
||||||
mkdirp: 2.1.6
|
mkdirp: 2.1.6
|
||||||
node-gzip: 1.1.2
|
node-gzip: 1.1.2
|
||||||
node-rsa: 1.1.1
|
node-rsa: 1.1.1
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
|
||||||
prismarine-entity: 2.5.0
|
prismarine-entity: 2.5.0
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0)
|
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0)
|
||||||
prismarine-windows: 2.9.0
|
prismarine-windows: 2.9.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
rambda: 9.4.2
|
rambda: 9.4.2
|
||||||
|
|
@ -14532,7 +14509,7 @@ snapshots:
|
||||||
detect-port@1.6.1:
|
detect-port@1.6.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
address: 1.2.2
|
address: 1.2.2
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
|
@ -14542,8 +14519,8 @@ snapshots:
|
||||||
|
|
||||||
diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71:
|
diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/cfaad2d1d5909fdfa63c8cc7bc05fb5e87782d71:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
random-seed: 0.3.0
|
random-seed: 0.3.0
|
||||||
vec3: 0.1.10
|
vec3: 0.1.10
|
||||||
|
|
@ -16139,7 +16116,7 @@ snapshots:
|
||||||
https-proxy-agent@4.0.0:
|
https-proxy-agent@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 5.1.1
|
agent-base: 5.1.1
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
|
@ -16986,17 +16963,13 @@ snapshots:
|
||||||
maxrects-packer: '@zardoy/maxrects-packer@2.7.4'
|
maxrects-packer: '@zardoy/maxrects-packer@2.7.4'
|
||||||
zod: 3.24.2
|
zod: 3.24.2
|
||||||
|
|
||||||
mc-bridge@0.1.3(minecraft-data@3.98.0):
|
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:
|
|
||||||
minecraft-data: 3.98.0
|
|
||||||
|
|
||||||
mcraft-fun-mineflayer@0.1.23(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)):
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@zardoy/flying-squid': 0.0.49(encoding@0.1.13)
|
'@zardoy/flying-squid': 0.0.49(encoding@0.1.13)
|
||||||
exit-hook: 2.2.1
|
exit-hook: 2.2.1
|
||||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
|
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/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
|
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
ws: 8.18.1
|
ws: 8.18.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
|
|
@ -17225,7 +17198,7 @@ snapshots:
|
||||||
micromark@4.0.2:
|
micromark@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
decode-named-character-reference: 1.1.0
|
decode-named-character-reference: 1.1.0
|
||||||
devlop: 1.1.0
|
devlop: 1.1.0
|
||||||
micromark-core-commonmark: 2.0.3
|
micromark-core-commonmark: 2.0.3
|
||||||
|
|
@ -17302,7 +17275,7 @@ snapshots:
|
||||||
|
|
||||||
min-indent@1.0.1: {}
|
min-indent@1.0.1: {}
|
||||||
|
|
||||||
minecraft-data@3.98.0: {}
|
minecraft-data@3.92.0: {}
|
||||||
|
|
||||||
minecraft-folder-path@1.2.0: {}
|
minecraft-folder-path@1.2.0: {}
|
||||||
|
|
||||||
|
|
@ -17313,7 +17286,11 @@ snapshots:
|
||||||
- '@types/react'
|
- '@types/react'
|
||||||
- react
|
- react
|
||||||
|
|
||||||
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13):
|
minecraft-lighting@0.0.10:
|
||||||
|
dependencies:
|
||||||
|
vec3: 0.1.10
|
||||||
|
|
||||||
|
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/6c2204a813690ead420e2b8c7f0ef32ca357d176(patch_hash=a8726e6981ddc3486262d981d1e2030f379901c055ac9c4bf3036b4149e860e0)(encoding@0.1.13):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node-rsa': 1.1.4
|
'@types/node-rsa': 1.1.4
|
||||||
'@types/readable-stream': 4.0.18
|
'@types/readable-stream': 4.0.18
|
||||||
|
|
@ -17322,7 +17299,7 @@ snapshots:
|
||||||
debug: 4.4.0(supports-color@8.1.1)
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
endian-toggle: 0.0.0
|
endian-toggle: 0.0.0
|
||||||
lodash.merge: 4.6.2
|
lodash.merge: 4.6.2
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
minecraft-folder-path: 1.2.0
|
minecraft-folder-path: 1.2.0
|
||||||
node-fetch: 2.7.0(encoding@0.1.13)
|
node-fetch: 2.7.0(encoding@0.1.13)
|
||||||
node-rsa: 0.4.2
|
node-rsa: 0.4.2
|
||||||
|
|
@ -17365,32 +17342,65 @@ snapshots:
|
||||||
|
|
||||||
mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13):
|
mineflayer-item-map-downloader@https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/a8d210ecdcf78dd082fa149a96e1612cc9747824(patch_hash=a731ebbace2d8790c973ab3a5ba33494a6e9658533a9710dd8ba36f86db061ad)(encoding@0.1.13):
|
||||||
dependencies:
|
dependencies:
|
||||||
mineflayer: https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13)
|
mineflayer: 4.30.0(encoding@0.1.13)
|
||||||
sharp: 0.30.7
|
sharp: 0.30.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
mineflayer-mouse@0.1.21:
|
mineflayer-mouse@0.1.11:
|
||||||
dependencies:
|
dependencies:
|
||||||
change-case: 5.4.4
|
change-case: 5.4.4
|
||||||
debug: 4.4.1
|
debug: 4.4.1
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dd3b1ff38506d6f72d90e8444186e4e75fe82659(encoding@0.1.13):
|
mineflayer-pathfinder@2.4.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nxg-org/mineflayer-physics-util': 1.8.10
|
minecraft-data: 3.92.0
|
||||||
minecraft-data: 3.98.0
|
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/bf89f7e86526c54d8c43f555d8f6dfa4948fd2d9(patch_hash=4ebdae314c68d01ce7879445c0b8bde5f90373abba8b66ed00d42e7a5f542f8b)(encoding@0.1.13)
|
prismarine-entity: 2.5.0
|
||||||
prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0)
|
prismarine-item: 1.16.0
|
||||||
|
prismarine-nbt: 2.7.0
|
||||||
|
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
|
||||||
|
vec3: 0.1.10
|
||||||
|
|
||||||
|
mineflayer@4.30.0(encoding@0.1.13):
|
||||||
|
dependencies:
|
||||||
|
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-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
prismarine-chat: 1.11.0
|
prismarine-chat: 1.11.0
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
|
||||||
prismarine-entity: 2.5.0
|
prismarine-entity: 2.5.0
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
|
prismarine-nbt: 2.7.0
|
||||||
|
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
|
||||||
|
prismarine-recipe: 1.3.1(prismarine-registry@1.11.0)
|
||||||
|
prismarine-registry: 1.11.0
|
||||||
|
prismarine-windows: 2.9.0
|
||||||
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
|
protodef: 1.18.0
|
||||||
|
typed-emitter: 1.4.0
|
||||||
|
vec3: 0.1.10
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
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.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.92.0)
|
||||||
|
prismarine-entity: 2.5.0
|
||||||
|
prismarine-item: 1.16.0
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
|
prismarine-physics: https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b
|
||||||
prismarine-recipe: 1.3.1(prismarine-registry@1.11.0)
|
prismarine-recipe: 1.3.1(prismarine-registry@1.11.0)
|
||||||
|
|
@ -17503,8 +17513,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
nearley: 2.20.1
|
nearley: 2.20.1
|
||||||
|
|
||||||
monaco-editor@0.52.2: {}
|
|
||||||
|
|
||||||
moo@0.5.2: {}
|
moo@0.5.2: {}
|
||||||
|
|
||||||
morgan@1.10.0:
|
morgan@1.10.0:
|
||||||
|
|
@ -17586,7 +17594,7 @@ snapshots:
|
||||||
|
|
||||||
neo-async@2.6.2: {}
|
neo-async@2.6.2: {}
|
||||||
|
|
||||||
net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/e754999ffdea67853bc9b10553b5e9908b40f618:
|
net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/17fb901e8ea480a52c8fd46373695be172be8aa5:
|
||||||
dependencies:
|
dependencies:
|
||||||
body-parser: 1.20.3
|
body-parser: 1.20.3
|
||||||
express: 4.21.2
|
express: 4.21.2
|
||||||
|
|
@ -18174,17 +18182,17 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
prismarine-biome@1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0):
|
prismarine-biome@1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
|
|
||||||
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
|
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0)
|
prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
|
||||||
prismarine-chat: 1.11.0
|
prismarine-chat: 1.11.0
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
|
|
||||||
|
|
@ -18194,9 +18202,9 @@ snapshots:
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
|
|
||||||
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0):
|
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
prismarine-biome: 1.3.0(minecraft-data@3.98.0)(prismarine-registry@1.11.0)
|
prismarine-biome: 1.3.0(minecraft-data@3.92.0)(prismarine-registry@1.11.0)
|
||||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
|
|
@ -18210,11 +18218,11 @@ snapshots:
|
||||||
prismarine-entity@2.5.0:
|
prismarine-entity@2.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prismarine-chat: 1.11.0
|
prismarine-chat: 1.11.0
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
vec3: 0.1.10
|
vec3: 0.1.10
|
||||||
|
|
||||||
prismarine-item@1.17.0:
|
prismarine-item@1.16.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
|
|
@ -18225,14 +18233,14 @@ snapshots:
|
||||||
|
|
||||||
prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b:
|
prismarine-physics@https://codeload.github.com/zardoy/prismarine-physics/tar.gz/353e25b800149393f40539ec381218be44cbb03b:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
vec3: 0.1.10
|
vec3: 0.1.10
|
||||||
|
|
||||||
prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.98.0):
|
prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.92.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.98.0)
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/c5feac83b61d95feb4d4f22c063dacfb8c192a9f(minecraft-data@3.92.0)
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
uint4: 0.1.2
|
uint4: 0.1.2
|
||||||
|
|
@ -18254,13 +18262,13 @@ snapshots:
|
||||||
|
|
||||||
prismarine-registry@1.11.0:
|
prismarine-registry@1.11.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
|
|
||||||
prismarine-schematic@1.2.3:
|
prismarine-schematic@1.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.98.0
|
minecraft-data: 3.92.0
|
||||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||||
prismarine-nbt: 2.7.0
|
prismarine-nbt: 2.7.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
|
|
@ -18268,7 +18276,7 @@ snapshots:
|
||||||
|
|
||||||
prismarine-windows@2.9.0:
|
prismarine-windows@2.9.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prismarine-item: 1.17.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
typed-emitter: 2.1.0
|
typed-emitter: 2.1.0
|
||||||
|
|
||||||
|
|
@ -18459,7 +18467,7 @@ snapshots:
|
||||||
puppeteer-core@2.1.1:
|
puppeteer-core@2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mime-types': 2.1.4
|
'@types/mime-types': 2.1.4
|
||||||
debug: 4.4.1
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
extract-zip: 1.7.0
|
extract-zip: 1.7.0
|
||||||
https-proxy-agent: 4.0.0
|
https-proxy-agent: 4.0.0
|
||||||
mime: 2.6.0
|
mime: 2.6.0
|
||||||
|
|
@ -19561,8 +19569,6 @@ snapshots:
|
||||||
stack-generator: 2.0.10
|
stack-generator: 2.0.10
|
||||||
stacktrace-gps: 3.1.2
|
stacktrace-gps: 3.1.2
|
||||||
|
|
||||||
state-local@1.0.7: {}
|
|
||||||
|
|
||||||
static-extend@0.1.2:
|
static-extend@0.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
define-property: 0.2.5
|
define-property: 0.2.5
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ function getAllMethods (obj) {
|
||||||
return [...methods] as string[]
|
return [...methods] as string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const delayedIterator = async <T> (arr: T[], delay: number, exec: (item: T, index: number) => Promise<void>, chunkSize = 1) => {
|
export const delayedIterator = async <T> (arr: T[], delay: number, exec: (item: T, index: number) => void, chunkSize = 1) => {
|
||||||
// if delay is 0 then don't use setTimeout
|
// if delay is 0 then don't use setTimeout
|
||||||
for (let i = 0; i < arr.length; i += chunkSize) {
|
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||||
if (delay) {
|
if (delay) {
|
||||||
|
|
@ -74,6 +74,6 @@ export const delayedIterator = async <T> (arr: T[], delay: number, exec: (item:
|
||||||
setTimeout(resolve, delay)
|
setTimeout(resolve, delay)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
await exec(arr[i], i)
|
exec(arr[i], i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,12 +73,12 @@ export const appAndRendererSharedConfig = () => defineConfig({
|
||||||
})
|
})
|
||||||
|
|
||||||
export const rspackViewerConfig = (config, { appendPlugins, addRules, rspack }: ModifyRspackConfigUtils) => {
|
export const rspackViewerConfig = (config, { appendPlugins, addRules, rspack }: ModifyRspackConfigUtils) => {
|
||||||
appendPlugins(new rspack.NormalModuleReplacementPlugin(/data|prismarine-physics/, (resource) => {
|
appendPlugins(new rspack.NormalModuleReplacementPlugin(/data/, (resource) => {
|
||||||
let absolute: string
|
let absolute: string
|
||||||
const request = resource.request.replaceAll('\\', '/')
|
const request = resource.request.replaceAll('\\', '/')
|
||||||
absolute = path.join(resource.context, request).replaceAll('\\', '/')
|
absolute = path.join(resource.context, request).replaceAll('\\', '/')
|
||||||
if (request.includes('minecraft-data/data/pc/1.') || request.includes('prismarine-physics')) {
|
if (request.includes('minecraft-data/data/pc/1.')) {
|
||||||
console.log('Error: incompatible resource', request, 'from', resource.contextInfo.issuer)
|
console.log('Error: incompatible resource', request, resource.contextInfo.issuer)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
// throw new Error(`${resource.request} was requested by ${resource.contextInfo.issuer}`)
|
// throw new Error(`${resource.request} was requested by ${resource.contextInfo.issuer}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ export const getInitialPlayerState = () => proxy({
|
||||||
heldItemMain: undefined as HandItemBlock | undefined,
|
heldItemMain: undefined as HandItemBlock | undefined,
|
||||||
heldItemOff: undefined as HandItemBlock | undefined,
|
heldItemOff: undefined as HandItemBlock | undefined,
|
||||||
perspective: 'first_person' as CameraPerspective,
|
perspective: 'first_person' as CameraPerspective,
|
||||||
onFire: false,
|
|
||||||
|
|
||||||
cameraSpectatingEntity: undefined as number | undefined,
|
cameraSpectatingEntity: undefined as number | undefined,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
import { PlayerObject, PlayerAnimation } from 'skinview3d'
|
|
||||||
import * as THREE from 'three'
|
|
||||||
import { WalkingGeneralSwing } from '../three/entity/animations'
|
|
||||||
import { loadSkinImage, stevePngUrl } from './utils/skins'
|
|
||||||
|
|
||||||
export type PlayerObjectType = PlayerObject & {
|
|
||||||
animation?: PlayerAnimation
|
|
||||||
realPlayerUuid: string
|
|
||||||
realUsername: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createPlayerObject (options: {
|
|
||||||
username?: string
|
|
||||||
uuid?: string
|
|
||||||
scale?: number
|
|
||||||
}): {
|
|
||||||
playerObject: PlayerObjectType
|
|
||||||
wrapper: THREE.Group
|
|
||||||
} {
|
|
||||||
const wrapper = new THREE.Group()
|
|
||||||
const playerObject = new PlayerObject() as PlayerObjectType
|
|
||||||
|
|
||||||
playerObject.realPlayerUuid = options.uuid ?? ''
|
|
||||||
playerObject.realUsername = options.username ?? ''
|
|
||||||
playerObject.position.set(0, 16, 0)
|
|
||||||
|
|
||||||
// fix issues with starfield
|
|
||||||
playerObject.traverse((obj) => {
|
|
||||||
if (obj instanceof THREE.Mesh && obj.material instanceof THREE.MeshStandardMaterial) {
|
|
||||||
obj.material.transparent = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
wrapper.add(playerObject as any)
|
|
||||||
const scale = options.scale ?? (1 / 16)
|
|
||||||
wrapper.scale.set(scale, scale, scale)
|
|
||||||
wrapper.rotation.set(0, Math.PI, 0)
|
|
||||||
|
|
||||||
// Set up animation
|
|
||||||
playerObject.animation = new WalkingGeneralSwing()
|
|
||||||
;(playerObject.animation as WalkingGeneralSwing).isMoving = false
|
|
||||||
playerObject.animation.update(playerObject, 0)
|
|
||||||
|
|
||||||
return { playerObject, wrapper }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const applySkinToPlayerObject = async (playerObject: PlayerObjectType, skinUrl: string) => {
|
|
||||||
return loadSkinImage(skinUrl || stevePngUrl).then(({ canvas }) => {
|
|
||||||
const skinTexture = new THREE.CanvasTexture(canvas)
|
|
||||||
skinTexture.magFilter = THREE.NearestFilter
|
|
||||||
skinTexture.minFilter = THREE.NearestFilter
|
|
||||||
skinTexture.needsUpdate = true
|
|
||||||
playerObject.skin.map = skinTexture as any
|
|
||||||
}).catch(console.error)
|
|
||||||
}
|
|
||||||
93
renderer/viewer/lib/lightEngine.ts
Normal file
93
renderer/viewer/lib/lightEngine.ts
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
import { createPrismarineLightEngineWorker } from 'minecraft-lighting'
|
||||||
|
import { world } from 'prismarine-world'
|
||||||
|
// import PrismarineWorker from 'minecraft-lighting/dist/prismarineWorker.worker.js'
|
||||||
|
import { WorldDataEmitter } from './worldDataEmitter'
|
||||||
|
import { initMesherWorker, meshersSendMcData } from './worldrendererCommon'
|
||||||
|
|
||||||
|
let lightEngineNew: ReturnType<typeof createPrismarineLightEngineWorker> | null = null
|
||||||
|
|
||||||
|
export const getLightEngineSafe = () => {
|
||||||
|
// return lightEngine
|
||||||
|
return lightEngineNew
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createLightEngineIfNeededNew = (worldView: WorldDataEmitter, version: string) => {
|
||||||
|
if (lightEngineNew) return
|
||||||
|
const worker = initMesherWorker((data) => {
|
||||||
|
// console.log('light engine worker message', data)
|
||||||
|
})
|
||||||
|
meshersSendMcData([worker], version)
|
||||||
|
worker.postMessage({ type: 'sideControl', value: 'lightEngine' })
|
||||||
|
lightEngineNew = createPrismarineLightEngineWorker(worker, worldView.world as unknown as world.WorldSync, loadedData)
|
||||||
|
lightEngineNew.initialize({
|
||||||
|
minY: worldView.minY,
|
||||||
|
height: worldView.minY + worldView.worldHeight,
|
||||||
|
// writeLightToOriginalWorld: true,
|
||||||
|
// enableSkyLight: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
globalThis.lightEngine = lightEngineNew
|
||||||
|
}
|
||||||
|
|
||||||
|
export const processLightChunk = async (x: number, z: number, doLighting: boolean) => {
|
||||||
|
const engine = getLightEngineSafe()
|
||||||
|
if (!engine) return
|
||||||
|
|
||||||
|
const chunkX = Math.floor(x / 16)
|
||||||
|
const chunkZ = Math.floor(z / 16)
|
||||||
|
// fillColumnWithZeroLight(engine.externalWorld, chunkX, chunkZ)
|
||||||
|
|
||||||
|
const updated = await engine.loadChunk(chunkX, chunkZ, doLighting)
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dumpLightData = (x: number, z: number) => {
|
||||||
|
const engine = getLightEngineSafe()
|
||||||
|
// return engine?.worldLightHolder.dumpChunk(Math.floor(x / 16), Math.floor(z / 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getDebugLightValues = (x: number, y: number, z: number) => {
|
||||||
|
const engine = getLightEngineSafe()
|
||||||
|
// return {
|
||||||
|
// blockLight: engine?.worldLightHolder.getBlockLight(x, y, z) ?? -1,
|
||||||
|
// skyLight: engine?.worldLightHolder.getSkyLight(x, y, z) ?? -1,
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateBlockLight = async (x: number, y: number, z: number, stateId: number, distance: number) => {
|
||||||
|
if (distance > 16) return []
|
||||||
|
const chunkX = Math.floor(x / 16) * 16
|
||||||
|
const chunkZ = Math.floor(z / 16) * 16
|
||||||
|
const engine = getLightEngineSafe()
|
||||||
|
if (!engine) return
|
||||||
|
const start = performance.now()
|
||||||
|
const result = await engine.setBlock(x, y, z, stateId)
|
||||||
|
const end = performance.now()
|
||||||
|
console.log(`[light engine] updateBlockLight (${x}, ${y}, ${z}) took`, Math.round(end - start), 'ms', result.length, 'chunks')
|
||||||
|
return result
|
||||||
|
|
||||||
|
// const engine = getLightEngineSafe()
|
||||||
|
// if (!engine) return
|
||||||
|
// const affected = engine['affectedChunksTimestamps'] as Map<string, number>
|
||||||
|
// const noAffected = affected.size === 0
|
||||||
|
// engine.setBlock(x, y, z, convertPrismarineBlockToWorldBlock(stateId, loadedData))
|
||||||
|
|
||||||
|
// if (affected.size > 0) {
|
||||||
|
// const chunks = [...affected.keys()].map(key => {
|
||||||
|
// return key.split(',').map(Number) as [number, number]
|
||||||
|
// })
|
||||||
|
// affected.clear()
|
||||||
|
// return chunks
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const lightRemoveColumn = (x: number, z: number) => {
|
||||||
|
const engine = getLightEngineSafe()
|
||||||
|
if (!engine) return
|
||||||
|
engine.unloadChunk(Math.floor(x / 16), Math.floor(z / 16))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const destroyLightEngine = () => {
|
||||||
|
lightEngineNew = null
|
||||||
|
globalThis.lightEngine = null
|
||||||
|
}
|
||||||
|
|
@ -72,7 +72,10 @@ const softCleanup = () => {
|
||||||
globalThis.world = world
|
globalThis.world = world
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sideControl = false
|
||||||
const handleMessage = data => {
|
const handleMessage = data => {
|
||||||
|
if (sideControl) return
|
||||||
|
|
||||||
const globalVar: any = globalThis
|
const globalVar: any = globalThis
|
||||||
|
|
||||||
if (data.type === 'mcData') {
|
if (data.type === 'mcData') {
|
||||||
|
|
@ -94,6 +97,13 @@ const handleMessage = data => {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
|
case 'sideControl': {
|
||||||
|
if (data.value === 'lightEngine') {
|
||||||
|
sideControl = true
|
||||||
|
import('minecraft-lighting/dist/prismarineWorker.worker.js')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
case 'mesherData': {
|
case 'mesherData': {
|
||||||
setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu')
|
setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu')
|
||||||
allDataReady = true
|
allDataReady = true
|
||||||
|
|
@ -109,6 +119,9 @@ const handleMessage = data => {
|
||||||
}
|
}
|
||||||
case 'chunk': {
|
case 'chunk': {
|
||||||
world.addColumn(data.x, data.z, data.chunk)
|
world.addColumn(data.x, data.z, data.chunk)
|
||||||
|
if (data.lightData) {
|
||||||
|
world.lightHolder.loadChunk(data.lightData)
|
||||||
|
}
|
||||||
if (data.customBlockModels) {
|
if (data.customBlockModels) {
|
||||||
const chunkKey = `${data.x},${data.z}`
|
const chunkKey = `${data.x},${data.z}`
|
||||||
world.customBlockModels.set(chunkKey, data.customBlockModels)
|
world.customBlockModels.set(chunkKey, data.customBlockModels)
|
||||||
|
|
|
||||||
|
|
@ -520,6 +520,7 @@ const isBlockWaterlogged = (block: Block) => {
|
||||||
|
|
||||||
let unknownBlockModel: BlockModelPartsResolved
|
let unknownBlockModel: BlockModelPartsResolved
|
||||||
export function getSectionGeometry (sx: number, sy: number, sz: number, world: World) {
|
export function getSectionGeometry (sx: number, sy: number, sz: number, world: World) {
|
||||||
|
world.hadSkyLight = false
|
||||||
let delayedRender = [] as Array<() => void>
|
let delayedRender = [] as Array<() => void>
|
||||||
|
|
||||||
const attr: MesherGeometryOutput = {
|
const attr: MesherGeometryOutput = {
|
||||||
|
|
@ -716,6 +717,8 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W
|
||||||
delete attr.uvs
|
delete attr.uvs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attr.hasSkylight = world.hadSkyLight
|
||||||
|
|
||||||
return attr
|
return attr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ export const defaultMesherConfig = {
|
||||||
enableLighting: true,
|
enableLighting: true,
|
||||||
skyLight: 15,
|
skyLight: 15,
|
||||||
smoothLighting: true,
|
smoothLighting: true,
|
||||||
|
usingCustomLightHolder: false,
|
||||||
|
flyingSquidWorkarounds: false,
|
||||||
|
|
||||||
outputFormat: 'threeJs' as 'threeJs' | 'webgpu',
|
outputFormat: 'threeJs' as 'threeJs' | 'webgpu',
|
||||||
// textureSize: 1024, // for testing
|
// textureSize: 1024, // for testing
|
||||||
debugModelVariant: undefined as undefined | number[],
|
debugModelVariant: undefined as undefined | number[],
|
||||||
|
|
@ -45,6 +48,7 @@ export type MesherGeometryOutput = {
|
||||||
hadErrors: boolean
|
hadErrors: boolean
|
||||||
blocksCount: number
|
blocksCount: number
|
||||||
customBlockModels?: CustomBlockModels
|
customBlockModels?: CustomBlockModels
|
||||||
|
hasSkylight?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MesherMainEvents {
|
export interface MesherMainEvents {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { WorldLightHolder } from 'minecraft-lighting/dist/worldLightHolder'
|
||||||
import Chunks from 'prismarine-chunk'
|
import Chunks from 'prismarine-chunk'
|
||||||
import mcData from 'minecraft-data'
|
import mcData from 'minecraft-data'
|
||||||
import { Block } from 'prismarine-block'
|
import { Block } from 'prismarine-block'
|
||||||
|
|
@ -32,6 +33,8 @@ export type WorldBlock = Omit<Block, 'position'> & {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class World {
|
export class World {
|
||||||
|
hadSkyLight = false
|
||||||
|
lightHolder = new WorldLightHolder(0, 0)
|
||||||
config = defaultMesherConfig
|
config = defaultMesherConfig
|
||||||
Chunk: typeof import('prismarine-chunk/types/index').PCChunk
|
Chunk: typeof import('prismarine-chunk/types/index').PCChunk
|
||||||
columns = {} as { [key: string]: import('prismarine-chunk/types/index').PCChunk }
|
columns = {} as { [key: string]: import('prismarine-chunk/types/index').PCChunk }
|
||||||
|
|
@ -53,38 +56,71 @@ export class World {
|
||||||
getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
|
getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') {
|
||||||
// for easier testing
|
// for easier testing
|
||||||
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
|
if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number])
|
||||||
const { enableLighting, skyLight } = this.config
|
|
||||||
|
const IS_USING_LOCAL_SERVER_LIGHTING = this.config.flyingSquidWorkarounds
|
||||||
|
// const IS_USING_SERVER_LIGHTING = false
|
||||||
|
|
||||||
|
const { enableLighting, skyLight, usingCustomLightHolder } = this.config
|
||||||
if (!enableLighting) return 15
|
if (!enableLighting) return 15
|
||||||
// const key = `${pos.x},${pos.y},${pos.z}`
|
|
||||||
// if (lightsCache.has(key)) return lightsCache.get(key)
|
|
||||||
const column = this.getColumnByPos(pos)
|
const column = this.getColumnByPos(pos)
|
||||||
if (!column || !hasChunkSection(column, pos)) return 15
|
if (!column) return 15
|
||||||
let result = Math.min(
|
if (!usingCustomLightHolder && !hasChunkSection(column, pos)) return 2
|
||||||
15,
|
let result = Math.max(
|
||||||
Math.max(
|
2,
|
||||||
column.getBlockLight(posInChunk(pos)),
|
Math.min(
|
||||||
Math.min(skyLight, column.getSkyLight(posInChunk(pos)))
|
15,
|
||||||
) + 2
|
Math.max(
|
||||||
|
this.getBlockLight(pos),
|
||||||
|
Math.min(skyLight, this.getSkyLight(pos))
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
// lightsCache.set(key, result)
|
if (result === 2 && IS_USING_LOCAL_SERVER_LIGHTING) {
|
||||||
if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
|
if ([this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
|
||||||
const lights = [
|
const lights = [
|
||||||
this.getLight(pos.offset(0, 1, 0), undefined, true),
|
this.getLight(pos.offset(0, 1, 0), undefined, true),
|
||||||
this.getLight(pos.offset(0, -1, 0), undefined, true),
|
this.getLight(pos.offset(0, -1, 0), undefined, true),
|
||||||
this.getLight(pos.offset(0, 0, 1), undefined, true),
|
this.getLight(pos.offset(0, 0, 1), undefined, true),
|
||||||
this.getLight(pos.offset(0, 0, -1), undefined, true),
|
this.getLight(pos.offset(0, 0, -1), undefined, true),
|
||||||
this.getLight(pos.offset(1, 0, 0), undefined, true),
|
this.getLight(pos.offset(1, 0, 0), undefined, true),
|
||||||
this.getLight(pos.offset(-1, 0, 0), undefined, true)
|
this.getLight(pos.offset(-1, 0, 0), undefined, true)
|
||||||
].filter(x => x !== 2)
|
].filter(x => x !== 2)
|
||||||
if (lights.length) {
|
if (lights.length) {
|
||||||
const min = Math.min(...lights)
|
const min = Math.min(...lights)
|
||||||
result = min
|
result = min
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (isNeighbor) result = 15 // TODO
|
||||||
}
|
}
|
||||||
if (isNeighbor && result === 2) result = 15 // TODO
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBlockLight (pos: Vec3) {
|
||||||
|
// if (this.config.clientSideLighting) {
|
||||||
|
// return this.lightHolder.getBlockLight(pos.x, pos.y, pos.z)
|
||||||
|
// }
|
||||||
|
|
||||||
|
const column = this.getColumnByPos(pos)
|
||||||
|
if (!column) return 15
|
||||||
|
return column.getBlockLight(posInChunk(pos))
|
||||||
|
}
|
||||||
|
|
||||||
|
getSkyLight (pos: Vec3) {
|
||||||
|
const result = this.getSkyLightInner(pos)
|
||||||
|
if (result > 2) this.hadSkyLight = true
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
getSkyLightInner (pos: Vec3) {
|
||||||
|
// if (this.config.clientSideLighting) {
|
||||||
|
// return this.lightHolder.getSkyLight(pos.x, pos.y, pos.z)
|
||||||
|
// }
|
||||||
|
|
||||||
|
const column = this.getColumnByPos(pos)
|
||||||
|
if (!column) return 15
|
||||||
|
return column.getSkyLight(posInChunk(pos))
|
||||||
|
}
|
||||||
|
|
||||||
addColumn (x, z, json) {
|
addColumn (x, z, json) {
|
||||||
const chunk = this.Chunk.fromJson(json)
|
const chunk = this.Chunk.fromJson(json)
|
||||||
this.columns[columnKey(x, z)] = chunk as any
|
this.columns[columnKey(x, z)] = chunk as any
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ import { Vec3 } from 'vec3'
|
||||||
import { BotEvents } from 'mineflayer'
|
import { BotEvents } from 'mineflayer'
|
||||||
import { proxy } from 'valtio'
|
import { proxy } from 'valtio'
|
||||||
import TypedEmitter from 'typed-emitter'
|
import TypedEmitter from 'typed-emitter'
|
||||||
import { Biome } from 'minecraft-data'
|
|
||||||
import { delayedIterator } from '../../playground/shared'
|
import { delayedIterator } from '../../playground/shared'
|
||||||
import { chunkPos } from './simpleUtils'
|
import { chunkPos } from './simpleUtils'
|
||||||
|
import { createLightEngineIfNeededNew, destroyLightEngine, lightRemoveColumn, processLightChunk, updateBlockLight } from './lightEngine'
|
||||||
|
import { WorldRendererConfig } from './worldrendererCommon'
|
||||||
|
|
||||||
export type ChunkPosKey = string // like '16,16'
|
export type ChunkPosKey = string // like '16,16'
|
||||||
type ChunkPos = { x: number, z: number } // like { x: 16, z: 16 }
|
type ChunkPos = { x: number, z: number } // like { x: 16, z: 16 }
|
||||||
|
|
@ -29,24 +30,24 @@ export type WorldDataEmitterEvents = {
|
||||||
updateLight: (data: { pos: Vec3 }) => void
|
updateLight: (data: { pos: Vec3 }) => void
|
||||||
onWorldSwitch: () => void
|
onWorldSwitch: () => void
|
||||||
end: () => void
|
end: () => void
|
||||||
biomeUpdate: (data: { biome: Biome }) => void
|
|
||||||
biomeReset: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmitter<WorldDataEmitterEvents>) {
|
export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmitter<WorldDataEmitterEvents>) {
|
||||||
static readonly restorerName = 'WorldDataEmitterWorker'
|
static readonly restorerName = 'WorldDataEmitterWorker'
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
this.removeAllListeners()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<WorldDataEmitterEvents>) {
|
export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<WorldDataEmitterEvents>) {
|
||||||
spiralNumber = 0
|
minY = -64
|
||||||
gotPanicLastTime = false
|
worldHeight = 384
|
||||||
panicChunksReload = () => {}
|
dimensionName = ''
|
||||||
|
version = ''
|
||||||
|
|
||||||
|
worldRendererConfig: WorldRendererConfig
|
||||||
loadedChunks: Record<ChunkPosKey, boolean>
|
loadedChunks: Record<ChunkPosKey, boolean>
|
||||||
private inLoading = false
|
|
||||||
private chunkReceiveTimes: number[] = []
|
|
||||||
private lastChunkReceiveTime = 0
|
|
||||||
public lastChunkReceiveTimeAvg = 0
|
|
||||||
private panicTimeout?: NodeJS.Timeout
|
|
||||||
readonly lastPos: Vec3
|
readonly lastPos: Vec3
|
||||||
private eventListeners: Record<string, any> = {}
|
private eventListeners: Record<string, any> = {}
|
||||||
private readonly emitter: WorldDataEmitter
|
private readonly emitter: WorldDataEmitter
|
||||||
|
|
@ -75,18 +76,22 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
this.emitter = this
|
this.emitter = this
|
||||||
}
|
}
|
||||||
|
|
||||||
setBlockStateId (position: Vec3, stateId: number) {
|
// setBlockStateId (position: Vec3, stateId: number) {
|
||||||
const val = this.world.setBlockStateId(position, stateId) as Promise<void> | void
|
// const val = this.world.setBlockStateId(position, stateId) as Promise<void> | void
|
||||||
if (val) throw new Error('setBlockStateId returned promise (not supported)')
|
// if (val) throw new Error('setBlockStateId returned promise (not supported)')
|
||||||
// const chunkX = Math.floor(position.x / 16)
|
// // const chunkX = Math.floor(position.x / 16)
|
||||||
// const chunkZ = Math.floor(position.z / 16)
|
// // const chunkZ = Math.floor(position.z / 16)
|
||||||
// if (!this.loadedChunks[`${chunkX},${chunkZ}`] && !this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) {
|
// // if (!this.loadedChunks[`${chunkX},${chunkZ}`] && !this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) {
|
||||||
// void this.loadChunk({ x: chunkX, z: chunkZ })
|
// // void this.loadChunk({ x: chunkX, z: chunkZ })
|
||||||
// return
|
// // return
|
||||||
// }
|
// // }
|
||||||
|
|
||||||
this.emit('blockUpdate', { pos: position, stateId })
|
// const updateChunks = this.worldRendererConfig.clientSideLighting ? updateBlockLight(position.x, position.y, position.z, stateId) ?? [] : []
|
||||||
}
|
// this.emit('blockUpdate', { pos: position, stateId })
|
||||||
|
// for (const chunk of updateChunks) {
|
||||||
|
// void this.loadChunk(new Vec3(chunk[0] * 16, 0, chunk[1] * 16), true, 'setBlockStateId light update')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
updateViewDistance (viewDistance: number) {
|
updateViewDistance (viewDistance: number) {
|
||||||
this.viewDistance = viewDistance
|
this.viewDistance = viewDistance
|
||||||
|
|
@ -94,6 +99,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
}
|
}
|
||||||
|
|
||||||
listenToBot (bot: typeof __type_bot) {
|
listenToBot (bot: typeof __type_bot) {
|
||||||
|
this.version = bot.version
|
||||||
const entitiesObjectData = new Map<string, number>()
|
const entitiesObjectData = new Map<string, number>()
|
||||||
bot._client.prependListener('spawn_entity', (data) => {
|
bot._client.prependListener('spawn_entity', (data) => {
|
||||||
if (data.objectData && data.entityId !== undefined) {
|
if (data.objectData && data.entityId !== undefined) {
|
||||||
|
|
@ -144,26 +150,26 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
this.emitter.emit('entity', { id: e.id, delete: true })
|
this.emitter.emit('entity', { id: e.id, delete: true })
|
||||||
},
|
},
|
||||||
chunkColumnLoad: (pos: Vec3) => {
|
chunkColumnLoad: (pos: Vec3) => {
|
||||||
const now = performance.now()
|
|
||||||
if (this.lastChunkReceiveTime) {
|
|
||||||
this.chunkReceiveTimes.push(now - this.lastChunkReceiveTime)
|
|
||||||
}
|
|
||||||
this.lastChunkReceiveTime = now
|
|
||||||
|
|
||||||
if (this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`]) {
|
if (this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`]) {
|
||||||
this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`](true)
|
this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`](true)
|
||||||
delete this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`]
|
delete this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`]
|
||||||
} else if (this.loadedChunks[`${pos.x},${pos.z}`]) {
|
} else if (this.loadedChunks[`${pos.x},${pos.z}`]) {
|
||||||
void this.loadChunk(pos, false, 'Received another chunkColumnLoad event while already loaded')
|
void this.loadChunk(pos, false, 'Received another chunkColumnLoad event while already loaded')
|
||||||
}
|
}
|
||||||
this.chunkProgress()
|
|
||||||
},
|
},
|
||||||
chunkColumnUnload: (pos: Vec3) => {
|
chunkColumnUnload: (pos: Vec3) => {
|
||||||
this.unloadChunk(pos)
|
this.unloadChunk(pos)
|
||||||
},
|
},
|
||||||
blockUpdate: (oldBlock: any, newBlock: any) => {
|
blockUpdate: async (oldBlock, newBlock) => {
|
||||||
|
if (typeof newBlock.stateId === 'number' && oldBlock?.stateId === newBlock.stateId) return
|
||||||
const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata)
|
const stateId = newBlock.stateId ?? ((newBlock.type << 4) | newBlock.metadata)
|
||||||
this.emitter.emit('blockUpdate', { pos: oldBlock.position, stateId })
|
const distance = newBlock.position.distanceTo(this.lastPos)
|
||||||
|
|
||||||
|
this.emit('blockUpdate', { pos: newBlock.position, stateId })
|
||||||
|
const updateChunks = this.worldRendererConfig.clientSideLighting === 'none' ? [] : await updateBlockLight(newBlock.position.x, newBlock.position.y, newBlock.position.z, stateId, distance) ?? []
|
||||||
|
for (const chunk of updateChunks) {
|
||||||
|
void this.loadChunk(new Vec3(chunk.chunkX * 16, 0, chunk.chunkZ * 16), true, 'setBlockStateId light update')
|
||||||
|
}
|
||||||
},
|
},
|
||||||
time: () => {
|
time: () => {
|
||||||
this.emitter.emit('time', bot.time.timeOfDay)
|
this.emitter.emit('time', bot.time.timeOfDay)
|
||||||
|
|
@ -172,17 +178,22 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
this.emitter.emit('end')
|
this.emitter.emit('end')
|
||||||
},
|
},
|
||||||
// when dimension might change
|
// when dimension might change
|
||||||
login: () => {
|
login () {
|
||||||
void this.updatePosition(bot.entity.position, true)
|
possiblyDimensionChange()
|
||||||
this.emitter.emit('playerEntity', bot.entity)
|
|
||||||
},
|
},
|
||||||
respawn: () => {
|
respawn: () => {
|
||||||
void this.updatePosition(bot.entity.position, true)
|
possiblyDimensionChange()
|
||||||
this.emitter.emit('playerEntity', bot.entity)
|
|
||||||
this.emitter.emit('onWorldSwitch')
|
this.emitter.emit('onWorldSwitch')
|
||||||
},
|
},
|
||||||
} satisfies Partial<BotEvents>
|
} satisfies Partial<BotEvents>
|
||||||
|
|
||||||
|
const possiblyDimensionChange = () => {
|
||||||
|
this.minY = bot.game['minY'] ?? -64
|
||||||
|
this.worldHeight = bot.game['height'] ?? 384
|
||||||
|
this.dimensionName = bot.game['dimension'] ?? ''
|
||||||
|
void this.updatePosition(bot.entity.position, true)
|
||||||
|
this.emitter.emit('playerEntity', bot.entity)
|
||||||
|
}
|
||||||
|
|
||||||
bot._client.on('update_light', ({ chunkX, chunkZ }) => {
|
bot._client.on('update_light', ({ chunkX, chunkZ }) => {
|
||||||
const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16)
|
const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16)
|
||||||
|
|
@ -222,6 +233,14 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
destroy () {
|
||||||
|
if (bot) {
|
||||||
|
this.removeListenersFromBot(bot as any)
|
||||||
|
}
|
||||||
|
this.emitter.removeAllListeners()
|
||||||
|
destroyLightEngine()
|
||||||
|
}
|
||||||
|
|
||||||
async init (pos: Vec3) {
|
async init (pos: Vec3) {
|
||||||
this.updateViewDistance(this.viewDistance)
|
this.updateViewDistance(this.viewDistance)
|
||||||
this.emitter.emit('chunkPosUpdate', { pos })
|
this.emitter.emit('chunkPosUpdate', { pos })
|
||||||
|
|
@ -237,59 +256,33 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
const positions = generateSpiralMatrix(this.viewDistance).map(([x, z]) => new Vec3((botX + x) * 16, 0, (botZ + z) * 16))
|
const positions = generateSpiralMatrix(this.viewDistance).map(([x, z]) => new Vec3((botX + x) * 16, 0, (botZ + z) * 16))
|
||||||
|
|
||||||
this.lastPos.update(pos)
|
this.lastPos.update(pos)
|
||||||
await this._loadChunks(positions, pos)
|
await this._loadChunks(positions)
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkProgress () {
|
async _loadChunks (positions: Vec3[], sliceSize = 5) {
|
||||||
if (this.panicTimeout) clearTimeout(this.panicTimeout)
|
|
||||||
if (this.chunkReceiveTimes.length >= 5) {
|
|
||||||
const avgReceiveTime = this.chunkReceiveTimes.reduce((a, b) => a + b, 0) / this.chunkReceiveTimes.length
|
|
||||||
this.lastChunkReceiveTimeAvg = avgReceiveTime
|
|
||||||
const timeoutDelay = avgReceiveTime * 2 + 1000 // 2x average + 1 second
|
|
||||||
|
|
||||||
// Clear any existing timeout
|
|
||||||
if (this.panicTimeout) clearTimeout(this.panicTimeout)
|
|
||||||
|
|
||||||
// Set new timeout for panic reload
|
|
||||||
this.panicTimeout = setTimeout(() => {
|
|
||||||
if (!this.gotPanicLastTime && this.inLoading) {
|
|
||||||
console.warn('Chunk loading seems stuck, triggering panic reload')
|
|
||||||
this.gotPanicLastTime = true
|
|
||||||
this.panicChunksReload()
|
|
||||||
}
|
|
||||||
}, timeoutDelay)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _loadChunks (positions: Vec3[], centerPos: Vec3) {
|
|
||||||
this.spiralNumber++
|
|
||||||
const { spiralNumber } = this
|
|
||||||
// stop loading previous chunks
|
// stop loading previous chunks
|
||||||
for (const pos of Object.keys(this.waitingSpiralChunksLoad)) {
|
for (const pos of Object.keys(this.waitingSpiralChunksLoad)) {
|
||||||
this.waitingSpiralChunksLoad[pos](false)
|
this.waitingSpiralChunksLoad[pos](false)
|
||||||
delete this.waitingSpiralChunksLoad[pos]
|
delete this.waitingSpiralChunksLoad[pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promises = [] as Array<Promise<void>>
|
||||||
let continueLoading = true
|
let continueLoading = true
|
||||||
this.inLoading = true
|
|
||||||
await delayedIterator(positions, this.addWaitTime, async (pos) => {
|
await delayedIterator(positions, this.addWaitTime, async (pos) => {
|
||||||
if (!continueLoading || this.loadedChunks[`${pos.x},${pos.z}`]) return
|
const promise = (async () => {
|
||||||
|
if (!continueLoading || this.loadedChunks[`${pos.x},${pos.z}`]) return
|
||||||
|
|
||||||
// Wait for chunk to be available from server
|
if (!this.world.getColumnAt(pos)) {
|
||||||
if (!this.world.getColumnAt(pos)) {
|
continueLoading = await new Promise<boolean>(resolve => {
|
||||||
continueLoading = await new Promise<boolean>(resolve => {
|
this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] = resolve
|
||||||
this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] = resolve
|
})
|
||||||
})
|
}
|
||||||
}
|
if (!continueLoading) return
|
||||||
if (!continueLoading) return
|
await this.loadChunk(pos)
|
||||||
await this.loadChunk(pos, undefined, `spiral ${spiralNumber} from ${centerPos.x},${centerPos.z}`)
|
})()
|
||||||
this.chunkProgress()
|
promises.push(promise)
|
||||||
})
|
})
|
||||||
if (this.panicTimeout) clearTimeout(this.panicTimeout)
|
await Promise.all(promises)
|
||||||
this.inLoading = false
|
|
||||||
this.gotPanicLastTime = false
|
|
||||||
this.chunkReceiveTimes = []
|
|
||||||
this.lastChunkReceiveTime = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readdDebug () {
|
readdDebug () {
|
||||||
|
|
@ -312,14 +305,33 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
// lastTime = 0
|
// lastTime = 0
|
||||||
|
|
||||||
async loadChunk (pos: ChunkPos, isLightUpdate = false, reason = 'spiral') {
|
async loadChunk (pos: ChunkPos, isLightUpdate = false, reason = 'spiral') {
|
||||||
const [botX, botZ] = chunkPos(this.lastPos)
|
createLightEngineIfNeededNew(this, this.version)
|
||||||
|
|
||||||
const dx = Math.abs(botX - Math.floor(pos.x / 16))
|
const [botX, botZ] = chunkPos(this.lastPos)
|
||||||
const dz = Math.abs(botZ - Math.floor(pos.z / 16))
|
const chunkX = Math.floor(pos.x / 16)
|
||||||
|
const chunkZ = Math.floor(pos.z / 16)
|
||||||
|
|
||||||
|
const dx = Math.abs(botX - chunkX)
|
||||||
|
const dz = Math.abs(botZ - chunkZ)
|
||||||
if (dx <= this.viewDistance && dz <= this.viewDistance) {
|
if (dx <= this.viewDistance && dz <= this.viewDistance) {
|
||||||
// eslint-disable-next-line @typescript-eslint/await-thenable -- todo allow to use async world provider but not sure if needed
|
// eslint-disable-next-line @typescript-eslint/await-thenable -- todo allow to use async world provider but not sure if needed
|
||||||
const column = await this.world.getColumnAt(pos['y'] ? pos as Vec3 : new Vec3(pos.x, 0, pos.z))
|
const column = await this.world.getColumnAt(pos['y'] ? pos as Vec3 : new Vec3(pos.x, 0, pos.z))
|
||||||
if (column) {
|
if (column) {
|
||||||
|
let result = [] as Array<{ chunkX: number, chunkZ: number }>
|
||||||
|
if (!isLightUpdate) {
|
||||||
|
const computeLighting = this.worldRendererConfig.clientSideLighting === 'full'
|
||||||
|
const promise = processLightChunk(pos.x, pos.z, computeLighting)
|
||||||
|
if (computeLighting) {
|
||||||
|
result = (await promise) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!result) return
|
||||||
|
for (const affectedChunk of result) {
|
||||||
|
if (affectedChunk.chunkX === chunkX && affectedChunk.chunkZ === chunkZ) continue
|
||||||
|
const loadedChunk = this.loadedChunks[`${affectedChunk.chunkX * 16},${affectedChunk.chunkZ * 16}`]
|
||||||
|
if (!loadedChunk) continue
|
||||||
|
void this.loadChunk(new Vec3(affectedChunk.chunkX * 16, 0, affectedChunk.chunkZ * 16), true)
|
||||||
|
}
|
||||||
// const latency = Math.floor(performance.now() - this.lastTime)
|
// const latency = Math.floor(performance.now() - this.lastTime)
|
||||||
// this.debugGotChunkLatency.push(latency)
|
// this.debugGotChunkLatency.push(latency)
|
||||||
// this.lastTime = performance.now()
|
// this.lastTime = performance.now()
|
||||||
|
|
@ -361,39 +373,11 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
this.emitter.emit('unloadChunk', { x: pos.x, z: pos.z })
|
this.emitter.emit('unloadChunk', { x: pos.x, z: pos.z })
|
||||||
delete this.loadedChunks[`${pos.x},${pos.z}`]
|
delete this.loadedChunks[`${pos.x},${pos.z}`]
|
||||||
delete this.debugChunksInfo[`${pos.x},${pos.z}`]
|
delete this.debugChunksInfo[`${pos.x},${pos.z}`]
|
||||||
|
lightRemoveColumn(pos.x, pos.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
lastBiomeId: number | null = null
|
|
||||||
|
|
||||||
udpateBiome (pos: Vec3) {
|
|
||||||
try {
|
|
||||||
const biomeId = this.world.getBiome(pos)
|
|
||||||
if (biomeId !== this.lastBiomeId) {
|
|
||||||
this.lastBiomeId = biomeId
|
|
||||||
const biomeData = loadedData.biomes[biomeId]
|
|
||||||
if (biomeData) {
|
|
||||||
this.emitter.emit('biomeUpdate', {
|
|
||||||
biome: biomeData
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// unknown biome
|
|
||||||
this.emitter.emit('biomeReset')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('error updating biome', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastPosCheck: Vec3 | null = null
|
|
||||||
async updatePosition (pos: Vec3, force = false) {
|
async updatePosition (pos: Vec3, force = false) {
|
||||||
if (!this.allowPositionUpdate) return
|
if (!this.allowPositionUpdate) return
|
||||||
const posFloored = pos.floored()
|
|
||||||
if (!force && this.lastPosCheck && this.lastPosCheck.equals(posFloored)) return
|
|
||||||
this.lastPosCheck = posFloored
|
|
||||||
|
|
||||||
this.udpateBiome(pos)
|
|
||||||
|
|
||||||
const [lastX, lastZ] = chunkPos(this.lastPos)
|
const [lastX, lastZ] = chunkPos(this.lastPos)
|
||||||
const [botX, botZ] = chunkPos(pos)
|
const [botX, botZ] = chunkPos(pos)
|
||||||
if (lastX !== botX || lastZ !== botZ || force) {
|
if (lastX !== botX || lastZ !== botZ || force) {
|
||||||
|
|
@ -411,6 +395,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
chunksToUnload.push(p)
|
chunksToUnload.push(p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('unloading', chunksToUnload.length, 'total now', Object.keys(this.loadedChunks).length)
|
||||||
for (const p of chunksToUnload) {
|
for (const p of chunksToUnload) {
|
||||||
this.unloadChunk(p)
|
this.unloadChunk(p)
|
||||||
}
|
}
|
||||||
|
|
@ -422,7 +407,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
||||||
return undefined!
|
return undefined!
|
||||||
}).filter(a => !!a)
|
}).filter(a => !!a)
|
||||||
this.lastPos.update(pos)
|
this.lastPos.update(pos)
|
||||||
void this._loadChunks(positions, pos)
|
void this._loadChunks(positions)
|
||||||
} else {
|
} else {
|
||||||
this.emitter.emit('chunkPosUpdate', { pos }) // todo-low
|
this.emitter.emit('chunkPosUpdate', { pos }) // todo-low
|
||||||
this.lastPos.update(pos)
|
this.lastPos.update(pos)
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import { buildCleanupDecorator } from './cleanupDecorator'
|
||||||
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from './mesher/shared'
|
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from './mesher/shared'
|
||||||
import { chunkPos } from './simpleUtils'
|
import { chunkPos } from './simpleUtils'
|
||||||
import { addNewStat, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats'
|
import { addNewStat, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats'
|
||||||
|
import { dumpLightData } from './lightEngine'
|
||||||
import { WorldDataEmitterWorker } from './worldDataEmitter'
|
import { WorldDataEmitterWorker } from './worldDataEmitter'
|
||||||
import { getPlayerStateUtils, PlayerStateReactive, PlayerStateRenderer, PlayerStateUtils } from './basePlayerState'
|
import { getPlayerStateUtils, PlayerStateReactive, PlayerStateRenderer, PlayerStateUtils } from './basePlayerState'
|
||||||
import { MesherLogReader } from './mesherlogReader'
|
import { MesherLogReader } from './mesherlogReader'
|
||||||
|
|
@ -32,50 +33,40 @@ const toMajorVersion = version => {
|
||||||
export const worldCleanup = buildCleanupDecorator('resetWorld')
|
export const worldCleanup = buildCleanupDecorator('resetWorld')
|
||||||
|
|
||||||
export const defaultWorldRendererConfig = {
|
export const defaultWorldRendererConfig = {
|
||||||
// Debug settings
|
|
||||||
showChunkBorders: false,
|
showChunkBorders: false,
|
||||||
enableDebugOverlay: false,
|
|
||||||
|
|
||||||
// Performance settings
|
|
||||||
mesherWorkers: 4,
|
mesherWorkers: 4,
|
||||||
addChunksBatchWaitTime: 200,
|
isPlayground: false,
|
||||||
_experimentalSmoothChunkLoading: true,
|
renderEars: true,
|
||||||
_renderByChunks: false,
|
skinTexturesProxy: undefined as string | undefined,
|
||||||
|
// game renderer setting actually
|
||||||
// Rendering engine settings
|
|
||||||
dayCycle: true,
|
|
||||||
smoothLighting: true,
|
|
||||||
enableLighting: true,
|
|
||||||
starfield: true,
|
|
||||||
defaultSkybox: true,
|
|
||||||
renderEntities: true,
|
|
||||||
extraBlockRenderers: true,
|
|
||||||
foreground: true,
|
|
||||||
fov: 75,
|
|
||||||
volume: 1,
|
|
||||||
|
|
||||||
// Camera visual related settings
|
|
||||||
showHand: false,
|
showHand: false,
|
||||||
viewBobbing: false,
|
viewBobbing: false,
|
||||||
renderEars: true,
|
extraBlockRenderers: true,
|
||||||
highlightBlockColor: 'blue',
|
clipWorldBelowY: undefined as number | undefined,
|
||||||
|
smoothLighting: true,
|
||||||
// Player models
|
enableLighting: true,
|
||||||
fetchPlayerSkins: true,
|
legacyLighting: false,
|
||||||
skinTexturesProxy: undefined as string | undefined,
|
clientSideLighting: 'full' as 'full' | 'partial' | 'none',
|
||||||
|
flyingSquidWorkarounds: false,
|
||||||
// VR settings
|
starfield: true,
|
||||||
|
addChunksBatchWaitTime: 200,
|
||||||
vrSupport: true,
|
vrSupport: true,
|
||||||
vrPageGameRendering: true,
|
vrPageGameRendering: true,
|
||||||
|
renderEntities: true,
|
||||||
// World settings
|
fov: 75,
|
||||||
clipWorldBelowY: undefined as number | undefined,
|
fetchPlayerSkins: true,
|
||||||
isPlayground: false
|
highlightBlockColor: 'blue',
|
||||||
|
foreground: true,
|
||||||
|
enableDebugOverlay: false,
|
||||||
|
_experimentalSmoothChunkLoading: true,
|
||||||
|
_renderByChunks: false,
|
||||||
|
volume: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
export type WorldRendererConfig = typeof defaultWorldRendererConfig
|
export type WorldRendererConfig = typeof defaultWorldRendererConfig
|
||||||
|
|
||||||
export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any> {
|
export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any> {
|
||||||
|
skyLight = 15
|
||||||
worldReadyResolvers = Promise.withResolvers<void>()
|
worldReadyResolvers = Promise.withResolvers<void>()
|
||||||
worldReadyPromise = this.worldReadyResolvers.promise
|
worldReadyPromise = this.worldReadyResolvers.promise
|
||||||
timeOfTheDay = 0
|
timeOfTheDay = 0
|
||||||
|
|
@ -510,9 +501,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
|
|
||||||
timeUpdated? (newTime: number): void
|
timeUpdated? (newTime: number): void
|
||||||
|
|
||||||
biomeUpdated? (biome: any): void
|
skylightUpdated? (): void
|
||||||
|
|
||||||
biomeReset? (): void
|
|
||||||
|
|
||||||
updateViewerPosition (pos: Vec3) {
|
updateViewerPosition (pos: Vec3) {
|
||||||
this.viewerChunkPosition = pos
|
this.viewerChunkPosition = pos
|
||||||
|
|
@ -561,7 +550,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
this.sendMesherMcData()
|
this.sendMesherMcData()
|
||||||
}
|
}
|
||||||
|
|
||||||
getMesherConfig (): MesherConfig {
|
changeSkyLight () {
|
||||||
let skyLight = 15
|
let skyLight = 15
|
||||||
const timeOfDay = this.timeOfTheDay
|
const timeOfDay = this.timeOfTheDay
|
||||||
if (timeOfDay < 0 || timeOfDay > 24_000) {
|
if (timeOfDay < 0 || timeOfDay > 24_000) {
|
||||||
|
|
@ -574,34 +563,35 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
skyLight = ((timeOfDay - 12_000) / 6000) * 15
|
skyLight = ((timeOfDay - 12_000) / 6000) * 15
|
||||||
}
|
}
|
||||||
|
|
||||||
skyLight = Math.floor(skyLight)
|
this.skyLight = Math.floor(skyLight)
|
||||||
|
}
|
||||||
|
|
||||||
|
getMesherConfig (): MesherConfig {
|
||||||
return {
|
return {
|
||||||
version: this.version,
|
version: this.version,
|
||||||
enableLighting: this.worldRendererConfig.enableLighting,
|
enableLighting: this.worldRendererConfig.enableLighting && !this.playerStateReactive.lightingDisabled,
|
||||||
skyLight,
|
skyLight: this.skyLight,
|
||||||
smoothLighting: this.worldRendererConfig.smoothLighting,
|
smoothLighting: this.worldRendererConfig.smoothLighting,
|
||||||
outputFormat: this.outputFormat,
|
outputFormat: this.outputFormat,
|
||||||
// textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width,
|
// textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width,
|
||||||
debugModelVariant: undefined,
|
debugModelVariant: undefined,
|
||||||
clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY,
|
clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY,
|
||||||
disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers,
|
disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers,
|
||||||
|
usingCustomLightHolder: false,
|
||||||
|
flyingSquidWorkarounds: this.worldRendererConfig.flyingSquidWorkarounds,
|
||||||
worldMinY: this.worldMinYRender,
|
worldMinY: this.worldMinYRender,
|
||||||
worldMaxY: this.worldMinYRender + this.worldSizeParams.worldHeight,
|
worldMaxY: this.worldMinYRender + this.worldSizeParams.worldHeight,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMesherMcData () {
|
sendMesherMcData () {
|
||||||
const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajorVersion(this.version)]
|
meshersSendMcData(
|
||||||
const mcData = {
|
this.workers,
|
||||||
version: JSON.parse(JSON.stringify(allMcData.version))
|
this.version,
|
||||||
}
|
{
|
||||||
for (const key of dynamicMcDataFiles) {
|
config: this.getMesherConfig()
|
||||||
mcData[key] = allMcData[key]
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
for (const worker of this.workers) {
|
|
||||||
worker.postMessage({ type: 'mcData', mcData, config: this.getMesherConfig() })
|
|
||||||
}
|
|
||||||
this.logWorkerWork('# mcData sent')
|
this.logWorkerWork('# mcData sent')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -659,7 +649,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
x,
|
x,
|
||||||
z,
|
z,
|
||||||
chunk,
|
chunk,
|
||||||
customBlockModels: customBlockModels || undefined
|
customBlockModels: customBlockModels || undefined,
|
||||||
|
lightData: dumpLightData(x, z)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.workers[0].postMessage({
|
this.workers[0].postMessage({
|
||||||
|
|
@ -835,24 +826,17 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
})
|
})
|
||||||
|
|
||||||
worldEmitter.on('time', (timeOfDay) => {
|
worldEmitter.on('time', (timeOfDay) => {
|
||||||
if (!this.worldRendererConfig.dayCycle) return
|
if (timeOfDay < 0 || timeOfDay > 24_000) {
|
||||||
this.timeUpdated?.(timeOfDay)
|
throw new Error('Invalid time of day. It should be between 0 and 24000.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldSkyLight = this.skyLight
|
||||||
this.timeOfTheDay = timeOfDay
|
this.timeOfTheDay = timeOfDay
|
||||||
|
this.changeSkyLight()
|
||||||
// if (this.worldRendererConfig.skyLight === skyLight) return
|
if (oldSkyLight !== this.skyLight) {
|
||||||
// this.worldRendererConfig.skyLight = skyLight
|
this.skylightUpdated?.()
|
||||||
// if (this instanceof WorldRendererThree) {
|
}
|
||||||
// (this).rerenderAllChunks?.()
|
this.timeUpdated?.(timeOfDay)
|
||||||
// }
|
|
||||||
})
|
|
||||||
|
|
||||||
worldEmitter.on('biomeUpdate', ({ biome }) => {
|
|
||||||
this.biomeUpdated?.(biome)
|
|
||||||
})
|
|
||||||
|
|
||||||
worldEmitter.on('biomeReset', () => {
|
|
||||||
this.biomeReset?.()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -945,7 +929,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
this.reactiveState.world.mesherWork = true
|
this.reactiveState.world.mesherWork = true
|
||||||
const distance = this.getDistance(pos)
|
const distance = this.getDistance(pos)
|
||||||
// todo shouldnt we check loadedChunks instead?
|
// todo shouldnt we check loadedChunks instead?
|
||||||
if (!this.workers.length || distance[0] > this.viewDistance || distance[1] > this.viewDistance) return
|
// if (!this.workers.length || distance[0] > this.viewDistance || distance[1] > this.viewDistance) return
|
||||||
const key = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`
|
const key = `${Math.floor(pos.x / 16) * 16},${Math.floor(pos.y / 16) * 16},${Math.floor(pos.z / 16) * 16}`
|
||||||
// if (this.sectionsOutstanding.has(key)) return
|
// if (this.sectionsOutstanding.has(key)) return
|
||||||
this.renderUpdateEmitter.emit('dirty', pos, value)
|
this.renderUpdateEmitter.emit('dirty', pos, value)
|
||||||
|
|
@ -1049,12 +1033,16 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
this.renderUpdateEmitter.removeAllListeners()
|
this.renderUpdateEmitter.removeAllListeners()
|
||||||
this.abortController.abort()
|
this.abortController.abort()
|
||||||
removeAllStats()
|
removeAllStats()
|
||||||
|
|
||||||
|
this.displayOptions.worldView.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initMesherWorker = (onGotMessage: (data: any) => void) => {
|
export const initMesherWorker = (onGotMessage: (data: any) => void) => {
|
||||||
// Node environment needs an absolute path, but browser needs the url of the file
|
// Node environment needs an absolute path, but browser needs the url of the file
|
||||||
const workerName = 'mesher.js'
|
const workerName = 'mesher.js'
|
||||||
|
// eslint-disable-next-line node/no-path-concat
|
||||||
|
const src = typeof window === 'undefined' ? `${__dirname}/${workerName}` : workerName
|
||||||
|
|
||||||
let worker: any
|
let worker: any
|
||||||
if (process.env.SINGLE_FILE_BUILD) {
|
if (process.env.SINGLE_FILE_BUILD) {
|
||||||
|
|
@ -1062,7 +1050,7 @@ export const initMesherWorker = (onGotMessage: (data: any) => void) => {
|
||||||
const blob = new Blob([workerCode], { type: 'text/javascript' })
|
const blob = new Blob([workerCode], { type: 'text/javascript' })
|
||||||
worker = new Worker(window.URL.createObjectURL(blob))
|
worker = new Worker(window.URL.createObjectURL(blob))
|
||||||
} else {
|
} else {
|
||||||
worker = new Worker(workerName)
|
worker = new Worker(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.onmessage = ({ data }) => {
|
worker.onmessage = ({ data }) => {
|
||||||
|
|
|
||||||
|
|
@ -80,12 +80,8 @@ export class CameraShake {
|
||||||
camera.setRotationFromQuaternion(yawQuat)
|
camera.setRotationFromQuaternion(yawQuat)
|
||||||
} else {
|
} else {
|
||||||
// For regular camera, apply all rotations
|
// For regular camera, apply all rotations
|
||||||
// Add tiny offsets to prevent z-fighting at ideal angles (90, 180, 270 degrees)
|
const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), this.basePitch)
|
||||||
const pitchOffset = this.addAntiZfightingOffset(this.basePitch)
|
const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), this.baseYaw)
|
||||||
const yawOffset = this.addAntiZfightingOffset(this.baseYaw)
|
|
||||||
|
|
||||||
const pitchQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset)
|
|
||||||
const yawQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset)
|
|
||||||
const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle))
|
const rollQuat = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), THREE.MathUtils.degToRad(this.rollAngle))
|
||||||
// Combine rotations in the correct order: pitch -> yaw -> roll
|
// Combine rotations in the correct order: pitch -> yaw -> roll
|
||||||
const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat)
|
const finalQuat = yawQuat.multiply(pitchQuat).multiply(rollQuat)
|
||||||
|
|
@ -100,21 +96,4 @@ export class CameraShake {
|
||||||
private easeInOut (t: number): number {
|
private easeInOut (t: number): number {
|
||||||
return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2
|
return t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
private addAntiZfightingOffset (angle: number): number {
|
|
||||||
const offset = 0.001 // Very small offset in radians (about 0.057 degrees)
|
|
||||||
|
|
||||||
// Check if the angle is close to ideal angles (0, π/2, π, 3π/2)
|
|
||||||
const normalizedAngle = ((angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2)
|
|
||||||
const tolerance = 0.01 // Tolerance for considering an angle "ideal"
|
|
||||||
|
|
||||||
if (Math.abs(normalizedAngle) < tolerance ||
|
|
||||||
Math.abs(normalizedAngle - Math.PI / 2) < tolerance ||
|
|
||||||
Math.abs(normalizedAngle - Math.PI) < tolerance ||
|
|
||||||
Math.abs(normalizedAngle - 3 * Math.PI / 2) < tolerance) {
|
|
||||||
return angle + offset
|
|
||||||
}
|
|
||||||
|
|
||||||
return angle
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,8 @@ export class DocumentRenderer {
|
||||||
this.previousCanvasWidth = this.canvas.width
|
this.previousCanvasWidth = this.canvas.width
|
||||||
this.previousCanvasHeight = this.canvas.height
|
this.previousCanvasHeight = this.canvas.height
|
||||||
|
|
||||||
const supportsWebGL2 = 'WebGL2RenderingContext' in window
|
|
||||||
// Only initialize stats and DOM-related features in main thread
|
// Only initialize stats and DOM-related features in main thread
|
||||||
if (!externalCanvas && supportsWebGL2) {
|
if (!externalCanvas) {
|
||||||
this.stats = new TopRightStats(this.canvas as HTMLCanvasElement, this.config.statsVisible)
|
this.stats = new TopRightStats(this.canvas as HTMLCanvasElement, this.config.statsVisible)
|
||||||
this.setupFpsTracking()
|
this.setupFpsTracking()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,7 @@ import { ItemSpecificContextProperties } from '../lib/basePlayerState'
|
||||||
import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins'
|
import { loadSkinFromUsername, loadSkinImage, stevePngUrl } from '../lib/utils/skins'
|
||||||
import { renderComponent } from '../sign-renderer'
|
import { renderComponent } from '../sign-renderer'
|
||||||
import { createCanvas } from '../lib/utils'
|
import { createCanvas } from '../lib/utils'
|
||||||
import { PlayerObjectType } from '../lib/createPlayerObject'
|
|
||||||
import { getBlockMeshFromModel } from './holdingBlock'
|
import { getBlockMeshFromModel } from './holdingBlock'
|
||||||
import { createItemMesh } from './itemMesh'
|
|
||||||
import * as Entity from './entity/EntityMesh'
|
import * as Entity from './entity/EntityMesh'
|
||||||
import { getMesh } from './entity/EntityMesh'
|
import { getMesh } from './entity/EntityMesh'
|
||||||
import { WalkingGeneralSwing } from './entity/animations'
|
import { WalkingGeneralSwing } from './entity/animations'
|
||||||
|
|
@ -34,6 +32,12 @@ export const steveTexture = loadThreeJsTextureFromUrl(stevePngUrl)
|
||||||
|
|
||||||
export const TWEEN_DURATION = 120
|
export const TWEEN_DURATION = 120
|
||||||
|
|
||||||
|
type PlayerObjectType = PlayerObject & {
|
||||||
|
animation?: PlayerAnimation
|
||||||
|
realPlayerUuid: string
|
||||||
|
realUsername: string
|
||||||
|
}
|
||||||
|
|
||||||
function convert2sComplementToHex (complement: number) {
|
function convert2sComplementToHex (complement: number) {
|
||||||
if (complement < 0) {
|
if (complement < 0) {
|
||||||
complement = (0xFF_FF_FF_FF + complement + 1) >>> 0
|
complement = (0xFF_FF_FF_FF + complement + 1) >>> 0
|
||||||
|
|
@ -137,7 +141,7 @@ const addNametag = (entity, options: { fontFamily: string }, mesh, version: stri
|
||||||
const canvas = getUsernameTexture(entity, options, version)
|
const canvas = getUsernameTexture(entity, options, version)
|
||||||
const tex = new THREE.Texture(canvas)
|
const tex = new THREE.Texture(canvas)
|
||||||
tex.needsUpdate = true
|
tex.needsUpdate = true
|
||||||
let nameTag: THREE.Object3D
|
let nameTag
|
||||||
if (entity.nameTagFixed) {
|
if (entity.nameTagFixed) {
|
||||||
const geometry = new THREE.PlaneGeometry()
|
const geometry = new THREE.PlaneGeometry()
|
||||||
const material = new THREE.MeshBasicMaterial({ map: tex })
|
const material = new THREE.MeshBasicMaterial({ map: tex })
|
||||||
|
|
@ -167,7 +171,6 @@ const addNametag = (entity, options: { fontFamily: string }, mesh, version: stri
|
||||||
nameTag.name = 'nametag'
|
nameTag.name = 'nametag'
|
||||||
|
|
||||||
mesh.add(nameTag)
|
mesh.add(nameTag)
|
||||||
return nameTag
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -491,10 +494,6 @@ export class Entities {
|
||||||
// todo true/undefined doesnt reset the skin to the default one
|
// todo true/undefined doesnt reset the skin to the default one
|
||||||
// eslint-disable-next-line max-params
|
// 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) {
|
async updatePlayerSkin (entityId: string | number, username: string | undefined, uuidCache: string | undefined, skinUrl: string | true, capeUrl: string | true | undefined = undefined) {
|
||||||
const isCustomSkin = skinUrl !== stevePngUrl
|
|
||||||
if (isCustomSkin) {
|
|
||||||
this.loadedSkinEntityIds.add(String(entityId))
|
|
||||||
}
|
|
||||||
if (uuidCache) {
|
if (uuidCache) {
|
||||||
if (typeof skinUrl === 'string' || typeof capeUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache] = {}
|
if (typeof skinUrl === 'string' || typeof capeUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache] = {}
|
||||||
if (typeof skinUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache].skinUrl = skinUrl
|
if (typeof skinUrl === 'string') this.uuidPerSkinUrlsCache[uuidCache].skinUrl = skinUrl
|
||||||
|
|
@ -718,7 +717,7 @@ export class Entities {
|
||||||
return typeof component === 'string' ? component : component.text ?? ''
|
return typeof component === 'string' ? component : component.text ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemMesh (item, specificProps: ItemSpecificContextProperties, faceCamera = false, previousModel?: string) {
|
getItemMesh (item, specificProps: ItemSpecificContextProperties, previousModel?: string) {
|
||||||
if (!item.nbt && item.nbtData) item.nbt = item.nbtData
|
if (!item.nbt && item.nbtData) item.nbt = item.nbtData
|
||||||
const textureUv = this.worldRenderer.getItemRenderData(item, specificProps)
|
const textureUv = this.worldRenderer.getItemRenderData(item, specificProps)
|
||||||
if (previousModel && previousModel === textureUv?.modelName) return undefined
|
if (previousModel && previousModel === textureUv?.modelName) return undefined
|
||||||
|
|
@ -737,41 +736,60 @@ export class Entities {
|
||||||
return {
|
return {
|
||||||
mesh: outerGroup,
|
mesh: outerGroup,
|
||||||
isBlock: true,
|
isBlock: true,
|
||||||
|
itemsTexture: null,
|
||||||
|
itemsTextureFlipped: null,
|
||||||
modelName: textureUv.modelName,
|
modelName: textureUv.modelName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render proper 3D model for items
|
// TODO: Render proper model (especially for blocks) instead of flat texture
|
||||||
if (textureUv) {
|
if (textureUv) {
|
||||||
const textureThree = textureUv.renderInfo?.texture === 'blocks' ? this.worldRenderer.material.map! : this.worldRenderer.itemsTexture
|
const textureThree = textureUv.renderInfo?.texture === 'blocks' ? this.worldRenderer.material.map! : this.worldRenderer.itemsTexture
|
||||||
|
// todo use geometry buffer uv instead!
|
||||||
const { u, v, su, sv } = textureUv
|
const { u, v, su, sv } = textureUv
|
||||||
const sizeX = su ?? 1 // su is actually width
|
const size = undefined
|
||||||
const sizeY = sv ?? 1 // sv is actually height
|
const itemsTexture = textureThree.clone()
|
||||||
|
itemsTexture.flipY = true
|
||||||
// Use the new unified item mesh function
|
const sizeY = (sv ?? size)!
|
||||||
const result = createItemMesh(textureThree, {
|
const sizeX = (su ?? size)!
|
||||||
u,
|
itemsTexture.offset.set(u, 1 - v - sizeY)
|
||||||
v,
|
itemsTexture.repeat.set(sizeX, sizeY)
|
||||||
sizeX,
|
itemsTexture.needsUpdate = true
|
||||||
sizeY
|
itemsTexture.magFilter = THREE.NearestFilter
|
||||||
}, {
|
itemsTexture.minFilter = THREE.NearestFilter
|
||||||
faceCamera,
|
const itemsTextureFlipped = itemsTexture.clone()
|
||||||
use3D: !faceCamera, // Only use 3D for non-camera-facing items
|
itemsTextureFlipped.repeat.x *= -1
|
||||||
|
itemsTextureFlipped.needsUpdate = true
|
||||||
|
itemsTextureFlipped.offset.set(u + (sizeX), 1 - v - sizeY)
|
||||||
|
const material = new THREE.MeshStandardMaterial({
|
||||||
|
map: itemsTexture,
|
||||||
|
transparent: true,
|
||||||
|
alphaTest: 0.1,
|
||||||
})
|
})
|
||||||
|
const materialFlipped = new THREE.MeshStandardMaterial({
|
||||||
|
map: itemsTextureFlipped,
|
||||||
|
transparent: true,
|
||||||
|
alphaTest: 0.1,
|
||||||
|
})
|
||||||
|
const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [
|
||||||
|
// top left and right bottom are black box materials others are transparent
|
||||||
|
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
|
||||||
|
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
|
||||||
|
material, materialFlipped,
|
||||||
|
])
|
||||||
let SCALE = 1
|
let SCALE = 1
|
||||||
if (specificProps['minecraft:display_context'] === 'ground') {
|
if (specificProps['minecraft:display_context'] === 'ground') {
|
||||||
SCALE = 0.5
|
SCALE = 0.5
|
||||||
} else if (specificProps['minecraft:display_context'] === 'thirdperson') {
|
} else if (specificProps['minecraft:display_context'] === 'thirdperson') {
|
||||||
SCALE = 6
|
SCALE = 6
|
||||||
}
|
}
|
||||||
result.mesh.scale.set(SCALE, SCALE, SCALE)
|
mesh.scale.set(SCALE, SCALE, SCALE)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mesh: result.mesh,
|
mesh,
|
||||||
isBlock: false,
|
isBlock: false,
|
||||||
|
itemsTexture,
|
||||||
|
itemsTextureFlipped,
|
||||||
modelName: textureUv.modelName,
|
modelName: textureUv.modelName,
|
||||||
cleanup: result.cleanup
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -787,6 +805,8 @@ export class Entities {
|
||||||
}
|
}
|
||||||
|
|
||||||
update (entity: SceneEntity['originalEntity'], overrides) {
|
update (entity: SceneEntity['originalEntity'], overrides) {
|
||||||
|
const justAdded = !this.entities[entity.id]
|
||||||
|
|
||||||
const isPlayerModel = entity.name === 'player'
|
const isPlayerModel = entity.name === 'player'
|
||||||
if (entity.name === 'zombie_villager' || entity.name === 'husk') {
|
if (entity.name === 'zombie_villager' || entity.name === 'husk') {
|
||||||
overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}`
|
overrides.texture = `textures/1.16.4/entity/${entity.name === 'zombie_villager' ? 'zombie_villager/zombie_villager.png' : `zombie/${entity.name}.png`}`
|
||||||
|
|
@ -797,7 +817,6 @@ export class Entities {
|
||||||
}
|
}
|
||||||
// this can be undefined in case where packet entity_destroy was sent twice (so it was already deleted)
|
// this can be undefined in case where packet entity_destroy was sent twice (so it was already deleted)
|
||||||
let e = this.entities[entity.id]
|
let e = this.entities[entity.id]
|
||||||
const justAdded = !e
|
|
||||||
|
|
||||||
if (entity.delete) {
|
if (entity.delete) {
|
||||||
if (!e) return
|
if (!e) return
|
||||||
|
|
@ -817,23 +836,21 @@ export class Entities {
|
||||||
if (e === undefined) {
|
if (e === undefined) {
|
||||||
const group = new THREE.Group() as unknown as SceneEntity
|
const group = new THREE.Group() as unknown as SceneEntity
|
||||||
group.originalEntity = entity
|
group.originalEntity = entity
|
||||||
if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block' || entity.name === 'snowball'
|
if (entity.name === 'item' || entity.name === 'tnt' || entity.name === 'falling_block') {
|
||||||
|| entity.name === 'egg' || entity.name === 'ender_pearl' || entity.name === 'experience_bottle'
|
const item = entity.name === 'tnt'
|
||||||
|| entity.name === 'splash_potion' || entity.name === 'lingering_potion') {
|
? { name: 'tnt' }
|
||||||
const item = entity.name === 'tnt' || entity.type === 'projectile'
|
|
||||||
? { name: entity.name }
|
|
||||||
: entity.name === 'falling_block'
|
: entity.name === 'falling_block'
|
||||||
? { blockState: entity['objectData'] }
|
? { blockState: entity['objectData'] }
|
||||||
: entity.metadata?.find((m: any) => typeof m === 'object' && m?.itemCount)
|
: entity.metadata?.find((m: any) => typeof m === 'object' && m?.itemCount)
|
||||||
if (item) {
|
if (item) {
|
||||||
const object = this.getItemMesh(item, {
|
const object = this.getItemMesh(item, {
|
||||||
'minecraft:display_context': 'ground',
|
'minecraft:display_context': 'ground',
|
||||||
}, entity.type === 'projectile')
|
})
|
||||||
if (object) {
|
if (object) {
|
||||||
mesh = object.mesh
|
mesh = object.mesh
|
||||||
if (entity.name === 'item' || entity.type === 'projectile') {
|
if (entity.name === 'item') {
|
||||||
mesh.scale.set(0.5, 0.5, 0.5)
|
mesh.scale.set(0.5, 0.5, 0.5)
|
||||||
mesh.position.set(0, entity.name === 'item' ? 0.2 : 0.1, 0)
|
mesh.position.set(0, 0.2, 0)
|
||||||
} else {
|
} else {
|
||||||
mesh.scale.set(2, 2, 2)
|
mesh.scale.set(2, 2, 2)
|
||||||
mesh.position.set(0, 0.5, 0)
|
mesh.position.set(0, 0.5, 0)
|
||||||
|
|
@ -841,8 +858,8 @@ export class Entities {
|
||||||
// set faces
|
// set faces
|
||||||
// mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5)
|
// mesh.position.set(targetPos.x + 0.5 + 2, targetPos.y + 0.5, targetPos.z + 0.5)
|
||||||
// viewer.scene.add(mesh)
|
// viewer.scene.add(mesh)
|
||||||
|
const clock = new THREE.Clock()
|
||||||
if (entity.name === 'item') {
|
if (entity.name === 'item') {
|
||||||
const clock = new THREE.Clock()
|
|
||||||
mesh.onBeforeRender = () => {
|
mesh.onBeforeRender = () => {
|
||||||
const delta = clock.getDelta()
|
const delta = clock.getDelta()
|
||||||
mesh!.rotation.y += delta
|
mesh!.rotation.y += delta
|
||||||
|
|
@ -871,9 +888,8 @@ export class Entities {
|
||||||
|
|
||||||
group.additionalCleanup = () => {
|
group.additionalCleanup = () => {
|
||||||
// important: avoid texture memory leak and gpu slowdown
|
// important: avoid texture memory leak and gpu slowdown
|
||||||
if (object.cleanup) {
|
object.itemsTexture?.dispose()
|
||||||
object.cleanup()
|
object.itemsTextureFlipped?.dispose()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -884,14 +900,20 @@ export class Entities {
|
||||||
mesh = wrapper
|
mesh = wrapper
|
||||||
|
|
||||||
if (entity.username) {
|
if (entity.username) {
|
||||||
const nametag = addNametag(entity, { fontFamily: 'mojangles' }, wrapper, this.worldRenderer.version)
|
// todo proper colors
|
||||||
if (nametag) {
|
const nameTag = new NameTagObject(fromFormattedString(entity.username).text, {
|
||||||
nametag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3
|
font: `48px ${this.entitiesOptions.fontFamily}`,
|
||||||
nametag.scale.multiplyScalar(12)
|
})
|
||||||
}
|
nameTag.position.y = playerObject.position.y + playerObject.scale.y * 16 + 3
|
||||||
|
nameTag.renderOrder = 1000
|
||||||
|
|
||||||
|
nameTag.name = 'nametag'
|
||||||
|
|
||||||
|
//@ts-expect-error
|
||||||
|
wrapper.add(nameTag)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mesh = getEntityMesh(entity, this.worldRenderer, this.entitiesOptions, { ...overrides, customModel: entity['customModel'] })
|
mesh = getEntityMesh(entity, this.worldRenderer, this.entitiesOptions, overrides)
|
||||||
}
|
}
|
||||||
if (!mesh) return
|
if (!mesh) return
|
||||||
mesh.name = 'mesh'
|
mesh.name = 'mesh'
|
||||||
|
|
@ -1147,7 +1169,8 @@ export class Entities {
|
||||||
const cameraPos = this.worldRenderer.cameraObject.position
|
const cameraPos = this.worldRenderer.cameraObject.position
|
||||||
const distance = mesh.position.distanceTo(cameraPos)
|
const distance = mesh.position.distanceTo(cameraPos)
|
||||||
if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (this.worldRenderer.viewDistance * 16)) {
|
if (distance < MAX_DISTANCE_SKIN_LOAD && distance < (this.worldRenderer.viewDistance * 16)) {
|
||||||
if (this.loadedSkinEntityIds.has(String(entityId))) return
|
if (this.loadedSkinEntityIds.has(entityId)) return
|
||||||
|
this.loadedSkinEntityIds.add(entityId)
|
||||||
void this.updatePlayerSkin(entityId, mesh.playerObject.realUsername, mesh.playerObject.realPlayerUuid, true, true)
|
void this.updatePlayerSkin(entityId, mesh.playerObject.realUsername, mesh.playerObject.realPlayerUuid, true, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1262,9 +1285,8 @@ export class Entities {
|
||||||
const group = new THREE.Object3D()
|
const group = new THREE.Object3D()
|
||||||
group['additionalCleanup'] = () => {
|
group['additionalCleanup'] = () => {
|
||||||
// important: avoid texture memory leak and gpu slowdown
|
// important: avoid texture memory leak and gpu slowdown
|
||||||
if (itemObject.cleanup) {
|
itemObject.itemsTexture?.dispose()
|
||||||
itemObject.cleanup()
|
itemObject.itemsTextureFlipped?.dispose()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const itemMesh = itemObject.mesh
|
const itemMesh = itemObject.mesh
|
||||||
group.rotation.z = -Math.PI / 16
|
group.rotation.z = -Math.PI / 16
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,6 @@ const getBackendMethods = (worldRenderer: WorldRendererThree) => {
|
||||||
shakeFromDamage: worldRenderer.cameraShake.shakeFromDamage.bind(worldRenderer.cameraShake),
|
shakeFromDamage: worldRenderer.cameraShake.shakeFromDamage.bind(worldRenderer.cameraShake),
|
||||||
onPageInteraction: worldRenderer.media.onPageInteraction.bind(worldRenderer.media),
|
onPageInteraction: worldRenderer.media.onPageInteraction.bind(worldRenderer.media),
|
||||||
downloadMesherLog: worldRenderer.downloadMesherLog.bind(worldRenderer),
|
downloadMesherLog: worldRenderer.downloadMesherLog.bind(worldRenderer),
|
||||||
|
|
||||||
addWaypoint: worldRenderer.waypoints.addWaypoint.bind(worldRenderer.waypoints),
|
|
||||||
removeWaypoint: worldRenderer.waypoints.removeWaypoint.bind(worldRenderer.waypoints),
|
|
||||||
|
|
||||||
// New method for updating skybox
|
|
||||||
setSkyboxImage: worldRenderer.skyboxRenderer.setSkyboxImage.bind(worldRenderer.skyboxRenderer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,7 @@ export default class HoldingBlock {
|
||||||
'minecraft:display_context': 'firstperson',
|
'minecraft:display_context': 'firstperson',
|
||||||
'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks,
|
'minecraft:use_duration': this.worldRenderer.playerStateReactive.itemUsageTicks,
|
||||||
'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks,
|
'minecraft:using_item': !!this.worldRenderer.playerStateReactive.itemUsageTicks,
|
||||||
}, false, this.lastItemModelName)
|
}, this.lastItemModelName)
|
||||||
if (result) {
|
if (result) {
|
||||||
const { mesh: itemMesh, isBlock, modelName } = result
|
const { mesh: itemMesh, isBlock, modelName } = result
|
||||||
if (isBlock) {
|
if (isBlock) {
|
||||||
|
|
|
||||||
|
|
@ -1,427 +0,0 @@
|
||||||
import * as THREE from 'three'
|
|
||||||
|
|
||||||
export interface Create3DItemMeshOptions {
|
|
||||||
depth: number
|
|
||||||
pixelSize?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Create3DItemMeshResult {
|
|
||||||
geometry: THREE.BufferGeometry
|
|
||||||
totalVertices: number
|
|
||||||
totalTriangles: number
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a 3D item geometry with front/back faces and connecting edges
|
|
||||||
* from a canvas containing the item texture
|
|
||||||
*/
|
|
||||||
export function create3DItemMesh (
|
|
||||||
canvas: HTMLCanvasElement,
|
|
||||||
options: Create3DItemMeshOptions
|
|
||||||
): Create3DItemMeshResult {
|
|
||||||
const { depth, pixelSize } = options
|
|
||||||
|
|
||||||
// Validate canvas dimensions
|
|
||||||
if (canvas.width <= 0 || canvas.height <= 0) {
|
|
||||||
throw new Error(`Invalid canvas dimensions: ${canvas.width}x${canvas.height}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d')!
|
|
||||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
|
||||||
const { data } = imageData
|
|
||||||
|
|
||||||
const w = canvas.width
|
|
||||||
const h = canvas.height
|
|
||||||
const halfDepth = depth / 2
|
|
||||||
const actualPixelSize = pixelSize ?? (1 / Math.max(w, h))
|
|
||||||
|
|
||||||
// Find opaque pixels
|
|
||||||
const isOpaque = (x: number, y: number) => {
|
|
||||||
if (x < 0 || y < 0 || x >= w || y >= h) return false
|
|
||||||
const i = (y * w + x) * 4
|
|
||||||
return data[i + 3] > 128 // alpha > 128
|
|
||||||
}
|
|
||||||
|
|
||||||
const vertices: number[] = []
|
|
||||||
const indices: number[] = []
|
|
||||||
const uvs: number[] = []
|
|
||||||
const normals: number[] = []
|
|
||||||
|
|
||||||
let vertexIndex = 0
|
|
||||||
|
|
||||||
// Helper to add a vertex
|
|
||||||
const addVertex = (x: number, y: number, z: number, u: number, v: number, nx: number, ny: number, nz: number) => {
|
|
||||||
vertices.push(x, y, z)
|
|
||||||
uvs.push(u, v)
|
|
||||||
normals.push(nx, ny, nz)
|
|
||||||
return vertexIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to add a quad (two triangles)
|
|
||||||
const addQuad = (v0: number, v1: number, v2: number, v3: number) => {
|
|
||||||
indices.push(v0, v1, v2, v0, v2, v3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert pixel coordinates to world coordinates
|
|
||||||
const pixelToWorld = (px: number, py: number) => {
|
|
||||||
const x = (px / w - 0.5) * actualPixelSize * w
|
|
||||||
const y = -(py / h - 0.5) * actualPixelSize * h
|
|
||||||
return { x, y }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a grid of vertices for front and back faces
|
|
||||||
const frontVertices: Array<Array<number | null>> = Array.from({ length: h + 1 }, () => Array.from({ length: w + 1 }, () => null))
|
|
||||||
const backVertices: Array<Array<number | null>> = Array.from({ length: h + 1 }, () => Array.from({ length: w + 1 }, () => null))
|
|
||||||
|
|
||||||
// Create vertices at pixel corners
|
|
||||||
for (let py = 0; py <= h; py++) {
|
|
||||||
for (let px = 0; px <= w; px++) {
|
|
||||||
const { x, y } = pixelToWorld(px - 0.5, py - 0.5)
|
|
||||||
|
|
||||||
// UV coordinates should map to the texture space of the extracted tile
|
|
||||||
const u = px / w
|
|
||||||
const v = py / h
|
|
||||||
|
|
||||||
// Check if this vertex is needed for any face or edge
|
|
||||||
let needVertex = false
|
|
||||||
|
|
||||||
// Check all 4 adjacent pixels to see if any are opaque
|
|
||||||
const adjacentPixels = [
|
|
||||||
[px - 1, py - 1], // top-left pixel
|
|
||||||
[px, py - 1], // top-right pixel
|
|
||||||
[px - 1, py], // bottom-left pixel
|
|
||||||
[px, py] // bottom-right pixel
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const [adjX, adjY] of adjacentPixels) {
|
|
||||||
if (isOpaque(adjX, adjY)) {
|
|
||||||
needVertex = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needVertex) {
|
|
||||||
frontVertices[py][px] = addVertex(x, y, halfDepth, u, v, 0, 0, 1)
|
|
||||||
backVertices[py][px] = addVertex(x, y, -halfDepth, u, v, 0, 0, -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create front and back faces
|
|
||||||
for (let py = 0; py < h; py++) {
|
|
||||||
for (let px = 0; px < w; px++) {
|
|
||||||
if (!isOpaque(px, py)) continue
|
|
||||||
|
|
||||||
const v00 = frontVertices[py][px]
|
|
||||||
const v10 = frontVertices[py][px + 1]
|
|
||||||
const v11 = frontVertices[py + 1][px + 1]
|
|
||||||
const v01 = frontVertices[py + 1][px]
|
|
||||||
|
|
||||||
const b00 = backVertices[py][px]
|
|
||||||
const b10 = backVertices[py][px + 1]
|
|
||||||
const b11 = backVertices[py + 1][px + 1]
|
|
||||||
const b01 = backVertices[py + 1][px]
|
|
||||||
|
|
||||||
if (v00 !== null && v10 !== null && v11 !== null && v01 !== null) {
|
|
||||||
// Front face
|
|
||||||
addQuad(v00, v10, v11, v01)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b00 !== null && b10 !== null && b11 !== null && b01 !== null) {
|
|
||||||
// Back face (reversed winding)
|
|
||||||
addQuad(b10, b00, b01, b11)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create edge faces for each side of the pixel with proper UVs
|
|
||||||
for (let py = 0; py < h; py++) {
|
|
||||||
for (let px = 0; px < w; px++) {
|
|
||||||
if (!isOpaque(px, py)) continue
|
|
||||||
|
|
||||||
const pixelU = (px + 0.5) / w // Center of current pixel
|
|
||||||
const pixelV = (py + 0.5) / h
|
|
||||||
|
|
||||||
// Left edge (x = px)
|
|
||||||
if (!isOpaque(px - 1, py)) {
|
|
||||||
const f0 = frontVertices[py][px]
|
|
||||||
const f1 = frontVertices[py + 1][px]
|
|
||||||
const b0 = backVertices[py][px]
|
|
||||||
const b1 = backVertices[py + 1][px]
|
|
||||||
|
|
||||||
if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) {
|
|
||||||
// Create new vertices for edge with current pixel's UV
|
|
||||||
const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, -1, 0, 0)
|
|
||||||
const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, -1, 0, 0)
|
|
||||||
const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, -1, 0, 0)
|
|
||||||
const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, -1, 0, 0)
|
|
||||||
addQuad(ef0, ef1, eb1, eb0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right edge (x = px + 1)
|
|
||||||
if (!isOpaque(px + 1, py)) {
|
|
||||||
const f0 = frontVertices[py + 1][px + 1]
|
|
||||||
const f1 = frontVertices[py][px + 1]
|
|
||||||
const b0 = backVertices[py + 1][px + 1]
|
|
||||||
const b1 = backVertices[py][px + 1]
|
|
||||||
|
|
||||||
if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) {
|
|
||||||
const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, 1, 0, 0)
|
|
||||||
const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, 1, 0, 0)
|
|
||||||
const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, 1, 0, 0)
|
|
||||||
const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, 1, 0, 0)
|
|
||||||
addQuad(ef0, ef1, eb1, eb0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top edge (y = py)
|
|
||||||
if (!isOpaque(px, py - 1)) {
|
|
||||||
const f0 = frontVertices[py][px]
|
|
||||||
const f1 = frontVertices[py][px + 1]
|
|
||||||
const b0 = backVertices[py][px]
|
|
||||||
const b1 = backVertices[py][px + 1]
|
|
||||||
|
|
||||||
if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) {
|
|
||||||
const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, 0, -1, 0)
|
|
||||||
const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, 0, -1, 0)
|
|
||||||
const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, 0, -1, 0)
|
|
||||||
const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, 0, -1, 0)
|
|
||||||
addQuad(ef0, ef1, eb1, eb0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom edge (y = py + 1)
|
|
||||||
if (!isOpaque(px, py + 1)) {
|
|
||||||
const f0 = frontVertices[py + 1][px + 1]
|
|
||||||
const f1 = frontVertices[py + 1][px]
|
|
||||||
const b0 = backVertices[py + 1][px + 1]
|
|
||||||
const b1 = backVertices[py + 1][px]
|
|
||||||
|
|
||||||
if (f0 !== null && f1 !== null && b0 !== null && b1 !== null) {
|
|
||||||
const ef0 = addVertex(vertices[f0 * 3], vertices[f0 * 3 + 1], vertices[f0 * 3 + 2], pixelU, pixelV, 0, 1, 0)
|
|
||||||
const ef1 = addVertex(vertices[f1 * 3], vertices[f1 * 3 + 1], vertices[f1 * 3 + 2], pixelU, pixelV, 0, 1, 0)
|
|
||||||
const eb1 = addVertex(vertices[b1 * 3], vertices[b1 * 3 + 1], vertices[b1 * 3 + 2], pixelU, pixelV, 0, 1, 0)
|
|
||||||
const eb0 = addVertex(vertices[b0 * 3], vertices[b0 * 3 + 1], vertices[b0 * 3 + 2], pixelU, pixelV, 0, 1, 0)
|
|
||||||
addQuad(ef0, ef1, eb1, eb0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const geometry = new THREE.BufferGeometry()
|
|
||||||
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3))
|
|
||||||
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2))
|
|
||||||
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3))
|
|
||||||
geometry.setIndex(indices)
|
|
||||||
|
|
||||||
// Compute normals properly
|
|
||||||
geometry.computeVertexNormals()
|
|
||||||
|
|
||||||
return {
|
|
||||||
geometry,
|
|
||||||
totalVertices: vertexIndex,
|
|
||||||
totalTriangles: indices.length / 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemTextureInfo {
|
|
||||||
u: number
|
|
||||||
v: number
|
|
||||||
sizeX: number
|
|
||||||
sizeY: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemMeshResult {
|
|
||||||
mesh: THREE.Object3D
|
|
||||||
itemsTexture?: THREE.Texture
|
|
||||||
itemsTextureFlipped?: THREE.Texture
|
|
||||||
cleanup?: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts item texture region to a canvas
|
|
||||||
*/
|
|
||||||
export function extractItemTextureToCanvas (
|
|
||||||
sourceTexture: THREE.Texture,
|
|
||||||
textureInfo: ItemTextureInfo
|
|
||||||
): HTMLCanvasElement {
|
|
||||||
const { u, v, sizeX, sizeY } = textureInfo
|
|
||||||
|
|
||||||
// Calculate canvas size - fix the calculation
|
|
||||||
const canvasWidth = Math.max(1, Math.floor(sizeX * sourceTexture.image.width))
|
|
||||||
const canvasHeight = Math.max(1, Math.floor(sizeY * sourceTexture.image.height))
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
canvas.width = canvasWidth
|
|
||||||
canvas.height = canvasHeight
|
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d')!
|
|
||||||
ctx.imageSmoothingEnabled = false
|
|
||||||
|
|
||||||
// Draw the item texture region to canvas
|
|
||||||
ctx.drawImage(
|
|
||||||
sourceTexture.image,
|
|
||||||
u * sourceTexture.image.width,
|
|
||||||
v * sourceTexture.image.height,
|
|
||||||
sizeX * sourceTexture.image.width,
|
|
||||||
sizeY * sourceTexture.image.height,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
canvas.width,
|
|
||||||
canvas.height
|
|
||||||
)
|
|
||||||
|
|
||||||
return canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates either a 2D or 3D item mesh based on parameters
|
|
||||||
*/
|
|
||||||
export function createItemMesh (
|
|
||||||
sourceTexture: THREE.Texture,
|
|
||||||
textureInfo: ItemTextureInfo,
|
|
||||||
options: {
|
|
||||||
faceCamera?: boolean
|
|
||||||
use3D?: boolean
|
|
||||||
depth?: number
|
|
||||||
} = {}
|
|
||||||
): ItemMeshResult {
|
|
||||||
const { faceCamera = false, use3D = true, depth = 0.04 } = options
|
|
||||||
const { u, v, sizeX, sizeY } = textureInfo
|
|
||||||
|
|
||||||
if (faceCamera) {
|
|
||||||
// Create sprite for camera-facing items
|
|
||||||
const itemsTexture = sourceTexture.clone()
|
|
||||||
itemsTexture.flipY = true
|
|
||||||
itemsTexture.offset.set(u, 1 - v - sizeY)
|
|
||||||
itemsTexture.repeat.set(sizeX, sizeY)
|
|
||||||
itemsTexture.needsUpdate = true
|
|
||||||
itemsTexture.magFilter = THREE.NearestFilter
|
|
||||||
itemsTexture.minFilter = THREE.NearestFilter
|
|
||||||
|
|
||||||
const spriteMat = new THREE.SpriteMaterial({
|
|
||||||
map: itemsTexture,
|
|
||||||
transparent: true,
|
|
||||||
alphaTest: 0.1,
|
|
||||||
})
|
|
||||||
const mesh = new THREE.Sprite(spriteMat)
|
|
||||||
|
|
||||||
return {
|
|
||||||
mesh,
|
|
||||||
itemsTexture,
|
|
||||||
cleanup () {
|
|
||||||
itemsTexture.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (use3D) {
|
|
||||||
// Try to create 3D mesh
|
|
||||||
try {
|
|
||||||
const canvas = extractItemTextureToCanvas(sourceTexture, textureInfo)
|
|
||||||
const { geometry } = create3DItemMesh(canvas, { depth })
|
|
||||||
|
|
||||||
// Create texture from canvas for the 3D mesh
|
|
||||||
const itemsTexture = new THREE.CanvasTexture(canvas)
|
|
||||||
itemsTexture.magFilter = THREE.NearestFilter
|
|
||||||
itemsTexture.minFilter = THREE.NearestFilter
|
|
||||||
itemsTexture.wrapS = itemsTexture.wrapT = THREE.ClampToEdgeWrapping
|
|
||||||
itemsTexture.flipY = false
|
|
||||||
itemsTexture.needsUpdate = true
|
|
||||||
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
map: itemsTexture,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
alphaTest: 0.1,
|
|
||||||
})
|
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(geometry, material)
|
|
||||||
|
|
||||||
return {
|
|
||||||
mesh,
|
|
||||||
itemsTexture,
|
|
||||||
cleanup () {
|
|
||||||
itemsTexture.dispose()
|
|
||||||
geometry.dispose()
|
|
||||||
if (material.map) material.map.dispose()
|
|
||||||
material.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to create 3D item mesh, falling back to 2D:', error)
|
|
||||||
// Fall through to 2D rendering
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to 2D flat rendering
|
|
||||||
const itemsTexture = sourceTexture.clone()
|
|
||||||
itemsTexture.flipY = true
|
|
||||||
itemsTexture.offset.set(u, 1 - v - sizeY)
|
|
||||||
itemsTexture.repeat.set(sizeX, sizeY)
|
|
||||||
itemsTexture.needsUpdate = true
|
|
||||||
itemsTexture.magFilter = THREE.NearestFilter
|
|
||||||
itemsTexture.minFilter = THREE.NearestFilter
|
|
||||||
|
|
||||||
const itemsTextureFlipped = itemsTexture.clone()
|
|
||||||
itemsTextureFlipped.repeat.x *= -1
|
|
||||||
itemsTextureFlipped.needsUpdate = true
|
|
||||||
itemsTextureFlipped.offset.set(u + sizeX, 1 - v - sizeY)
|
|
||||||
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
map: itemsTexture,
|
|
||||||
transparent: true,
|
|
||||||
alphaTest: 0.1,
|
|
||||||
})
|
|
||||||
const materialFlipped = new THREE.MeshStandardMaterial({
|
|
||||||
map: itemsTextureFlipped,
|
|
||||||
transparent: true,
|
|
||||||
alphaTest: 0.1,
|
|
||||||
})
|
|
||||||
const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 0), [
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
|
|
||||||
new THREE.MeshBasicMaterial({ color: 0x00_00_00 }), new THREE.MeshBasicMaterial({ color: 0x00_00_00 }),
|
|
||||||
material, materialFlipped,
|
|
||||||
])
|
|
||||||
|
|
||||||
return {
|
|
||||||
mesh,
|
|
||||||
itemsTexture,
|
|
||||||
itemsTextureFlipped,
|
|
||||||
cleanup () {
|
|
||||||
itemsTexture.dispose()
|
|
||||||
itemsTextureFlipped.dispose()
|
|
||||||
material.dispose()
|
|
||||||
materialFlipped.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a complete 3D item mesh from a canvas texture
|
|
||||||
*/
|
|
||||||
export function createItemMeshFromCanvas (
|
|
||||||
canvas: HTMLCanvasElement,
|
|
||||||
options: Create3DItemMeshOptions
|
|
||||||
): THREE.Mesh {
|
|
||||||
const { geometry } = create3DItemMesh(canvas, options)
|
|
||||||
|
|
||||||
// Base color texture for the item
|
|
||||||
const colorTexture = new THREE.CanvasTexture(canvas)
|
|
||||||
colorTexture.magFilter = THREE.NearestFilter
|
|
||||||
colorTexture.minFilter = THREE.NearestFilter
|
|
||||||
colorTexture.wrapS = colorTexture.wrapT = THREE.ClampToEdgeWrapping
|
|
||||||
colorTexture.flipY = false // Important for canvas textures
|
|
||||||
colorTexture.needsUpdate = true
|
|
||||||
|
|
||||||
// Material - no transparency, no alpha test needed for edges
|
|
||||||
const material = new THREE.MeshBasicMaterial({
|
|
||||||
map: colorTexture,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
alphaTest: 0.1
|
|
||||||
})
|
|
||||||
|
|
||||||
return new THREE.Mesh(geometry, material)
|
|
||||||
}
|
|
||||||
|
|
@ -10,11 +10,11 @@ export type ResolvedItemModelRender = {
|
||||||
|
|
||||||
export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: ResourcesManagerCommon, debugIsQuickbar = false, fullBlockModelSupport = false): {
|
export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: ResourcesManagerCommon, debugIsQuickbar = false, fullBlockModelSupport = false): {
|
||||||
texture: string,
|
texture: string,
|
||||||
blockData: Record<string, { slice, path }> & { resolvedModel: BlockModel } | null,
|
blockData?: Record<string, { slice, path }> & { resolvedModel: BlockModel },
|
||||||
scale: number | null,
|
scale?: number,
|
||||||
slice: number[] | null,
|
slice?: number[],
|
||||||
modelName: string | null,
|
modelName?: string,
|
||||||
} => {
|
} | undefined => {
|
||||||
let itemModelName = model.modelName
|
let itemModelName = model.modelName
|
||||||
const isItem = loadedData.itemsByName[itemModelName]
|
const isItem = loadedData.itemsByName[itemModelName]
|
||||||
|
|
||||||
|
|
@ -37,8 +37,6 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res
|
||||||
texture: 'gui',
|
texture: 'gui',
|
||||||
slice: [x, y, atlas.tileSize, atlas.tileSize],
|
slice: [x, y, atlas.tileSize, atlas.tileSize],
|
||||||
scale: 0.25,
|
scale: 0.25,
|
||||||
blockData: null,
|
|
||||||
modelName: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,18 +63,14 @@ export const renderSlot = (model: ResolvedItemModelRender, resourcesManager: Res
|
||||||
return {
|
return {
|
||||||
texture: itemTexture.type,
|
texture: itemTexture.type,
|
||||||
slice: itemTexture.slice,
|
slice: itemTexture.slice,
|
||||||
modelName: itemModelName,
|
modelName: itemModelName
|
||||||
blockData: null,
|
|
||||||
scale: null
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// is block
|
// is block
|
||||||
return {
|
return {
|
||||||
texture: 'blocks',
|
texture: 'blocks',
|
||||||
blockData: itemTexture,
|
blockData: itemTexture,
|
||||||
modelName: itemModelName,
|
modelName: itemModelName
|
||||||
slice: null,
|
|
||||||
scale: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,406 +0,0 @@
|
||||||
import * as THREE from 'three'
|
|
||||||
import { DebugGui } from '../lib/DebugGui'
|
|
||||||
|
|
||||||
export const DEFAULT_TEMPERATURE = 0.75
|
|
||||||
|
|
||||||
export class SkyboxRenderer {
|
|
||||||
private texture: THREE.Texture | null = null
|
|
||||||
private mesh: THREE.Mesh<THREE.SphereGeometry, THREE.MeshBasicMaterial> | null = null
|
|
||||||
private skyMesh: THREE.Mesh | null = null
|
|
||||||
private voidMesh: THREE.Mesh | null = null
|
|
||||||
|
|
||||||
// World state
|
|
||||||
private worldTime = 0
|
|
||||||
private partialTicks = 0
|
|
||||||
private viewDistance = 4
|
|
||||||
private temperature = DEFAULT_TEMPERATURE
|
|
||||||
private inWater = false
|
|
||||||
private waterBreathing = false
|
|
||||||
private fogBrightness = 0
|
|
||||||
private prevFogBrightness = 0
|
|
||||||
private readonly fogOrangeness = 0 // Debug property to control sky color orangeness
|
|
||||||
private readonly distanceFactor = 2.7
|
|
||||||
|
|
||||||
private readonly brightnessAtPosition = 1
|
|
||||||
debugGui: DebugGui
|
|
||||||
|
|
||||||
constructor (private readonly scene: THREE.Scene, public defaultSkybox: boolean, public initialImage: string | null) {
|
|
||||||
this.debugGui = new DebugGui('skybox_renderer', this, [
|
|
||||||
'temperature',
|
|
||||||
'worldTime',
|
|
||||||
'inWater',
|
|
||||||
'waterBreathing',
|
|
||||||
'fogOrangeness',
|
|
||||||
'brightnessAtPosition',
|
|
||||||
'distanceFactor'
|
|
||||||
], {
|
|
||||||
brightnessAtPosition: { min: 0, max: 1, step: 0.01 },
|
|
||||||
temperature: { min: 0, max: 1, step: 0.01 },
|
|
||||||
worldTime: { min: 0, max: 24_000, step: 1 },
|
|
||||||
fogOrangeness: { min: -1, max: 1, step: 0.01 },
|
|
||||||
distanceFactor: { min: 0, max: 5, step: 0.01 },
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!initialImage) {
|
|
||||||
this.createGradientSky()
|
|
||||||
}
|
|
||||||
// this.debugGui.activate()
|
|
||||||
}
|
|
||||||
|
|
||||||
async init () {
|
|
||||||
if (this.initialImage) {
|
|
||||||
await this.setSkyboxImage(this.initialImage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setSkyboxImage (imageUrl: string) {
|
|
||||||
// Dispose old textures if they exist
|
|
||||||
if (this.texture) {
|
|
||||||
this.texture.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the equirectangular texture
|
|
||||||
const textureLoader = new THREE.TextureLoader()
|
|
||||||
this.texture = await new Promise((resolve) => {
|
|
||||||
textureLoader.load(
|
|
||||||
imageUrl,
|
|
||||||
(texture) => {
|
|
||||||
texture.mapping = THREE.EquirectangularReflectionMapping
|
|
||||||
texture.encoding = THREE.sRGBEncoding
|
|
||||||
// Keep pixelated look
|
|
||||||
texture.minFilter = THREE.NearestFilter
|
|
||||||
texture.magFilter = THREE.NearestFilter
|
|
||||||
texture.needsUpdate = true
|
|
||||||
resolve(texture)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create or update the skybox
|
|
||||||
if (this.mesh) {
|
|
||||||
// Just update the texture on the existing material
|
|
||||||
this.mesh.material.map = this.texture
|
|
||||||
this.mesh.material.needsUpdate = true
|
|
||||||
} else {
|
|
||||||
// Create a large sphere geometry for the skybox
|
|
||||||
const geometry = new THREE.SphereGeometry(500, 60, 40)
|
|
||||||
// Flip the geometry inside out
|
|
||||||
geometry.scale(-1, 1, 1)
|
|
||||||
|
|
||||||
// Create material using the loaded texture
|
|
||||||
const material = new THREE.MeshBasicMaterial({
|
|
||||||
map: this.texture,
|
|
||||||
side: THREE.FrontSide // Changed to FrontSide since we're flipping the geometry
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create and add the skybox mesh
|
|
||||||
this.mesh = new THREE.Mesh(geometry, material)
|
|
||||||
this.scene.add(this.mesh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update (cameraPosition: THREE.Vector3, newViewDistance: number) {
|
|
||||||
if (newViewDistance !== this.viewDistance) {
|
|
||||||
this.viewDistance = newViewDistance
|
|
||||||
this.updateSkyColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mesh) {
|
|
||||||
// Update skybox position
|
|
||||||
this.mesh.position.copy(cameraPosition)
|
|
||||||
} else if (this.skyMesh) {
|
|
||||||
// Update gradient sky position
|
|
||||||
this.skyMesh.position.copy(cameraPosition)
|
|
||||||
this.voidMesh?.position.copy(cameraPosition)
|
|
||||||
this.updateSkyColors() // Update colors based on time of day
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update world time
|
|
||||||
updateTime (timeOfDay: number, partialTicks = 0) {
|
|
||||||
if (this.debugGui.visible) return
|
|
||||||
this.worldTime = timeOfDay
|
|
||||||
this.partialTicks = partialTicks
|
|
||||||
this.updateSkyColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update view distance
|
|
||||||
updateViewDistance (viewDistance: number) {
|
|
||||||
this.viewDistance = viewDistance
|
|
||||||
this.updateSkyColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update temperature (for biome support)
|
|
||||||
updateTemperature (temperature: number) {
|
|
||||||
if (this.debugGui.visible) return
|
|
||||||
this.temperature = temperature
|
|
||||||
this.updateSkyColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update water state
|
|
||||||
updateWaterState (inWater: boolean, waterBreathing: boolean) {
|
|
||||||
if (this.debugGui.visible) return
|
|
||||||
this.inWater = inWater
|
|
||||||
this.waterBreathing = waterBreathing
|
|
||||||
this.updateSkyColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update default skybox setting
|
|
||||||
updateDefaultSkybox (defaultSkybox: boolean) {
|
|
||||||
if (this.debugGui.visible) return
|
|
||||||
this.defaultSkybox = defaultSkybox
|
|
||||||
this.updateSkyColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
private createGradientSky () {
|
|
||||||
const size = 64
|
|
||||||
const scale = 256 / size + 2
|
|
||||||
|
|
||||||
{
|
|
||||||
const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2)
|
|
||||||
geometry.rotateX(-Math.PI / 2)
|
|
||||||
geometry.translate(0, 16, 0)
|
|
||||||
|
|
||||||
const material = new THREE.MeshBasicMaterial({
|
|
||||||
color: 0xff_ff_ff,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
depthTest: false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.skyMesh = new THREE.Mesh(geometry, material)
|
|
||||||
this.scene.add(this.skyMesh)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const geometry = new THREE.PlaneGeometry(size * scale * 2, size * scale * 2)
|
|
||||||
geometry.rotateX(-Math.PI / 2)
|
|
||||||
geometry.translate(0, -16, 0)
|
|
||||||
|
|
||||||
const material = new THREE.MeshBasicMaterial({
|
|
||||||
color: 0xff_ff_ff,
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
depthTest: false
|
|
||||||
})
|
|
||||||
|
|
||||||
this.voidMesh = new THREE.Mesh(geometry, material)
|
|
||||||
this.scene.add(this.voidMesh)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateSkyColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFogColor (partialTicks = 0): THREE.Vector3 {
|
|
||||||
const angle = this.getCelestialAngle(partialTicks)
|
|
||||||
let rotation = Math.cos(angle * Math.PI * 2) * 2 + 0.5
|
|
||||||
rotation = Math.max(0, Math.min(1, rotation))
|
|
||||||
|
|
||||||
let x = 0.752_941_2
|
|
||||||
let y = 0.847_058_83
|
|
||||||
let z = 1
|
|
||||||
|
|
||||||
x *= (rotation * 0.94 + 0.06)
|
|
||||||
y *= (rotation * 0.94 + 0.06)
|
|
||||||
z *= (rotation * 0.91 + 0.09)
|
|
||||||
|
|
||||||
return new THREE.Vector3(x, y, z)
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSkyColor (x = 0, z = 0, partialTicks = 0): THREE.Vector3 {
|
|
||||||
const angle = this.getCelestialAngle(partialTicks)
|
|
||||||
let brightness = Math.cos(angle * 3.141_593 * 2) * 2 + 0.5
|
|
||||||
|
|
||||||
if (brightness < 0) brightness = 0
|
|
||||||
if (brightness > 1) brightness = 1
|
|
||||||
|
|
||||||
const temperature = this.getTemperature(x, z)
|
|
||||||
const rgb = this.getSkyColorByTemp(temperature)
|
|
||||||
|
|
||||||
const red = ((rgb >> 16) & 0xff) / 255
|
|
||||||
const green = ((rgb >> 8) & 0xff) / 255
|
|
||||||
const blue = (rgb & 0xff) / 255
|
|
||||||
|
|
||||||
return new THREE.Vector3(
|
|
||||||
red * brightness,
|
|
||||||
green * brightness,
|
|
||||||
blue * brightness
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateCelestialAngle (time: number, partialTicks: number): number {
|
|
||||||
const modTime = (time % 24_000)
|
|
||||||
let angle = (modTime + partialTicks) / 24_000 - 0.25
|
|
||||||
|
|
||||||
if (angle < 0) {
|
|
||||||
angle++
|
|
||||||
}
|
|
||||||
if (angle > 1) {
|
|
||||||
angle--
|
|
||||||
}
|
|
||||||
|
|
||||||
angle = 1 - ((Math.cos(angle * Math.PI) + 1) / 2)
|
|
||||||
angle += (angle - angle) / 3
|
|
||||||
|
|
||||||
return angle
|
|
||||||
}
|
|
||||||
|
|
||||||
private getCelestialAngle (partialTicks: number): number {
|
|
||||||
return this.calculateCelestialAngle(this.worldTime, partialTicks)
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTemperature (x: number, z: number): number {
|
|
||||||
return this.temperature
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSkyColorByTemp (temperature: number): number {
|
|
||||||
temperature /= 3
|
|
||||||
if (temperature < -1) temperature = -1
|
|
||||||
if (temperature > 1) temperature = 1
|
|
||||||
|
|
||||||
// Apply debug fog orangeness to hue - positive values make it more orange, negative make it less orange
|
|
||||||
const baseHue = 0.622_222_2 - temperature * 0.05
|
|
||||||
// Orange is around hue 0.08-0.15, so we need to shift from blue-purple (0.62) toward orange
|
|
||||||
// Use a more dramatic shift and also increase saturation for more noticeable effect
|
|
||||||
const orangeHue = 0.12 // Orange hue value
|
|
||||||
const hue = this.fogOrangeness > 0
|
|
||||||
? baseHue + (orangeHue - baseHue) * this.fogOrangeness * 0.8 // Blend toward orange
|
|
||||||
: baseHue + this.fogOrangeness * 0.1 // Subtle shift for negative values
|
|
||||||
const saturation = 0.5 + temperature * 0.1 + Math.abs(this.fogOrangeness) * 0.3 // Increase saturation with orangeness
|
|
||||||
const brightness = 1
|
|
||||||
|
|
||||||
return this.hsbToRgb(hue, saturation, brightness)
|
|
||||||
}
|
|
||||||
|
|
||||||
private hsbToRgb (hue: number, saturation: number, brightness: number): number {
|
|
||||||
let r = 0; let g = 0; let b = 0
|
|
||||||
if (saturation === 0) {
|
|
||||||
r = g = b = Math.floor(brightness * 255 + 0.5)
|
|
||||||
} else {
|
|
||||||
const h = (hue - Math.floor(hue)) * 6
|
|
||||||
const f = h - Math.floor(h)
|
|
||||||
const p = brightness * (1 - saturation)
|
|
||||||
const q = brightness * (1 - saturation * f)
|
|
||||||
const t = brightness * (1 - (saturation * (1 - f)))
|
|
||||||
switch (Math.floor(h)) {
|
|
||||||
case 0:
|
|
||||||
r = Math.floor(brightness * 255 + 0.5)
|
|
||||||
g = Math.floor(t * 255 + 0.5)
|
|
||||||
b = Math.floor(p * 255 + 0.5)
|
|
||||||
break
|
|
||||||
case 1:
|
|
||||||
r = Math.floor(q * 255 + 0.5)
|
|
||||||
g = Math.floor(brightness * 255 + 0.5)
|
|
||||||
b = Math.floor(p * 255 + 0.5)
|
|
||||||
break
|
|
||||||
case 2:
|
|
||||||
r = Math.floor(p * 255 + 0.5)
|
|
||||||
g = Math.floor(brightness * 255 + 0.5)
|
|
||||||
b = Math.floor(t * 255 + 0.5)
|
|
||||||
break
|
|
||||||
case 3:
|
|
||||||
r = Math.floor(p * 255 + 0.5)
|
|
||||||
g = Math.floor(q * 255 + 0.5)
|
|
||||||
b = Math.floor(brightness * 255 + 0.5)
|
|
||||||
break
|
|
||||||
case 4:
|
|
||||||
r = Math.floor(t * 255 + 0.5)
|
|
||||||
g = Math.floor(p * 255 + 0.5)
|
|
||||||
b = Math.floor(brightness * 255 + 0.5)
|
|
||||||
break
|
|
||||||
case 5:
|
|
||||||
r = Math.floor(brightness * 255 + 0.5)
|
|
||||||
g = Math.floor(p * 255 + 0.5)
|
|
||||||
b = Math.floor(q * 255 + 0.5)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0xff_00_00_00 | (r << 16) | (g << 8) | (Math.trunc(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateSkyColors () {
|
|
||||||
if (!this.skyMesh || !this.voidMesh) return
|
|
||||||
|
|
||||||
// If default skybox is disabled, hide the skybox meshes
|
|
||||||
if (!this.defaultSkybox) {
|
|
||||||
this.skyMesh.visible = false
|
|
||||||
this.voidMesh.visible = false
|
|
||||||
if (this.mesh) {
|
|
||||||
this.mesh.visible = false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show skybox meshes when default skybox is enabled
|
|
||||||
this.skyMesh.visible = true
|
|
||||||
this.voidMesh.visible = true
|
|
||||||
if (this.mesh) {
|
|
||||||
this.mesh.visible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update fog brightness with smooth transition
|
|
||||||
this.prevFogBrightness = this.fogBrightness
|
|
||||||
const renderDistance = this.viewDistance / 32
|
|
||||||
const targetBrightness = this.brightnessAtPosition * (1 - renderDistance) + renderDistance
|
|
||||||
this.fogBrightness += (targetBrightness - this.fogBrightness) * 0.1
|
|
||||||
|
|
||||||
// Handle water fog
|
|
||||||
if (this.inWater) {
|
|
||||||
const waterViewDistance = this.waterBreathing ? 100 : 5
|
|
||||||
this.scene.fog = new THREE.Fog(new THREE.Color(0, 0, 1), 0.0025, waterViewDistance)
|
|
||||||
this.scene.background = new THREE.Color(0, 0, 1)
|
|
||||||
|
|
||||||
// Update sky and void colors for underwater effect
|
|
||||||
;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 1))
|
|
||||||
;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(0, 0, 0.6))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normal sky colors
|
|
||||||
const viewDistance = this.viewDistance * 16
|
|
||||||
const viewFactor = 1 - (0.25 + 0.75 * this.viewDistance / 32) ** 0.25
|
|
||||||
|
|
||||||
const angle = this.getCelestialAngle(this.partialTicks)
|
|
||||||
const skyColor = this.getSkyColor(0, 0, this.partialTicks)
|
|
||||||
const fogColor = this.getFogColor(this.partialTicks)
|
|
||||||
|
|
||||||
const brightness = Math.cos(angle * Math.PI * 2) * 2 + 0.5
|
|
||||||
const clampedBrightness = Math.max(0, Math.min(1, brightness))
|
|
||||||
|
|
||||||
// Interpolate fog brightness
|
|
||||||
const interpolatedBrightness = this.prevFogBrightness + (this.fogBrightness - this.prevFogBrightness) * this.partialTicks
|
|
||||||
|
|
||||||
const red = (fogColor.x + (skyColor.x - fogColor.x) * viewFactor) * clampedBrightness * interpolatedBrightness
|
|
||||||
const green = (fogColor.y + (skyColor.y - fogColor.y) * viewFactor) * clampedBrightness * interpolatedBrightness
|
|
||||||
const blue = (fogColor.z + (skyColor.z - fogColor.z) * viewFactor) * clampedBrightness * interpolatedBrightness
|
|
||||||
|
|
||||||
this.scene.background = new THREE.Color(red, green, blue)
|
|
||||||
this.scene.fog = new THREE.Fog(new THREE.Color(red, green, blue), 0.0025, viewDistance * this.distanceFactor)
|
|
||||||
|
|
||||||
;(this.skyMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(skyColor.x, skyColor.y, skyColor.z))
|
|
||||||
;(this.voidMesh.material as THREE.MeshBasicMaterial).color.set(new THREE.Color(
|
|
||||||
skyColor.x * 0.2 + 0.04,
|
|
||||||
skyColor.y * 0.2 + 0.04,
|
|
||||||
skyColor.z * 0.6 + 0.1
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose () {
|
|
||||||
if (this.texture) {
|
|
||||||
this.texture.dispose()
|
|
||||||
}
|
|
||||||
if (this.mesh) {
|
|
||||||
this.mesh.geometry.dispose()
|
|
||||||
;(this.mesh.material as THREE.Material).dispose()
|
|
||||||
this.scene.remove(this.mesh)
|
|
||||||
}
|
|
||||||
if (this.skyMesh) {
|
|
||||||
this.skyMesh.geometry.dispose()
|
|
||||||
;(this.skyMesh.material as THREE.Material).dispose()
|
|
||||||
this.scene.remove(this.skyMesh)
|
|
||||||
}
|
|
||||||
if (this.voidMesh) {
|
|
||||||
this.voidMesh.geometry.dispose()
|
|
||||||
;(this.voidMesh.material as THREE.Material).dispose()
|
|
||||||
this.scene.remove(this.voidMesh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,7 @@ import * as THREE from 'three'
|
||||||
import { WorldRendererThree } from './worldrendererThree'
|
import { WorldRendererThree } from './worldrendererThree'
|
||||||
|
|
||||||
export interface SoundSystem {
|
export interface SoundSystem {
|
||||||
playSound: (position: { x: number, y: number, z: number }, path: string, volume?: number, pitch?: number, timeout?: number) => void
|
playSound: (position: { x: number, y: number, z: number }, path: string, volume?: number, pitch?: number) => void
|
||||||
destroy: () => void
|
destroy: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -10,17 +10,7 @@ export class ThreeJsSound implements SoundSystem {
|
||||||
audioListener: THREE.AudioListener | undefined
|
audioListener: THREE.AudioListener | undefined
|
||||||
private readonly activeSounds = new Set<THREE.PositionalAudio>()
|
private readonly activeSounds = new Set<THREE.PositionalAudio>()
|
||||||
private readonly audioContext: AudioContext | undefined
|
private readonly audioContext: AudioContext | undefined
|
||||||
private readonly soundVolumes = new Map<THREE.PositionalAudio, number>()
|
|
||||||
baseVolume = 1
|
|
||||||
|
|
||||||
constructor (public worldRenderer: WorldRendererThree) {
|
constructor (public worldRenderer: WorldRendererThree) {
|
||||||
worldRenderer.onWorldSwitched.push(() => {
|
|
||||||
this.stopAll()
|
|
||||||
})
|
|
||||||
|
|
||||||
worldRenderer.onReactiveConfigUpdated('volume', (volume) => {
|
|
||||||
this.changeVolume(volume)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initAudioListener () {
|
initAudioListener () {
|
||||||
|
|
@ -29,24 +19,20 @@ export class ThreeJsSound implements SoundSystem {
|
||||||
this.worldRenderer.camera.add(this.audioListener)
|
this.worldRenderer.camera.add(this.audioListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
playSound (position: { x: number, y: number, z: number }, path: string, volume = 1, pitch = 1, timeout = 500) {
|
playSound (position: { x: number, y: number, z: number }, path: string, volume = 1, pitch = 1) {
|
||||||
this.initAudioListener()
|
this.initAudioListener()
|
||||||
|
|
||||||
const sound = new THREE.PositionalAudio(this.audioListener!)
|
const sound = new THREE.PositionalAudio(this.audioListener!)
|
||||||
this.activeSounds.add(sound)
|
this.activeSounds.add(sound)
|
||||||
this.soundVolumes.set(sound, volume)
|
|
||||||
|
|
||||||
const audioLoader = new THREE.AudioLoader()
|
const audioLoader = new THREE.AudioLoader()
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
void audioLoader.loadAsync(path).then((buffer) => {
|
void audioLoader.loadAsync(path).then((buffer) => {
|
||||||
if (Date.now() - start > timeout) {
|
if (Date.now() - start > 500) return
|
||||||
console.warn('Ignored playing sound', path, 'due to timeout:', timeout, 'ms <', Date.now() - start, 'ms')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// play
|
// play
|
||||||
sound.setBuffer(buffer)
|
sound.setBuffer(buffer)
|
||||||
sound.setRefDistance(20)
|
sound.setRefDistance(20)
|
||||||
sound.setVolume(volume * this.baseVolume)
|
sound.setVolume(volume)
|
||||||
sound.setPlaybackRate(pitch) // set the pitch
|
sound.setPlaybackRate(pitch) // set the pitch
|
||||||
this.worldRenderer.scene.add(sound)
|
this.worldRenderer.scene.add(sound)
|
||||||
// set sound position
|
// set sound position
|
||||||
|
|
@ -57,35 +43,21 @@ export class ThreeJsSound implements SoundSystem {
|
||||||
sound.disconnect()
|
sound.disconnect()
|
||||||
}
|
}
|
||||||
this.activeSounds.delete(sound)
|
this.activeSounds.delete(sound)
|
||||||
this.soundVolumes.delete(sound)
|
|
||||||
audioLoader.manager.itemEnd(path)
|
audioLoader.manager.itemEnd(path)
|
||||||
}
|
}
|
||||||
sound.play()
|
sound.play()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
stopAll () {
|
destroy () {
|
||||||
|
// Stop and clean up all active sounds
|
||||||
for (const sound of this.activeSounds) {
|
for (const sound of this.activeSounds) {
|
||||||
if (!sound) continue
|
|
||||||
sound.stop()
|
sound.stop()
|
||||||
if (sound.source) {
|
if (sound.source) {
|
||||||
sound.disconnect()
|
sound.disconnect()
|
||||||
}
|
}
|
||||||
this.worldRenderer.scene.remove(sound)
|
|
||||||
}
|
}
|
||||||
this.activeSounds.clear()
|
|
||||||
this.soundVolumes.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
changeVolume (volume: number) {
|
|
||||||
this.baseVolume = volume
|
|
||||||
for (const [sound, individualVolume] of this.soundVolumes) {
|
|
||||||
sound.setVolume(individualVolume * this.baseVolume)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy () {
|
|
||||||
this.stopAll()
|
|
||||||
// Remove and cleanup audio listener
|
// Remove and cleanup audio listener
|
||||||
if (this.audioListener) {
|
if (this.audioListener) {
|
||||||
this.audioListener.removeFromParent()
|
this.audioListener.removeFromParent()
|
||||||
|
|
|
||||||
|
|
@ -1,418 +0,0 @@
|
||||||
import * as THREE from 'three'
|
|
||||||
|
|
||||||
// Centralized visual configuration (in screen pixels)
|
|
||||||
export const WAYPOINT_CONFIG = {
|
|
||||||
// Target size in screen pixels (this controls the final sprite size)
|
|
||||||
TARGET_SCREEN_PX: 150,
|
|
||||||
// Canvas size for internal rendering (keep power of 2 for textures)
|
|
||||||
CANVAS_SIZE: 256,
|
|
||||||
// Relative positions in canvas (0-1)
|
|
||||||
LAYOUT: {
|
|
||||||
DOT_Y: 0.3,
|
|
||||||
NAME_Y: 0.45,
|
|
||||||
DISTANCE_Y: 0.55,
|
|
||||||
},
|
|
||||||
// Multiplier for canvas internal resolution to keep text crisp
|
|
||||||
CANVAS_SCALE: 2,
|
|
||||||
ARROW: {
|
|
||||||
enabledDefault: false,
|
|
||||||
pixelSize: 50,
|
|
||||||
paddingPx: 50,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WaypointSprite = {
|
|
||||||
group: THREE.Group
|
|
||||||
sprite: THREE.Sprite
|
|
||||||
// Offscreen arrow controls
|
|
||||||
enableOffscreenArrow: (enabled: boolean) => void
|
|
||||||
setArrowParent: (parent: THREE.Object3D | null) => void
|
|
||||||
// Convenience combined updater
|
|
||||||
updateForCamera: (
|
|
||||||
cameraPosition: THREE.Vector3,
|
|
||||||
camera: THREE.PerspectiveCamera,
|
|
||||||
viewportWidthPx: number,
|
|
||||||
viewportHeightPx: number
|
|
||||||
) => boolean
|
|
||||||
// Utilities
|
|
||||||
setColor: (color: number) => void
|
|
||||||
setLabel: (label?: string) => void
|
|
||||||
updateDistanceText: (label: string, distanceText: string) => void
|
|
||||||
setVisible: (visible: boolean) => void
|
|
||||||
setPosition: (x: number, y: number, z: number) => void
|
|
||||||
dispose: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createWaypointSprite (options: {
|
|
||||||
position: THREE.Vector3 | { x: number, y: number, z: number },
|
|
||||||
color?: number,
|
|
||||||
label?: string,
|
|
||||||
depthTest?: boolean,
|
|
||||||
// Y offset in world units used by updateScaleWorld only (screen-pixel API ignores this)
|
|
||||||
labelYOffset?: number,
|
|
||||||
metadata?: any,
|
|
||||||
}): WaypointSprite {
|
|
||||||
const color = options.color ?? 0xFF_00_00
|
|
||||||
const depthTest = options.depthTest ?? false
|
|
||||||
const labelYOffset = options.labelYOffset ?? 1.5
|
|
||||||
|
|
||||||
// Build combined sprite
|
|
||||||
const sprite = createCombinedSprite(color, options.label ?? '', '0m', depthTest)
|
|
||||||
sprite.renderOrder = 10
|
|
||||||
let currentLabel = options.label ?? ''
|
|
||||||
|
|
||||||
// Offscreen arrow (detached by default)
|
|
||||||
let arrowSprite: THREE.Sprite | undefined
|
|
||||||
let arrowParent: THREE.Object3D | null = null
|
|
||||||
let arrowEnabled = WAYPOINT_CONFIG.ARROW.enabledDefault
|
|
||||||
|
|
||||||
// Group for easy add/remove
|
|
||||||
const group = new THREE.Group()
|
|
||||||
group.add(sprite)
|
|
||||||
|
|
||||||
// Initial position
|
|
||||||
const { x, y, z } = options.position
|
|
||||||
group.position.set(x, y, z)
|
|
||||||
|
|
||||||
function setColor (newColor: number) {
|
|
||||||
const canvas = drawCombinedCanvas(newColor, currentLabel, '0m')
|
|
||||||
const texture = new THREE.CanvasTexture(canvas)
|
|
||||||
const mat = sprite.material
|
|
||||||
mat.map?.dispose()
|
|
||||||
mat.map = texture
|
|
||||||
mat.needsUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLabel (newLabel?: string) {
|
|
||||||
currentLabel = newLabel ?? ''
|
|
||||||
const canvas = drawCombinedCanvas(color, currentLabel, '0m')
|
|
||||||
const texture = new THREE.CanvasTexture(canvas)
|
|
||||||
const mat = sprite.material
|
|
||||||
mat.map?.dispose()
|
|
||||||
mat.map = texture
|
|
||||||
mat.needsUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDistanceText (label: string, distanceText: string) {
|
|
||||||
const canvas = drawCombinedCanvas(color, label, distanceText)
|
|
||||||
const texture = new THREE.CanvasTexture(canvas)
|
|
||||||
const mat = sprite.material
|
|
||||||
mat.map?.dispose()
|
|
||||||
mat.map = texture
|
|
||||||
mat.needsUpdate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVisible (visible: boolean) {
|
|
||||||
sprite.visible = visible
|
|
||||||
}
|
|
||||||
|
|
||||||
function setPosition (nx: number, ny: number, nz: number) {
|
|
||||||
group.position.set(nx, ny, nz)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep constant pixel size on screen using global config
|
|
||||||
function updateScaleScreenPixels (
|
|
||||||
cameraPosition: THREE.Vector3,
|
|
||||||
cameraFov: number,
|
|
||||||
distance: number,
|
|
||||||
viewportHeightPx: number
|
|
||||||
) {
|
|
||||||
const vFovRad = cameraFov * Math.PI / 180
|
|
||||||
const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance
|
|
||||||
// Use configured target screen size
|
|
||||||
const scale = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.TARGET_SCREEN_PX / viewportHeightPx)
|
|
||||||
sprite.scale.set(scale, scale, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureArrow () {
|
|
||||||
if (arrowSprite) return
|
|
||||||
const size = 128
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
canvas.width = size
|
|
||||||
canvas.height = size
|
|
||||||
const ctx = canvas.getContext('2d')!
|
|
||||||
ctx.clearRect(0, 0, size, size)
|
|
||||||
|
|
||||||
// Draw arrow shape
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.moveTo(size * 0.15, size * 0.5)
|
|
||||||
ctx.lineTo(size * 0.85, size * 0.5)
|
|
||||||
ctx.lineTo(size * 0.5, size * 0.15)
|
|
||||||
ctx.closePath()
|
|
||||||
|
|
||||||
// Use waypoint color for arrow
|
|
||||||
const colorHex = `#${color.toString(16).padStart(6, '0')}`
|
|
||||||
ctx.lineWidth = 6
|
|
||||||
ctx.strokeStyle = 'black'
|
|
||||||
ctx.stroke()
|
|
||||||
ctx.fillStyle = colorHex
|
|
||||||
ctx.fill()
|
|
||||||
|
|
||||||
const texture = new THREE.CanvasTexture(canvas)
|
|
||||||
const material = new THREE.SpriteMaterial({ map: texture, transparent: true, depthTest: false, depthWrite: false })
|
|
||||||
arrowSprite = new THREE.Sprite(material)
|
|
||||||
arrowSprite.renderOrder = 12
|
|
||||||
arrowSprite.visible = false
|
|
||||||
if (arrowParent) arrowParent.add(arrowSprite)
|
|
||||||
}
|
|
||||||
|
|
||||||
function enableOffscreenArrow (enabled: boolean) {
|
|
||||||
arrowEnabled = enabled
|
|
||||||
if (!enabled && arrowSprite) arrowSprite.visible = false
|
|
||||||
}
|
|
||||||
|
|
||||||
function setArrowParent (parent: THREE.Object3D | null) {
|
|
||||||
if (arrowSprite?.parent) arrowSprite.parent.remove(arrowSprite)
|
|
||||||
arrowParent = parent
|
|
||||||
if (arrowSprite && parent) parent.add(arrowSprite)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateOffscreenArrow (
|
|
||||||
camera: THREE.PerspectiveCamera,
|
|
||||||
viewportWidthPx: number,
|
|
||||||
viewportHeightPx: number
|
|
||||||
): boolean {
|
|
||||||
if (!arrowEnabled) return true
|
|
||||||
ensureArrow()
|
|
||||||
if (!arrowSprite) return true
|
|
||||||
|
|
||||||
// Check if onlyLeftRight is enabled in metadata
|
|
||||||
const onlyLeftRight = options.metadata?.onlyLeftRight === true
|
|
||||||
|
|
||||||
// Build camera basis using camera.up to respect custom orientations
|
|
||||||
const forward = new THREE.Vector3()
|
|
||||||
camera.getWorldDirection(forward) // camera look direction
|
|
||||||
const upWorld = camera.up.clone().normalize()
|
|
||||||
const right = new THREE.Vector3().copy(forward).cross(upWorld).normalize()
|
|
||||||
const upCam = new THREE.Vector3().copy(right).cross(forward).normalize()
|
|
||||||
|
|
||||||
// Vector from camera to waypoint
|
|
||||||
const camPos = new THREE.Vector3().setFromMatrixPosition(camera.matrixWorld)
|
|
||||||
const toWp = new THREE.Vector3(group.position.x, group.position.y, group.position.z).sub(camPos)
|
|
||||||
|
|
||||||
// Components in camera basis
|
|
||||||
const z = toWp.dot(forward)
|
|
||||||
const x = toWp.dot(right)
|
|
||||||
const y = toWp.dot(upCam)
|
|
||||||
|
|
||||||
const aspect = viewportWidthPx / viewportHeightPx
|
|
||||||
const vFovRad = camera.fov * Math.PI / 180
|
|
||||||
const hFovRad = 2 * Math.atan(Math.tan(vFovRad / 2) * aspect)
|
|
||||||
|
|
||||||
// Determine if waypoint is inside view frustum using angular checks
|
|
||||||
const thetaX = Math.atan2(x, z)
|
|
||||||
const thetaY = Math.atan2(y, z)
|
|
||||||
const visible = z > 0 && Math.abs(thetaX) <= hFovRad / 2 && Math.abs(thetaY) <= vFovRad / 2
|
|
||||||
if (visible) {
|
|
||||||
arrowSprite.visible = false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Direction on screen in normalized frustum units
|
|
||||||
let rx = thetaX / (hFovRad / 2)
|
|
||||||
let ry = thetaY / (vFovRad / 2)
|
|
||||||
|
|
||||||
// If behind the camera, snap to dominant axis to avoid confusing directions
|
|
||||||
if (z <= 0) {
|
|
||||||
if (Math.abs(rx) > Math.abs(ry)) {
|
|
||||||
rx = Math.sign(rx)
|
|
||||||
ry = 0
|
|
||||||
} else {
|
|
||||||
rx = 0
|
|
||||||
ry = Math.sign(ry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply onlyLeftRight logic - restrict arrows to left/right edges only
|
|
||||||
if (onlyLeftRight) {
|
|
||||||
// Force the arrow to appear only on left or right edges
|
|
||||||
if (Math.abs(rx) > Math.abs(ry)) {
|
|
||||||
// Horizontal direction is dominant, keep it
|
|
||||||
ry = 0
|
|
||||||
} else {
|
|
||||||
// Vertical direction is dominant, but we want only left/right
|
|
||||||
// So choose left or right based on the sign of rx
|
|
||||||
rx = rx >= 0 ? 1 : -1
|
|
||||||
ry = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place on the rectangle border [-1,1]x[-1,1]
|
|
||||||
const s = Math.max(Math.abs(rx), Math.abs(ry)) || 1
|
|
||||||
let ndcX = rx / s
|
|
||||||
let ndcY = ry / s
|
|
||||||
|
|
||||||
// Apply padding in pixel space by clamping
|
|
||||||
const padding = WAYPOINT_CONFIG.ARROW.paddingPx
|
|
||||||
const pxX = ((ndcX + 1) * 0.5) * viewportWidthPx
|
|
||||||
const pxY = ((1 - ndcY) * 0.5) * viewportHeightPx
|
|
||||||
const clampedPxX = Math.min(Math.max(pxX, padding), viewportWidthPx - padding)
|
|
||||||
const clampedPxY = Math.min(Math.max(pxY, padding), viewportHeightPx - padding)
|
|
||||||
ndcX = (clampedPxX / viewportWidthPx) * 2 - 1
|
|
||||||
ndcY = -(clampedPxY / viewportHeightPx) * 2 + 1
|
|
||||||
|
|
||||||
// Compute world position at a fixed distance in front of the camera using camera basis
|
|
||||||
const placeDist = Math.max(2, camera.near * 4)
|
|
||||||
const halfPlaneHeight = Math.tan(vFovRad / 2) * placeDist
|
|
||||||
const halfPlaneWidth = halfPlaneHeight * aspect
|
|
||||||
const pos = camPos.clone()
|
|
||||||
.add(forward.clone().multiplyScalar(placeDist))
|
|
||||||
.add(right.clone().multiplyScalar(ndcX * halfPlaneWidth))
|
|
||||||
.add(upCam.clone().multiplyScalar(ndcY * halfPlaneHeight))
|
|
||||||
|
|
||||||
// Update arrow sprite
|
|
||||||
arrowSprite.visible = true
|
|
||||||
arrowSprite.position.copy(pos)
|
|
||||||
|
|
||||||
// Angle for rotation relative to screen right/up (derived from camera up vector)
|
|
||||||
const angle = Math.atan2(ry, rx)
|
|
||||||
arrowSprite.material.rotation = angle - Math.PI / 2
|
|
||||||
|
|
||||||
// Constant pixel size for arrow (use fixed placement distance)
|
|
||||||
const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * placeDist
|
|
||||||
const sPx = worldUnitsPerScreenHeightAtDist * (WAYPOINT_CONFIG.ARROW.pixelSize / viewportHeightPx)
|
|
||||||
arrowSprite.scale.set(sPx, sPx, 1)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeDistance (cameraPosition: THREE.Vector3): number {
|
|
||||||
return cameraPosition.distanceTo(group.position)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateForCamera (
|
|
||||||
cameraPosition: THREE.Vector3,
|
|
||||||
camera: THREE.PerspectiveCamera,
|
|
||||||
viewportWidthPx: number,
|
|
||||||
viewportHeightPx: number
|
|
||||||
): boolean {
|
|
||||||
const distance = computeDistance(cameraPosition)
|
|
||||||
// Keep constant pixel size
|
|
||||||
updateScaleScreenPixels(cameraPosition, camera.fov, distance, viewportHeightPx)
|
|
||||||
// Update text
|
|
||||||
updateDistanceText(currentLabel, `${Math.round(distance)}m`)
|
|
||||||
// Update arrow and visibility
|
|
||||||
const onScreen = updateOffscreenArrow(camera, viewportWidthPx, viewportHeightPx)
|
|
||||||
setVisible(onScreen)
|
|
||||||
return onScreen
|
|
||||||
}
|
|
||||||
|
|
||||||
function dispose () {
|
|
||||||
const mat = sprite.material
|
|
||||||
mat.map?.dispose()
|
|
||||||
mat.dispose()
|
|
||||||
if (arrowSprite) {
|
|
||||||
const am = arrowSprite.material
|
|
||||||
am.map?.dispose()
|
|
||||||
am.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
group,
|
|
||||||
sprite,
|
|
||||||
enableOffscreenArrow,
|
|
||||||
setArrowParent,
|
|
||||||
updateForCamera,
|
|
||||||
setColor,
|
|
||||||
setLabel,
|
|
||||||
updateDistanceText,
|
|
||||||
setVisible,
|
|
||||||
setPosition,
|
|
||||||
dispose,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal helpers
|
|
||||||
function drawCombinedCanvas (color: number, id: string, distance: string): HTMLCanvasElement {
|
|
||||||
const scale = WAYPOINT_CONFIG.CANVAS_SCALE * (globalThis.devicePixelRatio || 1)
|
|
||||||
const size = WAYPOINT_CONFIG.CANVAS_SIZE * scale
|
|
||||||
const canvas = document.createElement('canvas')
|
|
||||||
canvas.width = size
|
|
||||||
canvas.height = size
|
|
||||||
const ctx = canvas.getContext('2d')!
|
|
||||||
|
|
||||||
// Clear canvas
|
|
||||||
ctx.clearRect(0, 0, size, size)
|
|
||||||
|
|
||||||
// Draw dot
|
|
||||||
const centerX = size / 2
|
|
||||||
const dotY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DOT_Y)
|
|
||||||
const radius = Math.round(size * 0.05) // Dot takes up ~12% of canvas height
|
|
||||||
const borderWidth = Math.max(2, Math.round(4 * scale))
|
|
||||||
|
|
||||||
// Outer border (black)
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(centerX, dotY, radius + borderWidth, 0, Math.PI * 2)
|
|
||||||
ctx.fillStyle = 'black'
|
|
||||||
ctx.fill()
|
|
||||||
|
|
||||||
// Inner circle (colored)
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(centerX, dotY, radius, 0, Math.PI * 2)
|
|
||||||
ctx.fillStyle = `#${color.toString(16).padStart(6, '0')}`
|
|
||||||
ctx.fill()
|
|
||||||
|
|
||||||
// Text properties
|
|
||||||
ctx.textAlign = 'center'
|
|
||||||
ctx.textBaseline = 'middle'
|
|
||||||
|
|
||||||
// Title
|
|
||||||
const nameFontPx = Math.round(size * 0.08) // ~8% of canvas height
|
|
||||||
const distanceFontPx = Math.round(size * 0.06) // ~6% of canvas height
|
|
||||||
ctx.font = `bold ${nameFontPx}px mojangles`
|
|
||||||
ctx.lineWidth = Math.max(2, Math.round(3 * scale))
|
|
||||||
const nameY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.NAME_Y)
|
|
||||||
|
|
||||||
ctx.strokeStyle = 'black'
|
|
||||||
ctx.strokeText(id, centerX, nameY)
|
|
||||||
ctx.fillStyle = 'white'
|
|
||||||
ctx.fillText(id, centerX, nameY)
|
|
||||||
|
|
||||||
// Distance
|
|
||||||
ctx.font = `bold ${distanceFontPx}px mojangles`
|
|
||||||
ctx.lineWidth = Math.max(2, Math.round(2 * scale))
|
|
||||||
const distanceY = Math.round(size * WAYPOINT_CONFIG.LAYOUT.DISTANCE_Y)
|
|
||||||
|
|
||||||
ctx.strokeStyle = 'black'
|
|
||||||
ctx.strokeText(distance, centerX, distanceY)
|
|
||||||
ctx.fillStyle = '#CCCCCC'
|
|
||||||
ctx.fillText(distance, centerX, distanceY)
|
|
||||||
|
|
||||||
return canvas
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCombinedSprite (color: number, id: string, distance: string, depthTest: boolean): THREE.Sprite {
|
|
||||||
const canvas = drawCombinedCanvas(color, id, distance)
|
|
||||||
const texture = new THREE.CanvasTexture(canvas)
|
|
||||||
texture.anisotropy = 1
|
|
||||||
texture.magFilter = THREE.LinearFilter
|
|
||||||
texture.minFilter = THREE.LinearFilter
|
|
||||||
const material = new THREE.SpriteMaterial({
|
|
||||||
map: texture,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 1,
|
|
||||||
depthTest,
|
|
||||||
depthWrite: false,
|
|
||||||
})
|
|
||||||
const sprite = new THREE.Sprite(material)
|
|
||||||
sprite.position.set(0, 0, 0)
|
|
||||||
return sprite
|
|
||||||
}
|
|
||||||
|
|
||||||
export const WaypointHelpers = {
|
|
||||||
// World-scale constant size helper
|
|
||||||
computeWorldScale (distance: number, fixedReference = 10) {
|
|
||||||
return Math.max(0.0001, distance / fixedReference)
|
|
||||||
},
|
|
||||||
// Screen-pixel constant size helper
|
|
||||||
computeScreenPixelScale (
|
|
||||||
camera: THREE.PerspectiveCamera,
|
|
||||||
distance: number,
|
|
||||||
pixelSize: number,
|
|
||||||
viewportHeightPx: number
|
|
||||||
) {
|
|
||||||
const vFovRad = camera.fov * Math.PI / 180
|
|
||||||
const worldUnitsPerScreenHeightAtDist = Math.tan(vFovRad / 2) * 2 * distance
|
|
||||||
return worldUnitsPerScreenHeightAtDist * (pixelSize / viewportHeightPx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
import * as THREE from 'three'
|
|
||||||
import { WorldRendererThree } from './worldrendererThree'
|
|
||||||
import { createWaypointSprite, type WaypointSprite } from './waypointSprite'
|
|
||||||
|
|
||||||
interface Waypoint {
|
|
||||||
id: string
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
z: number
|
|
||||||
minDistance: number
|
|
||||||
color: number
|
|
||||||
label?: string
|
|
||||||
sprite: WaypointSprite
|
|
||||||
}
|
|
||||||
|
|
||||||
interface WaypointOptions {
|
|
||||||
color?: number
|
|
||||||
label?: string
|
|
||||||
minDistance?: number
|
|
||||||
metadata?: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WaypointsRenderer {
|
|
||||||
private readonly waypoints = new Map<string, Waypoint>()
|
|
||||||
private readonly waypointScene = new THREE.Scene()
|
|
||||||
|
|
||||||
constructor (
|
|
||||||
private readonly worldRenderer: WorldRendererThree
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateWaypoints () {
|
|
||||||
const playerPos = this.worldRenderer.cameraObject.position
|
|
||||||
const sizeVec = this.worldRenderer.renderer.getSize(new THREE.Vector2())
|
|
||||||
|
|
||||||
for (const waypoint of this.waypoints.values()) {
|
|
||||||
const waypointPos = new THREE.Vector3(waypoint.x, waypoint.y, waypoint.z)
|
|
||||||
const distance = playerPos.distanceTo(waypointPos)
|
|
||||||
const visible = !waypoint.minDistance || distance >= waypoint.minDistance
|
|
||||||
|
|
||||||
waypoint.sprite.setVisible(visible)
|
|
||||||
|
|
||||||
if (visible) {
|
|
||||||
// Update position
|
|
||||||
waypoint.sprite.setPosition(waypoint.x, waypoint.y, waypoint.z)
|
|
||||||
// Ensure camera-based update each frame
|
|
||||||
waypoint.sprite.updateForCamera(this.worldRenderer.getCameraPosition(), this.worldRenderer.camera, sizeVec.width, sizeVec.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
if (this.waypoints.size === 0) return
|
|
||||||
|
|
||||||
// Update waypoint scaling
|
|
||||||
this.updateWaypoints()
|
|
||||||
|
|
||||||
// Render waypoints scene with the world camera
|
|
||||||
this.worldRenderer.renderer.render(this.waypointScene, this.worldRenderer.camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removed sprite/label texture creation. Use utils/waypointSprite.ts
|
|
||||||
|
|
||||||
addWaypoint (
|
|
||||||
id: string,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
z: number,
|
|
||||||
options: WaypointOptions = {}
|
|
||||||
) {
|
|
||||||
// Remove existing waypoint if it exists
|
|
||||||
this.removeWaypoint(id)
|
|
||||||
|
|
||||||
const color = options.color ?? 0xFF_00_00
|
|
||||||
const { label, metadata } = options
|
|
||||||
const minDistance = options.minDistance ?? 0
|
|
||||||
|
|
||||||
const sprite = createWaypointSprite({
|
|
||||||
position: new THREE.Vector3(x, y, z),
|
|
||||||
color,
|
|
||||||
label: (label || id),
|
|
||||||
metadata,
|
|
||||||
})
|
|
||||||
sprite.enableOffscreenArrow(true)
|
|
||||||
sprite.setArrowParent(this.waypointScene)
|
|
||||||
|
|
||||||
this.waypointScene.add(sprite.group)
|
|
||||||
|
|
||||||
this.waypoints.set(id, {
|
|
||||||
id, x: x + 0.5, y: y + 0.5, z: z + 0.5, minDistance,
|
|
||||||
color, label,
|
|
||||||
sprite,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
removeWaypoint (id: string) {
|
|
||||||
const waypoint = this.waypoints.get(id)
|
|
||||||
if (waypoint) {
|
|
||||||
this.waypointScene.remove(waypoint.sprite.group)
|
|
||||||
waypoint.sprite.dispose()
|
|
||||||
this.waypoints.delete(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear () {
|
|
||||||
for (const id of this.waypoints.keys()) {
|
|
||||||
this.removeWaypoint(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
testWaypoint () {
|
|
||||||
this.addWaypoint('Test Point', 0, 70, 0, { color: 0x00_FF_00, label: 'Test Point' })
|
|
||||||
this.addWaypoint('Spawn', 0, 64, 0, { color: 0xFF_FF_00, label: 'Spawn' })
|
|
||||||
this.addWaypoint('Far Point', 100, 70, 100, { color: 0x00_00_FF, label: 'Far Point' })
|
|
||||||
}
|
|
||||||
|
|
||||||
getWaypoint (id: string): Waypoint | undefined {
|
|
||||||
return this.waypoints.get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
getAllWaypoints (): Waypoint[] {
|
|
||||||
return [...this.waypoints.values()]
|
|
||||||
}
|
|
||||||
|
|
||||||
setWaypointColor (id: string, color: number) {
|
|
||||||
const waypoint = this.waypoints.get(id)
|
|
||||||
if (waypoint) {
|
|
||||||
waypoint.sprite.setColor(color)
|
|
||||||
waypoint.color = color
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setWaypointLabel (id: string, label?: string) {
|
|
||||||
const waypoint = this.waypoints.get(id)
|
|
||||||
if (waypoint) {
|
|
||||||
waypoint.label = label
|
|
||||||
waypoint.sprite.setLabel(label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -28,7 +28,7 @@ export class CursorBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
cursorLineMaterial: LineMaterial
|
cursorLineMaterial: LineMaterial
|
||||||
interactionLines: null | { blockPos: Vec3, mesh: THREE.Group, shapePositions: BlocksShapes | undefined } = null
|
interactionLines: null | { blockPos: Vec3, mesh: THREE.Group } = null
|
||||||
prevColor: string | undefined
|
prevColor: string | undefined
|
||||||
blockBreakMesh: THREE.Mesh
|
blockBreakMesh: THREE.Mesh
|
||||||
breakTextures: THREE.Texture[] = []
|
breakTextures: THREE.Texture[] = []
|
||||||
|
|
@ -62,13 +62,6 @@ export class CursorBlock {
|
||||||
this.worldRenderer.onReactivePlayerStateUpdated('gameMode', () => {
|
this.worldRenderer.onReactivePlayerStateUpdated('gameMode', () => {
|
||||||
this.updateLineMaterial()
|
this.updateLineMaterial()
|
||||||
})
|
})
|
||||||
// todo figure out why otherwise fog from skybox breaks it
|
|
||||||
setTimeout(() => {
|
|
||||||
this.updateLineMaterial()
|
|
||||||
if (this.interactionLines) {
|
|
||||||
this.setHighlightCursorBlock(this.interactionLines.blockPos, this.interactionLines.shapePositions, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update functions
|
// Update functions
|
||||||
|
|
@ -76,9 +69,6 @@ export class CursorBlock {
|
||||||
const inCreative = this.worldRenderer.playerStateReactive.gameMode === 'creative'
|
const inCreative = this.worldRenderer.playerStateReactive.gameMode === 'creative'
|
||||||
const pixelRatio = this.worldRenderer.renderer.getPixelRatio()
|
const pixelRatio = this.worldRenderer.renderer.getPixelRatio()
|
||||||
|
|
||||||
if (this.cursorLineMaterial) {
|
|
||||||
this.cursorLineMaterial.dispose()
|
|
||||||
}
|
|
||||||
this.cursorLineMaterial = new LineMaterial({
|
this.cursorLineMaterial = new LineMaterial({
|
||||||
color: (() => {
|
color: (() => {
|
||||||
switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) {
|
switch (this.worldRenderer.worldRendererConfig.highlightBlockColor) {
|
||||||
|
|
@ -125,8 +115,8 @@ export class CursorBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes, force = false): void {
|
setHighlightCursorBlock (blockPos: Vec3 | null, shapePositions?: BlocksShapes): void {
|
||||||
if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos) && !force) {
|
if (blockPos && this.interactionLines && blockPos.equals(this.interactionLines.blockPos)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.interactionLines !== null) {
|
if (this.interactionLines !== null) {
|
||||||
|
|
@ -150,7 +140,7 @@ export class CursorBlock {
|
||||||
}
|
}
|
||||||
this.worldRenderer.scene.add(group)
|
this.worldRenderer.scene.add(group)
|
||||||
group.visible = !this.cursorLinesHidden
|
group.visible = !this.cursorLinesHidden
|
||||||
this.interactionLines = { blockPos, mesh: group, shapePositions }
|
this.interactionLines = { blockPos, mesh: group }
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import { Vec3 } from 'vec3'
|
||||||
import nbt from 'prismarine-nbt'
|
import nbt from 'prismarine-nbt'
|
||||||
import PrismarineChatLoader from 'prismarine-chat'
|
import PrismarineChatLoader from 'prismarine-chat'
|
||||||
import * as tweenJs from '@tweenjs/tween.js'
|
import * as tweenJs from '@tweenjs/tween.js'
|
||||||
import { Biome } from 'minecraft-data'
|
|
||||||
import { renderSign } from '../sign-renderer'
|
import { renderSign } from '../sign-renderer'
|
||||||
import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer'
|
import { DisplayWorldOptions, GraphicsInitOptions } from '../../../src/appViewer'
|
||||||
import { chunkPos, sectionPos } from '../lib/simpleUtils'
|
import { chunkPos, sectionPos } from '../lib/simpleUtils'
|
||||||
|
|
@ -24,14 +23,12 @@ import { ThreeJsSound } from './threeJsSound'
|
||||||
import { CameraShake } from './cameraShake'
|
import { CameraShake } from './cameraShake'
|
||||||
import { ThreeJsMedia } from './threeJsMedia'
|
import { ThreeJsMedia } from './threeJsMedia'
|
||||||
import { Fountain } from './threeJsParticles'
|
import { Fountain } from './threeJsParticles'
|
||||||
import { WaypointsRenderer } from './waypoints'
|
|
||||||
import { DEFAULT_TEMPERATURE, SkyboxRenderer } from './skyboxRenderer'
|
|
||||||
|
|
||||||
type SectionKey = string
|
type SectionKey = string
|
||||||
|
|
||||||
export class WorldRendererThree extends WorldRendererCommon {
|
export class WorldRendererThree extends WorldRendererCommon {
|
||||||
outputFormat = 'threeJs' as const
|
outputFormat = 'threeJs' as const
|
||||||
sectionObjects: Record<string, THREE.Object3D & { foutain?: boolean }> = {}
|
sectionObjects: Record<string, THREE.Object3D & { foutain?: boolean, hasSkylight?: boolean }> = {}
|
||||||
chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
|
chunkTextures = new Map<string, { [pos: string]: THREE.Texture }>()
|
||||||
signsCache = new Map<string, any>()
|
signsCache = new Map<string, any>()
|
||||||
starField: StarField
|
starField: StarField
|
||||||
|
|
@ -51,7 +48,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
cameraContainer: THREE.Object3D
|
cameraContainer: THREE.Object3D
|
||||||
media: ThreeJsMedia
|
media: ThreeJsMedia
|
||||||
waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] }
|
waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] }
|
||||||
waypoints: WaypointsRenderer
|
|
||||||
camera: THREE.PerspectiveCamera
|
camera: THREE.PerspectiveCamera
|
||||||
renderTimeAvg = 0
|
renderTimeAvg = 0
|
||||||
sectionsOffsetsAnimations = {} as {
|
sectionsOffsetsAnimations = {} as {
|
||||||
|
|
@ -73,7 +69,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
}
|
}
|
||||||
fountains: Fountain[] = []
|
fountains: Fountain[] = []
|
||||||
DEBUG_RAYCAST = false
|
DEBUG_RAYCAST = false
|
||||||
skyboxRenderer: SkyboxRenderer
|
|
||||||
|
|
||||||
private currentPosTween?: tweenJs.Tween<THREE.Vector3>
|
private currentPosTween?: tweenJs.Tween<THREE.Vector3>
|
||||||
private currentRotTween?: tweenJs.Tween<{ pitch: number, yaw: number }>
|
private currentRotTween?: tweenJs.Tween<{ pitch: number, yaw: number }>
|
||||||
|
|
@ -97,10 +92,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
this.holdingBlock = new HoldingBlock(this)
|
this.holdingBlock = new HoldingBlock(this)
|
||||||
this.holdingBlockLeft = new HoldingBlock(this, true)
|
this.holdingBlockLeft = new HoldingBlock(this, true)
|
||||||
|
|
||||||
// Initialize skybox renderer
|
|
||||||
this.skyboxRenderer = new SkyboxRenderer(this.scene, this.worldRendererConfig.defaultSkybox, null)
|
|
||||||
void this.skyboxRenderer.init()
|
|
||||||
|
|
||||||
this.addDebugOverlay()
|
this.addDebugOverlay()
|
||||||
this.resetScene()
|
this.resetScene()
|
||||||
void this.init()
|
void this.init()
|
||||||
|
|
@ -108,8 +99,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
this.soundSystem = new ThreeJsSound(this)
|
this.soundSystem = new ThreeJsSound(this)
|
||||||
this.cameraShake = new CameraShake(this, this.onRender)
|
this.cameraShake = new CameraShake(this, this.onRender)
|
||||||
this.media = new ThreeJsMedia(this)
|
this.media = new ThreeJsMedia(this)
|
||||||
this.waypoints = new WaypointsRenderer(this)
|
|
||||||
|
|
||||||
// this.fountain = new Fountain(this.scene, this.scene, {
|
// this.fountain = new Fountain(this.scene, this.scene, {
|
||||||
// position: new THREE.Vector3(0, 10, 0),
|
// position: new THREE.Vector3(0, 10, 0),
|
||||||
// })
|
// })
|
||||||
|
|
@ -130,8 +119,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
this.protocolCustomBlocks.clear()
|
this.protocolCustomBlocks.clear()
|
||||||
// Reset section animations
|
// Reset section animations
|
||||||
this.sectionsOffsetsAnimations = {}
|
this.sectionsOffsetsAnimations = {}
|
||||||
// Clear waypoints
|
|
||||||
this.waypoints.clear()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,18 +161,23 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
override watchReactivePlayerState () {
|
override watchReactivePlayerState () {
|
||||||
super.watchReactivePlayerState()
|
super.watchReactivePlayerState()
|
||||||
this.onReactivePlayerStateUpdated('inWater', (value) => {
|
this.onReactivePlayerStateUpdated('inWater', (value) => {
|
||||||
this.skyboxRenderer.updateWaterState(value, this.playerStateReactive.waterBreathing)
|
this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, this.playerStateReactive.waterBreathing ? 100 : 20) : null
|
||||||
})
|
|
||||||
this.onReactivePlayerStateUpdated('waterBreathing', (value) => {
|
|
||||||
this.skyboxRenderer.updateWaterState(this.playerStateReactive.inWater, value)
|
|
||||||
})
|
})
|
||||||
this.onReactivePlayerStateUpdated('ambientLight', (value) => {
|
this.onReactivePlayerStateUpdated('ambientLight', (value) => {
|
||||||
if (!value) return
|
if (!value) return
|
||||||
this.ambientLight.intensity = value
|
if (this.worldRendererConfig.legacyLighting) {
|
||||||
|
this.ambientLight.intensity = value
|
||||||
|
} else {
|
||||||
|
this.ambientLight.intensity = 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
this.onReactivePlayerStateUpdated('directionalLight', (value) => {
|
this.onReactivePlayerStateUpdated('directionalLight', (value) => {
|
||||||
if (!value) return
|
if (!value) return
|
||||||
this.directionalLight.intensity = value
|
if (this.worldRendererConfig.legacyLighting) {
|
||||||
|
this.directionalLight.intensity = value
|
||||||
|
} else {
|
||||||
|
this.directionalLight.intensity = 0.4
|
||||||
|
}
|
||||||
})
|
})
|
||||||
this.onReactivePlayerStateUpdated('lookingAtBlock', (value) => {
|
this.onReactivePlayerStateUpdated('lookingAtBlock', (value) => {
|
||||||
this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes)
|
this.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes)
|
||||||
|
|
@ -206,9 +198,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
this.onReactiveConfigUpdated('showChunkBorders', (value) => {
|
this.onReactiveConfigUpdated('showChunkBorders', (value) => {
|
||||||
this.updateShowChunksBorder(value)
|
this.updateShowChunksBorder(value)
|
||||||
})
|
})
|
||||||
this.onReactiveConfigUpdated('defaultSkybox', (value) => {
|
|
||||||
this.skyboxRenderer.updateDefaultSkybox(value)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) {
|
changeHandSwingingState (isAnimationPlaying: boolean, isLeft = false) {
|
||||||
|
|
@ -271,25 +260,40 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
} else {
|
} else {
|
||||||
this.starField.remove()
|
this.starField.remove()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.skyboxRenderer.updateTime(newTime)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
biomeUpdated (biome: Biome): void {
|
skylightUpdated (): void {
|
||||||
if (biome?.temperature !== undefined) {
|
let updated = 0
|
||||||
this.skyboxRenderer.updateTemperature(biome.temperature)
|
for (const sectionKey of Object.keys(this.sectionObjects)) {
|
||||||
|
if (this.sectionObjects[sectionKey].hasSkylight) {
|
||||||
|
// set section to be updated
|
||||||
|
const [x, y, z] = sectionKey.split(',').map(Number)
|
||||||
|
this.setSectionDirty(new Vec3(x, y, z))
|
||||||
|
updated++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
biomeReset (): void {
|
console.log(`Skylight changed to ${this.skyLight}. Updated`, updated, 'sections')
|
||||||
// Reset to default temperature when biome is unknown
|
|
||||||
this.skyboxRenderer.updateTemperature(DEFAULT_TEMPERATURE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getItemRenderData (item: Record<string, any>, specificProps: ItemSpecificContextProperties) {
|
getItemRenderData (item: Record<string, any>, specificProps: ItemSpecificContextProperties) {
|
||||||
return getItemUv(item, specificProps, this.resourcesManager, this.playerStateReactive)
|
return getItemUv(item, specificProps, this.resourcesManager, this.playerStateReactive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugOnlySunlightSections (enable: boolean, state = true) {
|
||||||
|
for (const sectionKey of Object.keys(this.sectionObjects)) {
|
||||||
|
if (!enable) {
|
||||||
|
this.sectionObjects[sectionKey].visible = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (this.sectionObjects[sectionKey].hasSkylight) {
|
||||||
|
this.sectionObjects[sectionKey].visible = state
|
||||||
|
} else {
|
||||||
|
this.sectionObjects[sectionKey].visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async demoModel () {
|
async demoModel () {
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
const pos = cursorBlockRel(0, 1, 0).position
|
const pos = cursorBlockRel(0, 1, 0).position
|
||||||
|
|
@ -377,7 +381,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
// debugRecomputedDeletedObjects = 0
|
// debugRecomputedDeletedObjects = 0
|
||||||
handleWorkerMessage (data: { geometry: MesherGeometryOutput, key, type }): void {
|
handleWorkerMessage (data: { geometry: MesherGeometryOutput, key, type }): void {
|
||||||
if (data.type !== 'geometry') return
|
if (data.type !== 'geometry') return
|
||||||
let object: THREE.Object3D = this.sectionObjects[data.key]
|
let object = this.sectionObjects[data.key]
|
||||||
if (object) {
|
if (object) {
|
||||||
this.scene.remove(object)
|
this.scene.remove(object)
|
||||||
disposeObject(object)
|
disposeObject(object)
|
||||||
|
|
@ -436,7 +440,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
object.add(head)
|
object.add(head)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object.hasSkylight = data.geometry.hasSkylight
|
||||||
this.sectionObjects[data.key] = object
|
this.sectionObjects[data.key] = object
|
||||||
|
|
||||||
if (this.displayOptions.inWorldRenderingConfig._renderByChunks) {
|
if (this.displayOptions.inWorldRenderingConfig._renderByChunks) {
|
||||||
object.visible = false
|
object.visible = false
|
||||||
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
||||||
|
|
@ -485,7 +492,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
return worldPos
|
return worldPos
|
||||||
}
|
}
|
||||||
|
|
||||||
getSectionCameraPosition () {
|
getWorldCameraPosition () {
|
||||||
const pos = this.getCameraPosition()
|
const pos = this.getCameraPosition()
|
||||||
return new Vec3(
|
return new Vec3(
|
||||||
Math.floor(pos.x / 16),
|
Math.floor(pos.x / 16),
|
||||||
|
|
@ -495,7 +502,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCameraSectionPos () {
|
updateCameraSectionPos () {
|
||||||
const newSectionPos = this.getSectionCameraPosition()
|
const newSectionPos = this.getWorldCameraPosition()
|
||||||
if (!this.cameraSectionPos.equals(newSectionPos)) {
|
if (!this.cameraSectionPos.equals(newSectionPos)) {
|
||||||
this.cameraSectionPos = newSectionPos
|
this.cameraSectionPos = newSectionPos
|
||||||
this.cameraSectionPositionUpdate()
|
this.cameraSectionPositionUpdate()
|
||||||
|
|
@ -734,10 +741,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
this.cursorBlock.render()
|
this.cursorBlock.render()
|
||||||
this.updateSectionOffsets()
|
this.updateSectionOffsets()
|
||||||
|
|
||||||
// Update skybox position to follow camera
|
|
||||||
const cameraPos = this.getCameraPosition()
|
|
||||||
this.skyboxRenderer.update(cameraPos, this.viewDistance)
|
|
||||||
|
|
||||||
const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov
|
const sizeOrFovChanged = sizeChanged || this.displayOptions.inWorldRenderingConfig.fov !== this.camera.fov
|
||||||
if (sizeOrFovChanged) {
|
if (sizeOrFovChanged) {
|
||||||
const size = this.renderer.getSize(new THREE.Vector2())
|
const size = this.renderer.getSize(new THREE.Vector2())
|
||||||
|
|
@ -773,8 +776,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
fountain.render()
|
fountain.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
this.waypoints.render()
|
|
||||||
|
|
||||||
for (const onRender of this.onRender) {
|
for (const onRender of this.onRender) {
|
||||||
onRender()
|
onRender()
|
||||||
}
|
}
|
||||||
|
|
@ -787,17 +788,12 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) {
|
renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) {
|
||||||
let textureData: string
|
const textures = blockEntity.SkullOwner?.Properties?.textures[0]
|
||||||
if (blockEntity.SkullOwner) {
|
if (!textures) return
|
||||||
textureData = blockEntity.SkullOwner.Properties?.textures?.[0]?.Value
|
|
||||||
} else {
|
|
||||||
textureData = blockEntity.profile?.properties?.find(p => p.name === 'textures')?.value
|
|
||||||
}
|
|
||||||
if (!textureData) return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decodedData = JSON.parse(Buffer.from(textureData, 'base64').toString())
|
const textureData = JSON.parse(Buffer.from(textures.Value, 'base64').toString())
|
||||||
let skinUrl = decodedData.textures?.SKIN?.url
|
let skinUrl = textureData.textures?.SKIN?.url
|
||||||
const { skinTexturesProxy } = this.worldRendererConfig
|
const { skinTexturesProxy } = this.worldRendererConfig
|
||||||
if (skinTexturesProxy) {
|
if (skinTexturesProxy) {
|
||||||
skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy)
|
skinUrl = skinUrl?.replace('http://textures.minecraft.net/', skinTexturesProxy)
|
||||||
|
|
@ -982,7 +978,6 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
|
|
||||||
destroy (): void {
|
destroy (): void {
|
||||||
super.destroy()
|
super.destroy()
|
||||||
this.skyboxRenderer.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldObjectVisible (object: THREE.Object3D) {
|
shouldObjectVisible (object: THREE.Object3D) {
|
||||||
|
|
@ -1066,13 +1061,6 @@ class StarField {
|
||||||
constructor (
|
constructor (
|
||||||
private readonly worldRenderer: WorldRendererThree
|
private readonly worldRenderer: WorldRendererThree
|
||||||
) {
|
) {
|
||||||
const clock = new THREE.Clock()
|
|
||||||
const speed = 0.2
|
|
||||||
this.worldRenderer.onRender.push(() => {
|
|
||||||
if (!this.points) return
|
|
||||||
this.points.position.copy(this.worldRenderer.getCameraPosition());
|
|
||||||
(this.points.material as StarfieldMaterial).uniforms.time.value = clock.getElapsedTime() * speed
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addToScene () {
|
addToScene () {
|
||||||
|
|
@ -1083,6 +1071,7 @@ class StarField {
|
||||||
const count = 7000
|
const count = 7000
|
||||||
const factor = 7
|
const factor = 7
|
||||||
const saturation = 10
|
const saturation = 10
|
||||||
|
const speed = 0.2
|
||||||
|
|
||||||
const geometry = new THREE.BufferGeometry()
|
const geometry = new THREE.BufferGeometry()
|
||||||
|
|
||||||
|
|
@ -1115,6 +1104,11 @@ class StarField {
|
||||||
this.points = new THREE.Points(geometry, material)
|
this.points = new THREE.Points(geometry, material)
|
||||||
this.worldRenderer.scene.add(this.points)
|
this.worldRenderer.scene.add(this.points)
|
||||||
|
|
||||||
|
const clock = new THREE.Clock()
|
||||||
|
this.points.onBeforeRender = (renderer, scene, camera) => {
|
||||||
|
this.points?.position.copy?.(this.worldRenderer.getCameraPosition())
|
||||||
|
material.uniforms.time.value = clock.getElapsedTime() * speed
|
||||||
|
}
|
||||||
this.points.renderOrder = -1
|
this.points.renderOrder = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,13 +139,6 @@ const appConfig = defineConfig({
|
||||||
// 50kb limit for data uri
|
// 50kb limit for data uri
|
||||||
dataUriLimit: SINGLE_FILE_BUILD ? 1 * 1024 * 1024 * 1024 : 50 * 1024
|
dataUriLimit: SINGLE_FILE_BUILD ? 1 * 1024 * 1024 * 1024 : 50 * 1024
|
||||||
},
|
},
|
||||||
performance: {
|
|
||||||
// prefetch: {
|
|
||||||
// include(filename) {
|
|
||||||
// return filename.includes('mc-data') || filename.includes('mc-assets')
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
source: {
|
source: {
|
||||||
entry: {
|
entry: {
|
||||||
index: './src/index.ts',
|
index: './src/index.ts',
|
||||||
|
|
@ -161,7 +154,7 @@ const appConfig = defineConfig({
|
||||||
'process.platform': '"browser"',
|
'process.platform': '"browser"',
|
||||||
'process.env.GITHUB_URL':
|
'process.env.GITHUB_URL':
|
||||||
JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` || githubRepositoryFallback}`),
|
JSON.stringify(`https://github.com/${process.env.GITHUB_REPOSITORY || `${process.env.VERCEL_GIT_REPO_OWNER}/${process.env.VERCEL_GIT_REPO_SLUG}` || githubRepositoryFallback}`),
|
||||||
'process.env.ALWAYS_MINIMAL_SERVER_UI': JSON.stringify(process.env.ALWAYS_MINIMAL_SERVER_UI),
|
'process.env.DEPS_VERSIONS': JSON.stringify({}),
|
||||||
'process.env.RELEASE_TAG': JSON.stringify(releaseTag),
|
'process.env.RELEASE_TAG': JSON.stringify(releaseTag),
|
||||||
'process.env.RELEASE_LINK': JSON.stringify(releaseLink),
|
'process.env.RELEASE_LINK': JSON.stringify(releaseLink),
|
||||||
'process.env.RELEASE_CHANGELOG': JSON.stringify(releaseChangelog),
|
'process.env.RELEASE_CHANGELOG': JSON.stringify(releaseChangelog),
|
||||||
|
|
@ -197,7 +190,7 @@ const appConfig = defineConfig({
|
||||||
childProcess.execSync('tsx ./scripts/optimizeBlockCollisions.ts', { stdio: 'inherit' })
|
childProcess.execSync('tsx ./scripts/optimizeBlockCollisions.ts', { stdio: 'inherit' })
|
||||||
}
|
}
|
||||||
// childProcess.execSync(['tsx', './scripts/genLargeDataAliases.ts', ...(SINGLE_FILE_BUILD ? ['--compressed'] : [])].join(' '), { stdio: 'inherit' })
|
// childProcess.execSync(['tsx', './scripts/genLargeDataAliases.ts', ...(SINGLE_FILE_BUILD ? ['--compressed'] : [])].join(' '), { stdio: 'inherit' })
|
||||||
genLargeDataAliases(SINGLE_FILE_BUILD || process.env.ALWAYS_COMPRESS_LARGE_DATA === 'true')
|
genLargeDataAliases(SINGLE_FILE_BUILD)
|
||||||
fsExtra.copySync('./node_modules/mc-assets/dist/other-textures/latest/entity', './dist/textures/entity')
|
fsExtra.copySync('./node_modules/mc-assets/dist/other-textures/latest/entity', './dist/textures/entity')
|
||||||
fsExtra.copySync('./assets/background', './dist/background')
|
fsExtra.copySync('./assets/background', './dist/background')
|
||||||
fs.copyFileSync('./assets/favicon.png', './dist/favicon.png')
|
fs.copyFileSync('./assets/favicon.png', './dist/favicon.png')
|
||||||
|
|
@ -240,10 +233,6 @@ const appConfig = defineConfig({
|
||||||
prep()
|
prep()
|
||||||
})
|
})
|
||||||
build.onAfterBuild(async () => {
|
build.onAfterBuild(async () => {
|
||||||
if (fs.readdirSync('./assets/customTextures').length > 0) {
|
|
||||||
childProcess.execSync('tsx ./scripts/patchAssets.ts', { stdio: 'inherit' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SINGLE_FILE_BUILD) {
|
if (SINGLE_FILE_BUILD) {
|
||||||
// check that only index.html is in the dist/single folder
|
// check that only index.html is in the dist/single folder
|
||||||
const singleBuildFiles = fs.readdirSync('./dist/single')
|
const singleBuildFiles = fs.readdirSync('./dist/single')
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,7 @@ export const genLargeDataAliases = async (isCompressed: boolean) => {
|
||||||
|
|
||||||
let str = `${decoderCode}\nexport const importLargeData = async (mod: ${Object.keys(modules).map(x => `'${x}'`).join(' | ')}) => {\n`
|
let str = `${decoderCode}\nexport const importLargeData = async (mod: ${Object.keys(modules).map(x => `'${x}'`).join(' | ')}) => {\n`
|
||||||
for (const [module, { compressed, raw }] of Object.entries(modules)) {
|
for (const [module, { compressed, raw }] of Object.entries(modules)) {
|
||||||
const chunkName = module === 'mcData' ? 'mc-data' : 'mc-assets';
|
let importCode = `(await import('${isCompressed ? compressed : raw}')).default`;
|
||||||
let importCode = `(await import(/* webpackChunkName: "${chunkName}" */ '${isCompressed ? compressed : raw}')).default`;
|
|
||||||
if (isCompressed) {
|
if (isCompressed) {
|
||||||
importCode = `JSON.parse(decompressFromBase64(${importCode}))`
|
importCode = `JSON.parse(decompressFromBase64(${importCode}))`
|
||||||
}
|
}
|
||||||
|
|
@ -31,8 +30,6 @@ export const genLargeDataAliases = async (isCompressed: boolean) => {
|
||||||
const decoderCode = /* ts */ `
|
const decoderCode = /* ts */ `
|
||||||
import pako from 'pako';
|
import pako from 'pako';
|
||||||
|
|
||||||
globalThis.pako = { inflate: pako.inflate.bind(pako) }
|
|
||||||
|
|
||||||
function decompressFromBase64(input) {
|
function decompressFromBase64(input) {
|
||||||
console.time('decompressFromBase64')
|
console.time('decompressFromBase64')
|
||||||
// Decode the Base64 string
|
// Decode the Base64 string
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import { dirname } from 'node:path'
|
||||||
import supportedVersions from '../src/supportedVersions.mjs'
|
import supportedVersions from '../src/supportedVersions.mjs'
|
||||||
import { gzipSizeFromFileSync } from 'gzip-size'
|
import { gzipSizeFromFileSync } from 'gzip-size'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { default as _JsonOptimizer } from '../src/optimizeJson'
|
import {default as _JsonOptimizer} from '../src/optimizeJson'
|
||||||
import { gzipSync } from 'zlib'
|
import { gzipSync } from 'zlib';
|
||||||
import MinecraftData from 'minecraft-data'
|
import MinecraftData from 'minecraft-data'
|
||||||
import MCProtocol from 'minecraft-protocol'
|
import MCProtocol from 'minecraft-protocol'
|
||||||
|
|
||||||
|
|
@ -21,12 +21,12 @@ const require = Module.createRequire(import.meta.url)
|
||||||
|
|
||||||
const dataPaths = require('minecraft-data/minecraft-data/data/dataPaths.json')
|
const dataPaths = require('minecraft-data/minecraft-data/data/dataPaths.json')
|
||||||
|
|
||||||
function toMajor(version) {
|
function toMajor (version) {
|
||||||
const [a, b] = (version + '').split('.')
|
const [a, b] = (version + '').split('.')
|
||||||
return `${a}.${b}`
|
return `${a}.${b}`
|
||||||
}
|
}
|
||||||
|
|
||||||
let versions = {}
|
const versions = {}
|
||||||
const dataTypes = new Set()
|
const dataTypes = new Set()
|
||||||
|
|
||||||
for (const [version, dataSet] of Object.entries(dataPaths.pc)) {
|
for (const [version, dataSet] of Object.entries(dataPaths.pc)) {
|
||||||
|
|
@ -42,31 +42,6 @@ const versionToNumber = (ver) => {
|
||||||
return +`${x.padStart(2, '0')}${y.padStart(2, '0')}${z.padStart(2, '0')}`
|
return +`${x.padStart(2, '0')}${y.padStart(2, '0')}${z.padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version clipping support
|
|
||||||
const minVersion = process.env.MIN_MC_VERSION
|
|
||||||
const maxVersion = process.env.MAX_MC_VERSION
|
|
||||||
|
|
||||||
// Filter versions based on MIN_VERSION and MAX_VERSION if provided
|
|
||||||
if (minVersion || maxVersion) {
|
|
||||||
const filteredVersions = {}
|
|
||||||
const minVersionNum = minVersion ? versionToNumber(minVersion) : 0
|
|
||||||
const maxVersionNum = maxVersion ? versionToNumber(maxVersion) : Infinity
|
|
||||||
|
|
||||||
for (const [version, dataSet] of Object.entries(versions)) {
|
|
||||||
const versionNum = versionToNumber(version)
|
|
||||||
if (versionNum >= minVersionNum && versionNum <= maxVersionNum) {
|
|
||||||
filteredVersions[version] = dataSet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
versions = filteredVersions
|
|
||||||
|
|
||||||
console.log(`Version clipping applied: ${minVersion || 'none'} to ${maxVersion || 'none'}`)
|
|
||||||
console.log(`Processing ${Object.keys(versions).length} versions:`, Object.keys(versions).sort((a, b) => versionToNumber(a) - versionToNumber(b)))
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Bundling version range:', Object.keys(versions)[0], 'to', Object.keys(versions).at(-1))
|
|
||||||
|
|
||||||
// if not included here (even as {}) will not be bundled & accessible!
|
// if not included here (even as {}) will not be bundled & accessible!
|
||||||
// const compressedOutput = !!process.env.SINGLE_FILE_BUILD
|
// const compressedOutput = !!process.env.SINGLE_FILE_BUILD
|
||||||
const compressedOutput = true
|
const compressedOutput = true
|
||||||
|
|
@ -82,27 +57,22 @@ const dataTypeBundling2 = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const dataTypeBundling = {
|
const dataTypeBundling = {
|
||||||
language: process.env.SKIP_MC_DATA_LANGUAGE === 'true' ? {
|
language: {
|
||||||
raw: {}
|
|
||||||
} : {
|
|
||||||
ignoreRemoved: true,
|
ignoreRemoved: true,
|
||||||
ignoreChanges: true
|
ignoreChanges: true
|
||||||
},
|
},
|
||||||
blocks: {
|
blocks: {
|
||||||
arrKey: 'name',
|
arrKey: 'name',
|
||||||
processData(current, prev, _, version) {
|
processData (current, prev) {
|
||||||
for (const block of current) {
|
for (const block of current) {
|
||||||
const prevBlock = prev?.find(x => x.name === block.name)
|
|
||||||
if (block.transparent) {
|
if (block.transparent) {
|
||||||
const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/) || ['melon_block', 'lit_pumpkin', 'lit_redstone_ore', 'lit_furnace'].includes(block.name)
|
const forceOpaque = block.name.includes('shulker_box') || block.name.match(/^double_.+_slab\d?$/) || ['melon_block', 'lit_pumpkin', 'lit_redstone_ore', 'lit_furnace'].includes(block.name)
|
||||||
|
|
||||||
|
const prevBlock = prev?.find(x => x.name === block.name);
|
||||||
if (forceOpaque || (prevBlock && !prevBlock.transparent)) {
|
if (forceOpaque || (prevBlock && !prevBlock.transparent)) {
|
||||||
block.transparent = false
|
block.transparent = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (block.hardness === 0 && prevBlock && prevBlock.hardness > 0) {
|
|
||||||
block.hardness = prevBlock.hardness
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ignoreRemoved: true,
|
// ignoreRemoved: true,
|
||||||
|
|
@ -166,9 +136,7 @@ const dataTypeBundling = {
|
||||||
blockLoot: {
|
blockLoot: {
|
||||||
arrKey: 'block'
|
arrKey: 'block'
|
||||||
},
|
},
|
||||||
recipes: process.env.SKIP_MC_DATA_RECIPES === 'true' ? {
|
recipes: {
|
||||||
raw: {}
|
|
||||||
} : {
|
|
||||||
raw: true
|
raw: true
|
||||||
// processData: processRecipes
|
// processData: processRecipes
|
||||||
},
|
},
|
||||||
|
|
@ -182,7 +150,7 @@ const dataTypeBundling = {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRecipes(current, prev, getData, version) {
|
function processRecipes (current, prev, getData, version) {
|
||||||
// can require the same multiple times per different versions
|
// can require the same multiple times per different versions
|
||||||
if (current._proccessed) return
|
if (current._proccessed) return
|
||||||
const items = getData('items')
|
const items = getData('items')
|
||||||
|
|
@ -274,39 +242,30 @@ for (const [i, [version, dataSet]] of versionsArr.reverse().entries()) {
|
||||||
for (const [dataType, dataPath] of Object.entries(dataSet)) {
|
for (const [dataType, dataPath] of Object.entries(dataSet)) {
|
||||||
const config = dataTypeBundling[dataType]
|
const config = dataTypeBundling[dataType]
|
||||||
if (!config) continue
|
if (!config) continue
|
||||||
const ignoreCollisionShapes = dataType === 'blockCollisionShapes' && versionToNumber(version) >= versionToNumber('1.13')
|
if (dataType === 'blockCollisionShapes' && versionToNumber(version) >= versionToNumber('1.13')) {
|
||||||
|
// contents += ` get ${dataType} () { return window.globalGetCollisionShapes?.("${version}") },\n`
|
||||||
|
continue
|
||||||
|
}
|
||||||
let injectCode = ''
|
let injectCode = ''
|
||||||
const getRealData = (type) => {
|
const getData = (type) => {
|
||||||
const loc = `minecraft-data/data/${dataSet[type]}/`
|
const loc = `minecraft-data/data/${dataSet[type]}/`
|
||||||
const dataPathAbsolute = require.resolve(`minecraft-data/${loc}${type}`)
|
const dataPathAbsolute = require.resolve(`minecraft-data/${loc}${type}`)
|
||||||
// const data = fs.readFileSync(dataPathAbsolute, 'utf8')
|
// const data = fs.readFileSync(dataPathAbsolute, 'utf8')
|
||||||
const dataRaw = require(dataPathAbsolute)
|
const dataRaw = require(dataPathAbsolute)
|
||||||
return dataRaw
|
return dataRaw
|
||||||
}
|
}
|
||||||
const dataRaw = getRealData(dataType)
|
const dataRaw = getData(dataType)
|
||||||
let rawData = dataRaw
|
let rawData = dataRaw
|
||||||
if (config.raw) {
|
if (config.raw) {
|
||||||
rawDataVersions[dataType] ??= {}
|
rawDataVersions[dataType] ??= {}
|
||||||
rawDataVersions[dataType][version] = rawData
|
rawDataVersions[dataType][version] = rawData
|
||||||
if (config.raw === true) {
|
rawData = dataRaw
|
||||||
rawData = dataRaw
|
|
||||||
} else {
|
|
||||||
rawData = config.raw
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ignoreCollisionShapes && dataType === 'blockCollisionShapes') {
|
|
||||||
rawData = {
|
|
||||||
blocks: {},
|
|
||||||
shapes: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (!diffSources[dataType]) {
|
if (!diffSources[dataType]) {
|
||||||
diffSources[dataType] = new JsonOptimizer(config.arrKey, config.ignoreChanges, config.ignoreRemoved)
|
diffSources[dataType] = new JsonOptimizer(config.arrKey, config.ignoreChanges, config.ignoreRemoved)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
config.processData?.(dataRaw, previousData[dataType], getRealData, version)
|
config.processData?.(dataRaw, previousData[dataType], getData, version)
|
||||||
diffSources[dataType].recordDiff(version, dataRaw)
|
diffSources[dataType].recordDiff(version, dataRaw)
|
||||||
injectCode = `restoreDiff(sources, ${JSON.stringify(dataType)}, ${JSON.stringify(version)})`
|
injectCode = `restoreDiff(sources, ${JSON.stringify(dataType)}, ${JSON.stringify(version)})`
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -338,16 +297,16 @@ console.log('total size (mb)', totalSize / 1024 / 1024)
|
||||||
console.log(
|
console.log(
|
||||||
'size per data type (mb, %)',
|
'size per data type (mb, %)',
|
||||||
Object.fromEntries(Object.entries(sizePerDataType).map(([dataType, size]) => {
|
Object.fromEntries(Object.entries(sizePerDataType).map(([dataType, size]) => {
|
||||||
return [dataType, [size / 1024 / 1024, Math.round(size / totalSize * 100)]]
|
return [dataType, [size / 1024 / 1024, Math.round(size / totalSize * 100)]];
|
||||||
}).sort((a, b) => {
|
}).sort((a, b) => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
return b[1][1] - a[1][1]
|
return b[1][1] - a[1][1];
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
function compressToBase64(input) {
|
function compressToBase64(input) {
|
||||||
const buffer = gzipSync(input)
|
const buffer = gzipSync(input);
|
||||||
return buffer.toString('base64')
|
return buffer.toString('base64');
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = './generated/minecraft-data-optimized.json'
|
const filePath = './generated/minecraft-data-optimized.json'
|
||||||
|
|
@ -371,7 +330,6 @@ console.log('size', fs.lstatSync(filePath).size / 1000 / 1000, gzipSizeFromFileS
|
||||||
|
|
||||||
const { defaultVersion } = MCProtocol
|
const { defaultVersion } = MCProtocol
|
||||||
const data = MinecraftData(defaultVersion)
|
const data = MinecraftData(defaultVersion)
|
||||||
console.log('defaultVersion', defaultVersion, !!data)
|
|
||||||
const initialMcData = {
|
const initialMcData = {
|
||||||
[defaultVersion]: {
|
[defaultVersion]: {
|
||||||
version: data.version,
|
version: data.version,
|
||||||
|
|
|
||||||
|
|
@ -1,137 +0,0 @@
|
||||||
import blocksAtlas from 'mc-assets/dist/blocksAtlases.json'
|
|
||||||
import itemsAtlas from 'mc-assets/dist/itemsAtlases.json'
|
|
||||||
import * as fs from 'fs'
|
|
||||||
import * as path from 'path'
|
|
||||||
import sharp from 'sharp'
|
|
||||||
|
|
||||||
interface AtlasFile {
|
|
||||||
latest: {
|
|
||||||
suSv: number
|
|
||||||
tileSize: number
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
textures: {
|
|
||||||
[key: string]: {
|
|
||||||
u: number
|
|
||||||
v: number
|
|
||||||
su: number
|
|
||||||
sv: number
|
|
||||||
tileIndex: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function patchTextureAtlas(
|
|
||||||
atlasType: 'blocks' | 'items',
|
|
||||||
atlasData: AtlasFile,
|
|
||||||
customTexturesDir: string,
|
|
||||||
distDir: string
|
|
||||||
) {
|
|
||||||
// Check if custom textures directory exists and has files
|
|
||||||
if (!fs.existsSync(customTexturesDir) || fs.readdirSync(customTexturesDir).length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the latest atlas file
|
|
||||||
const atlasFiles = fs.readdirSync(distDir)
|
|
||||||
.filter(file => file.startsWith(`${atlasType}AtlasLatest`) && file.endsWith('.png'))
|
|
||||||
.sort()
|
|
||||||
|
|
||||||
if (atlasFiles.length === 0) {
|
|
||||||
console.log(`No ${atlasType}AtlasLatest.png found in ${distDir}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const latestAtlasFile = atlasFiles[atlasFiles.length - 1]
|
|
||||||
const atlasPath = path.join(distDir, latestAtlasFile)
|
|
||||||
console.log(`Patching ${atlasPath}`)
|
|
||||||
|
|
||||||
// Get atlas dimensions
|
|
||||||
const atlasMetadata = await sharp(atlasPath).metadata()
|
|
||||||
if (!atlasMetadata.width || !atlasMetadata.height) {
|
|
||||||
throw new Error(`Failed to get atlas dimensions for ${atlasPath}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process each custom texture
|
|
||||||
const customTextureFiles = fs.readdirSync(customTexturesDir)
|
|
||||||
.filter(file => file.endsWith('.png'))
|
|
||||||
|
|
||||||
if (customTextureFiles.length === 0) return
|
|
||||||
|
|
||||||
// Prepare composite operations
|
|
||||||
const composites: sharp.OverlayOptions[] = []
|
|
||||||
|
|
||||||
for (const textureFile of customTextureFiles) {
|
|
||||||
const textureName = path.basename(textureFile, '.png')
|
|
||||||
|
|
||||||
if (atlasData.latest.textures[textureName]) {
|
|
||||||
const textureData = atlasData.latest.textures[textureName]
|
|
||||||
const customTexturePath = path.join(customTexturesDir, textureFile)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Convert UV coordinates to pixel coordinates
|
|
||||||
const x = Math.round(textureData.u * atlasMetadata.width)
|
|
||||||
const y = Math.round(textureData.v * atlasMetadata.height)
|
|
||||||
const width = Math.round((textureData.su ?? atlasData.latest.suSv) * atlasMetadata.width)
|
|
||||||
const height = Math.round((textureData.sv ?? atlasData.latest.suSv) * atlasMetadata.height)
|
|
||||||
|
|
||||||
// Resize custom texture to match atlas dimensions and add to composite operations
|
|
||||||
const resizedTextureBuffer = await sharp(customTexturePath)
|
|
||||||
.resize(width, height, {
|
|
||||||
fit: 'fill',
|
|
||||||
kernel: 'nearest' // Preserve pixel art quality
|
|
||||||
})
|
|
||||||
.png()
|
|
||||||
.toBuffer()
|
|
||||||
|
|
||||||
composites.push({
|
|
||||||
input: resizedTextureBuffer,
|
|
||||||
left: x,
|
|
||||||
top: y,
|
|
||||||
blend: 'over'
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(`Prepared ${textureName} at (${x}, ${y}) with size (${width}, ${height})`)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to prepare ${textureName}:`, error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn(`Texture ${textureName} not found in ${atlasType} atlas`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (composites.length > 0) {
|
|
||||||
// Apply all patches at once using Sharp's composite
|
|
||||||
await sharp(atlasPath)
|
|
||||||
.composite(composites)
|
|
||||||
.png()
|
|
||||||
.toFile(atlasPath + '.tmp')
|
|
||||||
|
|
||||||
// Replace original with patched version
|
|
||||||
fs.renameSync(atlasPath + '.tmp', atlasPath)
|
|
||||||
console.log(`Saved patched ${atlasType} atlas to ${atlasPath}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const customBlocksDir = './assets/customTextures/blocks'
|
|
||||||
const customItemsDir = './assets/customTextures/items'
|
|
||||||
const distDir = './dist/static/image'
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Patch blocks atlas
|
|
||||||
await patchTextureAtlas('blocks', blocksAtlas as unknown as AtlasFile, customBlocksDir, distDir)
|
|
||||||
|
|
||||||
// Patch items atlas
|
|
||||||
await patchTextureAtlas('items', itemsAtlas as unknown as AtlasFile, customItemsDir, distDir)
|
|
||||||
|
|
||||||
console.log('Texture atlas patching completed!')
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to patch texture atlases:', error)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run the script
|
|
||||||
main()
|
|
||||||
|
|
@ -35,7 +35,7 @@ export type AppConfig = {
|
||||||
// defaultVersion?: string
|
// defaultVersion?: string
|
||||||
peerJsServer?: string
|
peerJsServer?: string
|
||||||
peerJsServerFallback?: string
|
peerJsServerFallback?: string
|
||||||
promoteServers?: Array<{ ip, description, name?, version?, }>
|
promoteServers?: Array<{ ip, description, version? }>
|
||||||
mapsProvider?: string
|
mapsProvider?: string
|
||||||
|
|
||||||
appParams?: Record<string, any> // query string params
|
appParams?: Record<string, any> // query string params
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ export type AppQsParams = {
|
||||||
username?: string
|
username?: string
|
||||||
lockConnect?: string
|
lockConnect?: string
|
||||||
autoConnect?: string
|
autoConnect?: string
|
||||||
alwaysReconnect?: string
|
|
||||||
// googledrive.ts params
|
// googledrive.ts params
|
||||||
state?: string
|
state?: string
|
||||||
// ServersListProvider.tsx params
|
// ServersListProvider.tsx params
|
||||||
|
|
@ -47,7 +46,6 @@ export type AppQsParams = {
|
||||||
connectText?: string
|
connectText?: string
|
||||||
freezeSettings?: string
|
freezeSettings?: string
|
||||||
testIosCrash?: string
|
testIosCrash?: string
|
||||||
addPing?: string
|
|
||||||
|
|
||||||
// Replay params
|
// Replay params
|
||||||
replayFilter?: string
|
replayFilter?: string
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,6 @@ import { options } from './optionsStorage'
|
||||||
import { ResourcesManager, ResourcesManagerTransferred } from './resourcesManager'
|
import { ResourcesManager, ResourcesManagerTransferred } from './resourcesManager'
|
||||||
import { watchOptionsAfterWorldViewInit } from './watchOptions'
|
import { watchOptionsAfterWorldViewInit } from './watchOptions'
|
||||||
import { loadMinecraftData } from './connect'
|
import { loadMinecraftData } from './connect'
|
||||||
import { reloadChunks } from './utils'
|
|
||||||
import { displayClientChat } from './botUtils'
|
|
||||||
|
|
||||||
export interface RendererReactiveState {
|
export interface RendererReactiveState {
|
||||||
world: {
|
world: {
|
||||||
|
|
@ -199,13 +197,7 @@ export class AppViewer {
|
||||||
this.currentDisplay = 'world'
|
this.currentDisplay = 'world'
|
||||||
const startPosition = bot.entity?.position ?? new Vec3(0, 64, 0)
|
const startPosition = bot.entity?.position ?? new Vec3(0, 64, 0)
|
||||||
this.worldView = new WorldDataEmitter(world, renderDistance, startPosition)
|
this.worldView = new WorldDataEmitter(world, renderDistance, startPosition)
|
||||||
this.worldView.panicChunksReload = () => {
|
this.worldView.worldRendererConfig = this.inWorldRenderingConfig
|
||||||
if (!options.experimentalClientSelfReload) return
|
|
||||||
if (process.env.NODE_ENV === 'development') {
|
|
||||||
displayClientChat(`[client] client panicked due to too long loading time. Soft reloading chunks...`)
|
|
||||||
}
|
|
||||||
void reloadChunks()
|
|
||||||
}
|
|
||||||
window.worldView = this.worldView
|
window.worldView = this.worldView
|
||||||
watchOptionsAfterWorldViewInit(this.worldView)
|
watchOptionsAfterWorldViewInit(this.worldView)
|
||||||
this.appConfigUdpate()
|
this.appConfigUdpate()
|
||||||
|
|
@ -269,6 +261,7 @@ export class AppViewer {
|
||||||
if (cleanState) {
|
if (cleanState) {
|
||||||
this.currentState = undefined
|
this.currentState = undefined
|
||||||
this.currentDisplay = null
|
this.currentDisplay = null
|
||||||
|
this.worldView?.destroy()
|
||||||
this.worldView = undefined
|
this.worldView = undefined
|
||||||
}
|
}
|
||||||
if (this.backend) {
|
if (this.backend) {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,7 @@ let audioContext: AudioContext
|
||||||
const sounds: Record<string, any> = {}
|
const sounds: Record<string, any> = {}
|
||||||
|
|
||||||
// Track currently playing sounds and their gain nodes
|
// Track currently playing sounds and their gain nodes
|
||||||
const activeSounds: Array<{
|
const activeSounds: Array<{ source: AudioBufferSourceNode; gainNode: GainNode; volumeMultiplier: number }> = []
|
||||||
source: AudioBufferSourceNode;
|
|
||||||
gainNode: GainNode;
|
|
||||||
volumeMultiplier: number;
|
|
||||||
isMusic: boolean;
|
|
||||||
}> = []
|
|
||||||
window.activeSounds = activeSounds
|
window.activeSounds = activeSounds
|
||||||
|
|
||||||
// load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded
|
// load as many resources on page load as possible instead on demand as user can disable internet connection after he thinks the page is loaded
|
||||||
|
|
@ -48,7 +43,7 @@ export async function loadSound (path: string, contents = path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = options.remoteSoundsLoadTimeout, loop = false, isMusic = false) => {
|
export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = 500) => {
|
||||||
const soundBuffer = sounds[url]
|
const soundBuffer = sounds[url]
|
||||||
if (!soundBuffer) {
|
if (!soundBuffer) {
|
||||||
const start = Date.now()
|
const start = Date.now()
|
||||||
|
|
@ -56,11 +51,11 @@ export const loadOrPlaySound = async (url, soundVolume = 1, loadTimeout = option
|
||||||
if (cancelled || Date.now() - start > loadTimeout) return
|
if (cancelled || Date.now() - start > loadTimeout) return
|
||||||
}
|
}
|
||||||
|
|
||||||
return playSound(url, soundVolume, loop, isMusic)
|
return playSound(url, soundVolume)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function playSound (url, soundVolume = 1, loop = false, isMusic = false) {
|
export async function playSound (url, soundVolume = 1) {
|
||||||
const volume = soundVolume * (options.volume / 100) * (isMusic ? options.musicVolume / 100 : 1)
|
const volume = soundVolume * (options.volume / 100)
|
||||||
|
|
||||||
if (!volume) return
|
if (!volume) return
|
||||||
|
|
||||||
|
|
@ -80,14 +75,13 @@ export async function playSound (url, soundVolume = 1, loop = false, isMusic = f
|
||||||
const gainNode = audioContext.createGain()
|
const gainNode = audioContext.createGain()
|
||||||
const source = audioContext.createBufferSource()
|
const source = audioContext.createBufferSource()
|
||||||
source.buffer = soundBuffer
|
source.buffer = soundBuffer
|
||||||
source.loop = loop
|
|
||||||
source.connect(gainNode)
|
source.connect(gainNode)
|
||||||
gainNode.connect(audioContext.destination)
|
gainNode.connect(audioContext.destination)
|
||||||
gainNode.gain.value = volume
|
gainNode.gain.value = volume
|
||||||
source.start(0)
|
source.start(0)
|
||||||
|
|
||||||
// Add to active sounds
|
// Add to active sounds
|
||||||
activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume, isMusic })
|
activeSounds.push({ source, gainNode, volumeMultiplier: soundVolume })
|
||||||
|
|
||||||
const callbacks = [] as Array<() => void>
|
const callbacks = [] as Array<() => void>
|
||||||
source.onended = () => {
|
source.onended = () => {
|
||||||
|
|
@ -105,17 +99,6 @@ export async function playSound (url, soundVolume = 1, loop = false, isMusic = f
|
||||||
onEnded (callback: () => void) {
|
onEnded (callback: () => void) {
|
||||||
callbacks.push(callback)
|
callbacks.push(callback)
|
||||||
},
|
},
|
||||||
stop () {
|
|
||||||
try {
|
|
||||||
source.stop()
|
|
||||||
// Remove from active sounds
|
|
||||||
const index = activeSounds.findIndex(s => s.source === source)
|
|
||||||
if (index !== -1) activeSounds.splice(index, 1)
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Failed to stop sound:', err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
gainNode,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,24 +113,11 @@ export function stopAllSounds () {
|
||||||
activeSounds.length = 0
|
activeSounds.length = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopSound (url: string) {
|
export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number) {
|
||||||
const soundIndex = activeSounds.findIndex(s => s.source.buffer === sounds[url])
|
|
||||||
if (soundIndex !== -1) {
|
|
||||||
const { source } = activeSounds[soundIndex]
|
|
||||||
try {
|
|
||||||
source.stop()
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Failed to stop sound:', err)
|
|
||||||
}
|
|
||||||
activeSounds.splice(soundIndex, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusicVolume: number) {
|
|
||||||
const normalizedVolume = newVolume / 100
|
const normalizedVolume = newVolume / 100
|
||||||
for (const { gainNode, volumeMultiplier, isMusic } of activeSounds) {
|
for (const { gainNode, volumeMultiplier } of activeSounds) {
|
||||||
try {
|
try {
|
||||||
gainNode.gain.value = normalizedVolume * volumeMultiplier * (isMusic ? newMusicVolume / 100 : 1)
|
gainNode.gain.value = normalizedVolume * volumeMultiplier
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn('Failed to change sound volume:', err)
|
console.warn('Failed to change sound volume:', err)
|
||||||
}
|
}
|
||||||
|
|
@ -155,9 +125,5 @@ export function changeVolumeOfCurrentlyPlayingSounds (newVolume: number, newMusi
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribeKey(options, 'volume', () => {
|
subscribeKey(options, 'volume', () => {
|
||||||
changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume)
|
changeVolumeOfCurrentlyPlayingSounds(options.volume)
|
||||||
})
|
|
||||||
|
|
||||||
subscribeKey(options, 'musicVolume', () => {
|
|
||||||
changeVolumeOfCurrentlyPlayingSounds(options.volume, options.musicVolume)
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -118,14 +118,6 @@ export const formatMessage = (message: MessageInput, mcData: IndexedData = globa
|
||||||
return msglist
|
return msglist
|
||||||
}
|
}
|
||||||
|
|
||||||
export const messageToString = (message: MessageInput | string) => {
|
|
||||||
if (typeof message === 'string') {
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
const msglist = formatMessage(message)
|
|
||||||
return msglist.map(msg => msg.text).join('')
|
|
||||||
}
|
|
||||||
|
|
||||||
const blockToItemRemaps = {
|
const blockToItemRemaps = {
|
||||||
water: 'water_bucket',
|
water: 'water_bucket',
|
||||||
lava: 'lava_bucket',
|
lava: 'lava_bucket',
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import MinecraftData from 'minecraft-data'
|
import MinecraftData from 'minecraft-data'
|
||||||
import PrismarineBlock from 'prismarine-block'
|
import PrismarineBlock from 'prismarine-block'
|
||||||
import PrismarineItem from 'prismarine-item'
|
import PrismarineItem from 'prismarine-item'
|
||||||
|
import pathfinder from 'mineflayer-pathfinder'
|
||||||
import { miscUiState } from './globalState'
|
import { miscUiState } from './globalState'
|
||||||
import supportedVersions from './supportedVersions.mjs'
|
import supportedVersions from './supportedVersions.mjs'
|
||||||
import { options } from './optionsStorage'
|
import { options } from './optionsStorage'
|
||||||
|
|
@ -64,6 +65,7 @@ export const loadMinecraftData = async (version: string) => {
|
||||||
window.PrismarineItem = PrismarineItem(mcData.version.minecraftVersion!)
|
window.PrismarineItem = PrismarineItem(mcData.version.minecraftVersion!)
|
||||||
window.loadedData = mcData
|
window.loadedData = mcData
|
||||||
window.mcData = mcData
|
window.mcData = mcData
|
||||||
|
window.pathfinder = pathfinder
|
||||||
miscUiState.loadedDataVersion = version
|
miscUiState.loadedDataVersion = version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -818,11 +818,6 @@ export const f3Keybinds: Array<{
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
export const reloadChunksAction = () => {
|
|
||||||
const action = f3Keybinds.find(f3Keybind => f3Keybind.key === 'KeyA')
|
|
||||||
void action!.action()
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (!isGameActive(false)) return
|
if (!isGameActive(false)) return
|
||||||
if (contro.pressedKeys.has('F3')) {
|
if (contro.pressedKeys.has('F3')) {
|
||||||
|
|
@ -992,17 +987,14 @@ export function updateBinds (commands: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onF3LongPress = async () => {
|
export const onF3LongPress = async () => {
|
||||||
const actions = f3Keybinds.filter(f3Keybind => {
|
const select = await showOptionsModal('', f3Keybinds.filter(f3Keybind => {
|
||||||
return f3Keybind.mobileTitle && (f3Keybind.enabled?.() ?? true)
|
return f3Keybind.mobileTitle && (f3Keybind.enabled?.() ?? true)
|
||||||
})
|
}).map(f3Keybind => {
|
||||||
const actionNames = actions.map(f3Keybind => {
|
|
||||||
return `${f3Keybind.mobileTitle}${f3Keybind.key ? ` (F3+${f3Keybind.key})` : ''}`
|
return `${f3Keybind.mobileTitle}${f3Keybind.key ? ` (F3+${f3Keybind.key})` : ''}`
|
||||||
})
|
}))
|
||||||
const select = await showOptionsModal('', actionNames)
|
|
||||||
if (!select) return
|
if (!select) return
|
||||||
const actionIndex = actionNames.indexOf(select)
|
const f3Keybind = f3Keybinds.find(f3Keybind => f3Keybind.mobileTitle === select)
|
||||||
const f3Keybind = actions[actionIndex]!
|
if (f3Keybind) void f3Keybind.action()
|
||||||
void f3Keybind.action()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const handleMobileButtonCustomAction = (action: CustomAction) => {
|
export const handleMobileButtonCustomAction = (action: CustomAction) => {
|
||||||
|
|
|
||||||
|
|
@ -1,106 +0,0 @@
|
||||||
import { proxy } from 'valtio'
|
|
||||||
|
|
||||||
export const ideState = proxy({
|
|
||||||
id: '',
|
|
||||||
contents: '',
|
|
||||||
line: 0,
|
|
||||||
column: 0,
|
|
||||||
language: 'typescript',
|
|
||||||
title: '',
|
|
||||||
})
|
|
||||||
globalThis.ideState = ideState
|
|
||||||
|
|
||||||
export const registerIdeChannels = () => {
|
|
||||||
registerIdeOpenChannel()
|
|
||||||
registerIdeSaveChannel()
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerIdeOpenChannel = () => {
|
|
||||||
const CHANNEL_NAME = 'minecraft-web-client:ide-open'
|
|
||||||
|
|
||||||
const packetStructure = [
|
|
||||||
'container',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'id',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'language',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'contents',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'line',
|
|
||||||
type: 'i32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'column',
|
|
||||||
type: 'i32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
bot._client.registerChannel(CHANNEL_NAME, packetStructure, true)
|
|
||||||
|
|
||||||
bot._client.on(CHANNEL_NAME as any, (data) => {
|
|
||||||
const { id, language, contents, line, column, title } = data
|
|
||||||
|
|
||||||
ideState.contents = contents
|
|
||||||
ideState.line = line
|
|
||||||
ideState.column = column
|
|
||||||
ideState.id = id
|
|
||||||
ideState.language = language || 'typescript'
|
|
||||||
ideState.title = title
|
|
||||||
})
|
|
||||||
|
|
||||||
console.debug(`registered custom channel ${CHANNEL_NAME} channel`)
|
|
||||||
}
|
|
||||||
const IDE_SAVE_CHANNEL_NAME = 'minecraft-web-client:ide-save'
|
|
||||||
const registerIdeSaveChannel = () => {
|
|
||||||
|
|
||||||
const packetStructure = [
|
|
||||||
'container',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'id',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'contents',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'language',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'line',
|
|
||||||
type: 'i32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'column',
|
|
||||||
type: 'i32'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
]
|
|
||||||
bot._client.registerChannel(IDE_SAVE_CHANNEL_NAME, packetStructure, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const saveIde = () => {
|
|
||||||
bot._client.writeChannel(IDE_SAVE_CHANNEL_NAME, {
|
|
||||||
id: ideState.id,
|
|
||||||
contents: ideState.contents,
|
|
||||||
language: ideState.language,
|
|
||||||
// todo: reflect updated
|
|
||||||
line: ideState.line,
|
|
||||||
column: ideState.column,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -2,20 +2,19 @@ import PItem from 'prismarine-item'
|
||||||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
||||||
import { options } from './optionsStorage'
|
import { options } from './optionsStorage'
|
||||||
import { jeiCustomCategories } from './inventoryWindows'
|
import { jeiCustomCategories } from './inventoryWindows'
|
||||||
import { registerIdeChannels } from './core/ideChannels'
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
customEvents.on('mineflayerBotCreated', async () => {
|
customEvents.on('mineflayerBotCreated', async () => {
|
||||||
if (!options.customChannels) return
|
if (!options.customChannels) return
|
||||||
bot.once('login', () => {
|
await new Promise(resolve => {
|
||||||
registerBlockModelsChannel()
|
bot.once('login', () => {
|
||||||
registerMediaChannels()
|
resolve(true)
|
||||||
registerSectionAnimationChannels()
|
})
|
||||||
registeredJeiChannel()
|
|
||||||
registerBlockInteractionsCustomizationChannel()
|
|
||||||
registerWaypointChannels()
|
|
||||||
registerIdeChannels()
|
|
||||||
})
|
})
|
||||||
|
registerBlockModelsChannel()
|
||||||
|
registerMediaChannels()
|
||||||
|
registerSectionAnimationChannels()
|
||||||
|
registeredJeiChannel()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,95 +32,6 @@ const registerChannel = (channelName: string, packetStructure: any[], handler: (
|
||||||
console.debug(`registered custom channel ${channelName} channel`)
|
console.debug(`registered custom channel ${channelName} channel`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const registerBlockInteractionsCustomizationChannel = () => {
|
|
||||||
const CHANNEL_NAME = 'minecraft-web-client:block-interactions-customization'
|
|
||||||
const packetStructure = [
|
|
||||||
'container',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'newConfiguration',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
registerChannel(CHANNEL_NAME, packetStructure, (data) => {
|
|
||||||
const config = JSON.parse(data.newConfiguration)
|
|
||||||
bot.mouse.setConfigFromPacket(config)
|
|
||||||
}, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerWaypointChannels = () => {
|
|
||||||
const packetStructure = [
|
|
||||||
'container',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'id',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'x',
|
|
||||||
type: 'f32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'y',
|
|
||||||
type: 'f32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'z',
|
|
||||||
type: 'f32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'minDistance',
|
|
||||||
type: 'i32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'label',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'color',
|
|
||||||
type: 'i32'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'metadataJson',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
registerChannel('minecraft-web-client:waypoint-add', packetStructure, (data) => {
|
|
||||||
// Parse metadata if provided
|
|
||||||
let metadata: any = {}
|
|
||||||
if (data.metadataJson && data.metadataJson.trim() !== '') {
|
|
||||||
try {
|
|
||||||
metadata = JSON.parse(data.metadataJson)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to parse waypoint metadataJson:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getThreeJsRendererMethods()?.addWaypoint(data.id, data.x, data.y, data.z, {
|
|
||||||
minDistance: data.minDistance,
|
|
||||||
label: data.label || undefined,
|
|
||||||
color: data.color || undefined,
|
|
||||||
metadata
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
registerChannel('minecraft-web-client:waypoint-delete', [
|
|
||||||
'container',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'id',
|
|
||||||
type: ['pstring', { countType: 'i16' }]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
], (data) => {
|
|
||||||
getThreeJsRendererMethods()?.removeWaypoint(data.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const registerBlockModelsChannel = () => {
|
const registerBlockModelsChannel = () => {
|
||||||
const CHANNEL_NAME = 'minecraft-web-client:blockmodels'
|
const CHANNEL_NAME = 'minecraft-web-client:blockmodels'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
//@ts-check
|
|
||||||
import * as nbt from 'prismarine-nbt'
|
|
||||||
import { options } from './optionsStorage'
|
import { options } from './optionsStorage'
|
||||||
|
|
||||||
|
//@ts-check
|
||||||
const { EventEmitter } = require('events')
|
const { EventEmitter } = require('events')
|
||||||
const debug = require('debug')('minecraft-protocol')
|
const debug = require('debug')('minecraft-protocol')
|
||||||
const states = require('minecraft-protocol/src/states')
|
const states = require('minecraft-protocol/src/states')
|
||||||
|
|
@ -52,20 +51,8 @@ class CustomChannelClient extends EventEmitter {
|
||||||
this.emit('state', newProperty, oldProperty)
|
this.emit('state', newProperty, oldProperty)
|
||||||
}
|
}
|
||||||
|
|
||||||
end(endReason, fullReason) {
|
end(reason) {
|
||||||
// eslint-disable-next-line unicorn/no-this-assignment
|
this._endReason = reason
|
||||||
const client = this
|
|
||||||
if (client.state === states.PLAY) {
|
|
||||||
fullReason ||= loadedData.supportFeature('chatPacketsUseNbtComponents')
|
|
||||||
? nbt.comp({ text: nbt.string(endReason) })
|
|
||||||
: JSON.stringify({ text: endReason })
|
|
||||||
client.write('kick_disconnect', { reason: fullReason })
|
|
||||||
} else if (client.state === states.LOGIN) {
|
|
||||||
fullReason ||= JSON.stringify({ text: endReason })
|
|
||||||
client.write('disconnect', { reason: fullReason })
|
|
||||||
}
|
|
||||||
|
|
||||||
this._endReason = endReason
|
|
||||||
this.emit('end', this._endReason) // still emits on server side only, doesn't send anything to our client
|
this.emit('end', this._endReason) // still emits on server side only, doesn't send anything to our client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
46
src/dayCycle.ts
Normal file
46
src/dayCycle.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { options } from './optionsStorage'
|
||||||
|
import { assertDefined } from './utils'
|
||||||
|
import { updateBackground } from './water'
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
const timeUpdated = () => {
|
||||||
|
// 0 morning
|
||||||
|
const dayTotal = 24_000
|
||||||
|
const evening = 11_500
|
||||||
|
const night = 13_500
|
||||||
|
const morningStart = 23_000
|
||||||
|
const morningEnd = 23_961
|
||||||
|
const timeProgress = options.dayCycle ? bot.time.timeOfDay : 0
|
||||||
|
|
||||||
|
// todo check actual colors
|
||||||
|
const dayColorRainy = { r: 111 / 255, g: 156 / 255, b: 236 / 255 }
|
||||||
|
// todo yes, we should make animations (and rain)
|
||||||
|
// eslint-disable-next-line unicorn/numeric-separators-style
|
||||||
|
const dayColor = bot.isRaining ? dayColorRainy : { r: 0.6784313725490196, g: 0.8470588235294118, b: 0.9019607843137255 } // lightblue
|
||||||
|
// let newColor = dayColor
|
||||||
|
let int = 1
|
||||||
|
if (timeProgress < evening) {
|
||||||
|
// stay dayily
|
||||||
|
} else if (timeProgress < night) {
|
||||||
|
const progressNorm = timeProgress - evening
|
||||||
|
const progressMax = night - evening
|
||||||
|
int = 1 - progressNorm / progressMax
|
||||||
|
} else if (timeProgress < morningStart) {
|
||||||
|
int = 0
|
||||||
|
} else if (timeProgress < morningEnd) {
|
||||||
|
const progressNorm = timeProgress - morningStart
|
||||||
|
const progressMax = night - morningEnd
|
||||||
|
int = progressNorm / progressMax
|
||||||
|
}
|
||||||
|
// todo need to think wisely how to set these values & also move directional light around!
|
||||||
|
const colorInt = Math.max(int, 0.1)
|
||||||
|
updateBackground({ r: dayColor.r * colorInt, g: dayColor.g * colorInt, b: dayColor.b * colorInt })
|
||||||
|
// if (!options.newVersionsLighting && bot.supportFeature('blockStateId')) {
|
||||||
|
// appViewer.playerState.reactive.ambientLight = Math.max(int, 0.25)
|
||||||
|
// appViewer.playerState.reactive.directionalLight = Math.min(int, 0.45)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.on('time', timeUpdated)
|
||||||
|
timeUpdated()
|
||||||
|
}
|
||||||
|
|
@ -16,8 +16,7 @@ export const defaultOptions = {
|
||||||
chatOpacityOpened: 100,
|
chatOpacityOpened: 100,
|
||||||
messagesLimit: 200,
|
messagesLimit: 200,
|
||||||
volume: 50,
|
volume: 50,
|
||||||
enableMusic: true,
|
enableMusic: false,
|
||||||
musicVolume: 50,
|
|
||||||
// fov: 70,
|
// fov: 70,
|
||||||
fov: 75,
|
fov: 75,
|
||||||
defaultPerspective: 'first_person' as 'first_person' | 'third_person_back' | 'third_person_front',
|
defaultPerspective: 'first_person' as 'first_person' | 'third_person_back' | 'third_person_front',
|
||||||
|
|
@ -37,12 +36,11 @@ export const defaultOptions = {
|
||||||
/** @unstable */
|
/** @unstable */
|
||||||
debugLogNotFrequentPackets: false,
|
debugLogNotFrequentPackets: false,
|
||||||
unimplementedContainers: false,
|
unimplementedContainers: false,
|
||||||
dayCycleAndLighting: true,
|
dayCycle: true,
|
||||||
loadPlayerSkins: true,
|
loadPlayerSkins: true,
|
||||||
renderEars: true,
|
renderEars: true,
|
||||||
lowMemoryMode: false,
|
lowMemoryMode: false,
|
||||||
starfieldRendering: true,
|
starfieldRendering: true,
|
||||||
defaultSkybox: true,
|
|
||||||
enabledResourcepack: null as string | null,
|
enabledResourcepack: null as string | null,
|
||||||
useVersionsTextures: 'latest',
|
useVersionsTextures: 'latest',
|
||||||
serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never',
|
serverResourcePacks: 'prompt' as 'prompt' | 'always' | 'never',
|
||||||
|
|
@ -79,17 +77,13 @@ export const defaultOptions = {
|
||||||
frameLimit: false as number | false,
|
frameLimit: false as number | false,
|
||||||
alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null,
|
alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null,
|
||||||
alwaysShowMobileControls: false,
|
alwaysShowMobileControls: false,
|
||||||
excludeCommunicationDebugEvents: [] as string[],
|
excludeCommunicationDebugEvents: [],
|
||||||
preventDevReloadWhilePlaying: false,
|
preventDevReloadWhilePlaying: false,
|
||||||
numWorkers: 4,
|
numWorkers: 4,
|
||||||
localServerOptions: {
|
localServerOptions: {
|
||||||
gameMode: 1
|
gameMode: 1
|
||||||
} as any,
|
} as any,
|
||||||
saveLoginPassword: 'prompt' as 'prompt' | 'never' | 'always',
|
|
||||||
preferLoadReadonly: false,
|
preferLoadReadonly: false,
|
||||||
experimentalClientSelfReload: false,
|
|
||||||
remoteSoundsSupport: false,
|
|
||||||
remoteSoundsLoadTimeout: 500,
|
|
||||||
disableLoadPrompts: false,
|
disableLoadPrompts: false,
|
||||||
guestUsername: 'guest',
|
guestUsername: 'guest',
|
||||||
askGuestName: true,
|
askGuestName: true,
|
||||||
|
|
@ -98,8 +92,16 @@ export const defaultOptions = {
|
||||||
showCursorBlockInSpectator: false,
|
showCursorBlockInSpectator: false,
|
||||||
renderEntities: true,
|
renderEntities: true,
|
||||||
smoothLighting: true,
|
smoothLighting: true,
|
||||||
newVersionsLighting: false,
|
|
||||||
chatSelect: true,
|
chatSelect: true,
|
||||||
|
// experimentalLighting: IS_BETA_TESTER,
|
||||||
|
experimentalLightingV1: false,
|
||||||
|
/**
|
||||||
|
* Controls how lighting is calculated and rendered:
|
||||||
|
* - 'always-client': Always use client-side lighting engine for all light calculations
|
||||||
|
* - 'prefer-server': Use server lighting data when available, fallback to client-side calculations
|
||||||
|
* - 'always-server': Only use lighting data from the server, disable client-side calculations
|
||||||
|
*/
|
||||||
|
lightingStrategy: 'prefer-server' as 'always-client' | 'prefer-server' | 'always-server',
|
||||||
autoJump: 'auto' as 'auto' | 'always' | 'never',
|
autoJump: 'auto' as 'auto' | 'always' | 'never',
|
||||||
autoParkour: false,
|
autoParkour: false,
|
||||||
vrSupport: true, // doesn't directly affect the VR mode, should only disable the button which is annoying to android users
|
vrSupport: true, // doesn't directly affect the VR mode, should only disable the button which is annoying to android users
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,6 @@ import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree'
|
||||||
import { enable, disable, enabled } from 'debug'
|
import { enable, disable, enabled } from 'debug'
|
||||||
import { Vec3 } from 'vec3'
|
import { Vec3 } from 'vec3'
|
||||||
|
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
|
||||||
window.debugServerPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toClient.types).map(name => {
|
|
||||||
name = name.replace('packet_', '')
|
|
||||||
return [name, name]
|
|
||||||
}))
|
|
||||||
window.debugClientPacketNames = Object.fromEntries(Object.keys(loadedData.protocol.play.toServer.types).map(name => {
|
|
||||||
name = name.replace('packet_', '')
|
|
||||||
return [name, name]
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
window.Vec3 = Vec3
|
window.Vec3 = Vec3
|
||||||
window.cursorBlockRel = (x = 0, y = 0, z = 0) => {
|
window.cursorBlockRel = (x = 0, y = 0, z = 0) => {
|
||||||
const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z)
|
const newPos = bot.blockAtCursor(5)?.position.offset(x, y, z)
|
||||||
|
|
@ -266,6 +255,7 @@ function connectWebSocket () {
|
||||||
|
|
||||||
const wsUrl = getWebSocketUrl()
|
const wsUrl = getWebSocketUrl()
|
||||||
if (!wsUrl) {
|
if (!wsUrl) {
|
||||||
|
console.log('WebSocket server not configured')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,6 @@ export const getFixedFilesize = (bytes: number) => {
|
||||||
return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isInterestedInDownload = () => {
|
|
||||||
const { map, texturepack, replayFileUrl } = appQueryParams
|
|
||||||
const { mapDir } = appQueryParamsArray
|
|
||||||
return !!map || !!texturepack || !!replayFileUrl || !!mapDir
|
|
||||||
}
|
|
||||||
|
|
||||||
const inner = async () => {
|
const inner = async () => {
|
||||||
const { map, texturepack, replayFileUrl } = appQueryParams
|
const { map, texturepack, replayFileUrl } = appQueryParams
|
||||||
const { mapDir } = appQueryParamsArray
|
const { mapDir } = appQueryParamsArray
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import fs from 'fs'
|
||||||
import * as nbt from 'prismarine-nbt'
|
import * as nbt from 'prismarine-nbt'
|
||||||
import RegionFile from 'prismarine-provider-anvil/src/region'
|
import RegionFile from 'prismarine-provider-anvil/src/region'
|
||||||
import { versions } from 'minecraft-data'
|
import { versions } from 'minecraft-data'
|
||||||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
|
||||||
import { openWorldDirectory, openWorldZip } from './browserfs'
|
import { openWorldDirectory, openWorldZip } from './browserfs'
|
||||||
import { isGameActive } from './globalState'
|
import { isGameActive } from './globalState'
|
||||||
import { showNotification } from './react/NotificationProvider'
|
import { showNotification } from './react/NotificationProvider'
|
||||||
|
|
@ -13,9 +12,6 @@ const parseNbt = promisify(nbt.parse)
|
||||||
const simplifyNbt = nbt.simplify
|
const simplifyNbt = nbt.simplify
|
||||||
window.nbt = nbt
|
window.nbt = nbt
|
||||||
|
|
||||||
// Supported image types for skybox
|
|
||||||
const VALID_IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp']
|
|
||||||
|
|
||||||
// todo display drop zone
|
// todo display drop zone
|
||||||
for (const event of ['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop']) {
|
for (const event of ['drag', 'dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop']) {
|
||||||
window.addEventListener(event, (e: any) => {
|
window.addEventListener(event, (e: any) => {
|
||||||
|
|
@ -49,34 +45,6 @@ window.addEventListener('drop', async e => {
|
||||||
})
|
})
|
||||||
|
|
||||||
async function handleDroppedFile (file: File) {
|
async function handleDroppedFile (file: File) {
|
||||||
// Check for image files first when game is active
|
|
||||||
if (isGameActive(false) && VALID_IMAGE_EXTENSIONS.some(ext => file.name.toLowerCase().endsWith(ext))) {
|
|
||||||
try {
|
|
||||||
// Convert image to base64
|
|
||||||
const reader = new FileReader()
|
|
||||||
const base64Promise = new Promise<string>((resolve, reject) => {
|
|
||||||
reader.onload = () => resolve(reader.result as string)
|
|
||||||
reader.onerror = reject
|
|
||||||
})
|
|
||||||
reader.readAsDataURL(file)
|
|
||||||
const base64Image = await base64Promise
|
|
||||||
|
|
||||||
// Get ThreeJS backend methods and update skybox
|
|
||||||
const setSkyboxImage = getThreeJsRendererMethods()?.setSkyboxImage
|
|
||||||
if (setSkyboxImage) {
|
|
||||||
await setSkyboxImage(base64Image)
|
|
||||||
showNotification('Skybox updated successfully')
|
|
||||||
} else {
|
|
||||||
showNotification('Cannot update skybox - renderer does not support it')
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to update skybox:', err)
|
|
||||||
showNotification('Failed to update skybox', 'error')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.name.endsWith('.zip')) {
|
if (file.name.endsWith('.zip')) {
|
||||||
void openWorldZip(file)
|
void openWorldZip(file)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -126,28 +126,6 @@ customEvents.on('gameLoaded', () => {
|
||||||
if (entityStatus === EntityStatus.HURT) {
|
if (entityStatus === EntityStatus.HURT) {
|
||||||
getThreeJsRendererMethods()?.damageEntity(entityId, entityStatus)
|
getThreeJsRendererMethods()?.damageEntity(entityId, entityStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entityStatus === EntityStatus.BURNED) {
|
|
||||||
updateEntityStates(entityId, true, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// on fire events
|
|
||||||
bot._client.on('entity_metadata', (data) => {
|
|
||||||
if (data.entityId !== bot.entity.id) return
|
|
||||||
handleEntityMetadata(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
bot.on('end', () => {
|
|
||||||
if (onFireTimeout) {
|
|
||||||
clearTimeout(onFireTimeout)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
bot.on('respawn', () => {
|
|
||||||
if (onFireTimeout) {
|
|
||||||
clearTimeout(onFireTimeout)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const updateCamera = (entity: Entity) => {
|
const updateCamera = (entity: Entity) => {
|
||||||
|
|
@ -246,29 +224,22 @@ customEvents.on('gameLoaded', () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// even if not found, still record to cache
|
// even if not found, still record to cache
|
||||||
void getThreeJsRendererMethods()!.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl)
|
void getThreeJsRendererMethods()?.updatePlayerSkin(entityId, player.username, player.uuid, skinUrl ?? true, capeUrl)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
reportError(new Error('Error applying skin texture:', { cause: err }))
|
console.error('Error decoding player texture:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.on('playerJoined', updateSkin)
|
bot.on('playerJoined', updateSkin)
|
||||||
bot.on('playerUpdated', updateSkin)
|
bot.on('playerUpdated', updateSkin)
|
||||||
for (const entity of Object.values(bot.players)) {
|
|
||||||
updateSkin(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
const teamUpdated = (team: Team) => {
|
bot.on('teamUpdated', (team: Team) => {
|
||||||
for (const entity of Object.values(bot.entities)) {
|
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)) {
|
if (entity.type === 'player' && entity.username && team.members.includes(entity.username) || entity.uuid && team.members.includes(entity.uuid)) {
|
||||||
bot.emit('entityUpdate', entity)
|
bot.emit('entityUpdate', entity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
bot.on('teamUpdated', teamUpdated)
|
|
||||||
for (const team of Object.values(bot.teams)) {
|
|
||||||
teamUpdated(team)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateEntityNameTags = (team: Team) => {
|
const updateEntityNameTags = (team: Team) => {
|
||||||
for (const entity of Object.values(bot.entities)) {
|
for (const entity of Object.values(bot.entities)) {
|
||||||
|
|
@ -317,7 +288,7 @@ customEvents.on('gameLoaded', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
bot.on('teamRemoved', (team: Team) => {
|
bot.on('teamRemoved', (team: Team) => {
|
||||||
if (appViewer.playerState.reactive.team?.team === team?.team) {
|
if (appViewer.playerState.reactive.team?.team === team.team) {
|
||||||
appViewer.playerState.reactive.team = undefined
|
appViewer.playerState.reactive.team = undefined
|
||||||
// Player's team was removed, need to update all entities that are in a team
|
// Player's team was removed, need to update all entities that are in a team
|
||||||
updateEntityNameTags(team)
|
updateEntityNameTags(team)
|
||||||
|
|
@ -325,44 +296,3 @@ customEvents.on('gameLoaded', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Constants
|
|
||||||
const SHARED_FLAGS_KEY = 0
|
|
||||||
const ENTITY_FLAGS = {
|
|
||||||
ON_FIRE: 0x01, // Bit 0
|
|
||||||
SNEAKING: 0x02, // Bit 1
|
|
||||||
SPRINTING: 0x08, // Bit 3
|
|
||||||
SWIMMING: 0x10, // Bit 4
|
|
||||||
INVISIBLE: 0x20, // Bit 5
|
|
||||||
GLOWING: 0x40, // Bit 6
|
|
||||||
FALL_FLYING: 0x80 // Bit 7 (elytra flying)
|
|
||||||
}
|
|
||||||
|
|
||||||
let onFireTimeout: NodeJS.Timeout | undefined
|
|
||||||
const updateEntityStates = (entityId: number, onFire: boolean, timeout?: boolean) => {
|
|
||||||
if (entityId !== bot.entity.id) return
|
|
||||||
appViewer.playerState.reactive.onFire = onFire
|
|
||||||
if (onFireTimeout) {
|
|
||||||
clearTimeout(onFireTimeout)
|
|
||||||
}
|
|
||||||
if (timeout) {
|
|
||||||
onFireTimeout = setTimeout(() => {
|
|
||||||
updateEntityStates(entityId, false, false)
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process entity metadata packet
|
|
||||||
function handleEntityMetadata (packet: { entityId: number, metadata: Array<{ key: number, type: string, value: number }> }) {
|
|
||||||
const { entityId, metadata } = packet
|
|
||||||
|
|
||||||
// Find shared flags in metadata
|
|
||||||
const flagsData = metadata.find(meta => meta.key === SHARED_FLAGS_KEY &&
|
|
||||||
meta.type === 'byte')
|
|
||||||
|
|
||||||
// Update fire state if flags were found
|
|
||||||
if (flagsData) {
|
|
||||||
const wasOnFire = appViewer.playerState.reactive.onFire
|
|
||||||
appViewer.playerState.reactive.onFire = (flagsData.value & ENTITY_FLAGS.ON_FIRE) !== 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
20
src/env.d.ts
vendored
20
src/env.d.ts
vendored
|
|
@ -2,36 +2,30 @@ declare namespace NodeJS {
|
||||||
interface ProcessEnv {
|
interface ProcessEnv {
|
||||||
// Build configuration
|
// Build configuration
|
||||||
NODE_ENV: 'development' | 'production'
|
NODE_ENV: 'development' | 'production'
|
||||||
MIN_MC_VERSION?: string
|
SINGLE_FILE_BUILD?: string
|
||||||
MAX_MC_VERSION?: string
|
|
||||||
ALWAYS_COMPRESS_LARGE_DATA?: 'true' | 'false'
|
|
||||||
SINGLE_FILE_BUILD?: 'true' | 'false'
|
|
||||||
WS_PORT?: string
|
WS_PORT?: string
|
||||||
DISABLE_SERVICE_WORKER?: 'true' | 'false'
|
DISABLE_SERVICE_WORKER?: string
|
||||||
CONFIG_JSON_SOURCE?: 'BUNDLED' | 'REMOTE'
|
CONFIG_JSON_SOURCE?: 'BUNDLED' | 'REMOTE'
|
||||||
LOCAL_CONFIG_FILE?: string
|
LOCAL_CONFIG_FILE?: string
|
||||||
BUILD_VERSION?: string
|
BUILD_VERSION?: string
|
||||||
|
|
||||||
// Build internals
|
// GitHub and Vercel related
|
||||||
GITHUB_REPOSITORY?: string
|
GITHUB_REPOSITORY?: string
|
||||||
VERCEL_GIT_REPO_OWNER?: string
|
VERCEL_GIT_REPO_OWNER?: string
|
||||||
VERCEL_GIT_REPO_SLUG?: string
|
VERCEL_GIT_REPO_SLUG?: string
|
||||||
|
|
||||||
// UI
|
// UI and Features
|
||||||
MAIN_MENU_LINKS?: string
|
MAIN_MENU_LINKS?: string
|
||||||
ALWAYS_MINIMAL_SERVER_UI?: 'true' | 'false'
|
|
||||||
|
|
||||||
// App features
|
|
||||||
ENABLE_COOKIE_STORAGE?: string
|
ENABLE_COOKIE_STORAGE?: string
|
||||||
COOKIE_STORAGE_PREFIX?: string
|
COOKIE_STORAGE_PREFIX?: string
|
||||||
|
|
||||||
// Build info. Release information
|
// Release information
|
||||||
RELEASE_TAG?: string
|
RELEASE_TAG?: string
|
||||||
RELEASE_LINK?: string
|
RELEASE_LINK?: string
|
||||||
RELEASE_CHANGELOG?: string
|
RELEASE_CHANGELOG?: string
|
||||||
|
|
||||||
// Build info
|
// Other configurations
|
||||||
|
DEPS_VERSIONS?: string
|
||||||
INLINED_APP_CONFIG?: string
|
INLINED_APP_CONFIG?: string
|
||||||
GITHUB_URL?: string
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
256
src/index.ts
256
src/index.ts
|
|
@ -29,7 +29,7 @@ import './reactUi'
|
||||||
import { lockUrl, onBotCreate } from './controls'
|
import { lockUrl, onBotCreate } from './controls'
|
||||||
import './dragndrop'
|
import './dragndrop'
|
||||||
import { possiblyCleanHandle } from './browserfs'
|
import { possiblyCleanHandle } from './browserfs'
|
||||||
import downloadAndOpenFile, { isInterestedInDownload } from './downloadAndOpenFile'
|
import downloadAndOpenFile from './downloadAndOpenFile'
|
||||||
|
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import net, { Socket } from 'net'
|
import net, { Socket } from 'net'
|
||||||
|
|
@ -56,12 +56,13 @@ import { isCypress } from './standaloneUtils'
|
||||||
|
|
||||||
import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer'
|
import { startLocalServer, unsupportedLocalServerFeatures } from './createLocalServer'
|
||||||
import defaultServerOptions from './defaultLocalServerOptions'
|
import defaultServerOptions from './defaultLocalServerOptions'
|
||||||
|
import dayCycle from './dayCycle'
|
||||||
|
|
||||||
import { onAppLoad, resourcepackReload, resourcePackState } from './resourcePack'
|
import { onAppLoad, resourcepackReload, resourcePackState } from './resourcePack'
|
||||||
import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer'
|
import { ConnectPeerOptions, connectToPeer } from './localServerMultiplayer'
|
||||||
import CustomChannelClient from './customClient'
|
import CustomChannelClient from './customClient'
|
||||||
import { registerServiceWorker } from './serviceWorker'
|
import { registerServiceWorker } from './serviceWorker'
|
||||||
import { appStatusState, lastConnectOptions, quickDevReconnect } from './react/AppStatusProvider'
|
import { appStatusState, lastConnectOptions } from './react/AppStatusProvider'
|
||||||
|
|
||||||
import { fsState } from './loadSave'
|
import { fsState } from './loadSave'
|
||||||
import { watchFov } from './rendererUtils'
|
import { watchFov } from './rendererUtils'
|
||||||
|
|
@ -96,7 +97,6 @@ import { registerOpenBenchmarkListener } from './benchmark'
|
||||||
import { tryHandleBuiltinCommand } from './builtinCommands'
|
import { tryHandleBuiltinCommand } from './builtinCommands'
|
||||||
import { loadingTimerState } from './react/LoadingTimer'
|
import { loadingTimerState } from './react/LoadingTimer'
|
||||||
import { loadPluginsIntoWorld } from './react/CreateWorldProvider'
|
import { loadPluginsIntoWorld } from './react/CreateWorldProvider'
|
||||||
import { getCurrentProxy, getCurrentUsername } from './react/ServersList'
|
|
||||||
|
|
||||||
window.debug = debug
|
window.debug = debug
|
||||||
window.beforeRenderFrame = []
|
window.beforeRenderFrame = []
|
||||||
|
|
@ -166,7 +166,6 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
appStatusState.showReconnect = false
|
|
||||||
loadingTimerState.loading = true
|
loadingTimerState.loading = true
|
||||||
loadingTimerState.start = Date.now()
|
loadingTimerState.start = Date.now()
|
||||||
miscUiState.hasErrors = false
|
miscUiState.hasErrors = false
|
||||||
|
|
@ -214,13 +213,8 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
const destroyAll = (wasKicked = false) => {
|
const destroyAll = (wasKicked = false) => {
|
||||||
if (ended) return
|
if (ended) return
|
||||||
loadingTimerState.loading = false
|
loadingTimerState.loading = false
|
||||||
const { alwaysReconnect } = appQueryParams
|
if (!wasKicked && miscUiState.appConfig?.allowAutoConnect && appQueryParams.autoConnect && hadConnected) {
|
||||||
if ((!wasKicked && miscUiState.appConfig?.allowAutoConnect && appQueryParams.autoConnect && hadConnected) || (alwaysReconnect)) {
|
location.reload()
|
||||||
if (alwaysReconnect === 'quick' || alwaysReconnect === 'fast') {
|
|
||||||
quickDevReconnect()
|
|
||||||
} else {
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
errorAbortController.abort()
|
errorAbortController.abort()
|
||||||
ended = true
|
ended = true
|
||||||
|
|
@ -235,12 +229,8 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
bot.emit('end', '')
|
bot.emit('end', '')
|
||||||
bot.removeAllListeners()
|
bot.removeAllListeners()
|
||||||
bot._client.removeAllListeners()
|
bot._client.removeAllListeners()
|
||||||
bot._client = {
|
//@ts-expect-error TODO?
|
||||||
//@ts-expect-error
|
bot._client = undefined
|
||||||
write (packetName) {
|
|
||||||
console.warn('Tried to write packet', packetName, 'after bot was destroyed')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//@ts-expect-error
|
//@ts-expect-error
|
||||||
window.bot = bot = undefined
|
window.bot = bot = undefined
|
||||||
}
|
}
|
||||||
|
|
@ -286,10 +276,6 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.reason?.stack?.includes('chrome-extension://')) {
|
|
||||||
// ignore issues caused by chrome extension
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handleError(e.reason)
|
handleError(e.reason)
|
||||||
}, {
|
}, {
|
||||||
signal: errorAbortController.signal
|
signal: errorAbortController.signal
|
||||||
|
|
@ -304,7 +290,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
|
|
||||||
if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) {
|
if (connectOptions.server && !connectOptions.viewerWsConnect && !parsedServer.isWebSocket) {
|
||||||
console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`)
|
console.log(`using proxy ${proxy.host}:${proxy.port || location.port}`)
|
||||||
net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` }, artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined })
|
net['setProxy']({ hostname: proxy.host, port: proxy.port, headers: { Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}` } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance
|
const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance
|
||||||
|
|
@ -793,6 +779,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
initMotionTracking()
|
initMotionTracking()
|
||||||
|
dayCycle()
|
||||||
|
|
||||||
// Bot position callback
|
// Bot position callback
|
||||||
const botPosition = () => {
|
const botPosition = () => {
|
||||||
|
|
@ -893,7 +880,37 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined
|
||||||
|
|
||||||
listenGlobalEvents()
|
listenGlobalEvents()
|
||||||
|
const unsubscribe = subscribe(miscUiState, async () => {
|
||||||
|
if (miscUiState.fsReady && miscUiState.appConfig) {
|
||||||
|
unsubscribe()
|
||||||
|
if (reconnectOptions) {
|
||||||
|
sessionStorage.removeItem('reconnectOptions')
|
||||||
|
if (Date.now() - reconnectOptions.timestamp < 1000 * 60 * 2) {
|
||||||
|
void connect(reconnectOptions.value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (appQueryParams.singleplayer === '1' || appQueryParams.sp === '1') {
|
||||||
|
loadSingleplayer({}, {
|
||||||
|
worldFolder: undefined,
|
||||||
|
...appQueryParams.version ? { version: appQueryParams.version } : {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (appQueryParams.loadSave) {
|
||||||
|
const savePath = `/data/worlds/${appQueryParams.loadSave}`
|
||||||
|
try {
|
||||||
|
await fs.promises.stat(savePath)
|
||||||
|
} catch (err) {
|
||||||
|
alert(`Save ${savePath} not found`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await loadInMemorySave(savePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// #region fire click event on touch as we disable default behaviors
|
// #region fire click event on touch as we disable default behaviors
|
||||||
let activeTouch: { touch: Touch, elem: HTMLElement, start: number } | undefined
|
let activeTouch: { touch: Touch, elem: HTMLElement, start: number } | undefined
|
||||||
|
|
@ -929,148 +946,90 @@ document.body.addEventListener('touchstart', (e) => {
|
||||||
}, { passive: false })
|
}, { passive: false })
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
// immediate game enter actions: reconnect or URL QS
|
// qs open actions
|
||||||
const maybeEnterGame = () => {
|
if (!reconnectOptions) {
|
||||||
const waitForConfigFsLoad = (fn: () => void) => {
|
downloadAndOpenFile().then((downloadAction) => {
|
||||||
let unsubscribe: () => void | undefined
|
if (downloadAction) return
|
||||||
const checkDone = () => {
|
if (appQueryParams.reconnect && process.env.NODE_ENV === 'development') {
|
||||||
if (miscUiState.fsReady && miscUiState.appConfig) {
|
const lastConnect = JSON.parse(localStorage.lastConnectOptions ?? {})
|
||||||
fn()
|
|
||||||
unsubscribe?.()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkDone()) {
|
|
||||||
const text = miscUiState.appConfig ? 'Loading' : 'Loading config'
|
|
||||||
setLoadingScreenStatus(text)
|
|
||||||
unsubscribe = subscribe(miscUiState, checkDone)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const reconnectOptions = sessionStorage.getItem('reconnectOptions') ? JSON.parse(sessionStorage.getItem('reconnectOptions')!) : undefined
|
|
||||||
|
|
||||||
if (reconnectOptions) {
|
|
||||||
sessionStorage.removeItem('reconnectOptions')
|
|
||||||
if (Date.now() - reconnectOptions.timestamp < 1000 * 60 * 2) {
|
|
||||||
return waitForConfigFsLoad(async () => {
|
|
||||||
void connect(reconnectOptions.value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appQueryParams.reconnect && localStorage.lastConnectOptions && process.env.NODE_ENV === 'development') {
|
|
||||||
const lastConnect = JSON.parse(localStorage.lastConnectOptions ?? {})
|
|
||||||
return waitForConfigFsLoad(async () => {
|
|
||||||
void connect({
|
void connect({
|
||||||
botVersion: appQueryParams.version ?? undefined,
|
botVersion: appQueryParams.version ?? undefined,
|
||||||
...lastConnect,
|
...lastConnect,
|
||||||
ip: appQueryParams.ip || undefined
|
ip: appQueryParams.ip || undefined
|
||||||
})
|
})
|
||||||
})
|
return
|
||||||
}
|
|
||||||
|
|
||||||
if (appQueryParams.singleplayer === '1' || appQueryParams.sp === '1') {
|
|
||||||
return waitForConfigFsLoad(async () => {
|
|
||||||
loadSingleplayer({}, {
|
|
||||||
worldFolder: undefined,
|
|
||||||
...appQueryParams.version ? { version: appQueryParams.version } : {}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (appQueryParams.loadSave) {
|
|
||||||
const enterSave = async () => {
|
|
||||||
const savePath = `/data/worlds/${appQueryParams.loadSave}`
|
|
||||||
try {
|
|
||||||
await fs.promises.stat(savePath)
|
|
||||||
await loadInMemorySave(savePath)
|
|
||||||
} catch (err) {
|
|
||||||
alert(`Save ${savePath} not found`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return waitForConfigFsLoad(enterSave)
|
if (appQueryParams.ip || appQueryParams.proxy) {
|
||||||
}
|
const waitAppConfigLoad = !appQueryParams.proxy
|
||||||
|
const openServerEditor = () => {
|
||||||
if (appQueryParams.ip || appQueryParams.proxy) {
|
hideModal()
|
||||||
const openServerAction = () => {
|
if (appQueryParams.onlyConnect) {
|
||||||
if (appQueryParams.autoConnect && miscUiState.appConfig?.allowAutoConnect) {
|
showModal({ reactType: 'only-connect-server' })
|
||||||
void connect({
|
} else {
|
||||||
server: appQueryParams.ip,
|
showModal({ reactType: 'editServer' })
|
||||||
proxy: getCurrentProxy(),
|
}
|
||||||
botVersion: appQueryParams.version ?? undefined,
|
|
||||||
username: getCurrentUsername()!,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
showModal({ reactType: 'empty' })
|
||||||
setLoadingScreenStatus(undefined)
|
if (waitAppConfigLoad) {
|
||||||
if (appQueryParams.onlyConnect || process.env.ALWAYS_MINIMAL_SERVER_UI === 'true') {
|
const unsubscribe = subscribe(miscUiState, checkCanDisplay)
|
||||||
showModal({ reactType: 'only-connect-server' })
|
checkCanDisplay()
|
||||||
|
// eslint-disable-next-line no-inner-declarations
|
||||||
|
function checkCanDisplay () {
|
||||||
|
if (miscUiState.appConfig) {
|
||||||
|
unsubscribe()
|
||||||
|
openServerEditor()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
showModal({ reactType: 'editServer' })
|
openServerEditor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// showModal({ reactType: 'empty' })
|
void Promise.resolve().then(() => {
|
||||||
return waitForConfigFsLoad(openServerAction)
|
// try to connect to peer
|
||||||
}
|
const peerId = appQueryParams.connectPeer
|
||||||
|
const peerOptions = {} as ConnectPeerOptions
|
||||||
if (appQueryParams.connectPeer) {
|
if (appQueryParams.server) {
|
||||||
// try to connect to peer
|
peerOptions.server = appQueryParams.server
|
||||||
const peerId = appQueryParams.connectPeer
|
}
|
||||||
const peerOptions = {} as ConnectPeerOptions
|
const version = appQueryParams.peerVersion
|
||||||
if (appQueryParams.server) {
|
if (peerId) {
|
||||||
peerOptions.server = appQueryParams.server
|
let username: string | null = options.guestUsername
|
||||||
}
|
if (options.askGuestName) username = prompt('Enter your username', username)
|
||||||
const version = appQueryParams.peerVersion
|
if (!username) return
|
||||||
let username: string | null = options.guestUsername
|
options.guestUsername = username
|
||||||
if (options.askGuestName) username = prompt('Enter your username to connect to peer', username)
|
void connect({
|
||||||
if (!username) return
|
username,
|
||||||
options.guestUsername = username
|
botVersion: version || undefined,
|
||||||
void connect({
|
peerId,
|
||||||
username,
|
peerOptions
|
||||||
botVersion: version || undefined,
|
})
|
||||||
peerId,
|
}
|
||||||
peerOptions
|
|
||||||
})
|
})
|
||||||
return
|
|
||||||
|
|
||||||
}
|
if (appQueryParams.serversList && !appQueryParams.ip) {
|
||||||
|
showModal({ reactType: 'serversList' })
|
||||||
if (appQueryParams.viewerConnect) {
|
|
||||||
void connect({
|
|
||||||
username: `viewer-${Math.random().toString(36).slice(2, 10)}`,
|
|
||||||
viewerWsConnect: appQueryParams.viewerConnect,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appQueryParams.modal) {
|
|
||||||
const modals = appQueryParams.modal.split(',')
|
|
||||||
for (const modal of modals) {
|
|
||||||
showModal({ reactType: modal })
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (appQueryParams.serversList && !miscUiState.appConfig?.appParams?.serversList) {
|
const viewerWsConnect = appQueryParams.viewerConnect
|
||||||
// open UI only if it's in URL
|
if (viewerWsConnect) {
|
||||||
showModal({ reactType: 'serversList' })
|
void connect({
|
||||||
}
|
username: `viewer-${Math.random().toString(36).slice(2, 10)}`,
|
||||||
|
viewerWsConnect,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (isInterestedInDownload()) {
|
if (appQueryParams.modal) {
|
||||||
void downloadAndOpenFile()
|
const modals = appQueryParams.modal.split(',')
|
||||||
}
|
for (const modal of modals) {
|
||||||
|
showModal({ reactType: modal })
|
||||||
void possiblyHandleStateVariable()
|
}
|
||||||
}
|
}
|
||||||
|
}, (err) => {
|
||||||
try {
|
console.error(err)
|
||||||
maybeEnterGame()
|
alert(`Something went wrong: ${err}`)
|
||||||
} catch (err) {
|
})
|
||||||
console.error(err)
|
|
||||||
alert(`Something went wrong: ${err}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
|
|
@ -1081,5 +1040,6 @@ if (initialLoader) {
|
||||||
}
|
}
|
||||||
window.pageLoaded = true
|
window.pageLoaded = true
|
||||||
|
|
||||||
|
void possiblyHandleStateVariable()
|
||||||
appViewer.waitBackendLoadPromises.push(appStartup())
|
appViewer.waitBackendLoadPromises.push(appStartup())
|
||||||
registerOpenBenchmarkListener()
|
registerOpenBenchmarkListener()
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ import PItem, { Item } from 'prismarine-item'
|
||||||
import { versionToNumber } from 'renderer/viewer/common/utils'
|
import { versionToNumber } from 'renderer/viewer/common/utils'
|
||||||
import { getRenamedData } from 'flying-squid/dist/blockRenames'
|
import { getRenamedData } from 'flying-squid/dist/blockRenames'
|
||||||
import PrismarineChatLoader from 'prismarine-chat'
|
import PrismarineChatLoader from 'prismarine-chat'
|
||||||
import * as nbt from 'prismarine-nbt'
|
|
||||||
import { BlockModel } from 'mc-assets'
|
import { BlockModel } from 'mc-assets'
|
||||||
import { renderSlot } from 'renderer/viewer/three/renderSlot'
|
import { renderSlot } from 'renderer/viewer/three/renderSlot'
|
||||||
import { loadSkinFromUsername } from 'renderer/viewer/lib/utils/skins'
|
|
||||||
import Generic95 from '../assets/generic_95.png'
|
import Generic95 from '../assets/generic_95.png'
|
||||||
import { appReplacableResources } from './generated/resources'
|
import { appReplacableResources } from './generated/resources'
|
||||||
import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState'
|
import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState'
|
||||||
|
|
@ -24,7 +22,6 @@ import { getItemDescription } from './itemsDescriptions'
|
||||||
import { MessageFormatPart } from './chatUtils'
|
import { MessageFormatPart } from './chatUtils'
|
||||||
import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items'
|
import { GeneralInputItem, getItemMetadata, getItemModelName, getItemNameRaw, RenderItem } from './mineflayer/items'
|
||||||
import { playerState } from './mineflayer/playerState'
|
import { playerState } from './mineflayer/playerState'
|
||||||
import { modelViewerState } from './react/OverlayModelViewer'
|
|
||||||
|
|
||||||
const loadedImagesCache = new Map<string, HTMLImageElement | ImageBitmap>()
|
const loadedImagesCache = new Map<string, HTMLImageElement | ImageBitmap>()
|
||||||
const cleanLoadedImagesCache = () => {
|
const cleanLoadedImagesCache = () => {
|
||||||
|
|
@ -42,34 +39,6 @@ export const jeiCustomCategories = proxy({
|
||||||
value: [] as Array<{ id: string, categoryTitle: string, items: any[] }>
|
value: [] as Array<{ id: string, categoryTitle: string, items: any[] }>
|
||||||
})
|
})
|
||||||
|
|
||||||
let remotePlayerSkin: string | undefined | Promise<string>
|
|
||||||
|
|
||||||
export const showInventoryPlayer = () => {
|
|
||||||
modelViewerState.model = {
|
|
||||||
positioning: {
|
|
||||||
windowWidth: 176,
|
|
||||||
windowHeight: 166,
|
|
||||||
x: 25,
|
|
||||||
y: 8,
|
|
||||||
width: 50,
|
|
||||||
height: 70,
|
|
||||||
scaled: true,
|
|
||||||
onlyInitialScale: true,
|
|
||||||
followCursor: true,
|
|
||||||
},
|
|
||||||
// models: ['https://bucket.mcraft.fun/sitarbuckss.glb'],
|
|
||||||
// debug: true,
|
|
||||||
steveModelSkin: appViewer.playerState.reactive.playerSkin ?? (typeof remotePlayerSkin === 'string' ? remotePlayerSkin : ''),
|
|
||||||
}
|
|
||||||
if (remotePlayerSkin === undefined && !appViewer.playerState.reactive.playerSkin) {
|
|
||||||
remotePlayerSkin = loadSkinFromUsername(bot.username, 'skin').then(a => {
|
|
||||||
setTimeout(() => { showInventoryPlayer() }, 0) // todo patch instead and make reactive
|
|
||||||
remotePlayerSkin = a ?? ''
|
|
||||||
return remotePlayerSkin
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const onGameLoad = () => {
|
export const onGameLoad = () => {
|
||||||
version = bot.version
|
version = bot.version
|
||||||
|
|
||||||
|
|
@ -87,23 +56,12 @@ export const onGameLoad = () => {
|
||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
|
|
||||||
const maybeParseNbtJson = (data: any) => {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
try {
|
|
||||||
data = JSON.parse(data)
|
|
||||||
} catch (err) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nbt.simplify(data) ?? data
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.on('windowOpen', (win) => {
|
bot.on('windowOpen', (win) => {
|
||||||
const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)]
|
const implementedWindow = implementedContainersGuiMap[mapWindowType(win.type as string, win.inventoryStart)]
|
||||||
if (implementedWindow) {
|
if (implementedWindow) {
|
||||||
openWindow(implementedWindow, maybeParseNbtJson(win.title))
|
openWindow(implementedWindow)
|
||||||
} else if (options.unimplementedContainers) {
|
} else if (options.unimplementedContainers) {
|
||||||
openWindow('ChestWin', maybeParseNbtJson(win.title))
|
openWindow('ChestWin')
|
||||||
} else {
|
} else {
|
||||||
// todo format
|
// todo format
|
||||||
displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`)
|
displayClientChat(`[client error] cannot open unimplemented window ${win.id} (${win.type}). Slots: ${win.slots.map(item => getItemName(item)).filter(Boolean).join(', ')}`)
|
||||||
|
|
@ -300,7 +258,6 @@ export const upInventoryItems = (isInventory: boolean, invWindow = lastWindow) =
|
||||||
// inv.pwindow.inv.slots[2].blockData = getBlockData('dirt')
|
// inv.pwindow.inv.slots[2].blockData = getBlockData('dirt')
|
||||||
const customSlots = mapSlots((isInventory ? bot.inventory : bot.currentWindow)!.slots)
|
const customSlots = mapSlots((isInventory ? bot.inventory : bot.currentWindow)!.slots)
|
||||||
invWindow.pwindow.setSlots(customSlots)
|
invWindow.pwindow.setSlots(customSlots)
|
||||||
return customSlots
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onModalClose = (callback: () => any) => {
|
export const onModalClose = (callback: () => any) => {
|
||||||
|
|
@ -397,7 +354,7 @@ const upWindowItemsLocal = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let skipClosePacketSending = false
|
let skipClosePacketSending = false
|
||||||
const openWindow = (type: string | undefined, title: string | any = undefined) => {
|
const openWindow = (type: string | undefined) => {
|
||||||
// if (activeModalStack.some(x => x.reactType?.includes?.('player_win:'))) {
|
// if (activeModalStack.some(x => x.reactType?.includes?.('player_win:'))) {
|
||||||
if (activeModalStack.length) { // game is not in foreground, don't close current modal
|
if (activeModalStack.length) { // game is not in foreground, don't close current modal
|
||||||
if (type) {
|
if (type) {
|
||||||
|
|
@ -422,16 +379,12 @@ const openWindow = (type: string | undefined, title: string | any = undefined) =
|
||||||
miscUiState.displaySearchInput = false
|
miscUiState.displaySearchInput = false
|
||||||
destroyFn()
|
destroyFn()
|
||||||
skipClosePacketSending = false
|
skipClosePacketSending = false
|
||||||
|
|
||||||
modelViewerState.model = undefined
|
|
||||||
})
|
})
|
||||||
if (type === undefined) {
|
|
||||||
showInventoryPlayer()
|
|
||||||
}
|
|
||||||
cleanLoadedImagesCache()
|
cleanLoadedImagesCache()
|
||||||
const inv = openItemsCanvas(type)
|
const inv = openItemsCanvas(type)
|
||||||
inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch
|
inv.canvasManager.children[0].mobileHelpers = miscUiState.currentTouch
|
||||||
window.inventory = inv
|
window.inventory = inv
|
||||||
|
const title = bot.currentWindow?.title
|
||||||
const PrismarineChat = PrismarineChatLoader(bot.version)
|
const PrismarineChat = PrismarineChatLoader(bot.version)
|
||||||
try {
|
try {
|
||||||
inv.canvasManager.children[0].customTitleText = title ?
|
inv.canvasManager.children[0].customTitleText = title ?
|
||||||
|
|
@ -470,7 +423,6 @@ const openWindow = (type: string | undefined, title: string | any = undefined) =
|
||||||
const isRightClick = type === 'rightclick'
|
const isRightClick = type === 'rightclick'
|
||||||
const isLeftClick = type === 'leftclick'
|
const isLeftClick = type === 'leftclick'
|
||||||
if (isLeftClick || isRightClick) {
|
if (isLeftClick || isRightClick) {
|
||||||
modelViewerState.model = undefined
|
|
||||||
inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item)
|
inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -502,7 +454,6 @@ const openWindow = (type: string | undefined, title: string | any = undefined) =
|
||||||
if (freeSlot === null) return
|
if (freeSlot === null) return
|
||||||
void bot.creative.setInventorySlot(freeSlot, item)
|
void bot.creative.setInventorySlot(freeSlot, item)
|
||||||
} else {
|
} else {
|
||||||
modelViewerState.model = undefined
|
|
||||||
inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0])
|
inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item], true)[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -571,7 +522,7 @@ const getResultingRecipe = (slots: Array<Item | null>, gridRows: number) => {
|
||||||
type Result = RecipeItem | undefined
|
type Result = RecipeItem | undefined
|
||||||
let shapelessResult: Result
|
let shapelessResult: Result
|
||||||
let shapeResult: Result
|
let shapeResult: Result
|
||||||
outer: for (const [id, recipeVariants] of Object.entries(loadedData.recipes ?? {})) {
|
outer: for (const [id, recipeVariants] of Object.entries(loadedData.recipes)) {
|
||||||
for (const recipeVariant of recipeVariants) {
|
for (const recipeVariant of recipeVariants) {
|
||||||
if ('inShape' in recipeVariant && equals(currentShape, recipeVariant.inShape as number[][])) {
|
if ('inShape' in recipeVariant && equals(currentShape, recipeVariant.inShape as number[][])) {
|
||||||
shapeResult = recipeVariant.result!
|
shapeResult = recipeVariant.result!
|
||||||
|
|
@ -599,7 +550,7 @@ const getAllItemRecipes = (itemName: string) => {
|
||||||
const item = loadedData.itemsByName[itemName]
|
const item = loadedData.itemsByName[itemName]
|
||||||
if (!item) return
|
if (!item) return
|
||||||
const itemId = item.id
|
const itemId = item.id
|
||||||
const recipes = loadedData.recipes?.[itemId]
|
const recipes = loadedData.recipes[itemId]
|
||||||
if (!recipes) return
|
if (!recipes) return
|
||||||
const results = [] as Array<{
|
const results = [] as Array<{
|
||||||
result: Item,
|
result: Item,
|
||||||
|
|
@ -644,7 +595,7 @@ const getAllItemUsages = (itemName: string) => {
|
||||||
if (!item) return
|
if (!item) return
|
||||||
const foundRecipeIds = [] as string[]
|
const foundRecipeIds = [] as string[]
|
||||||
|
|
||||||
for (const [id, recipes] of Object.entries(loadedData.recipes ?? {})) {
|
for (const [id, recipes] of Object.entries(loadedData.recipes)) {
|
||||||
for (const recipe of recipes) {
|
for (const recipe of recipes) {
|
||||||
if ('inShape' in recipe) {
|
if ('inShape' in recipe) {
|
||||||
if (recipe.inShape.some(row => row.includes(item.id))) {
|
if (recipe.inShape.some(row => row.includes(item.id))) {
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,13 @@
|
||||||
import net from 'net'
|
|
||||||
import { Client } from 'minecraft-protocol'
|
import { Client } from 'minecraft-protocol'
|
||||||
import { appQueryParams } from '../appParams'
|
import { appQueryParams } from '../appParams'
|
||||||
import { downloadAllMinecraftData, getVersionAutoSelect } from '../connect'
|
import { downloadAllMinecraftData, getVersionAutoSelect } from '../connect'
|
||||||
import { gameAdditionalState } from '../globalState'
|
import { gameAdditionalState } from '../globalState'
|
||||||
import { ProgressReporter } from '../core/progressReporter'
|
import { ProgressReporter } from '../core/progressReporter'
|
||||||
import { parseServerAddress } from '../parseServerAddress'
|
|
||||||
import { getCurrentProxy } from '../react/ServersList'
|
|
||||||
import { pingServerVersion, validatePacket } from './minecraft-protocol-extra'
|
import { pingServerVersion, validatePacket } from './minecraft-protocol-extra'
|
||||||
import { getWebsocketStream } from './websocket-core'
|
import { getWebsocketStream } from './websocket-core'
|
||||||
|
|
||||||
let lastPacketTime = 0
|
let lastPacketTime = 0
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
customEvents.on('mineflayerBotCreated', () => {
|
||||||
// const oldParsePacketBuffer = bot._client.deserializer.parsePacketBuffer
|
|
||||||
// try {
|
|
||||||
// const parsed = oldParsePacketBuffer(buffer)
|
|
||||||
// } catch (err) {
|
|
||||||
// debugger
|
|
||||||
// reportError(new Error(`Error parsing packet ${buffer.subarray(0, 30).toString('hex')}`, { cause: err }))
|
|
||||||
// throw err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
class MinecraftProtocolError extends Error {
|
|
||||||
constructor (message: string, cause?: Error, public data?: any) {
|
|
||||||
if (data?.customPayload) {
|
|
||||||
message += ` (Custom payload: ${data.customPayload.channel})`
|
|
||||||
}
|
|
||||||
super(message, { cause })
|
|
||||||
this.name = 'MinecraftProtocolError'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClientError = (err, data) => {
|
|
||||||
const error = new MinecraftProtocolError(`Minecraft protocol client error: ${err.message}`, err, data)
|
|
||||||
reportError(error)
|
|
||||||
}
|
|
||||||
if (typeof bot._client['_events'].error === 'function') {
|
|
||||||
// dont report to bot for more explicit error
|
|
||||||
bot._client['_events'].error = onClientError
|
|
||||||
} else {
|
|
||||||
bot._client.on('error' as any, onClientError)
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo move more code here
|
// todo move more code here
|
||||||
if (!appQueryParams.noPacketsValidation) {
|
if (!appQueryParams.noPacketsValidation) {
|
||||||
(bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => {
|
(bot._client as unknown as Client).on('packet', (data, packetMeta, buffer, fullBuffer) => {
|
||||||
|
|
@ -68,7 +35,7 @@ setInterval(() => {
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
|
||||||
export const getServerInfo = async (ip: string, port?: number, preferredVersion = getVersionAutoSelect(), ping = false, progressReporter?: ProgressReporter, setProxyParams?: ProxyParams) => {
|
export const getServerInfo = async (ip: string, port?: number, preferredVersion = getVersionAutoSelect(), ping = false, progressReporter?: ProgressReporter) => {
|
||||||
await downloadAllMinecraftData()
|
await downloadAllMinecraftData()
|
||||||
const isWebSocket = ip.startsWith('ws://') || ip.startsWith('wss://')
|
const isWebSocket = ip.startsWith('ws://') || ip.startsWith('wss://')
|
||||||
let stream
|
let stream
|
||||||
|
|
@ -76,8 +43,6 @@ export const getServerInfo = async (ip: string, port?: number, preferredVersion
|
||||||
progressReporter?.setMessage('Connecting to WebSocket server')
|
progressReporter?.setMessage('Connecting to WebSocket server')
|
||||||
stream = (await getWebsocketStream(ip)).mineflayerStream
|
stream = (await getWebsocketStream(ip)).mineflayerStream
|
||||||
progressReporter?.setMessage('WebSocket connected. Ping packet sent, waiting for response')
|
progressReporter?.setMessage('WebSocket connected. Ping packet sent, waiting for response')
|
||||||
} else if (setProxyParams) {
|
|
||||||
setProxy(setProxyParams)
|
|
||||||
}
|
}
|
||||||
window.setLoadingMessage = (message?: string) => {
|
window.setLoadingMessage = (message?: string) => {
|
||||||
if (message === undefined) {
|
if (message === undefined) {
|
||||||
|
|
@ -94,46 +59,3 @@ export const getServerInfo = async (ip: string, port?: number, preferredVersion
|
||||||
window.setLoadingMessage = undefined
|
window.setLoadingMessage = undefined
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.debugTestPing = async (ip: string) => {
|
|
||||||
const parsed = parseServerAddress(ip, false)
|
|
||||||
const result = await getServerInfo(parsed.host, parsed.port ? Number(parsed.port) : undefined, undefined, true, undefined, { address: getCurrentProxy(), })
|
|
||||||
console.log('result', result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDefaultProxyParams = () => {
|
|
||||||
return {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${new URLSearchParams(location.search).get('token') ?? ''}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProxyParams = {
|
|
||||||
address?: string
|
|
||||||
headers?: Record<string, string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setProxy = (proxyParams: ProxyParams) => {
|
|
||||||
if (proxyParams.address?.startsWith(':')) {
|
|
||||||
proxyParams.address = `${location.protocol}//${location.hostname}${proxyParams.address}`
|
|
||||||
}
|
|
||||||
if (proxyParams.address && location.port !== '80' && location.port !== '443' && !/:\d+$/.test(proxyParams.address)) {
|
|
||||||
const https = proxyParams.address.startsWith('https://') || location.protocol === 'https:'
|
|
||||||
proxyParams.address = `${proxyParams.address}:${https ? 443 : 80}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedProxy = parseServerAddress(proxyParams.address, false)
|
|
||||||
const proxy = { host: parsedProxy.host, port: parsedProxy.port }
|
|
||||||
proxyParams.headers ??= getDefaultProxyParams().headers
|
|
||||||
net['setProxy']({
|
|
||||||
hostname: proxy.host,
|
|
||||||
port: proxy.port,
|
|
||||||
headers: proxyParams.headers,
|
|
||||||
artificialDelay: appQueryParams.addPing ? Number(appQueryParams.addPing) : undefined
|
|
||||||
})
|
|
||||||
return {
|
|
||||||
proxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ const domListeners = (bot: Bot) => {
|
||||||
}, { signal: abortController.signal })
|
}, { signal: abortController.signal })
|
||||||
|
|
||||||
bot.mouse.beforeUpdateChecks = () => {
|
bot.mouse.beforeUpdateChecks = () => {
|
||||||
if (!document.hasFocus() || !isGameActive(true)) {
|
if (!document.hasFocus()) {
|
||||||
// deactive all buttons
|
// deactive all buttons
|
||||||
bot.mouse.buttons.fill(false)
|
bot.mouse.buttons.fill(false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,9 @@ class CustomDuplex extends Duplex {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getWebsocketStream = async (host: string) => {
|
export const getWebsocketStream = async (host: string) => {
|
||||||
const baseProtocol = host.startsWith('ws://') ? 'ws' : 'wss'
|
const baseProtocol = location.protocol === 'https:' ? 'wss' : host.startsWith('ws://') ? 'ws' : 'wss'
|
||||||
const hostClean = host.replace('ws://', '').replace('wss://', '')
|
const hostClean = host.replace('ws://', '').replace('wss://', '')
|
||||||
const hostURL = new URL(`${baseProtocol}://${hostClean}`)
|
const ws = new WebSocket(`${baseProtocol}://${hostClean}`)
|
||||||
const hostParams = hostURL.searchParams
|
|
||||||
hostParams.append('client_mcraft', '')
|
|
||||||
const ws = new WebSocket(`${baseProtocol}://${hostURL.host}${hostURL.pathname}?${hostParams.toString()}`)
|
|
||||||
const clientDuplex = new CustomDuplex(undefined, data => {
|
const clientDuplex = new CustomDuplex(undefined, data => {
|
||||||
ws.send(data)
|
ws.send(data)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -82,19 +82,23 @@ export const guiOptionsScheme: {
|
||||||
custom () {
|
custom () {
|
||||||
return <Category>Experimental</Category>
|
return <Category>Experimental</Category>
|
||||||
},
|
},
|
||||||
dayCycleAndLighting: {
|
experimentalLightingV1: {
|
||||||
text: 'Day Cycle',
|
text: 'Experimental Lighting',
|
||||||
|
tooltip: 'Once stable this setting will be removed and always enabled',
|
||||||
},
|
},
|
||||||
smoothLighting: {},
|
smoothLighting: {},
|
||||||
newVersionsLighting: {
|
lightingStrategy: {
|
||||||
text: 'Lighting in Newer Versions',
|
values: [
|
||||||
|
['prefer-server', 'Prefer Server'],
|
||||||
|
['always-client', 'Always Client'],
|
||||||
|
['always-server', 'Always Server'],
|
||||||
|
],
|
||||||
},
|
},
|
||||||
lowMemoryMode: {
|
lowMemoryMode: {
|
||||||
text: 'Low Memory Mode',
|
text: 'Low Memory Mode',
|
||||||
enableWarning: 'Enabling it will make chunks load ~4x slower. When in the game, app needs to be reloaded to apply this setting.',
|
enableWarning: 'Enabling it will make chunks load ~4x slower. When in the game, app needs to be reloaded to apply this setting.',
|
||||||
},
|
},
|
||||||
starfieldRendering: {},
|
starfieldRendering: {},
|
||||||
renderEntities: {},
|
|
||||||
keepChunksDistance: {
|
keepChunksDistance: {
|
||||||
max: 5,
|
max: 5,
|
||||||
unit: '',
|
unit: '',
|
||||||
|
|
@ -480,24 +484,6 @@ export const guiOptionsScheme: {
|
||||||
],
|
],
|
||||||
sound: [
|
sound: [
|
||||||
{ volume: {} },
|
{ volume: {} },
|
||||||
{
|
|
||||||
custom () {
|
|
||||||
return <OptionSlider
|
|
||||||
valueOverride={options.enableMusic ? undefined : 0}
|
|
||||||
onChange={(value) => {
|
|
||||||
options.musicVolume = value
|
|
||||||
}}
|
|
||||||
item={{
|
|
||||||
type: 'slider',
|
|
||||||
id: 'musicVolume',
|
|
||||||
text: 'Music Volume',
|
|
||||||
min: 0,
|
|
||||||
max: 100,
|
|
||||||
unit: '%',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
custom () {
|
custom () {
|
||||||
return <Button label='Sound Muffler' onClick={() => showModal({ reactType: 'sound-muffler' })} inScreen />
|
return <Button label='Sound Muffler' onClick={() => showModal({ reactType: 'sound-muffler' })} inScreen />
|
||||||
|
|
@ -568,16 +554,6 @@ export const guiOptionsScheme: {
|
||||||
return <Category>Server Connection</Category>
|
return <Category>Server Connection</Category>
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
saveLoginPassword: {
|
|
||||||
tooltip: 'Controls whether to save login passwords for servers in this browser memory.',
|
|
||||||
values: [
|
|
||||||
'prompt',
|
|
||||||
'always',
|
|
||||||
'never'
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
custom () {
|
custom () {
|
||||||
const { serversAutoVersionSelect } = useSnapshot(options)
|
const { serversAutoVersionSelect } = useSnapshot(options)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import { appStorage } from './react/appStorageProvider'
|
||||||
import { miscUiState } from './globalState'
|
import { miscUiState } from './globalState'
|
||||||
import { defaultOptions } from './defaultOptions'
|
import { defaultOptions } from './defaultOptions'
|
||||||
|
|
||||||
|
defaultOptions.experimentalLightingV1 = location.hostname.startsWith('lighting.') // todo
|
||||||
|
|
||||||
const isDev = process.env.NODE_ENV === 'development'
|
const isDev = process.env.NODE_ENV === 'development'
|
||||||
const initialAppConfig = process.env?.INLINED_APP_CONFIG as AppConfig ?? {}
|
const initialAppConfig = process.env?.INLINED_APP_CONFIG as AppConfig ?? {}
|
||||||
|
|
||||||
|
|
@ -23,6 +25,11 @@ export const disabledSettings = proxy({
|
||||||
})
|
})
|
||||||
|
|
||||||
const migrateOptions = (options: Partial<AppOptions & Record<string, any>>) => {
|
const migrateOptions = (options: Partial<AppOptions & Record<string, any>>) => {
|
||||||
|
if (options.dayCycleAndLighting) {
|
||||||
|
delete options.dayCycleAndLighting
|
||||||
|
options.dayCycle = options.dayCycleAndLighting
|
||||||
|
}
|
||||||
|
|
||||||
if (options.highPerformanceGpu) {
|
if (options.highPerformanceGpu) {
|
||||||
options.gpuPreference = 'high-performance'
|
options.gpuPreference = 'high-performance'
|
||||||
delete options.highPerformanceGpu
|
delete options.highPerformanceGpu
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ export const startLocalReplayServer = (contents: string) => {
|
||||||
const server = createServer({
|
const server = createServer({
|
||||||
Server: LocalServer as any,
|
Server: LocalServer as any,
|
||||||
version: header.minecraftVersion,
|
version: header.minecraftVersion,
|
||||||
keepAlive: false,
|
|
||||||
'online-mode': false
|
'online-mode': false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,10 @@ interface Props {
|
||||||
accounts?: string[]
|
accounts?: string[]
|
||||||
authenticatedAccounts?: number
|
authenticatedAccounts?: number
|
||||||
versions?: string[]
|
versions?: string[]
|
||||||
|
allowAutoConnect?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, placeholders, accounts, versions }: Props) => {
|
export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, placeholders, accounts, versions, allowAutoConnect }: Props) => {
|
||||||
const isSmallHeight = !usePassesScaledDimensions(null, 350)
|
const isSmallHeight = !usePassesScaledDimensions(null, 350)
|
||||||
const qsParamName = parseQs ? appQueryParams.name : undefined
|
const qsParamName = parseQs ? appQueryParams.name : undefined
|
||||||
const qsParamIp = parseQs ? appQueryParams.ip : undefined
|
const qsParamIp = parseQs ? appQueryParams.ip : undefined
|
||||||
|
|
@ -39,6 +40,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
||||||
const qsParamProxy = parseQs ? appQueryParams.proxy : undefined
|
const qsParamProxy = parseQs ? appQueryParams.proxy : undefined
|
||||||
const qsParamUsername = parseQs ? appQueryParams.username : undefined
|
const qsParamUsername = parseQs ? appQueryParams.username : undefined
|
||||||
const qsParamLockConnect = parseQs ? appQueryParams.lockConnect : undefined
|
const qsParamLockConnect = parseQs ? appQueryParams.lockConnect : undefined
|
||||||
|
const qsParamAutoConnect = parseQs ? appQueryParams.autoConnect : undefined
|
||||||
|
|
||||||
const parsedQsIp = parseServerAddress(qsParamIp)
|
const parsedQsIp = parseServerAddress(qsParamIp)
|
||||||
const parsedInitialIp = parseServerAddress(initialData?.ip)
|
const parsedInitialIp = parseServerAddress(initialData?.ip)
|
||||||
|
|
@ -116,8 +118,14 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (qsParamAutoConnect && qsParamIp && qsParamVersion && allowAutoConnect) {
|
||||||
|
onQsConnect?.(commonUseOptions)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const displayConnectButton = qsParamIp
|
const displayConnectButton = qsParamIp
|
||||||
const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg', 'wss://play.webmc.fun']
|
const serverExamples = ['example.com:25565', 'play.hypixel.net', 'ws://play.pcm.gg']
|
||||||
// pick random example
|
// pick random example
|
||||||
const example = serverExamples[Math.floor(Math.random() * serverExamples.length)]
|
const example = serverExamples[Math.floor(Math.random() * serverExamples.length)]
|
||||||
|
|
||||||
|
|
@ -223,7 +231,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
||||||
Cancel
|
Cancel
|
||||||
</ButtonWrapper>
|
</ButtonWrapper>
|
||||||
<ButtonWrapper type='submit'>
|
<ButtonWrapper type='submit'>
|
||||||
{displayConnectButton ? translate('Save') : <strong>{translate('Save')}</strong>}
|
{displayConnectButton ? 'Save' : <strong>Save</strong>}
|
||||||
</ButtonWrapper>
|
</ButtonWrapper>
|
||||||
</>}
|
</>}
|
||||||
{displayConnectButton && (
|
{displayConnectButton && (
|
||||||
|
|
@ -238,7 +246,7 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ
|
||||||
onQsConnect?.(commonUseOptions)
|
onQsConnect?.(commonUseOptions)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong>{translate('Connect')}</strong>
|
<strong>Connect</strong>
|
||||||
</ButtonWrapper>
|
</ButtonWrapper>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -54,17 +54,6 @@ export const reconnectReload = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const quickDevReconnect = () => {
|
|
||||||
if (!lastConnectOptions.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
resetAppStatusState()
|
|
||||||
window.dispatchEvent(new window.CustomEvent('connect', {
|
|
||||||
detail: lastConnectOptions.value
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const lastState = useRef(JSON.parse(JSON.stringify(appStatusState)))
|
const lastState = useRef(JSON.parse(JSON.stringify(appStatusState)))
|
||||||
const currentState = useSnapshot(appStatusState)
|
const currentState = useSnapshot(appStatusState)
|
||||||
|
|
@ -116,6 +105,13 @@ export default () => {
|
||||||
}
|
}
|
||||||
}, [isOpen])
|
}, [isOpen])
|
||||||
|
|
||||||
|
const reconnect = () => {
|
||||||
|
resetAppStatusState()
|
||||||
|
window.dispatchEvent(new window.CustomEvent('connect', {
|
||||||
|
detail: lastConnectOptions.value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController()
|
const controller = new AbortController()
|
||||||
window.addEventListener('keyup', (e) => {
|
window.addEventListener('keyup', (e) => {
|
||||||
|
|
@ -123,7 +119,7 @@ export default () => {
|
||||||
if (activeModalStack.at(-1)?.reactType !== 'app-status') return
|
if (activeModalStack.at(-1)?.reactType !== 'app-status') return
|
||||||
// todo do only if reconnect is possible
|
// todo do only if reconnect is possible
|
||||||
if (e.code !== 'KeyR' || !lastConnectOptions.value) return
|
if (e.code !== 'KeyR' || !lastConnectOptions.value) return
|
||||||
quickDevReconnect()
|
reconnect()
|
||||||
}, {
|
}, {
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
})
|
})
|
||||||
|
|
@ -144,7 +140,7 @@ export default () => {
|
||||||
const account = await showOptionsModal('Choose account to connect with', [...accounts.map(account => account.username), 'Use other account'])
|
const account = await showOptionsModal('Choose account to connect with', [...accounts.map(account => account.username), 'Use other account'])
|
||||||
if (!account) return
|
if (!account) return
|
||||||
lastConnectOptions.value!.authenticatedAccount = accounts.find(acc => acc.username === account) || true
|
lastConnectOptions.value!.authenticatedAccount = accounts.find(acc => acc.username === account) || true
|
||||||
quickDevReconnect()
|
reconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastAutoCapturedPackets = getLastAutoCapturedPackets()
|
const lastAutoCapturedPackets = getLastAutoCapturedPackets()
|
||||||
|
|
@ -188,7 +184,7 @@ export default () => {
|
||||||
actionsSlot={
|
actionsSlot={
|
||||||
<>
|
<>
|
||||||
{displayAuthButton && <Button label='Authenticate' onClick={authReconnectAction} />}
|
{displayAuthButton && <Button label='Authenticate' onClick={authReconnectAction} />}
|
||||||
{displayVpnButton && <PossiblyVpnBypassProxyButton reconnect={quickDevReconnect} />}
|
{displayVpnButton && <PossiblyVpnBypassProxyButton reconnect={reconnect} />}
|
||||||
{replayActive && <Button label={`Download Packets Replay ${replayLogger?.contents.split('\n').length}L`} onClick={downloadPacketsReplay} />}
|
{replayActive && <Button label={`Download Packets Replay ${replayLogger?.contents.split('\n').length}L`} onClick={downloadPacketsReplay} />}
|
||||||
{wasDisconnected && lastAutoCapturedPackets && <Button label={`Inspect Last ${lastAutoCapturedPackets} Packets`} onClick={() => downloadAutoCapturedPackets()} />}
|
{wasDisconnected && lastAutoCapturedPackets && <Button label={`Inspect Last ${lastAutoCapturedPackets} Packets`} onClick={() => downloadAutoCapturedPackets()} />}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ const MessageLine = ({ message, currentPlayerName, chatOpened }: { message: Mess
|
||||||
return <li className={Object.entries(classes).filter(([, val]) => val).map(([name]) => name).join(' ')} data-time={message.timestamp ? new Date(message.timestamp).toLocaleString('en-US', { hour12: false }) : undefined}>
|
return <li className={Object.entries(classes).filter(([, val]) => val).map(([name]) => name).join(' ')} data-time={message.timestamp ? new Date(message.timestamp).toLocaleString('en-US', { hour12: false }) : undefined}>
|
||||||
{message.parts.map((msg, i) => {
|
{message.parts.map((msg, i) => {
|
||||||
// Check if this is a text part that might contain a mention
|
// Check if this is a text part that might contain a mention
|
||||||
if (typeof msg.text === 'string' && currentPlayerName) {
|
if (msg.text && currentPlayerName) {
|
||||||
const parts = msg.text.split(new RegExp(`(@${currentPlayerName})`, 'i'))
|
const parts = msg.text.split(new RegExp(`(@${currentPlayerName})`, 'i'))
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
return parts.map((txtPart, j) => {
|
return parts.map((txtPart, j) => {
|
||||||
|
|
@ -125,9 +125,7 @@ export default ({
|
||||||
const chatInput = useRef<HTMLInputElement>(null!)
|
const chatInput = useRef<HTMLInputElement>(null!)
|
||||||
const chatMessages = useRef<HTMLDivElement>(null)
|
const chatMessages = useRef<HTMLDivElement>(null)
|
||||||
const chatHistoryPos = useRef(sendHistoryRef.current.length)
|
const chatHistoryPos = useRef(sendHistoryRef.current.length)
|
||||||
const commandHistoryPos = useRef(0)
|
|
||||||
const inputCurrentlyEnteredValue = useRef('')
|
const inputCurrentlyEnteredValue = useRef('')
|
||||||
const commandHistoryRef = useRef(sendHistoryRef.current.filter((msg: string) => msg.startsWith('/')))
|
|
||||||
|
|
||||||
const { scrollToBottom, isAtBottom, wasAtBottom, currentlyAtBottom } = useScrollBehavior(chatMessages, { messages, opened })
|
const { scrollToBottom, isAtBottom, wasAtBottom, currentlyAtBottom } = useScrollBehavior(chatMessages, { messages, opened })
|
||||||
const [rightNowAtBottom, setRightNowAtBottom] = useState(false)
|
const [rightNowAtBottom, setRightNowAtBottom] = useState(false)
|
||||||
|
|
@ -144,9 +142,6 @@ export default ({
|
||||||
sendHistoryRef.current = newHistory
|
sendHistoryRef.current = newHistory
|
||||||
window.sessionStorage.chatHistory = JSON.stringify(newHistory)
|
window.sessionStorage.chatHistory = JSON.stringify(newHistory)
|
||||||
chatHistoryPos.current = newHistory.length
|
chatHistoryPos.current = newHistory.length
|
||||||
// Update command history (only messages starting with /)
|
|
||||||
commandHistoryRef.current = newHistory.filter((msg: string) => msg.startsWith('/'))
|
|
||||||
commandHistoryPos.current = commandHistoryRef.current.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const acceptComplete = (item: string) => {
|
const acceptComplete = (item: string) => {
|
||||||
|
|
@ -185,21 +180,6 @@ export default ({
|
||||||
updateInputValue(sendHistoryRef.current[chatHistoryPos.current] || inputCurrentlyEnteredValue.current || '')
|
updateInputValue(sendHistoryRef.current[chatHistoryPos.current] || inputCurrentlyEnteredValue.current || '')
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCommandArrowUp = () => {
|
|
||||||
if (commandHistoryPos.current === 0 || commandHistoryRef.current.length === 0) return
|
|
||||||
if (commandHistoryPos.current === commandHistoryRef.current.length) { // started navigating command history
|
|
||||||
inputCurrentlyEnteredValue.current = chatInput.current.value
|
|
||||||
}
|
|
||||||
commandHistoryPos.current--
|
|
||||||
updateInputValue(commandHistoryRef.current[commandHistoryPos.current] || '')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCommandArrowDown = () => {
|
|
||||||
if (commandHistoryPos.current === commandHistoryRef.current.length) return
|
|
||||||
commandHistoryPos.current++
|
|
||||||
updateInputValue(commandHistoryRef.current[commandHistoryPos.current] || inputCurrentlyEnteredValue.current || '')
|
|
||||||
}
|
|
||||||
|
|
||||||
const auxInputFocus = (direction: 'up' | 'down') => {
|
const auxInputFocus = (direction: 'up' | 'down') => {
|
||||||
chatInput.current.focus()
|
chatInput.current.focus()
|
||||||
if (direction === 'up') {
|
if (direction === 'up') {
|
||||||
|
|
@ -223,7 +203,6 @@ export default ({
|
||||||
updateInputValue(chatInputValueGlobal.value)
|
updateInputValue(chatInputValueGlobal.value)
|
||||||
chatInputValueGlobal.value = ''
|
chatInputValueGlobal.value = ''
|
||||||
chatHistoryPos.current = sendHistoryRef.current.length
|
chatHistoryPos.current = sendHistoryRef.current.length
|
||||||
commandHistoryPos.current = commandHistoryRef.current.length
|
|
||||||
if (!usingTouch) {
|
if (!usingTouch) {
|
||||||
chatInput.current.focus()
|
chatInput.current.focus()
|
||||||
}
|
}
|
||||||
|
|
@ -545,19 +524,9 @@ export default ({
|
||||||
onBlur={() => setIsInputFocused(false)}
|
onBlur={() => setIsInputFocused(false)}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.code === 'ArrowUp') {
|
if (e.code === 'ArrowUp') {
|
||||||
if (e.altKey) {
|
handleArrowUp()
|
||||||
handleCommandArrowUp()
|
|
||||||
e.preventDefault()
|
|
||||||
} else {
|
|
||||||
handleArrowUp()
|
|
||||||
}
|
|
||||||
} else if (e.code === 'ArrowDown') {
|
} else if (e.code === 'ArrowDown') {
|
||||||
if (e.altKey) {
|
handleArrowDown()
|
||||||
handleCommandArrowDown()
|
|
||||||
e.preventDefault()
|
|
||||||
} else {
|
|
||||||
handleArrowDown()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (e.code === 'Tab') {
|
if (e.code === 'Tab') {
|
||||||
if (completionItemsSource.length) {
|
if (completionItemsSource.length) {
|
||||||
|
|
|
||||||
|
|
@ -73,28 +73,16 @@ export default () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const builtinHandled = tryHandleBuiltinCommand(message)
|
const builtinHandled = tryHandleBuiltinCommand(message)
|
||||||
if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register')) && options.saveLoginPassword !== 'never') {
|
if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register'))) {
|
||||||
const savePassword = () => {
|
showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, () => {
|
||||||
let hadPassword = false
|
|
||||||
updateLoadedServerData((server) => {
|
updateLoadedServerData((server) => {
|
||||||
server.autoLogin ??= {}
|
server.autoLogin ??= {}
|
||||||
const password = message.split(' ')[1]
|
const password = message.split(' ')[1]
|
||||||
hadPassword = !!server.autoLogin[bot.username]
|
|
||||||
server.autoLogin[bot.username] = password
|
server.autoLogin[bot.username] = password
|
||||||
return { ...server }
|
return { ...server }
|
||||||
})
|
})
|
||||||
if (options.saveLoginPassword === 'always') {
|
hideNotification()
|
||||||
const message = hadPassword ? 'Password updated in browser for auto-login' : 'Password saved in browser for auto-login'
|
})
|
||||||
showNotification(message, undefined, false, undefined)
|
|
||||||
} else {
|
|
||||||
hideNotification()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (options.saveLoginPassword === 'prompt') {
|
|
||||||
showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, savePassword)
|
|
||||||
} else {
|
|
||||||
savePassword()
|
|
||||||
}
|
|
||||||
notificationProxy.id = 'auto-login'
|
notificationProxy.id = 'auto-login'
|
||||||
const listener = () => {
|
const listener = () => {
|
||||||
hideNotification()
|
hideNotification()
|
||||||
|
|
|
||||||
|
|
@ -98,16 +98,13 @@ export default ({
|
||||||
cursor: chunk ? 'pointer' : 'default',
|
cursor: chunk ? 'pointer' : 'default',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: `${tileSize}px`,
|
width: `${tileSize}px`,
|
||||||
flexDirection: 'column',
|
|
||||||
height: `${tileSize}px`,
|
height: `${tileSize}px`,
|
||||||
padding: 1,
|
|
||||||
// pre-wrap
|
// pre-wrap
|
||||||
whiteSpace: 'pre',
|
whiteSpace: 'pre',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{relX}, {relZ}{'\n'}
|
{relX}, {relZ}{'\n'}
|
||||||
{chunk?.lines[0]}{'\n'}
|
{chunk?.lines.join('\n')}
|
||||||
<span style={{ fontSize: `${fontSize * 0.8}px` }}>{chunk?.lines[1]}</span>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ import { useEffect, useState } from 'react'
|
||||||
import { useUtilsEffect } from '@zardoy/react-util'
|
import { useUtilsEffect } from '@zardoy/react-util'
|
||||||
import { WorldRendererCommon } from 'renderer/viewer/lib/worldrendererCommon'
|
import { WorldRendererCommon } from 'renderer/viewer/lib/worldrendererCommon'
|
||||||
import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree'
|
import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree'
|
||||||
import { Vec3 } from 'vec3'
|
|
||||||
import { generateSpiralMatrix } from 'flying-squid/dist/utils'
|
|
||||||
import Screen from './Screen'
|
import Screen from './Screen'
|
||||||
import ChunksDebug, { ChunkDebug } from './ChunksDebug'
|
import ChunksDebug, { ChunkDebug } from './ChunksDebug'
|
||||||
import { useIsModalActive } from './utilsApp'
|
import { useIsModalActive } from './utilsApp'
|
||||||
|
|
@ -14,10 +12,6 @@ const Inner = () => {
|
||||||
const [update, setUpdate] = useState(0)
|
const [update, setUpdate] = useState(0)
|
||||||
|
|
||||||
useUtilsEffect(({ interval }) => {
|
useUtilsEffect(({ interval }) => {
|
||||||
const up = () => {
|
|
||||||
// setUpdate(u => u + 1)
|
|
||||||
}
|
|
||||||
bot.on('chunkColumnLoad', up)
|
|
||||||
interval(
|
interval(
|
||||||
500,
|
500,
|
||||||
() => {
|
() => {
|
||||||
|
|
@ -26,48 +20,17 @@ const Inner = () => {
|
||||||
setUpdate(u => u + 1)
|
setUpdate(u => u + 1)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return () => {
|
|
||||||
bot.removeListener('chunkColumnLoad', up)
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Track first load time for all chunks
|
|
||||||
const allLoadTimes = Object.values(worldView!.debugChunksInfo)
|
|
||||||
.map(chunk => chunk?.loads[0]?.time ?? Infinity)
|
|
||||||
.filter(time => time !== Infinity)
|
|
||||||
.sort((a, b) => a - b)
|
|
||||||
|
|
||||||
const allSpiralChunks = Object.fromEntries(generateSpiralMatrix(worldView!.viewDistance).map(pos => [`${pos[0]},${pos[1]}`, pos]))
|
|
||||||
|
|
||||||
const mapChunk = (key: string, state: ChunkDebug['state']): ChunkDebug => {
|
const mapChunk = (key: string, state: ChunkDebug['state']): ChunkDebug => {
|
||||||
const x = Number(key.split(',')[0])
|
|
||||||
const z = Number(key.split(',')[1])
|
|
||||||
const chunkX = Math.floor(x / 16)
|
|
||||||
const chunkZ = Math.floor(z / 16)
|
|
||||||
|
|
||||||
delete allSpiralChunks[`${chunkX},${chunkZ}`]
|
|
||||||
const chunk = worldView!.debugChunksInfo[key]
|
const chunk = worldView!.debugChunksInfo[key]
|
||||||
const firstLoadTime = chunk?.loads[0]?.time
|
|
||||||
const loadIndex = firstLoadTime ? allLoadTimes.indexOf(firstLoadTime) + 1 : 0
|
|
||||||
// const timeSinceFirstLoad = firstLoadTime ? firstLoadTime - allLoadTimes[0] : 0
|
|
||||||
const timeSinceFirstLoad = firstLoadTime ? firstLoadTime - allLoadTimes[0] : 0
|
|
||||||
let line = ''
|
|
||||||
let line2 = ''
|
|
||||||
if (loadIndex) {
|
|
||||||
line = `${loadIndex}`
|
|
||||||
line2 = `${timeSinceFirstLoad}ms`
|
|
||||||
}
|
|
||||||
if (chunk?.loads.length > 1) {
|
|
||||||
line += ` - ${chunk.loads.length}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
x,
|
x: Number(key.split(',')[0]),
|
||||||
z,
|
z: Number(key.split(',')[1]),
|
||||||
state,
|
state,
|
||||||
lines: [line, line2],
|
lines: [String(chunk?.loads.length ?? 0)],
|
||||||
sidebarLines: [
|
sidebarLines: [
|
||||||
`loads: ${chunk?.loads?.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`,
|
`loads: ${chunk.loads?.map(l => `${l.reason} ${l.dataLength} ${l.time}`).join('\n')}`,
|
||||||
// `blockUpdates: ${chunk.blockUpdates}`,
|
// `blockUpdates: ${chunk.blockUpdates}`,
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
@ -92,22 +55,14 @@ const Inner = () => {
|
||||||
|
|
||||||
const chunksDone = Object.keys(world.finishedChunks).map(key => mapChunk(key, 'done'))
|
const chunksDone = Object.keys(world.finishedChunks).map(key => mapChunk(key, 'done'))
|
||||||
|
|
||||||
|
|
||||||
const chunksWaitingOrder = Object.values(allSpiralChunks).map(([x, z]) => {
|
|
||||||
const pos = new Vec3(x * 16, 0, z * 16)
|
|
||||||
if (bot.world.getColumnAt(pos) === null) return null
|
|
||||||
return mapChunk(`${pos.x},${pos.z}`, 'order-queued')
|
|
||||||
}).filter(a => !!a)
|
|
||||||
|
|
||||||
const allChunks = [
|
const allChunks = [
|
||||||
...chunksWaitingServer,
|
...chunksWaitingServer,
|
||||||
...chunksWaitingClient,
|
...chunksWaitingClient,
|
||||||
...clientProcessingChunks,
|
...clientProcessingChunks,
|
||||||
...chunksDone,
|
...chunksDone,
|
||||||
...chunksDoneEmpty,
|
...chunksDoneEmpty,
|
||||||
...chunksWaitingOrder,
|
|
||||||
]
|
]
|
||||||
return <Screen title={`Chunks Debug (avg: ${worldView!.lastChunkReceiveTimeAvg.toFixed(1)}ms)`}>
|
return <Screen title="Chunks Debug">
|
||||||
<ChunksDebug
|
<ChunksDebug
|
||||||
chunks={allChunks}
|
chunks={allChunks}
|
||||||
playerChunk={{
|
playerChunk={{
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,7 @@ export default () => {
|
||||||
const [packetsString, setPacketsString] = useState('')
|
const [packetsString, setPacketsString] = useState('')
|
||||||
const { showDebugHud } = useSnapshot(miscUiState)
|
const { showDebugHud } = useSnapshot(miscUiState)
|
||||||
const [pos, setPos] = useState<{ x: number, y: number, z: number }>({ x: 0, y: 0, z: 0 })
|
const [pos, setPos] = useState<{ x: number, y: number, z: number }>({ x: 0, y: 0, z: 0 })
|
||||||
const [skyL, setSkyL] = useState(0)
|
const [lightInfo, setLightInfo] = useState<{ sky: number, block: number, info: string }>({ sky: 0, block: 0, info: '-' })
|
||||||
const [blockL, setBlockL] = useState(0)
|
|
||||||
const [biomeId, setBiomeId] = useState(0)
|
const [biomeId, setBiomeId] = useState(0)
|
||||||
const [day, setDay] = useState(0)
|
const [day, setDay] = useState(0)
|
||||||
const [timeOfDay, setTimeOfDay] = useState(0)
|
const [timeOfDay, setTimeOfDay] = useState(0)
|
||||||
|
|
@ -122,9 +121,28 @@ export default () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const freqUpdateInterval = setInterval(() => {
|
const freqUpdateInterval = setInterval(() => {
|
||||||
|
const lightingEnabled = appViewer.inWorldRenderingConfig.enableLighting
|
||||||
|
const { clientSideLighting } = appViewer.inWorldRenderingConfig
|
||||||
|
let info = ''
|
||||||
|
if (lightingEnabled) {
|
||||||
|
if (clientSideLighting === 'none') {
|
||||||
|
info = 'Server Lighting'
|
||||||
|
} else if (clientSideLighting === 'full') {
|
||||||
|
info = 'Client Engine'
|
||||||
|
} else {
|
||||||
|
info = 'Server + Client Engine'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info = 'Lighting Disabled'
|
||||||
|
}
|
||||||
|
setLightInfo({
|
||||||
|
sky: bot.world.getSkyLight(bot.entity.position),
|
||||||
|
block: bot.world.getBlockLight(bot.entity.position),
|
||||||
|
info
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
setPos({ ...bot.entity.position })
|
setPos({ ...bot.entity.position })
|
||||||
setSkyL(bot.world.getSkyLight(bot.entity.position))
|
|
||||||
setBlockL(bot.world.getBlockLight(bot.entity.position))
|
|
||||||
setBiomeId(bot.world.getBiome(bot.entity.position))
|
setBiomeId(bot.world.getBiome(bot.entity.position))
|
||||||
setDimension(bot.game.dimension)
|
setDimension(bot.game.dimension)
|
||||||
setDay(bot.time.day)
|
setDay(bot.time.day)
|
||||||
|
|
@ -182,7 +200,7 @@ export default () => {
|
||||||
<p>Client TPS: {clientTps} {serverTps ? `Server TPS: ${serverTps.value} ${serverTps.frozen ? '(frozen)' : ''}` : ''}</p>
|
<p>Client TPS: {clientTps} {serverTps ? `Server TPS: ${serverTps.value} ${serverTps.frozen ? '(frozen)' : ''}` : ''}</p>
|
||||||
<p>Facing (viewer): {bot.entity.yaw.toFixed(3)} {bot.entity.pitch.toFixed(3)}</p>
|
<p>Facing (viewer): {bot.entity.yaw.toFixed(3)} {bot.entity.pitch.toFixed(3)}</p>
|
||||||
<p>Facing (minecraft): {quadsDescription[minecraftQuad.current]} ({minecraftYaw.current.toFixed(1)} {(bot.entity.pitch * -180 / Math.PI).toFixed(1)})</p>
|
<p>Facing (minecraft): {quadsDescription[minecraftQuad.current]} ({minecraftYaw.current.toFixed(1)} {(bot.entity.pitch * -180 / Math.PI).toFixed(1)})</p>
|
||||||
<p>Light: {blockL} ({skyL} sky)</p>
|
<p>Light: {lightInfo.block} ({lightInfo.sky} sky) ({lightInfo.info})</p>
|
||||||
|
|
||||||
<p>Biome: minecraft:{loadedData.biomesArray[biomeId]?.name ?? 'unknown biome'}</p>
|
<p>Biome: minecraft:{loadedData.biomesArray[biomeId]?.name ?? 'unknown biome'}</p>
|
||||||
<p>Day: {day} Time: {timeOfDay}</p>
|
<p>Day: {day} Time: {timeOfDay}</p>
|
||||||
|
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
/* eslint-disable no-await-in-loop */
|
|
||||||
import { useSnapshot } from 'valtio'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { getLoadedImage } from 'mc-assets/dist/utils'
|
|
||||||
import { createCanvas } from 'renderer/viewer/lib/utils'
|
|
||||||
|
|
||||||
const TEXTURE_UPDATE_INTERVAL = 100 // 5 times per second
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const { onFire, perspective } = useSnapshot(appViewer.playerState.reactive)
|
|
||||||
const [fireTextures, setFireTextures] = useState<string[]>([])
|
|
||||||
const [currentTextureIndex, setCurrentTextureIndex] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let animationFrameId: number
|
|
||||||
let lastTextureUpdate = 0
|
|
||||||
|
|
||||||
const updateTexture = (timestamp: number) => {
|
|
||||||
if (onFire && fireTextures.length > 0) {
|
|
||||||
if (timestamp - lastTextureUpdate >= TEXTURE_UPDATE_INTERVAL) {
|
|
||||||
setCurrentTextureIndex(prev => (prev + 1) % fireTextures.length)
|
|
||||||
lastTextureUpdate = timestamp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
animationFrameId = requestAnimationFrame(updateTexture)
|
|
||||||
}
|
|
||||||
|
|
||||||
animationFrameId = requestAnimationFrame(updateTexture)
|
|
||||||
return () => cancelAnimationFrame(animationFrameId)
|
|
||||||
}, [onFire, fireTextures])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadTextures = async () => {
|
|
||||||
const fireImageUrls: string[] = []
|
|
||||||
|
|
||||||
const { resourcesManager } = appViewer
|
|
||||||
const { blocksAtlasParser } = resourcesManager
|
|
||||||
if (!blocksAtlasParser?.atlas?.latest) {
|
|
||||||
console.warn('FireRenderer: Blocks atlas parser not available')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = Object.keys(blocksAtlasParser.atlas.latest.textures).filter(key => /^fire_\d+$/.exec(key))
|
|
||||||
for (const key of keys) {
|
|
||||||
const textureInfo = blocksAtlasParser.getTextureInfo(key) as { u: number, v: number, width?: number, height?: number }
|
|
||||||
if (textureInfo) {
|
|
||||||
const defaultSize = blocksAtlasParser.atlas.latest.tileSize
|
|
||||||
const imageWidth = blocksAtlasParser.atlas.latest.width
|
|
||||||
const imageHeight = blocksAtlasParser.atlas.latest.height
|
|
||||||
const textureWidth = textureInfo.width ?? defaultSize
|
|
||||||
const textureHeight = textureInfo.height ?? defaultSize
|
|
||||||
|
|
||||||
// Create a temporary canvas for the full texture
|
|
||||||
const tempCanvas = createCanvas(textureWidth, textureHeight)
|
|
||||||
const tempCtx = tempCanvas.getContext('2d')
|
|
||||||
if (tempCtx && blocksAtlasParser.latestImage) {
|
|
||||||
const image = await getLoadedImage(blocksAtlasParser.latestImage)
|
|
||||||
tempCtx.drawImage(
|
|
||||||
image,
|
|
||||||
textureInfo.u * imageWidth,
|
|
||||||
textureInfo.v * imageHeight,
|
|
||||||
textureWidth,
|
|
||||||
textureHeight,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
textureWidth,
|
|
||||||
textureHeight
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create final canvas with only top 20% of the texture
|
|
||||||
const finalHeight = Math.ceil(textureHeight * 0.4)
|
|
||||||
const canvas = createCanvas(textureWidth, finalHeight)
|
|
||||||
const ctx = canvas.getContext('2d')
|
|
||||||
if (ctx) {
|
|
||||||
// Draw only the top portion
|
|
||||||
ctx.drawImage(
|
|
||||||
tempCanvas,
|
|
||||||
0,
|
|
||||||
0, // Start from top
|
|
||||||
textureWidth,
|
|
||||||
finalHeight,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
textureWidth,
|
|
||||||
finalHeight
|
|
||||||
)
|
|
||||||
|
|
||||||
const blob = await canvas.convertToBlob()
|
|
||||||
const url = URL.createObjectURL(blob)
|
|
||||||
fireImageUrls.push(url)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setFireTextures(fireImageUrls)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load textures initially
|
|
||||||
if (appViewer.resourcesManager.currentResources) {
|
|
||||||
void loadTextures()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set up listener for texture updates
|
|
||||||
const onAssetsUpdated = () => {
|
|
||||||
void loadTextures()
|
|
||||||
}
|
|
||||||
appViewer.resourcesManager.on('assetsTexturesUpdated', onAssetsUpdated)
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
return () => {
|
|
||||||
appViewer.resourcesManager.off('assetsTexturesUpdated', onAssetsUpdated)
|
|
||||||
// Cleanup texture URLs
|
|
||||||
for (const url of fireTextures) URL.revokeObjectURL(url)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (!onFire || fireTextures.length === 0 || perspective !== 'first_person') return null
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className='fire-renderer-container'
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
height: '20dvh',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'flex-end',
|
|
||||||
overflow: 'hidden',
|
|
||||||
zIndex: -1
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
backgroundImage: `url(${fireTextures[currentTextureIndex]})`,
|
|
||||||
backgroundSize: '50% 100%',
|
|
||||||
backgroundPosition: 'center',
|
|
||||||
backgroundRepeat: 'repeat-x',
|
|
||||||
opacity: 0.7,
|
|
||||||
filter: 'brightness(1.2) contrast(1.2)',
|
|
||||||
mixBlendMode: 'screen'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { useRef, useEffect } from 'react'
|
||||||
import { subscribe, useSnapshot } from 'valtio'
|
import { subscribe, useSnapshot } from 'valtio'
|
||||||
import { useUtilsEffect } from '@zardoy/react-util'
|
import { useUtilsEffect } from '@zardoy/react-util'
|
||||||
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods'
|
||||||
import { isItemActivatableMobile } from 'mineflayer-mouse/dist/activatableItemsMobile'
|
|
||||||
import { options } from '../optionsStorage'
|
import { options } from '../optionsStorage'
|
||||||
import { activeModalStack, isGameActive, miscUiState } from '../globalState'
|
import { activeModalStack, isGameActive, miscUiState } from '../globalState'
|
||||||
import { onCameraMove, CameraMoveEvent } from '../cameraRotationControls'
|
import { onCameraMove, CameraMoveEvent } from '../cameraRotationControls'
|
||||||
|
|
@ -78,10 +77,7 @@ function GameInteractionOverlayInner ({
|
||||||
if (options.touchInteractionType === 'classic') {
|
if (options.touchInteractionType === 'classic') {
|
||||||
virtualClickTimeout ??= setTimeout(() => {
|
virtualClickTimeout ??= setTimeout(() => {
|
||||||
virtualClickActive = true
|
virtualClickActive = true
|
||||||
// If held item is activatable, use right click instead of left
|
document.dispatchEvent(new MouseEvent('mousedown', { button: 0 }))
|
||||||
const heldItemName = bot?.heldItem?.name
|
|
||||||
const isOnlyActivatable = heldItemName && isItemActivatableMobile(heldItemName, loadedData)
|
|
||||||
document.dispatchEvent(new MouseEvent('mousedown', { button: isOnlyActivatable ? 2 : 0 }))
|
|
||||||
}, touchStartBreakingBlockMs)
|
}, touchStartBreakingBlockMs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,23 +150,16 @@ function GameInteractionOverlayInner ({
|
||||||
|
|
||||||
if (virtualClickActive) {
|
if (virtualClickActive) {
|
||||||
// button 0 is left click
|
// button 0 is left click
|
||||||
// If held item is activatable, use right click instead of left
|
document.dispatchEvent(new MouseEvent('mouseup', { button: 0 }))
|
||||||
const heldItemName = bot?.heldItem?.name
|
|
||||||
const isOnlyActivatable = heldItemName && isItemActivatableMobile(heldItemName, loadedData)
|
|
||||||
document.dispatchEvent(new MouseEvent('mouseup', { button: isOnlyActivatable ? 2 : 0 }))
|
|
||||||
virtualClickActive = false
|
virtualClickActive = false
|
||||||
} else if (!capturedPointer.active.activateCameraMove && (Date.now() - capturedPointer.active.time < touchStartBreakingBlockMs)) {
|
} else if (!capturedPointer.active.activateCameraMove && (Date.now() - capturedPointer.active.time < touchStartBreakingBlockMs)) {
|
||||||
// single click action
|
// single click action
|
||||||
const MOUSE_BUTTON_RIGHT = 2
|
const MOUSE_BUTTON_RIGHT = 2
|
||||||
const MOUSE_BUTTON_LEFT = 0
|
const MOUSE_BUTTON_LEFT = 0
|
||||||
const heldItemName = bot?.heldItem?.name
|
|
||||||
const isOnlyActivatable = heldItemName && isItemActivatableMobile(heldItemName, loadedData)
|
|
||||||
const gonnaAttack = !!bot.mouse.getCursorState().entity || !!videoCursorInteraction()
|
const gonnaAttack = !!bot.mouse.getCursorState().entity || !!videoCursorInteraction()
|
||||||
// If not attacking entity and item is activatable, use right click for breaking
|
document.dispatchEvent(new MouseEvent('mousedown', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT }))
|
||||||
const useButton = !gonnaAttack && isOnlyActivatable ? MOUSE_BUTTON_RIGHT : (gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT)
|
|
||||||
document.dispatchEvent(new MouseEvent('mousedown', { button: useButton }))
|
|
||||||
bot.mouse.update()
|
bot.mouse.update()
|
||||||
document.dispatchEvent(new MouseEvent('mouseup', { button: useButton }))
|
document.dispatchEvent(new MouseEvent('mouseup', { button: gonnaAttack ? MOUSE_BUTTON_LEFT : MOUSE_BUTTON_RIGHT }))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenTouches > 0) {
|
if (screenTouches > 0) {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ const HotbarInner = () => {
|
||||||
container.current.appendChild(inv.canvas)
|
container.current.appendChild(inv.canvas)
|
||||||
const upHotbarItems = () => {
|
const upHotbarItems = () => {
|
||||||
if (!appViewer.resourcesManager?.itemsAtlasParser) return
|
if (!appViewer.resourcesManager?.itemsAtlasParser) return
|
||||||
globalThis.debugHotbarItems = upInventoryItems(true, inv)
|
upInventoryItems(true, inv)
|
||||||
}
|
}
|
||||||
|
|
||||||
canvasManager.canvas.onclick = (e) => {
|
canvasManager.canvas.onclick = (e) => {
|
||||||
|
|
@ -127,7 +127,6 @@ const HotbarInner = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.debugUpHotbarItems = upHotbarItems
|
|
||||||
upHotbarItems()
|
upHotbarItems()
|
||||||
bot.inventory.on('updateSlot', upHotbarItems)
|
bot.inventory.on('updateSlot', upHotbarItems)
|
||||||
appViewer.resourcesManager.on('assetsTexturesUpdated', upHotbarItems)
|
appViewer.resourcesManager.on('assetsTexturesUpdated', upHotbarItems)
|
||||||
|
|
@ -201,28 +200,17 @@ const HotbarInner = () => {
|
||||||
<ItemName itemKey={itemKey} />
|
<ItemName itemKey={itemKey} />
|
||||||
<Portal>
|
<Portal>
|
||||||
<div
|
<div
|
||||||
className='hotbar-fullscreen-container'
|
className='hotbar' ref={container} style={{
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: 0,
|
|
||||||
left: 0,
|
left: 0,
|
||||||
width: '100dvw',
|
right: 0,
|
||||||
height: '100dvh',
|
|
||||||
zIndex: hasModals ? 1 : 8,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
zIndex: hasModals ? 1 : 8,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
}}>
|
bottom: 'var(--hud-bottom-raw)'
|
||||||
<div
|
}}
|
||||||
className='hotbar'
|
/>
|
||||||
ref={container}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
bottom: 'var(--hud-bottom-raw)'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
</SharedHudVars>
|
</SharedHudVars>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { Effect } from 'mineflayer'
|
||||||
import { inGameError } from '../utils'
|
import { inGameError } from '../utils'
|
||||||
import { fsState } from '../loadSave'
|
import { fsState } from '../loadSave'
|
||||||
import { gameAdditionalState, miscUiState } from '../globalState'
|
import { gameAdditionalState, miscUiState } from '../globalState'
|
||||||
|
import { options } from '../optionsStorage'
|
||||||
import IndicatorEffects, { EffectType, defaultIndicatorsState } from './IndicatorEffects'
|
import IndicatorEffects, { EffectType, defaultIndicatorsState } from './IndicatorEffects'
|
||||||
import { images } from './effectsImages'
|
import { images } from './effectsImages'
|
||||||
|
|
||||||
|
|
@ -66,6 +67,7 @@ export default ({ displayEffects = true, displayIndicators = true }: { displayEf
|
||||||
const { mesherWork } = useSnapshot(appViewer.rendererState).world
|
const { mesherWork } = useSnapshot(appViewer.rendererState).world
|
||||||
|
|
||||||
const { hasErrors } = useSnapshot(miscUiState)
|
const { hasErrors } = useSnapshot(miscUiState)
|
||||||
|
const { disabledUiParts } = useSnapshot(options)
|
||||||
const { isReadonly, openReadOperations, openWriteOperations } = useSnapshot(fsState)
|
const { isReadonly, openReadOperations, openWriteOperations } = useSnapshot(fsState)
|
||||||
const { noConnection, poorConnection } = useSnapshot(gameAdditionalState)
|
const { noConnection, poorConnection } = useSnapshot(gameAdditionalState)
|
||||||
const allIndicators: typeof defaultIndicatorsState = {
|
const allIndicators: typeof defaultIndicatorsState = {
|
||||||
|
|
@ -120,7 +122,7 @@ export default ({ displayEffects = true, displayIndicators = true }: { displayEf
|
||||||
return <IndicatorEffects
|
return <IndicatorEffects
|
||||||
indicators={allIndicators}
|
indicators={allIndicators}
|
||||||
effects={effects}
|
effects={effects}
|
||||||
displayIndicators={displayIndicators}
|
displayIndicators
|
||||||
displayEffects={displayEffects}
|
displayEffects
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
.monaco-editor-container {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-editor-title {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #fff;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-editor-wrapper {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 80vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
border: 3px solid #000;
|
|
||||||
background-color: #000;
|
|
||||||
padding: 3px;
|
|
||||||
box-shadow: inset 0 0 0 1px #fff, inset 0 0 0 2px #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-editor-close {
|
|
||||||
position: fixed;
|
|
||||||
top: 16px;
|
|
||||||
left: 16px;
|
|
||||||
z-index: 1001;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.monaco-editor-container {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.monaco-editor-wrapper {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
.monaco-editor-close {
|
|
||||||
top: 8px;
|
|
||||||
left: 8px;
|
|
||||||
}
|
|
||||||
.monaco-editor-title {
|
|
||||||
/* todo: make it work on mobile */
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
import { proxy, useSnapshot } from 'valtio'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
import { Editor } from '@monaco-editor/react'
|
|
||||||
import PixelartIcon, { pixelartIcons } from '../react/PixelartIcon'
|
|
||||||
import { useIsModalActive } from '../react/utilsApp'
|
|
||||||
import { showNotification } from '../react/NotificationProvider'
|
|
||||||
import { hideModal, showModal } from '../globalState'
|
|
||||||
import { ideState, saveIde } from '../core/ideChannels'
|
|
||||||
import './MonacoEditor.css'
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const { contents, line, column, id, language, title } = useSnapshot(ideState)
|
|
||||||
const isModalActive = useIsModalActive('monaco-editor')
|
|
||||||
const bodyFont = getComputedStyle(document.body).fontFamily
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (id && !isModalActive) {
|
|
||||||
showModal({ reactType: 'monaco-editor' })
|
|
||||||
}
|
|
||||||
if (!id && isModalActive) {
|
|
||||||
hideModal()
|
|
||||||
}
|
|
||||||
}, [id])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isModalActive && id) {
|
|
||||||
try {
|
|
||||||
saveIde()
|
|
||||||
} catch (err) {
|
|
||||||
reportError(err)
|
|
||||||
showNotification('Failed to save the editor', 'Please try again', true)
|
|
||||||
}
|
|
||||||
ideState.id = ''
|
|
||||||
ideState.contents = ''
|
|
||||||
}
|
|
||||||
}, [isModalActive])
|
|
||||||
|
|
||||||
if (!isModalActive) return null
|
|
||||||
|
|
||||||
return <div className="monaco-editor-container">
|
|
||||||
<div className="monaco-editor-close">
|
|
||||||
<PixelartIcon
|
|
||||||
iconName={pixelartIcons.close}
|
|
||||||
width={26}
|
|
||||||
onClick={() => {
|
|
||||||
hideModal()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="monaco-editor-title">
|
|
||||||
{title}
|
|
||||||
</div>
|
|
||||||
<div className="monaco-editor-wrapper">
|
|
||||||
<Editor
|
|
||||||
height="100%"
|
|
||||||
width="100%"
|
|
||||||
language={language}
|
|
||||||
theme='vs-dark'
|
|
||||||
line={line}
|
|
||||||
onChange={(value) => {
|
|
||||||
ideState.contents = value ?? ''
|
|
||||||
}}
|
|
||||||
value={contents}
|
|
||||||
options={{
|
|
||||||
fontFamily: bodyFont,
|
|
||||||
minimap: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
@ -4,13 +4,11 @@ import { titleCase } from 'title-case'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { disabledSettings, options, qsOptions } from '../optionsStorage'
|
import { disabledSettings, options, qsOptions } from '../optionsStorage'
|
||||||
import { hideAllModals, miscUiState } from '../globalState'
|
import { hideAllModals, miscUiState } from '../globalState'
|
||||||
import { reloadChunksAction } from '../controls'
|
|
||||||
import Button from './Button'
|
import Button from './Button'
|
||||||
import Slider from './Slider'
|
import Slider from './Slider'
|
||||||
import Screen from './Screen'
|
import Screen from './Screen'
|
||||||
import { showOptionsModal } from './SelectOption'
|
import { showOptionsModal } from './SelectOption'
|
||||||
import PixelartIcon, { pixelartIcons } from './PixelartIcon'
|
import PixelartIcon, { pixelartIcons } from './PixelartIcon'
|
||||||
import { reconnectReload } from './AppStatusProvider'
|
|
||||||
|
|
||||||
type GeneralItem<T extends string | number | boolean> = {
|
type GeneralItem<T extends string | number | boolean> = {
|
||||||
id?: string
|
id?: string
|
||||||
|
|
@ -20,8 +18,7 @@ type GeneralItem<T extends string | number | boolean> = {
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
// description?: string
|
// description?: string
|
||||||
enableWarning?: string
|
enableWarning?: string
|
||||||
requiresRestart?: boolean
|
willHaveNoEffect?: boolean
|
||||||
requiresChunksReload?: boolean
|
|
||||||
values?: Array<T | [T, string]>
|
values?: Array<T | [T, string]>
|
||||||
disableIf?: [option: keyof typeof options, value: any]
|
disableIf?: [option: keyof typeof options, value: any]
|
||||||
}
|
}
|
||||||
|
|
@ -59,14 +56,7 @@ const useCommonComponentsProps = (item: OptionMeta) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ignoreReloadWarningsCache = new Set<string>()
|
export const OptionButton = ({ item }: { item: Extract<OptionMeta, { type: 'toggle' }> }) => {
|
||||||
|
|
||||||
export const OptionButton = ({ item, onClick, valueText, cacheKey }: {
|
|
||||||
item: Extract<OptionMeta, { type: 'toggle' }>,
|
|
||||||
onClick?: () => void,
|
|
||||||
valueText?: string,
|
|
||||||
cacheKey?: string,
|
|
||||||
}) => {
|
|
||||||
const { disabledBecauseOfSetting } = useCommonComponentsProps(item)
|
const { disabledBecauseOfSetting } = useCommonComponentsProps(item)
|
||||||
|
|
||||||
const optionValue = useSnapshot(options)[item.id!]
|
const optionValue = useSnapshot(options)[item.id!]
|
||||||
|
|
@ -94,63 +84,40 @@ export const OptionButton = ({ item, onClick, valueText, cacheKey }: {
|
||||||
|
|
||||||
return <Button
|
return <Button
|
||||||
data-setting={item.id}
|
data-setting={item.id}
|
||||||
label={`${translate(item.text)}: ${translate(valueText ?? valuesTitlesMap[optionValue])}`}
|
label={`${item.text}: ${valuesTitlesMap[optionValue]}`}
|
||||||
|
// label={`${item.text}:`}
|
||||||
|
// postLabel={valuesTitlesMap[optionValue]}
|
||||||
onClick={async (event) => {
|
onClick={async (event) => {
|
||||||
if (disabledReason) {
|
if (disabledReason) {
|
||||||
await showOptionsModal(`${translate('The option is not available')}: ${disabledReason}`, [])
|
await showOptionsModal(`The option is unavailable. ${disabledReason}`, [])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (item.enableWarning && !options[item.id!]) {
|
if (item.enableWarning && !options[item.id!]) {
|
||||||
const result = await showOptionsModal(item.enableWarning, ['Enable'])
|
const result = await showOptionsModal(item.enableWarning, ['Enable'])
|
||||||
if (!result) return
|
if (!result) return
|
||||||
}
|
}
|
||||||
onClick?.()
|
const { values } = item
|
||||||
if (item.id) {
|
if (values) {
|
||||||
const { values } = item
|
const getOptionValue = (arrItem) => {
|
||||||
if (values) {
|
if (typeof arrItem === 'string') {
|
||||||
const getOptionValue = (arrItem) => {
|
return arrItem
|
||||||
if (typeof arrItem === 'string') {
|
|
||||||
return arrItem
|
|
||||||
} else {
|
|
||||||
return arrItem[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const currentIndex = values.findIndex((value) => {
|
|
||||||
return getOptionValue(value) === optionValue
|
|
||||||
})
|
|
||||||
if (currentIndex === -1) {
|
|
||||||
options[item.id] = getOptionValue(values[0])
|
|
||||||
} else {
|
} else {
|
||||||
const nextIndex = event.shiftKey
|
return arrItem[0]
|
||||||
? (currentIndex - 1 + values.length) % values.length
|
|
||||||
: (currentIndex + 1) % values.length
|
|
||||||
options[item.id] = getOptionValue(values[nextIndex])
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
const currentIndex = values.findIndex((value) => {
|
||||||
|
return getOptionValue(value) === optionValue
|
||||||
|
})
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
options[item.id!] = getOptionValue(values[0])
|
||||||
} else {
|
} else {
|
||||||
options[item.id] = !options[item.id]
|
const nextIndex = event.shiftKey
|
||||||
}
|
? (currentIndex - 1 + values.length) % values.length
|
||||||
}
|
: (currentIndex + 1) % values.length
|
||||||
|
options[item.id!] = getOptionValue(values[nextIndex])
|
||||||
const toCacheKey = cacheKey ?? item.id ?? ''
|
|
||||||
if (toCacheKey && !ignoreReloadWarningsCache.has(toCacheKey)) {
|
|
||||||
ignoreReloadWarningsCache.add(toCacheKey)
|
|
||||||
|
|
||||||
if (item.requiresRestart) {
|
|
||||||
const result = await showOptionsModal(translate('The option requires a restart to take effect'), ['Restart', 'I will do it later'], {
|
|
||||||
cancel: false,
|
|
||||||
})
|
|
||||||
if (result) {
|
|
||||||
reconnectReload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (item.requiresChunksReload) {
|
|
||||||
const result = await showOptionsModal(translate('The option requires a chunks reload to take effect'), ['Reload', 'I will do it later'], {
|
|
||||||
cancel: false,
|
|
||||||
})
|
|
||||||
if (result) {
|
|
||||||
reloadChunksAction()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
options[item.id!] = !options[item.id!]
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title={disabledReason ? `${disabledReason} | ${item.tooltip}` : item.tooltip}
|
title={disabledReason ? `${disabledReason} | ${item.tooltip}` : item.tooltip}
|
||||||
|
|
@ -161,15 +128,7 @@ export const OptionButton = ({ item, onClick, valueText, cacheKey }: {
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OptionSlider = ({
|
export const OptionSlider = ({ item }: { item: Extract<OptionMeta, { type: 'slider' }> }) => {
|
||||||
item,
|
|
||||||
onChange,
|
|
||||||
valueOverride
|
|
||||||
}: {
|
|
||||||
item: Extract<OptionMeta, { type: 'slider' }>
|
|
||||||
onChange?: (value: number) => void
|
|
||||||
valueOverride?: number
|
|
||||||
}) => {
|
|
||||||
const { disabledBecauseOfSetting } = useCommonComponentsProps(item)
|
const { disabledBecauseOfSetting } = useCommonComponentsProps(item)
|
||||||
|
|
||||||
const optionValue = useSnapshot(options)[item.id!]
|
const optionValue = useSnapshot(options)[item.id!]
|
||||||
|
|
@ -182,7 +141,7 @@ export const OptionSlider = ({
|
||||||
return (
|
return (
|
||||||
<Slider
|
<Slider
|
||||||
label={item.text!}
|
label={item.text!}
|
||||||
value={valueOverride ?? options[item.id!]}
|
value={options[item.id!]}
|
||||||
data-setting={item.id}
|
data-setting={item.id}
|
||||||
disabledReason={isLocked(item) ? 'qs' : disabledBecauseOfSetting ? `Disabled because ${item.disableIf![0]} is ${item.disableIf![1]}` : item.disabledReason}
|
disabledReason={isLocked(item) ? 'qs' : disabledBecauseOfSetting ? `Disabled because ${item.disableIf![0]} is ${item.disableIf![1]}` : item.disabledReason}
|
||||||
min={item.min}
|
min={item.min}
|
||||||
|
|
@ -192,7 +151,6 @@ export const OptionSlider = ({
|
||||||
updateOnDragEnd={item.delayApply}
|
updateOnDragEnd={item.delayApply}
|
||||||
updateValue={(value) => {
|
updateValue={(value) => {
|
||||||
options[item.id!] = value
|
options[item.id!] = value
|
||||||
onChange?.(value)
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,554 +0,0 @@
|
||||||
import { proxy, useSnapshot, subscribe } from 'valtio'
|
|
||||||
import { useEffect, useMemo, useRef } from 'react'
|
|
||||||
import * as THREE from 'three'
|
|
||||||
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
|
||||||
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
|
||||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
|
||||||
import { applySkinToPlayerObject, createPlayerObject, PlayerObjectType } from '../../renderer/viewer/lib/createPlayerObject'
|
|
||||||
import { currentScaling } from '../scaleInterface'
|
|
||||||
import { activeModalStack } from '../globalState'
|
|
||||||
|
|
||||||
THREE.ColorManagement.enabled = false
|
|
||||||
|
|
||||||
export const modelViewerState = proxy({
|
|
||||||
model: undefined as undefined | {
|
|
||||||
models?: string[] // Array of model URLs (URL itself is the cache key)
|
|
||||||
steveModelSkin?: string
|
|
||||||
debug?: boolean
|
|
||||||
// absolute positioning
|
|
||||||
positioning: {
|
|
||||||
windowWidth: number
|
|
||||||
windowHeight: number
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
scaled?: boolean
|
|
||||||
onlyInitialScale?: boolean
|
|
||||||
followCursor?: boolean
|
|
||||||
}
|
|
||||||
modelCustomization?: { [modelUrl: string]: { color?: string, opacity?: number, metalness?: number, roughness?: number } }
|
|
||||||
resetRotationOnReleae?: boolean
|
|
||||||
continiousRender?: boolean
|
|
||||||
alwaysRender?: boolean
|
|
||||||
}
|
|
||||||
})
|
|
||||||
globalThis.modelViewerState = modelViewerState
|
|
||||||
|
|
||||||
// Global debug function to get camera and model values
|
|
||||||
globalThis.getModelViewerValues = () => {
|
|
||||||
const scene = globalThis.sceneRef?.current
|
|
||||||
if (!scene) return null
|
|
||||||
|
|
||||||
const { camera, playerObject } = scene
|
|
||||||
if (!playerObject) return null
|
|
||||||
|
|
||||||
const wrapper = playerObject.parent
|
|
||||||
if (!wrapper) return null
|
|
||||||
|
|
||||||
const box = new THREE.Box3().setFromObject(wrapper)
|
|
||||||
const size = box.getSize(new THREE.Vector3())
|
|
||||||
const center = box.getCenter(new THREE.Vector3())
|
|
||||||
|
|
||||||
return {
|
|
||||||
camera: {
|
|
||||||
position: camera.position.clone(),
|
|
||||||
fov: camera.fov,
|
|
||||||
aspect: camera.aspect
|
|
||||||
},
|
|
||||||
model: {
|
|
||||||
position: wrapper.position.clone(),
|
|
||||||
rotation: wrapper.rotation.clone(),
|
|
||||||
scale: wrapper.scale.clone(),
|
|
||||||
size,
|
|
||||||
center
|
|
||||||
},
|
|
||||||
cursor: {
|
|
||||||
position: globalThis.cursorPosition || { x: 0, y: 0 },
|
|
||||||
normalized: globalThis.cursorPosition ? {
|
|
||||||
x: globalThis.cursorPosition.x * 2 - 1,
|
|
||||||
y: globalThis.cursorPosition.y * 2 - 1
|
|
||||||
} : { x: 0, y: 0 }
|
|
||||||
},
|
|
||||||
visibleArea: {
|
|
||||||
height: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z,
|
|
||||||
width: 2 * Math.tan(camera.fov * Math.PI / 180 / 2) * camera.position.z * camera.aspect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(activeModalStack, () => {
|
|
||||||
if (!modelViewerState.model || !modelViewerState.model?.alwaysRender) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (activeModalStack.length === 0) {
|
|
||||||
modelViewerState.model = undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default () => {
|
|
||||||
const { model } = useSnapshot(modelViewerState)
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
|
||||||
const sceneRef = useRef<{
|
|
||||||
scene: THREE.Scene
|
|
||||||
camera: THREE.PerspectiveCamera
|
|
||||||
renderer: THREE.WebGLRenderer
|
|
||||||
controls: OrbitControls
|
|
||||||
playerObject?: PlayerObjectType
|
|
||||||
dispose: () => void
|
|
||||||
}>()
|
|
||||||
const initialScale = useMemo(() => {
|
|
||||||
return currentScaling.scale
|
|
||||||
}, [])
|
|
||||||
globalThis.sceneRef = sceneRef
|
|
||||||
|
|
||||||
// Cursor following state
|
|
||||||
const cursorPosition = useRef({ x: 0, y: 0 })
|
|
||||||
const isFollowingCursor = useRef(false)
|
|
||||||
|
|
||||||
// Model management state
|
|
||||||
const loadedModels = useRef<Map<string, THREE.Object3D>>(new Map())
|
|
||||||
const modelLoaders = useRef<Map<string, GLTFLoader | OBJLoader>>(new Map())
|
|
||||||
|
|
||||||
// Model management functions
|
|
||||||
const loadModel = (modelUrl: string) => {
|
|
||||||
if (loadedModels.current.has(modelUrl)) return // Already loaded
|
|
||||||
|
|
||||||
const isGLTF = modelUrl.toLowerCase().endsWith('.gltf') || modelUrl.toLowerCase().endsWith('.glb')
|
|
||||||
const loader = isGLTF ? new GLTFLoader() : new OBJLoader()
|
|
||||||
modelLoaders.current.set(modelUrl, loader)
|
|
||||||
|
|
||||||
const onLoad = (object: THREE.Object3D) => {
|
|
||||||
// Apply customization if available and enable shadows
|
|
||||||
const customization = model?.modelCustomization?.[modelUrl]
|
|
||||||
object.traverse((child) => {
|
|
||||||
if (child instanceof THREE.Mesh) {
|
|
||||||
// Enable shadow casting and receiving for all meshes
|
|
||||||
child.castShadow = true
|
|
||||||
child.receiveShadow = true
|
|
||||||
|
|
||||||
if (child.material && customization) {
|
|
||||||
const material = child.material as THREE.MeshStandardMaterial
|
|
||||||
if (customization.color) {
|
|
||||||
material.color.setHex(parseInt(customization.color.replace('#', ''), 16))
|
|
||||||
}
|
|
||||||
if (customization.opacity !== undefined) {
|
|
||||||
material.opacity = customization.opacity
|
|
||||||
material.transparent = customization.opacity < 1
|
|
||||||
}
|
|
||||||
if (customization.metalness !== undefined) {
|
|
||||||
material.metalness = customization.metalness
|
|
||||||
}
|
|
||||||
if (customization.roughness !== undefined) {
|
|
||||||
material.roughness = customization.roughness
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Center and scale model
|
|
||||||
const box = new THREE.Box3().setFromObject(object)
|
|
||||||
const center = box.getCenter(new THREE.Vector3())
|
|
||||||
const size = box.getSize(new THREE.Vector3())
|
|
||||||
const maxDim = Math.max(size.x, size.y, size.z)
|
|
||||||
const scale = 2 / maxDim
|
|
||||||
object.scale.setScalar(scale)
|
|
||||||
object.position.sub(center.multiplyScalar(scale))
|
|
||||||
|
|
||||||
// Store the model using URL as key
|
|
||||||
loadedModels.current.set(modelUrl, object)
|
|
||||||
sceneRef.current?.scene.add(object)
|
|
||||||
|
|
||||||
// Trigger render
|
|
||||||
if (sceneRef.current) {
|
|
||||||
setTimeout(() => {
|
|
||||||
const render = () => sceneRef.current?.renderer.render(sceneRef.current.scene, sceneRef.current.camera)
|
|
||||||
render()
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isGLTF) {
|
|
||||||
(loader as GLTFLoader).load(modelUrl, (gltf) => {
|
|
||||||
onLoad(gltf.scene)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
(loader as OBJLoader).load(modelUrl, onLoad)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeModel = (modelUrl: string) => {
|
|
||||||
const model = loadedModels.current.get(modelUrl)
|
|
||||||
if (model) {
|
|
||||||
sceneRef.current?.scene.remove(model)
|
|
||||||
model.traverse((child) => {
|
|
||||||
if (child instanceof THREE.Mesh) {
|
|
||||||
if (child.material) {
|
|
||||||
if (Array.isArray(child.material)) {
|
|
||||||
for (const mat of child.material) {
|
|
||||||
mat.dispose()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
child.material.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (child.geometry) {
|
|
||||||
child.geometry.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
loadedModels.current.delete(modelUrl)
|
|
||||||
}
|
|
||||||
modelLoaders.current.delete(modelUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe to model changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (!modelViewerState.model?.models) return
|
|
||||||
|
|
||||||
const modelsChanged = () => {
|
|
||||||
const currentModels = modelViewerState.model?.models || []
|
|
||||||
const currentModelUrls = new Set(currentModels)
|
|
||||||
const loadedModelUrls = new Set(loadedModels.current.keys())
|
|
||||||
|
|
||||||
// Remove models that are no longer in the state
|
|
||||||
for (const modelUrl of loadedModelUrls) {
|
|
||||||
if (!currentModelUrls.has(modelUrl)) {
|
|
||||||
removeModel(modelUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new models
|
|
||||||
for (const modelUrl of currentModels) {
|
|
||||||
if (!loadedModelUrls.has(modelUrl)) {
|
|
||||||
loadModel(modelUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const unsubscribe = subscribe(modelViewerState.model.models, modelsChanged)
|
|
||||||
|
|
||||||
let unmounted = false
|
|
||||||
setTimeout(() => {
|
|
||||||
if (unmounted) return
|
|
||||||
modelsChanged()
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unmounted = true
|
|
||||||
unsubscribe?.()
|
|
||||||
}
|
|
||||||
}, [model?.models])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!model || !containerRef.current) return
|
|
||||||
|
|
||||||
// Setup scene
|
|
||||||
const scene = new THREE.Scene()
|
|
||||||
scene.background = null // Transparent background
|
|
||||||
|
|
||||||
// Setup camera with optimal settings for player model viewing
|
|
||||||
const camera = new THREE.PerspectiveCamera(
|
|
||||||
50, // Reduced FOV for better model viewing
|
|
||||||
model.positioning.width / model.positioning.height,
|
|
||||||
0.1,
|
|
||||||
1000
|
|
||||||
)
|
|
||||||
camera.position.set(0, 0, 3) // Position camera to view player model optimally
|
|
||||||
|
|
||||||
// Setup renderer with pixel density awareness
|
|
||||||
const renderer = new THREE.WebGLRenderer({ alpha: true })
|
|
||||||
let scale = window.devicePixelRatio || 1
|
|
||||||
if (modelViewerState.model?.positioning.scaled) {
|
|
||||||
scale *= currentScaling.scale
|
|
||||||
}
|
|
||||||
renderer.setPixelRatio(scale)
|
|
||||||
renderer.setSize(model.positioning.width, model.positioning.height)
|
|
||||||
|
|
||||||
// Enable shadow rendering for depth and realism
|
|
||||||
renderer.shadowMap.enabled = true
|
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap // Soft shadows for better quality
|
|
||||||
renderer.shadowMap.autoUpdate = true
|
|
||||||
|
|
||||||
containerRef.current.appendChild(renderer.domElement)
|
|
||||||
|
|
||||||
// Setup controls
|
|
||||||
const controls = new OrbitControls(camera, renderer.domElement)
|
|
||||||
// controls.enableZoom = false
|
|
||||||
// controls.enablePan = false
|
|
||||||
controls.minPolarAngle = Math.PI / 2 // Lock vertical rotation
|
|
||||||
controls.maxPolarAngle = Math.PI / 2
|
|
||||||
controls.enableDamping = true
|
|
||||||
controls.dampingFactor = 0.05
|
|
||||||
|
|
||||||
// Add ambient light for overall illumination
|
|
||||||
const ambientLight = new THREE.AmbientLight(0xff_ff_ff, 0.4) // Reduced intensity to allow shadows
|
|
||||||
scene.add(ambientLight)
|
|
||||||
|
|
||||||
// Add directional light for shadows and depth (similar to Minecraft inventory lighting)
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xff_ff_ff, 0.6)
|
|
||||||
directionalLight.position.set(2, 2, 2) // Position light from top-right-front
|
|
||||||
directionalLight.target.position.set(0, 0, 0) // Point towards center of scene
|
|
||||||
|
|
||||||
// Configure shadow properties for optimal quality
|
|
||||||
directionalLight.castShadow = true
|
|
||||||
directionalLight.shadow.mapSize.width = 2048 // High resolution shadow map
|
|
||||||
directionalLight.shadow.mapSize.height = 2048
|
|
||||||
directionalLight.shadow.camera.near = 0.1
|
|
||||||
directionalLight.shadow.camera.far = 10
|
|
||||||
directionalLight.shadow.camera.left = -3
|
|
||||||
directionalLight.shadow.camera.right = 3
|
|
||||||
directionalLight.shadow.camera.top = 3
|
|
||||||
directionalLight.shadow.camera.bottom = -3
|
|
||||||
directionalLight.shadow.bias = -0.0001 // Reduce shadow acne
|
|
||||||
|
|
||||||
scene.add(directionalLight)
|
|
||||||
scene.add(directionalLight.target)
|
|
||||||
|
|
||||||
// Cursor following function
|
|
||||||
const updatePlayerLookAt = () => {
|
|
||||||
if (!isFollowingCursor.current || !sceneRef.current?.playerObject) return
|
|
||||||
|
|
||||||
const { playerObject } = sceneRef.current
|
|
||||||
const { x, y } = cursorPosition.current
|
|
||||||
|
|
||||||
// Convert 0-1 cursor position to normalized coordinates (-1 to 1)
|
|
||||||
const normalizedX = x * 2 - 1
|
|
||||||
const normalizedY = y * 2 - 1 // Inverted: top of screen = negative pitch, bottom = positive pitch
|
|
||||||
|
|
||||||
// Calculate head rotation based on cursor position
|
|
||||||
// Limit head movement to realistic angles
|
|
||||||
const maxHeadYaw = Math.PI / 3 // 60 degrees
|
|
||||||
const maxHeadPitch = Math.PI / 4 // 45 degrees
|
|
||||||
|
|
||||||
const headYaw = normalizedX * maxHeadYaw
|
|
||||||
const headPitch = normalizedY * maxHeadPitch
|
|
||||||
|
|
||||||
// Apply head rotation with smooth interpolation
|
|
||||||
const lerpFactor = 0.1 // Smooth interpolation factor
|
|
||||||
playerObject.skin.head.rotation.y = THREE.MathUtils.lerp(
|
|
||||||
playerObject.skin.head.rotation.y,
|
|
||||||
headYaw,
|
|
||||||
lerpFactor
|
|
||||||
)
|
|
||||||
playerObject.skin.head.rotation.x = THREE.MathUtils.lerp(
|
|
||||||
playerObject.skin.head.rotation.x,
|
|
||||||
headPitch,
|
|
||||||
lerpFactor
|
|
||||||
)
|
|
||||||
|
|
||||||
// Apply slight body rotation for more natural movement
|
|
||||||
const bodyYaw = headYaw * 0.3 // Body follows head but with less rotation
|
|
||||||
playerObject.rotation.y = THREE.MathUtils.lerp(
|
|
||||||
playerObject.rotation.y,
|
|
||||||
bodyYaw,
|
|
||||||
lerpFactor * 0.5 // Slower body movement
|
|
||||||
)
|
|
||||||
|
|
||||||
render()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render function
|
|
||||||
const render = () => {
|
|
||||||
renderer.render(scene, camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup animation/render strategy
|
|
||||||
if (model.continiousRender) {
|
|
||||||
// Continuous animation loop
|
|
||||||
const animate = () => {
|
|
||||||
requestAnimationFrame(animate)
|
|
||||||
render()
|
|
||||||
}
|
|
||||||
animate()
|
|
||||||
} else {
|
|
||||||
// Render only on camera movement
|
|
||||||
controls.addEventListener('change', render)
|
|
||||||
// Initial render
|
|
||||||
render()
|
|
||||||
// Render after model loads
|
|
||||||
if (model.steveModelSkin !== undefined) {
|
|
||||||
// Create player model
|
|
||||||
const { playerObject, wrapper } = createPlayerObject({
|
|
||||||
scale: 1 // Start with base scale, will adjust below
|
|
||||||
})
|
|
||||||
|
|
||||||
// Enable shadows for player object
|
|
||||||
wrapper.traverse((child) => {
|
|
||||||
if (child instanceof THREE.Mesh) {
|
|
||||||
child.castShadow = true
|
|
||||||
child.receiveShadow = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Calculate proper scale and positioning for camera view
|
|
||||||
const box = new THREE.Box3().setFromObject(wrapper)
|
|
||||||
const size = box.getSize(new THREE.Vector3())
|
|
||||||
const center = box.getCenter(new THREE.Vector3())
|
|
||||||
|
|
||||||
// Calculate scale to fit within camera view (considering FOV and distance)
|
|
||||||
const cameraDistance = camera.position.z
|
|
||||||
const fov = camera.fov * Math.PI / 180 // Convert to radians
|
|
||||||
const visibleHeight = 2 * Math.tan(fov / 2) * cameraDistance
|
|
||||||
const visibleWidth = visibleHeight * (model.positioning.width / model.positioning.height)
|
|
||||||
|
|
||||||
const scaleFactor = Math.min(
|
|
||||||
(visibleHeight) / size.y,
|
|
||||||
(visibleWidth) / size.x
|
|
||||||
)
|
|
||||||
|
|
||||||
wrapper.scale.multiplyScalar(scaleFactor)
|
|
||||||
|
|
||||||
// Center the player object
|
|
||||||
wrapper.position.sub(center.multiplyScalar(scaleFactor))
|
|
||||||
|
|
||||||
// Rotate to face camera (remove the default 180° rotation)
|
|
||||||
wrapper.rotation.set(0, 0, 0)
|
|
||||||
|
|
||||||
scene.add(wrapper)
|
|
||||||
sceneRef.current = {
|
|
||||||
...sceneRef.current!,
|
|
||||||
playerObject
|
|
||||||
}
|
|
||||||
|
|
||||||
void applySkinToPlayerObject(playerObject, model.steveModelSkin).then(() => {
|
|
||||||
setTimeout(render, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set up cursor following if enabled
|
|
||||||
if (model.positioning.followCursor) {
|
|
||||||
isFollowingCursor.current = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window cursor tracking for followCursor
|
|
||||||
let lastCursorUpdate = 0
|
|
||||||
let waitingRender = false
|
|
||||||
const handleWindowPointerMove = (event: PointerEvent) => {
|
|
||||||
if (!model.positioning.followCursor) return
|
|
||||||
|
|
||||||
// Track cursor position as 0-1 across the entire window
|
|
||||||
const newPosition = {
|
|
||||||
x: event.clientX / window.innerWidth,
|
|
||||||
y: event.clientY / window.innerHeight
|
|
||||||
}
|
|
||||||
cursorPosition.current = newPosition
|
|
||||||
globalThis.cursorPosition = newPosition // Expose for debug
|
|
||||||
lastCursorUpdate = Date.now()
|
|
||||||
updatePlayerLookAt()
|
|
||||||
if (!waitingRender) {
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
render()
|
|
||||||
waitingRender = false
|
|
||||||
})
|
|
||||||
waitingRender = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add window event listeners
|
|
||||||
if (model.positioning.followCursor) {
|
|
||||||
window.addEventListener('pointermove', handleWindowPointerMove)
|
|
||||||
isFollowingCursor.current = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store refs for cleanup
|
|
||||||
sceneRef.current = {
|
|
||||||
...sceneRef.current!,
|
|
||||||
scene,
|
|
||||||
camera,
|
|
||||||
renderer,
|
|
||||||
controls,
|
|
||||||
dispose () {
|
|
||||||
if (!model.continiousRender) {
|
|
||||||
controls.removeEventListener('change', render)
|
|
||||||
}
|
|
||||||
if (model.positioning.followCursor) {
|
|
||||||
window.removeEventListener('pointermove', handleWindowPointerMove)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up loaded models
|
|
||||||
for (const [modelUrl, model] of loadedModels.current) {
|
|
||||||
scene.remove(model)
|
|
||||||
model.traverse((child) => {
|
|
||||||
if (child instanceof THREE.Mesh) {
|
|
||||||
if (child.material) {
|
|
||||||
if (Array.isArray(child.material)) {
|
|
||||||
for (const mat of child.material) {
|
|
||||||
mat.dispose()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
child.material.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (child.geometry) {
|
|
||||||
child.geometry.dispose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
loadedModels.current.clear()
|
|
||||||
modelLoaders.current.clear()
|
|
||||||
|
|
||||||
const playerObject = sceneRef.current?.playerObject
|
|
||||||
if (playerObject?.skin.map) {
|
|
||||||
(playerObject.skin.map as unknown as THREE.Texture).dispose()
|
|
||||||
}
|
|
||||||
renderer.dispose()
|
|
||||||
renderer.domElement?.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
sceneRef.current?.dispose()
|
|
||||||
}
|
|
||||||
}, [model])
|
|
||||||
|
|
||||||
if (!model) return null
|
|
||||||
|
|
||||||
const { x, y, width, height, scaled, onlyInitialScale } = model.positioning
|
|
||||||
const { windowWidth } = model.positioning
|
|
||||||
const { windowHeight } = model.positioning
|
|
||||||
const scaleValue = onlyInitialScale ? initialScale : 'var(--guiScale)'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className='overlay-model-viewer-container'
|
|
||||||
style={{
|
|
||||||
zIndex: 100,
|
|
||||||
position: 'fixed',
|
|
||||||
inset: 0,
|
|
||||||
width: '100dvw',
|
|
||||||
height: '100dvh',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
transform: scaled ? `scale(${scaleValue})` : 'none',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className='overlay-model-viewer-window'
|
|
||||||
style={{
|
|
||||||
width: windowWidth,
|
|
||||||
height: windowHeight,
|
|
||||||
position: 'relative',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={containerRef}
|
|
||||||
className='overlay-model-viewer'
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
left: x,
|
|
||||||
top: y,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
pointerEvents: 'auto',
|
|
||||||
backgroundColor: model.debug ? 'red' : undefined,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -289,6 +289,11 @@ export default () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{!lockConnect && <>
|
||||||
|
<Button className="button" style={{ width: '204px' }} onClick={disconnect}>
|
||||||
|
{fsState.inMemorySave && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect & Reset'}
|
||||||
|
</Button>
|
||||||
|
</>}
|
||||||
{(noConnection || appConfig?.alwaysReconnectButton) && (
|
{(noConnection || appConfig?.alwaysReconnectButton) && (
|
||||||
<div className={styles.row}>
|
<div className={styles.row}>
|
||||||
<Button className="button" style={{ width: appConfig?.reportBugButtonWithReconnect ? '98px' : '204px' }} onClick={reconnectReload}>
|
<Button className="button" style={{ width: appConfig?.reportBugButtonWithReconnect ? '98px' : '204px' }} onClick={reconnectReload}>
|
||||||
|
|
@ -338,11 +343,6 @@ export default () => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!lockConnect && <>
|
|
||||||
<Button className="button" style={{ width: '204px' }} onClick={disconnect}>
|
|
||||||
{fsState.inMemorySave && !fsState.syncFs && !fsState.isReadonly ? 'Save & Quit' : 'Disconnect & Reset'}
|
|
||||||
</Button>
|
|
||||||
</>}
|
|
||||||
</div>
|
</div>
|
||||||
<LoadingTimer />
|
<LoadingTimer />
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { useEffect, useMemo, useState } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { useUtilsEffect } from '@zardoy/react-util'
|
import { useUtilsEffect } from '@zardoy/react-util'
|
||||||
import { useSnapshot } from 'valtio'
|
import { useSnapshot } from 'valtio'
|
||||||
import { supportedVersions } from 'minecraft-protocol'
|
|
||||||
import { versionToNumber } from 'mc-assets/dist/utils'
|
|
||||||
import { ConnectOptions } from '../connect'
|
import { ConnectOptions } from '../connect'
|
||||||
import { activeModalStack, hideCurrentModal, miscUiState, notHideableModalsWithoutForce, showModal } from '../globalState'
|
import { activeModalStack, hideCurrentModal, miscUiState, notHideableModalsWithoutForce, showModal } from '../globalState'
|
||||||
import appSupportedVersions from '../supportedVersions.mjs'
|
import supportedVersions from '../supportedVersions.mjs'
|
||||||
import { appQueryParams } from '../appParams'
|
import { appQueryParams } from '../appParams'
|
||||||
import { fetchServerStatus, isServerValid } from '../api/mcStatusApi'
|
import { fetchServerStatus, isServerValid } from '../api/mcStatusApi'
|
||||||
import { getServerInfo } from '../mineflayer/mc-protocol'
|
import { getServerInfo } from '../mineflayer/mc-protocol'
|
||||||
|
|
@ -22,10 +20,6 @@ import Button from './Button'
|
||||||
import { pixelartIcons } from './PixelartIcon'
|
import { pixelartIcons } from './PixelartIcon'
|
||||||
import { showNotification } from './NotificationProvider'
|
import { showNotification } from './NotificationProvider'
|
||||||
|
|
||||||
const firstProtocolVersion = versionToNumber(supportedVersions[0])
|
|
||||||
const lastProtocolVersion = versionToNumber(supportedVersions.at(-1)!)
|
|
||||||
const protocolSupportedVersions = appSupportedVersions.filter(v => versionToNumber(v) >= firstProtocolVersion && versionToNumber(v) <= lastProtocolVersion)
|
|
||||||
|
|
||||||
const EXPLICIT_SHARE_SERVER_MODE = false
|
const EXPLICIT_SHARE_SERVER_MODE = false
|
||||||
|
|
||||||
if (appQueryParams.lockConnect) {
|
if (appQueryParams.lockConnect) {
|
||||||
|
|
@ -119,7 +113,6 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
|
||||||
...serversListProvided,
|
...serversListProvided,
|
||||||
...(customServersList ? [] : (miscUiState.appConfig?.promoteServers ?? [])).map((server): StoreServerItem => ({
|
...(customServersList ? [] : (miscUiState.appConfig?.promoteServers ?? [])).map((server): StoreServerItem => ({
|
||||||
ip: server.ip,
|
ip: server.ip,
|
||||||
name: server.name,
|
|
||||||
versionOverride: server.version,
|
versionOverride: server.version,
|
||||||
description: server.description,
|
description: server.description,
|
||||||
isRecommended: true
|
isRecommended: true
|
||||||
|
|
@ -163,23 +156,13 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
|
||||||
const isWebSocket = server.ip.startsWith('ws://') || server.ip.startsWith('wss://')
|
const isWebSocket = server.ip.startsWith('ws://') || server.ip.startsWith('wss://')
|
||||||
let data
|
let data
|
||||||
if (isWebSocket) {
|
if (isWebSocket) {
|
||||||
try {
|
const pingResult = await getServerInfo(server.ip, undefined, undefined, true)
|
||||||
const pingResult = await getServerInfo(server.ip, undefined, undefined, true)
|
console.log('pingResult.fullInfo.description', pingResult.fullInfo.description)
|
||||||
console.log('pingResult.fullInfo.description', pingResult.fullInfo.description)
|
data = {
|
||||||
data = {
|
formattedText: pingResult.fullInfo.description,
|
||||||
formattedText: pingResult.fullInfo.description,
|
textNameRight: `ws ${pingResult.latency}ms`,
|
||||||
icon: pingResult.fullInfo.favicon,
|
textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`,
|
||||||
textNameRight: `ws ${pingResult.latency}ms`,
|
offline: false
|
||||||
textNameRightGrayed: `${pingResult.fullInfo.players?.online ?? '??'}/${pingResult.fullInfo.players?.max ?? '??'}`,
|
|
||||||
offline: false
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
data = {
|
|
||||||
formattedText: 'Failed to connect',
|
|
||||||
textNameRight: '',
|
|
||||||
textNameRightGrayed: '',
|
|
||||||
offline: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data = await fetchServerStatus(server.ip, /* signal */undefined, server.versionOverride) // DONT ADD SIGNAL IT WILL CRUSH JS RUNTIME
|
data = await fetchServerStatus(server.ip, /* signal */undefined, server.versionOverride) // DONT ADD SIGNAL IT WILL CRUSH JS RUNTIME
|
||||||
|
|
@ -234,6 +217,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
|
||||||
})
|
})
|
||||||
|
|
||||||
const editModalJsx = isEditScreenModal ? <AddServerOrConnect
|
const editModalJsx = isEditScreenModal ? <AddServerOrConnect
|
||||||
|
allowAutoConnect={miscUiState.appConfig?.allowAutoConnect}
|
||||||
placeholders={{
|
placeholders={{
|
||||||
proxyOverride: getCurrentProxy(),
|
proxyOverride: getCurrentProxy(),
|
||||||
usernameOverride: getCurrentUsername(),
|
usernameOverride: getCurrentUsername(),
|
||||||
|
|
@ -270,7 +254,7 @@ const Inner = ({ hidden, customServersList }: { hidden?: boolean, customServersL
|
||||||
}
|
}
|
||||||
dispatchEvent(new CustomEvent('connect', { detail: connectOptions }))
|
dispatchEvent(new CustomEvent('connect', { detail: connectOptions }))
|
||||||
}}
|
}}
|
||||||
versions={protocolSupportedVersions}
|
versions={supportedVersions}
|
||||||
/> : null
|
/> : null
|
||||||
|
|
||||||
const serversListJsx = <ServersList
|
const serversListJsx = <ServersList
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Slider.tsx
|
// Slider.tsx
|
||||||
import React, { useState, useEffect, useRef, useCallback } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import styles from './slider.module.css'
|
import styles from './slider.module.css'
|
||||||
import SharedHudVars from './SharedHudVars'
|
import SharedHudVars from './SharedHudVars'
|
||||||
|
|
||||||
|
|
@ -12,7 +12,6 @@ interface Props extends React.ComponentProps<'div'> {
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
disabledReason?: string;
|
disabledReason?: string;
|
||||||
throttle?: number | false; // milliseconds, default 100, false to disable
|
|
||||||
|
|
||||||
updateValue?: (value: number) => void;
|
updateValue?: (value: number) => void;
|
||||||
updateOnDragEnd?: boolean;
|
updateOnDragEnd?: boolean;
|
||||||
|
|
@ -27,24 +26,15 @@ const Slider: React.FC<Props> = ({
|
||||||
min = 0,
|
min = 0,
|
||||||
max = 100,
|
max = 100,
|
||||||
disabledReason,
|
disabledReason,
|
||||||
throttle = 0,
|
|
||||||
|
|
||||||
updateOnDragEnd = false,
|
updateOnDragEnd = false,
|
||||||
updateValue,
|
updateValue,
|
||||||
...divProps
|
...divProps
|
||||||
}) => {
|
}) => {
|
||||||
label = translate(label)
|
|
||||||
disabledReason = translate(disabledReason)
|
|
||||||
valueDisplay = typeof valueDisplay === 'string' ? translate(valueDisplay) : valueDisplay
|
|
||||||
|
|
||||||
const [value, setValue] = useState(valueProp)
|
const [value, setValue] = useState(valueProp)
|
||||||
const getRatio = (v = value) => Math.max(Math.min((v - min) / (max - min), 1), 0)
|
const getRatio = (v = value) => Math.max(Math.min((v - min) / (max - min), 1), 0)
|
||||||
const [ratio, setRatio] = useState(getRatio())
|
const [ratio, setRatio] = useState(getRatio())
|
||||||
|
|
||||||
// Throttling refs
|
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
|
|
||||||
const lastValueRef = useRef<number>(valueProp)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(valueProp)
|
setValue(valueProp)
|
||||||
}, [valueProp])
|
}, [valueProp])
|
||||||
|
|
@ -52,52 +42,14 @@ const Slider: React.FC<Props> = ({
|
||||||
setRatio(getRatio())
|
setRatio(getRatio())
|
||||||
}, [value, min, max])
|
}, [value, min, max])
|
||||||
|
|
||||||
const throttledUpdateValue = useCallback((newValue: number, dragEnd: boolean) => {
|
|
||||||
if (updateOnDragEnd !== dragEnd) return
|
|
||||||
if (!updateValue) return
|
|
||||||
|
|
||||||
lastValueRef.current = newValue
|
|
||||||
|
|
||||||
if (!throttle) {
|
|
||||||
// No throttling
|
|
||||||
updateValue(newValue)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear existing timeout
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set new timeout
|
|
||||||
timeoutRef.current = setTimeout(() => {
|
|
||||||
updateValue(lastValueRef.current)
|
|
||||||
timeoutRef.current = null
|
|
||||||
}, throttle)
|
|
||||||
}, [updateValue, updateOnDragEnd, throttle])
|
|
||||||
|
|
||||||
// Cleanup timeout on unmount
|
|
||||||
useEffect(() => {
|
|
||||||
return () => {
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current)
|
|
||||||
// Fire the last value immediately on cleanup
|
|
||||||
if (updateValue && lastValueRef.current !== undefined) {
|
|
||||||
updateValue(lastValueRef.current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [updateValue])
|
|
||||||
|
|
||||||
const fireValueUpdate = (dragEnd: boolean, v = value) => {
|
const fireValueUpdate = (dragEnd: boolean, v = value) => {
|
||||||
throttledUpdateValue(v, dragEnd)
|
if (updateOnDragEnd !== dragEnd) return
|
||||||
|
updateValue?.(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelText = `${label}: ${valueDisplay ?? value} ${unit}`
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SharedHudVars>
|
<SharedHudVars>
|
||||||
<div className={`${styles['slider-container']} settings-text-container ${labelText.length > 17 ? 'settings-text-container-long' : ''}`} style={{ width }} {...divProps}>
|
<div className={styles['slider-container']} style={{ width }} {...divProps}>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
className={styles.slider}
|
className={styles.slider}
|
||||||
|
|
@ -124,7 +76,7 @@ const Slider: React.FC<Props> = ({
|
||||||
<div className={styles.disabled} title={disabledReason} />
|
<div className={styles.disabled} title={disabledReason} />
|
||||||
<div className={styles['slider-thumb']} style={{ left: `calc((100% * ${ratio}) - (8px * ${ratio}))` }} />
|
<div className={styles['slider-thumb']} style={{ left: `calc((100% * ${ratio}) - (8px * ${ratio}))` }} />
|
||||||
<label className={styles.label}>
|
<label className={styles.label}>
|
||||||
{labelText}
|
{label}: {valueDisplay ?? value} {unit}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</SharedHudVars>
|
</SharedHudVars>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { CSSProperties, PointerEvent, useEffect, useRef, useState } from 'react'
|
import { CSSProperties, PointerEvent, useEffect, useRef, useState } from 'react'
|
||||||
import { proxy, ref, useSnapshot } from 'valtio'
|
import { proxy, ref, useSnapshot } from 'valtio'
|
||||||
import activatableItemsMobile from 'mineflayer-mouse/dist/activatableItemsMobile'
|
|
||||||
import { contro } from '../controls'
|
import { contro } from '../controls'
|
||||||
import { options } from '../optionsStorage'
|
import { options } from '../optionsStorage'
|
||||||
import PixelartIcon from './PixelartIcon'
|
import PixelartIcon from './PixelartIcon'
|
||||||
|
|
@ -73,12 +72,10 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props)
|
||||||
break: false,
|
break: false,
|
||||||
jump: bot?.getControlState('jump'),
|
jump: bot?.getControlState('jump'),
|
||||||
}[name]
|
}[name]
|
||||||
const RIGHT_MOUSE_BUTTON = 2
|
|
||||||
const LEFT_MOUSE_BUTTON = 0
|
|
||||||
const holdDown = {
|
const holdDown = {
|
||||||
action () {
|
action () {
|
||||||
if (!bot) return
|
if (!bot) return
|
||||||
document.dispatchEvent(new MouseEvent('mousedown', { button: RIGHT_MOUSE_BUTTON }))
|
document.dispatchEvent(new MouseEvent('mousedown', { button: 2 }))
|
||||||
bot.mouse.update()
|
bot.mouse.update()
|
||||||
},
|
},
|
||||||
sneak () {
|
sneak () {
|
||||||
|
|
@ -90,7 +87,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props)
|
||||||
},
|
},
|
||||||
break () {
|
break () {
|
||||||
if (!bot) return
|
if (!bot) return
|
||||||
document.dispatchEvent(new MouseEvent('mousedown', { button: LEFT_MOUSE_BUTTON }))
|
document.dispatchEvent(new MouseEvent('mousedown', { button: 0 }))
|
||||||
bot.mouse.update()
|
bot.mouse.update()
|
||||||
active = true
|
active = true
|
||||||
},
|
},
|
||||||
|
|
@ -104,7 +101,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props)
|
||||||
}
|
}
|
||||||
const holdUp = {
|
const holdUp = {
|
||||||
action () {
|
action () {
|
||||||
document.dispatchEvent(new MouseEvent('mouseup', { button: RIGHT_MOUSE_BUTTON }))
|
document.dispatchEvent(new MouseEvent('mouseup', { button: 2 }))
|
||||||
},
|
},
|
||||||
sneak () {
|
sneak () {
|
||||||
void contro.emit('release', {
|
void contro.emit('release', {
|
||||||
|
|
@ -115,7 +112,7 @@ export default ({ setupActive, closeButtonsSetup, foregroundGameActive }: Props)
|
||||||
},
|
},
|
||||||
break () {
|
break () {
|
||||||
if (!bot) return
|
if (!bot) return
|
||||||
document.dispatchEvent(new MouseEvent('mouseup', { button: LEFT_MOUSE_BUTTON }))
|
document.dispatchEvent(new MouseEvent('mouseup', { button: 0 }))
|
||||||
bot.mouse.update()
|
bot.mouse.update()
|
||||||
active = false
|
active = false
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -91,14 +91,6 @@ const setCookieValue = (key: string, value: string): boolean => {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.cookie = cookie
|
document.cookie = cookie
|
||||||
|
|
||||||
// Verify the cookie was actually saved by reading it back
|
|
||||||
const savedValue = getCookieValue(key)
|
|
||||||
if (savedValue !== value) {
|
|
||||||
console.warn(`Cookie verification failed for key '${key}'. Expected: ${value}, Got: ${savedValue}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to set cookie for key '${key}':`, error)
|
console.error(`Failed to set cookie for key '${key}':`, error)
|
||||||
|
|
@ -237,19 +229,12 @@ export const getRandomUsername = (appConfig: AppConfig) => {
|
||||||
|
|
||||||
export const appStorage = proxy({ ...defaultStorageData })
|
export const appStorage = proxy({ ...defaultStorageData })
|
||||||
|
|
||||||
// Track if cookies failed in this session
|
|
||||||
let cookiesFailedThisSession = false
|
|
||||||
|
|
||||||
// Check if cookie storage should be used (will be set by options)
|
// Check if cookie storage should be used (will be set by options)
|
||||||
const shouldUseCookieStorage = () => {
|
const shouldUseCookieStorage = () => {
|
||||||
// If cookies failed this session, don't try again
|
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
|
||||||
if (cookiesFailedThisSession) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSecureCookiesAvailable = () => {
|
const isSecureCookiesAvailable = () => {
|
||||||
// either https or localhost
|
// 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()) {
|
if (!isSecureCookiesAvailable()) {
|
||||||
return false
|
return false
|
||||||
|
|
@ -360,10 +345,8 @@ const saveKey = (key: keyof StorageData) => {
|
||||||
// Remove from localStorage if cookie save was successful
|
// Remove from localStorage if cookie save was successful
|
||||||
markLocalStorageAsMigrated(key)
|
markLocalStorageAsMigrated(key)
|
||||||
} else {
|
} else {
|
||||||
// Cookie save failed, disable cookies for this session and fallback to localStorage
|
// Disabling for now so no confusing conflicts modal after page reload
|
||||||
console.warn(`Cookie save failed for key '${key}', disabling cookies for this session`)
|
// useLocalStorage = true
|
||||||
cookiesFailedThisSession = true
|
|
||||||
useLocalStorage = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,9 +66,6 @@ import CreditsAboutModal from './react/CreditsAboutModal'
|
||||||
import GlobalOverlayHints from './react/GlobalOverlayHints'
|
import GlobalOverlayHints from './react/GlobalOverlayHints'
|
||||||
import FullscreenTime from './react/FullscreenTime'
|
import FullscreenTime from './react/FullscreenTime'
|
||||||
import StorageConflictModal from './react/StorageConflictModal'
|
import StorageConflictModal from './react/StorageConflictModal'
|
||||||
import FireRenderer from './react/FireRenderer'
|
|
||||||
import MonacoEditor from './react/MonacoEditor'
|
|
||||||
import OverlayModelViewer from './react/OverlayModelViewer'
|
|
||||||
|
|
||||||
const isFirefox = ua.getBrowser().name === 'Firefox'
|
const isFirefox = ua.getBrowser().name === 'Firefox'
|
||||||
if (isFirefox) {
|
if (isFirefox) {
|
||||||
|
|
@ -174,7 +171,6 @@ const InGameUi = () => {
|
||||||
<VoiceMicrophone />
|
<VoiceMicrophone />
|
||||||
<ChunksDebugScreen />
|
<ChunksDebugScreen />
|
||||||
<RendererDebugMenu />
|
<RendererDebugMenu />
|
||||||
{!disabledUiParts.includes('fire') && <FireRenderer />}
|
|
||||||
</PerComponentErrorBoundary>
|
</PerComponentErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -250,6 +246,7 @@ const App = () => {
|
||||||
<PacketsReplayProvider />
|
<PacketsReplayProvider />
|
||||||
<NotificationProvider />
|
<NotificationProvider />
|
||||||
<ModsPage />
|
<ModsPage />
|
||||||
|
|
||||||
<SelectOption />
|
<SelectOption />
|
||||||
<CreditsAboutModal />
|
<CreditsAboutModal />
|
||||||
<NoModalFoundProvider />
|
<NoModalFoundProvider />
|
||||||
|
|
@ -260,8 +257,6 @@ const App = () => {
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
<DebugEdges />
|
<DebugEdges />
|
||||||
<OverlayModelViewer />
|
|
||||||
<MonacoEditor />
|
|
||||||
<DebugResponseTimeIndicator />
|
<DebugResponseTimeIndicator />
|
||||||
</RobustPortal>
|
</RobustPortal>
|
||||||
</ButtonAppProvider>
|
</ButtonAppProvider>
|
||||||
|
|
|
||||||
|
|
@ -486,6 +486,17 @@ const downloadAndUseResourcePack = async (url: string, progressReporter: Progres
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const waitForGameEvent = async () => {
|
||||||
|
if (miscUiState.gameLoaded) return
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
const listener = () => resolve()
|
||||||
|
customEvents.once('gameLoaded', listener)
|
||||||
|
watchUnloadForCleanup(() => {
|
||||||
|
customEvents.removeListener('gameLoaded', listener)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const onAppLoad = () => {
|
export const onAppLoad = () => {
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
customEvents.on('mineflayerBotCreated', () => {
|
||||||
// todo also handle resourcePack
|
// todo also handle resourcePack
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,6 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
z-index: 12;
|
z-index: 12;
|
||||||
/* Account for GUI scaling */
|
|
||||||
width: calc(100dvw / var(--guiScale, 1));
|
|
||||||
height: calc(100dvh / var(--guiScale, 1));
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.screen-content {
|
.screen-content {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
import { versionToNumber } from 'renderer/viewer/common/utils'
|
import { versionToNumber } from 'renderer/viewer/common/utils'
|
||||||
import { restoreMinecraftData } from '../optimizeJson'
|
import { restoreMinecraftData } from '../optimizeJson'
|
||||||
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
|
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
|
||||||
import { toMajorVersion } from '../utils'
|
|
||||||
import { importLargeData } from '../../generated/large-data-aliases'
|
import { importLargeData } from '../../generated/large-data-aliases'
|
||||||
|
|
||||||
|
const toMajorVersion = version => {
|
||||||
|
const [a, b] = (String(version)).split('.')
|
||||||
|
return `${a}.${b}`
|
||||||
|
}
|
||||||
|
|
||||||
const customResolver = () => {
|
const customResolver = () => {
|
||||||
const resolver = Promise.withResolvers()
|
const resolver = Promise.withResolvers()
|
||||||
let resolvedData
|
let resolvedData
|
||||||
|
|
@ -19,6 +23,8 @@ const customResolver = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//@ts-expect-error for workers using minecraft-data
|
||||||
|
globalThis.window ??= globalThis
|
||||||
let dataStatus = 'not-called'
|
let dataStatus = 'not-called'
|
||||||
|
|
||||||
const optimizedDataResolver = customResolver()
|
const optimizedDataResolver = customResolver()
|
||||||
|
|
@ -75,7 +81,7 @@ const possiblyGetFromCache = (version: string) => {
|
||||||
cacheTime.set(version, Date.now())
|
cacheTime.set(version, Date.now())
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
window.allLoadedMcData = new Proxy({}, {
|
window.allLoadedMcData ??= new Proxy({}, {
|
||||||
get (t, version: string) {
|
get (t, version: string) {
|
||||||
// special properties like $typeof
|
// special properties like $typeof
|
||||||
if (version.includes('$')) return
|
if (version.includes('$')) return
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import { showNotification } from '../react/NotificationProvider'
|
||||||
import { pixelartIcons } from '../react/PixelartIcon'
|
import { pixelartIcons } from '../react/PixelartIcon'
|
||||||
import { createSoundMap, SoundMap } from './soundsMap'
|
import { createSoundMap, SoundMap } from './soundsMap'
|
||||||
import { musicSystem } from './musicSystem'
|
import { musicSystem } from './musicSystem'
|
||||||
import './customSoundSystem'
|
|
||||||
|
|
||||||
let soundMap: SoundMap | undefined
|
let soundMap: SoundMap | undefined
|
||||||
|
|
||||||
|
|
@ -51,9 +50,8 @@ subscribeKey(miscUiState, 'gameLoaded', async () => {
|
||||||
appViewer.backend?.soundSystem?.playSound(
|
appViewer.backend?.soundSystem?.playSound(
|
||||||
position,
|
position,
|
||||||
soundData.url,
|
soundData.url,
|
||||||
soundData.volume,
|
soundData.volume * (options.volume / 100),
|
||||||
Math.max(Math.min(pitch ?? 1, 2), 0.5),
|
Math.max(Math.min(pitch ?? 1, 2), 0.5)
|
||||||
soundData.timeout ?? options.remoteSoundsLoadTimeout
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (getDistance(bot.entity.position, position) < 4 * 16) {
|
if (getDistance(bot.entity.position, position) < 4 * 16) {
|
||||||
|
|
@ -83,7 +81,7 @@ subscribeKey(miscUiState, 'gameLoaded', async () => {
|
||||||
}
|
}
|
||||||
const randomMusicKey = musicKeys[Math.floor(Math.random() * musicKeys.length)]
|
const randomMusicKey = musicKeys[Math.floor(Math.random() * musicKeys.length)]
|
||||||
const soundData = await soundMap.getSoundUrl(randomMusicKey)
|
const soundData = await soundMap.getSoundUrl(randomMusicKey)
|
||||||
if (!soundData || !soundMap) return
|
if (!soundData) return
|
||||||
await musicSystem.playMusic(soundData.url, soundData.volume)
|
await musicSystem.playMusic(soundData.url, soundData.volume)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,9 +109,6 @@ subscribeKey(miscUiState, 'gameLoaded', async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.on('soundEffectHeard', async (soundId, position, volume, pitch) => {
|
bot.on('soundEffectHeard', async (soundId, position, volume, pitch) => {
|
||||||
if (/^https?:/.test(soundId.replace('minecraft:', ''))) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await playHardcodedSound(soundId, position, volume, pitch)
|
await playHardcodedSound(soundId, position, volume, pitch)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import { loadOrPlaySound, stopAllSounds, stopSound } from '../basicSounds'
|
|
||||||
import { options } from '../optionsStorage'
|
|
||||||
|
|
||||||
const customSoundSystem = () => {
|
|
||||||
bot._client.on('named_sound_effect', packet => {
|
|
||||||
if (!options.remoteSoundsSupport) return
|
|
||||||
let { soundName } = packet
|
|
||||||
let metadata = {} as { loadTimeout?: number, loop?: boolean }
|
|
||||||
|
|
||||||
// Extract JSON metadata from parentheses at the end
|
|
||||||
const jsonMatch = /\(({.*})\)$/.exec(soundName)
|
|
||||||
if (jsonMatch) {
|
|
||||||
try {
|
|
||||||
metadata = JSON.parse(jsonMatch[1])
|
|
||||||
soundName = soundName.slice(0, -jsonMatch[0].length)
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Failed to parse sound metadata:', jsonMatch[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^https?:/.test(soundName.replace('minecraft:', ''))) {
|
|
||||||
const { loadTimeout, loop } = metadata
|
|
||||||
void loadOrPlaySound(soundName, packet.volume, loadTimeout, loop)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
bot._client.on('stop_sound', packet => {
|
|
||||||
const { flags, source, sound } = packet
|
|
||||||
|
|
||||||
if (flags === 0) {
|
|
||||||
// Stop all sounds
|
|
||||||
stopAllSounds()
|
|
||||||
} else if (sound) {
|
|
||||||
// Stop specific sound by name
|
|
||||||
stopSound(sound)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
bot.on('end', () => {
|
|
||||||
stopAllSounds()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
|
||||||
customSoundSystem()
|
|
||||||
})
|
|
||||||
|
|
@ -5,10 +5,10 @@ class MusicSystem {
|
||||||
private currentMusic: string | null = null
|
private currentMusic: string | null = null
|
||||||
|
|
||||||
async playMusic (url: string, musicVolume = 1) {
|
async playMusic (url: string, musicVolume = 1) {
|
||||||
if (!options.enableMusic || this.currentMusic || options.musicVolume === 0) return
|
if (!options.enableMusic || this.currentMusic) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { onEnded } = await loadOrPlaySound(url, musicVolume, 5000, undefined, true) ?? {}
|
const { onEnded } = await loadOrPlaySound(url, 0.5 * musicVolume, 5000) ?? {}
|
||||||
|
|
||||||
if (!onEnded) return
|
if (!onEnded) return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ interface ResourcePackSoundEntry {
|
||||||
name: string
|
name: string
|
||||||
stream?: boolean
|
stream?: boolean
|
||||||
volume?: number
|
volume?: number
|
||||||
timeout?: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ResourcePackSound {
|
interface ResourcePackSound {
|
||||||
|
|
@ -141,7 +140,7 @@ export class SoundMap {
|
||||||
await scan(soundsBasePath)
|
await scan(soundsBasePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSoundUrl (soundKey: string, volume = 1): Promise<{ url: string; volume: number, timeout?: number } | undefined> {
|
async getSoundUrl (soundKey: string, volume = 1): Promise<{ url: string; volume: number } | undefined> {
|
||||||
// First check resource pack sounds.json
|
// First check resource pack sounds.json
|
||||||
if (this.activeResourcePackSoundsJson && soundKey in this.activeResourcePackSoundsJson) {
|
if (this.activeResourcePackSoundsJson && soundKey in this.activeResourcePackSoundsJson) {
|
||||||
const rpSound = this.activeResourcePackSoundsJson[soundKey]
|
const rpSound = this.activeResourcePackSoundsJson[soundKey]
|
||||||
|
|
@ -152,13 +151,6 @@ export class SoundMap {
|
||||||
if (this.activeResourcePackBasePath) {
|
if (this.activeResourcePackBasePath) {
|
||||||
const tryFormat = async (format: string) => {
|
const tryFormat = async (format: string) => {
|
||||||
try {
|
try {
|
||||||
if (sound.name.startsWith('http://') || sound.name.startsWith('https://')) {
|
|
||||||
return {
|
|
||||||
url: sound.name,
|
|
||||||
volume: soundVolume * Math.max(Math.min(volume, 1), 0),
|
|
||||||
timeout: sound.timeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const resourcePackPath = path.join(this.activeResourcePackBasePath!, `/assets/minecraft/sounds/${sound.name}.${format}`)
|
const resourcePackPath = path.join(this.activeResourcePackBasePath!, `/assets/minecraft/sounds/${sound.name}.${format}`)
|
||||||
const fileData = await fs.promises.readFile(resourcePackPath)
|
const fileData = await fs.promises.readFile(resourcePackPath)
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import { subscribeKey } from 'valtio/utils'
|
import { subscribeKey } from 'valtio/utils'
|
||||||
import { isMobile } from 'renderer/viewer/lib/simpleUtils'
|
import { isMobile } from 'renderer/viewer/lib/simpleUtils'
|
||||||
import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter'
|
import { WorldDataEmitter } from 'renderer/viewer/lib/worldDataEmitter'
|
||||||
import { setSkinsConfig } from 'renderer/viewer/lib/utils/skins'
|
|
||||||
import { options, watchValue } from './optionsStorage'
|
import { options, watchValue } from './optionsStorage'
|
||||||
import { reloadChunks } from './utils'
|
import { reloadChunks } from './utils'
|
||||||
import { miscUiState } from './globalState'
|
import { miscUiState } from './globalState'
|
||||||
|
|
@ -81,10 +80,6 @@ export const watchOptionsAfterViewerInit = () => {
|
||||||
updateFpsLimit(o)
|
updateFpsLimit(o)
|
||||||
})
|
})
|
||||||
|
|
||||||
watchValue(options, o => {
|
|
||||||
appViewer.inWorldRenderingConfig.volume = Math.max(o.volume / 100, 0)
|
|
||||||
})
|
|
||||||
|
|
||||||
watchValue(options, o => {
|
watchValue(options, o => {
|
||||||
appViewer.inWorldRenderingConfig.vrSupport = o.vrSupport
|
appViewer.inWorldRenderingConfig.vrSupport = o.vrSupport
|
||||||
appViewer.inWorldRenderingConfig.vrPageGameRendering = o.vrPageGameRendering
|
appViewer.inWorldRenderingConfig.vrPageGameRendering = o.vrPageGameRendering
|
||||||
|
|
@ -98,8 +93,6 @@ export const watchOptionsAfterViewerInit = () => {
|
||||||
appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor
|
appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor
|
||||||
appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading
|
appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading
|
||||||
appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks
|
appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks
|
||||||
|
|
||||||
setSkinsConfig({ apiEnabled: o.loadPlayerSkins })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting
|
appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting
|
||||||
|
|
@ -107,22 +100,46 @@ export const watchOptionsAfterViewerInit = () => {
|
||||||
appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting
|
appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting
|
||||||
})
|
})
|
||||||
|
|
||||||
subscribeKey(options, 'newVersionsLighting', () => {
|
const updateLightingStrategy = () => {
|
||||||
appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting
|
if (!bot) return
|
||||||
})
|
if (!options.experimentalLightingV1) {
|
||||||
|
appViewer.inWorldRenderingConfig.clientSideLighting = 'none'
|
||||||
|
appViewer.inWorldRenderingConfig.enableLighting = false
|
||||||
|
appViewer.inWorldRenderingConfig.legacyLighting = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const lightingEnabled = options.dayCycle
|
||||||
|
if (!lightingEnabled) {
|
||||||
|
appViewer.inWorldRenderingConfig.clientSideLighting = 'none'
|
||||||
|
appViewer.inWorldRenderingConfig.enableLighting = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
appViewer.inWorldRenderingConfig.legacyLighting = false
|
||||||
|
|
||||||
|
// for now ignore saved lighting to allow proper updates and singleplayer created worlds
|
||||||
|
// appViewer.inWorldRenderingConfig.flyingSquidWorkarounds = miscUiState.flyingSquid
|
||||||
|
const serverParsingSupported = miscUiState.flyingSquid ? /* !bot.supportFeature('blockStateId') */false : bot.supportFeature('blockStateId')
|
||||||
|
|
||||||
|
const serverLightingPossible = serverParsingSupported && (options.lightingStrategy === 'prefer-server' || options.lightingStrategy === 'always-server')
|
||||||
|
const clientLightingPossible = options.lightingStrategy !== 'always-server'
|
||||||
|
|
||||||
|
const clientSideLighting = !serverLightingPossible
|
||||||
|
appViewer.inWorldRenderingConfig.clientSideLighting = serverLightingPossible && clientLightingPossible ? 'partial' : clientSideLighting ? 'full' : 'none'
|
||||||
|
appViewer.inWorldRenderingConfig.enableLighting = serverLightingPossible || clientLightingPossible
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeKey(options, 'lightingStrategy', updateLightingStrategy)
|
||||||
|
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
customEvents.on('mineflayerBotCreated', () => {
|
||||||
appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting
|
updateLightingStrategy()
|
||||||
})
|
})
|
||||||
|
|
||||||
watchValue(options, o => {
|
watchValue(options, o => {
|
||||||
appViewer.inWorldRenderingConfig.starfield = o.starfieldRendering
|
appViewer.inWorldRenderingConfig.starfield = o.starfieldRendering
|
||||||
})
|
})
|
||||||
|
|
||||||
watchValue(options, o => {
|
|
||||||
appViewer.inWorldRenderingConfig.defaultSkybox = o.defaultSkybox
|
|
||||||
})
|
|
||||||
|
|
||||||
watchValue(options, o => {
|
watchValue(options, o => {
|
||||||
// appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates
|
// appViewer.inWorldRenderingConfig.neighborChunkUpdates = o.neighborChunkUpdates
|
||||||
})
|
})
|
||||||
|
|
@ -135,6 +152,5 @@ export const watchOptionsAfterWorldViewInit = (worldView: WorldDataEmitter) => {
|
||||||
appViewer.inWorldRenderingConfig.renderEars = o.renderEars
|
appViewer.inWorldRenderingConfig.renderEars = o.renderEars
|
||||||
appViewer.inWorldRenderingConfig.showHand = o.showHand
|
appViewer.inWorldRenderingConfig.showHand = o.showHand
|
||||||
appViewer.inWorldRenderingConfig.viewBobbing = o.viewBobbing
|
appViewer.inWorldRenderingConfig.viewBobbing = o.viewBobbing
|
||||||
appViewer.inWorldRenderingConfig.dayCycle = o.dayCycleAndLighting
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue