Files
wireless-docs/blockstate.js
2026-07-05 10:14:01 -04:00

146 lines
4.8 KiB
JavaScript

export class BlockstateHandler {
async process(cache, id, url) {
if (id === ':missing') {
return new Blockstate({variants: {"": {model: ":missing"}}});
}
try {
const res = await fetch(url + '.json');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
return new Blockstate(json);
} catch (e) {
console.warn(`Blockstate missing/broken (${id}), falling back to :missing`);
return new Blockstate({variants: {"": {model: ":missing"}}});
}
}
}
function evaluateWhen(properties, condition) {
if (!condition) return true;
if (condition.OR) return condition.OR.some(sub => evaluateWhen(properties, sub));
if (condition.AND) return condition.AND.every(sub => evaluateWhen(properties, sub));
for (const [key, val] of Object.entries(condition)) {
if (key === 'OR' || key === 'AND') continue;
const prop = properties[key];
if (typeof val === 'string' && val.includes('|')) {
if (!val.split('|').includes(prop)) return false;
} else if (prop !== val) {
return false;
}
}
return true;
}
export class Blockstate {
constructor(json) {
this.variants = json.variants;
this.multipart = json.multipart;
this._resolutionCache = new Map();
}
_getVariantsSummary() {
const reqs = {};
for (const key of Object.keys(this.variants)) {
if (key === "" || key === "normal") continue;
const pairs = key.split(',');
for (const pair of pairs) {
const [prop, val] = pair.split('=');
if (prop && val) {
if (!reqs[prop]) reqs[prop] = new Set();
reqs[prop].add(val);
}
}
}
const summary = {};
for (const [k, v] of Object.entries(reqs)) {
summary[k] = Array.from(v);
}
return summary;
}
_getMultipartSummary() {
const reqs = {};
const traverse = (cond) => {
if (!cond) return;
if (cond.OR) cond.OR.forEach(traverse);
if (cond.AND) cond.AND.forEach(traverse);
for (const [key, val] of Object.entries(cond)) {
if (key === 'OR' || key === 'AND') continue;
if (!reqs[key]) reqs[key] = new Set();
reqs[key].add(val);
}
};
if (this.multipart) {
for (const part of this.multipart) {
traverse(part.when);
}
}
const summary = {};
for (const [k, v] of Object.entries(reqs)) {
summary[k] = Array.from(v);
}
return summary;
}
resolveParts(properties) {
const cacheKey = Object.keys(properties).sort().map(k => `${k}=${properties[k]}`).join(',');
if (this._resolutionCache.has(cacheKey)) {
return this._resolutionCache.get(cacheKey);
}
const parts = [];
if (this.variants) {
let variantDef = this.variants[cacheKey] || this.variants[""] || this.variants["normal"];
if (!variantDef) {
console.warn(
`Failed to resolve block variant: "${cacheKey}". Falling back to :missing.\n` +
`Properties evaluated by this blockstate:`,
this._getVariantsSummary()
);
const missingFallback = [{model: ":missing"}];
this._resolutionCache.set(cacheKey, missingFallback);
return missingFallback;
}
if (Array.isArray(variantDef)) variantDef = variantDef[0];
parts.push(variantDef);
} else if (this.multipart) {
for (const part of this.multipart) {
if (evaluateWhen(properties, part.when)) {
let applyDef = part.apply;
if (Array.isArray(applyDef)) applyDef = applyDef[0];
parts.push(applyDef);
}
}
if (parts.length === 0) {
console.warn(
`Multipart block ("${cacheKey}") resolved to no parts. Falling back to :missing.\n` +
`Properties evaluated by this blockstate:`,
this._getMultipartSummary()
);
const missingFallback = [{model: ":missing"}];
this._resolutionCache.set(cacheKey, missingFallback);
return missingFallback;
}
}
this._resolutionCache.set(cacheKey, parts);
return parts;
}
static getVariantHash(part) {
return `${part.model}#y=${part.y || 0},x=${part.x || 0},uvlock=${!!part.uvlock}`;
}
}