@nodecraft/rendermite 1.1.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/assets/builtin/renderchest/models/block/banner_standing.json +225 -0
- package/assets/builtin/renderchest/models/block/banner_wall.json +155 -0
- package/assets/builtin/renderchest/models/block/bed_foot.json +44 -0
- package/assets/builtin/renderchest/models/block/bed_head.json +44 -0
- package/assets/builtin/renderchest/models/block/chest_left.json +182 -0
- package/assets/builtin/renderchest/models/block/chest_right.json +182 -0
- package/assets/builtin/renderchest/models/block/copper_golem_statue.json +709 -0
- package/assets/builtin/renderchest/models/block/fluid.json +83 -0
- package/assets/builtin/renderchest/models/block/fluid_tinted.json +89 -0
- package/assets/builtin/renderchest/models/block/hanging_sign.json +54 -0
- package/assets/builtin/renderchest/models/block/standing_sign.json +32 -0
- package/assets/builtin/renderchest/models/block/wall_sign.json +19 -0
- package/dist/atlas/registry.js +1 -1
- package/dist/atlas/sources.js +1 -1
- package/dist/atlas-pack-CoOih7By.js +130 -0
- package/dist/atlas-pack-DRTrWcSU.d.ts +41 -0
- package/dist/block-entities-BbS1kv0m.js +481 -0
- package/dist/blocks/atlas-pack.d.ts +2 -0
- package/dist/blocks/atlas-pack.js +2 -0
- package/dist/blocks/block-entities.d.ts +93 -0
- package/dist/blocks/block-entities.js +2 -0
- package/dist/blocks/blockstate-eval.d.ts +2 -0
- package/dist/blocks/blockstate-eval.js +2 -0
- package/dist/blocks/cli.d.ts +9 -0
- package/dist/blocks/cli.js +2 -0
- package/dist/blocks/export.d.ts +2 -0
- package/dist/blocks/export.js +2 -0
- package/dist/blocks/obj-mesh.d.ts +2 -0
- package/dist/blocks/obj-mesh.js +2 -0
- package/dist/blocks/tints.d.ts +2 -0
- package/dist/blocks/tints.js +2 -0
- package/dist/{blockstate-CdstnW2-.js → blockstate-DVb8Mhxc.js} +1 -1
- package/dist/blockstate-eval-Ckjlyxn4.js +231 -0
- package/dist/blockstate-eval-DDArZs1P.d.ts +107 -0
- package/dist/cli-D7_oUuPT.js +161 -0
- package/dist/cli.js +9 -7
- package/dist/{descriptor-DWebtkI1.js → descriptor-BVIoEpPO.js} +3 -3
- package/dist/{discovery-B9aqV_Rz.js → discovery-CsZu08re.js} +1 -1
- package/dist/dynamic/generators.js +1 -1
- package/dist/{entity-fallback-Cu22uEIK.js → entity-fallback-DNO0Ta9C.js} +3 -155
- package/dist/entity-textures-DFRIVdF1.js +209 -0
- package/dist/export-BIpqNU3H.d.ts +44 -0
- package/dist/export-DxREy3qA.js +701 -0
- package/dist/{face-cQ-pVHoe.d.ts → face-BKwkeTps.d.ts} +1 -1
- package/dist/{fluid-bucket-BzoLofB_.js → fluid-bucket-B-I4ZYHl.js} +1 -1
- package/dist/{generators-BlG4KmtP.js → generators-BCZVlWig.js} +1 -1
- package/dist/{geo-w9HHIrb5.js → geo-BguldlU-.js} +5 -5
- package/dist/{geometry-w0yQbzDZ.d.ts → geometry-Bkur58Vi.d.ts} +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +21 -16
- package/dist/{item-styles-upgEO1yP.js → item-styles-DjYRdyHi.js} +1 -1
- package/dist/items/blockstate.js +1 -1
- package/dist/items/discovery.js +1 -1
- package/dist/{jar-D9z-X7tA.js → jar-5aLDI0nT.js} +2 -2
- package/dist/{legacy-reshape-C05SfAPR.js → legacy-reshape-CcdstKyI.js} +2 -2
- package/dist/{legacy-tints-B09J0Rxh.js → legacy-tints-l-541wgI.js} +1 -1
- package/dist/{loader-CWsTZgrk.js → loader-BvLcevlN.js} +100 -9
- package/dist/{loader-Lb0ZeILz.js → loader-C18GpurU.js} +1 -1
- package/dist/{loader-DD83d80u.d.ts → loader-C_1_amsq.d.ts} +18 -1
- package/dist/{locator-ycZzh_6z.js → locator-COMEtEn9.js} +23 -1
- package/dist/{manager-CkletR5K.js → manager-CYbAkWuT.js} +2 -2
- package/dist/models/loader.d.ts +1 -1
- package/dist/models/loader.js +1 -1
- package/dist/models/types.d.ts +1 -1
- package/dist/{models-BKxGkPS7.js → models-CQwcTZcg.js} +1 -1
- package/dist/mods/models.js +1 -1
- package/dist/mods/prepare.js +1 -1
- package/dist/mods/synthesize.js +1 -1
- package/dist/mods/tints.js +1 -1
- package/dist/{renderer-DB1Oe7xs.js → obj-BEjq3_Ao.js} +234 -234
- package/dist/obj-mesh-C6Sp4Ysc.d.ts +41 -0
- package/dist/obj-mesh-CiHiqrlm.js +97 -0
- package/dist/output/item-styles.js +1 -1
- package/dist/{pipeline-Bp6amYz_.d.ts → pipeline-BDvXcw20.d.ts} +1 -1
- package/dist/{pipeline-CgJaVpoL.js → pipeline-DRMsBiod.js} +14 -14
- package/dist/{prepare-D56Z-9cg.js → prepare-B8oPdGYz.js} +5 -5
- package/dist/{prepare-Cc8QZ3j0.js → prepare-C95vjt_o.js} +5 -5
- package/dist/{registry-DS5PZ57l.js → registry-DlduyjtM.js} +2 -2
- package/dist/render/entity-fallback.d.ts +3 -3
- package/dist/render/entity-fallback.js +1 -1
- package/dist/render/face.d.ts +1 -1
- package/dist/render/face.js +1 -1
- package/dist/render/fluid-bucket.js +1 -1
- package/dist/render/geo.d.ts +2 -2
- package/dist/render/geo.js +1 -1
- package/dist/render/geometry.d.ts +1 -1
- package/dist/render/legacy-tints.d.ts +1 -1
- package/dist/render/legacy-tints.js +1 -1
- package/dist/render/obj.d.ts +10 -3
- package/dist/render/obj.js +2 -2
- package/dist/render/pipeline.d.ts +1 -1
- package/dist/render/pipeline.js +1 -1
- package/dist/render/renderer.d.ts +1 -1
- package/dist/render/renderer.js +1 -1
- package/dist/render/tints.d.ts +1 -1
- package/dist/render/tints.js +1 -1
- package/dist/{renderer-C79EQDSn.d.ts → renderer-U20hnhbi.d.ts} +2 -2
- package/dist/resources/descriptor.js +1 -1
- package/dist/resources/entity-textures.d.ts +51 -0
- package/dist/resources/entity-textures.js +2 -0
- package/dist/resources/jar.js +1 -1
- package/dist/resources/locator.js +1 -1
- package/dist/resources/manager.js +1 -1
- package/dist/resources/synthetic.js +1 -1
- package/dist/{run-uq8_hUx9.d.ts → run-CvIqiZ6T.d.ts} +1 -1
- package/dist/{run-BPfpBZyZ.js → run-D2S_W5As.js} +10 -7
- package/dist/run.d.ts +1 -1
- package/dist/run.js +1 -1
- package/dist/{sources-Bxk-WOzH.js → sources-BlUriDCs.js} +1 -1
- package/dist/{synthesize-3Q0aaOnK.js → synthesize-jTjVjPAt.js} +2 -2
- package/dist/{synthetic-ZAUjXT7E.js → synthetic-rc9BPT4P.js} +0 -0
- package/dist/textures/loader.js +1 -1
- package/dist/tints-2k3ZhkY3.d.ts +41 -0
- package/dist/{tints-3VI_2SNy.js → tints-B3WQcPtx.js} +1 -1
- package/dist/{tints-Do9Sh3dg.d.ts → tints-DqhgFi8a.d.ts} +1 -1
- package/dist/tints-a9FWZLma.js +176 -0
- package/dist/{tints-CebmvjmP.js → tints-nxtGrqv5.js} +3 -3
- package/dist/{types-DpRSF9Vj.d.ts → types-qlp3I8_-.d.ts} +11 -0
- package/dist/versions/download.js +1 -1
- package/dist/versions/legacy-reshape.js +1 -1
- package/dist/versions/prepare.js +1 -1
- package/dist/workers/render-worker.d.ts +1 -1
- package/dist/workers/render-worker.js +2 -2
- package/package.json +1 -1
- package/dist/{download-D9aARBFp.js → download-1wAfrGKx.js} +1 -1
- package/dist/{face-CMJ3b3Ae.js → face-CKHboPfh.js} +1 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { n as ResourceLocator } from "./locator-COMEtEn9.js";
|
|
2
|
+
//#region src/blocks/blockstate-eval.ts
|
|
3
|
+
/**
|
|
4
|
+
* Real blockstate evaluation: given a block's actual property map, the models
|
|
5
|
+
* the game would draw for it.
|
|
6
|
+
*
|
|
7
|
+
* `src/items/blockstate.ts` reads the same files but answers a different
|
|
8
|
+
* question — "which variant looks most like a fresh block", so an item icon has
|
|
9
|
+
* something to draw. A chunk renderer knows `facing=north,half=bottom` and needs
|
|
10
|
+
* the model that key names, every model when several apply, and the whole
|
|
11
|
+
* weighted list when the game would pick one at random.
|
|
12
|
+
*
|
|
13
|
+
* Everything here is parse-once: the string keys, the `a|b` alternates and the
|
|
14
|
+
* `OR`/`AND` nesting are all resolved into structures, because the exported pack
|
|
15
|
+
* is meant to leave the consumer nothing to resolve at runtime.
|
|
16
|
+
*/
|
|
17
|
+
var BlockStateError = class extends Error {};
|
|
18
|
+
/** JSON writes booleans and numbers unquoted; blockstate keys write them as text. */
|
|
19
|
+
function propertyValue(value) {
|
|
20
|
+
return typeof value === "string" ? value : String(value);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The third rotation axis, which vanilla does not have. AE2 writes it as
|
|
24
|
+
* `ae2:z`; the namespace is left open because a loader extension is a loader
|
|
25
|
+
* extension whoever ships it, and only a numeric `z` key can match.
|
|
26
|
+
*/
|
|
27
|
+
function zRotation(entry) {
|
|
28
|
+
for (const [name, value] of Object.entries(entry)) if ((name === "z" || name.endsWith(":z")) && typeof value === "number") return value;
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
/** Null for an explicit `weight: 0`, which the game can never pick. */
|
|
32
|
+
function parseVariant(namespace, raw, context) {
|
|
33
|
+
if (typeof raw !== "object" || raw === null) throw new BlockStateError(`${context}: variant is not an object`);
|
|
34
|
+
const entry = raw;
|
|
35
|
+
if (typeof entry.model !== "string" || entry.model.length === 0) throw new BlockStateError(`${context}: variant states no model`);
|
|
36
|
+
const model = ResourceLocator.tryParse(entry.model, namespace);
|
|
37
|
+
if (model === null) throw new BlockStateError(`${context}: unparseable model ${entry.model}`);
|
|
38
|
+
const weight = typeof entry.weight === "number" ? entry.weight : 1;
|
|
39
|
+
if (!Number.isFinite(weight) || weight <= 0) return null;
|
|
40
|
+
return {
|
|
41
|
+
model: model.toString(),
|
|
42
|
+
x: typeof entry.x === "number" ? entry.x : 0,
|
|
43
|
+
y: typeof entry.y === "number" ? entry.y : 0,
|
|
44
|
+
z: zRotation(raw),
|
|
45
|
+
uvlock: entry.uvlock === true,
|
|
46
|
+
weight
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseApply(namespace, raw, context) {
|
|
50
|
+
const entries = Array.isArray(raw) ? raw : [raw];
|
|
51
|
+
if (entries.length === 0) throw new BlockStateError(`${context}: empty variant list`);
|
|
52
|
+
const variants = entries.map((entry) => parseVariant(namespace, entry, context)).filter((variant) => variant !== null);
|
|
53
|
+
if (variants.length === 0) throw new BlockStateError(`${context}: every variant has weight 0`);
|
|
54
|
+
return variants;
|
|
55
|
+
}
|
|
56
|
+
/** `facing=north,half=bottom` → `{ facing: 'north', half: 'bottom' }`; `''` → `{}`. */
|
|
57
|
+
function parseVariantKey(key, context) {
|
|
58
|
+
const when = {};
|
|
59
|
+
if (key.length === 0) return when;
|
|
60
|
+
for (const pair of key.split(",")) {
|
|
61
|
+
const separator = pair.indexOf("=");
|
|
62
|
+
if (separator <= 0) throw new BlockStateError(`${context}: variant key segment "${pair}" states no property; a bare key is the pre-1.13 format, below this exporter's floor`);
|
|
63
|
+
when[pair.slice(0, separator)] = pair.slice(separator + 1);
|
|
64
|
+
}
|
|
65
|
+
return when;
|
|
66
|
+
}
|
|
67
|
+
function parseCondition(raw, context) {
|
|
68
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new BlockStateError(`${context}: "when" is not an object`);
|
|
69
|
+
const terms = [];
|
|
70
|
+
const properties = {};
|
|
71
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
72
|
+
if (name === "OR" || name === "AND") {
|
|
73
|
+
if (!Array.isArray(value)) throw new BlockStateError(`${context}: "${name}" is not an array`);
|
|
74
|
+
terms.push({
|
|
75
|
+
op: name === "OR" ? "or" : "and",
|
|
76
|
+
terms: value.map((entry) => parseCondition(entry, context))
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
properties[name] = propertyValue(value).split("|");
|
|
81
|
+
}
|
|
82
|
+
if (Object.keys(properties).length > 0) terms.push({
|
|
83
|
+
op: "match",
|
|
84
|
+
properties
|
|
85
|
+
});
|
|
86
|
+
if (terms.length === 0) throw new BlockStateError(`${context}: empty "when"`);
|
|
87
|
+
return terms.length === 1 ? terms[0] : {
|
|
88
|
+
op: "and",
|
|
89
|
+
terms
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* NeoForge 1.21.6+ lets a mod register a blockstate *definition* loader the way
|
|
94
|
+
* it can register a model loader, and the file then carries the loader's own
|
|
95
|
+
* shape instead of variants. Only the fallback is recoverable from assets:
|
|
96
|
+
* `base_model` is what the block looks like before whatever the loader does at
|
|
97
|
+
* runtime — for FramedBlocks' 233 camouflage blocks, the uncamouflaged cube.
|
|
98
|
+
*/
|
|
99
|
+
function parseDefinitionLoader(namespace, json, context) {
|
|
100
|
+
const entries = json;
|
|
101
|
+
const loader = Object.entries(entries).find(([name]) => name === "definition_type" || name.endsWith(":definition_type"))?.[1];
|
|
102
|
+
if (typeof loader !== "string") return null;
|
|
103
|
+
const base = entries.base_model;
|
|
104
|
+
const model = typeof base === "string" ? base : base?.model;
|
|
105
|
+
if (typeof model !== "string") throw new BlockStateError(`${context}: definition loader ${loader} states no base_model`);
|
|
106
|
+
return {
|
|
107
|
+
kind: "variants",
|
|
108
|
+
cases: [{
|
|
109
|
+
when: {},
|
|
110
|
+
apply: parseApply(namespace, { model }, context)
|
|
111
|
+
}]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** Parse the cases of one form, dropping unusable ones when tolerated. */
|
|
115
|
+
function parseCases(entries, build, context, options) {
|
|
116
|
+
const cases = [];
|
|
117
|
+
for (const [key, value] of entries) try {
|
|
118
|
+
cases.push(build(key, value));
|
|
119
|
+
} catch (err) {
|
|
120
|
+
if (!options.onCaseError) throw err;
|
|
121
|
+
options.onCaseError(err.message);
|
|
122
|
+
}
|
|
123
|
+
if (cases.length === 0) throw new BlockStateError(`${context}: no usable cases`);
|
|
124
|
+
return cases;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse a blockstate file. `namespace` is the block's own, which is what an
|
|
128
|
+
* unqualified model reference resolves against.
|
|
129
|
+
*/
|
|
130
|
+
function parseBlockState(namespace, json, context = namespace, options = {}) {
|
|
131
|
+
if (typeof json !== "object" || json === null) throw new BlockStateError(`${context}: blockstate is not an object`);
|
|
132
|
+
const { variants, multipart } = json;
|
|
133
|
+
if (variants !== void 0 && multipart !== void 0) throw new BlockStateError(`${context}: states both variants and multipart`);
|
|
134
|
+
if (variants !== void 0) {
|
|
135
|
+
if (typeof variants !== "object" || variants === null || Array.isArray(variants)) throw new BlockStateError(`${context}: "variants" is not an object`);
|
|
136
|
+
const entries = Object.entries(variants);
|
|
137
|
+
if (entries.length === 0) throw new BlockStateError(`${context}: empty "variants"`);
|
|
138
|
+
return {
|
|
139
|
+
kind: "variants",
|
|
140
|
+
cases: parseCases(entries, (key, value) => {
|
|
141
|
+
return {
|
|
142
|
+
when: parseVariantKey(key, context),
|
|
143
|
+
apply: parseApply(namespace, value, `${context} [${key}]`)
|
|
144
|
+
};
|
|
145
|
+
}, context, options)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (Array.isArray(multipart)) {
|
|
149
|
+
if (multipart.length === 0) throw new BlockStateError(`${context}: empty "multipart"`);
|
|
150
|
+
return {
|
|
151
|
+
kind: "multipart",
|
|
152
|
+
cases: parseCases(multipart.map((part, index) => [String(index), part]), (key, value) => {
|
|
153
|
+
const entry = value;
|
|
154
|
+
return {
|
|
155
|
+
when: entry.when === void 0 ? null : parseCondition(entry.when, `${context} [part ${key}]`),
|
|
156
|
+
apply: parseApply(namespace, entry.apply, `${context} [part ${key}]`)
|
|
157
|
+
};
|
|
158
|
+
}, context, options)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const viaLoader = parseDefinitionLoader(namespace, json, context);
|
|
162
|
+
if (viaLoader !== null) {
|
|
163
|
+
options.onNote?.("a loader defines this blockstate; exported as its base_model, which is what the block shows before the loader runs");
|
|
164
|
+
return viaLoader;
|
|
165
|
+
}
|
|
166
|
+
throw new BlockStateError(`${context}: states neither variants nor multipart`);
|
|
167
|
+
}
|
|
168
|
+
function matchesCondition(condition, properties) {
|
|
169
|
+
switch (condition.op) {
|
|
170
|
+
case "match": return Object.entries(condition.properties).every(([name, accepted]) => accepted.includes(properties[name]));
|
|
171
|
+
case "and": return condition.terms.every((term) => matchesCondition(term, properties));
|
|
172
|
+
case "or": return condition.terms.some((term) => matchesCondition(term, properties));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* The models the game draws for a block in this state, as one weighted list per
|
|
177
|
+
* contributing part — a fence is several models at once, and each of them may
|
|
178
|
+
* itself be a random pick. Empty when nothing matches, which is a real answer:
|
|
179
|
+
* a blockstate that covers only some of a block's states draws nothing for the
|
|
180
|
+
* rest.
|
|
181
|
+
*/
|
|
182
|
+
function evaluateBlockState(definition, properties) {
|
|
183
|
+
if (definition.kind === "multipart") return definition.cases.filter((part) => part.when === null || matchesCondition(part.when, properties)).map((part) => part.apply);
|
|
184
|
+
let best = null;
|
|
185
|
+
for (const variant of definition.cases) {
|
|
186
|
+
const entries = Object.entries(variant.when);
|
|
187
|
+
if (!entries.every(([name, value]) => properties[name] === value)) continue;
|
|
188
|
+
if (best === null || entries.length > Object.keys(best.when).length) best = variant;
|
|
189
|
+
}
|
|
190
|
+
return best === null ? [] : [best.apply];
|
|
191
|
+
}
|
|
192
|
+
/** Every model a blockstate can name, for deciding what to export. */
|
|
193
|
+
function collectModels(definition) {
|
|
194
|
+
const models = /* @__PURE__ */ new Set();
|
|
195
|
+
for (const part of definition.cases) for (const variant of part.apply) models.add(variant.model);
|
|
196
|
+
return [...models];
|
|
197
|
+
}
|
|
198
|
+
const JAVA_MULTIPLIER = 25214903917n;
|
|
199
|
+
const JAVA_MASK = (1n << 48n) - 1n;
|
|
200
|
+
/**
|
|
201
|
+
* Vanilla's per-position pick from a weighted variant list, so the consumer's
|
|
202
|
+
* grass rotations land where the game puts them instead of somewhere merely
|
|
203
|
+
* deterministic.
|
|
204
|
+
*
|
|
205
|
+
* This is `Mth.getSeed(x, y, z)` seeding a `java.util.Random`, then
|
|
206
|
+
* `WeightedBakedModel`'s `abs((int) nextLong()) % totalWeight`. Both halves are
|
|
207
|
+
* stable across 1.13–1.21.3; 1.21.4 reworked the model system and parity there
|
|
208
|
+
* is unverified, which costs a variant rotation and nothing else.
|
|
209
|
+
*/
|
|
210
|
+
function weightedVariantIndex(weights, blockX, blockY, blockZ) {
|
|
211
|
+
if (weights.length <= 1) return 0;
|
|
212
|
+
const total = weights.reduce((sum, weight) => sum + weight, 0);
|
|
213
|
+
let seed = BigInt(blockX) * 3129871n ^ BigInt(blockZ) * 116129781n ^ BigInt(blockY);
|
|
214
|
+
seed = seed * seed * 42317861n + seed * 11n;
|
|
215
|
+
seed = BigInt.asIntN(64, seed) >> 16n;
|
|
216
|
+
let state = (BigInt.asUintN(64, seed) ^ JAVA_MULTIPLIER) & JAVA_MASK;
|
|
217
|
+
const next = () => {
|
|
218
|
+
state = state * JAVA_MULTIPLIER + 11n & JAVA_MASK;
|
|
219
|
+
return BigInt.asIntN(32, state >> 16n);
|
|
220
|
+
};
|
|
221
|
+
next();
|
|
222
|
+
const truncated = Number(next());
|
|
223
|
+
let remaining = (truncated === -2147483648 ? truncated : Math.abs(truncated)) % total;
|
|
224
|
+
for (const [index, weight] of weights.entries()) {
|
|
225
|
+
remaining -= weight;
|
|
226
|
+
if (remaining < 0) return index;
|
|
227
|
+
}
|
|
228
|
+
return weights.length - 1;
|
|
229
|
+
}
|
|
230
|
+
//#endregion
|
|
231
|
+
export { parseBlockState as a, matchesCondition as i, collectModels as n, weightedVariantIndex as o, evaluateBlockState as r, BlockStateError as t };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
//#region src/blocks/blockstate-eval.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Real blockstate evaluation: given a block's actual property map, the models
|
|
4
|
+
* the game would draw for it.
|
|
5
|
+
*
|
|
6
|
+
* `src/items/blockstate.ts` reads the same files but answers a different
|
|
7
|
+
* question — "which variant looks most like a fresh block", so an item icon has
|
|
8
|
+
* something to draw. A chunk renderer knows `facing=north,half=bottom` and needs
|
|
9
|
+
* the model that key names, every model when several apply, and the whole
|
|
10
|
+
* weighted list when the game would pick one at random.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is parse-once: the string keys, the `a|b` alternates and the
|
|
13
|
+
* `OR`/`AND` nesting are all resolved into structures, because the exported pack
|
|
14
|
+
* is meant to leave the consumer nothing to resolve at runtime.
|
|
15
|
+
*/
|
|
16
|
+
/** A placed block's properties, stringified the way blockstate keys write them. */
|
|
17
|
+
type BlockProperties = Readonly<Record<string, string>>;
|
|
18
|
+
interface BlockModelVariant {
|
|
19
|
+
/** Fully-qualified model locator (`minecraft:block/oak_stairs`). */
|
|
20
|
+
model: string;
|
|
21
|
+
/** Blockstate rotation in degrees about X, applied to the baked model. */
|
|
22
|
+
x: number;
|
|
23
|
+
/** Blockstate rotation in degrees about Y. */
|
|
24
|
+
y: number;
|
|
25
|
+
/**
|
|
26
|
+
* Rotation about Z, applied after X and Y. Vanilla has no third axis; AE2
|
|
27
|
+
* adds one as `ae2:z` so a drive or charger can spin on the wall it faces.
|
|
28
|
+
*/
|
|
29
|
+
z: number;
|
|
30
|
+
/** Hold UVs still while the model rotates. */
|
|
31
|
+
uvlock: boolean;
|
|
32
|
+
/** Relative weight within its list; the game picks one per block position. */
|
|
33
|
+
weight: number;
|
|
34
|
+
}
|
|
35
|
+
/** One entry of a `variants` map, with its `a=b,c=d` key already parsed. */
|
|
36
|
+
interface BlockStateVariantCase {
|
|
37
|
+
/** Properties this case constrains; unstated ones are wildcards. */
|
|
38
|
+
when: Record<string, string>;
|
|
39
|
+
apply: BlockModelVariant[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A `multipart` predicate. `match` holds one or more accepted values per
|
|
43
|
+
* property (the `a|b|c` form), and every property in it must match.
|
|
44
|
+
*/
|
|
45
|
+
type MultipartCondition = {
|
|
46
|
+
op: 'match';
|
|
47
|
+
properties: Record<string, string[]>;
|
|
48
|
+
} | {
|
|
49
|
+
op: 'and';
|
|
50
|
+
terms: MultipartCondition[];
|
|
51
|
+
} | {
|
|
52
|
+
op: 'or';
|
|
53
|
+
terms: MultipartCondition[];
|
|
54
|
+
};
|
|
55
|
+
interface BlockStateMultipartCase {
|
|
56
|
+
/** null for an unconditional part, which always applies. */
|
|
57
|
+
when: MultipartCondition | null;
|
|
58
|
+
apply: BlockModelVariant[];
|
|
59
|
+
}
|
|
60
|
+
type BlockStateDefinition = {
|
|
61
|
+
kind: 'variants';
|
|
62
|
+
cases: BlockStateVariantCase[];
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'multipart';
|
|
65
|
+
cases: BlockStateMultipartCase[];
|
|
66
|
+
};
|
|
67
|
+
declare class BlockStateError extends Error {}
|
|
68
|
+
interface ParseBlockStateOptions {
|
|
69
|
+
/**
|
|
70
|
+
* Called instead of throwing when one case is unusable, so the rest of the
|
|
71
|
+
* block survives it. AE2's `crafting_monitor` states no model for
|
|
72
|
+
* `formed=true` because the game draws that state itself — without this,
|
|
73
|
+
* one such case takes all eleven others with it.
|
|
74
|
+
*/
|
|
75
|
+
onCaseError?: (message: string) => void;
|
|
76
|
+
/** Called when the blockstate parsed, but not as the vanilla format. */
|
|
77
|
+
onNote?: (message: string) => void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Parse a blockstate file. `namespace` is the block's own, which is what an
|
|
81
|
+
* unqualified model reference resolves against.
|
|
82
|
+
*/
|
|
83
|
+
declare function parseBlockState(namespace: string, json: unknown, context?: string, options?: ParseBlockStateOptions): BlockStateDefinition;
|
|
84
|
+
declare function matchesCondition(condition: MultipartCondition, properties: BlockProperties): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* The models the game draws for a block in this state, as one weighted list per
|
|
87
|
+
* contributing part — a fence is several models at once, and each of them may
|
|
88
|
+
* itself be a random pick. Empty when nothing matches, which is a real answer:
|
|
89
|
+
* a blockstate that covers only some of a block's states draws nothing for the
|
|
90
|
+
* rest.
|
|
91
|
+
*/
|
|
92
|
+
declare function evaluateBlockState(definition: BlockStateDefinition, properties: BlockProperties): BlockModelVariant[][];
|
|
93
|
+
/** Every model a blockstate can name, for deciding what to export. */
|
|
94
|
+
declare function collectModels(definition: BlockStateDefinition): string[];
|
|
95
|
+
/**
|
|
96
|
+
* Vanilla's per-position pick from a weighted variant list, so the consumer's
|
|
97
|
+
* grass rotations land where the game puts them instead of somewhere merely
|
|
98
|
+
* deterministic.
|
|
99
|
+
*
|
|
100
|
+
* This is `Mth.getSeed(x, y, z)` seeding a `java.util.Random`, then
|
|
101
|
+
* `WeightedBakedModel`'s `abs((int) nextLong()) % totalWeight`. Both halves are
|
|
102
|
+
* stable across 1.13–1.21.3; 1.21.4 reworked the model system and parity there
|
|
103
|
+
* is unverified, which costs a variant rotation and nothing else.
|
|
104
|
+
*/
|
|
105
|
+
declare function weightedVariantIndex(weights: readonly number[], blockX: number, blockY: number, blockZ: number): number;
|
|
106
|
+
//#endregion
|
|
107
|
+
export { BlockStateMultipartCase as a, ParseBlockStateOptions as c, matchesCondition as d, parseBlockState as f, BlockStateError as i, collectModels as l, BlockProperties as n, BlockStateVariantCase as o, weightedVariantIndex as p, BlockStateDefinition as r, MultipartCondition as s, BlockModelVariant as t, evaluateBlockState as u };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { n as builtinPackPath } from "./builtin-WkWjATeh.js";
|
|
2
|
+
import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-CYbAkWuT.js";
|
|
3
|
+
import { t as exportBlockPack } from "./export-DxREy3qA.js";
|
|
4
|
+
import { n as prepareModResources } from "./prepare-B8oPdGYz.js";
|
|
5
|
+
import { t as downloadClientJar } from "./download-1wAfrGKx.js";
|
|
6
|
+
import { a as prepareVersionResources, r as compareVersions } from "./prepare-C95vjt_o.js";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import { parseArgs } from "node:util";
|
|
10
|
+
//#region src/blocks/cli.ts
|
|
11
|
+
/**
|
|
12
|
+
* `rendermite blocks` — the block pack export, mirroring the icon CLI's input
|
|
13
|
+
* handling (download a version's jar, or read asset folders, plus mod jars) but
|
|
14
|
+
* emitting geometry and an atlas instead of icons.
|
|
15
|
+
*/
|
|
16
|
+
/** The Flattening: below it a block is `35:14` and needs a numeric-id table. */
|
|
17
|
+
const MINIMUM_VERSION = "1.13";
|
|
18
|
+
const HELP = `rendermite blocks — Minecraft block pack export (geometry + atlas, 1.13+)
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
rendermite blocks --version <mc version> --output <dir> [options]
|
|
22
|
+
rendermite blocks --assets <dir> [--assets <dir> ...] --output <dir> [options]
|
|
23
|
+
|
|
24
|
+
Input:
|
|
25
|
+
-v, --version <ver> download + prepare a vanilla version (1.13 … latest)
|
|
26
|
+
-a, --assets <dir> asset root (repeatable, first wins), <namespace>/ children
|
|
27
|
+
-m, --mod <jar> mod jar layered under vanilla (repeatable)
|
|
28
|
+
|
|
29
|
+
Options:
|
|
30
|
+
-n, --namespace <ns> namespace to export (repeatable, default: minecraft,
|
|
31
|
+
or every mod namespace when --mod is given)
|
|
32
|
+
-o, --output <dir> pack directory to write
|
|
33
|
+
--padding <px> edge-extended sprite border (default 4)
|
|
34
|
+
--max-page-size <px> atlas page ceiling (default 4096)
|
|
35
|
+
--builtin <dir> override the vendored geometry pack
|
|
36
|
+
--no-builtin do not substitute geometry for code-drawn blocks
|
|
37
|
+
--cache-dir <dir> jar download cache (default ~/.rendermite)
|
|
38
|
+
`;
|
|
39
|
+
/**
|
|
40
|
+
* The sizes reach the packer directly, where a `NaN` classifies every sprite as
|
|
41
|
+
* oversized and writes an empty atlas — worse than a crash, and only after the
|
|
42
|
+
* jar download and a full export. Checked up front instead.
|
|
43
|
+
*/
|
|
44
|
+
function parseSizes(padding, maxPageSize) {
|
|
45
|
+
const errors = [];
|
|
46
|
+
const size = (raw, flag, minimum) => {
|
|
47
|
+
if (raw === void 0) return;
|
|
48
|
+
const value = Number(raw);
|
|
49
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
50
|
+
errors.push(`--${flag} must be a whole number of at least ${minimum}; got "${raw}".`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
padding: size(padding, "padding", 0),
|
|
57
|
+
maxPageSize: size(maxPageSize, "max-page-size", 16),
|
|
58
|
+
errors
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function runBlocksCli(argv) {
|
|
62
|
+
const { values } = parseArgs({
|
|
63
|
+
args: argv,
|
|
64
|
+
options: {
|
|
65
|
+
"assets": {
|
|
66
|
+
type: "string",
|
|
67
|
+
short: "a",
|
|
68
|
+
multiple: true
|
|
69
|
+
},
|
|
70
|
+
"version": {
|
|
71
|
+
type: "string",
|
|
72
|
+
short: "v"
|
|
73
|
+
},
|
|
74
|
+
"mod": {
|
|
75
|
+
type: "string",
|
|
76
|
+
short: "m",
|
|
77
|
+
multiple: true
|
|
78
|
+
},
|
|
79
|
+
"namespace": {
|
|
80
|
+
type: "string",
|
|
81
|
+
short: "n",
|
|
82
|
+
multiple: true
|
|
83
|
+
},
|
|
84
|
+
"output": {
|
|
85
|
+
type: "string",
|
|
86
|
+
short: "o"
|
|
87
|
+
},
|
|
88
|
+
"padding": { type: "string" },
|
|
89
|
+
"max-page-size": { type: "string" },
|
|
90
|
+
"cache-dir": { type: "string" },
|
|
91
|
+
"builtin": { type: "string" },
|
|
92
|
+
"no-builtin": {
|
|
93
|
+
type: "boolean",
|
|
94
|
+
default: false
|
|
95
|
+
},
|
|
96
|
+
"help": {
|
|
97
|
+
type: "boolean",
|
|
98
|
+
short: "h",
|
|
99
|
+
default: false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
if (values.help || !values.output || !(values.assets?.length || values.version)) {
|
|
104
|
+
console.log(HELP);
|
|
105
|
+
return values.help ? 0 : 1;
|
|
106
|
+
}
|
|
107
|
+
if (values.version && compareVersions(values.version, MINIMUM_VERSION) < 0) {
|
|
108
|
+
console.error(`Block packs start at ${MINIMUM_VERSION} (the Flattening); ${values.version} needs a numeric-id table that is out of scope.`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
const sizes = parseSizes(values.padding, values["max-page-size"]);
|
|
112
|
+
if (sizes.errors.length > 0) {
|
|
113
|
+
for (const message of sizes.errors) console.error(message);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
const cacheDir = values["cache-dir"] ?? path.join(os.homedir(), ".rendermite");
|
|
117
|
+
let resources;
|
|
118
|
+
if (values.version) {
|
|
119
|
+
console.log(`Fetching client jar for ${values.version}…`);
|
|
120
|
+
resources = await prepareVersionResources({
|
|
121
|
+
version: values.version,
|
|
122
|
+
jar: await downloadClientJar(values.version, cacheDir)
|
|
123
|
+
});
|
|
124
|
+
} else {
|
|
125
|
+
const managers = (values.assets ?? []).map((root) => new FolderResourceManager(root));
|
|
126
|
+
resources = managers.length === 1 ? managers[0] : new LayeredResourceManager(managers);
|
|
127
|
+
}
|
|
128
|
+
let namespaces = values.namespace?.length ? values.namespace : ["minecraft"];
|
|
129
|
+
let closeMods = null;
|
|
130
|
+
if (values.mod?.length) {
|
|
131
|
+
const prepared = await prepareModResources({
|
|
132
|
+
jars: values.mod,
|
|
133
|
+
vanilla: resources
|
|
134
|
+
});
|
|
135
|
+
resources = prepared.resources;
|
|
136
|
+
closeMods = () => prepared.close();
|
|
137
|
+
for (const { jar, reason } of prepared.rejected) console.error(`Rejected mod jar ${jar}: ${reason}`);
|
|
138
|
+
namespaces = values.namespace?.length ? values.namespace : prepared.namespaces;
|
|
139
|
+
}
|
|
140
|
+
try {
|
|
141
|
+
const result = await exportBlockPack({
|
|
142
|
+
resources,
|
|
143
|
+
builtin: values["no-builtin"] ? void 0 : values.builtin ?? builtinPackPath("modern"),
|
|
144
|
+
output: values.output,
|
|
145
|
+
version: values.version ?? "custom",
|
|
146
|
+
namespaces,
|
|
147
|
+
padding: sizes.padding,
|
|
148
|
+
maxPageSize: sizes.maxPageSize,
|
|
149
|
+
onProgress: (stage, done, total) => {
|
|
150
|
+
if (done === total || done % 250 === 0) console.log(`${stage}: ${done}/${total}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
console.log(`\n${result.blocks} blocks, ${result.models} models, ${result.sprites} sprites across ${result.pages} atlas page(s); ${result.writtenFiles} files written.`);
|
|
154
|
+
console.log(`${result.failures.length} entries in failures.json.`);
|
|
155
|
+
return 0;
|
|
156
|
+
} finally {
|
|
157
|
+
await closeMods?.();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
export { runBlocksCli as t };
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as builtinPackPath, t as bedCompatPath } from "./builtin-WkWjATeh.js";
|
|
3
|
-
import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-
|
|
4
|
-
import { t as curatedModelOverrides } from "./models-
|
|
5
|
-
import { n as harvestTintOverrides, t as curatedTintOverrides } from "./tints-
|
|
6
|
-
import { t as JarResourceManager } from "./jar-
|
|
7
|
-
import { t as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
3
|
+
import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-CYbAkWuT.js";
|
|
4
|
+
import { t as curatedModelOverrides } from "./models-CQwcTZcg.js";
|
|
5
|
+
import { n as harvestTintOverrides, t as curatedTintOverrides } from "./tints-nxtGrqv5.js";
|
|
6
|
+
import { t as JarResourceManager } from "./jar-5aLDI0nT.js";
|
|
7
|
+
import { t as downloadClientJar } from "./download-1wAfrGKx.js";
|
|
8
|
+
import { a as prepareVersionResources, i as eraForVersion, n as GEOMETRY_DONOR_VERSIONS } from "./prepare-C95vjt_o.js";
|
|
9
|
+
import { t as runBlocksCli } from "./cli-D7_oUuPT.js";
|
|
10
|
+
import { t as run } from "./run-D2S_W5As.js";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
import os from "node:os";
|
|
12
13
|
import { parseArgs } from "node:util";
|
|
@@ -17,6 +18,7 @@ import { parseArgs } from "node:util";
|
|
|
17
18
|
* (donor geometry, legacy reshaping, bed compat), and picks the right builtin
|
|
18
19
|
* pack — one command from nothing to icons for any version back to 1.2.5.
|
|
19
20
|
*/
|
|
21
|
+
if (process.argv[2] === "blocks") process.exit(await runBlocksCli(process.argv.slice(3)));
|
|
20
22
|
const { values } = parseArgs({ options: {
|
|
21
23
|
"assets": {
|
|
22
24
|
type: "string",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-
|
|
2
|
-
import { n as MapResourceManager, t as KindFilteredResourceManager } from "./synthetic-
|
|
3
|
-
import { t as JarResourceManager } from "./jar-
|
|
1
|
+
import { n as LayeredResourceManager, t as FolderResourceManager } from "./manager-CYbAkWuT.js";
|
|
2
|
+
import { n as MapResourceManager, t as KindFilteredResourceManager } from "./synthetic-rc9BPT4P.js";
|
|
3
|
+
import { t as JarResourceManager } from "./jar-5aLDI0nT.js";
|
|
4
4
|
//#region src/resources/descriptor.ts
|
|
5
5
|
/**
|
|
6
6
|
* Serializable descriptions of a resource tree, so the same layered tree can be
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ResourceLocator } from "./locator-
|
|
1
|
+
import { n as ResourceLocator } from "./locator-COMEtEn9.js";
|
|
2
2
|
import { o as identity } from "./vector-CCxLae_T.js";
|
|
3
3
|
import { n as InvalidItemDefinitionError, o as evaluateItemModel } from "./definition-ColsvWrH.js";
|
|
4
4
|
//#region src/items/discovery.ts
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as builtinDynamicResources, i as TRIM_MATERIALS, n as POTTERY_SHERDS, r as TRIM_DARKER_MATERIALS, t as ARMOR_ITEM_TYPES } from "../generators-
|
|
1
|
+
import { a as builtinDynamicResources, i as TRIM_MATERIALS, n as POTTERY_SHERDS, r as TRIM_DARKER_MATERIALS, t as ARMOR_ITEM_TYPES } from "../generators-BCZVlWig.js";
|
|
2
2
|
export { ARMOR_ITEM_TYPES, POTTERY_SHERDS, TRIM_DARKER_MATERIALS, TRIM_MATERIALS, builtinDynamicResources };
|