Created
September 16, 2021 06:55
-
-
Save iamarkdev/9a2c6caa25c8afaf1dee4145befa4e1b to your computer and use it in GitHub Desktop.
A node.js implementation to parse minecraft .mca region files and count total blocks, structures and biomes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fs = require('fs') | |
const path = require('path'); | |
const { AnvilParser, NBTParser } = require('mc-anvil'); | |
const minecraftIds = require('./minecraft-ids.js'); | |
// iterate biomes for biomes | |
// determine mountains / caverns / ocean floor from heightmaps | |
// iterate sections for block info | |
function getWorldAttributes(worldRegionDirectoryPath) { | |
const regionFiles = fs.readdirSync(worldRegionDirectoryPath).filter(f => { | |
return f.includes('.mca'); | |
}); | |
const chunksAttributes = []; | |
for (let i = 0; i < regionFiles.length; i++) { | |
const regionFilePath = path.join(worldRegionDirectoryPath, regionFiles[i]); | |
const regionFileData = fs.readFileSync(regionFilePath); | |
const regionChunks = getPopulatedChunks(regionFileData.buffer); | |
for (let k = 0; k < regionChunks.length; k++) { | |
chunksAttributes.push(getRegionChunkAttributes(regionChunks[k])); | |
} | |
} | |
const worldAttributes = chunkAttributesToWorldAttributes(chunksAttributes); | |
console.log(worldAttributes); | |
} | |
// Helpers | |
function chunkAttributesToWorldAttributes(chunksAttributes) { | |
const attributes = { | |
blocks: {}, | |
structures: {}, | |
biomeIds: [], | |
}; | |
for (let i = 0; i < chunksAttributes.length; i++) { | |
const chunkAttributes = chunksAttributes[i]; | |
const blockNames = Object.keys(chunkAttributes.blocks); | |
const structureNames = Object.keys(chunkAttributes.structures); | |
const biomeIds = chunkAttributes.biomeIds; | |
// Flatten biome ids | |
attributes.biomeIds = [ ...new Set([ ...attributes.biomeIds, ...biomeIds ]) ]; | |
// Flatten blocks | |
for (let k = 0; k < blockNames.length; k++) { | |
const blockName = blockNames[k]; | |
attributes.blocks[blockName] = attributes.blocks[blockName] | |
? attributes.blocks[blockName] + chunkAttributes.blocks[blockName] | |
: chunkAttributes.blocks[blockName]; | |
} | |
// Flatten structures | |
for (let k = 0; k < structureNames.length; k++) { | |
const structureName = structureNames[k]; | |
attributes.structures[structureName] = attributes.structures[structureName] | |
? attributes.structures[structureName] + chunkAttributes.structures[structureName] | |
: chunkAttributes.structures[structureName]; | |
} | |
} | |
return attributes; | |
} | |
function getPopulatedChunks(mcaArrayBuffer) { | |
const anvilParser = new AnvilParser(mcaArrayBuffer); | |
const chunks = anvilParser.getLocationEntries(); | |
const populatedChunks = chunks.reduce((populatedChunks, chunk) => { | |
const chunkOffset = chunk.offset; | |
if (chunkOffset > 0) { | |
populatedChunks.push(anvilParser.getChunkData(chunkOffset)); | |
} | |
return populatedChunks; | |
}, []); | |
return populatedChunks; | |
} | |
function getRegionChunkAttributes(regionChunk) { | |
const attributes = { | |
blocks: {}, | |
structures: {}, | |
biomeIds: [], | |
} | |
const chunk = parseChunkNBTData(regionChunk); | |
const level = findChunkDataValueByName(chunk.data, 'Level'); | |
const biomes = findChunkDataValueByName(level.data, 'Biomes'); | |
const structures = findChunkDataValueByName(level.data, 'Structures'); | |
const structureStarts = findChunkDataValueByName(structures.data, 'Starts'); | |
const structureStartsData = structureStarts.data; | |
const sections = findChunkDataValueByName(level.data, 'Sections'); | |
const sectionsData = sections.data.data; | |
// Get Biome Ids for chunk | |
attributes.biomeIds = [ ...new Set(biomes.data) ]; | |
// Get Blocks for chunk | |
const blockPalette = findChunkDataValueByName(sectionsData, 'Palette'); | |
for (let i = 0; i < sectionsData.length; i++) { | |
const section = sectionsData[i]; | |
const sectionPalette = findChunkDataValueByName(section, 'Palette'); | |
if (!sectionPalette) { | |
continue; | |
} | |
const sectionPaletteData = sectionPalette.data.data; | |
for (let k = 0; k < sectionPaletteData.length; k++) { | |
const sectionBlock = sectionPaletteData[k]; | |
const blockName = findChunkDataValueByName(sectionBlock, 'Name').data; | |
attributes.blocks[blockName] = attributes.blocks[blockName] | |
? attributes.blocks[blockName] + 1 | |
: 1; | |
} | |
} | |
// Get Structures for chunk | |
for (let i = 0; i < structureStartsData.length; i++) { | |
const structure = structureStartsData[i]; | |
if (!structure.name) { | |
continue; | |
} | |
const structureId = findChunkDataValueByName(structure.data, 'id'); | |
if (structureId.data !== 'INVALID') { | |
console.log(structure); | |
attributes.structures[structure.name] = attributes.structures[structure.name] | |
? attributes.structures[structure.name] + 1 | |
: 1; | |
} | |
} | |
return attributes; | |
} | |
function parseChunkNBTData(chunk) { | |
const nbtParser = new NBTParser(chunk); | |
return nbtParser.getTag(); | |
} | |
function findChunkDataValueByName(chunkData, name) { | |
return chunkData.find(item => item.name === name); | |
} | |
module.exports = { getWorldAttributes }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment