derive default layers, armor wearers, arms/chest pose flags and mob variant textures from the vanilla dump, drop the hardcoded lists
This commit is contained in:
1 parent
022b080fce
commit
a743dc32c7
14 files changed
+720
-162
No files matched your search
+132
-4
@@ -103,6 +103,7 @@ public class VanillaModelDump {
|
||||
for (Object layer : ls) collectLayer(layer, found, textures);
|
||||
}
|
||||
JsonObject info = entityInfo(r, state);
|
||||
addLayerInfo(info, layers, r);
|
||||
addTransform(info, r);
|
||||
Model<?> main = found.containsKey("model") ? found.get("model") : found.get("adultModel");
|
||||
if (main != null) addPoseVariants(info, main, r);
|
||||
@@ -124,6 +125,7 @@ public class VanillaModelDump {
|
||||
for (Object layer : ls) collectLayer(layer, found, textures);
|
||||
}
|
||||
JsonObject info = new JsonObject();
|
||||
addLayerInfo(info, layers, r);
|
||||
addTransform(info, r);
|
||||
Model<?> main = found.get("model");
|
||||
if (main != null) addPoseVariants(info, main, r);
|
||||
@@ -187,6 +189,7 @@ public class VanillaModelDump {
|
||||
JsonObject ents = new JsonObject();
|
||||
entities.forEach(ents::add);
|
||||
root.add("entities", ents);
|
||||
root.add("variants", registryVariants());
|
||||
try (FileWriter w = new FileWriter(args.length > 0 ? args[0] : "vanilla_entity_models.json")) {
|
||||
new GsonBuilder().create().toJson(root, w);
|
||||
}
|
||||
@@ -194,6 +197,111 @@ public class VanillaModelDump {
|
||||
for (String f : failed) System.out.println("FAILED " + f);
|
||||
}
|
||||
|
||||
/**
|
||||
* The renderer's layers: {@code layers: {LayerClass: drawnByDefault}} — whether the layer submits anything
|
||||
* for the default (idle, adult, untamed, unequipped, …) render state, measured by running its real
|
||||
* {@code submit} against a recording collector — plus {@code humanoidArmor: true} when one of them is a
|
||||
* HumanoidArmorLayer (the entity wears humanoid armor).
|
||||
*/
|
||||
static void addLayerInfo(JsonObject info, Object layers, EntityRenderer<?, ?> r) {
|
||||
if (!(layers instanceof List<?> ls) || ls.isEmpty()) return;
|
||||
JsonObject out = new JsonObject();
|
||||
for (Object layer : ls) {
|
||||
Class<?> lc = layer.getClass();
|
||||
while (lc.getSimpleName().isEmpty()) lc = lc.getSuperclass(); // anonymous layer -> its base class
|
||||
String name = lc.getSimpleName();
|
||||
if (layer instanceof net.minecraft.client.renderer.entity.layers.HumanoidArmorLayer<?, ?, ?>) {
|
||||
info.addProperty("humanoidArmor", true);
|
||||
}
|
||||
Boolean drawn = layerDrawsByDefault(layer, r);
|
||||
if (drawn == null) {
|
||||
System.out.println("layer " + name + " (" + r.getClass().getSimpleName() + "): submit failed");
|
||||
drawn = false;
|
||||
}
|
||||
if (!out.has(name) || drawn) out.addProperty(name, drawn);
|
||||
}
|
||||
info.add("layers", out);
|
||||
}
|
||||
|
||||
/** True/false = the layer submits / doesn't submit geometry for the default state; null = submit threw. */
|
||||
static Boolean layerDrawsByDefault(Object layer, EntityRenderer<?, ?> r) {
|
||||
Object state;
|
||||
try {
|
||||
state = r.createRenderState();
|
||||
} catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
idleTweaks(state);
|
||||
boolean[] submitted = {false};
|
||||
Object proxy = recordingCollector(m -> {
|
||||
if (m.getName().startsWith("submit")) submitted[0] = true;
|
||||
});
|
||||
Method submit = null;
|
||||
for (Method m : layer.getClass().getMethods()) {
|
||||
if (m.getName().equals("submit") && m.getParameterCount() == 6 && !m.isBridge()) submit = m;
|
||||
}
|
||||
if (submit == null) return null;
|
||||
try {
|
||||
submit.invoke(layer, new com.mojang.blaze3d.vertex.PoseStack(), proxy, 0xF000F0, state, 0f, 0f);
|
||||
} catch (Throwable t) {
|
||||
return submitted[0] ? Boolean.TRUE : null;
|
||||
}
|
||||
return submitted[0];
|
||||
}
|
||||
|
||||
/** A SubmitNodeCollector proxy that reports every call and otherwise does nothing. */
|
||||
static Object recordingCollector(java.util.function.Consumer<Method> onCall) {
|
||||
Class<?> collector;
|
||||
try {
|
||||
collector = Class.forName("net.minecraft.client.renderer.SubmitNodeCollector");
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return java.lang.reflect.Proxy.newProxyInstance(
|
||||
VanillaModelDump.class.getClassLoader(), new Class<?>[] {collector}, (px, m, a) -> {
|
||||
if (m.getName().equals("order")) return px;
|
||||
onCall.accept(m);
|
||||
Class<?> rt = m.getReturnType();
|
||||
if (rt == boolean.class) return false;
|
||||
if (rt.isPrimitive() && rt != void.class) return 0;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The built-in data-driven mob variants ({@code data/minecraft/<type>_variant/<key>.json}, cat/wolf/frog/
|
||||
* cow/pig/chicken/zombie_nautilus …) with their client assets: {@code variants: {<type>: {<key>: {asset_id,
|
||||
* baby_asset_id, assets{wild,tame,angry}, baby_assets{…}, model}}}} (spawn conditions dropped).
|
||||
*/
|
||||
static JsonObject registryVariants() throws java.io.IOException {
|
||||
JsonObject out = new JsonObject();
|
||||
java.net.URL jar = SharedConstants.class.getProtectionDomain().getCodeSource().getLocation();
|
||||
java.util.regex.Pattern p =
|
||||
java.util.regex.Pattern.compile("data/minecraft/([a-z0-9_]+)_variant/([a-z0-9_/.-]+)\\.json");
|
||||
try (java.util.zip.ZipFile zf = new java.util.zip.ZipFile(new java.io.File(jar.getPath()))) {
|
||||
List<? extends java.util.zip.ZipEntry> entries =
|
||||
zf.stream().sorted(java.util.Comparator.comparing(java.util.zip.ZipEntry::getName)).toList();
|
||||
for (java.util.zip.ZipEntry ze : entries) {
|
||||
java.util.regex.Matcher m = p.matcher(ze.getName());
|
||||
if (!m.matches()) continue;
|
||||
String type = m.group(1);
|
||||
if (type.endsWith("_sound") || type.equals("painting")) continue; // not mob textures
|
||||
JsonObject v;
|
||||
try (java.io.Reader rd = new java.io.InputStreamReader(zf.getInputStream(ze))) {
|
||||
v = com.google.gson.JsonParser.parseReader(rd).getAsJsonObject();
|
||||
}
|
||||
JsonObject keep = new JsonObject();
|
||||
for (String k : new String[] {"asset_id", "baby_asset_id", "assets", "baby_assets", "model"}) {
|
||||
if (v.has(k)) keep.add(k, v.get(k));
|
||||
}
|
||||
if (keep.isEmpty()) continue;
|
||||
if (!out.has(type)) out.add(type, new JsonObject());
|
||||
out.getAsJsonObject(type).add(m.group(2), keep);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** One block-entity model posed with {@code state} (see main), stored as {@code posed/<model>#<layer>}. */
|
||||
static void posedBlockEntity(
|
||||
Map<String, JsonObject> out,
|
||||
@@ -359,8 +467,13 @@ public class VanillaModelDump {
|
||||
for (String f : new String[] {"isPassenger", "isRiding"})
|
||||
if (hasField(probe, f)) passenger.add(new String[] {"passenger", f, "true"});
|
||||
if (!passenger.isEmpty()) toggles.add(passenger);
|
||||
if (hasField(probe, "rightArmPose")) toggles.add(java.util.Collections.singletonList(new String[] {"item_r", "rightArmPose", "ITEM"}));
|
||||
if (hasField(probe, "leftArmPose")) toggles.add(java.util.Collections.singletonList(new String[] {"item_l", "leftArmPose", "ITEM"}));
|
||||
if (hasField(probe, "rightArmPose") && probeEnum(probe, "rightArmPose")) toggles.add(java.util.Collections.singletonList(new String[] {"item_r", "rightArmPose", "ITEM"}));
|
||||
if (hasField(probe, "leftArmPose") && probeEnum(probe, "leftArmPose")) toggles.add(java.util.Collections.singletonList(new String[] {"item_l", "leftArmPose", "ITEM"}));
|
||||
// State flags that switch model parts on in setupAnim: armor-stand arms, donkey/mule/llama chests.
|
||||
for (String[] f : new String[][] {{"arms", "showArms"}, {"chest", "hasChest"}}) {
|
||||
if (hasField(probe, f[1]))
|
||||
toggles.add(java.util.Collections.singletonList(new String[] {f[0], f[1], "true"}));
|
||||
}
|
||||
List<List<String[]>> combos = new ArrayList<>();
|
||||
for (int mask = 1; mask < (1 << toggles.size()); mask++) {
|
||||
List<String[]> c = new ArrayList<>();
|
||||
@@ -395,8 +508,14 @@ public class VanillaModelDump {
|
||||
for (int i = 0; same && i < x.length; i++) same = Math.abs(b[i] - x[i]) < 1e-4f;
|
||||
if (same) continue;
|
||||
JsonObject po = new JsonObject();
|
||||
po.add("pivot", arr(x[0], x[1], x[2]));
|
||||
po.add("rotate", arr(x[3], x[4], x[5]));
|
||||
boolean moved = b == null;
|
||||
for (int i = 0; !moved && i < 6; i++) moved = Math.abs(b[i] - x[i]) >= 1e-4f;
|
||||
if (moved) {
|
||||
// Absolute pivot + rotation; omitted for pure visibility toggles (arms, chests), which then
|
||||
// apply to every model of the type (small/baby models have other pivots).
|
||||
po.add("pivot", arr(x[0], x[1], x[2]));
|
||||
po.add("rotate", arr(x[3], x[4], x[5]));
|
||||
}
|
||||
po.addProperty("visible", x[6] > 0.5f);
|
||||
diff.add(e.getKey(), po);
|
||||
}
|
||||
@@ -410,6 +529,15 @@ public class VanillaModelDump {
|
||||
poseOf(model, freshState(r, List.of()));
|
||||
}
|
||||
|
||||
/** Whether the state field is an enum (HumanoidModel.ArmPose), not e.g. an armor stand's Rotations. */
|
||||
static boolean probeEnum(Object o, String f) {
|
||||
try {
|
||||
return o.getClass().getField(f).getType().isEnum();
|
||||
} catch (NoSuchFieldException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean hasField(Object o, String f) {
|
||||
try {
|
||||
o.getClass().getField(f);
|
||||
|
||||
Reference in new issue
Block a user