kings tree implementation

tons of 1.20 files
This commit is contained in:
craisinlord committed 2026-09-10 09:51:32 -05:00
1 parent f4f4e0c717
commit 326c503ddb
3734 files changed
+121959 -8

No files matched your search

+330
View File
@@ -0,0 +1,330 @@
import fs from "node:fs";
import path from "node:path";
import zlib from "node:zlib";
const TAG = {
END: 0,
BYTE: 1,
SHORT: 2,
INT: 3,
LONG: 4,
FLOAT: 5,
DOUBLE: 6,
BYTE_ARRAY: 7,
STRING: 8,
LIST: 9,
COMPOUND: 10,
INT_ARRAY: 11,
LONG_ARRAY: 12
};
class Reader {
constructor(buffer) {
this.buffer = buffer;
this.offset = 0;
}
byte() { return this.buffer.readInt8(this.offset++); }
ushort() { const value = this.buffer.readUInt16BE(this.offset); this.offset += 2; return value; }
short() { const value = this.buffer.readInt16BE(this.offset); this.offset += 2; return value; }
int() { const value = this.buffer.readInt32BE(this.offset); this.offset += 4; return value; }
long() { const value = this.buffer.readBigInt64BE(this.offset); this.offset += 8; return value; }
float() { const value = this.buffer.readFloatBE(this.offset); this.offset += 4; return value; }
double() { const value = this.buffer.readDoubleBE(this.offset); this.offset += 8; return value; }
string() { const length = this.ushort(); const value = this.buffer.toString("utf8", this.offset, this.offset + length); this.offset += length; return value; }
payload(type) {
switch (type) {
case TAG.BYTE: return this.byte();
case TAG.SHORT: return this.short();
case TAG.INT: return this.int();
case TAG.LONG: return this.long();
case TAG.FLOAT: return this.float();
case TAG.DOUBLE: return this.double();
case TAG.BYTE_ARRAY: {
const length = this.int();
const value = Buffer.from(this.buffer.subarray(this.offset, this.offset + length));
this.offset += length;
return value;
}
case TAG.STRING: return this.string();
case TAG.LIST: {
const elementType = this.byte();
const length = this.int();
const items = new Array(length);
for (let i = 0; i < length; i++) items[i] = this.payload(elementType);
return { elementType, items };
}
case TAG.COMPOUND: {
const value = {};
while (true) {
const childType = this.byte();
if (childType === TAG.END) break;
value[this.string()] = { type: childType, value: this.payload(childType) };
}
return value;
}
case TAG.INT_ARRAY: {
const length = this.int();
const value = new Int32Array(length);
for (let i = 0; i < length; i++) value[i] = this.int();
return value;
}
case TAG.LONG_ARRAY: {
const length = this.int();
const value = new Array(length);
for (let i = 0; i < length; i++) value[i] = this.long();
return value;
}
default: throw new Error(`Unsupported NBT tag type ${type}`);
}
}
root() {
const type = this.byte();
if (type !== TAG.COMPOUND) throw new Error(`Expected compound root, got ${type}`);
this.string();
return { type, value: this.payload(type) };
}
}
class Writer {
constructor() { this.parts = []; }
push(buffer) { this.parts.push(buffer); }
byte(value) { const b = Buffer.allocUnsafe(1); b.writeInt8(value); this.push(b); }
short(value) { const b = Buffer.allocUnsafe(2); b.writeInt16BE(value); this.push(b); }
ushort(value) { const b = Buffer.allocUnsafe(2); b.writeUInt16BE(value); this.push(b); }
int(value) { const b = Buffer.allocUnsafe(4); b.writeInt32BE(value); this.push(b); }
long(value) { const b = Buffer.allocUnsafe(8); b.writeBigInt64BE(BigInt(value)); this.push(b); }
float(value) { const b = Buffer.allocUnsafe(4); b.writeFloatBE(value); this.push(b); }
double(value) { const b = Buffer.allocUnsafe(8); b.writeDoubleBE(value); this.push(b); }
string(value) { const b = Buffer.from(value, "utf8"); this.ushort(b.length); this.push(b); }
payload(type, value) {
switch (type) {
case TAG.BYTE: this.byte(value); break;
case TAG.SHORT: this.short(value); break;
case TAG.INT: this.int(value); break;
case TAG.LONG: this.long(value); break;
case TAG.FLOAT: this.float(value); break;
case TAG.DOUBLE: this.double(value); break;
case TAG.BYTE_ARRAY: this.int(value.length); this.push(value); break;
case TAG.STRING: this.string(value); break;
case TAG.LIST:
this.byte(value.elementType);
this.int(value.items.length);
for (const item of value.items) this.payload(value.elementType, item);
break;
case TAG.COMPOUND:
for (const [name, child] of Object.entries(value)) {
this.byte(child.type);
this.string(name);
this.payload(child.type, child.value);
}
this.byte(TAG.END);
break;
case TAG.INT_ARRAY:
this.int(value.length);
for (const item of value) this.int(item);
break;
case TAG.LONG_ARRAY:
this.int(value.length);
for (const item of value) this.long(item);
break;
default: throw new Error(`Unsupported NBT tag type ${type}`);
}
}
root(root) {
this.byte(TAG.COMPOUND);
this.string("");
this.payload(TAG.COMPOUND, root.value);
return Buffer.concat(this.parts);
}
}
const inputDir = path.resolve("common-1.21.1/src/main/resources/data/antarchy/structure/kings_tree");
const outputDir = path.resolve("common-1.21.1/src/main/resources/data/antarchy/structure/kings_tree_tiles");
const Y_SHIFT = 22;
const TREE_HEIGHT = 358;
const CENTER_X = 137;
const CENTER_Z = 163;
const sources = [
{ file: "kings_tree_bottom.nbt", offset: [0, -22, 0] },
{ file: "kings_tree1.nbt", offset: [0, 0, 0] },
{ file: "kings_tree2.nbt", offset: [-40, 12, -52] },
{ file: "kings_tree3.nbt", offset: [-40, 149, -52] },
{ file: "kings_tree4.nbt", offset: [-54, 233, -64] }
];
const expectedConnections = [
["kings_tree1.nbt", [287, 0, 363], "kings_tree_bottom.nbt", [287, 21, 363]],
["kings_tree1.nbt", [274, 11, 363], "kings_tree2.nbt", [314, 0, 415]],
["kings_tree2.nbt", [314, 136, 442], "kings_tree3.nbt", [314, 0, 442]],
["kings_tree3.nbt", [314, 83, 420], "kings_tree4.nbt", [328, 0, 432]]
];
const intList = (...values) => ({ type: TAG.LIST, value: { elementType: TAG.INT, items: values } });
const doubleList = (...values) => ({ type: TAG.LIST, value: { elementType: TAG.DOUBLE, items: values } });
const floorDiv = (value, divisor) => Math.floor(value / divisor);
const floorMod = (value, divisor) => ((value % divisor) + divisor) % divisor;
const paletteKey = value => JSON.stringify(value, (_, item) => typeof item === "bigint" ? `${item}n` : item);
const tileNamePart = value => value < 0 ? `m${-value}` : `p${value}`;
const tiles = new Map();
const globalPalette = [];
const globalPaletteIds = new Map();
let dataVersion;
let sourceBlockCount = 0;
let skippedMarkerCount = 0;
const jigsaws = new Map();
function getTile(tileX, tileZ) {
const key = `${tileX},${tileZ}`;
let tile = tiles.get(key);
if (!tile) {
tile = { tileX, tileZ, blocks: new Map(), entities: [] };
tiles.set(key, tile);
}
return tile;
}
function paletteId(stateTag) {
const key = paletteKey(stateTag);
let id = globalPaletteIds.get(key);
if (id === undefined) {
id = globalPalette.length;
globalPaletteIds.set(key, id);
globalPalette.push(stateTag);
}
return id;
}
for (const source of sources) {
const compressed = fs.readFileSync(path.join(inputDir, source.file));
const root = new Reader(zlib.gunzipSync(compressed)).root();
dataVersion ??= root.value.DataVersion;
const paletteTag = root.value.palette;
if (!paletteTag || paletteTag.type !== TAG.LIST) throw new Error(`${source.file} has no primary palette`);
const palette = paletteTag.value.items;
const remap = palette.map(paletteId);
const blocks = root.value.blocks?.value.items ?? [];
sourceBlockCount += blocks.length;
for (const block of blocks) {
const stateIndex = block.state.value;
const blockName = palette[stateIndex].Name.value;
if (blockName === "minecraft:jigsaw") {
jigsaws.set(`${source.file}:${block.pos.value.items.join(",")}`, {
state: palette[stateIndex],
nbt: block.nbt?.value ?? {}
});
}
if (blockName === "minecraft:jigsaw" || blockName === "minecraft:structure_void" || blockName === "minecraft:structure_block") {
skippedMarkerCount++;
continue;
}
const [x, y, z] = block.pos.value.items;
const relativeX = x + source.offset[0] - CENTER_X;
const relativeY = y + source.offset[1] + Y_SHIFT;
const relativeZ = z + source.offset[2] - CENTER_Z;
if (relativeY < 0 || relativeY >= TREE_HEIGHT) throw new Error(`${source.file} produced out-of-range Y ${relativeY}`);
const tileX = floorDiv(relativeX, 16);
const tileZ = floorDiv(relativeZ, 16);
const localX = floorMod(relativeX, 16);
const localZ = floorMod(relativeZ, 16);
const translated = {
...block,
pos: intList(localX, relativeY, localZ),
state: { type: TAG.INT, value: remap[stateIndex] }
};
getTile(tileX, tileZ).blocks.set(`${localX},${relativeY},${localZ}`, translated);
}
const entities = root.value.entities?.value.items ?? [];
for (const entity of entities) {
const [blockX, blockY, blockZ] = entity.blockPos.value.items;
const [x, y, z] = entity.pos.value.items;
const relativeBlockX = blockX + source.offset[0] - CENTER_X;
const relativeBlockY = blockY + source.offset[1] + Y_SHIFT;
const relativeBlockZ = blockZ + source.offset[2] - CENTER_Z;
const tileX = floorDiv(relativeBlockX, 16);
const tileZ = floorDiv(relativeBlockZ, 16);
const tileOriginX = tileX * 16;
const tileOriginZ = tileZ * 16;
getTile(tileX, tileZ).entities.push({
...entity,
blockPos: intList(relativeBlockX - tileOriginX, relativeBlockY, relativeBlockZ - tileOriginZ),
pos: doubleList(
x + source.offset[0] - CENTER_X - tileOriginX,
y + source.offset[1] + Y_SHIFT,
z + source.offset[2] - CENTER_Z - tileOriginZ
)
});
}
}
for (const [parentFile, parentPos, childFile, childPos] of expectedConnections) {
const parent = sources.find(source => source.file === parentFile);
const child = sources.find(source => source.file === childFile);
const parentJigsaw = jigsaws.get(`${parentFile}:${parentPos.join(",")}`);
const childJigsaw = jigsaws.get(`${childFile}:${childPos.join(",")}`);
if (!parentJigsaw || !childJigsaw) {
throw new Error(`Missing expected jigsaw connection between ${parentFile} and ${childFile}`);
}
const parentTarget = parentJigsaw.nbt.target?.value;
const childName = childJigsaw.nbt.name?.value;
if (parentTarget !== childName) {
throw new Error(`Jigsaw target mismatch: ${parentFile} targets ${parentTarget}, but ${childFile} is named ${childName}`);
}
const parentOrientation = parentJigsaw.state.Properties?.value.orientation?.value;
const childOrientation = childJigsaw.state.Properties?.value.orientation?.value;
console.log(`${parentFile} ${parentOrientation} -> ${childFile} ${childOrientation}`);
const [parentFront, parentTop] = parentOrientation.split("_");
const [childFront, childTop] = childOrientation.split("_");
const opposite = { up: "down", down: "up", north: "south", south: "north", east: "west", west: "east" };
const facingStep = {
up: [0, 1, 0], down: [0, -1, 0], north: [0, 0, -1],
south: [0, 0, 1], east: [1, 0, 0], west: [-1, 0, 0]
};
const parentWorld = parentPos.map((value, axis) => value + parent.offset[axis]);
const expectedChildWorld = parentWorld.map((value, axis) => value + facingStep[parentFront][axis]);
const childWorld = childPos.map((value, axis) => value + child.offset[axis]);
if (expectedChildWorld.some((value, axis) => value !== childWorld[axis])) {
throw new Error(`Misaligned jigsaw connection: ${parentFile} expects ${expectedChildWorld}, but ${childFile} is at ${childWorld}`);
}
if (opposite[parentFront] !== childFront) {
throw new Error(`Jigsaw facing mismatch: ${parentOrientation} cannot connect to ${childOrientation}`);
}
const joint = parentJigsaw.nbt.joint?.value ?? "rollable";
if (joint === "aligned" && parentTop !== childTop) {
throw new Error(`Jigsaw ${parentFile} requires a rotated child: ${parentOrientation} vs ${childOrientation}`);
}
}
fs.rmSync(outputDir, { recursive: true, force: true });
fs.mkdirSync(outputDir, { recursive: true });
let outputBlockCount = 0;
for (const tile of tiles.values()) {
if (tile.blocks.size === 0 && tile.entities.length === 0) continue;
const root = {
type: TAG.COMPOUND,
value: {
DataVersion: dataVersion,
size: intList(16, TREE_HEIGHT, 16),
palette: { type: TAG.LIST, value: { elementType: TAG.COMPOUND, items: globalPalette } },
blocks: { type: TAG.LIST, value: { elementType: TAG.COMPOUND, items: [...tile.blocks.values()] } },
entities: { type: TAG.LIST, value: { elementType: TAG.COMPOUND, items: tile.entities } }
}
};
outputBlockCount += tile.blocks.size;
const encoded = new Writer().root(root);
const name = `${tileNamePart(tile.tileX)}_${tileNamePart(tile.tileZ)}.nbt`;
fs.writeFileSync(path.join(outputDir, name), zlib.gzipSync(encoded, { level: 9 }));
}
console.log(`Read ${sourceBlockCount.toLocaleString()} blocks from ${sources.length} templates.`);
console.log(`Skipped ${skippedMarkerCount.toLocaleString()} structure marker blocks.`);
console.log(`Wrote ${outputBlockCount.toLocaleString()} blocks in ${tiles.size} chunk-local templates.`);
console.log(`Global palette contains ${globalPalette.length} block states.`);
console.log(`Validated ${expectedConnections.length} matching jigsaw connections.`);