diff --git a/README.MD b/README.MD index be25d24a..ca70dfc1 100644 --- a/README.MD +++ b/README.MD @@ -54,9 +54,9 @@ However, there are many things that can be done in online version. You can acces - `bot` - Mineflayer bot instance. See Mineflayer documentation for more. - `viewer` - Three.js viewer instance, basically does all the rendering. -- `viewer.world.sectionMeshs` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group. -- `localServer` - Only for singleplayer host / guest mode. Flying Squid server instance, see it's documentation for more. -- `localServer.overworld.storageProvider.regions` - See ALL LOADED region files with all raw data! +- `viewer.world.sectionObjects` - Object with all active chunk sections (geometries) in the world. Each chunk section is a Three.js mesh or group. +- `localServer` - Only for singleplayer mode/host. Flying Squid server instance, see it's documentation for more. +- `localServer.overworld.storageProvider.regions` - See ALL LOADED region files with all raw data. - `nbt.simplify(someNbt)` - Simplifies nbt data, so it's easier to read. diff --git a/prismarine-viewer/package.json b/prismarine-viewer/package.json index ad3e601b..1d6d3b1c 100644 --- a/prismarine-viewer/package.json +++ b/prismarine-viewer/package.json @@ -42,6 +42,7 @@ "socket.io": "^4.0.0", "socket.io-client": "^4.0.0", "three.meshline": "^1.3.0", - "vec3": "^0.1.7" + "vec3": "^0.1.7", + "node-canvas-webgl": "^0.3.0" } } diff --git a/prismarine-viewer/viewer/lib/worldrenderer.js b/prismarine-viewer/viewer/lib/worldrenderer.js index eeab4756..e64ab7e7 100644 --- a/prismarine-viewer/viewer/lib/worldrenderer.js +++ b/prismarine-viewer/viewer/lib/worldrenderer.js @@ -18,7 +18,8 @@ function mod (x, n) { class WorldRenderer { constructor (scene, numWorkers = 4) { this.blockEntities = {} - this.sectionMeshs = {} + this.sectionObjects = {} + this.showChunkBorders = false this.active = false this.version = undefined /** @type {THREE.Scene} */ @@ -47,11 +48,11 @@ class WorldRenderer { }) if (data.type === 'geometry') { /** @type {THREE.Object3D} */ - let mesh = this.sectionMeshs[data.key] - if (mesh) { - this.scene.remove(mesh) - dispose3(mesh) - delete this.sectionMeshs[data.key] + let object = this.sectionObjects[data.key] + if (object) { + this.scene.remove(object) + dispose3(object) + delete this.sectionObjects[data.key] } const chunkCoords = data.key.split(',') @@ -64,25 +65,25 @@ class WorldRenderer { geometry.setAttribute('uv', new THREE.BufferAttribute(data.geometry.uvs, 2)) geometry.setIndex(data.geometry.indices) - const _mesh = new THREE.Mesh(geometry, this.material) - _mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) - const boxHelper = new THREE.BoxHelper(_mesh, 0xffff00) - // shouldnt it compute once + const mesh = new THREE.Mesh(geometry, this.material) + mesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) + object = new THREE.Group() + object.add(mesh) + if (this.showChunkBorders) { + const boxHelper = new THREE.BoxHelper(mesh, 0xffff00) + object.add(boxHelper) + } + // should not it compute once if (Object.keys(data.geometry.signs).length) { - mesh = new THREE.Group() - mesh.add(_mesh) - mesh.add(boxHelper) for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.signs)) { const [x, y, z] = posKey.split(',') const signBlockEntity = this.blockEntities[posKey] if (!signBlockEntity) continue - mesh.add(this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity))) + object.add(this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity))) } - } else { - mesh = _mesh } - this.sectionMeshs[data.key] = mesh - this.scene.add(mesh) + this.sectionObjects[data.key] = object + this.scene.add(object) } else if (data.type === 'sectionFinished') { this.sectionsOutstanding.delete(data.key) this.renderUpdateEmitter.emit('update') @@ -125,10 +126,10 @@ class WorldRenderer { resetWorld () { this.active = false - for (const mesh of Object.values(this.sectionMeshs)) { + for (const mesh of Object.values(this.sectionObjects)) { this.scene.remove(mesh) } - this.sectionMeshs = {} + this.sectionObjects = {} this.loadedChunks = {} this.sectionsOutstanding = new Set() for (const worker of this.workers) { @@ -196,12 +197,12 @@ class WorldRenderer { for (let y = 0; y < 256; y += 16) { this.setSectionDirty(new Vec3(x, y, z), false) const key = `${x},${y},${z}` - const mesh = this.sectionMeshs[key] + const mesh = this.sectionObjects[key] if (mesh) { this.scene.remove(mesh) dispose3(mesh) } - delete this.sectionMeshs[key] + delete this.sectionObjects[key] } } diff --git a/src/controls.ts b/src/controls.ts index def14644..e0d8ad28 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -8,6 +8,7 @@ import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types import { stringStartsWith } from 'contro-max/build/stringUtils' import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCurrentModal } from './globalState' import { reloadChunks } from './utils' +import { options } from './optionsStorage' // doesnt seem to work for now const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) @@ -48,12 +49,12 @@ export const contro = new ControMax({ }, }, { target: document, - captureEvents() { + captureEvents () { return bot && isGameActive(false) }, storeProvider: { load: () => customKeymaps, - save() { }, + save () { }, }, gamepadPollingInterval: 10 }) @@ -103,10 +104,10 @@ let lastCommandTrigger = null as { command: string, time: number } | null const secondActionActivationTimeout = 300 const secondActionCommands = { - 'general.jump'() { + 'general.jump' () { toggleFly() }, - 'general.forward'() { + 'general.forward' () { setSprinting(true) } } @@ -233,14 +234,23 @@ document.addEventListener('keydown', (e) => { } reloadChunks() } + if (e.code === 'KeyG') { + // todo make it work without reload + options.showChunkBorders = !options.showChunkBorders + } + return } - if (hardcodedPressedKeys.has(e.code)) return hardcodedPressedKeys.add(e.code) }) document.addEventListener('keyup', (e) => { hardcodedPressedKeys.delete(e.code) }) +document.addEventListener('visibilitychange', (e) => { + if (document.visibilityState === 'hidden') { + hardcodedPressedKeys.clear() + } +}) // #region creative fly // these controls are more like for gamemode 3 diff --git a/src/index.ts b/src/index.ts index 15ebc6b4..dedeab59 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,7 +33,7 @@ import { contro } from './controls' import './dragndrop' import './browserfs' import './eruda' -import './watchOptions' +import { watchOptionsAfterViewerInit } from './watchOptions' import downloadAndOpenFile from './downloadAndOpenFile' import net from 'net' @@ -111,6 +111,7 @@ window.viewer = viewer viewer.entities.entitiesOptions = { fontFamily: 'mojangles' } +watchOptionsAfterViewerInit() initPanoramaOptions(viewer) watchTexturepackInViewer(viewer) @@ -163,7 +164,7 @@ const updateCursor = () => { debugMenu ??= hud.shadowRoot.querySelector('#debug-overlay') debugMenu.cursorBlock = blockInteraction.cursorBlock } -function onCameraMove(e) { +function onCameraMove (e) { if (e.type !== 'touchmove' && !pointerLock.hasPointerLock) return e.stopPropagation?.() const now = performance.now() @@ -181,12 +182,12 @@ function onCameraMove(e) { window.addEventListener('mousemove', onCameraMove, { capture: true }) -function hideCurrentScreens() { +function hideCurrentScreens () { activeModalStacks['main-menu'] = [...activeModalStack] insertActiveModalStack('', []) } -async function main() { +async function main () { const menu = document.getElementById('play-screen') menu.addEventListener('connect', e => { const options = e.detail @@ -234,7 +235,7 @@ const cleanConnectIp = (host: string | undefined, defaultPort: string | undefine } } -async function connect(connectOptions: { +async function connect (connectOptions: { server?: string; singleplayer?: any; username?: string; password?: any; proxy?: any; botVersion?: any; serverOverrides?; peerId?: string }) { document.getElementById('play-screen').style = 'display: none;' @@ -377,7 +378,7 @@ async function connect(connectOptions: { } : {}, ...singeplayer ? { version: serverOptions.version, - connect() { }, + connect () { }, Client: CustomChannelClient as any, } : {}, username, @@ -387,7 +388,7 @@ async function connect(connectOptions: { noPongTimeout: 240 * 1000, closeTimeout: 240 * 1000, respawn: options.autoRespawn, - async versionSelectedHook(client) { + async versionSelectedHook (client) { // todo keep in sync with esbuild preload, expose cache ideally if (client.version === '1.20.1') { // ignore cache hit @@ -542,7 +543,7 @@ async function connect(connectOptions: { dayCycle() // Bot position callback - function botPosition() { + function botPosition () { // this might cause lag, but not sure viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) worldView.updatePosition(bot.entity.position) @@ -560,7 +561,7 @@ async function connect(connectOptions: { bot.entity.yaw -= x } - function changeCallback() { + function changeCallback () { notification.show = false if (!pointerLock.hasPointerLock && activeModalStack.length === 0) { showModal(pauseMenu) diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index d6428a5e..427c5f3e 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -27,6 +27,7 @@ const defaultOptions = { touchButtonsSize: 40, highPerformanceGpu: false, + showChunkBorders: false, frameLimit: false as number | false, alwaysBackupWorldBeforeLoading: undefined as boolean | undefined | null, alwaysShowMobileControls: false, @@ -61,7 +62,7 @@ type WatchValue = >(proxy: T, callback: (p: T) => export const watchValue: WatchValue = (proxy, callback) => { const watchedProps = new Set() callback(new Proxy(proxy, { - get(target, p, receiver) { + get (target, p, receiver) { watchedProps.add(p.toString()) return Reflect.get(target, p, receiver) }, diff --git a/src/watchOptions.ts b/src/watchOptions.ts index de545033..026c43cb 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -1,7 +1,13 @@ // not all options are watched here import { subscribeKey } from 'valtio/utils' -import { options } from './optionsStorage' +import { options, watchValue } from './optionsStorage' import { reloadChunks } from './utils' subscribeKey(options, 'renderDistance', reloadChunks) + +export const watchOptionsAfterViewerInit = () => { + watchValue(options, o => { + viewer.world.showChunkBorders = o.showChunkBorders + }) +}