Merge pull request #2007 from rdbtCVS/fix/1962-passenger-animation

🤖 Agent: contrib-gate-17 verified this head (26.3 sources, integration tree build, clippy, tests, parity; details in the PR comment). Thanks @rdbtCVS!
This commit is contained in:
Ludovic Marie
2026-10-26 13:55:12 +01:00
committed by GitHub
4 changed files with 50 additions and 5 deletions
@@ -393,7 +393,7 @@ pub fn main() -> horizon_client::Result<()> {
mob.extra.age = -24000;
}
mob.age = f("ageInTicks") as u32;
let state = State {
let mut state = State {
pos: f("walkAnimationPos"),
speed: f("walkAnimationSpeed"),
age: f("ageInTicks"),
@@ -405,6 +405,20 @@ pub fn main() -> horizon_client::Result<()> {
};
let mut poses = Poses::new(&model);
entity_state(&mut mob, def.species, case);
if let Some(ticks) = case["@walkTicks"].as_array() {
for speed in ticks {
mob.previous_limb_speed = mob.limb_speed;
mob.limb_speed += (speed.as_f64().unwrap() as f32 - mob.limb_speed) * 0.4;
mob.limb_position += mob.limb_speed;
}
if !case["@alive"].as_bool().unwrap() {
mob.health = 0.0;
}
let extracted = anim::state(&mob, f("@partial"));
state.pos = extracted.pos;
state.speed = extracted.speed;
}
let flag = |k: &str| case[k].as_bool().unwrap_or(false);
let num = |k: &str| case[k].as_f64().unwrap_or(0.0) as f32;
let started = |k: &str| AnimationState {
@@ -598,7 +612,11 @@ pub fn main() -> horizon_client::Result<()> {
}
_ => anim::setup(&mob, s, p, &animations),
}
out.push_str(&format!("{}\n", emit(&model, &mut poses, case, layer)));
let mut result = emit(&model, &mut poses, case, layer);
if case["@walkTicks"].is_array() {
result["walk"] = json!([state.pos, state.speed]);
}
out.push_str(&format!("{result}\n"));
}
fs::write(&args[2], out)?;
Ok(())
+4 -3
View File
@@ -189,14 +189,15 @@ pub fn state(m: &Mob, alpha: f32) -> State {
swing += 1.0;
}
let swing = m.anim.attack_anim_o + swing * alpha;
let alive = m.alive();
// LivingEntityRenderer.extractRenderState: riding motion is not walking.
let walking = m.alive() && !m.extra.passenger;
State {
pos: if alive {
pos: if walking {
m.limb_position - m.limb_speed * (1.0 - alpha)
} else {
0.0
},
speed: if alive {
speed: if walking {
(m.previous_limb_speed + (m.limb_speed - m.previous_limb_speed) * alpha).min(1.0)
} else {
0.0
@@ -312,6 +312,19 @@ public final class VanillaEntityAnimOracle {
state.getClass();
for (var entry : kase.entrySet())
if (!entry.getKey().startsWith("@")) set(state, entry.getKey(), entry.getValue());
if (kase.has("@walkTicks")) {
var walk = new net.minecraft.world.entity.WalkAnimationState();
for (var speed : kase.getAsJsonArray("@walkTicks"))
walk.update(speed.getAsFloat(), 0.4F, 1.0F);
// LivingEntity.calculateEntityAnimation stops passengers/dead entities;
// LivingEntityRenderer.extractRenderState also zeros both fields.
if (kase.get("isPassenger").getAsBoolean() || !kase.get("@alive").getAsBoolean())
walk.stop();
float partial = kase.get("@partial").getAsFloat();
set(state, "walkAnimationPos", gson.toJsonTree(walk.position(partial)));
set(state, "walkAnimationSpeed", gson.toJsonTree(walk.speed(partial)));
result.add("walk", gson.toJsonTree(new float[]{walk.position(partial), walk.speed(partial)}));
}
hooks(state, kase);
if (state instanceof net.minecraft.client.renderer.entity.state.ArmedEntityRenderState armed && armed.swingAnimation > 0)
armed.currentSwing = new net.minecraft.world.entity.LivingEntity.SwingDescription(net.minecraft.world.InteractionHand.MAIN_HAND, net.minecraft.world.item.component.SwingAnimation.DEFAULT, 6);
+13
View File
@@ -164,6 +164,15 @@ def cases(layer, model=None):
cases_out = [{**c, **DEFAULTS.get(name, {})} for c in cases_out]
yield from cases_out
yield from species_cases(layer, cases_out)
# Exercise render-state extraction with a live walk clock, including the
# first mounted frame. Existing setupAnim cases bypass anim::state.
if name == 'skeleton':
for passenger in [False, True]:
for alive in [False, True]:
for partial in [0.0, 0.5, 1.0]:
yield {**cases_out[2], 'isPassenger': passenger,
'@walkTicks': [0.5, 1.0, 0.75],
'@alive': alive, '@partial': partial}
part = model and scaled_part(model)
if part:
for index, scale in enumerate(PART_SCALES):
@@ -387,6 +396,10 @@ def main():
skipped.add(layer); continue
checked += 1
bad = []
if 'walk' in v:
for a, b in zip(v['walk'], r['walk']):
if abs(a - b) > args.tolerance * (1.0 + abs(a)):
bad.append(('@walk', f'{v["walk"]} != {r["walk"]}'))
for path, expected in v['parts'].items():
got = r['parts'].get(path)
if got is None: