From 0acaa652a3187704096726b60bfc0ff0fbac6b33 Mon Sep 17 00:00:00 2001 From: Vitaly Date: Sat, 20 Apr 2024 13:16:36 +0300 Subject: [PATCH] big worlds refactor with more scalable api, which allows hmr workers fix: better free up ram after world unload & create meshers only when needed feat: add low ram setting, which makes chunks load performance ~4x worse --- README.MD | 7 ++ prismarine-viewer/tsconfig.json | 15 ++-- prismarine-viewer/viewer/lib/mesher/mesher.ts | 21 +++-- prismarine-viewer/viewer/lib/mesher/models.ts | 4 +- .../viewer/lib/mesher/test/mesherTester.ts | 4 +- prismarine-viewer/viewer/lib/mesher/world.ts | 12 +-- prismarine-viewer/viewer/lib/viewer.ts | 18 ++-- .../viewer/lib/worldDataEmitter.ts | 8 +- .../viewer/lib/worldrendererCommon.ts | 83 ++++++++++++++----- .../viewer/lib/worldrendererThree.ts | 15 ++-- src/index.ts | 2 +- src/optionsGuiScheme.tsx | 37 ++++++--- src/optionsStorage.ts | 1 + src/react/OptionsItems.tsx | 23 ++++- src/soundSystem.ts | 26 +++++- src/texturePack.ts | 2 +- src/watchOptions.ts | 16 ++-- tsconfig.json | 1 + 18 files changed, 203 insertions(+), 92 deletions(-) diff --git a/README.MD b/README.MD index 4431fe03..7a93ce7e 100644 --- a/README.MD +++ b/README.MD @@ -19,6 +19,13 @@ You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm. - Resource pack support - even even more! +### Recommended Settings + +- Controls -> **Raw Input** -> **On** - This will make the controls more precise +- Controls -> **Touch Controls Type** -> **Joystick** +- Controls -> **Auto Full Screen** -> **On** - To avoid ctrl+w issue +- Interface -> **Chat Select** -> **On** - To select chat messages + ### World Loading Zip files and folders are supported. Just drag and drop them into the browser window. You can open folders in readonly and read-write mode. New chunks may be generated incorrectly for now. diff --git a/prismarine-viewer/tsconfig.json b/prismarine-viewer/tsconfig.json index 1cf48c6e..b6e39df3 100644 --- a/prismarine-viewer/tsconfig.json +++ b/prismarine-viewer/tsconfig.json @@ -1,9 +1,10 @@ { - "compilerOptions": { - "module": "commonjs", - "strictNullChecks": true - }, - "files": [ - "index.d.ts" - ] + "compilerOptions": { + "module": "commonjs", + "strictNullChecks": true, + "experimentalDecorators": true + }, + "files": [ + "index.d.ts" + ] } diff --git a/prismarine-viewer/viewer/lib/mesher/mesher.ts b/prismarine-viewer/viewer/lib/mesher/mesher.ts index 28859a3e..0bcf7250 100644 --- a/prismarine-viewer/viewer/lib/mesher/mesher.ts +++ b/prismarine-viewer/viewer/lib/mesher/mesher.ts @@ -1,6 +1,6 @@ import { World } from './world' import { Vec3 } from 'vec3' -import { getSectionGeometry, setRendererData } from './models' +import { getSectionGeometry, setBlockStatesData } from './models' if (module.require) { // If we are in a node environement, we need to fake some env variables @@ -39,7 +39,8 @@ function setSectionDirty (pos, value = true) { } const softCleanup = () => { - world.blockCache = {} + // clean block cache and loaded chunks + world = new World(world.config.version) } self.onmessage = ({ data }) => { @@ -47,16 +48,18 @@ self.onmessage = ({ data }) => { if (data.type === 'mcData') { globalVar.mcData = data.mcData - world = new World(data.version) - } else if (data.type === 'rendererData') { - setRendererData(data.json/* , data.textureSize */) - world.outputFormat = data.outputFormat ?? world.outputFormat + } + + if (data.config) { + world ??= new World(data.config.version) + world.config = {...world.config, ...data.config} + } + + if (data.type === 'mesherData') { + setBlockStatesData(data.json) blockStatesReady = true } else if (data.type === 'dirty') { const loc = new Vec3(data.x, data.y, data.z) - world.skyLight = data.skyLight - world.smoothLighting = data.smoothLighting - world.enableLighting = data.enableLighting setSectionDirty(loc, data.value) } else if (data.type === 'chunk') { world.addColumn(data.x, data.z, data.chunk) diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index e9327621..29b4edb5 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -386,7 +386,7 @@ function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr const corner = world.getBlock(cursor.offset(...cornerDir)) let cornerLightResult = 15 - if (world.smoothLighting) { + if (world.config.smoothLighting) { const side1Light = world.getLight(cursor.plus(new Vec3(...side1Dir)), true) const side2Light = world.getLight(cursor.plus(new Vec3(...side2Dir)), true) const cornerLight = world.getLight(cursor.plus(new Vec3(...cornerDir)), true) @@ -605,7 +605,7 @@ function getModelVariants (block: import('prismarine-block').Block) { return [] } -export const setRendererData = (_blockStates: BlockStatesOutput | null, _needTiles = false) => { +export const setBlockStatesData = (_blockStates: BlockStatesOutput | null, _needTiles = false) => { blockStates = _blockStates! needTiles = _needTiles } diff --git a/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts b/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts index 15ec165b..21c5d129 100644 --- a/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts +++ b/prismarine-viewer/viewer/lib/mesher/test/mesherTester.ts @@ -1,4 +1,4 @@ -import { setRendererData, getSectionGeometry } from '../models' +import { setBlockStatesData, getSectionGeometry } from '../models' import { World as MesherWorld } from '../world' import ChunkLoader from 'prismarine-chunk' import { Vec3 } from 'vec3' @@ -30,7 +30,7 @@ export const setup = (version, initialBlocks: [number[], string][]) => { } } - setRendererData(blockStates, true) + setBlockStatesData(blockStates, true) mesherWorld.addColumn(0, 0, chunk1.toJson()) return { diff --git a/prismarine-viewer/viewer/lib/mesher/world.ts b/prismarine-viewer/viewer/lib/mesher/world.ts index af9d3d9f..50616f82 100644 --- a/prismarine-viewer/viewer/lib/mesher/world.ts +++ b/prismarine-viewer/viewer/lib/mesher/world.ts @@ -3,6 +3,7 @@ import mcData from 'minecraft-data' import { Block } from "prismarine-block" import { Vec3 } from 'vec3' import moreBlockDataGeneratedJson from '../moreBlockDataGenerated.json' +import { defaultMesherConfig } from './shared' const ignoreAoBlocks = Object.keys(moreBlockDataGeneratedJson.noOcclusions) @@ -24,10 +25,7 @@ export type WorldBlock = Block & { export class World { - enableLighting = true - skyLight = 15 - smoothLighting = true - outputFormat = 'threeJs' as 'threeJs' | 'webgl' + config = defaultMesherConfig Chunk: typeof import('prismarine-chunk/types/index').PCChunk columns = {} as { [key: string]: import('prismarine-chunk/types/index').PCChunk } blockCache = {} @@ -36,10 +34,12 @@ export class World { constructor(version) { this.Chunk = Chunks(version) as any this.biomeCache = mcData(version).biomes + this.config.version = version } getLight (pos: Vec3, isNeighbor = false) { - if (!this.enableLighting) return 15 + const { enableLighting, skyLight } = this.config + 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) @@ -48,7 +48,7 @@ export class World { 15, Math.max( column.getBlockLight(posInChunk(pos)), - Math.min(this.skyLight, column.getSkyLight(posInChunk(pos))) + Math.min(skyLight, column.getSkyLight(posInChunk(pos))) ) + 2 ) // lightsCache.set(key, result) diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 85c15719..9097c7ec 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -6,7 +6,7 @@ import { getVersion } from './version' import EventEmitter from 'events' import { WorldRendererThree } from './worldrendererThree' import { generateSpiralMatrix } from 'flying-squid/dist/utils' -import { WorldRendererCommon } from './worldrendererCommon' +import { WorldRendererCommon, WorldRendererConfig, defaultWorldRendererConfig } from './worldrendererCommon' export class Viewer { scene: THREE.Scene @@ -19,13 +19,13 @@ export class Viewer { domElement: HTMLCanvasElement playerHeight = 1.62 isSneaking = false - version: string + threeJsWorld: WorldRendererThree cameraObjectOverride?: THREE.Object3D // for xr audioListener: THREE.AudioListener renderingUntilNoUpdates = false processEntityOverrides = (e, overrides) => overrides - constructor(public renderer: THREE.WebGLRenderer, numWorkers?: number) { + constructor(public renderer: THREE.WebGLRenderer, worldConfig = defaultWorldRendererConfig) { // https://discourse.threejs.org/t/updates-to-color-management-in-three-js-r152/50791 THREE.ColorManagement.enabled = false renderer.outputColorSpace = THREE.LinearSRGBColorSpace @@ -33,13 +33,18 @@ export class Viewer { this.scene = new THREE.Scene() this.scene.matrixAutoUpdate = false // for perf this.resetScene() - this.world = new WorldRendererThree(this.scene, this.renderer, this.camera, numWorkers) + this.threeJsWorld = new WorldRendererThree(this.scene, this.renderer, this.camera, worldConfig) + this.setWorld() this.entities = new Entities(this.scene) // this.primitives = new Primitives(this.scene, this.camera) this.domElement = renderer.domElement } + setWorld () { + this.world = this.threeJsWorld + } + resetScene () { this.scene.background = new THREE.Color('lightblue') @@ -67,7 +72,6 @@ export class Viewer { setVersion (userVersion: string) { const texturesVersion = getVersion(userVersion) console.log('[viewer] Using version:', userVersion, 'textures:', texturesVersion) - this.version = userVersion this.world.setVersion(userVersion, texturesVersion) this.entities.clear() // this.primitives.clear() @@ -187,8 +191,8 @@ export class Viewer { skyLight = ((timeOfDay - 12000) / 6000) * 15 } - if (this.world.skyLight === skyLight) return - this.world.skyLight = skyLight + if (this.world.mesherConfig.skyLight === skyLight) return + this.world.mesherConfig.skyLight = skyLight ; (this.world as WorldRendererThree).rerenderAllChunks?.() }) diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index cfcee0e4..ed53847d 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -151,6 +151,13 @@ export class WorldDataEmitter extends EventEmitter { } } + unloadAllChunks () { + for (const coords of Object.keys(this.loadedChunks)) { + const [x, z] = coords.split(',').map(Number) + this.unloadChunk({ x, z }) + } + } + unloadChunk (pos: ChunkPos) { this.emitter.emit('unloadChunk', { x: pos.x, z: pos.z }) delete this.loadedChunks[`${pos.x},${pos.z}`] @@ -172,7 +179,6 @@ export class WorldDataEmitter extends EventEmitter { chunksToUnload.push(p) } } - // todo @sa2urami console.log('unloading', chunksToUnload.length, 'total now', Object.keys(this.loadedChunks).length) for (const p of chunksToUnload) { this.unloadChunk(p) diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index 427bc446..151b2679 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -7,21 +7,35 @@ import mcDataRaw from 'minecraft-data/data.js' // handled correctly in esbuild p import { dynamicMcDataFiles } from '../../buildMesherConfig.mjs' import { toMajor } from './version.js' import { chunkPos } from './simpleUtils' +import { defaultMesherConfig } from './mesher/shared' +import { buildCleanupDecorator } from './cleanupDecorator' function mod (x, n) { return ((x % n) + n) % n } +export const worldCleanup = buildCleanupDecorator('resetWorld') + +export const defaultWorldRendererConfig = { + showChunkBorders: false, + numWorkers: 4 +} + +export type WorldRendererConfig = typeof defaultWorldRendererConfig + export abstract class WorldRendererCommon { worldConfig = { minY: 0, worldHeight: 256 } // todo @sa2urami set alphaTest back to 0.1 and instead properly sort transparent and solid objects (needs to be done in worker too) material = new THREE.MeshLambertMaterial({ vertexColors: true, transparent: true, alphaTest: 0.5 }) - showChunkBorders = false + @worldCleanup() active = false version = undefined as string | undefined + @worldCleanup() loadedChunks = {} as Record + @worldCleanup() finishedChunks = {} as Record + @worldCleanup() sectionsOutstanding = new Map() renderUpdateEmitter = new EventEmitter() customBlockStatesData = undefined as any @@ -37,15 +51,21 @@ export abstract class WorldRendererCommon texturesVersion?: string viewDistance = -1 chunksLength = 0 - skyLight = 15 - smoothLighting = true - enableLighting = true + @worldCleanup() allChunksFinished = false handleResize = () => { } + mesherConfig = defaultMesherConfig abstract outputFormat: 'threeJs' | 'webgl' - constructor(numWorkers: number) { + constructor (public config: WorldRendererConfig) { + // this.initWorkers(1) // preload script on page load + this.snapshotInitialValues() + } + + snapshotInitialValues() {} + + initWorkers(numWorkers = this.config.numWorkers) { // init workers for (let i = 0; i < numWorkers; i++) { // Node environment needs an absolute path, but browser needs the url of the file @@ -127,32 +147,38 @@ export abstract class WorldRendererCommon abstract updateShowChunksBorder (value: boolean): void resetWorld () { - this.active = false - this.loadedChunks = {} - this.sectionsOutstanding = new Map() - this.finishedChunks = {} - this.allChunksFinished = false + // destroy workers for (const worker of this.workers) { - worker.postMessage({ type: 'reset' }) + worker.terminate() } + this.workers = [] } + + // new game load happens here setVersion (version, texturesVersion = version) { this.version = version this.texturesVersion = texturesVersion this.resetWorld() + this.initWorkers() this.active = true + this.mesherConfig.outputFormat = this.outputFormat + this.mesherConfig.version = this.version! - const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] - for (const worker of this.workers) { - const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) - mcData.version = JSON.parse(JSON.stringify(mcData.version)) - worker.postMessage({ type: 'mcData', mcData, version: this.version }) - } - + this.sendMesherMcData() this.updateTexturesData() } + sendMesherMcData () { + const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajor(this.version)] + const mcData = Object.fromEntries(Object.entries(allMcData).filter(([key]) => dynamicMcDataFiles.includes(key))) + mcData.version = JSON.parse(JSON.stringify(mcData.version)) + + for (const worker of this.workers) { + worker.postMessage({ type: 'mcData', mcData, config: this.mesherConfig }) + } + } + updateTexturesData () { loadTexture(this.customTexturesDataUrl || `textures/${this.texturesVersion}.png`, (texture: import('three').Texture) => { texture.magFilter = THREE.NearestFilter @@ -166,15 +192,20 @@ export abstract class WorldRendererCommon if (this.customBlockStatesData) return resolve(this.customBlockStatesData) return loadJSON(`/blocksStates/${this.texturesVersion}.json`, (data) => { this.downloadedBlockStatesData = data - // todo this.renderUpdateEmitter.emit('blockStatesDownloaded') resolve(data) }) }) } loadBlockStates().then((blockStates) => { + this.mesherConfig.textureSize = this.material.map!.image.width + for (const worker of this.workers) { - worker.postMessage({ type: 'rendererData', json: blockStates, textureSize: this.material.map!.image.width, outputFormat: this.outputFormat }) + worker.postMessage({ + type: 'mesherData', + json: blockStates, + config: this.mesherConfig, + }) } }) }) @@ -182,6 +213,7 @@ export abstract class WorldRendererCommon } addColumn (x, z, chunk) { + if (this.workers.length === 0) throw new Error('workers not initialized yet') this.initialChunksLoad = false this.loadedChunks[`${x},${z}`] = true for (const worker of this.workers) { @@ -224,7 +256,7 @@ export abstract class WorldRendererCommon if (this.viewDistance === -1) throw new Error('viewDistance not set') this.allChunksFinished = false const distance = this.getDistance(pos) - if (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}` // if (this.sectionsOutstanding.has(key)) return this.renderUpdateEmitter.emit('dirty', pos, value) @@ -233,7 +265,14 @@ export abstract class WorldRendererCommon // is always dispatched to the same worker const hash = mod(Math.floor(pos.x / 16) + Math.floor(pos.y / 16) + Math.floor(pos.z / 16), this.workers.length) this.sectionsOutstanding.set(key, (this.sectionsOutstanding.get(key) ?? 0) + 1) - this.workers[hash].postMessage({ type: 'dirty', x: pos.x, y: pos.y, z: pos.z, value, skyLight: this.skyLight, smoothLighting: this.smoothLighting, enableLighting: this.enableLighting }) + this.workers[hash].postMessage({ + type: 'dirty', + x: pos.x, + y: pos.y, + z: pos.z, + value, + config: this.mesherConfig, + }) } // Listen for chunk rendering updates emitted if a worker finished a render and resolve if the number diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 05d9a76f..364df12a 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -5,19 +5,14 @@ import { dispose3 } from './dispose' import PrismarineChatLoader from 'prismarine-chat' import { renderSign } from '../sign-renderer/' import { chunkPos, sectionPos } from './simpleUtils' -import { WorldRendererCommon } from './worldrendererCommon' +import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import * as tweenJs from '@tweenjs/tween.js' import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass } from 'three-stdlib' -function mod (x, n) { - return ((x % n) + n) % n -} - export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const blockEntities = {} sectionObjects: Record = {} - showChunkBorders = false chunkTextures = new Map() signsCache = new Map() @@ -25,8 +20,8 @@ export class WorldRendererThree extends WorldRendererCommon { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) } - constructor(public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public camera: THREE.PerspectiveCamera, numWorkers = 4) { - super(numWorkers) + constructor(public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public camera: THREE.PerspectiveCamera, public config: WorldRendererConfig) { + super(config) } /** @@ -94,7 +89,7 @@ export class WorldRendererThree extends WorldRendererCommon { object.name = 'chunk' //@ts-ignore object.tilesCount = data.geometry.positions.length / 3 / 4 - if (!this.showChunkBorders) { + if (!this.config.showChunkBorders) { boxHelper.visible = false } // should not compute it once @@ -198,7 +193,7 @@ export class WorldRendererThree extends WorldRendererCommon { } updateShowChunksBorder (value: boolean) { - this.showChunkBorders = value + this.config.showChunkBorders = value for (const object of Object.values(this.sectionObjects)) { for (const child of object.children) { if (child.name === 'helper') { diff --git a/src/index.ts b/src/index.ts index 10090dc7..88eda6a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -135,7 +135,7 @@ if (isFirefox) { } // Create viewer -const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer, options.numWorkers) +const viewer: import('prismarine-viewer/viewer/lib/viewer').Viewer = new Viewer(renderer) window.viewer = viewer new THREE.TextureLoader().load(itemsPng, (texture) => { viewer.entities.itemsTexture = texture diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index f1ac35ca..d80a9ebe 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -53,7 +53,11 @@ export const guiOptionsScheme: { smoothLighting: {}, newVersionsLighting: { text: 'Lighting in newer versions', - } + }, + lowMemoryMode: { + text: 'Low Memory Mode', + enableWarning: 'Enabling it will make chunks load ~4x slower' + }, }, ], main: [ @@ -175,6 +179,9 @@ export const guiOptionsScheme: { ], controls: [ { + custom () { + return Keyboard & Mouse + }, // keybindings mouseSensX: {}, mouseSensY: { @@ -188,9 +195,6 @@ export const guiOptionsScheme: { // eslint-disable-next-line no-extra-boolean-cast disabledReason: Boolean(document.documentElement.requestPointerLock) ? undefined : 'Your browser does not support pointer lock.', }, - alwaysShowMobileControls: { - text: 'Always Mobile Controls', - }, autoFullScreen: { tooltip: 'Auto Fullscreen allows you to use Ctrl+W and Escape having to wait/click on screen again.', disabledReason: navigator['keyboard'] ? undefined : 'Your browser doesn\'t support keyboard lock API' @@ -198,6 +202,14 @@ export const guiOptionsScheme: { autoExitFullscreen: { tooltip: 'Exit fullscreen on escape (pause menu open). But note you can always do it with F11.', }, + }, + { + custom () { + return Touch Controls + }, + alwaysShowMobileControls: { + text: 'Always Mobile Controls', + }, touchButtonsSize: { min: 40 }, @@ -211,13 +223,6 @@ export const guiOptionsScheme: { touchControlsType: { values: [['classic', 'Classic'], ['joystick-buttons', 'New']], }, - autoJump: { - values: [ - 'always', - 'auto', - 'never' - ], - }, }, { custom () { @@ -226,6 +231,16 @@ export const guiOptionsScheme: { }, }, { + custom () { + return Auto Jump + }, + autoJump: { + values: [ + 'always', + 'auto', + 'never' + ], + }, autoParkour: {}, } ], diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index af284b62..559740b6 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -57,6 +57,7 @@ const defaultOptions = { unimplementedContainers: false, dayCycleAndLighting: true, loadPlayerSkins: true, + lowMemoryMode: false, // antiAliasing: false, showChunkBorders: false, // todo rename option diff --git a/src/react/OptionsItems.tsx b/src/react/OptionsItems.tsx index 9351d66b..5dd31923 100644 --- a/src/react/OptionsItems.tsx +++ b/src/react/OptionsItems.tsx @@ -6,12 +6,15 @@ import { options, qsOptions } from '../optionsStorage' import Button from './Button' import Slider from './Slider' import Screen from './Screen' +import { showOptionsModal } from './SelectOption' type GeneralItem = { id?: string text?: string, disabledReason?: string, tooltip?: string + // description?: string + enableWarning?: string willHaveNoEffect?: boolean values?: Array } @@ -56,7 +59,11 @@ export const OptionButton = ({ item }: { item: Extract { + onClick={async () => { + if (item.enableWarning && !options[item.id!]) { + const result = await showOptionsModal(item.enableWarning, ['Enable']) + if (!result) return + } const { values } = item if (values) { const getOptionValue = (arrItem) => { @@ -108,9 +115,17 @@ const RenderOption = ({ item }: { item: OptionMeta }) => { item.text ??= titleCase(noCase(item.id)) } - if (item.type === 'toggle') return - if (item.type === 'slider') return - if (item.type === 'element') return + let baseElement = null as React.ReactNode | null + if (item.type === 'toggle') baseElement = + if (item.type === 'slider') baseElement = + if (item.type === 'element') baseElement = + return baseElement + // if (!item.description && item.type === 'element') return baseElement + + // return
+ // {baseElement} + // {item.description &&
{item.description}
} + //
} interface Props { diff --git a/src/soundSystem.ts b/src/soundSystem.ts index 254b5f5d..a2cc1f70 100644 --- a/src/soundSystem.ts +++ b/src/soundSystem.ts @@ -8,10 +8,15 @@ import { options } from './optionsStorage' import { loadOrPlaySound } from './basicSounds' import { showNotification } from './react/NotificationProvider' +const globalObject = window as { + allSoundsMap?: Record>, + allSoundsVersionedMap?: Record, +} + subscribeKey(miscUiState, 'gameLoaded', async () => { if (!miscUiState.gameLoaded) return const soundsLegacyMap = window.allSoundsVersionedMap as Record - const allSoundsMap = window.allSoundsMap as Record> + const { allSoundsMap } = globalObject const allSoundsMeta = window.allSoundsMeta as { format: string, baseUrl: string } if (!allSoundsMap) { return @@ -115,7 +120,7 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { } const getStepSound = (blockUnder: Block) => { - // const soundsMap = window.allSoundsMap?.[bot.version] + // const soundsMap = globalObject.allSoundsMap?.[bot.version] // if (!soundsMap) return // let soundResult = 'block.stone.step' // for (const x of Object.keys(soundsMap).map(n => n.split(';')[1])) { @@ -215,8 +220,21 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { // }) }) +// todo +// const music = { +// activated: false, +// playing: '', +// activate () { +// const gameMusic = Object.entries(globalObject.allSoundsMap?.[bot.version] ?? {}).find(([id, sound]) => sound.includes('music.game')) +// if (!gameMusic) return +// const soundPath = gameMusic[0].split(';')[1] +// const next = () => {} +// } +// } + export const earlyCheck = () => { - const allSoundsMap = window.allSoundsMap as Record> + const { allSoundsMap } = globalObject + if (!allSoundsMap) return // todo also use major versioned hardcoded sounds const soundsMap = allSoundsMap[bot.version] @@ -239,7 +257,7 @@ const getVersionedSound = (version: string, item: string, itemsMapSortedEntries: } export const downloadSoundsIfNeeded = async () => { - if (!window.allSoundsMap) { + if (!globalObject.allSoundsMap) { try { await loadScript('./sounds.js') } catch (err) { diff --git a/src/texturePack.ts b/src/texturePack.ts index 6ace186b..8c251b49 100644 --- a/src/texturePack.ts +++ b/src/texturePack.ts @@ -93,7 +93,7 @@ export const completeTexturePackInstall = async (name?: string) => { await fs.promises.writeFile(join(texturePackBasePath, 'name.txt'), name ?? '??', 'utf8') if (viewer?.world.active) { - await genTexturePackTextures(viewer.version) + await genTexturePackTextures(viewer.world.version!) } setLoadingScreenStatus(undefined) showNotification('Texturepack installed') diff --git a/src/watchOptions.ts b/src/watchOptions.ts index cfed6afa..4380c6f3 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -30,24 +30,30 @@ export const watchOptionsAfterViewerInit = () => { watchValue(options, o => { if (!viewer) return - viewer.world.showChunkBorders = o.showChunkBorders + viewer.world.config.showChunkBorders = o.showChunkBorders viewer.entities.setDebugMode(o.showChunkBorders ? 'basic' : 'none') }) + watchValue(options, o => { + if (!viewer) return + // todo ideally there shouldnt be this setting and we don't need to send all same chunks to all workers + viewer.world.config.numWorkers = o.lowMemoryMode ? 1 : o.numWorkers + }) + watchValue(options, o => { viewer.entities.setVisible(o.renderEntities) }) - viewer.world.smoothLighting = options.smoothLighting + viewer.world.mesherConfig.smoothLighting = options.smoothLighting subscribeKey(options, 'smoothLighting', () => { - viewer.world.smoothLighting = options.smoothLighting; + viewer.world.mesherConfig.smoothLighting = options.smoothLighting; (viewer.world as WorldRendererThree).rerenderAllChunks() }) subscribeKey(options, 'newVersionsLighting', () => { - viewer.world.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting; + viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting; (viewer.world as WorldRendererThree).rerenderAllChunks() }) customEvents.on('gameLoaded', () => { - viewer.world.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting + viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting }) } diff --git a/tsconfig.json b/tsconfig.json index 47a363c3..5d018a46 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ "forceConsistentCasingInFileNames": true, "useUnknownInCatchVariables": false, "skipLibCheck": true, + "experimentalDecorators": true, // this the only options that allows smooth transition from js to ts (by not dropping types from js files) // however might need to consider includeing *only needed libraries* instead of using this "maxNodeModuleJsDepth": 1,