@nodecraft/rendermite 1.5.0 → 1.6.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 +2 -0
- package/dist/blocks/cli.js +1 -1
- package/dist/blocks/entities.d.ts +30 -0
- package/dist/blocks/entities.js +2 -0
- package/dist/blocks/entity-geo.d.ts +18 -0
- package/dist/blocks/entity-geo.js +2 -0
- package/dist/blocks/entity-models.d.ts +2 -0
- package/dist/blocks/entity-models.js +2 -0
- package/dist/blocks/export.d.ts +1 -1
- package/dist/blocks/export.js +1 -1
- package/dist/blocks/item-sprites.d.ts +1 -1
- package/dist/blocks/obj-mesh.js +1 -1
- package/dist/blocks/pack-format.d.ts +2 -2
- package/dist/blocks/sprite-sheet.d.ts +1 -1
- package/dist/classfile-BwaQjnUM.d.ts +51 -0
- package/dist/classfile-CAhS5qNf.js +255 -0
- package/dist/{cli-DMTaIbDI.js → cli-BSKnXBPr.js} +24 -4
- package/dist/cli.js +4 -4
- package/dist/entities/classfile.d.ts +2 -0
- package/dist/entities/classfile.js +2 -0
- package/dist/entities/geo-model.d.ts +13 -0
- package/dist/entities/geo-model.js +2 -0
- package/dist/entities/inherit.d.ts +29 -0
- package/dist/entities/inherit.js +2 -0
- package/dist/entities/jar-classes.d.ts +26 -0
- package/dist/entities/jar-classes.js +2 -0
- package/dist/entities/link.d.ts +71 -0
- package/dist/entities/link.js +2 -0
- package/dist/entities/mappings.d.ts +2 -0
- package/dist/entities/mappings.js +2 -0
- package/dist/entities/merge.d.ts +18 -0
- package/dist/entities/merge.js +2 -0
- package/dist/entities/model-bytecode.d.ts +31 -0
- package/dist/entities/model-bytecode.js +2 -0
- package/dist/entities/model-code.d.ts +2 -0
- package/dist/entities/model-code.js +2 -0
- package/dist/entities/model.d.ts +2 -0
- package/dist/entities/model.js +1 -0
- package/dist/entities-DvmA9fEV.js +175 -0
- package/dist/{entity-fallback-DutR9Hdz.js → entity-fallback-Bth0fa7m.js} +1 -1
- package/dist/entity-geo-B2CkuMWx.js +100 -0
- package/dist/entity-models-B6-0r2gR.js +123 -0
- package/dist/entity-models-c2HpfdWV.d.ts +49 -0
- package/dist/{export-wvt0Fryo.d.ts → export-D8fLC1wS.d.ts} +22 -1
- package/dist/{export-sadR0pWp.js → export-DT_drDoI.js} +93 -1
- package/dist/geo-CmjrXu0G.d.ts +59 -0
- package/dist/{geo-BC8jtG5Q.js → geo-Desq-gRC.js} +6 -5
- package/dist/geo-model-Bo1gpEwX.js +163 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -8
- package/dist/inherit-KkDsM2N_.js +77 -0
- package/dist/jar-classes-Bj15hjdr.js +75 -0
- package/dist/link-Bm5oQYh6.js +331 -0
- package/dist/mappings-BEPVaiwE.js +139 -0
- package/dist/mappings-DxX4KoZl.d.ts +75 -0
- package/dist/merge-3HkeQ_6A.js +46 -0
- package/dist/model-PnQ4gV0-.d.ts +49 -0
- package/dist/model-bytecode-C1nw2G8O.js +732 -0
- package/dist/model-code-B7jSFQqd.d.ts +16 -0
- package/dist/model-code-BKMEMXxF.js +49 -0
- package/dist/mods/prepare.js +1 -1
- package/dist/{obj-BaGvPUbb.js → obj-CJHEHpki.js} +5 -5
- package/dist/{obj-mesh-CazmV8ya.js → obj-mesh-JwPkZEWL.js} +1 -1
- package/dist/{pack-format-oRExIeD3.d.ts → pack-format-QcVFxG_b.d.ts} +100 -1
- package/dist/pack.d.ts +1 -1
- package/dist/{pipeline-AAfohoHU.js → pipeline-BUIMzMzg.js} +4 -4
- package/dist/render/entity-fallback.js +1 -1
- package/dist/render/face.js +1 -1
- package/dist/render/geo.d.ts +2 -54
- package/dist/render/geo.js +2 -2
- package/dist/render/obj.js +1 -1
- package/dist/render/pipeline.js +1 -1
- package/dist/render/renderer.js +1 -1
- package/dist/resources/descriptor.js +1 -1
- package/dist/{run-UKgVb-kW.js → run-BQJkqc5f.js} +2 -2
- package/dist/run.js +1 -1
- package/dist/versions/prepare.js +1 -1
- package/dist/workers/render-worker.js +2 -2
- package/package.json +1 -1
- package/dist/{descriptor-D7VKgeGT.js → descriptor-JyXhfd-c.js} +1 -1
- package/dist/{face-CMJ3b3Ae.js → face-B2JjWT46.js} +1 -1
- package/dist/{prepare-8LswFoS-.js → prepare-D49Gu4Lv.js} +1 -1
- package/dist/{prepare-B_tRy9lB.js → prepare-Myd_ec4v.js} +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { t as ClassFile } from "../classfile-BwaQjnUM.js";
|
|
2
|
+
import { t as Mappings } from "../mappings-DxX4KoZl.js";
|
|
3
|
+
//#region src/entities/link.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Entity types whose renderer or model is not named after them. Values are class simple names to look
|
|
6
|
+
* for; several may be listed because the game renamed the holder between versions — 1.21.4 folded
|
|
7
|
+
* `ChestedHorseRenderer` into `DonkeyRenderer`, and a pack is exported for both eras.
|
|
8
|
+
*/
|
|
9
|
+
declare const RENDERER_ALIASES: Readonly<Record<string, string | readonly string[]>>;
|
|
10
|
+
/**
|
|
11
|
+
* Entity types the game draws without a `ModelPart` hierarchy at all, so having no geometry for them
|
|
12
|
+
* is the right answer rather than a gap.
|
|
13
|
+
*
|
|
14
|
+
* Three kinds: things that draw a *block* or an *item* instead (a dropped stack, a falling block, a
|
|
15
|
+
* thrown egg), things the renderer draws with hand-built quads (an arrow, a painting, the end
|
|
16
|
+
* crystal's beam), and things that are deliberately invisible (a marker, the display entities).
|
|
17
|
+
*
|
|
18
|
+
* Listed rather than inferred so that a version adding a mob cannot slip through as "no model": the
|
|
19
|
+
* coverage test fails on any type that is neither linked nor named here.
|
|
20
|
+
*/
|
|
21
|
+
declare const NO_MODEL: ReadonlySet<string>;
|
|
22
|
+
interface LinkContext {
|
|
23
|
+
mappings: Mappings;
|
|
24
|
+
/** Reads a class by its obfuscated name, or `null` when the jar has no such class. */
|
|
25
|
+
read: (obfuscated: string) => Promise<ClassFile | null>;
|
|
26
|
+
/** Whether this class actually yields geometry, used to skip abstract bases. */
|
|
27
|
+
hasGeometry: (obfuscated: string) => Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* `buildModelNameIndex` over the jar being exported, for a mod whose classes follow none of
|
|
30
|
+
* vanilla's naming. Absent for a vanilla-only link, where the named routes are exact and a fuzzy
|
|
31
|
+
* one would only ever be wrong.
|
|
32
|
+
*/
|
|
33
|
+
nameIndex?: Map<string, string[]>;
|
|
34
|
+
}
|
|
35
|
+
interface EntityModelLink {
|
|
36
|
+
/** Deobfuscated class name, for diagnostics and for the pack's model id. */
|
|
37
|
+
className: string;
|
|
38
|
+
/** The class to actually read the bytecode of. */
|
|
39
|
+
obfuscated: string;
|
|
40
|
+
/** How it was found, so a report can say how much rests on the curated table. */
|
|
41
|
+
route: 'model-name' | 'renderer' | 'alias' | 'mod-name';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* A class's simple name reduced to the entity it is about.
|
|
45
|
+
*
|
|
46
|
+
* `Lionfish_Model`, `ModelLionfish`, `LionfishEntityModel` and `lionfish` all come out `lionfish`.
|
|
47
|
+
* Mods put the marker word at either end and sometimes both — `Modelcutter` is the old MCP style,
|
|
48
|
+
* `Lionfish_Model` the modern one — so the strip repeats until nothing more comes off. Separators go
|
|
49
|
+
* too, because a mod's class is `Deepling_Brute_Model` where its entity is `deepling_brute`.
|
|
50
|
+
*/
|
|
51
|
+
declare function normaliseModelName(name: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Normalised name → the classes that could be it, for a jar whose classes are not named the way
|
|
54
|
+
* vanilla's are.
|
|
55
|
+
*
|
|
56
|
+
* Built once per jar because the alternative is scanning three thousand classes per entity. Nested
|
|
57
|
+
* classes are indexed under the part after the `$` as well: occultism files a familiar's head as
|
|
58
|
+
* `HeadlessFamiliarRenderer$CthulhuHeadModel`, and only the tail of that names the mob.
|
|
59
|
+
*/
|
|
60
|
+
declare function buildModelNameIndex(internalNames: Iterable<string>): Map<string, string[]>;
|
|
61
|
+
/**
|
|
62
|
+
* The class holding one entity's geometry, or `null` when nothing links.
|
|
63
|
+
*
|
|
64
|
+
* A candidate is only accepted if it *yields geometry*: a renderer chain can land on an abstract base
|
|
65
|
+
* that names no boxes, and returning that would report coverage the pack does not have.
|
|
66
|
+
*/
|
|
67
|
+
declare function linkEntityModel(entityId: string, context: LinkContext): Promise<EntityModelLink | null>;
|
|
68
|
+
/** Reads and caches classes out of an extracted jar directory or a jar reader. */
|
|
69
|
+
declare function classReader(load: (obfuscated: string) => Promise<Uint8Array | null>): LinkContext['read'];
|
|
70
|
+
//#endregion
|
|
71
|
+
export { EntityModelLink, LinkContext, NO_MODEL, RENDERER_ALIASES, buildModelNameIndex, classReader, linkEntityModel, normaliseModelName };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as linkEntityModel, i as classReader, n as RENDERER_ALIASES, o as normaliseModelName, r as buildModelNameIndex, t as NO_MODEL } from "../link-Bm5oQYh6.js";
|
|
2
|
+
export { NO_MODEL, RENDERER_ALIASES, buildModelNameIndex, classReader, linkEntityModel, normaliseModelName };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as isObfuscated, c as resolveMappings, i as identityMappings, n as MappingsUnavailableError, o as loadMappings, r as downloadClientMappings, s as parseMappings, t as Mappings } from "../mappings-DxX4KoZl.js";
|
|
2
|
+
export { Mappings, MappingsUnavailableError, downloadClientMappings, identityMappings, isObfuscated, loadMappings, parseMappings, resolveMappings };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as loadMappings, i as isObfuscated, n as downloadClientMappings, o as parseMappings, r as identityMappings, s as resolveMappings, t as MappingsUnavailableError } from "../mappings-BEPVaiwE.js";
|
|
2
|
+
export { MappingsUnavailableError, downloadClientMappings, identityMappings, isObfuscated, loadMappings, parseMappings, resolveMappings };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { i as Part, r as ExtractedModel } from "../model-PnQ4gV0-.js";
|
|
2
|
+
//#region src/entities/merge.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Where a piece's parts sit: at the top, or inside the one anonymous root a `MeshDefinition` makes.
|
|
5
|
+
* Both merge into the same list of named slots and only one of them is nested.
|
|
6
|
+
*/
|
|
7
|
+
declare function slotsOf(parts: Part[]): {
|
|
8
|
+
container: Part | null;
|
|
9
|
+
slots: Part[];
|
|
10
|
+
};
|
|
11
|
+
/** True when this piece built its own mesh rather than adding to one it was handed. */
|
|
12
|
+
declare function buildsOwnMesh(model: ExtractedModel): boolean;
|
|
13
|
+
/** `base` plus the parts of `extra` that `base` does not name. */
|
|
14
|
+
declare function mergeModels(base: ExtractedModel, extra: ExtractedModel): ExtractedModel;
|
|
15
|
+
declare function countCubes(part: Part): number;
|
|
16
|
+
declare function cubesOf(model: ExtractedModel): number;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { buildsOwnMesh, countCubes, cubesOf, mergeModels, slotsOf };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { r as ExtractedModel } from "../model-PnQ4gV0-.js";
|
|
2
|
+
import { t as ClassFile } from "../classfile-BwaQjnUM.js";
|
|
3
|
+
//#region src/entities/model-bytecode.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Extracts whatever geometry a class's methods build.
|
|
6
|
+
*
|
|
7
|
+
* Every method is tried, because where the model is built differs by era: 1.17+ vanilla uses a static
|
|
8
|
+
* factory, older vanilla and most mods use the constructor. The one that built the fullest *mesh* is
|
|
9
|
+
* the model, and the methods that only add to a mesh handed to them are merged into it:
|
|
10
|
+
* `QuadrupedModel` puts its head and body in `createBodyMesh` and its four legs in `createLegs`, so
|
|
11
|
+
* picking a single winner silently drops whichever it did not pick.
|
|
12
|
+
*
|
|
13
|
+
* Only those helpers merge, never two methods that each built their own mesh. Those are variants of the
|
|
14
|
+
* same model, a boat and a chest boat, and folding them together puts a chest on every boat.
|
|
15
|
+
*/
|
|
16
|
+
/** What a call may be followed into, and whether this class is the sort that makes such calls. */
|
|
17
|
+
interface Following {
|
|
18
|
+
/** The class a call names, or null for the builder API and everything else off limits. */
|
|
19
|
+
resolve: (internalName: string) => ClassFile | null;
|
|
20
|
+
/**
|
|
21
|
+
* Whether the class being extracted is model code. A renderer is not: it constructs the models it
|
|
22
|
+
* draws, so following those calls makes it look like it holds their geometry, and a pufferfish
|
|
23
|
+
* renderer that builds all three sizes comes out as one model with three sets of spines.
|
|
24
|
+
*
|
|
25
|
+
* Defaults to true, which is right when the caller has no way to tell the two apart.
|
|
26
|
+
*/
|
|
27
|
+
isModelCode?: boolean;
|
|
28
|
+
}
|
|
29
|
+
declare function extractModel(bytes: Uint8Array, following?: Following): ExtractedModel;
|
|
30
|
+
//#endregion
|
|
31
|
+
export { Following, extractModel };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { n as ResourceLocator } from "./locator-COMEtEn9.js";
|
|
2
|
+
import { l as findMobTexture, u as tokens } from "./entity-textures-DFRIVdF1.js";
|
|
3
|
+
//#region src/blocks/entities.ts
|
|
4
|
+
/**
|
|
5
|
+
* The entity registry, and one colour per entity.
|
|
6
|
+
*
|
|
7
|
+
* A world viewer has no geometry for a mob — nothing in any jar does, at any version, because entity
|
|
8
|
+
* models are Java code — so what it draws is a box the size of the hitbox. Two things make that box
|
|
9
|
+
* worth looking at rather than merely present: the entity's *name*, so a reader knows what it is, and
|
|
10
|
+
* the entity's *colour*, so a pen of cows does not read as the same grey mass as the shoal beside it.
|
|
11
|
+
*
|
|
12
|
+
* Both come out of the assets today, with no geometry decision made:
|
|
13
|
+
*
|
|
14
|
+
* - The registry is the lang file. `entity.<ns>.<name>` is written per registered entity type by
|
|
15
|
+
* datagen, so it is the closest thing a jar carries to the entity registry — the same trick the item
|
|
16
|
+
* sheet uses to tell a real item from a template model.
|
|
17
|
+
* - The colour is the mean of the opaque pixels of the entity's own texture sheet. Crude on purpose:
|
|
18
|
+
* it is a marker's tint, not a render, and a mean is the one summary that cannot be wrong about
|
|
19
|
+
* which mob it came from.
|
|
20
|
+
*
|
|
21
|
+
* `texture` is carried alongside so that whatever eventually draws real entity geometry has the
|
|
22
|
+
* id → sheet mapping already resolved, which is the fiddly half of the problem.
|
|
23
|
+
*/
|
|
24
|
+
/** `entity.<ns>.<name>`, and only the plain ones: a deeper key is a subtitle or a variant label. */
|
|
25
|
+
const ENTITY_KEY_SEGMENTS = 3;
|
|
26
|
+
/**
|
|
27
|
+
* Texture names filed under a mob's own directory that are not the mob.
|
|
28
|
+
*
|
|
29
|
+
* A llama's directory holds `spit`, which is the projectile it throws; a horse's holds its armour, a
|
|
30
|
+
* cat's its collar, a happy ghast's its harness. Those score exactly as well on name overlap as the
|
|
31
|
+
* body sheet does — `entity/llama/spit` and `entity/llama/creamy` both name the llama and sit at the
|
|
32
|
+
* same depth — so without this the tie breaks arbitrarily and a llama comes out the colour of spit.
|
|
33
|
+
*
|
|
34
|
+
* Overlays are here for the same reason: a blink or an eye layer is a few opaque pixels whose mean
|
|
35
|
+
* says nothing about the animal.
|
|
36
|
+
*/
|
|
37
|
+
const NOT_THE_MOB = /* @__PURE__ */ new Set([
|
|
38
|
+
"spit",
|
|
39
|
+
"armor",
|
|
40
|
+
"saddle",
|
|
41
|
+
"collar",
|
|
42
|
+
"harness",
|
|
43
|
+
"chest",
|
|
44
|
+
"overlay",
|
|
45
|
+
"blink",
|
|
46
|
+
"eyes",
|
|
47
|
+
"markings",
|
|
48
|
+
"glow"
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Alpha at or below which a pixel does not count toward the mean.
|
|
52
|
+
*
|
|
53
|
+
* Not zero: mob sheets are full of near-transparent antialiasing at the edges of a part, and letting
|
|
54
|
+
* those in drags every colour toward whatever the sheet's background happens to be.
|
|
55
|
+
*/
|
|
56
|
+
const OPAQUE_ALPHA = 128;
|
|
57
|
+
/** The mean of a texture's opaque pixels as `#rrggbb`, or `null` when it has none. */
|
|
58
|
+
function meanColor(texture) {
|
|
59
|
+
let red = 0;
|
|
60
|
+
let green = 0;
|
|
61
|
+
let blue = 0;
|
|
62
|
+
let counted = 0;
|
|
63
|
+
for (let offset = 0; offset < texture.data.length; offset += 4) {
|
|
64
|
+
if (texture.data[offset + 3] <= OPAQUE_ALPHA) continue;
|
|
65
|
+
red += texture.data[offset];
|
|
66
|
+
green += texture.data[offset + 1];
|
|
67
|
+
blue += texture.data[offset + 2];
|
|
68
|
+
counted++;
|
|
69
|
+
}
|
|
70
|
+
if (counted === 0) return null;
|
|
71
|
+
return `#${[
|
|
72
|
+
red,
|
|
73
|
+
green,
|
|
74
|
+
blue
|
|
75
|
+
].map((channel) => Math.round(channel / counted).toString(16).padStart(2, "0")).join("")}`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Every entity type a namespace registers, by its lang keys.
|
|
79
|
+
*
|
|
80
|
+
* Keys with more segments than `<prefix>.<ns>.<name>` are dropped: those are a variant's own label
|
|
81
|
+
* (`entity.minecraft.villager.farmer`) or a death message, never a type.
|
|
82
|
+
*/
|
|
83
|
+
async function entityTypesOf(resources, namespace) {
|
|
84
|
+
const raw = await resources.getResource("lang", new ResourceLocator(namespace, "en_us"));
|
|
85
|
+
if (!raw) return [];
|
|
86
|
+
let lang;
|
|
87
|
+
try {
|
|
88
|
+
lang = JSON.parse(raw.toString("utf8"));
|
|
89
|
+
} catch {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
const prefix = `entity.${namespace}.`;
|
|
93
|
+
const types = [];
|
|
94
|
+
for (const [key, value] of Object.entries(lang)) {
|
|
95
|
+
if (!key.startsWith(prefix) || key.split(".").length !== ENTITY_KEY_SEGMENTS || typeof value !== "string") continue;
|
|
96
|
+
types.push({
|
|
97
|
+
id: `${namespace}:${key.slice(prefix.length)}`,
|
|
98
|
+
name: value
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return types;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A mob's sheet where the conventional paths do not have it.
|
|
105
|
+
*
|
|
106
|
+
* `findMobTexture` wants a file named exactly after the entity, which is how most of them are filed.
|
|
107
|
+
* The rest are named after a *variant* — a rabbit is `entity/rabbit/brown`, a horse is
|
|
108
|
+
* `entity/horse/horse_white`, an elder guardian is `entity/guardian/guardian_elder` — so this falls
|
|
109
|
+
* back to the best token overlap under `entity/`, preferring a shallower path so a family's plain
|
|
110
|
+
* sheet beats its armour or blink overlay.
|
|
111
|
+
*
|
|
112
|
+
* Restricted to `entity/` deliberately. Widening it finds a *block* texture with a matching word for
|
|
113
|
+
* anything that has no sheet at all, and a lectern-coloured marker is worse than no colour.
|
|
114
|
+
*
|
|
115
|
+
* The name is also matched with its underscores squashed out, which is a real vanilla convention and
|
|
116
|
+
* not a guess: an armour stand is filed under `entity/armorstand/` and a magma cube is
|
|
117
|
+
* `entity/slime/magmacube`, and neither shares a single token with its own id.
|
|
118
|
+
*/
|
|
119
|
+
async function findVariantTexture(resources, namespace, name) {
|
|
120
|
+
const wanted = tokens(name);
|
|
121
|
+
const squashed = name.replaceAll("_", "");
|
|
122
|
+
let best = null;
|
|
123
|
+
let bestScore = 0;
|
|
124
|
+
for (const locator of await resources.listResources("texture", namespace)) {
|
|
125
|
+
if (!locator.path.startsWith("entity/")) continue;
|
|
126
|
+
const leaf = locator.path.split("/").at(-1) ?? "";
|
|
127
|
+
if (tokens(leaf).some((token) => NOT_THE_MOB.has(token))) continue;
|
|
128
|
+
const candidate = tokens(locator.path);
|
|
129
|
+
let score = 0;
|
|
130
|
+
for (const token of wanted) if (candidate.includes(token)) score += 2;
|
|
131
|
+
if (squashed !== name && candidate.includes(squashed)) score += wanted.length * 2;
|
|
132
|
+
if (score === 0) continue;
|
|
133
|
+
const depth = locator.path.split("/").length;
|
|
134
|
+
score = score * 10 - depth - candidate.length;
|
|
135
|
+
if (score > bestScore) {
|
|
136
|
+
best = locator;
|
|
137
|
+
bestScore = score;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return best;
|
|
141
|
+
}
|
|
142
|
+
async function collectEntities(options) {
|
|
143
|
+
const { resources, textures } = options;
|
|
144
|
+
const entities = {};
|
|
145
|
+
const failures = [];
|
|
146
|
+
const types = [];
|
|
147
|
+
for (const namespace of options.namespaces) types.push(...await entityTypesOf(resources, namespace));
|
|
148
|
+
let done = 0;
|
|
149
|
+
for (const type of types.toSorted((left, right) => left.id.localeCompare(right.id))) {
|
|
150
|
+
options.onProgress?.(++done, types.length);
|
|
151
|
+
const [namespace, name] = [type.id.slice(0, type.id.indexOf(":")), type.id.slice(type.id.indexOf(":") + 1)];
|
|
152
|
+
const entry = { name: type.name };
|
|
153
|
+
const locator = await findMobTexture(resources, namespace, name) ?? await findVariantTexture(resources, namespace, name);
|
|
154
|
+
if (locator) {
|
|
155
|
+
const texture = await textures.load(locator).catch(() => null);
|
|
156
|
+
const colour = texture ? meanColor(texture) : null;
|
|
157
|
+
entry.texture = locator.toString();
|
|
158
|
+
if (colour) entry.color = colour;
|
|
159
|
+
else failures.push({
|
|
160
|
+
id: type.id,
|
|
161
|
+
reason: `texture ${locator} has no opaque pixels; exported without a colour`
|
|
162
|
+
});
|
|
163
|
+
} else failures.push({
|
|
164
|
+
id: type.id,
|
|
165
|
+
reason: "no entity texture found; exported without a colour"
|
|
166
|
+
});
|
|
167
|
+
entities[type.id] = entry;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
entities,
|
|
171
|
+
failures
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { entityTypesOf as n, collectEntities as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as ResourceLocator } from "./locator-COMEtEn9.js";
|
|
2
2
|
import { a as findChestTexture, i as findBedTexture, l as findMobTexture, n as HEAD_LAYOUTS, o as findEntityTexture, r as coversSheet, t as DYE_COLORS, u as tokens } from "./entity-textures-DFRIVdF1.js";
|
|
3
|
-
import { c as renderModel } from "./obj-
|
|
3
|
+
import { c as renderModel } from "./obj-CJHEHpki.js";
|
|
4
4
|
//#region src/render/entity-fallback.ts
|
|
5
5
|
/**
|
|
6
6
|
* Heuristic rendering for mod items that parent `builtin/entity` — chests,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { n as ResourceLocator } from "./locator-COMEtEn9.js";
|
|
2
|
+
import { t as geoToModel } from "./geo-model-Bo1gpEwX.js";
|
|
3
|
+
import { i as parseGeo, r as nameTokens } from "./geo-Desq-gRC.js";
|
|
4
|
+
//#region src/blocks/entity-geo.ts
|
|
5
|
+
/**
|
|
6
|
+
* Finding the `.geo.json` a GeckoLib entity is drawn from.
|
|
7
|
+
*
|
|
8
|
+
* The registration is in Java — `GeoModel.getModelResource` returns a resource location — so there is
|
|
9
|
+
* no data to read it out of, and this is a search by name. The same problem `findGeoForItem` solves for
|
|
10
|
+
* items, with the directories a *mob* is filed under instead: `geo/entity/` if the mod is tidy, a flat
|
|
11
|
+
* `geo/` if not (Creeper Overhaul, Ars Nouveau and Aquatic Frontiers all are).
|
|
12
|
+
*
|
|
13
|
+
* The search never leaves a `geo` directory, and every rule below is a name test rather than a
|
|
14
|
+
* similarity score, so the worst case is finding nothing rather than drawing a mob as another one.
|
|
15
|
+
*/
|
|
16
|
+
/** Where a mod keeps entity geometry, in the order worth trying. */
|
|
17
|
+
const GEO_DIRECTORIES = [
|
|
18
|
+
"geo/entity",
|
|
19
|
+
"geo",
|
|
20
|
+
"geckolib/geo/entity",
|
|
21
|
+
"geckolib/geo",
|
|
22
|
+
"geo/entities"
|
|
23
|
+
];
|
|
24
|
+
/** A geo path under some `geo` directory, for the scan that runs when the conventional spots miss. */
|
|
25
|
+
const UNDER_GEO = /(?:^|\/)(?:geo|geckolib\/geo)\//;
|
|
26
|
+
/**
|
|
27
|
+
* The geometry for one entity, or null when the mod does not ship any under that name.
|
|
28
|
+
*
|
|
29
|
+
* `namespace` and `name` come from the entity id, so `occultism:afrit` looks for
|
|
30
|
+
* `assets/occultism/geo/entity/afrit.geo.json` and its neighbours. Nothing is inferred from the
|
|
31
|
+
* *class* here: a GeckoLib mob has no model class to inspect.
|
|
32
|
+
*/
|
|
33
|
+
async function findGeoForEntity(resources, namespace, name) {
|
|
34
|
+
const paths = GEO_DIRECTORIES.map((directory) => `${directory}/${name}.geo.json`);
|
|
35
|
+
for (const path of paths) {
|
|
36
|
+
const found = await load(resources, namespace, path);
|
|
37
|
+
if (found) return found;
|
|
38
|
+
}
|
|
39
|
+
const files = (await resources.listResources("raw", namespace)).map((candidate) => candidate.path).filter((path) => path.endsWith(".geo.json") && UNDER_GEO.test(path)).toSorted((left, right) => left.length - right.length);
|
|
40
|
+
for (const path of ranked(files, name)) {
|
|
41
|
+
const found = await load(resources, namespace, path);
|
|
42
|
+
if (found) return found;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const baseName = (path) => path.split("/").pop()?.replace(/\.geo\.json$/, "") ?? "";
|
|
47
|
+
/**
|
|
48
|
+
* The geo files that could be this entity's, best first.
|
|
49
|
+
*
|
|
50
|
+
* A mod's entity id and its geometry's filename agree less often than you would hope, and they
|
|
51
|
+
* disagree in three shapes, each of which is here because a real mod does it:
|
|
52
|
+
*
|
|
53
|
+
* 1. **The same name spelled differently.** Ars Nouveau registers `bookwyrm` and ships
|
|
54
|
+
* `book_wyrm.geo.json`.
|
|
55
|
+
* 2. **A decorated entity, the plain model.** `familiar_drygmy` is drawn by `drygmy`, and
|
|
56
|
+
* `marid_unbound` by `marid`. So a geo whose every token appears in the entity's name qualifies,
|
|
57
|
+
* most specific first — a subset test rather than a similarity score, so an unrelated model can
|
|
58
|
+
* never win.
|
|
59
|
+
* 3. **A plain entity, a decorated model.** Occultism registers `foliot` and ships only its jobs:
|
|
60
|
+
* `foliot_worker`, `foliot_crusher`, `foliot_lumberjack`. Any of them is a foliot, so the shortest
|
|
61
|
+
* wins. Last, because this is the one direction that is a guess.
|
|
62
|
+
*/
|
|
63
|
+
function ranked(files, name) {
|
|
64
|
+
const flattened = name.replaceAll("_", "");
|
|
65
|
+
const wanted = new Set(nameTokens(name));
|
|
66
|
+
const exact = files.filter((path) => baseName(path) === name);
|
|
67
|
+
const spelling = files.filter((path) => baseName(path).replaceAll("_", "") === flattened);
|
|
68
|
+
const decorated = files.filter((path) => {
|
|
69
|
+
const tokens = nameTokens(baseName(path));
|
|
70
|
+
return tokens.length > 0 && tokens.every((token) => wanted.has(token));
|
|
71
|
+
}).toSorted((left, right) => nameTokens(baseName(right)).length - nameTokens(baseName(left)).length);
|
|
72
|
+
const variants = files.filter((path) => {
|
|
73
|
+
const tokens = new Set(nameTokens(baseName(path)));
|
|
74
|
+
return [...wanted].every((token) => tokens.has(token));
|
|
75
|
+
});
|
|
76
|
+
return [.../* @__PURE__ */ new Set([
|
|
77
|
+
...exact,
|
|
78
|
+
...spelling,
|
|
79
|
+
...decorated,
|
|
80
|
+
...variants
|
|
81
|
+
])];
|
|
82
|
+
}
|
|
83
|
+
async function load(resources, namespace, path) {
|
|
84
|
+
const raw = await resources.getResource("raw", new ResourceLocator(namespace, path));
|
|
85
|
+
if (!raw) return null;
|
|
86
|
+
let model;
|
|
87
|
+
try {
|
|
88
|
+
const geo = parseGeo(JSON.parse(raw.toString("utf8")));
|
|
89
|
+
if (!geo) return null;
|
|
90
|
+
model = geoToModel(geo);
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return model.parts.length > 0 ? {
|
|
95
|
+
id: `${namespace}:${path}`,
|
|
96
|
+
model
|
|
97
|
+
} : null;
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { findGeoForEntity as t };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { t as findGeoForEntity } from "./entity-geo-B2CkuMWx.js";
|
|
2
|
+
import { n as extractInheritedModel } from "./inherit-KkDsM2N_.js";
|
|
3
|
+
import { a as linkEntityModel, i as classReader, r as buildModelNameIndex } from "./link-Bm5oQYh6.js";
|
|
4
|
+
import { s as resolveMappings, t as MappingsUnavailableError } from "./mappings-BEPVaiwE.js";
|
|
5
|
+
import { n as loadModelCode } from "./model-code-BKMEMXxF.js";
|
|
6
|
+
//#region src/blocks/entity-models.ts
|
|
7
|
+
/**
|
|
8
|
+
* Entity geometry for the pack: link each entity to the class that builds it, read that class's
|
|
9
|
+
* bytecode, and stitch the sheets those models are drawn with.
|
|
10
|
+
*
|
|
11
|
+
* The pieces all live in `src/entities/` — this is the part that knows what a *pack* wants. Which is
|
|
12
|
+
* mostly two things the extractor does not care about: several entities share one model (every zombie
|
|
13
|
+
* in the game is a `HumanoidModel`), so models are keyed by class and pointed at rather than copied;
|
|
14
|
+
* and the `uv` values in a model address the entity's own sheet, so those sheets have to be in an
|
|
15
|
+
* atlas the consumer can sample.
|
|
16
|
+
*
|
|
17
|
+
* Needs the *code*, not just the assets. An `--assets` folder has none, and a pack exported from one
|
|
18
|
+
* carries no entity geometry — which is the honest answer rather than a silent gap.
|
|
19
|
+
*/
|
|
20
|
+
function countCubes(part) {
|
|
21
|
+
return part.cubes.length + part.children.reduce((sum, child) => sum + countCubes(child), 0);
|
|
22
|
+
}
|
|
23
|
+
/** Drops the internal shape into the pack's, omitting what is at its default. */
|
|
24
|
+
function toPackPart(part) {
|
|
25
|
+
return {
|
|
26
|
+
name: part.name,
|
|
27
|
+
pivot: [...part.pivot],
|
|
28
|
+
rotation: [...part.rotation],
|
|
29
|
+
cubes: part.cubes.map((cube) => {
|
|
30
|
+
return {
|
|
31
|
+
origin: [...cube.origin],
|
|
32
|
+
size: [...cube.size],
|
|
33
|
+
uv: [...cube.uv],
|
|
34
|
+
...cube.inflate !== 0 && { inflate: cube.inflate },
|
|
35
|
+
...cube.mirror && { mirror: true },
|
|
36
|
+
...cube.faces && { faces: cube.faces }
|
|
37
|
+
};
|
|
38
|
+
}),
|
|
39
|
+
children: part.children.map((child) => toPackPart(child))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Recovers geometry for as many of `entityIds` as the code describes.
|
|
44
|
+
*
|
|
45
|
+
* Mappings are resolved once: downloaded for an obfuscated jar, and the identity for one that is not
|
|
46
|
+
* (from the 26 line the client ships in the clear, and a mod jar never was obfuscated). A version that
|
|
47
|
+
* is obfuscated *and* unmapped — 1.13.x — yields nothing, with the reason recorded once rather than
|
|
48
|
+
* per entity.
|
|
49
|
+
*/
|
|
50
|
+
async function collectEntityGeometry(options) {
|
|
51
|
+
const { classes, entityIds } = options;
|
|
52
|
+
const models = {};
|
|
53
|
+
const modelOf = /* @__PURE__ */ new Map();
|
|
54
|
+
const spriteIds = /* @__PURE__ */ new Set();
|
|
55
|
+
const failures = [];
|
|
56
|
+
const names = await classes.list();
|
|
57
|
+
let mappings;
|
|
58
|
+
try {
|
|
59
|
+
({mappings} = await resolveMappings(options.version, options.cacheDir, async () => names));
|
|
60
|
+
} catch (err) {
|
|
61
|
+
failures.push({
|
|
62
|
+
id: options.version,
|
|
63
|
+
reason: err instanceof MappingsUnavailableError ? `no entity geometry: ${err.message}` : `no entity geometry: mappings could not be loaded (${err.message})`
|
|
64
|
+
});
|
|
65
|
+
return {
|
|
66
|
+
models,
|
|
67
|
+
modelOf,
|
|
68
|
+
spriteIds,
|
|
69
|
+
failures
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const code = await loadModelCode(names, async (internal) => await classes.read(internal), (internal) => mappings.originalOf.get(internal) ?? internal);
|
|
73
|
+
const extracted = /* @__PURE__ */ new Map();
|
|
74
|
+
const geometryOf = async (internal) => {
|
|
75
|
+
if (!extracted.has(internal)) extracted.set(internal, await extractInheritedModel(internal, async (name) => await classes.read(name), code));
|
|
76
|
+
return extracted.get(internal) ?? null;
|
|
77
|
+
};
|
|
78
|
+
const context = {
|
|
79
|
+
mappings,
|
|
80
|
+
read: classReader(async (internal) => await classes.read(internal)),
|
|
81
|
+
hasGeometry: async (internal) => {
|
|
82
|
+
const model = await geometryOf(internal);
|
|
83
|
+
return model !== null && model.parts.reduce((sum, part) => sum + countCubes(part), 0) > 0;
|
|
84
|
+
},
|
|
85
|
+
nameIndex: buildModelNameIndex(names)
|
|
86
|
+
};
|
|
87
|
+
const geoFor = async (entityId) => {
|
|
88
|
+
if (!options.resources) return null;
|
|
89
|
+
const [namespace, name] = entityId.includes(":") ? entityId.split(":") : ["minecraft", entityId];
|
|
90
|
+
return await findGeoForEntity(options.resources, namespace, name);
|
|
91
|
+
};
|
|
92
|
+
let done = 0;
|
|
93
|
+
for (const entityId of [...entityIds].toSorted((left, right) => left.localeCompare(right))) {
|
|
94
|
+
options.onProgress?.(++done, entityIds.length);
|
|
95
|
+
const link = await linkEntityModel(entityId, context);
|
|
96
|
+
const found = link ? {
|
|
97
|
+
id: link.className,
|
|
98
|
+
model: await geometryOf(link.obfuscated)
|
|
99
|
+
} : await geoFor(entityId);
|
|
100
|
+
if (!found?.model) continue;
|
|
101
|
+
const { id: modelId, model } = found;
|
|
102
|
+
if (!models[modelId]) models[modelId] = {
|
|
103
|
+
textureSize: [...model.textureSize],
|
|
104
|
+
parts: model.parts.map((part) => toPackPart(part)),
|
|
105
|
+
...model.notes.length > 0 && { notes: [...model.notes] }
|
|
106
|
+
};
|
|
107
|
+
modelOf.set(entityId, modelId);
|
|
108
|
+
const texture = options.textureOf.get(entityId);
|
|
109
|
+
if (texture) spriteIds.add(texture);
|
|
110
|
+
else failures.push({
|
|
111
|
+
id: entityId,
|
|
112
|
+
reason: `geometry recovered from ${modelId} but no entity texture resolved`
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
models,
|
|
117
|
+
modelOf,
|
|
118
|
+
spriteIds,
|
|
119
|
+
failures
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
export { collectEntityGeometry as t };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { a as ResourceManager } from "./manager-exOIcdyx.js";
|
|
2
|
+
import { S as EntityPackModel } from "./pack-format-QcVFxG_b.js";
|
|
3
|
+
//#region src/blocks/entity-models.d.ts
|
|
4
|
+
/** What the exporter needs to read code out of; a jar, in practice. */
|
|
5
|
+
interface ClassSource {
|
|
6
|
+
list: () => Promise<string[]>;
|
|
7
|
+
read: (internalName: string) => Promise<Uint8Array | null>;
|
|
8
|
+
}
|
|
9
|
+
interface EntityGeometryResult {
|
|
10
|
+
/** Model id (the class it came from) → geometry. */
|
|
11
|
+
models: Record<string, EntityPackModel>;
|
|
12
|
+
/** Entity id → model id, for the entity table to point with. */
|
|
13
|
+
modelOf: Map<string, string>;
|
|
14
|
+
/** Entity sheets the models address, to be stitched into the pack's own atlas. */
|
|
15
|
+
spriteIds: Set<string>;
|
|
16
|
+
failures: Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
interface CollectEntityGeometryOptions {
|
|
22
|
+
version: string;
|
|
23
|
+
/** Where mappings cache; the same directory the jars do. */
|
|
24
|
+
cacheDir: string;
|
|
25
|
+
classes: ClassSource;
|
|
26
|
+
/** Entity ids to look for, as the registry named them. */
|
|
27
|
+
entityIds: readonly string[];
|
|
28
|
+
/** The sheet each entity resolved to, from the entity registry, keyed by entity id. */
|
|
29
|
+
textureOf: ReadonlyMap<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* The assets, for the mods whose geometry is not in code at all.
|
|
32
|
+
*
|
|
33
|
+
* A GeckoLib mob ships Blockbench's bedrock format and registers it by resource location, so there
|
|
34
|
+
* is nothing for the interpreter to read; `entity-geo.ts` finds and converts it instead.
|
|
35
|
+
*/
|
|
36
|
+
resources?: ResourceManager;
|
|
37
|
+
onProgress?: (done: number, total: number) => void;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Recovers geometry for as many of `entityIds` as the code describes.
|
|
41
|
+
*
|
|
42
|
+
* Mappings are resolved once: downloaded for an obfuscated jar, and the identity for one that is not
|
|
43
|
+
* (from the 26 line the client ships in the clear, and a mod jar never was obfuscated). A version that
|
|
44
|
+
* is obfuscated *and* unmapped — 1.13.x — yields nothing, with the reason recorded once rather than
|
|
45
|
+
* per entity.
|
|
46
|
+
*/
|
|
47
|
+
declare function collectEntityGeometry(options: CollectEntityGeometryOptions): Promise<EntityGeometryResult>;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { collectEntityGeometry as i, CollectEntityGeometryOptions as n, EntityGeometryResult as r, ClassSource as t };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { a as ResourceManager } from "./manager-exOIcdyx.js";
|
|
2
|
+
import { t as ClassSource } from "./entity-models-c2HpfdWV.js";
|
|
2
3
|
//#region src/blocks/export.d.ts
|
|
3
4
|
interface ExportBlockPackOptions {
|
|
4
5
|
resources: ResourceManager;
|
|
@@ -25,6 +26,22 @@ interface ExportBlockPackOptions {
|
|
|
25
26
|
* or a handful of charcoal from. On by default.
|
|
26
27
|
*/
|
|
27
28
|
items?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Export `entities.json`: every entity type the namespaces register, with a display name and the
|
|
31
|
+
* mean colour of its sheet. On by default. No geometry — no jar has any — but it is what lets a
|
|
32
|
+
* consumer name and tint the stand-in box it draws for a mob.
|
|
33
|
+
*/
|
|
34
|
+
entities?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Reads class files, for recovering entity geometry from the client's own code.
|
|
37
|
+
*
|
|
38
|
+
* Entity models are Java at every version, so without this a pack carries entity names and marker
|
|
39
|
+
* colours but no shapes. Absent for an `--assets` run, which has no code to read — the honest
|
|
40
|
+
* answer there is no geometry rather than a silent gap.
|
|
41
|
+
*/
|
|
42
|
+
classes?: ClassSource;
|
|
43
|
+
/** Where mappings cache; the jar cache directory. */
|
|
44
|
+
cacheDir?: string;
|
|
28
45
|
onProgress?: (stage: string, done: number, total: number) => void;
|
|
29
46
|
}
|
|
30
47
|
interface BlockPackFailure {
|
|
@@ -33,7 +50,7 @@ interface BlockPackFailure {
|
|
|
33
50
|
* `intentional` is not a failure: it marks a block that is empty because the
|
|
34
51
|
* game draws nothing for it. Filter it out to get the real gaps.
|
|
35
52
|
*/
|
|
36
|
-
kind: 'blockstate' | 'model' | 'texture' | 'sprite' | 'geometry' | 'tint' | 'biome' | 'item' | 'intentional';
|
|
53
|
+
kind: 'blockstate' | 'model' | 'texture' | 'sprite' | 'geometry' | 'tint' | 'biome' | 'item' | 'entity' | 'intentional';
|
|
37
54
|
reason: string;
|
|
38
55
|
}
|
|
39
56
|
interface BlockPackResult {
|
|
@@ -45,6 +62,10 @@ interface BlockPackResult {
|
|
|
45
62
|
biomes: number;
|
|
46
63
|
/** Flat items described in `items.json`; `0` means the file was not written. */
|
|
47
64
|
items: number;
|
|
65
|
+
/** Entity types described in `entities.json`; `0` means the file was not written. */
|
|
66
|
+
entities: number;
|
|
67
|
+
/** Distinct entity models in `entity-models.json`; `0` means the file was not written. */
|
|
68
|
+
entityModels: number;
|
|
48
69
|
/** Files actually written — the only trustworthy success signal. */
|
|
49
70
|
writtenFiles: number;
|
|
50
71
|
failures: BlockPackFailure[];
|