Merge pull request #2547 from kacpherek/fix/variant-textures

🤖 Agent: Merged by Ludovic's AI agent (Claude, contrib-gate-19). Thanks @kacpherek: Draw mob variants: rabbit, parrot, axolotl, salmon size and baby models. I checked it against vanilla 26.3, and it passed every harness it reaches in a union with your other PRs, plus lan_e2e in both modes.
This commit is contained in:
Ludovic Marie authored and GitHub committed 2026-10-27 09:36:34 +01:00
commit 0e793be2ac
2 files changed
+132 -4

No files matched your search

+66 -1
View File
@@ -491,7 +491,36 @@ pub fn mob_to_nbt(m: &Mob, uuid: [i32; 4], raw: Option<&Tag>) -> Tag {
crate::mobs::villager::write_nbt(m, map);
}
"minecraft:wandering_trader" => put_offers(map, m),
"minecraft:axolotl" => write_axolotl_memories(m, map),
"minecraft:axolotl" => {
write_axolotl_memories(m, map);
map.insert(
"Variant".into(),
Tag::Int(i32::from(m.extra.variant.min(4))),
);
}
// Rabbit.addAdditionalSaveData (the legacy id; 99 is the killer bunny).
"minecraft:rabbit" => {
map.insert("RabbitType".into(), Tag::Int(i32::from(m.extra.variant)));
}
"minecraft:parrot" => {
map.insert(
"Variant".into(),
Tag::Int(i32::from(m.extra.variant.min(4))),
);
}
"minecraft:salmon" => {
let size = SALMON_SIZES[usize::from(m.extra.variant).min(2)];
map.insert("type".into(), string(size));
}
// TropicalFish.addAdditionalSaveData: the packed variant.
"minecraft:tropical_fish" => {
let pattern = m.extra.variant.min(11);
let packed = i32::from(pattern >= 6)
| i32::from(pattern % 6) << 8
| i32::from(m.extra.variant2 & 15) << 16
| i32::from(m.extra.variant3 & 15) << 24;
map.insert("Variant".into(), Tag::Int(packed));
}
"minecraft:allay" => {
// InventoryCarrier: the fetched stack; the liked player memory.
let mut inv = Vec::new();
@@ -1092,6 +1121,7 @@ pub fn mob_from_nbt(tag: &Tag, types: &HashMap<String, Arc<MobType>>, id: u32) -
if m.species() == crate::mobs::Species::Axolotl {
restore_axolotl_memories(&mut m, tag);
}
read_variant(&mut m, tag);
m.fire_ticks = number(tag.get("Fire")).unwrap_or(0) as i32;
m.air = number(tag.get("Air")).unwrap_or(300) as i32;
if m.species() == crate::mobs::Species::Allay {
@@ -2273,6 +2303,41 @@ fn write_axolotl_memories(m: &Mob, map: &mut Compound) {
}
}
}
/// The variant tags `Axolotl`, `Rabbit`, `Parrot`, `Salmon` and
/// `TropicalFish` read; a missing tag is the default variant (a medium
/// salmon, otherwise index 0).
fn read_variant(m: &mut Mob, tag: &Tag) {
use crate::mobs::Species as S;
let int = |key| number(tag.get(key)).unwrap_or(0) as i32;
match m.species() {
S::Axolotl | S::Parrot => m.extra.variant = int("Variant").clamp(0, 4) as u8,
// Rabbit.Variant.LEGACY_CODEC: an unknown id is brown.
S::Rabbit => {
m.extra.variant = match int("RabbitType") {
v @ (0..=5 | 99) => v as u8,
_ => 0,
}
}
S::Salmon => {
m.extra.variant = text(tag.get("type"))
.and_then(|t| SALMON_SIZES.iter().position(|v| *v == t))
.unwrap_or(1) as u8;
}
S::TropicalFish => {
let packed = int("Variant");
let (size, index) = (packed & 0xFF, (packed >> 8) & 0xFF);
// TropicalFish.Pattern.byId: an unknown id is a kob.
m.extra.variant = if size <= 1 && index < 6 {
(size * 6 + index) as u8
} else {
0
};
m.extra.variant2 = ((packed >> 16) & 15) as u8;
m.extra.variant3 = ((packed >> 24) & 15) as u8;
}
_ => {}
}
}
fn restore_axolotl_memories(m: &mut Mob, tag: &Tag) {
let memories = tag.get("Brain").and_then(|b| b.get("memories"));
let ttl = memories
+66 -3
View File
@@ -177,8 +177,71 @@ pub fn body(m: &Mob) -> (String, String) {
"frog#main".into(),
format!("entity/frog/frog_{}", climate(m)),
),
S::Rabbit => (
baby_or(m, "rabbit#main", "rabbit_baby#main"),
// RabbitRenderer.getTextureLocation: the Toast name tag, else the
// variant (the killer bunny's id is 99).
S::Rabbit => {
let name = if m.custom_name.as_deref() == Some("Toast") {
"toast"
} else {
match m.extra.variant {
1 => "white",
2 => "black",
3 => "white_splotched",
4 => "gold",
5 => "salt",
99 => "caerbannog",
_ => "brown",
}
};
(
baby_or(m, "rabbit#main", "rabbit_baby#main"),
b(&format!("entity/rabbit/rabbit_{name}")),
)
}
// ParrotRenderer.getVariantTexture.
S::Parrot => {
let name = ["red_blue", "blue", "green", "yellow_blue", "grey"]
[usize::from(m.extra.variant).min(4)];
(m.kind.model_key(), format!("entity/parrot/parrot_{name}"))
}
// AxolotlRenderer.getTextureLocation.
S::Axolotl => {
let name =
["lucy", "wild", "gold", "cyan", "blue"][usize::from(m.extra.variant).min(4)];
(
baby_or(m, "axolotl#main", "axolotl_baby#main"),
b(&format!("entity/axolotl/axolotl_{name}")),
)
}
// SalmonRenderer.submit: the model of the salmon's size.
S::Salmon => {
let model = match m.extra.variant {
0 => "salmon_small#main",
2 => "salmon_large#main",
_ => "salmon#main",
};
(model.into(), "entity/fish/salmon".into())
}
// The baby models and textures of AgeableMobRenderer's other
// subclasses.
S::Goat => (
baby_or(m, "goat#main", "goat_baby#main"),
b(&m.kind.texture),
),
S::PolarBear => (
baby_or(m, "polar_bear#main", "polar_bear_baby#main"),
b(&m.kind.texture),
),
S::Ocelot => (
baby_or(m, "ocelot#main", "ocelot_baby#main"),
b(&m.kind.texture),
),
S::Turtle => (
baby_or(m, "turtle#main", "turtle_baby#main"),
b(&m.kind.texture),
),
S::Armadillo => (
baby_or(m, "armadillo#main", "armadillo_baby#main"),
b(&m.kind.texture),
),
S::Pig => {
@@ -241,7 +304,7 @@ pub fn body(m: &Mob) -> (String, String) {
),
S::ZombieVillager => (
baby_or(m, "zombie_villager#main", "zombie_villager_baby#main"),
"entity/zombie_villager/zombie_villager".into(),
b("entity/zombie_villager/zombie_villager"),
),
S::Villager => (
baby_or(m, "villager#main", "villager_baby#main"),