diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e177e399..56312001 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: java-version: 21 java-package: jre - name: Install xi - run: sudo apt-get install -y libxi-dev libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev build-essential libglu1-mesa-dev libglew-dev + run: sudo apt-get update && sudo apt-get install -y libxi-dev libcairo2-dev libjpeg-dev libpango1.0-dev libgif-dev build-essential libglu1-mesa-dev libglew-dev - run: npm install - run: npm run jestTest -- -t ${{ matrix.mcVersion }} - uses: actions/upload-artifact@v4 diff --git a/lib/index.js b/lib/index.js index baf11fe9..62493f17 100644 --- a/lib/index.js +++ b/lib/index.js @@ -54,7 +54,7 @@ socket.on('version', (version) => { viewer.setFirstPersonCamera(pos, yaw, pitch) return } - if (pos.y > 0 && firstPositionUpdate) { + if (firstPositionUpdate) { controls.target.set(pos.x, pos.y, pos.z) viewer.camera.position.set(pos.x, pos.y + 20, pos.z + 20) controls.update() diff --git a/package.json b/package.json index 6cfbc510..3c28c586 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "@tweenjs/tween.js": "^23.1.1", "compression": "^1.7.4", "express": "^4.17.1", - "minecraft-data": "^3.0.0", + "minecraft-data": "github:mneuhaus/node-minecraft-data#add-26.1.2-data-wrapper", "prismarine-block": "^1.7.3", - "prismarine-chunk": "^1.22.0", + "prismarine-chunk": "github:mneuhaus/prismarine-chunk#pc26_1_2-fluid-count", "prismarine-world": "^3.3.1", "socket.io": "^4.0.0", "socket.io-client": "^4.0.0", @@ -42,7 +42,7 @@ "fs-extra": "^11.0.0", "jest": "^29.7.0", "jest-puppeteer": "^11.0.0", - "minecraft-assets": "^1.9.0", + "minecraft-assets": "github:mneuhaus/node-minecraft-assets#add-26.1.2-assets", "minecraft-wrap": "^1.3.0", "minecrafthawkeye": "^1.2.5", "mineflayer": "^4.0.0", diff --git a/viewer/lib/modelsBuilder.js b/viewer/lib/modelsBuilder.js index a7736c6b..e35d31e4 100644 --- a/viewer/lib/modelsBuilder.js +++ b/viewer/lib/modelsBuilder.js @@ -1,8 +1,23 @@ function cleanupBlockName (name) { + name = getTextureName(name) + if (name === null) return 'missing_texture' if (name.startsWith('block') || name.startsWith('minecraft:block')) return name.split('/')[1] return name } +function getTextureName (texture) { + if (texture === null || texture === undefined) return null + if (typeof texture === 'string') return texture + if (typeof texture.sprite === 'string') return texture.sprite + if (typeof texture.texture === 'string') return texture.texture + return null +} + +function resolveTexture (texture, texturesJson) { + const name = cleanupBlockName(texture) + return JSON.parse(JSON.stringify(texturesJson[name] || texturesJson.missing_texture)) +} + function getModel (name, blocksModels) { name = cleanupBlockName(name) const data = blocksModels[name] @@ -36,32 +51,30 @@ function getModel (name, blocksModels) { function prepareModel (model, texturesJson) { // resolve texture names eg west: #all -> blocks/stone for (const tex in model.textures) { - let root = model.textures[tex] - while (root.charAt(0) === '#') { - root = model.textures[root.substr(1)] + let root = getTextureName(model.textures[tex]) + while (root && root.charAt(0) === '#') { + root = getTextureName(model.textures[root.substr(1)]) } - model.textures[tex] = root + model.textures[tex] = root || 'missing_texture' } for (const tex in model.textures) { - let name = model.textures[tex] - name = cleanupBlockName(name) - model.textures[tex] = texturesJson[name] + model.textures[tex] = resolveTexture(model.textures[tex], texturesJson) } for (const elem of model.elements) { for (const sideName of Object.keys(elem.faces)) { const face = elem.faces[sideName] + const faceTextureName = getTextureName(face.texture) - if (face.texture.charAt(0) === '#') { - face.texture = JSON.parse(JSON.stringify(model.textures[face.texture.substr(1)])) + if (faceTextureName && faceTextureName.charAt(0) === '#') { + face.texture = JSON.parse(JSON.stringify(model.textures[faceTextureName.substr(1)])) } else if ( - !(cleanupBlockName(face.texture) in texturesJson) && - face.texture in model.textures + !(cleanupBlockName(faceTextureName) in texturesJson) && + faceTextureName && + faceTextureName in model.textures ) { - face.texture = JSON.parse(JSON.stringify(model.textures[face.texture])) + face.texture = JSON.parse(JSON.stringify(model.textures[faceTextureName])) } else { - let name = face.texture - name = cleanupBlockName(name) - face.texture = JSON.parse(JSON.stringify(texturesJson[name])) + face.texture = resolveTexture(faceTextureName, texturesJson) } let uv = face.uv diff --git a/viewer/lib/version.js b/viewer/lib/version.js index 39e3ad86..a9b12c57 100644 --- a/viewer/lib/version.js +++ b/viewer/lib/version.js @@ -1,4 +1,4 @@ -const supportedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.1', '1.16.4', '1.17.1', '1.18.1', '1.19', '1.20.1', '1.21.1', '1.21.4'] +const supportedVersions = ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.1', '1.16.4', '1.17.1', '1.18.1', '1.19', '1.20.1', '1.21.1', '1.21.4', '1.21.11', '26.1.2'] const lastOfMajor = {} for (const version of supportedVersions) { diff --git a/viewer/lib/worker.js b/viewer/lib/worker.js index b7431677..d5584f08 100644 --- a/viewer/lib/worker.js +++ b/viewer/lib/worker.js @@ -32,7 +32,7 @@ function setSectionDirty (pos, value = true) { if (!value) { delete dirtySections[key] postMessage({ type: 'sectionFinished', key }) - } else if (chunk && chunk.sections[Math.floor(y / 16)]) { + } else if (hasSection(chunk, y)) { dirtySections[key] = value } else { postMessage({ type: 'sectionFinished', key }) @@ -74,7 +74,7 @@ setInterval(() => { y = parseInt(y, 10) z = parseInt(z, 10) const chunk = world.getColumn(x, z) - if (chunk && chunk.sections[Math.floor(y / 16)]) { + if (hasSection(chunk, y)) { delete dirtySections[key] const geometry = getSectionGeometry(x, y, z, world, blocksStates) const transferable = [geometry.positions.buffer, geometry.normals.buffer, geometry.colors.buffer, geometry.uvs.buffer] @@ -85,3 +85,9 @@ setInterval(() => { // const time = performance.now() - start // console.log(`Processed ${sections.length} sections in ${time} ms (${time / sections.length} ms/section)`) }, 50) + +function hasSection (chunk, y) { + if (!chunk) return false + const minY = chunk.minY ?? 0 + return Boolean(chunk.sections[(y - minY) >> 4]) +} diff --git a/viewer/lib/worldrenderer.js b/viewer/lib/worldrenderer.js index 744224a7..6830cb55 100644 --- a/viewer/lib/worldrenderer.js +++ b/viewer/lib/worldrenderer.js @@ -16,6 +16,7 @@ class WorldRenderer { this.version = undefined this.scene = scene this.loadedChunks = {} + this.loadedChunkRanges = {} this.sectionsOutstanding = new Set() this.renderUpdateEmitter = new EventEmitter() this.blockStatesData = undefined @@ -70,6 +71,8 @@ class WorldRenderer { this.scene.remove(mesh) } this.sectionMeshs = {} + this.loadedChunks = {} + this.loadedChunkRanges = {} for (const worker of this.workers) { worker.postMessage({ type: 'reset' }) } @@ -109,10 +112,12 @@ class WorldRenderer { addColumn (x, z, chunk) { this.loadedChunks[`${x},${z}`] = true + const range = getChunkVerticalRange(chunk) + this.loadedChunkRanges[`${x},${z}`] = range for (const worker of this.workers) { worker.postMessage({ type: 'chunk', x, z, chunk }) } - for (let y = 0; y < 256; y += 16) { + for (let y = range.minY; y < range.maxY; y += 16) { const loc = new Vec3(x, y, z) this.setSectionDirty(loc) this.setSectionDirty(loc.offset(-16, 0, 0)) @@ -124,10 +129,12 @@ class WorldRenderer { removeColumn (x, z) { delete this.loadedChunks[`${x},${z}`] + const range = this.loadedChunkRanges[`${x},${z}`] || { minY: 0, maxY: 256 } + delete this.loadedChunkRanges[`${x},${z}`] for (const worker of this.workers) { worker.postMessage({ type: 'unloadChunk', x, z }) } - for (let y = 0; y < 256; y += 16) { + for (let y = range.minY; y < range.maxY; y += 16) { this.setSectionDirty(new Vec3(x, y, z), false) const key = `${x},${y},${z}` const mesh = this.sectionMeshs[key] @@ -181,4 +188,12 @@ class WorldRenderer { } } +function getChunkVerticalRange (chunk) { + if (!chunk) return { minY: 0, maxY: 256 } + const data = typeof chunk === 'string' ? JSON.parse(chunk) : chunk + const minY = data.minY ?? 0 + const worldHeight = data.worldHeight ?? 256 + return { minY, maxY: minY + worldHeight } +} + module.exports = { WorldRenderer } diff --git a/webpack.config.js b/webpack.config.js index d1577c9d..13b2c0bf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,6 +8,7 @@ const path = require('path') const blockedIndexFiles = ['blocksB2J', 'blocksJ2B', 'blockMappings', 'steve', 'recipes'] const allowedWorkerFiles = ['blocks', 'blockCollisionShapes', 'tints', 'blockStates', 'biomes', 'features', 'version', 'legacy', 'versions', 'version', 'protocolVersions'] +const allowedWorkerBedrockFiles = ['legacy', 'versions', 'protocolVersions'] const indexConfig = { entry: './lib/index.js', @@ -18,6 +19,7 @@ const indexConfig = { }, resolve: { fallback: { + assert: require.resolve('assert/'), zlib: false } }, @@ -59,6 +61,7 @@ const workerConfig = { }, resolve: { fallback: { + assert: require.resolve('assert/'), zlib: false } }, @@ -74,7 +77,12 @@ const workerConfig = { externals: [ function (req, cb) { if (req.context.includes('minecraft-data') && req.request.endsWith('.json')) { + const requestPath = `${req.context}/${req.request}` const fileName = req.request.split('/').pop().replace('.json', '') + if (requestPath.match(/[\\/]data[\\/]bedrock[\\/]/) && !allowedWorkerBedrockFiles.includes(fileName)) { + cb(null, []) + return + } if (!allowedWorkerFiles.includes(fileName)) { cb(null, []) return