Hatch a ghastling with its own setup, not an adult's

DriedGhastBlock.spawnGhastling calls setBaby(true) before addFreshEntity.
AgeableMob.setBaby -> setAge crosses the age boundary there, so
HappyGhast.ageBoundaryReached runs babyGhastSetup while the mob is still
being hatched: its box, its flying navigation and its brain are a
ghastling's before it joins the level.

Horizon's hatch path wrote extra.age directly after spawn_mob had already
registered the adult's goals (HappyGhastFloat, the tempt goal,
FloatAround) with a ground navigation and the brain stopped, so the
ghastling spent its first tick flying adult goals and only its first
aiStep (ensure_setup) corrected that. age_boundary_reached also had no
HappyGhast arm, so the crossing ran nothing for it at all.

The hatch now goes through finalize::set_age, which dispatches the
crossing to happy_ghast::ensure_setup like the other ageable species'
ageBoundaryReached arms.

The port_map row for HappyGhast.ageBoundaryReached named ensure_setup
without the dispatch that reaches it; it now points at the dispatch.
This commit is contained in:
MSedgeMC committed 2026-11-02 15:24:16 +00:00
1 parent 7504abf07f
commit f395135ea5
4 files changed
+48 -2

No files matched your search

+6 -1
View File
@@ -155,7 +155,12 @@ impl Game<'_> {
if let Some(id) = self.spawn_mob("minecraft:happy_ghast", at, true)
&& let Some(m) = self.mobs.iter_mut().find(|m| m.id == id)
{
m.extra.age = -24000;
// DriedGhastBlock.spawnGhastling sets the baby age before
// addFreshEntity, so ageBoundaryReached runs the ghastling setup
// (`babyGhastSetup`) while the mob is still being hatched: its
// goals, navigation and brain are a ghastling's before its first
// tick, not an adult's until the next aiStep.
horizon_client::mobs::finalize::set_age(m, -24000);
m.yaw = yaw;
m.body_yaw = yaw;
m.head_yaw = yaw;
@@ -863,6 +863,11 @@ fn age_boundary_reached(m: &mut Mob) {
Species::Goat => set_base(m, Attr::AttackDamage, if baby { 1.0 } else { 2.0 }),
Species::Hoglin => set_base(m, Attr::AttackDamage, if baby { 0.5 } else { 6.0 }),
Species::SulfurCube if !baby => sulfur_size(m, 2),
// HappyGhast.ageBoundaryReached: adultGhastSetup or babyGhastSetup,
// then Animal's no-op. A hatched ghastling needs it before it is in
// the level (DriedGhastBlock.spawnGhastling sets the baby age before
// addFreshEntity), or its first tick flies the adult goals.
Species::HappyGhast => super::happy_ghast::ensure_setup(m),
_ => {}
}
}
@@ -667,4 +667,40 @@ mod tests {
fn travel_speed_is_five_thirds_of_the_flying_speed() {
assert!((f64::from(AIR_SPEED) - FLYING_SPEED * 5.0 / 3.0).abs() < 1e-7);
}
/// `HappyGhast.ageBoundaryReached` runs the setup at the age crossing, not
/// at the next tick: `DriedGhastBlock.spawnGhastling` makes its ghastling a
/// baby before `addFreshEntity`, and `AgeableMob.setAge` calls
/// `ageBoundaryReached` there. Without it the hatched ghastling joins the
/// level with the adult's three goals, its ground navigation and its brain
/// stopped, and only its first `aiStep` (`ensure_setup`) fixes that.
#[test]
fn a_hatched_ghastling_is_set_up_as_one_at_the_age_crossing() {
use crate::mobs::Species;
let mut t = (*crate::mobs::sensor_scan_tests::kind()).clone();
t.species = Species::HappyGhast;
let mut m = Mob::new(
1,
std::sync::Arc::new(t),
horizon_core::geometry::Vec3::ZERO,
0.0,
);
// `Mob::new` registers the adult's goals (HappyGhastFloat, its tempt
// goal and FloatAround) for the age-zero ghast it makes.
assert_eq!(m.extra.scratch[0], 2, "an adult's setup at construction");
assert!(m.ai.goals.has("HappyGhastFloatGoal"));
crate::mobs::finalize::set_age(&mut m, -24000);
assert!(m.is_baby());
assert_eq!(m.extra.scratch[0], 1, "babyGhastSetup ran");
assert!(
!m.ai.goals.has("HappyGhastFloatGoal")
&& !m.ai.goals.has("RandomFloatAroundGoal")
&& !m.ai.goals.has("ForNonPathfinders"),
"a ghastling has no goals: its brain drives it"
);
assert_eq!(m.ai.nav.kind, crate::mobs::nav::NavKind::Flying);
assert!(m.ai.nav.can_float);
}
}
+1 -1
View File
@@ -115,7 +115,7 @@ HappyGhast.setServerStillTimeout inline crates/horizon-client/src/mobs/happy_gha
HappyGhast.registerGoals port crates/horizon-client/src/mobs/happy_ghast.rs::setup adult goals: float, tempt (harness and food for a bare adult, food otherwise), float around within 16
HappyGhast.adultGhastSetup port crates/horizon-client/src/mobs/happy_ghast.rs::setup adult half: ghast move control, happy ghast look control, goals, brain stopped
HappyGhast.babyGhastSetup port crates/horizon-client/src/mobs/happy_ghast.rs::setup baby half: flying move control, baby flying navigation, no goals, timeout cleared
HappyGhast.ageBoundaryReached port crates/horizon-client/src/mobs/happy_ghast.rs::ensure_setup reruns setup when the age sign changes
HappyGhast.ageBoundaryReached port crates/horizon-client/src/mobs/finalize.rs::age_boundary_reached reruns the setup (happy_ghast::ensure_setup) when the age sign changes, so a hatched ghastling flies its own goals before its first tick
HappyGhast.createAttributes skip:data attributes come from entity-types.json
HappyGhast.getAmbientSoundInterval port crates/horizon-client/src/mobs.rs::Mob::ambient_interval six times as long while it carries a rider
HappyGhast.canUseSlot inline crates/horizon-client/src/game/ghasts.rs::Game::interact_happy_ghast a harness goes only on a living adult without one; shears take it off
1 Fox.defineSynchedData port crates/horizon-client/src/game/lan.rs::mob_meta gap: minor: the type and the flags byte are synced; the two trusted-entity slots are not
115 HappyGhast.registerGoals port crates/horizon-client/src/mobs/happy_ghast.rs::setup adult goals: float, tempt (harness and food for a bare adult, food otherwise), float around within 16
116 HappyGhast.adultGhastSetup port crates/horizon-client/src/mobs/happy_ghast.rs::setup adult half: ghast move control, happy ghast look control, goals, brain stopped
117 HappyGhast.babyGhastSetup port crates/horizon-client/src/mobs/happy_ghast.rs::setup baby half: flying move control, baby flying navigation, no goals, timeout cleared
118 HappyGhast.ageBoundaryReached port crates/horizon-client/src/mobs/happy_ghast.rs::ensure_setup crates/horizon-client/src/mobs/finalize.rs::age_boundary_reached reruns setup when the age sign changes reruns the setup (happy_ghast::ensure_setup) when the age sign changes, so a hatched ghastling flies its own goals before its first tick
119 HappyGhast.createAttributes skip:data attributes come from entity-types.json
120 HappyGhast.getAmbientSoundInterval port crates/horizon-client/src/mobs.rs::Mob::ambient_interval six times as long while it carries a rider
121 HappyGhast.canUseSlot inline crates/horizon-client/src/game/ghasts.rs::Game::interact_happy_ghast a harness goes only on a living adult without one; shears take it off