137 lines
4.6 KiB
JavaScript
137 lines
4.6 KiB
JavaScript
import fs from 'node:fs/promises'
|
|
|
|
const SUMMARY_REF = 'b8170fbc07725bf4930d189ad5dc16f70e09b9cd'
|
|
const ATLAS_REF = 'a73f0316d9cea52a53381664328bda00e5fe79e4'
|
|
const EXPECTED_VERSION = '26.3-snapshot-6'
|
|
const OUTPUT_ROOT = new URL(
|
|
'../../apps/app-frontend/src/lab/schematic-preview/assets/vanilla/',
|
|
import.meta.url,
|
|
)
|
|
|
|
function summaryUrl(path) {
|
|
return `https://raw.githubusercontent.com/misode/mcmeta/${SUMMARY_REF}/${path}`
|
|
}
|
|
|
|
function atlasUrl(path) {
|
|
return `https://raw.githubusercontent.com/misode/mcmeta/${ATLAS_REF}/${path}`
|
|
}
|
|
|
|
async function download(url) {
|
|
let failure
|
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
try {
|
|
const response = await fetch(url, { signal: AbortSignal.timeout(120_000) })
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
return new Uint8Array(await response.arrayBuffer())
|
|
} catch (error) {
|
|
failure = error
|
|
if (attempt < 3) await new Promise((resolve) => setTimeout(resolve, attempt * 500))
|
|
}
|
|
}
|
|
throw new Error(`Unable to download ${url}`, { cause: failure })
|
|
}
|
|
|
|
function parseJson(bytes, source) {
|
|
try {
|
|
return JSON.parse(new TextDecoder().decode(bytes))
|
|
} catch (error) {
|
|
throw new Error(`Invalid JSON from ${source}`, { cause: error })
|
|
}
|
|
}
|
|
|
|
function requireObject(value, source) {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
throw new Error(`${source} must contain a JSON object`)
|
|
}
|
|
return value
|
|
}
|
|
|
|
function pngDimensions(bytes) {
|
|
const signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]
|
|
if (bytes.length < 24 || signature.some((value, index) => bytes[index] !== value)) {
|
|
throw new Error('The downloaded texture atlas is not a PNG image')
|
|
}
|
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
return [view.getUint32(16), view.getUint32(20)]
|
|
}
|
|
|
|
const sources = {
|
|
definitions: summaryUrl('assets/block_definition/data.min.json'),
|
|
models: summaryUrl('assets/model/data.min.json'),
|
|
blocks: summaryUrl('blocks/data.min.json'),
|
|
summaryVersion: summaryUrl('version.json'),
|
|
atlasLayout: atlasUrl('all/data.min.json'),
|
|
atlasImage: atlasUrl('all/atlas.png'),
|
|
atlasVersion: atlasUrl('version.json'),
|
|
}
|
|
|
|
const entries = await Promise.all(
|
|
Object.entries(sources).map(async ([name, url]) => [name, await download(url)]),
|
|
)
|
|
const downloaded = Object.fromEntries(entries)
|
|
const summaryVersion = requireObject(
|
|
parseJson(downloaded.summaryVersion, sources.summaryVersion),
|
|
'summary version',
|
|
)
|
|
const atlasVersion = requireObject(
|
|
parseJson(downloaded.atlasVersion, sources.atlasVersion),
|
|
'atlas version',
|
|
)
|
|
if (summaryVersion.id !== EXPECTED_VERSION || atlasVersion.id !== EXPECTED_VERSION) {
|
|
throw new Error(
|
|
`Expected ${EXPECTED_VERSION}, received summary ${summaryVersion.id} and atlas ${atlasVersion.id}`,
|
|
)
|
|
}
|
|
|
|
const definitions = requireObject(
|
|
parseJson(downloaded.definitions, sources.definitions),
|
|
'block definitions',
|
|
)
|
|
const models = requireObject(parseJson(downloaded.models, sources.models), 'block models')
|
|
const blocks = requireObject(parseJson(downloaded.blocks, sources.blocks), 'block summary')
|
|
const atlasLayout = requireObject(
|
|
parseJson(downloaded.atlasLayout, sources.atlasLayout),
|
|
'atlas layout',
|
|
)
|
|
const defaultProperties = Object.fromEntries(
|
|
Object.entries(blocks).map(([blockId, summary]) => {
|
|
if (
|
|
!Array.isArray(summary) ||
|
|
!summary[1] ||
|
|
typeof summary[1] !== 'object' ||
|
|
Array.isArray(summary[1])
|
|
) {
|
|
throw new Error(`Block summary ${blockId} has no default property object`)
|
|
}
|
|
return [blockId, summary[1]]
|
|
}),
|
|
)
|
|
|
|
const [atlasWidth, atlasHeight] = pngDimensions(downloaded.atlasImage)
|
|
for (const [textureId, region] of Object.entries(atlasLayout)) {
|
|
if (
|
|
!Array.isArray(region) ||
|
|
region.length !== 4 ||
|
|
region.some((value) => !Number.isInteger(value) || value < 0) ||
|
|
region[0] + region[2] > atlasWidth ||
|
|
region[1] + region[3] > atlasHeight
|
|
) {
|
|
throw new Error(`Atlas region ${textureId} is outside the ${atlasWidth}x${atlasHeight} image`)
|
|
}
|
|
}
|
|
|
|
await Promise.all([
|
|
fs.writeFile(new URL('block-state-index.json', OUTPUT_ROOT), JSON.stringify(definitions)),
|
|
fs.writeFile(new URL('block-model-index.json', OUTPUT_ROOT), JSON.stringify(models)),
|
|
fs.writeFile(
|
|
new URL('block-property-defaults.json', OUTPUT_ROOT),
|
|
JSON.stringify(defaultProperties),
|
|
),
|
|
fs.writeFile(new URL('texture-layout.json', OUTPUT_ROOT), JSON.stringify(atlasLayout)),
|
|
fs.writeFile(new URL('texture-atlas.png', OUTPUT_ROOT), downloaded.atlasImage),
|
|
])
|
|
|
|
console.log(
|
|
`Updated schematic resources to ${EXPECTED_VERSION}: ${Object.keys(definitions).length} block definitions, ${Object.keys(models).length} models, and ${Object.keys(atlasLayout).length} atlas regions (${atlasWidth}x${atlasHeight}).`,
|
|
)
|