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:
MineTec committed 2026-11-04 15:48:03 +01:00
1 parent 022b080fce
commit a743dc32c7
14 files changed
+720 -162

No files matched your search

+1 -1
View File
@@ -228,7 +228,7 @@ public class BlockEntityTestRender {
log.info("rendered " + name);
}
File outDir = new File("/home/elias/Dokumente/PixelPics-BlockEntity-Renders");
File outDir = new File(System.getProperty("out", "/home/elias/Dokumente/PixelPics-BlockEntity-Renders"));
outDir.mkdirs();
for (int i = 0; i < names.size(); i++) ImageIO.write(cells.get(i), "png", new File(outDir, names.get(i) + ".png"));
+81 -12
View File
@@ -44,6 +44,49 @@ public class EntityTestRender {
"minecart","end_crystal","arrow","trident","leash_knot","evoker_fangs","wither_skull","shulker_bullet","cushion"
};
// "@variants" case set (before/after pixel diffs of variant / pose-flag / armor handling). Spec syntax:
// [baby:]type[@variant][+flag…], flags: chest, arms, tame (red collar), saddle, armor (full diamond), noarmor.
static final List<String> VARIANT_CASES = new ArrayList<>();
static {
for (String v : new String[] {"tabby", "black", "red", "siamese", "british_shorthair", "calico", "persian",
"ragdoll", "white", "jellie", "all_black"}) {
VARIANT_CASES.add("cat@" + v);
VARIANT_CASES.add("baby:cat@" + v + "+tame");
}
for (String v : new String[] {"pale", "spotted", "snowy", "black", "ashen", "rusty", "woods", "chestnut",
"striped"}) {
VARIANT_CASES.add("wolf@" + v);
VARIANT_CASES.add("wolf@" + v + "+tame");
VARIANT_CASES.add("baby:wolf@" + v);
VARIANT_CASES.add("baby:wolf@" + v + "+tame");
}
for (String t : new String[] {"cow", "pig", "chicken"})
for (String v : new String[] {"temperate", "warm", "cold"}) {
VARIANT_CASES.add(t + "@" + v);
VARIANT_CASES.add("baby:" + t + "@" + v);
}
for (String v : new String[] {"temperate", "warm", "cold"}) VARIANT_CASES.add("frog@" + v);
for (String v : new String[] {"temperate", "warm"}) VARIANT_CASES.add("zombie_nautilus@" + v);
VARIANT_CASES.add("mooshroom@red");
VARIANT_CASES.add("mooshroom@brown");
VARIANT_CASES.add("baby:mooshroom@brown");
// Armor stand with/without arms, small, with armor.
for (String a : new String[] {"armor_stand", "armor_stand+arms", "baby:armor_stand", "baby:armor_stand+arms",
"armor_stand+arms+armor", "baby:armor_stand+arms+armor"}) VARIANT_CASES.add(a);
// Chests.
for (String c : new String[] {"llama@creamy", "llama@creamy+chest", "llama@brown+chest+saddle", "llama@gray+chest",
"trader_llama@white+chest", "baby:llama@creamy+chest", "donkey", "donkey+chest", "donkey+chest+saddle",
"mule", "mule+chest", "mule+chest+saddle", "baby:donkey+chest"}) VARIANT_CASES.add(c);
// Armor wearers (and non-wearers given armor: nothing must appear).
for (String t : new String[] {"zombie", "husk", "drowned", "zombie_villager", "zombified_piglin", "skeleton",
"stray", "wither_skeleton", "bogged", "parched", "piglin", "piglin_brute", "giant", "player",
"mannequin", "armor_stand", "baby:zombie", "baby:piglin", "baby:zombified_piglin", "baby:husk",
"baby:drowned", "baby:zombie_villager", "villager", "pillager", "vindicator", "witch", "cow", "pig",
"creeper", "enderman", "evoker", "illusioner", "wandering_trader", "copper_golem", "fox", "panda"})
VARIANT_CASES.add(t + "+armor");
VARIANT_CASES.addAll(List.of(ENTITIES));
}
// Representative variants (the game always supplies these; null base textures wouldn't exist otherwise).
static final Map<String, String> VAR = Map.ofEntries(
Map.entry("cat", "tabby"), Map.entry("wolf", "pale"), Map.entry("axolotl", "lucy"),
@@ -135,7 +178,8 @@ public class EntityTestRender {
WorldSnapshot empty = new WorldSnapshot(Map.of(), 0, 1, air);
SkyContext sky = new SkyContext(6000, 0, 6000);
String[] list = args.length > 0 ? args : ENTITIES;
String[] list = args.length == 0 ? ENTITIES
: args[0].equals("@variants") ? VARIANT_CASES.toArray(new String[0]) : args;
List<BufferedImage> cells = new ArrayList<>();
for (String key : list) {
BufferedImage v1 = renderEntity(renderer, baker, empty, sky, key, 45);
@@ -144,13 +188,13 @@ public class EntityTestRender {
log.info("rendered " + key);
}
File outDir = new File("/home/elias/Dokumente/PixelPics-Entity-Renders");
File outDir = new File(System.getProperty("out", "/home/elias/Dokumente/PixelPics-Entity-Renders"));
outDir.mkdirs();
// One image per entity (both 45°/225° views side by side), named by entity key.
File single = new File(outDir, "einzeln");
single.mkdirs();
for (int i = 0; i < list.length; i++) {
ImageIO.write(cells.get(i), "png", new File(single, list[i].replace(':', '_') + ".png"));
ImageIO.write(cells.get(i), "png", new File(single, list[i].replaceAll("[:@+]", "_") + ".png"));
}
// Compose contact-sheet pages: 2 columns.
@@ -177,15 +221,7 @@ public class EntityTestRender {
static BufferedImage renderEntity(DefaultScreenRenderer renderer, eu.mhsl.minecraft.pixelpics.render.entity.cem.CemBaker baker, WorldSnapshot world,
SkyContext sky, String key, float yaw) {
boolean baby = key.startsWith("baby:");
if (baby) key = key.substring(5);
boolean isPlayer = key.equals("player");
int tint = key.equals("tropical_fish") ? 0xFFF9801D : (key.equals("sheep") ? 0xFFF9FFFE : 0);
int tint2 = key.equals("tropical_fish") ? 0xFF3AB3DA : 0;
EntityState s = new EntityState(key, 0, 0, 0, yaw, baby, 0.8, 1.0,
isPlayer, null, false, VAR.get(key), tint, 1.0, PROF.get(key), LVL.getOrDefault(key, 0),
MARK.get(key), SADDLE.contains(key), CHEST.contains(key), EQUIP.get(key), baby ? null : ARMOR.get(key),
!baby && INVISIBLE.contains(key), 0, 0, tint2, false, null, null, null, 0, false, null);
EntityState s = state(key, yaw);
RenderedEntity re = baker.bake(s);
if (re == null || re.cubes.isEmpty()) { // invisible with no equipment -> nothing renders
return new BufferedImage(TW, TH, BufferedImage.TYPE_INT_RGB);
@@ -208,6 +244,39 @@ public class EntityTestRender {
return renderer.execute(job);
}
/** Entity state for a case spec ({@code [baby:]type[@variant][+flag…]}, see VARIANT_CASES). */
static EntityState state(String spec, float yaw) {
boolean baby = spec.startsWith("baby:");
if (baby) spec = spec.substring(5);
String[] flagSplit = spec.split("\\+");
Set<String> flags = new HashSet<>(Arrays.asList(flagSplit).subList(1, flagSplit.length));
String key = flagSplit[0];
String variant = null;
if (key.contains("@")) {
variant = key.substring(key.indexOf('@') + 1);
key = key.substring(0, key.indexOf('@'));
}
boolean plain = flagSplit.length == 1 && variant == null; // default ENTITIES entry: legacy test setup
if (variant == null) variant = VAR.get(key);
boolean isPlayer = key.equals("player");
int tint = key.equals("tropical_fish") ? 0xFFF9801D : (key.equals("sheep") ? 0xFFF9FFFE : 0);
int tint2 = key.equals("tropical_fish") ? 0xFF3AB3DA : 0;
EntityState.Equipment armor = plain ? (baby ? null : ARMOR.get(key)) : null;
if (flags.contains("armor")) {
EntityState.EquipPiece d = P("diamond");
armor = new EntityState.Equipment(d, d, d, d);
}
boolean chest = plain ? CHEST.contains(key) : flags.contains("chest");
boolean saddle = plain ? SADDLE.contains(key) : flags.contains("saddle");
String equip = plain ? EQUIP.get(key) : null;
boolean arms = flags.contains("arms");
int collar = flags.contains("tame") ? 0xFFB02E26 : 0;
return new EntityState(key, 0, 0, 0, yaw, baby, 0.8, 1.0,
isPlayer, null, false, variant, tint, 1.0, PROF.get(key), LVL.getOrDefault(key, 0),
plain ? MARK.get(key) : null, saddle, chest, arms, equip, armor,
plain && !baby && INVISIBLE.contains(key), 0, 0, tint2, false, null, null, null, collar, false, null);
}
static BufferedImage labelCell(String key, BufferedImage v1, BufferedImage v2) {
int w = v1.getWidth() + v2.getWidth();
int labelH = 20;
+1 -1
View File
@@ -100,7 +100,7 @@ public class ObjectTestRender {
List<Vector> rayMap = EntityTestRender.buildRayMap(loc, W * 3, H * 3);
RenderJob job = new RenderJob(empty, rayMap, cam, W, H, sky, List.of(), List.of(), List.of(), objs);
BufferedImage img = renderer.execute(job);
File out = new File("/home/elias/Dokumente/PixelPics-Entity-Renders/objects.png");
File out = new File(System.getProperty("out", "/home/elias/Dokumente/PixelPics-Entity-Renders/objects.png"));
ImageIO.write(img, "png", out);
System.out.println("WROTE " + out);
}
+132 -4
View File
@@ -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);
+215 -1
View File
@@ -117,6 +117,17 @@ public class PPTest extends JavaPlugin {
scenes.put("be_items", new Scene("be_items", this::beItems, 4.5, 2.6, 11, 4.5, 0.8, 3));
scenes.put("be_items_back", new Scene("be_items_back", this::beItems, 4.5, 2.6, -5, 4.5, 0.8, 3));
scenes.put("proj_stuck_sw", new Scene("proj_stuck_sw", this::projStuck, -3, 6, 10, 8, 1.5, 2));
// Data-driven variants / pose flags / armor wearers (vanilla dump: asset_id, setupAnim flags, armor layers).
scenes.put("rf_cats", new Scene("rf_cats", this::rfCats, 6.5, 2.6, -4.5, 6.5, 0.2, 1.2));
scenes.put("rf_wolves", new Scene("rf_wolves", this::rfWolves, 5.5, 3.2, -5.5, 5.5, 0.2, 1.8));
scenes.put("rf_farm", new Scene("rf_farm", this::rfFarm, 6, 3.4, -6, 6, 0.3, 1.8));
scenes.put("rf_stands", new Scene("rf_stands", this::rfStands, 5, 2.4, -4.5, 5, 0.9, 0));
scenes.put("rf_stands_back", new Scene("rf_stands_back", this::rfStands, 5, 2.4, 4.5, 5, 0.9, 0));
scenes.put("rf_chests", new Scene("rf_chests", this::rfChests, 7, 4, -7, 7, 0.8, 1));
scenes.put("rf_chests_side", new Scene("rf_chests_side", this::rfChests, 7, 2.5, 7, 7, 0.8, 1));
// Needs a datapack cat variant "pptest:ocelotcat" (asset_id entity/cat/ocelot) in the world's datapacks.
scenes.put("rf_dpcat", new Scene("rf_dpcat", this::rfDatapackCat, 1.2, 1.4, -2.2, 1.2, 0.2, 0.5));
scenes.put("rf_armor", new Scene("rf_armor", this::rfArmor, 7, 3.2, -6.5, 7, 1.0, 1.5));
}
@Override
@@ -154,7 +165,7 @@ public class PPTest extends JavaPlugin {
Location eye = o.clone().add(sc.camX, sc.camY, sc.camZ);
Vector look = o.clone().add(sc.lookX, sc.lookY, sc.lookZ).toVector().subtract(eye.toVector());
eye.setDirection(look);
RenderJob job = r.prepare(eye, sc.name.startsWith("proj_") ? new Resolution(960, 640) : new Resolution(480, 320), null);
RenderJob job = r.prepare(eye, sc.name.startsWith("proj_") || sc.name.startsWith("rf_") ? new Resolution(960, 640) : new Resolution(480, 320), null);
if (sc.name.equals("dbg")) {
try {
java.lang.reflect.Field f = DefaultScreenRenderer.class.getDeclaredField("objectBaker");
@@ -690,6 +701,209 @@ public class PPTest extends JavaPlugin {
for (Entity e : w.getNearbyEntities(o, 12, 8, 8)) if (e instanceof Wither wi) wi.setBodyYaw(180);
}
// --- data-driven variants / pose flags / armor wearers ------------------------------------------
private static <T extends Keyed> List<T> registry(io.papermc.paper.registry.RegistryKey<T> key) {
List<T> out = new ArrayList<>();
io.papermc.paper.registry.RegistryAccess.registryAccess().getRegistry(key).forEach(out::add);
out.sort(Comparator.comparing(k -> k.getKey().toString()));
return out;
}
/** Spawn without vanilla's random spawn data (baby chance, jockeys, gear, biome variants): reproducible. */
private <T extends Entity> T rfSpawn(Location o, double x, double y, double z, float yaw, Class<T> type) {
Location l = o.clone().add(x, y, z);
l.setYaw(yaw);
T e = w.spawn(l, type, false, en -> {
if (en instanceof Mob m) m.setAI(false);
en.setPersistent(false);
});
if (e instanceof LivingEntity le) le.setRemoveWhenFarAway(false);
return e;
}
private Entity rfSpawn(Location o, double x, double y, double z, float yaw, EntityType type) {
Location l = o.clone().add(x, y, z);
l.setYaw(yaw);
Entity e = w.spawnEntity(l, type, false);
if (e instanceof Mob m) m.setAI(false);
return e;
}
private void faceNorth(Location o) {
for (Entity e : w.getNearbyEntities(o.clone().add(6, 0, 0), 14, 4, 12))
if (e instanceof LivingEntity le && !(e instanceof Player)) le.setBodyYaw(180);
}
private void rfCats(Location o) {
List<Cat.Type> types = registry(io.papermc.paper.registry.RegistryKey.CAT_VARIANT);
for (int i = 0; i < types.size(); i++) {
rfSpawn(o, i * 1.2, 0, 0, 180, Cat.class).setCatType(types.get(i));
Cat kit = rfSpawn(o, i * 1.2, 0, 1.6, 180, Cat.class);
kit.setCatType(types.get(i));
kit.setBaby();
if (i % 2 == 0) tame(kit, DyeColor.values()[i % 16]);
}
faceNorth(o);
}
private void rfDatapackCat(Location o) {
Cat.Type custom = io.papermc.paper.registry.RegistryAccess.registryAccess()
.getRegistry(io.papermc.paper.registry.RegistryKey.CAT_VARIANT)
.get(new NamespacedKey("pptest", "ocelotcat"));
if (custom == null) throw new IllegalStateException("datapack variant pptest:ocelotcat missing");
rfSpawn(o, 0, 0, 0, 180, Cat.class).setCatType(custom);
Cat kit = rfSpawn(o, 1.2, 0, 0, 180, Cat.class);
kit.setCatType(custom);
kit.setBaby();
rfSpawn(o, 2.4, 0, 0, 180, Cat.class).setCatType(Cat.Type.TABBY);
faceNorth(o);
}
private void rfWolves(Location o) {
List<Wolf.Variant> vs = registry(io.papermc.paper.registry.RegistryKey.WOLF_VARIANT);
for (int i = 0; i < vs.size(); i++) {
rfSpawn(o, i * 1.3, 0, 0, 180, Wolf.class).setVariant(vs.get(i));
Wolf t = rfSpawn(o, i * 1.3, 0, 1.6, 180, Wolf.class);
t.setVariant(vs.get(i));
tame(t, DyeColor.values()[i % 16]);
Wolf pup = rfSpawn(o, i * 1.3, 0, 3.0, 180, Wolf.class);
pup.setVariant(vs.get(i));
pup.setBaby();
if (i % 2 == 1) tame(pup, DyeColor.BLUE);
}
faceNorth(o);
}
private void rfFarm(Location o) {
List<Cow.Variant> cows = registry(io.papermc.paper.registry.RegistryKey.COW_VARIANT);
List<Pig.Variant> pigs = registry(io.papermc.paper.registry.RegistryKey.PIG_VARIANT);
List<Chicken.Variant> chickens = registry(io.papermc.paper.registry.RegistryKey.CHICKEN_VARIANT);
List<Frog.Variant> frogs = registry(io.papermc.paper.registry.RegistryKey.FROG_VARIANT);
List<ZombieNautilus.Variant> nautili = registry(io.papermc.paper.registry.RegistryKey.ZOMBIE_NAUTILUS_VARIANT);
for (int i = 0; i < 3; i++) {
double x = i * 4;
setVar(rfSpawn(o, x, 0, 0, 180, Cow.class), cows.get(i));
Cow cb = rfSpawn(o, x + 1.6, 0, 0, 180, Cow.class);
setVar(cb, cows.get(i));
cb.setBaby();
setVar(rfSpawn(o, x, 0, 1.8, 180, Pig.class), pigs.get(i));
Pig pb = rfSpawn(o, x + 1.4, 0, 1.8, 180, Pig.class);
setVar(pb, pigs.get(i));
pb.setBaby();
setVar(rfSpawn(o, x + 2.6, 0, 1.8, 180, Chicken.class), chickens.get(i));
Chicken chb = rfSpawn(o, x + 3.3, 0, 1.8, 180, Chicken.class);
setVar(chb, chickens.get(i));
chb.setBaby();
setVar(rfSpawn(o, x + 0.5, 0, 3.4, 180, Frog.class), frogs.get(i));
}
for (int i = 0; i < nautili.size(); i++)
setVar(rfSpawn(o, 2.5 + i * 3, 0, 4.8, 180, ZombieNautilus.class), nautili.get(i));
faceNorth(o);
}
/** setVariant via reflection (the variant setters' declaring interface differs between API and server). */
private static void setVar(Entity e, Object variant) {
for (java.lang.reflect.Method m : e.getClass().getMethods())
if (m.getName().equals("setVariant") && m.getParameterCount() == 1
&& m.getParameterTypes()[0].isInstance(variant)) {
try {
m.invoke(e, variant);
return;
} catch (ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
throw new IllegalStateException("no setVariant for " + variant);
}
private void rfStands(Location o) {
rfSpawn(o, 0, 0, 0, 180, ArmorStand.class);
rfSpawn(o, 1.5, 0, 0, 180, ArmorStand.class).setArms(true);
rfSpawn(o, 3, 0, 0, 180, ArmorStand.class).setSmall(true);
ArmorStand sa = rfSpawn(o, 4.2, 0, 0, 180, ArmorStand.class);
sa.setSmall(true);
sa.setArms(true);
ArmorStand full = rfSpawn(o, 5.7, 0, 0, 180, ArmorStand.class);
full.setArms(true);
diamondArmor(full);
hands(full, Material.IRON_SWORD, Material.SHIELD);
ArmorStand sfull = rfSpawn(o, 7.2, 0, 0, 180, ArmorStand.class);
sfull.setSmall(true);
sfull.setArms(true);
diamondArmor(sfull);
hands(sfull, Material.TORCH, null);
ArmorStand noArmsHeld = rfSpawn(o, 8.6, 0, 0, 180, ArmorStand.class);
hands(noArmsHeld, Material.DIAMOND, null);
ArmorStand inv = rfSpawn(o, 10, 0, 0, 180, ArmorStand.class);
inv.setArms(true);
inv.setInvisible(true);
diamondArmor(inv);
}
private static void diamondArmor(LivingEntity e) {
e.getEquipment().setHelmet(it(Material.DIAMOND_HELMET));
e.getEquipment().setChestplate(it(Material.DIAMOND_CHESTPLATE));
e.getEquipment().setLeggings(it(Material.DIAMOND_LEGGINGS));
e.getEquipment().setBoots(it(Material.DIAMOND_BOOTS));
}
private void rfChests(Location o) {
rfSpawn(o, 0, 0, 0, 180, Llama.class).setColor(Llama.Color.CREAMY);
Llama lc = rfSpawn(o, 1.6, 0, 0, 180, Llama.class);
lc.setColor(Llama.Color.BROWN);
lc.setCarryingChest(true);
Llama lcc = rfSpawn(o, 3.2, 0, 0, 180, Llama.class);
lcc.setColor(Llama.Color.GRAY);
lcc.setCarryingChest(true);
lcc.getInventory().setDecor(it(Material.RED_CARPET));
TraderLlama tl = rfSpawn(o, 4.8, 0, 0, 180, TraderLlama.class);
tl.setColor(Llama.Color.WHITE);
tl.setCarryingChest(true);
Llama bl = rfSpawn(o, 6.2, 0, 0, 180, Llama.class);
bl.setColor(Llama.Color.CREAMY);
bl.setBaby();
rfSpawn(o, 8, 0, 0, 180, Donkey.class);
Donkey dc = rfSpawn(o, 9.6, 0, 0, 180, Donkey.class);
dc.setCarryingChest(true);
Donkey dcs = rfSpawn(o, 11.2, 0, 0, 180, Donkey.class);
dcs.setCarryingChest(true);
dcs.getInventory().setSaddle(it(Material.SADDLE));
Mule mc = rfSpawn(o, 12.8, 0, 0, 180, Mule.class);
mc.setCarryingChest(true);
Mule mcs = rfSpawn(o, 14.4, 0, 0, 180, Mule.class);
mcs.setCarryingChest(true);
mcs.getInventory().setSaddle(it(Material.SADDLE));
faceNorth(o);
}
private void rfArmor(Location o) {
Class<?>[] wearers = {Zombie.class, Husk.class, Drowned.class, ZombieVillager.class, PigZombie.class,
Skeleton.class, Stray.class, WitherSkeleton.class, Bogged.class, Parched.class, Piglin.class, PiglinBrute.class};
for (int i = 0; i < wearers.length; i++) {
@SuppressWarnings("unchecked")
LivingEntity e = rfSpawn(o, i * 1.25, 0, 0, 180, (Class<LivingEntity>) wearers[i]);
diamondArmor(e);
}
LivingEntity m = (LivingEntity) rfSpawn(o, 0, 0, 2.2, 180, EntityType.MANNEQUIN);
diamondArmor(m);
Zombie bz = rfSpawn(o, 1.3, 0, 2.2, 180, Zombie.class);
bz.setBaby();
diamondArmor(bz);
Piglin bp = rfSpawn(o, 2.5, 0, 2.2, 180, Piglin.class);
bp.setBaby();
diamondArmor(bp);
// Non-wearers given armor: vanilla draws nothing.
Class<?>[] others = {Villager.class, Pillager.class, Vindicator.class, Witch.class, Evoker.class,
Creeper.class, Enderman.class, Cow.class, Fox.class};
for (int i = 0; i < others.length; i++) {
@SuppressWarnings("unchecked")
LivingEntity e = rfSpawn(o, 4 + i * 1.25, 0, 2.2, 180, (Class<LivingEntity>) others[i]);
diamondArmor(e);
}
faceNorth(o);
}
// --- projectiles -----------------------------------------------------------------------------
/** Spawns a projectile at p moving along dir, rotated like vanilla Projectile#shoot (yaw atan2(x,z), pitch up). */