block-entity contents from the decompiled renderers: enchanting table book (closed, open towards a nearby photographer), campfire and shelf items, vault display item, copper golem statues (poses, weathering), spawner and trial spawner mobs, beacon beam with glass colours, end portal and gateway star field, block-entity parts in block displays (lectern book, bell), outer item-model transformations for special models, contents test scenes

This commit is contained in:
MineTec committed 2026-11-04 20:15:40 +01:00
1 parent 318a309580
commit 9e48e35680
13 files changed
+925 -28

No files matched your search

+66
View File
@@ -199,6 +199,35 @@ public class BlockEntityTestRender {
scenes.put("wall_piglin_E", List.of(head(Kind.WALL_HEAD, "piglin", 270)));
frontCam.put("wall_piglin_E", 270f);
// --- block-entity contents (no block models in this harness: tables/campfires/shelves/cages absent) ---
scenes.put("enchant_book_closed", List.of(be(Kind.ENCHANTING_TABLE, 0)));
scenes.put("enchant_book_open", List.of(st(Kind.ENCHANTING_TABLE, 0, 0, 90, null, true, null, 0, null, List.of(), List.of())));
frontCam.put("enchant_book_open", 0f);
scenes.put("campfire_items", List.of(contents(Kind.CAMPFIRE, 0, items("beef", "cod", "porkchop", null), null, null, null, null)));
scenes.put("campfire_items_E", List.of(contents(Kind.CAMPFIRE, 270, items("beef", "cod", "porkchop", "salmon"), null, null, null, null)));
scenes.put("shelf_items_S", List.of(contents(Kind.SHELF, 0, items("diamond", "book", "golden_apple"), null, null, null, null)));
frontCam.put("shelf_items_S", 0f);
scenes.put("shelf_items_E", List.of(contents(Kind.SHELF, 270, items("stick", null, "emerald"), null, null, null, null)));
frontCam.put("shelf_items_E", 270f);
scenes.put("vault_item", List.of(contents(Kind.VAULT, 0, items("trial_key"), null, null, null, null)));
frontCam.put("vault_item", 0f);
String[] poses = {"standing", "running", "sitting", "star"};
String[] stages = {null, "exposed", "weathered", "oxidized"};
for (int i = 0; i < 4; i++) {
scenes.put("statue_" + poses[i], List.of(contents(Kind.COPPER_GOLEM_STATUE, i * 90, List.of(), null, null, poses[i], stages[i])));
frontCam.put("statue_" + poses[i], i * 90f);
}
for (String mob : new String[] {"zombie", "pig", "spider", "blaze"}) {
scenes.put("spawner_" + mob, List.of(contents(Kind.SPAWNER, 0, List.of(), spawnerMob(mob), null, null, null)));
frontCam.put("spawner_" + mob, 0f);
}
scenes.put("beacon_beam", List.of(contents(Kind.BEACON, 0, List.of(), null,
new Beam(List.of(new BeamSection(0xFFF9FFFE, 1), new BeamSection(0xFFB02E26, 2), new BeamSection(0xFF3C44AA, 1)), 1f), null, null)));
List<BlockEntityState> portal = new ArrayList<>();
for (int x = 0; x < 3; x++) for (int z = 0; z < 3; z++) portal.add(st(Kind.END_PORTAL, x, z, 0, null, false, null, 0, null, List.of(), List.of()));
scenes.put("end_portal", portal);
scenes.put("end_gateway", List.of(st(Kind.END_GATEWAY, 0, 0, 0, "down+up+north+south+west+east", false, null, 0, null, List.of(), List.of())));
// Decoration scenes (paintings + item frames), rendered via the same scene path.
LinkedHashMap<String, List<DecorationState>> decoScenes = new LinkedHashMap<>();
// kebab is 1x1; place a thin quad facing south spanning the block at z=0..0.0625.
@@ -252,6 +281,42 @@ public class BlockEntityTestRender {
List.of(), List.of(), null, null);
}
/** A contents-bearing block-entity at the origin (items / spawner mob / beacon beam / statue pose). */
static BlockEntityState contents(Kind kind, float yaw, List<eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec> items,
EntityState mob, Beam beam, String pose, String color) {
return new BlockEntityState(kind, 0, 0, 0, yaw, null, false, null, 0, color, null, null,
List.of(), List.of(), null, null, items, mob, beam, pose);
}
static List<eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec> items(String... ids) {
List<eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec> out = new ArrayList<>();
for (String id : ids) out.add(id == null ? null : new eu.mhsl.minecraft.pixelpics.render.entity.ItemSpec(id, null, null, false));
return out;
}
/** EntityTestRender's default state of a mob with its real bounding box (spawner scale depends on it). */
static EntityState spawnerMob(String type) throws Exception {
EntityState e = EntityTestRender.state(type, 0);
double[] dims = switch (type) {
case "zombie" -> new double[] {0.6, 1.95};
case "pig" -> new double[] {0.9, 0.9};
case "spider" -> new double[] {1.4, 0.9};
default -> new double[] {0.6, 1.8};
};
var comps = EntityState.class.getRecordComponents();
Object[] vals = new Object[comps.length];
Class<?>[] types = new Class<?>[comps.length];
for (int i = 0; i < comps.length; i++) {
types[i] = comps[i].getType();
vals[i] = switch (comps[i].getName()) {
case "width" -> dims[0];
case "height" -> dims[1];
default -> comps[i].getAccessor().invoke(e);
};
}
return EntityState.class.getDeclaredConstructor(types).newInstance(vals);
}
static BlockEntityState head(Kind kind, String type, float yaw) {
return st(kind, 0, 0, yaw, null, false, null, 0, type, List.of(), List.of());
}
@@ -266,6 +331,7 @@ public class BlockEntityTestRender {
for (int a = 0; a < 3; a++) { min[a] = Math.min(min[a], re.aabbMin[a]); max[a] = Math.max(max[a], re.aabbMax[a]); }
}
if (min[0] > max[0]) { min = new double[]{0, 0, 0}; max = new double[]{1, 1, 1}; }
if (states.get(0).kind() == Kind.BEACON) max[1] = min[1] + 4; // frame the beam's foot, not 2048 blocks
double cx = (min[0] + max[0]) / 2, cy = (min[1] + max[1]) / 2, cz = (min[2] + max[2]) / 2;
double ext = 0.5;
for (int a = 0; a < 3; a++) ext = Math.max(ext, max[a] - min[a]);
+35 -1
View File
@@ -185,6 +185,29 @@ public class VanillaModelDump {
} catch (ReflectiveOperationException e) {
failed.add("posed/book (" + e + ")");
}
// EnchantTableRenderer: BookModel.State.forAnimation(time 0, flip 0 → pages 0.1/0.9, open) — closed
// (idle, no player within 3 blocks) and fully open (a player nearby).
for (float open : new float[] {0F, 1F}) {
posedBlockEntity(out, failed, set, net.minecraft.client.model.geom.ModelLayers.BOOK,
"posed/enchanting_book_" + (open == 0 ? "closed" : "open") + "#main",
m -> new net.minecraft.client.model.object.book.BookModel(m),
net.minecraft.client.model.object.book.BookModel.State.forAnimation(0F, 0.1F, 0.9F, open));
}
// CopperGolemStatueBlockRenderer: one baked layer per statue pose, CopperGolemStatueModel.setupAnim
// (root.y = 0, root.zRot = π).
String[][] statues = {{"standing", "COPPER_GOLEM"}, {"running", "COPPER_GOLEM_RUNNING"},
{"sitting", "COPPER_GOLEM_SITTING"}, {"star", "COPPER_GOLEM_STAR"}};
for (String[] st : statues) {
try {
ModelLayerLocation loc = (ModelLayerLocation) net.minecraft.client.model.geom.ModelLayers.class
.getField(st[1]).get(null);
posedBlockEntity(out, failed, set, loc, "posed/copper_golem_statue_" + st[0] + "#main",
m -> new net.minecraft.client.model.object.statue.CopperGolemStatueModel(m),
net.minecraft.util.Unit.INSTANCE);
} catch (ReflectiveOperationException e) {
failed.add("posed/copper_golem_statue_" + st[0] + " (" + e + ")");
}
}
JsonObject root = new JsonObject();
root.addProperty("version", SharedConstants.getCurrentVersion().name());
JsonObject models = new JsonObject();
@@ -607,7 +630,18 @@ public class VanillaModelDump {
ModelLayerLocation loc,
java.util.function.Function<ModelPart, Model<?>> factory,
Object state) {
String name = "posed/" + loc.model().getPath() + "#" + loc.layer();
posedBlockEntity(out, failed, set, loc, "posed/" + loc.model().getPath() + "#" + loc.layer(), factory, state);
}
/** {@link #posedBlockEntity} under an explicit name (several static states of one layer). */
static void posedBlockEntity(
Map<String, JsonObject> out,
List<String> failed,
EntityModelSet set,
ModelLayerLocation loc,
String name,
java.util.function.Function<ModelPart, Model<?>> factory,
Object state) {
try {
JsonObject jem = dump(factory.apply(set.bakeLayer(loc)), state, name, failed);
if (jem == null) return;
+67 -1
View File
@@ -189,6 +189,72 @@ public class PPTest extends JavaPlugin {
scenes.put("geo_water", new Scene("geo_water", this::geoWater, 6, 3.5, -5, 6, -3, 2.5));
scenes.put("geo_water_top", new Scene("geo_water_top", this::geoWater, 6, 9, 2.4, 6, -4, 2.5));
scenes.put("geo_water_under", new Scene("geo_water_under", this::geoWater, 11.5, -3.3, 0.3, 4, -5, 3));
// Block-entity contents: enchanting-table book (closed; open when the camera is within 3 blocks), campfire
// and shelf items, copper golem statues (4 poses x stages), spawner/trial-spawner mobs, vault, beacon beam
// through red+blue glass, 3x3 end portal and an end gateway. Rendered 100 ticks after building (beacon
// levels update every 80 ticks).
scenes.put("be_contents", new Scene("be_contents", this::beContents, 7, 4.5, 13, 7, 0.8, 3));
scenes.put("be_contents_n", new Scene("be_contents_n", this::beContents, 7, 4.5, -7, 7, 0.8, 3));
scenes.put("be_contents_close", new Scene("be_contents_close", this::beContents, 1.2, 1.8, 2.2, 1.5, 0.6, 0));
scenes.put("be_contents_beam", new Scene("be_contents_beam", this::beContents, 4, 6, 26, 10, 9, 6));
scenes.put("be_contents_portal", new Scene("be_contents_portal", this::beContents, 11.5, 3.6, -1.8, 11.5, 0, 1.2));
}
private void beContents(Location o) {
put(o, 0, 0, 0, "enchanting_table");
put(o, 2, 0, 0, "campfire[facing=south,lit=true]");
put(o, 4, 0, 0, "soul_campfire[facing=east,lit=false]");
put(o, 6, 0, 0, "oak_shelf[facing=south]");
put(o, 8, 0, 0, "cherry_shelf[facing=south,powered=false]");
if (o.clone().add(2, 0, 0).getBlock().getState() instanceof org.bukkit.block.Campfire c) {
Material[] food = {Material.BEEF, Material.COD, Material.PORKCHOP, Material.POTATO};
for (int i = 0; i < 4; i++) {
c.setItem(i, it(food[i]));
c.setCookTimeTotal(i, 1_000_000); // else the lit campfire pops them out at once (total 0)
}
c.update(true, false);
}
if (o.clone().add(4, 0, 0).getBlock().getState() instanceof org.bukkit.block.Campfire c) {
c.setItem(0, it(Material.SALMON));
c.setItem(2, it(Material.CHICKEN));
c.update(true, false);
}
Material[][] shelves = {{Material.DIAMOND, Material.BOOK, Material.OAK_LOG}, {Material.GOLDEN_APPLE, null, Material.LANTERN}};
for (int k = 0; k < 2; k++) {
if (o.clone().add(6 + 2 * k, 0, 0).getBlock().getState() instanceof org.bukkit.block.Shelf sh) {
for (int i = 0; i < 3; i++) if (shelves[k][i] != null) sh.getSnapshotInventory().setItem(i, it(shelves[k][i]));
sh.update(true, false);
}
}
String[] poses = {"standing", "running", "sitting", "star"};
String[] stages = {"", "exposed_", "weathered_", "waxed_oxidized_"};
String[] facing = {"south", "south", "east", "west"};
for (int i = 0; i < 4; i++)
put(o, i * 2, 0, 3, stages[i] + "copper_golem_statue[copper_golem_pose=" + poses[i] + ",facing=" + facing[i] + "]");
put(o, 0, 0, 6, "spawner");
put(o, 2, 0, 6, "spawner");
put(o, 4, 0, 6, "trial_spawner[trial_spawner_state=waiting_for_players]");
put(o, 6, 0, 6, "vault[facing=south]");
EntityType[] mobs = {EntityType.ZOMBIE, EntityType.PIG};
for (int i = 0; i < 2; i++) {
if (o.clone().add(i * 2, 0, 6).getBlock().getState() instanceof org.bukkit.block.CreatureSpawner cs) {
cs.setSpawnedType(mobs[i]);
cs.update(true, false);
}
}
if (o.clone().add(4, 0, 6).getBlock().getState() instanceof org.bukkit.block.TrialSpawner ts) {
ts.getNormalConfiguration().setSpawnedType(EntityType.SKELETON);
ts.update(true, false);
put(o, 4, 0, 6, "trial_spawner[trial_spawner_state=waiting_for_players]");
}
// Beacon on a 3x3 iron pyramid, red glass 2 above, blue glass 4 above (red+blue = averaged section).
for (int x = -1; x <= 1; x++) for (int z = -1; z <= 1; z++) put(o, 10 + x, -1, 6 + z, "iron_block");
put(o, 10, 0, 6, "beacon");
put(o, 10, 2, 6, "red_stained_glass");
put(o, 10, 4, 6, "blue_stained_glass_pane");
for (int x = 0; x < 3; x++) for (int z = 0; z < 3; z++) put(o, 10 + x, 0, z, "end_portal");
put(o, 14, 1, 1, "end_gateway");
put(o, 14, 0, 1, "obsidian");
}
private void rfVisualLayers(Location o) {
@@ -592,7 +658,7 @@ public class PPTest extends JavaPlugin {
t.printStackTrace();
}
// Let one tick pass (passengers/leashes settle), then render.
Bukkit.getScheduler().runTaskLater(this, () -> render(sc, o), sc.name.equals("proj_shake") ? 4 : sc.name.startsWith("proj_") ? 30 : 10);
Bukkit.getScheduler().runTaskLater(this, () -> render(sc, o), sc.name.equals("proj_shake") ? 4 : sc.name.startsWith("proj_") ? 30 : sc.name.startsWith("be_contents") ? 100 : 10);
}
private void render(Scene sc, Location o) {