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 |
19 changed files with 441 additions and 99 deletions
|
|
@ -17,9 +17,10 @@ For building the project yourself / contributing, see [Development, Debugging &
|
|||
|
||||
### 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.
|
||||
- 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)
|
||||
- Singleplayer mode with simple world generations!
|
||||
- Works offline
|
||||
|
|
|
|||
|
|
@ -155,6 +155,7 @@
|
|||
"https-browserify": "^1.0.0",
|
||||
"mc-assets": "^0.2.62",
|
||||
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
|
||||
"minecraft-lighting": "^0.0.10",
|
||||
"mineflayer": "github:zardoy/mineflayer#gen-the-master",
|
||||
"mineflayer-mouse": "^0.1.11",
|
||||
"mineflayer-pathfinder": "^2.4.4",
|
||||
|
|
|
|||
11
pnpm-lock.yaml
generated
11
pnpm-lock.yaml
generated
|
|
@ -339,6 +339,9 @@ importers:
|
|||
minecraft-inventory-gui:
|
||||
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)
|
||||
minecraft-lighting:
|
||||
specifier: ^0.0.10
|
||||
version: 0.0.10
|
||||
mineflayer:
|
||||
specifier: github:zardoy/mineflayer#gen-the-master
|
||||
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/3daf1f4bdc6afad0dedd87b879875f3dbb7b0980(encoding@0.1.13)
|
||||
|
|
@ -6651,6 +6654,10 @@ packages:
|
|||
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/89c33d396f3fde4804c71f4be3c203ade1833b41}
|
||||
version: 1.0.1
|
||||
|
||||
minecraft-lighting@0.0.10:
|
||||
resolution: {integrity: sha512-m3RNe5opaibquxyO0ly1FpKdehapvp9hRRY37RccKY4bio2LGnN3nCZ3PrOXy0C596YpxBsG1OCYg0dqtPzehg==}
|
||||
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
|
||||
|
|
@ -17279,6 +17286,10 @@ snapshots:
|
|||
- '@types/react'
|
||||
- react
|
||||
|
||||
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:
|
||||
'@types/node-rsa': 1.1.4
|
||||
|
|
|
|||
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
|
||||
}
|
||||
|
||||
let sideControl = false
|
||||
const handleMessage = data => {
|
||||
if (sideControl) return
|
||||
|
||||
const globalVar: any = globalThis
|
||||
|
||||
if (data.type === 'mcData') {
|
||||
|
|
@ -94,6 +97,13 @@ const handleMessage = data => {
|
|||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'sideControl': {
|
||||
if (data.value === 'lightEngine') {
|
||||
sideControl = true
|
||||
import('minecraft-lighting/dist/prismarineWorker.worker.js')
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'mesherData': {
|
||||
setMesherData(data.blockstatesModels, data.blocksAtlas, data.config.outputFormat === 'webgpu')
|
||||
allDataReady = true
|
||||
|
|
@ -109,6 +119,9 @@ const handleMessage = data => {
|
|||
}
|
||||
case 'chunk': {
|
||||
world.addColumn(data.x, data.z, data.chunk)
|
||||
if (data.lightData) {
|
||||
world.lightHolder.loadChunk(data.lightData)
|
||||
}
|
||||
if (data.customBlockModels) {
|
||||
const chunkKey = `${data.x},${data.z}`
|
||||
world.customBlockModels.set(chunkKey, data.customBlockModels)
|
||||
|
|
|
|||
|
|
@ -520,6 +520,7 @@ const isBlockWaterlogged = (block: Block) => {
|
|||
|
||||
let unknownBlockModel: BlockModelPartsResolved
|
||||
export function getSectionGeometry (sx: number, sy: number, sz: number, world: World) {
|
||||
world.hadSkyLight = false
|
||||
let delayedRender = [] as Array<() => void>
|
||||
|
||||
const attr: MesherGeometryOutput = {
|
||||
|
|
@ -716,6 +717,8 @@ export function getSectionGeometry (sx: number, sy: number, sz: number, world: W
|
|||
delete attr.uvs
|
||||
}
|
||||
|
||||
attr.hasSkylight = world.hadSkyLight
|
||||
|
||||
return attr
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ export const defaultMesherConfig = {
|
|||
enableLighting: true,
|
||||
skyLight: 15,
|
||||
smoothLighting: true,
|
||||
usingCustomLightHolder: false,
|
||||
flyingSquidWorkarounds: false,
|
||||
|
||||
outputFormat: 'threeJs' as 'threeJs' | 'webgpu',
|
||||
// textureSize: 1024, // for testing
|
||||
debugModelVariant: undefined as undefined | number[],
|
||||
|
|
@ -45,6 +48,7 @@ export type MesherGeometryOutput = {
|
|||
hadErrors: boolean
|
||||
blocksCount: number
|
||||
customBlockModels?: CustomBlockModels
|
||||
hasSkylight?: boolean
|
||||
}
|
||||
|
||||
export interface MesherMainEvents {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { WorldLightHolder } from 'minecraft-lighting/dist/worldLightHolder'
|
||||
import Chunks from 'prismarine-chunk'
|
||||
import mcData from 'minecraft-data'
|
||||
import { Block } from 'prismarine-block'
|
||||
|
|
@ -32,6 +33,8 @@ export type WorldBlock = Omit<Block, 'position'> & {
|
|||
}
|
||||
|
||||
export class World {
|
||||
hadSkyLight = false
|
||||
lightHolder = new WorldLightHolder(0, 0)
|
||||
config = defaultMesherConfig
|
||||
Chunk: typeof 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 = '') {
|
||||
// for easier testing
|
||||
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
|
||||
// const key = `${pos.x},${pos.y},${pos.z}`
|
||||
// if (lightsCache.has(key)) return lightsCache.get(key)
|
||||
const column = this.getColumnByPos(pos)
|
||||
if (!column || !hasChunkSection(column, pos)) return 15
|
||||
let result = Math.min(
|
||||
15,
|
||||
Math.max(
|
||||
column.getBlockLight(posInChunk(pos)),
|
||||
Math.min(skyLight, column.getSkyLight(posInChunk(pos)))
|
||||
) + 2
|
||||
if (!column) return 15
|
||||
if (!usingCustomLightHolder && !hasChunkSection(column, pos)) return 2
|
||||
let result = Math.max(
|
||||
2,
|
||||
Math.min(
|
||||
15,
|
||||
Math.max(
|
||||
this.getBlockLight(pos),
|
||||
Math.min(skyLight, this.getSkyLight(pos))
|
||||
)
|
||||
)
|
||||
)
|
||||
// lightsCache.set(key, result)
|
||||
if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
|
||||
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, 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)
|
||||
].filter(x => x !== 2)
|
||||
if (lights.length) {
|
||||
const min = Math.min(...lights)
|
||||
result = min
|
||||
if (result === 2 && IS_USING_LOCAL_SERVER_LIGHTING) {
|
||||
if ([this.getBlock(pos)?.name ?? '', curBlockName].some(x => /_stairs|slab|glass_pane/.exec(x)) && !skipMoreChecks) { // todo this is obviously wrong
|
||||
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, 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)
|
||||
].filter(x => x !== 2)
|
||||
if (lights.length) {
|
||||
const min = Math.min(...lights)
|
||||
result = min
|
||||
}
|
||||
}
|
||||
if (isNeighbor) result = 15 // TODO
|
||||
}
|
||||
if (isNeighbor && result === 2) result = 15 // TODO
|
||||
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) {
|
||||
const chunk = this.Chunk.fromJson(json)
|
||||
this.columns[columnKey(x, z)] = chunk as any
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { proxy } from 'valtio'
|
|||
import TypedEmitter from 'typed-emitter'
|
||||
import { delayedIterator } from '../../playground/shared'
|
||||
import { chunkPos } from './simpleUtils'
|
||||
import { createLightEngineIfNeededNew, destroyLightEngine, lightRemoveColumn, processLightChunk, updateBlockLight } from './lightEngine'
|
||||
import { WorldRendererConfig } from './worldrendererCommon'
|
||||
|
||||
export type ChunkPosKey = string // like '16,16'
|
||||
type ChunkPos = { x: number, z: number } // like { x: 16, z: 16 }
|
||||
|
|
@ -32,9 +34,19 @@ export type WorldDataEmitterEvents = {
|
|||
|
||||
export class WorldDataEmitterWorker extends (EventEmitter as new () => TypedEmitter<WorldDataEmitterEvents>) {
|
||||
static readonly restorerName = 'WorldDataEmitterWorker'
|
||||
|
||||
destroy () {
|
||||
this.removeAllListeners()
|
||||
}
|
||||
}
|
||||
|
||||
export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<WorldDataEmitterEvents>) {
|
||||
minY = -64
|
||||
worldHeight = 384
|
||||
dimensionName = ''
|
||||
version = ''
|
||||
|
||||
worldRendererConfig: WorldRendererConfig
|
||||
loadedChunks: Record<ChunkPosKey, boolean>
|
||||
readonly lastPos: Vec3
|
||||
private eventListeners: Record<string, any> = {}
|
||||
|
|
@ -64,18 +76,22 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
this.emitter = this
|
||||
}
|
||||
|
||||
setBlockStateId (position: Vec3, stateId: number) {
|
||||
const val = this.world.setBlockStateId(position, stateId) as Promise<void> | void
|
||||
if (val) throw new Error('setBlockStateId returned promise (not supported)')
|
||||
// const chunkX = Math.floor(position.x / 16)
|
||||
// const chunkZ = Math.floor(position.z / 16)
|
||||
// if (!this.loadedChunks[`${chunkX},${chunkZ}`] && !this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) {
|
||||
// void this.loadChunk({ x: chunkX, z: chunkZ })
|
||||
// return
|
||||
// }
|
||||
// setBlockStateId (position: Vec3, stateId: number) {
|
||||
// const val = this.world.setBlockStateId(position, stateId) as Promise<void> | void
|
||||
// if (val) throw new Error('setBlockStateId returned promise (not supported)')
|
||||
// // const chunkX = Math.floor(position.x / 16)
|
||||
// // const chunkZ = Math.floor(position.z / 16)
|
||||
// // if (!this.loadedChunks[`${chunkX},${chunkZ}`] && !this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) {
|
||||
// // void this.loadChunk({ x: chunkX, z: chunkZ })
|
||||
// // 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) {
|
||||
this.viewDistance = viewDistance
|
||||
|
|
@ -83,6 +99,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
}
|
||||
|
||||
listenToBot (bot: typeof __type_bot) {
|
||||
this.version = bot.version
|
||||
const entitiesObjectData = new Map<string, number>()
|
||||
bot._client.prependListener('spawn_entity', (data) => {
|
||||
if (data.objectData && data.entityId !== undefined) {
|
||||
|
|
@ -143,9 +160,16 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
chunkColumnUnload: (pos: Vec3) => {
|
||||
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)
|
||||
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: () => {
|
||||
this.emitter.emit('time', bot.time.timeOfDay)
|
||||
|
|
@ -154,17 +178,22 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
this.emitter.emit('end')
|
||||
},
|
||||
// when dimension might change
|
||||
login: () => {
|
||||
void this.updatePosition(bot.entity.position, true)
|
||||
this.emitter.emit('playerEntity', bot.entity)
|
||||
login () {
|
||||
possiblyDimensionChange()
|
||||
},
|
||||
respawn: () => {
|
||||
void this.updatePosition(bot.entity.position, true)
|
||||
this.emitter.emit('playerEntity', bot.entity)
|
||||
possiblyDimensionChange()
|
||||
this.emitter.emit('onWorldSwitch')
|
||||
},
|
||||
} 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 }) => {
|
||||
const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16)
|
||||
|
|
@ -204,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) {
|
||||
this.updateViewDistance(this.viewDistance)
|
||||
this.emitter.emit('chunkPosUpdate', { pos })
|
||||
|
|
@ -268,14 +305,33 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
// lastTime = 0
|
||||
|
||||
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 dz = Math.abs(botZ - Math.floor(pos.z / 16))
|
||||
const [botX, botZ] = chunkPos(this.lastPos)
|
||||
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) {
|
||||
// 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))
|
||||
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)
|
||||
// this.debugGotChunkLatency.push(latency)
|
||||
// this.lastTime = performance.now()
|
||||
|
|
@ -317,6 +373,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
|
|||
this.emitter.emit('unloadChunk', { x: pos.x, z: pos.z })
|
||||
delete this.loadedChunks[`${pos.x},${pos.z}`]
|
||||
delete this.debugChunksInfo[`${pos.x},${pos.z}`]
|
||||
lightRemoveColumn(pos.x, pos.z)
|
||||
}
|
||||
|
||||
async updatePosition (pos: Vec3, force = false) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { buildCleanupDecorator } from './cleanupDecorator'
|
|||
import { HighestBlockInfo, CustomBlockModels, BlockStateModelInfo, getBlockAssetsCacheKey, MesherConfig, MesherMainEvent } from './mesher/shared'
|
||||
import { chunkPos } from './simpleUtils'
|
||||
import { addNewStat, removeAllStats, updatePanesVisibility, updateStatText } from './ui/newStats'
|
||||
import { dumpLightData } from './lightEngine'
|
||||
import { WorldDataEmitterWorker } from './worldDataEmitter'
|
||||
import { getPlayerStateUtils, PlayerStateReactive, PlayerStateRenderer, PlayerStateUtils } from './basePlayerState'
|
||||
import { MesherLogReader } from './mesherlogReader'
|
||||
|
|
@ -44,6 +45,9 @@ export const defaultWorldRendererConfig = {
|
|||
clipWorldBelowY: undefined as number | undefined,
|
||||
smoothLighting: true,
|
||||
enableLighting: true,
|
||||
legacyLighting: false,
|
||||
clientSideLighting: 'full' as 'full' | 'partial' | 'none',
|
||||
flyingSquidWorkarounds: false,
|
||||
starfield: true,
|
||||
addChunksBatchWaitTime: 200,
|
||||
vrSupport: true,
|
||||
|
|
@ -62,6 +66,7 @@ export const defaultWorldRendererConfig = {
|
|||
export type WorldRendererConfig = typeof defaultWorldRendererConfig
|
||||
|
||||
export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any> {
|
||||
skyLight = 15
|
||||
worldReadyResolvers = Promise.withResolvers<void>()
|
||||
worldReadyPromise = this.worldReadyResolvers.promise
|
||||
timeOfTheDay = 0
|
||||
|
|
@ -496,6 +501,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
|
||||
timeUpdated? (newTime: number): void
|
||||
|
||||
skylightUpdated? (): void
|
||||
|
||||
updateViewerPosition (pos: Vec3) {
|
||||
this.viewerChunkPosition = pos
|
||||
for (const [key, value] of Object.entries(this.loadedChunks)) {
|
||||
|
|
@ -543,7 +550,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.sendMesherMcData()
|
||||
}
|
||||
|
||||
getMesherConfig (): MesherConfig {
|
||||
changeSkyLight () {
|
||||
let skyLight = 15
|
||||
const timeOfDay = this.timeOfTheDay
|
||||
if (timeOfDay < 0 || timeOfDay > 24_000) {
|
||||
|
|
@ -556,34 +563,35 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
skyLight = ((timeOfDay - 12_000) / 6000) * 15
|
||||
}
|
||||
|
||||
skyLight = Math.floor(skyLight)
|
||||
this.skyLight = Math.floor(skyLight)
|
||||
}
|
||||
|
||||
getMesherConfig (): MesherConfig {
|
||||
return {
|
||||
version: this.version,
|
||||
enableLighting: this.worldRendererConfig.enableLighting,
|
||||
skyLight,
|
||||
enableLighting: this.worldRendererConfig.enableLighting && !this.playerStateReactive.lightingDisabled,
|
||||
skyLight: this.skyLight,
|
||||
smoothLighting: this.worldRendererConfig.smoothLighting,
|
||||
outputFormat: this.outputFormat,
|
||||
// textureSize: this.resourcesManager.currentResources!.blocksAtlasParser.atlas.latest.width,
|
||||
debugModelVariant: undefined,
|
||||
clipWorldBelowY: this.worldRendererConfig.clipWorldBelowY,
|
||||
disableSignsMapsSupport: !this.worldRendererConfig.extraBlockRenderers,
|
||||
usingCustomLightHolder: false,
|
||||
flyingSquidWorkarounds: this.worldRendererConfig.flyingSquidWorkarounds,
|
||||
worldMinY: this.worldMinYRender,
|
||||
worldMaxY: this.worldMinYRender + this.worldSizeParams.worldHeight,
|
||||
}
|
||||
}
|
||||
|
||||
sendMesherMcData () {
|
||||
const allMcData = mcDataRaw.pc[this.version] ?? mcDataRaw.pc[toMajorVersion(this.version)]
|
||||
const mcData = {
|
||||
version: JSON.parse(JSON.stringify(allMcData.version))
|
||||
}
|
||||
for (const key of dynamicMcDataFiles) {
|
||||
mcData[key] = allMcData[key]
|
||||
}
|
||||
|
||||
for (const worker of this.workers) {
|
||||
worker.postMessage({ type: 'mcData', mcData, config: this.getMesherConfig() })
|
||||
}
|
||||
meshersSendMcData(
|
||||
this.workers,
|
||||
this.version,
|
||||
{
|
||||
config: this.getMesherConfig()
|
||||
}
|
||||
)
|
||||
this.logWorkerWork('# mcData sent')
|
||||
}
|
||||
|
||||
|
|
@ -641,7 +649,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
x,
|
||||
z,
|
||||
chunk,
|
||||
customBlockModels: customBlockModels || undefined
|
||||
customBlockModels: customBlockModels || undefined,
|
||||
lightData: dumpLightData(x, z)
|
||||
})
|
||||
}
|
||||
this.workers[0].postMessage({
|
||||
|
|
@ -817,19 +826,17 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
})
|
||||
|
||||
worldEmitter.on('time', (timeOfDay) => {
|
||||
this.timeUpdated?.(timeOfDay)
|
||||
|
||||
if (timeOfDay < 0 || timeOfDay > 24_000) {
|
||||
throw new Error('Invalid time of day. It should be between 0 and 24000.')
|
||||
}
|
||||
|
||||
const oldSkyLight = this.skyLight
|
||||
this.timeOfTheDay = timeOfDay
|
||||
|
||||
// if (this.worldRendererConfig.skyLight === skyLight) return
|
||||
// this.worldRendererConfig.skyLight = skyLight
|
||||
// if (this instanceof WorldRendererThree) {
|
||||
// (this).rerenderAllChunks?.()
|
||||
// }
|
||||
this.changeSkyLight()
|
||||
if (oldSkyLight !== this.skyLight) {
|
||||
this.skylightUpdated?.()
|
||||
}
|
||||
this.timeUpdated?.(timeOfDay)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -922,7 +929,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.reactiveState.world.mesherWork = true
|
||||
const distance = this.getDistance(pos)
|
||||
// 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}`
|
||||
// if (this.sectionsOutstanding.has(key)) return
|
||||
this.renderUpdateEmitter.emit('dirty', pos, value)
|
||||
|
|
@ -1026,12 +1033,16 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
this.renderUpdateEmitter.removeAllListeners()
|
||||
this.abortController.abort()
|
||||
removeAllStats()
|
||||
|
||||
this.displayOptions.worldView.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
export const initMesherWorker = (onGotMessage: (data: any) => void) => {
|
||||
// Node environment needs an absolute path, but browser needs the url of the file
|
||||
const workerName = 'mesher.js'
|
||||
// eslint-disable-next-line node/no-path-concat
|
||||
const src = typeof window === 'undefined' ? `${__dirname}/${workerName}` : workerName
|
||||
|
||||
let worker: any
|
||||
if (process.env.SINGLE_FILE_BUILD) {
|
||||
|
|
@ -1039,7 +1050,7 @@ export const initMesherWorker = (onGotMessage: (data: any) => void) => {
|
|||
const blob = new Blob([workerCode], { type: 'text/javascript' })
|
||||
worker = new Worker(window.URL.createObjectURL(blob))
|
||||
} else {
|
||||
worker = new Worker(workerName)
|
||||
worker = new Worker(src)
|
||||
}
|
||||
|
||||
worker.onmessage = ({ data }) => {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ type SectionKey = string
|
|||
|
||||
export class WorldRendererThree extends WorldRendererCommon {
|
||||
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 }>()
|
||||
signsCache = new Map<string, any>()
|
||||
starField: StarField
|
||||
|
|
@ -165,11 +165,19 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
})
|
||||
this.onReactivePlayerStateUpdated('ambientLight', (value) => {
|
||||
if (!value) return
|
||||
this.ambientLight.intensity = value
|
||||
if (this.worldRendererConfig.legacyLighting) {
|
||||
this.ambientLight.intensity = value
|
||||
} else {
|
||||
this.ambientLight.intensity = 1
|
||||
}
|
||||
})
|
||||
this.onReactivePlayerStateUpdated('directionalLight', (value) => {
|
||||
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.cursorBlock.setHighlightCursorBlock(value ? new Vec3(value.x, value.y, value.z) : null, value?.shapes)
|
||||
|
|
@ -254,10 +262,38 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
}
|
||||
}
|
||||
|
||||
skylightUpdated (): void {
|
||||
let updated = 0
|
||||
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++
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Skylight changed to ${this.skyLight}. Updated`, updated, 'sections')
|
||||
}
|
||||
|
||||
getItemRenderData (item: Record<string, any>, specificProps: ItemSpecificContextProperties) {
|
||||
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 () {
|
||||
//@ts-expect-error
|
||||
const pos = cursorBlockRel(0, 1, 0).position
|
||||
|
|
@ -345,7 +381,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
// debugRecomputedDeletedObjects = 0
|
||||
handleWorkerMessage (data: { geometry: MesherGeometryOutput, key, type }): void {
|
||||
if (data.type !== 'geometry') return
|
||||
let object: THREE.Object3D = this.sectionObjects[data.key]
|
||||
let object = this.sectionObjects[data.key]
|
||||
if (object) {
|
||||
this.scene.remove(object)
|
||||
disposeObject(object)
|
||||
|
|
@ -404,7 +440,10 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
object.add(head)
|
||||
}
|
||||
}
|
||||
|
||||
object.hasSkylight = data.geometry.hasSkylight
|
||||
this.sectionObjects[data.key] = object
|
||||
|
||||
if (this.displayOptions.inWorldRenderingConfig._renderByChunks) {
|
||||
object.visible = false
|
||||
const chunkKey = `${chunkCoords[0]},${chunkCoords[2]}`
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@ export class AppViewer {
|
|||
this.currentDisplay = 'world'
|
||||
const startPosition = bot.entity?.position ?? new Vec3(0, 64, 0)
|
||||
this.worldView = new WorldDataEmitter(world, renderDistance, startPosition)
|
||||
this.worldView.worldRendererConfig = this.inWorldRenderingConfig
|
||||
window.worldView = this.worldView
|
||||
watchOptionsAfterWorldViewInit(this.worldView)
|
||||
this.appConfigUdpate()
|
||||
|
|
@ -260,6 +261,7 @@ export class AppViewer {
|
|||
if (cleanState) {
|
||||
this.currentState = undefined
|
||||
this.currentDisplay = null
|
||||
this.worldView?.destroy()
|
||||
this.worldView = undefined
|
||||
}
|
||||
if (this.backend) {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default () => {
|
|||
const night = 13_500
|
||||
const morningStart = 23_000
|
||||
const morningEnd = 23_961
|
||||
const timeProgress = options.dayCycleAndLighting ? bot.time.timeOfDay : 0
|
||||
const timeProgress = options.dayCycle ? bot.time.timeOfDay : 0
|
||||
|
||||
// todo check actual colors
|
||||
const dayColorRainy = { r: 111 / 255, g: 156 / 255, b: 236 / 255 }
|
||||
|
|
@ -35,10 +35,10 @@ export default () => {
|
|||
// 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.5)
|
||||
}
|
||||
// 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)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export const defaultOptions = {
|
|||
/** @unstable */
|
||||
debugLogNotFrequentPackets: false,
|
||||
unimplementedContainers: false,
|
||||
dayCycleAndLighting: true,
|
||||
dayCycle: true,
|
||||
loadPlayerSkins: true,
|
||||
renderEars: true,
|
||||
lowMemoryMode: false,
|
||||
|
|
@ -92,8 +92,16 @@ export const defaultOptions = {
|
|||
showCursorBlockInSpectator: false,
|
||||
renderEntities: true,
|
||||
smoothLighting: true,
|
||||
newVersionsLighting: false,
|
||||
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',
|
||||
autoParkour: false,
|
||||
vrSupport: true, // doesn't directly affect the VR mode, should only disable the button which is annoying to android users
|
||||
|
|
|
|||
|
|
@ -82,19 +82,23 @@ export const guiOptionsScheme: {
|
|||
custom () {
|
||||
return <Category>Experimental</Category>
|
||||
},
|
||||
dayCycleAndLighting: {
|
||||
text: 'Day Cycle',
|
||||
experimentalLightingV1: {
|
||||
text: 'Experimental Lighting',
|
||||
tooltip: 'Once stable this setting will be removed and always enabled',
|
||||
},
|
||||
smoothLighting: {},
|
||||
newVersionsLighting: {
|
||||
text: 'Lighting in Newer Versions',
|
||||
lightingStrategy: {
|
||||
values: [
|
||||
['prefer-server', 'Prefer Server'],
|
||||
['always-client', 'Always Client'],
|
||||
['always-server', 'Always Server'],
|
||||
],
|
||||
},
|
||||
lowMemoryMode: {
|
||||
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.',
|
||||
},
|
||||
starfieldRendering: {},
|
||||
renderEntities: {},
|
||||
keepChunksDistance: {
|
||||
max: 5,
|
||||
unit: '',
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { appStorage } from './react/appStorageProvider'
|
|||
import { miscUiState } from './globalState'
|
||||
import { defaultOptions } from './defaultOptions'
|
||||
|
||||
defaultOptions.experimentalLightingV1 = location.hostname.startsWith('lighting.') // todo
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development'
|
||||
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>>) => {
|
||||
if (options.dayCycleAndLighting) {
|
||||
delete options.dayCycleAndLighting
|
||||
options.dayCycle = options.dayCycleAndLighting
|
||||
}
|
||||
|
||||
if (options.highPerformanceGpu) {
|
||||
options.gpuPreference = 'high-performance'
|
||||
delete options.highPerformanceGpu
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@ export default () => {
|
|||
const [packetsString, setPacketsString] = useState('')
|
||||
const { showDebugHud } = useSnapshot(miscUiState)
|
||||
const [pos, setPos] = useState<{ x: number, y: number, z: number }>({ x: 0, y: 0, z: 0 })
|
||||
const [skyL, setSkyL] = useState(0)
|
||||
const [blockL, setBlockL] = useState(0)
|
||||
const [lightInfo, setLightInfo] = useState<{ sky: number, block: number, info: string }>({ sky: 0, block: 0, info: '-' })
|
||||
const [biomeId, setBiomeId] = useState(0)
|
||||
const [day, setDay] = useState(0)
|
||||
const [timeOfDay, setTimeOfDay] = useState(0)
|
||||
|
|
@ -122,9 +121,28 @@ export default () => {
|
|||
})
|
||||
|
||||
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 })
|
||||
setSkyL(bot.world.getSkyLight(bot.entity.position))
|
||||
setBlockL(bot.world.getBlockLight(bot.entity.position))
|
||||
setBiomeId(bot.world.getBiome(bot.entity.position))
|
||||
setDimension(bot.game.dimension)
|
||||
setDay(bot.time.day)
|
||||
|
|
@ -182,7 +200,7 @@ export default () => {
|
|||
<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 (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>Day: {day} Time: {timeOfDay}</p>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { versionToNumber } from 'renderer/viewer/common/utils'
|
||||
import { restoreMinecraftData } from '../optimizeJson'
|
||||
// import minecraftInitialDataJson from '../../generated/minecraft-initial-data.json'
|
||||
import { toMajorVersion } from '../utils'
|
||||
import { importLargeData } from '../../generated/large-data-aliases'
|
||||
|
||||
const toMajorVersion = version => {
|
||||
const [a, b] = (String(version)).split('.')
|
||||
return `${a}.${b}`
|
||||
}
|
||||
|
||||
const customResolver = () => {
|
||||
const resolver = Promise.withResolvers()
|
||||
let resolvedData
|
||||
|
|
@ -19,6 +23,8 @@ const customResolver = () => {
|
|||
}
|
||||
}
|
||||
|
||||
//@ts-expect-error for workers using minecraft-data
|
||||
globalThis.window ??= globalThis
|
||||
let dataStatus = 'not-called'
|
||||
|
||||
const optimizedDataResolver = customResolver()
|
||||
|
|
@ -75,7 +81,7 @@ const possiblyGetFromCache = (version: string) => {
|
|||
cacheTime.set(version, Date.now())
|
||||
return data
|
||||
}
|
||||
window.allLoadedMcData = new Proxy({}, {
|
||||
window.allLoadedMcData ??= new Proxy({}, {
|
||||
get (t, version: string) {
|
||||
// special properties like $typeof
|
||||
if (version.includes('$')) return
|
||||
|
|
|
|||
|
|
@ -100,12 +100,40 @@ export const watchOptionsAfterViewerInit = () => {
|
|||
appViewer.inWorldRenderingConfig.smoothLighting = options.smoothLighting
|
||||
})
|
||||
|
||||
subscribeKey(options, 'newVersionsLighting', () => {
|
||||
appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting
|
||||
})
|
||||
const updateLightingStrategy = () => {
|
||||
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', () => {
|
||||
appViewer.inWorldRenderingConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting
|
||||
updateLightingStrategy()
|
||||
})
|
||||
|
||||
watchValue(options, o => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue