diff --git a/package.json b/package.json index c20a781d..94bcedf5 100644 --- a/package.json +++ b/package.json @@ -150,7 +150,7 @@ "http-browserify": "^1.7.0", "http-server": "^14.1.1", "https-browserify": "^1.0.0", - "mc-assets": "^0.2.50", + "mc-assets": "^0.2.52", "mineflayer-mouse": "^0.1.7", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", "mineflayer": "github:zardoy/mineflayer", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4a86dd8..24c4e332 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -353,8 +353,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 mc-assets: - specifier: ^0.2.50 - version: 0.2.50 + specifier: ^0.2.52 + version: 0.2.52 minecraft-inventory-gui: specifier: github:zardoy/minecraft-inventory-gui#next version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00(@types/react@18.2.20)(react@18.2.0) @@ -3546,6 +3546,9 @@ packages: engines: {node: '>=8'} hasBin: true + '@zardoy/maxrects-packer@2.7.4': + resolution: {integrity: sha512-ZIDcSdtSg6EhKFxGYWCcTnA/0YVbpixBL+psUS6ncw4IvdDF5hWauMU3XeCfYwrT/88QFgAq/Pafxt+P9OJyoQ==} + '@zardoy/react-util@0.2.4': resolution: {integrity: sha512-YRBbXi54QOgWGDSn3NLEMMGrWbfL/gn2khxO31HT0WPFB6IW2rSnB4hcy+S/nc+2D6PRNq4kQxGs4vTAe4a7Xg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -6691,11 +6694,8 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - maxrects-packer@2.7.3: - resolution: {integrity: sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==} - - mc-assets@0.2.50: - resolution: {integrity: sha512-X27KLLTyeEAVTlBVKqGmoG/YvZq3tDG29kyRgy3Hj9s03m6+/TI8HvCyxumRjEQE8IYPcjPiX+7iuEZtNQ9N+w==} + mc-assets@0.2.52: + resolution: {integrity: sha512-6mUI63fcUIjB0Ghjls7bLMnse2XUgvhPajsFkRQf10PcXYbfS/OAnX51X8sNx2pzfoHSlA81U7v+v906YwoAUw==} engines: {node: '>=18.0.0'} mcraft-fun-mineflayer@0.1.14: @@ -13688,6 +13688,8 @@ snapshots: - encoding - supports-color + '@zardoy/maxrects-packer@2.7.4': {} + '@zardoy/react-util@0.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: classnames: 2.5.1 @@ -17603,11 +17605,9 @@ snapshots: math-intrinsics@1.1.0: {} - maxrects-packer@2.7.3: {} - - mc-assets@0.2.50: + mc-assets@0.2.52: dependencies: - maxrects-packer: 2.7.3 + maxrects-packer: '@zardoy/maxrects-packer@2.7.4' zod: 3.24.1 mcraft-fun-mineflayer@0.1.14(encoding@0.1.13)(mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)): diff --git a/renderer/viewer/lib/basePlayerState.ts b/renderer/viewer/lib/basePlayerState.ts index 3384f0b4..93f4f6ce 100644 --- a/renderer/viewer/lib/basePlayerState.ts +++ b/renderer/viewer/lib/basePlayerState.ts @@ -2,7 +2,7 @@ import { EventEmitter } from 'events' import { Vec3 } from 'vec3' import TypedEmitter from 'typed-emitter' import { ItemSelector } from 'mc-assets/dist/itemDefinitions' -import { proxy } from 'valtio' +import { proxy, ref } from 'valtio' import { GameMode } from 'mineflayer' import { HandItemBlock } from '../three/holdingBlock' @@ -34,6 +34,7 @@ export interface IPlayerState { reactive: { playerSkin: string | undefined inWater: boolean + waterBreathing: boolean backgroundColor: [number, number, number] ambientLight: number directionalLight: number @@ -45,7 +46,8 @@ export class BasePlayerState implements IPlayerState { reactive = proxy({ playerSkin: undefined as string | undefined, inWater: false, - backgroundColor: [0, 0, 0] as [number, number, number], + waterBreathing: false, + backgroundColor: ref([0, 0, 0]) as [number, number, number], ambientLight: 0, directionalLight: 0, }) diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index 981b1851..b7c2139f 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -41,9 +41,10 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter = {} private readonly emitter: WorldDataEmitter - keepChunksDistance = 0 addWaitTime = 1 - isPlayground = false + /* config */ keepChunksDistance = 0 + /* config */ isPlayground = false + /* config */ allowPositionUpdate = true public reactive = proxy({ cursorBlock: null as Vec3 | null, @@ -165,6 +166,8 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter TypedEmitter workersProcessAverageTime = 0 workersProcessAverageTimeCount = 0 maxWorkersProcessTime = 0 - geometryReceiveCount = {} + geometryReceiveCount = {} as Record allLoadedIn: undefined | number onWorldSwitched = [] as Array<() => void> @@ -137,6 +138,7 @@ export abstract class WorldRendererCommon abortController = new AbortController() lastRendered = 0 renderingActive = true + geometryReceiveCountPerSec = 0 workerLogger = { contents: [] as string[], active: new URL(location.href).searchParams.get('mesherlog') === 'true' @@ -155,6 +157,12 @@ export abstract class WorldRendererCommon }) this.connect(this.displayOptions.worldView) + + setInterval(() => { + this.geometryReceiveCountPerSec = Object.values(this.geometryReceiveCount).reduce((acc, curr) => acc + curr, 0) + this.geometryReceiveCount = {} + this.updateChunksStats() + }, 1000) } logWorkerWork (message: string | (() => string)) { @@ -164,6 +172,7 @@ export abstract class WorldRendererCommon init () { if (this.active) throw new Error('WorldRendererCommon is already initialized') + this.watchReactivePlayerState() void this.setVersion(this.version).then(() => { this.resourcesManager.on('assetsTexturesUpdated', () => { if (!this.active) return @@ -238,6 +247,17 @@ export abstract class WorldRendererCommon } } + onReactiveValueUpdated(key: T, callback: (value: typeof this.displayOptions.playerState.reactive[T]) => void) { + callback(this.displayOptions.playerState.reactive[key]) + subscribeKey(this.displayOptions.playerState.reactive, key, callback) + } + + watchReactivePlayerState () { + this.onReactiveValueUpdated('backgroundColor', (value) => { + this.changeBackgroundColor(value) + }) + } + async processMessageQueue (source: string) { if (this.isProcessingQueue || this.messageQueue.length === 0) return this.logWorkerWork(`# ${source} processing queue`) @@ -503,7 +523,7 @@ export abstract class WorldRendererCommon this.displayOptions.nonReactiveState.world.chunksTotalNumber = this.chunksLength this.reactiveState.world.allChunksLoaded = this.allChunksFinished - updateStatText('downloaded-chunks', `${Object.keys(this.loadedChunks).length}/${this.chunksLength} chunks D (${this.workers.length}:${this.workersProcessAverageTime.toFixed(0)}ms/${this.allLoadedIn?.toFixed(1) ?? '-'}s)`) + updateStatText('downloaded-chunks', `${Object.keys(this.loadedChunks).length}/${this.chunksLength} chunks D (${this.workers.length}:${this.workersProcessAverageTime.toFixed(0)}ms/${this.geometryReceiveCountPerSec}ss/${this.allLoadedIn?.toFixed(1) ?? '-'}s)`) } addColumn (x: number, z: number, chunk: any, isLightUpdate: boolean) { @@ -884,7 +904,6 @@ export abstract class WorldRendererCommon this.renderUpdateEmitter.removeAllListeners() this.displayOptions.worldView.removeAllListeners() // todo this.abortController.abort() - removeStat('chunks-loaded') - removeStat('chunks-read') + removeAllStats() } } diff --git a/renderer/viewer/three/graphicsBackend.ts b/renderer/viewer/three/graphicsBackend.ts index 06b17d2f..3ee34ea7 100644 --- a/renderer/viewer/three/graphicsBackend.ts +++ b/renderer/viewer/three/graphicsBackend.ts @@ -117,4 +117,5 @@ const createGraphicsBackend: GraphicsBackendLoader = (initOptions: GraphicsInitO return backend } +createGraphicsBackend.id = 'threejs' export default createGraphicsBackend diff --git a/renderer/viewer/three/holdingBlock.ts b/renderer/viewer/three/holdingBlock.ts index 9ba76224..cf294016 100644 --- a/renderer/viewer/three/holdingBlock.ts +++ b/renderer/viewer/three/holdingBlock.ts @@ -911,6 +911,6 @@ export const getBlockMeshFromModel = (material: THREE.Material, model: BlockMode const worldRenderModel = blockProvider.transformModel(model, { name, properties: {} - }) + }) as any return getThreeBlockModelGroup(material, [[worldRenderModel]], undefined, 'plains', loadedData) } diff --git a/renderer/viewer/three/worldrendererThree.ts b/renderer/viewer/three/worldrendererThree.ts index e705b2d1..4e800776 100644 --- a/renderer/viewer/three/worldrendererThree.ts +++ b/renderer/viewer/three/worldrendererThree.ts @@ -70,7 +70,6 @@ export class WorldRendererThree extends WorldRendererCommon { this.addDebugOverlay() this.resetScene() - this.watchReactivePlayerState() this.init() void initVR(this) @@ -120,22 +119,15 @@ export class WorldRendererThree extends WorldRendererCommon { this.camera = new THREE.PerspectiveCamera(75, size.x / size.y, 0.1, 1000) } - watchReactivePlayerState () { - const updateValue = (key: T, callback: (value: typeof this.displayOptions.playerState.reactive[T]) => void) => { - callback(this.displayOptions.playerState.reactive[key]) - subscribeKey(this.displayOptions.playerState.reactive, key, callback) - } - updateValue('backgroundColor', (value) => { - this.changeBackgroundColor(value) + override watchReactivePlayerState () { + this.onReactiveValueUpdated('inWater', (value) => { + this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, this.displayOptions.playerState.reactive.waterBreathing ? 100 : 20) : null }) - updateValue('inWater', (value) => { - this.scene.fog = value ? new THREE.Fog(0x00_00_ff, 0.1, 100) : null - }) - updateValue('ambientLight', (value) => { + this.onReactiveValueUpdated('ambientLight', (value) => { if (!value) return this.ambientLight.intensity = value }) - updateValue('directionalLight', (value) => { + this.onReactiveValueUpdated('directionalLight', (value) => { if (!value) return this.directionalLight.intensity = value }) @@ -613,8 +605,6 @@ export class WorldRendererThree extends WorldRendererCommon { } destroy (): void { - removeAllStats() - this.media.onWorldGone() super.destroy() } } diff --git a/src/appViewer.ts b/src/appViewer.ts index f4032712..1b973ad2 100644 --- a/src/appViewer.ts +++ b/src/appViewer.ts @@ -49,11 +49,13 @@ const defaultGraphicsBackendConfig: GraphicsBackendConfig = { sceneBackground: 'lightblue' } -export interface GraphicsInitOptions { +export interface GraphicsInitOptions { resourcesManager: ResourcesManager config: GraphicsBackendConfig + rendererSpecificSettings: S displayCriticalError: (error: Error) => void + setRendererSpecificSettings: (key: string, value: any) => void } export interface DisplayWorldOptions { @@ -65,7 +67,9 @@ export interface DisplayWorldOptions { nonReactiveState: NonReactiveState } -export type GraphicsBackendLoader = (options: GraphicsInitOptions) => GraphicsBackend +export type GraphicsBackendLoader = ((options: GraphicsInitOptions) => GraphicsBackend) & { + id: string +} // no sync methods export interface GraphicsBackend { @@ -73,7 +77,7 @@ export interface GraphicsBackend { displayName?: string startPanorama: () => void // prepareResources: (version: string, progressReporter: ProgressReporter) => Promise - startWorld: (options: DisplayWorldOptions) => void + startWorld: (options: DisplayWorldOptions) => Promise | void disconnect: () => void setRendering: (rendering: boolean) => void getDebugOverlay?: () => Record @@ -116,6 +120,13 @@ export class AppViewer { } this.backendLoader = loader + const rendererSpecificSettings = {} as Record + const rendererSettingsKey = `renderer.${this.backendLoader?.id}` + for (const key in options) { + if (key.startsWith(rendererSettingsKey)) { + rendererSpecificSettings[key.slice(rendererSettingsKey.length + 1)] = options[key] + } + } const loaderOptions: GraphicsInitOptions = { resourcesManager: this.resourcesManager, config: this.config, @@ -123,6 +134,10 @@ export class AppViewer { console.error(error) setLoadingScreenStatus(error.message, true) }, + rendererSpecificSettings, + setRendererSpecificSettings (key: string, value: any) { + options[`${rendererSettingsKey}.${key}`] = value + } } this.backend = loader(loaderOptions) @@ -135,12 +150,12 @@ export class AppViewer { const { method, args } = this.currentState this.backend[method](...args) if (method === 'startWorld') { - void this.worldView!.init(args[0].playerState.getPosition()) + // void this.worldView!.init(args[0].playerState.getPosition()) } } } - startWorld (world, renderDistance: number, playerStateSend: IPlayerState = this.playerState) { + async startWorld (world, renderDistance: number, playerStateSend: IPlayerState = this.playerState) { if (this.currentDisplay === 'world') throw new Error('World already started') this.currentDisplay = 'world' const startPosition = playerStateSend.getPosition() @@ -156,14 +171,17 @@ export class AppViewer { rendererState: this.rendererState, nonReactiveState: this.nonReactiveState } + let promise: undefined | Promise if (this.backend) { - this.backend.startWorld(displayWorldOptions) - void this.worldView.init(startPosition) + promise = this.backend.startWorld(displayWorldOptions) ?? undefined + // void this.worldView.init(startPosition) } this.currentState = { method: 'startWorld', args: [displayWorldOptions] } + await promise // Resolve the promise after world is started this.resolveWorldReady() + return !!promise } resetBackend (cleanState = false) { @@ -237,13 +255,15 @@ const initialMenuStart = async () => { } appViewer.startPanorama() - // await appViewer.resourcesManager.loadMcData('1.21.4') - // const world = getSyncWorld('1.21.4') - // world.setBlockStateId(new Vec3(0, 64, 0), 1) - // appViewer.resourcesManager.currentConfig = { version: '1.21.4' } + // const version = '1.18.2' + // const version = '1.21.4' + // await appViewer.resourcesManager.loadMcData(version) + // const world = getSyncWorld(version) + // world.setBlockStateId(new Vec3(0, 64, 0), loadedData.blocksByName.water.defaultState) + // appViewer.resourcesManager.currentConfig = { version } // await appViewer.resourcesManager.updateAssetsData({}) // appViewer.playerState = new BasePlayerState() as any - // appViewer.startWorld(world, 3) + // await appViewer.startWorld(world, 3) // appViewer.backend?.updateCamera(new Vec3(0, 64, 2), 0, 0) // void appViewer.worldView!.init(new Vec3(0, 64, 0)) } diff --git a/src/appViewerLoad.ts b/src/appViewerLoad.ts new file mode 100644 index 00000000..09649b3f --- /dev/null +++ b/src/appViewerLoad.ts @@ -0,0 +1,34 @@ +import { subscribeKey } from 'valtio/utils' +import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend' +import { options } from './optionsStorage' +import { appViewer } from './appViewer' +import { miscUiState } from './globalState' +import { watchOptionsAfterViewerInit } from './watchOptions' + +const loadBackend = () => { + if (options.activeRenderer === 'webgpu') { + // appViewer.loadBackend(createWebgpuBackend) + } else { + appViewer.loadBackend(createGraphicsBackend) + } +} +window.loadBackend = loadBackend +if (process.env.SINGLE_FILE_BUILD_MODE) { + const unsub = subscribeKey(miscUiState, 'fsReady', () => { + if (miscUiState.fsReady) { + // don't do it earlier to load fs and display menu faster + loadBackend() + unsub() + } + }) +} else { + loadBackend() +} + +const animLoop = () => { + for (const fn of beforeRenderFrame) fn() + requestAnimationFrame(animLoop) +} +requestAnimationFrame(animLoop) + +watchOptionsAfterViewerInit() diff --git a/src/builtinCommands.ts b/src/builtinCommands.ts index 9e971e22..a292c5cd 100644 --- a/src/builtinCommands.ts +++ b/src/builtinCommands.ts @@ -2,12 +2,13 @@ import fs from 'fs' import { join } from 'path' import JSZip from 'jszip' import { getThreeJsRendererMethods } from 'renderer/viewer/three/threeJsMethods' -import { readLevelDat } from './loadSave' +import { fsState, readLevelDat } from './loadSave' import { closeWan, openToWanAndCopyJoinLink } from './localServerMultiplayer' import { copyFilesAsync, uniqueFileNameFromWorldName } from './browserfs' import { saveServer } from './flyingSquidUtils' import { setLoadingScreenStatus } from './appStatus' import { displayClientChat } from './botUtils' +import { miscUiState } from './globalState' const notImplemented = () => { return 'Not implemented yet' @@ -69,7 +70,7 @@ export const exportWorld = async (path: string, type: 'zip' | 'folder', zipName // todo include in help const exportLoadedWorld = async () => { await saveServer() - let { worldFolder } = localServer!.options + let worldFolder = fsState.inMemorySavePath if (!worldFolder.startsWith('/')) worldFolder = `/${worldFolder}` await exportWorld(worldFolder, 'zip') } @@ -141,12 +142,12 @@ export const commands: Array<{ ] //@ts-format-ignore-endregion -export const getBuiltinCommandsList = () => commands.filter(command => command.alwaysAvailable || localServer).flatMap(command => command.command) +export const getBuiltinCommandsList = () => commands.filter(command => command.alwaysAvailable || miscUiState.singleplayer).flatMap(command => command.command) export const tryHandleBuiltinCommand = (message: string) => { const [userCommand, ...args] = message.split(' ') - for (const command of commands.filter(command => command.alwaysAvailable || localServer)) { + for (const command of commands.filter(command => command.alwaysAvailable || miscUiState.singleplayer)) { if (command.command.includes(userCommand)) { void command.invoke(args) // ignoring for now return true diff --git a/src/controls.ts b/src/controls.ts index aa78b103..d4522eae 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -417,8 +417,9 @@ const alwaysPressedHandledCommand = (command: Command) => { export function lockUrl () { let newQs = '' - if (fsState.saveLoaded) { - const save = localServer!.options.worldFolder.split('/').at(-1) + if (fsState.saveLoaded && fsState.inMemorySave) { + const worldFolder = fsState.inMemorySavePath + const save = worldFolder.split('/').at(-1) newQs = `loadSave=${save}` } else if (process.env.NODE_ENV === 'development') { newQs = `reconnect=1` @@ -579,7 +580,7 @@ contro.on('release', ({ command }) => { export const f3Keybinds: Array<{ key?: string, - action: () => void, + action: () => void | Promise, mobileTitle: string enabled?: () => boolean }> = [ @@ -694,7 +695,7 @@ document.addEventListener('keydown', (e) => { if (hardcodedPressedKeys.has('F3')) { const keybind = f3Keybinds.find((v) => v.key === e.code) if (keybind && (keybind.enabled?.() ?? true)) { - keybind.action() + void keybind.action() e.stopPropagation() } return @@ -933,13 +934,17 @@ window.addEventListener('keydown', (e) => { if (e.code === 'KeyL' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { console.clear() } - if (e.code === 'KeyK' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { + if (e.code === 'KeyK' && e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey) { if (sessionStorage.delayLoadUntilFocus) { sessionStorage.removeItem('delayLoadUntilFocus') } else { sessionStorage.setItem('delayLoadUntilFocus', 'true') } } + if (e.code === 'KeyK' && e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey) { + // eslint-disable-next-line no-debugger + debugger + } }) // #endregion diff --git a/src/core/timers.ts b/src/core/timers.ts index fd9c72f5..570f46c0 100644 --- a/src/core/timers.ts +++ b/src/core/timers.ts @@ -124,12 +124,12 @@ export const preventThrottlingWithSound = () => { // Start playing oscillator.start() - return () => { + return async () => { try { oscillator.stop() - audioContext.close() + await audioContext.close() } catch (err) { - console.error('Error stopping silent audio:', err) + console.warn('Error stopping silent audio:', err) } } } catch (err) { diff --git a/src/devtools.ts b/src/devtools.ts index 5b15711f..b9267127 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -21,7 +21,8 @@ window.inspectPlayer = () => require('fs').promises.readFile('/world/playerdata/ Object.defineProperty(window, 'debugSceneChunks', { get () { - return (window.world as WorldRendererThree)?.getLoadedChunksRelative?.(bot.entity.position, true) + if (!(window.world instanceof WorldRendererThree)) return undefined + return (window.world)?.getLoadedChunksRelative?.(bot.entity.position, true) }, }) @@ -149,7 +150,7 @@ Object.defineProperty(window, 'debugToggle', { }) customEvents.on('gameLoaded', () => { - window.holdingBlock = (window.world as WorldRendererThree).holdingBlock + window.holdingBlock = (window.world as WorldRendererThree | undefined)?.holdingBlock }) window.clearStorage = (...keysToKeep: string[]) => { diff --git a/src/globals.d.ts b/src/globals.d.ts index 2a854f16..b8741a12 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -34,4 +34,17 @@ declare interface Document { exitPointerLock?(): void } +declare module '*.frag' { + const png: string + export default png +} +declare module '*.vert' { + const png: string + export default png +} +declare module '*.wgsl' { + const png: string + export default png +} + declare interface Window extends Record { } diff --git a/src/globals.js b/src/globals.js index 005d422f..1aa141c6 100644 --- a/src/globals.js +++ b/src/globals.js @@ -8,3 +8,4 @@ window.worldView = undefined window.viewer = undefined window.loadedData = undefined window.customEvents = new EventEmitter() +window.customEvents.setMaxListeners(10_000) diff --git a/src/googledrive.ts b/src/googledrive.ts index 76fa1ce2..5e5e9ae9 100644 --- a/src/googledrive.ts +++ b/src/googledrive.ts @@ -4,7 +4,6 @@ import React from 'react' import { loadScript } from 'renderer/viewer/lib/utils' import { loadGoogleDriveApi, loadInMemorySave } from './react/SingleplayerProvider' import { setLoadingScreenStatus } from './appStatus' -import { mountGoogleDriveFolder } from './browserfs' import { showOptionsModal } from './react/SelectOption' import { appQueryParams } from './appParams' @@ -67,7 +66,7 @@ export const possiblyHandleStateVariable = async () => { } setLoadingScreenStatus('Opening world in read only mode...') googleProviderState.accessToken = response.access_token - await mountGoogleDriveFolder(true, parsed.ids[0]) + // await mountGoogleDriveFolder(true, parsed.ids[0]) await loadInMemorySave('/google') } }) diff --git a/src/index.ts b/src/index.ts index 623a88ac..b8a52a49 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,8 +95,7 @@ import { startLocalReplayServer } from './packetsReplay/replayPackets' import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording' import { createConsoleLogProgressReporter, createFullScreenProgressReporter, ProgressReporter } from './core/progressReporter' import { appViewer } from './appViewer' -import createGraphicsBackend from 'renderer/viewer/three/graphicsBackend' -import { subscribeKey } from 'valtio/utils' +import './appViewerLoad' window.debug = debug window.beforeRenderFrame = [] @@ -115,30 +114,6 @@ customChannels() if (appQueryParams.testCrashApp === '2') throw new Error('test') -const loadBackend = () => { - appViewer.loadBackend(createGraphicsBackend) -} -window.loadBackend = loadBackend -if (process.env.SINGLE_FILE_BUILD_MODE) { - const unsub = subscribeKey(miscUiState, 'fsReady', () => { - if (miscUiState.fsReady) { - // don't do it earlier to load fs and display menu faster - loadBackend() - unsub() - } - }) -} else { - loadBackend() -} - -const animLoop = () => { - for (const fn of beforeRenderFrame) fn() - requestAnimationFrame(animLoop) -} -requestAnimationFrame(animLoop) - -watchOptionsAfterViewerInit() - function hideCurrentScreens () { activeModalStacks['main-menu'] = [...activeModalStack] insertActiveModalStack('', []) @@ -223,6 +198,7 @@ export async function connect (connectOptions: ConnectOptions) { let bot!: typeof __type_bot const destroyAll = () => { if (ended) return + errorAbortController.abort() ended = true progress.end() // dont reset viewer so we can still do debugging @@ -240,7 +216,6 @@ export async function connect (connectOptions: ConnectOptions) { //@ts-expect-error window.bot = bot = undefined } - resetStateAfterDisconnect() cleanFs() } const cleanFs = () => { @@ -261,7 +236,6 @@ export async function connect (connectOptions: ConnectOptions) { if (err === 'ResizeObserver loop completed with undelivered notifications.') { return } - errorAbortController.abort() if (isCypress()) throw err miscUiState.hasErrors = true if (miscUiState.gameLoaded) return @@ -272,6 +246,7 @@ export async function connect (connectOptions: ConnectOptions) { destroyAll() } + // todo(hard): remove it! const errorAbortController = new AbortController() window.addEventListener('unhandledrejection', (e) => { if (e.reason.name === 'ServerPluginLoadFailure') { @@ -732,7 +707,7 @@ export async function connect (connectOptions: ConnectOptions) { console.log('bot spawned - starting viewer') - appViewer.startWorld(bot.world, renderDistance) + await appViewer.startWorld(bot.world, renderDistance) appViewer.worldView!.listenToBot(bot) initMotionTracking() diff --git a/src/loadSave.ts b/src/loadSave.ts index 4746a95c..c672193c 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -22,7 +22,8 @@ export const fsState = proxy({ saveLoaded: false, openReadOperations: 0, openWriteOperations: 0, - remoteBackend: false + remoteBackend: false, + inMemorySavePath: '' }) const PROPOSE_BACKUP = true @@ -181,6 +182,7 @@ export const loadSave = async (root = '/world') => { // todo should not be set here fsState.saveLoaded = true + fsState.inMemorySavePath = root window.dispatchEvent(new CustomEvent('singleplayer', { // todo check gamemode level.dat data etc detail: { diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index a247350b..5b83922f 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -61,8 +61,6 @@ export const guiOptionsScheme: { custom () { return } {noConnection && ( diff --git a/src/resourcesManager.ts b/src/resourcesManager.ts index d4c1fe81..4ee91ab4 100644 --- a/src/resourcesManager.ts +++ b/src/resourcesManager.ts @@ -50,7 +50,7 @@ export class LoadedResources { export interface ResourcesCurrentConfig { version: string texturesVersion?: string - noBlockstatesModels?: boolean + // noBlockstatesModels?: boolean noInventoryGui?: boolean includeOnlyBlocks?: string[] } @@ -115,34 +115,12 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter - const date = new Date() - if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) { - Object.assign(blockTexturesChanges, christmasPack) - } - - const customBlockTextures = Object.keys(resources.customTextures.blocks?.textures ?? {}) - const customItemTextures = Object.keys(resources.customTextures.items?.textures ?? {}) - - console.time('createBlocksAtlas') - const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas( - resources.texturesVersion, - (textureName) => { - if (this.currentConfig!.includeOnlyBlocks && !this.currentConfig!.includeOnlyBlocks.includes(textureName)) return false - const texture = resources.customTextures.blocks?.textures[textureName] - return blockTexturesChanges[textureName] ?? texture - }, - undefined, - undefined, - customBlockTextures - ) - console.timeEnd('createBlocksAtlas') + await this.recreateBlockAtlas(resources) if (abortController.signal.aborted) return + + const itemsAssetsParser = new AtlasParser(this.sourceItemsAtlases, itemsAtlasLatest, itemsAtlasLegacy) + const customItemTextures = Object.keys(resources.customTextures.items?.textures ?? {}) console.time('createItemsAtlas') const { atlas: itemsAtlas, canvas: itemsCanvas } = await itemsAssetsParser.makeNewAtlas( resources.texturesVersion, @@ -157,9 +135,7 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter TypedEmitter TypedEmitter + const date = new Date() + if ((date.getMonth() === 11 && date.getDate() >= 24) || (date.getMonth() === 0 && date.getDate() <= 6)) { + Object.assign(blockTexturesChanges, christmasPack) + } + + const blocksAssetsParser = new AtlasParser(this.sourceBlocksAtlases, blocksAtlasLatest, blocksAtlasLegacy) + + const customBlockTextures = Object.keys(resources.customTextures.blocks?.textures ?? {}) + console.time('createBlocksAtlas') + const { atlas: blocksAtlas, canvas: blocksCanvas } = await blocksAssetsParser.makeNewAtlas( + resources.texturesVersion, + (textureName) => { + if (this.currentConfig!.includeOnlyBlocks && !this.currentConfig!.includeOnlyBlocks.includes(textureName)) return false + const texture = resources.customTextures.blocks?.textures[textureName] + return blockTexturesChanges[textureName] ?? texture + }, + undefined, + undefined, + customBlockTextures, + { + needHorizontalIndexes: !!this.currentConfig!.includeOnlyBlocks, + } + ) + console.timeEnd('createBlocksAtlas') + + resources.blocksAtlasParser = new AtlasParser({ latest: blocksAtlas }, blocksCanvas.toDataURL()) + resources.blocksAtlasImage = await getLoadedImage(blocksCanvas.toDataURL()) + + resources.worldBlockProvider = worldBlockProvider( + resources.blockstatesModels, + resources.blocksAtlasParser.atlas, + STABLE_MODELS_VERSION + ) + } + async generateGuiTextures () { await generateGuiAtlas() } diff --git a/src/shims/empty.ts b/src/shims/empty.ts index f0a766d3..9fabff8f 100644 --- a/src/shims/empty.ts +++ b/src/shims/empty.ts @@ -1 +1,3 @@ +// eslint-disable-next-line @typescript-eslint/no-useless-empty-export export { } +export default {} diff --git a/src/shims/fs.js b/src/shims/fs.js index b238a376..df83f969 100644 --- a/src/shims/fs.js +++ b/src/shims/fs.js @@ -1,3 +1,12 @@ const BrowserFS = require('browserfs') -module.exports = BrowserFS.BFSRequire('fs') +globalThis.fs ??= BrowserFS.BFSRequire('fs') +globalThis.fs.promises = new Proxy({}, { + get(target, p) { + return (...args) => { + return globalThis.promises[p](...args) + } + } +}) + +module.exports = globalThis.fs diff --git a/src/shims/perf_hooks_replacement.js b/src/shims/perf_hooks_replacement.js index 69b0e2ed..6c19053a 100644 --- a/src/shims/perf_hooks_replacement.js +++ b/src/shims/perf_hooks_replacement.js @@ -1 +1 @@ -module.exports.performance = window.performance +module.exports.performance = globalThis.performance diff --git a/src/utils.ts b/src/utils.ts index 6399a5c4..17f5c2ec 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -65,6 +65,16 @@ export const pointerLock = { } } +export const logAction = (category: string, action: string, value?: string, label?: string) => { + if (!options.externalLoggingService) return + window.loggingServiceChannel?.({ + category, + action, + value, + label + }) +} + export const isInRealGameSession = () => { return isGameActive(true) && (!packetsReplayState.isOpen || packetsReplayState.isMinimized) && !gameAdditionalState.viewerConnection } @@ -148,11 +158,11 @@ export const setRenderDistance = () => { localServer!.players[0].view = 0 renderDistance = 0 } - worldView.updateViewDistance(renderDistance) + worldView?.updateViewDistance(renderDistance) prevRenderDistance = renderDistance } export const reloadChunks = async () => { - if (!worldView) return + if (!bot || !worldView) return setRenderDistance() await worldView.updatePosition(bot.entity.position, true) } diff --git a/src/watchOptions.ts b/src/watchOptions.ts index ddb25cb8..41f78d07 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -8,6 +8,8 @@ import { reloadChunks } from './utils' import { miscUiState } from './globalState' import { isCypress } from './standaloneUtils' +globalThis.viewer ??= { world: {} } + subscribeKey(options, 'renderDistance', reloadChunks) subscribeKey(options, 'multiplayerRenderDistance', reloadChunks) @@ -85,8 +87,8 @@ export const watchOptionsAfterViewerInit = () => { appViewer.inWorldRenderingConfig.extraBlockRenderers = !o.disableSignsMapsSupport appViewer.inWorldRenderingConfig.fetchPlayerSkins = o.loadPlayerSkins appViewer.inWorldRenderingConfig.highlightBlockColor = o.highlightBlockColor - appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererOptions.three._experimentalSmoothChunkLoading - appViewer.inWorldRenderingConfig._renderByChunks = o.rendererOptions.three._renderByChunks + appViewer.inWorldRenderingConfig._experimentalSmoothChunkLoading = o.rendererSharedOptions._experimentalSmoothChunkLoading + appViewer.inWorldRenderingConfig._renderByChunks = o.rendererSharedOptions._renderByChunks }) appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting diff --git a/src/water.ts b/src/water.ts index 9221ec8a..257a5e13 100644 --- a/src/water.ts +++ b/src/water.ts @@ -1,3 +1,4 @@ +import { ref } from 'valtio' import { watchUnloadForCleanup } from './gameUnload' let inWater = false @@ -9,9 +10,10 @@ customEvents.on('gameLoaded', () => { watchUnloadForCleanup(cleanup) const updateInWater = () => { - const waterBr = Object.keys(bot.entity.effects).find((effect: any) => loadedData.effects[effect.id].name === 'water_breathing') + const waterBr = Object.keys(bot.entity.effects).find((effect: any) => loadedData.effects[effect.id]?.name === 'water_breathing') if (inWater) { appViewer.playerState.reactive.inWater = true + appViewer.playerState.reactive.waterBreathing = waterBr !== undefined } else { cleanup() } @@ -31,5 +33,5 @@ let sceneBg = { r: 0, g: 0, b: 0 } export const updateBackground = (newSceneBg = sceneBg) => { sceneBg = newSceneBg const color: [number, number, number] = inWater ? [0, 0, 1] : [sceneBg.r, sceneBg.g, sceneBg.b] - appViewer.playerState.reactive.backgroundColor = color + appViewer.playerState.reactive.backgroundColor = ref(color) }