Skip to content

Instantly share code, notes, and snippets.

@iamarkdev
Created September 16, 2021 06:55
Show Gist options
  • Save iamarkdev/9a2c6caa25c8afaf1dee4145befa4e1b to your computer and use it in GitHub Desktop.
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.
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