Skip to content
Open
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
245300f
init
zardoy Feb 18, 2025
d5c61d8
a working light
zardoy Feb 19, 2025
48ead54
should work.
zardoy Mar 12, 2025
037e297
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Mar 12, 2025
ace45a9
not crash pls
zardoy Mar 13, 2025
ec6b249
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Mar 21, 2025
9f505f8
rm workaround
zardoy Mar 21, 2025
f18b3a1
Merge branch 'next' into light-engine
zardoy Apr 7, 2025
e10f610
humble and terrible progress
zardoy Apr 10, 2025
0fa66e2
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Apr 10, 2025
b1ba2cd
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Apr 12, 2025
3cd1ac3
Merge branch 'next' into light-engine
zardoy Apr 24, 2025
1918c68
finish lighting
zardoy Apr 25, 2025
b4c72db
fix crash opt
zardoy Apr 25, 2025
1f5b682
FINISH OPTIONS, FINISH RECOMPUTE, ADD LIGHT TO WATER
zardoy Apr 28, 2025
5a57d29
Merge branch 'next' into light-engine
zardoy Apr 28, 2025
2f6191a
Merge remote-tracking branch 'origin/next' into light-engine
zardoy Apr 30, 2025
f4eab39
finish lighting
zardoy Apr 30, 2025
79f0fdd
fix lava rendering
zardoy Apr 30, 2025
27c55b1
finish!
zardoy Apr 30, 2025
f4f5edd
fix lighting disabling
zardoy Apr 30, 2025
c97c7e0
finish combined computation, finish settings and strategies
zardoy May 1, 2025
c4b9c33
Update src/optionsStorage.ts
zardoy May 1, 2025
7d224fb
Merge remote-tracking branch 'origin/next' into light-engine
zardoy May 1, 2025
d6f394f
hide cursor block in spectator
zardoy May 2, 2025
ddf0810
final step: move engine to another thread
zardoy May 4, 2025
5720cfa
up light
zardoy May 4, 2025
7dba526
Merge remote-tracking branch 'origin/next' into light-engine
zardoy May 4, 2025
e95f84e
fix lock
zardoy May 4, 2025
90de0d0
up chunk?
zardoy May 4, 2025
f185df9
fix remaining issues with worker bundle with smart approach
zardoy May 8, 2025
6be3c5c
Merge remote-tracking branch 'origin/next' into light-engine
zardoy May 8, 2025
56aee16
Merge branch 'next' into light-engine
zardoy May 19, 2025
b8c8f8a
Merge branch 'next' into light-engine
zardoy Jul 14, 2025
6a8d15b
[deploy] properly destroy world view
zardoy Jul 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,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
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
"https-browserify": "^1.0.0",
"mc-assets": "^0.2.54",
"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.10",
"mineflayer-pathfinder": "^2.4.4",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

93 changes: 93 additions & 0 deletions renderer/viewer/lib/lightEngine.ts
Original file line number Diff line number Diff line change
@@ -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) => {

Check warning on line 57 in renderer/viewer/lib/lightEngine.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Async arrow function has too many parameters (5). Maximum allowed is 4
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
Comment on lines +57 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling for light engine operations

The updateBlockLight function logs performance data but doesn't handle errors.

Add try/catch block to handle potential errors from the light engine:

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()
+  try {
    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
+  } catch (error) {
+    console.error(`[light engine] Error updating block light at (${x}, ${y}, ${z}):`, error)
+    return []
+  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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
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()
try {
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
} catch (error) {
console.error(
`[light engine] Error updating block light at (${x}, ${y}, ${z}):`,
error
)
return []
}
}


// 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
}
13 changes: 13 additions & 0 deletions renderer/viewer/lib/mesher/mesher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ const softCleanup = () => {
globalThis.world = world
}

let sideControl = false
const handleMessage = data => {
if (sideControl) return

const globalVar: any = globalThis

if (data.type === 'mcData') {
Expand All @@ -92,6 +95,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
Expand All @@ -107,6 +117,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)
Expand Down
3 changes: 3 additions & 0 deletions renderer/viewer/lib/mesher/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
return [r / 255, g / 255, b / 255]
}

function getLiquidRenderHeight (world: World, block: WorldBlock | null, type: number, pos: Vec3, isWater: boolean, isRealWater: boolean) {

Check warning on line 106 in renderer/viewer/lib/mesher/models.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function 'getLiquidRenderHeight' has too many parameters (6). Maximum allowed is 4
if ((isWater && !isRealWater) || (block && isBlockWaterlogged(block))) return 8 / 9
if (!block || block.type !== type) return 1 / 9
if (block.metadata === 0) { // source block
Expand Down Expand Up @@ -132,7 +132,7 @@
return v.plus(dir)
}

function renderLiquid (world: World, cursor: Vec3, texture: any | undefined, type: number, biome: string, water: boolean, attr: Record<string, any>, isRealWater: boolean) {

Check warning on line 135 in renderer/viewer/lib/mesher/models.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Function 'renderLiquid' has too many parameters (8). Maximum allowed is 4
const heights: number[] = []
for (let z = -1; z <= 1; z++) {
for (let x = -1; x <= 1; x++) {
Expand Down Expand Up @@ -488,6 +488,7 @@

let unknownBlockModel: BlockModelPartsResolved
export function getSectionGeometry (sx, sy, sz, world: World) {
world.hadSkyLight = false
let delayedRender = [] as Array<() => void>

const attr: MesherGeometryOutput = {
Expand Down Expand Up @@ -691,6 +692,8 @@
delete attr.uvs
}

attr.hasSkylight = world.hadSkyLight

return attr
}

Expand Down
5 changes: 5 additions & 0 deletions renderer/viewer/lib/mesher/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { BlockType } from '../../../playground/shared'
// only here for easier testing
export const defaultMesherConfig = {
version: '',

enableLighting: true,
skyLight: 15,
smoothLighting: true,
usingCustomLightHolder: false,
flyingSquidWorkarounds: false,

outputFormat: 'threeJs' as 'threeJs' | 'webgpu',
textureSize: 1024, // for testing
debugModelVariant: undefined as undefined | number[],
Expand Down Expand Up @@ -44,6 +48,7 @@ export type MesherGeometryOutput = {
hadErrors: boolean
blocksCount: number
customBlockModels?: CustomBlockModels
hasSkylight?: boolean
}

export interface MesherMainEvents {
Expand Down
84 changes: 60 additions & 24 deletions renderer/viewer/lib/mesher/world.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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 }
Expand All @@ -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
Expand Down
Loading
Loading