@nodecraft/rendermite 1.7.3 → 1.8.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/dist/atlas/registry.d.ts +1 -1
- package/dist/atlas/registry.js +1 -1
- package/dist/atlas/sources.d.ts +1 -1
- package/dist/atlas/sources.js +1 -1
- package/dist/blocks/cli.js +1 -1
- package/dist/blocks/ender-storage.d.ts +1 -1
- package/dist/blocks/entities.d.ts +1 -1
- package/dist/blocks/entity-models.d.ts +3 -3
- package/dist/blocks/entity-models.js +1 -1
- package/dist/blocks/export.d.ts +1 -1
- package/dist/blocks/export.js +1 -1
- package/dist/blocks/geo-blocks.d.ts +1 -1
- package/dist/blocks/item-sprites.d.ts +1 -1
- package/dist/blocks/light-bytecode.d.ts +174 -0
- package/dist/blocks/light-bytecode.js +2 -0
- package/dist/blocks/light-eval.d.ts +2 -0
- package/dist/blocks/light-eval.js +2 -0
- package/dist/blocks/light.d.ts +38 -0
- package/dist/blocks/light.js +2 -0
- package/dist/blocks/pack-format.d.ts +2 -2
- package/dist/blocks/sprite-sheet.d.ts +1 -1
- package/dist/{classfile-Bo7FUrrD.js → classfile-CiIDF3vc.js} +23 -1
- package/dist/{classfile-BdLIk4Pe.d.ts → classfile-CsLXlh5z.d.ts} +23 -0
- package/dist/{cli-BuKutEzb.js → cli-Dw1Q5CPR.js} +5 -4
- package/dist/cli.js +5 -5
- package/dist/{descriptor-JyXhfd-c.js → descriptor-CmkIkDKL.js} +2 -2
- package/dist/entities/classfile.d.ts +1 -1
- package/dist/entities/classfile.js +1 -1
- package/dist/entities/inherit.d.ts +2 -2
- package/dist/entities/inherit.js +1 -1
- package/dist/entities/jar-classes.js +1 -1
- package/dist/entities/link.d.ts +2 -2
- package/dist/entities/link.js +1 -1
- package/dist/entities/mappings.d.ts +2 -2
- package/dist/entities/mappings.js +2 -2
- package/dist/entities/model-bytecode.d.ts +7 -2
- package/dist/entities/model-bytecode.js +2 -2
- package/dist/entities/model-code.d.ts +1 -1
- package/dist/entities/model-code.js +1 -1
- package/dist/{entity-models-DUKiHpn1.js → entity-models-BGiy3pey.js} +5 -5
- package/dist/{export-B8tPsf4K.d.ts → export-J13L9Lqu.d.ts} +3 -1
- package/dist/{export-CVXvPLv8.js → export-t8X_oRws.js} +23 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -7
- package/dist/{inherit-BYLi3Q28.js → inherit-BUn0Cve9.js} +2 -2
- package/dist/{jar-W2cV2ZVO.js → jar-CBYPMbw7.js} +1 -1
- package/dist/{jar-classes-3GVCJZT7.js → jar-classes-CSm7qq1R.js} +2 -2
- package/dist/light-CyRhYulM.js +582 -0
- package/dist/light-bytecode-Cpyuxo9b.js +1749 -0
- package/dist/light-eval-BsJJoSv4.js +23 -0
- package/dist/light-eval-D9x0ufZx.d.ts +13 -0
- package/dist/{link-5GmBLTzk.js → link-Cuhyrz07.js} +1 -1
- package/dist/{mappings-BEPVaiwE.js → mappings-Ep6ko0rg.js} +77 -9
- package/dist/{mappings-DxX4KoZl.d.ts → mappings-USwcjtcH.d.ts} +28 -2
- package/dist/{model-bytecode-B43uSqet.js → model-bytecode-VaLUQfGO.js} +2 -2
- package/dist/{model-code-BY44ao27.d.ts → model-code-Csc3bs3h.d.ts} +1 -1
- package/dist/{model-code-1Vuk-zj_.js → model-code-DbN2uVhp.js} +1 -1
- package/dist/mods/prepare.js +1 -1
- package/dist/mods/tints.js +1 -1
- package/dist/{pack-format-Baoc_aSZ.d.ts → pack-format-BqWf-DNc.d.ts} +23 -1
- package/dist/pack.d.ts +3 -2
- package/dist/pack.js +2 -1
- package/dist/{pipeline-Cci24Kb4.js → pipeline-CRTQGba7.js} +1 -1
- package/dist/{prepare-D49Gu4Lv.js → prepare-C61BcwFA.js} +2 -2
- package/dist/{prepare-Myd_ec4v.js → prepare-DxPEu9mo.js} +2 -2
- package/dist/{registry-DlduyjtM.js → registry-DafxsAhG.js} +1 -1
- package/dist/render/pipeline.js +1 -1
- package/dist/resources/descriptor.js +1 -1
- package/dist/resources/jar.js +1 -1
- package/dist/resources/zip-reader.js +1 -1
- package/dist/{run--oW_h01E.js → run-DYcem_Vv.js} +2 -2
- package/dist/run.js +1 -1
- package/dist/{sources-BlUriDCs.js → sources-DYY5Qu-t.js} +11 -2
- package/dist/{sources-DYYiyvRW.d.ts → sources-e5D09Jz_.d.ts} +7 -0
- package/dist/versions/prepare.js +1 -1
- package/dist/workers/render-worker.js +2 -2
- package/package.json +6 -6
- package/dist/{tints-BspVCz2G.js → tints-CWQz6UkM.js} +1 -1
- package/dist/{zip-reader-BSbJKUT1.js → zip-reader-zx3kdSq4.js} +1 -1
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
import { t as parseClassFile } from "./classfile-CiIDF3vc.js";
|
|
2
|
+
import { c as resolveMappings, s as remapDescriptor, t as MappingsUnavailableError } from "./mappings-Ep6ko0rg.js";
|
|
3
|
+
import { t as extractBlockLight } from "./light-bytecode-Cpyuxo9b.js";
|
|
4
|
+
import { r as compareVersions } from "./prepare-C61BcwFA.js";
|
|
5
|
+
//#region src/blocks/light.ts
|
|
6
|
+
/**
|
|
7
|
+
* Block light emission for the exported pack: `light.json`.
|
|
8
|
+
*
|
|
9
|
+
* Nothing in a jar's assets says a torch glows. Emission is set in Java, per block and often per
|
|
10
|
+
* blockstate — `Properties.lightLevel(state -> state.getValue(LIT) ? 7 : 0)` — so this reads the code,
|
|
11
|
+
* with the interpreter in `light-bytecode.ts`, for vanilla and mods alike. A mod's registry class is
|
|
12
|
+
* found the same way vanilla's is: it is the class whose string constants name the blocks.
|
|
13
|
+
*
|
|
14
|
+
* `VANILLA_LIGHT` is the check on that machinery, not its source. It is transcribed from `Blocks.java`
|
|
15
|
+
* and the wiki, and the test suite holds the extraction to it version by version: a block the
|
|
16
|
+
* interpreter reads differently from the table is a bug in one or the other, and either way a person
|
|
17
|
+
* looks. Mods have no table — their answers come from the code alone, and a block whose registration
|
|
18
|
+
* or light function the interpreter could not follow is recorded in `failures.json` rather than
|
|
19
|
+
* shipped dark.
|
|
20
|
+
*/
|
|
21
|
+
const TO_INT_FUNCTION = "Ljava/util/function/ToIntFunction;";
|
|
22
|
+
/** Registry classes to interpret, by how many of the pack's block names they mention. */
|
|
23
|
+
const MAX_ROOTS = 24;
|
|
24
|
+
/** Classes added for reading those classes' fields, and how many reads it takes to count. */
|
|
25
|
+
const MAX_REFERRERS = 2;
|
|
26
|
+
const MIN_REFERRER_REFS = 100;
|
|
27
|
+
/**
|
|
28
|
+
* Blockstate files that describe no block. The item frames are entities whose model the game looks up
|
|
29
|
+
* through a blockstate, so they have no registration to find and emit nothing.
|
|
30
|
+
*/
|
|
31
|
+
const NOT_BLOCKS = /* @__PURE__ */ new Set(["minecraft:item_frame", "minecraft:glow_item_frame"]);
|
|
32
|
+
/** Strings a class's constant pool holds, for spotting the one that registers the blocks. */
|
|
33
|
+
function stringConstants(classFile) {
|
|
34
|
+
const strings = /* @__PURE__ */ new Set();
|
|
35
|
+
for (const [index, constant] of classFile.constants.entries()) if (constant?.tag === "string") strings.add(classFile.utf8(index));
|
|
36
|
+
return strings;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The `Properties` builder class, in whatever the jars call it.
|
|
40
|
+
*
|
|
41
|
+
* Named by shape rather than by name, since an obfuscated jar has none: it is the class with a fluent
|
|
42
|
+
* method taking a `ToIntFunction` (the light function, 1.16+), and failing that the class most block
|
|
43
|
+
* constructors take as their last argument (1.13–1.15, where the light method takes an `int`).
|
|
44
|
+
*/
|
|
45
|
+
function findPropsClasses(roots, remapClass, resolve) {
|
|
46
|
+
const found = /* @__PURE__ */ new Set();
|
|
47
|
+
const lastParams = /* @__PURE__ */ new Map();
|
|
48
|
+
for (const classFile of roots) for (const [index, constant] of classFile.constants.entries()) {
|
|
49
|
+
if (constant?.tag !== "methodref" && constant?.tag !== "interfaceMethodref") continue;
|
|
50
|
+
const reference = classFile.reference(index);
|
|
51
|
+
if (!reference) continue;
|
|
52
|
+
if (reference.descriptor === `(${TO_INT_FUNCTION})L${reference.owner};` && !reference.owner.startsWith("java/")) found.add(reference.owner);
|
|
53
|
+
if (reference.name === "<init>") {
|
|
54
|
+
const match = /L([^;]+);\)V$/.exec(reference.descriptor);
|
|
55
|
+
if (match && !match[1].startsWith("java/")) lastParams.set(match[1], (lastParams.get(match[1]) ?? 0) + 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (const name of found) {
|
|
59
|
+
const classFile = resolve(name);
|
|
60
|
+
const fluent = classFile?.methods.filter((method) => method.descriptor === `()L${name};`).length ?? 0;
|
|
61
|
+
if (!classFile || classFile.superClass === "java/lang/Enum" || fluent < 3) found.delete(name);
|
|
62
|
+
}
|
|
63
|
+
for (const known of ["net/minecraft/world/level/block/state/BlockBehaviour$Properties", "net/minecraft/world/level/block/Block$Properties"]) {
|
|
64
|
+
const internal = remapClass(known);
|
|
65
|
+
if (resolve(internal)) {
|
|
66
|
+
found.add(internal);
|
|
67
|
+
found.add(known);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (found.size === 0) {
|
|
71
|
+
const commonest = [...lastParams].toSorted((left, right) => right[1] - left[1])[0];
|
|
72
|
+
if (commonest && commonest[1] >= 3) found.add(commonest[0]);
|
|
73
|
+
}
|
|
74
|
+
const spelled = new Set(found);
|
|
75
|
+
for (const name of found) spelled.add(remapClass(name));
|
|
76
|
+
return spelled;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Classes that lean on the named ones without naming any block themselves.
|
|
80
|
+
*
|
|
81
|
+
* From the 26 line `Blocks.<clinit>` holds no block name at all: the ids live in `BlockItemIds`, and
|
|
82
|
+
* `Blocks` reads them as static fields. The class with the most field reads into the named classes is
|
|
83
|
+
* that registry.
|
|
84
|
+
*/
|
|
85
|
+
function referrers(named, all, resolve) {
|
|
86
|
+
const targets = new Set(named.map((classFile) => classFile.thisClass));
|
|
87
|
+
const scored = [];
|
|
88
|
+
for (const internal of all) {
|
|
89
|
+
if (targets.has(internal)) continue;
|
|
90
|
+
const classFile = resolve(internal);
|
|
91
|
+
if (!classFile) continue;
|
|
92
|
+
let refs = 0;
|
|
93
|
+
for (const [index, constant] of classFile.constants.entries()) if (constant?.tag === "fieldref" && targets.has(classFile.reference(index)?.owner ?? "")) refs++;
|
|
94
|
+
if (refs >= MIN_REFERRER_REFS) scored.push({
|
|
95
|
+
classFile,
|
|
96
|
+
refs
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return scored.toSorted((left, right) => right.refs - left.refs || left.classFile.thisClass.localeCompare(right.classFile.thisClass)).slice(0, MAX_REFERRERS).map((entry) => entry.classFile);
|
|
100
|
+
}
|
|
101
|
+
/** The code of a method as `[opcode, operand bytes…]` groups, for matching a trivial body by shape. */
|
|
102
|
+
function trivialGetterField(code) {
|
|
103
|
+
return code.length === 5 && code[0] === 42 && code[1] === 180 && code[4] === 172 ? { index: code[2] << 8 | code[3] } : null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The methods a block class overrides to state its own light.
|
|
107
|
+
*
|
|
108
|
+
* Before 1.16 that is how a lit furnace worked: `Properties.lightValue(13)` on the block, and
|
|
109
|
+
* `getLightValue(state)` returning it or zero by `LIT`. An obfuscated jar names neither, so both are
|
|
110
|
+
* found by shape: the properties method that takes one `int`, the field it sets, the field the block's
|
|
111
|
+
* base constructor copies it into, and the base method that is nothing but a read of that field.
|
|
112
|
+
*
|
|
113
|
+
* From 1.16 the light is a lambda on the properties and vanilla overrides nothing, but a loader adds
|
|
114
|
+
* `getLightEmission(state, level, pos)` for mods, which is matched by its name — a mod jar keeps them.
|
|
115
|
+
*/
|
|
116
|
+
function findLightGetters(roots, propsClasses, resolve, notes) {
|
|
117
|
+
const getters = [{
|
|
118
|
+
name: "getLightEmission",
|
|
119
|
+
descriptor: "",
|
|
120
|
+
root: null
|
|
121
|
+
}];
|
|
122
|
+
const props = [...propsClasses].map((name) => resolve(name)).find((classFile) => classFile !== null);
|
|
123
|
+
if (!props) {
|
|
124
|
+
notes.push("light getters: no properties class resolves");
|
|
125
|
+
return getters;
|
|
126
|
+
}
|
|
127
|
+
if (props.methods.some((method) => method.descriptor === `(${TO_INT_FUNCTION})L${props.thisClass};`)) return getters;
|
|
128
|
+
const setter = props.methods.find((method) => method.descriptor === `(I)L${props.thisClass};` && method.code);
|
|
129
|
+
const setterCode = setter?.code;
|
|
130
|
+
if (!setterCode || setterCode.length !== 7 || setterCode[0] !== 42 || setterCode[1] !== 27 || setterCode[2] !== 181) {
|
|
131
|
+
notes.push(`light getters: no int setter of the expected shape on ${props.thisClass} (${setter ? `${setter.name}: ${setterCode?.length ?? 0} bytes` : "none"})`);
|
|
132
|
+
return getters;
|
|
133
|
+
}
|
|
134
|
+
const propsField = props.reference(setterCode[3] << 8 | setterCode[4]);
|
|
135
|
+
if (!propsField) return getters;
|
|
136
|
+
const ctorDescriptor = `(L${props.thisClass};)V`;
|
|
137
|
+
const bases = /* @__PURE__ */ new Map();
|
|
138
|
+
for (const classFile of roots) for (const [index, constant] of classFile.constants.entries()) {
|
|
139
|
+
if (constant?.tag !== "methodref") continue;
|
|
140
|
+
const reference = classFile.reference(index);
|
|
141
|
+
if (reference?.name !== "<init>" || reference.descriptor !== ctorDescriptor) continue;
|
|
142
|
+
let at = resolve(reference.owner);
|
|
143
|
+
let top = null;
|
|
144
|
+
for (let hops = 0; at && hops < 12; hops++) {
|
|
145
|
+
if (at.methods.some((method) => method.name === "<init>" && method.descriptor === ctorDescriptor)) top = at.thisClass;
|
|
146
|
+
at = at.superClass ? resolve(at.superClass) : null;
|
|
147
|
+
}
|
|
148
|
+
if (top) bases.set(top, (bases.get(top) ?? 0) + 1);
|
|
149
|
+
}
|
|
150
|
+
const base = [...bases].toSorted((left, right) => right[1] - left[1])[0];
|
|
151
|
+
const baseClass = base ? resolve(base[0]) : null;
|
|
152
|
+
const baseCtor = baseClass?.methods.find((method) => method.name === "<init>" && method.descriptor === ctorDescriptor)?.code;
|
|
153
|
+
if (!baseClass || !baseCtor) {
|
|
154
|
+
notes.push(`light getters: no base block class found (${bases.keys().toArray().join(", ") || "no constructors"})`);
|
|
155
|
+
return getters;
|
|
156
|
+
}
|
|
157
|
+
const readsPropsField = (reference, viaCall) => {
|
|
158
|
+
if (!reference) return false;
|
|
159
|
+
if (!viaCall) return reference.owner === propsField.owner && reference.name === propsField.name;
|
|
160
|
+
const code = (resolve(reference.owner)?.methods.find((method) => method.name === reference.name && method.descriptor === reference.descriptor))?.code;
|
|
161
|
+
if (!code || code.length !== 5 || code[0] !== 42 || code[1] !== 180) return false;
|
|
162
|
+
const field = resolve(reference.owner)?.reference(code[2] << 8 | code[3]);
|
|
163
|
+
return field?.owner === propsField.owner && field.name === propsField.name;
|
|
164
|
+
};
|
|
165
|
+
let blockField = null;
|
|
166
|
+
for (let at = 0; at + 6 < baseCtor.length; at++) {
|
|
167
|
+
if (baseCtor[at] !== 180 && baseCtor[at] !== 184) continue;
|
|
168
|
+
if (readsPropsField(baseClass.reference(baseCtor[at + 1] << 8 | baseCtor[at + 2]), baseCtor[at] === 184) && baseCtor[at + 3] === 181) {
|
|
169
|
+
const written = baseClass.reference(baseCtor[at + 4] << 8 | baseCtor[at + 5]);
|
|
170
|
+
blockField = written ? `${written.owner}.${written.name}` : null;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (!blockField) {
|
|
175
|
+
notes.push(`light getters: ${baseClass.thisClass}.<init> does not copy ${propsField.owner}.${propsField.name}`);
|
|
176
|
+
return getters;
|
|
177
|
+
}
|
|
178
|
+
for (const method of baseClass.methods) {
|
|
179
|
+
const shape = method.code ? trivialGetterField(method.code) : null;
|
|
180
|
+
if (!shape || !/^\(L[^;]+;\)I$/.test(method.descriptor)) continue;
|
|
181
|
+
const field = baseClass.reference(shape.index);
|
|
182
|
+
if (field && `${field.owner}.${field.name}` === blockField) getters.push({
|
|
183
|
+
name: method.name,
|
|
184
|
+
descriptor: method.descriptor,
|
|
185
|
+
root: baseClass.thisClass
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return getters;
|
|
189
|
+
}
|
|
190
|
+
/** Reference renaming through the mappings: a mod's original names onto an obfuscated client's. */
|
|
191
|
+
function remapper(mappings) {
|
|
192
|
+
const remapClass = (internalName) => {
|
|
193
|
+
const mapped = mappings.obfuscatedOf.get(internalName.replaceAll("/", "."));
|
|
194
|
+
return mapped ? mapped.replaceAll(".", "/") : internalName;
|
|
195
|
+
};
|
|
196
|
+
const remap = (reference, field) => {
|
|
197
|
+
const dotted = reference.owner.replaceAll("/", ".");
|
|
198
|
+
const owner = mappings.obfuscatedOf.get(dotted);
|
|
199
|
+
if (!owner || owner === dotted) return reference;
|
|
200
|
+
const member = field ? mappings.fieldOf.get(`${dotted}.${reference.name}`) : mappings.methodOf.get(`${dotted}.${reference.name}${reference.descriptor}`);
|
|
201
|
+
return {
|
|
202
|
+
owner: owner.replaceAll(".", "/"),
|
|
203
|
+
name: member ?? reference.name,
|
|
204
|
+
descriptor: remapDescriptor(reference.descriptor, mappings.obfuscatedOf)
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
return {
|
|
208
|
+
remap,
|
|
209
|
+
remapClass
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
async function collectBlockLight(options) {
|
|
213
|
+
const { classes, blockIds } = options;
|
|
214
|
+
const failures = [];
|
|
215
|
+
const notes = [];
|
|
216
|
+
const names = await classes.list();
|
|
217
|
+
let mappings;
|
|
218
|
+
try {
|
|
219
|
+
({mappings} = await resolveMappings(options.version, options.cacheDir, async () => names));
|
|
220
|
+
} catch (err) {
|
|
221
|
+
if (!(err instanceof MappingsUnavailableError)) throw err;
|
|
222
|
+
mappings = {
|
|
223
|
+
obfuscatedOf: /* @__PURE__ */ new Map(),
|
|
224
|
+
originalOf: /* @__PURE__ */ new Map(),
|
|
225
|
+
fieldOf: /* @__PURE__ */ new Map(),
|
|
226
|
+
methodOf: /* @__PURE__ */ new Map()
|
|
227
|
+
};
|
|
228
|
+
notes.push(err.message);
|
|
229
|
+
}
|
|
230
|
+
const { remap, remapClass } = remapper(mappings);
|
|
231
|
+
const bytes = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const name of names) {
|
|
233
|
+
const read = await classes.read(name);
|
|
234
|
+
if (read) bytes.set(name.replaceAll(".", "/"), read);
|
|
235
|
+
}
|
|
236
|
+
const parsed = /* @__PURE__ */ new Map();
|
|
237
|
+
const resolve = (name) => {
|
|
238
|
+
const internal = name.replaceAll(".", "/");
|
|
239
|
+
if (!parsed.has(internal)) {
|
|
240
|
+
const raw = bytes.get(internal);
|
|
241
|
+
let classFile;
|
|
242
|
+
try {
|
|
243
|
+
classFile = raw ? parseClassFile(raw) : null;
|
|
244
|
+
} catch {
|
|
245
|
+
classFile = null;
|
|
246
|
+
}
|
|
247
|
+
parsed.set(internal, classFile);
|
|
248
|
+
}
|
|
249
|
+
return parsed.get(internal) ?? null;
|
|
250
|
+
};
|
|
251
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
252
|
+
for (const id of blockIds) {
|
|
253
|
+
wanted.add(id);
|
|
254
|
+
wanted.add(id.includes(":") ? id.slice(id.indexOf(":") + 1) : id);
|
|
255
|
+
}
|
|
256
|
+
const scored = [];
|
|
257
|
+
let scanned = 0;
|
|
258
|
+
for (const internal of bytes.keys()) {
|
|
259
|
+
options.onProgress?.(++scanned, bytes.size);
|
|
260
|
+
const classFile = resolve(internal);
|
|
261
|
+
if (!classFile) continue;
|
|
262
|
+
let hits = 0;
|
|
263
|
+
for (const text of stringConstants(classFile)) if (wanted.has(text)) hits++;
|
|
264
|
+
if (hits > 0) scored.push({
|
|
265
|
+
classFile,
|
|
266
|
+
hits
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
const named = scored.toSorted((left, right) => right.hits - left.hits || left.classFile.thisClass.localeCompare(right.classFile.thisClass)).slice(0, MAX_ROOTS).map((entry) => entry.classFile);
|
|
270
|
+
const roots = [...named, ...referrers(named, bytes.keys(), resolve)];
|
|
271
|
+
if (roots.length === 0) {
|
|
272
|
+
for (const namespace of new Set(blockIds.map((id) => id.includes(":") ? id.slice(0, id.indexOf(":")) : "minecraft"))) failures.push({
|
|
273
|
+
id: namespace,
|
|
274
|
+
reason: "no class in the jars names its blocks; light unknown for all of them"
|
|
275
|
+
});
|
|
276
|
+
return {
|
|
277
|
+
light: {},
|
|
278
|
+
failures,
|
|
279
|
+
notes
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const propsClasses = findPropsClasses(roots, remapClass, resolve);
|
|
283
|
+
const source = {
|
|
284
|
+
resolve,
|
|
285
|
+
remap,
|
|
286
|
+
remapClass,
|
|
287
|
+
propsClasses,
|
|
288
|
+
lightGetters: findLightGetters(roots, propsClasses, resolve, notes),
|
|
289
|
+
isVanillaClass: (name) => mappings.originalOf.size === 0 || mappings.originalOf.has(name.replaceAll("/", ".")) || name.startsWith("net/minecraft/") || name.startsWith("com/mojang/")
|
|
290
|
+
};
|
|
291
|
+
notes.push(`registry classes: ${roots.map((root) => root.thisClass).join(", ")}`, `properties classes: ${[...propsClasses].join(", ") || "none found"}`, `light getters: ${source.lightGetters.map((getter) => `${getter.root ?? "*"}.${getter.name}${getter.descriptor}`).join(", ") || "none"}`);
|
|
292
|
+
const extraction = extractBlockLight(roots, source);
|
|
293
|
+
notes.push(...extraction.notes);
|
|
294
|
+
for (const id of options.trace ?? []) notes.push(extraction.describe(id));
|
|
295
|
+
const light = {};
|
|
296
|
+
for (const id of blockIds) {
|
|
297
|
+
if (NOT_BLOCKS.has(id)) continue;
|
|
298
|
+
const answer = extraction.lightOf(id);
|
|
299
|
+
if ("failure" in answer) failures.push({
|
|
300
|
+
id,
|
|
301
|
+
reason: answer.failure
|
|
302
|
+
});
|
|
303
|
+
else if (answer.light !== 0) light[id] = answer.light;
|
|
304
|
+
}
|
|
305
|
+
const modNamespaces = new Set(blockIds.map((id) => id.slice(0, id.indexOf(":"))).filter((namespace) => namespace !== "minecraft"));
|
|
306
|
+
if (modNamespaces.size === 1) {
|
|
307
|
+
const [namespace] = modNamespaces;
|
|
308
|
+
const listed = new Set(blockIds);
|
|
309
|
+
for (const [name, registration] of extraction.registrations) {
|
|
310
|
+
if (name.includes(":") || registration.vanilla || listed.has(`${namespace}:${name}`)) continue;
|
|
311
|
+
const answer = extraction.lightOf(`${namespace}:${name}`);
|
|
312
|
+
if ("light" in answer && answer.light !== 0) {
|
|
313
|
+
light[`${namespace}:${name}`] = answer.light;
|
|
314
|
+
notes.push(`${namespace}:${name} emits but ships no blockstate; kept from its registration alone`);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
light,
|
|
320
|
+
failures,
|
|
321
|
+
notes
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
/** `lit ? level : 0`, the shape a furnace, a redstone lamp or a campfire has. */
|
|
325
|
+
const lit = (level) => {
|
|
326
|
+
return {
|
|
327
|
+
default: 0,
|
|
328
|
+
cases: [{
|
|
329
|
+
when: { lit: "true" },
|
|
330
|
+
level
|
|
331
|
+
}]
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
/**
|
|
335
|
+
* Vanilla's emitters, transcribed from `Blocks.java` across 1.13 → 26.2 and checked against the wiki.
|
|
336
|
+
*
|
|
337
|
+
* By path, as a superset across versions: a block a version does not have never comes up in one of its
|
|
338
|
+
* worlds, and none of these levels has changed since the block was added. Cases are in the folded form
|
|
339
|
+
* the extractor emits — the commonest level as `default`, the rest as the fewest properties that
|
|
340
|
+
* decide them — so the two can be compared directly.
|
|
341
|
+
*/
|
|
342
|
+
const VANILLA_LIGHT = {
|
|
343
|
+
beacon: 15,
|
|
344
|
+
conduit: 15,
|
|
345
|
+
copper_lantern: 15,
|
|
346
|
+
exposed_copper_lantern: 15,
|
|
347
|
+
weathered_copper_lantern: 15,
|
|
348
|
+
oxidized_copper_lantern: 15,
|
|
349
|
+
waxed_copper_lantern: 15,
|
|
350
|
+
waxed_exposed_copper_lantern: 15,
|
|
351
|
+
waxed_weathered_copper_lantern: 15,
|
|
352
|
+
waxed_oxidized_copper_lantern: 15,
|
|
353
|
+
end_gateway: 15,
|
|
354
|
+
end_portal: 15,
|
|
355
|
+
fire: 15,
|
|
356
|
+
glowstone: 15,
|
|
357
|
+
jack_o_lantern: 15,
|
|
358
|
+
lantern: 15,
|
|
359
|
+
lava: 15,
|
|
360
|
+
lava_cauldron: 15,
|
|
361
|
+
ochre_froglight: 15,
|
|
362
|
+
pearlescent_froglight: 15,
|
|
363
|
+
sea_lantern: 15,
|
|
364
|
+
shroomlight: 15,
|
|
365
|
+
verdant_froglight: 15,
|
|
366
|
+
campfire: lit(15),
|
|
367
|
+
redstone_lamp: lit(15),
|
|
368
|
+
end_rod: 14,
|
|
369
|
+
torch: 14,
|
|
370
|
+
wall_torch: 14,
|
|
371
|
+
copper_torch: 14,
|
|
372
|
+
copper_wall_torch: 14,
|
|
373
|
+
cave_vines: {
|
|
374
|
+
default: 0,
|
|
375
|
+
cases: [{
|
|
376
|
+
when: { berries: "true" },
|
|
377
|
+
level: 14
|
|
378
|
+
}]
|
|
379
|
+
},
|
|
380
|
+
cave_vines_plant: {
|
|
381
|
+
default: 0,
|
|
382
|
+
cases: [{
|
|
383
|
+
when: { berries: "true" },
|
|
384
|
+
level: 14
|
|
385
|
+
}]
|
|
386
|
+
},
|
|
387
|
+
blast_furnace: lit(13),
|
|
388
|
+
furnace: lit(13),
|
|
389
|
+
smoker: lit(13),
|
|
390
|
+
nether_portal: 11,
|
|
391
|
+
crying_obsidian: 10,
|
|
392
|
+
soul_campfire: lit(10),
|
|
393
|
+
soul_fire: 10,
|
|
394
|
+
soul_lantern: 10,
|
|
395
|
+
soul_torch: 10,
|
|
396
|
+
soul_wall_torch: 10,
|
|
397
|
+
deepslate_redstone_ore: lit(9),
|
|
398
|
+
redstone_ore: lit(9),
|
|
399
|
+
enchanting_table: 7,
|
|
400
|
+
ender_chest: 7,
|
|
401
|
+
glow_lichen: {
|
|
402
|
+
default: 7,
|
|
403
|
+
cases: [{
|
|
404
|
+
when: {
|
|
405
|
+
down: "false",
|
|
406
|
+
east: "false",
|
|
407
|
+
north: "false",
|
|
408
|
+
south: "false",
|
|
409
|
+
up: "false",
|
|
410
|
+
west: "false"
|
|
411
|
+
},
|
|
412
|
+
level: 0
|
|
413
|
+
}]
|
|
414
|
+
},
|
|
415
|
+
redstone_torch: lit(7),
|
|
416
|
+
redstone_wall_torch: lit(7),
|
|
417
|
+
sculk_catalyst: 6,
|
|
418
|
+
amethyst_cluster: 5,
|
|
419
|
+
large_amethyst_bud: 4,
|
|
420
|
+
magma_block: 3,
|
|
421
|
+
medium_amethyst_bud: 2,
|
|
422
|
+
firefly_bush: 2,
|
|
423
|
+
brewing_stand: 1,
|
|
424
|
+
brown_mushroom: 1,
|
|
425
|
+
calibrated_sculk_sensor: 1,
|
|
426
|
+
dragon_egg: 1,
|
|
427
|
+
end_portal_frame: 1,
|
|
428
|
+
sculk_sensor: 1,
|
|
429
|
+
small_amethyst_bud: 1,
|
|
430
|
+
sea_pickle: {
|
|
431
|
+
default: 0,
|
|
432
|
+
cases: [
|
|
433
|
+
{
|
|
434
|
+
when: {
|
|
435
|
+
pickles: "1",
|
|
436
|
+
waterlogged: "true"
|
|
437
|
+
},
|
|
438
|
+
level: 6
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
when: {
|
|
442
|
+
pickles: "2",
|
|
443
|
+
waterlogged: "true"
|
|
444
|
+
},
|
|
445
|
+
level: 9
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
when: {
|
|
449
|
+
pickles: "3",
|
|
450
|
+
waterlogged: "true"
|
|
451
|
+
},
|
|
452
|
+
level: 12
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
when: {
|
|
456
|
+
pickles: "4",
|
|
457
|
+
waterlogged: "true"
|
|
458
|
+
},
|
|
459
|
+
level: 15
|
|
460
|
+
}
|
|
461
|
+
]
|
|
462
|
+
},
|
|
463
|
+
respawn_anchor: {
|
|
464
|
+
default: 0,
|
|
465
|
+
cases: [
|
|
466
|
+
{
|
|
467
|
+
when: { charges: "1" },
|
|
468
|
+
level: 3
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
when: { charges: "2" },
|
|
472
|
+
level: 7
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
when: { charges: "3" },
|
|
476
|
+
level: 11
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
when: { charges: "4" },
|
|
480
|
+
level: 15
|
|
481
|
+
}
|
|
482
|
+
]
|
|
483
|
+
},
|
|
484
|
+
light: {
|
|
485
|
+
default: 0,
|
|
486
|
+
cases: Array.from({ length: 15 }, (_, index) => {
|
|
487
|
+
return {
|
|
488
|
+
when: { level: String(index + 1) },
|
|
489
|
+
level: index + 1
|
|
490
|
+
};
|
|
491
|
+
})
|
|
492
|
+
},
|
|
493
|
+
copper_bulb: lit(15),
|
|
494
|
+
waxed_copper_bulb: lit(15),
|
|
495
|
+
exposed_copper_bulb: lit(12),
|
|
496
|
+
waxed_exposed_copper_bulb: lit(12),
|
|
497
|
+
weathered_copper_bulb: lit(8),
|
|
498
|
+
waxed_weathered_copper_bulb: lit(8),
|
|
499
|
+
oxidized_copper_bulb: lit(4),
|
|
500
|
+
waxed_oxidized_copper_bulb: lit(4),
|
|
501
|
+
trial_spawner: {
|
|
502
|
+
default: 8,
|
|
503
|
+
cases: [
|
|
504
|
+
{
|
|
505
|
+
when: { trial_spawner_state: "cooldown" },
|
|
506
|
+
level: 0
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
when: { trial_spawner_state: "inactive" },
|
|
510
|
+
level: 0
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
when: { trial_spawner_state: "waiting_for_players" },
|
|
514
|
+
level: 4
|
|
515
|
+
}
|
|
516
|
+
]
|
|
517
|
+
},
|
|
518
|
+
vault: {
|
|
519
|
+
default: 12,
|
|
520
|
+
cases: [{
|
|
521
|
+
when: { vault_state: "inactive" },
|
|
522
|
+
level: 6
|
|
523
|
+
}]
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
/**
|
|
527
|
+
* Where a block's light changed between versions: the table holds the current value, and a version at
|
|
528
|
+
* or below `until` gets the older one instead (`0` for a block that did not glow yet).
|
|
529
|
+
*/
|
|
530
|
+
const VANILLA_LIGHT_HISTORY = [{
|
|
531
|
+
path: "enchanting_table",
|
|
532
|
+
until: "1.17.1",
|
|
533
|
+
light: 0
|
|
534
|
+
}];
|
|
535
|
+
/** The vanilla table as it stood at a version: `VANILLA_LIGHT` with the history applied. */
|
|
536
|
+
function vanillaLightAt(version) {
|
|
537
|
+
const table = { ...VANILLA_LIGHT };
|
|
538
|
+
for (const change of VANILLA_LIGHT_HISTORY) if (compareVersions(version, change.until) <= 0) {
|
|
539
|
+
if (change.light === 0) delete table[change.path];
|
|
540
|
+
else table[change.path] = change.light;
|
|
541
|
+
}
|
|
542
|
+
return table;
|
|
543
|
+
}
|
|
544
|
+
for (const name of ["candle", ...[
|
|
545
|
+
"white",
|
|
546
|
+
"orange",
|
|
547
|
+
"magenta",
|
|
548
|
+
"light_blue",
|
|
549
|
+
"yellow",
|
|
550
|
+
"lime",
|
|
551
|
+
"pink",
|
|
552
|
+
"gray",
|
|
553
|
+
"light_gray",
|
|
554
|
+
"cyan",
|
|
555
|
+
"purple",
|
|
556
|
+
"blue",
|
|
557
|
+
"brown",
|
|
558
|
+
"green",
|
|
559
|
+
"red",
|
|
560
|
+
"black"
|
|
561
|
+
].map((colour) => `${colour}_candle`)]) {
|
|
562
|
+
VANILLA_LIGHT[name] = {
|
|
563
|
+
default: 0,
|
|
564
|
+
cases: [
|
|
565
|
+
1,
|
|
566
|
+
2,
|
|
567
|
+
3,
|
|
568
|
+
4
|
|
569
|
+
].map((candles) => {
|
|
570
|
+
return {
|
|
571
|
+
when: {
|
|
572
|
+
candles: String(candles),
|
|
573
|
+
lit: "true"
|
|
574
|
+
},
|
|
575
|
+
level: candles * 3
|
|
576
|
+
};
|
|
577
|
+
})
|
|
578
|
+
};
|
|
579
|
+
VANILLA_LIGHT[`${name}_cake`] = lit(3);
|
|
580
|
+
}
|
|
581
|
+
//#endregion
|
|
582
|
+
export { collectBlockLight as n, vanillaLightAt as r, VANILLA_LIGHT as t };
|