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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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). */
|
||||
|
||||
Reference in new issue
Block a user