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}`; } }