mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #11451 from MSedgeMC/fix/armadillo-state-save
🤖 Agent: An armadillo's state is saved always and restored through switchToState on load, as Armadillo.add/readAdditionalSaveData do.
This commit is contained in:
3 files changed
+102
-6
No files matched your search
@@ -1122,9 +1122,17 @@ pub fn mob_to_nbt(
|
||||
map.insert("anger".into(), Tag::compound(Vec::<(&str, Tag)>::new()));
|
||||
}
|
||||
}
|
||||
// Armadillo.addAdditionalSaveData: the time left to the next scute.
|
||||
// Armadillo.addAdditionalSaveData: the time left to the next scute, and
|
||||
// the state it is in, which is always written (`idle` when it is not
|
||||
// rolled up).
|
||||
"minecraft:armadillo" => {
|
||||
map.insert("scute_time".into(), Tag::Int(m.extra.egg_time));
|
||||
map.insert(
|
||||
"state".into(),
|
||||
string(crate::mobs::armadillo::state_name(
|
||||
crate::mobs::armadillo::state(m),
|
||||
)),
|
||||
);
|
||||
}
|
||||
"minecraft:ravager" => {
|
||||
map.insert("AttackTick".into(), Tag::Int(m.extra.counter));
|
||||
@@ -2217,11 +2225,18 @@ pub fn mob_from_nbt(
|
||||
st.vibration = crate::sculk::Data::from_nbt(listener);
|
||||
}
|
||||
}
|
||||
// Armadillo.readAdditionalSaveData: keeps the constructor's time when absent.
|
||||
if m.species() == crate::mobs::Species::Armadillo
|
||||
&& let Some(t) = number(tag.get("scute_time"))
|
||||
{
|
||||
m.extra.egg_time = t as i32;
|
||||
// Armadillo.readAdditionalSaveData: the saved state through `switchToState`
|
||||
// (`onSyncedDataUpdated` then restarts the time in that state), idle for a
|
||||
// save that names none; then the time to the next scute, the constructor's
|
||||
// when absent.
|
||||
if m.species() == crate::mobs::Species::Armadillo {
|
||||
crate::mobs::armadillo::set_state(
|
||||
&mut m,
|
||||
crate::mobs::armadillo::state_by_name(text(tag.get("state")).as_deref()),
|
||||
);
|
||||
if let Some(t) = number(tag.get("scute_time")) {
|
||||
m.extra.egg_time = t as i32;
|
||||
}
|
||||
}
|
||||
if m.species() == crate::mobs::Species::Ravager {
|
||||
m.extra.counter = number(tag.get("AttackTick")).unwrap_or(0) as i32;
|
||||
|
||||
@@ -188,6 +188,26 @@ pub fn set_state(m: &mut Mob, state: u8) {
|
||||
a.in_state_ticks = 0;
|
||||
}
|
||||
}
|
||||
/// `ArmadilloState`'s name (`StringRepresentable`, which its `CODEC` writes):
|
||||
/// what `Armadillo.addAdditionalSaveData` stores under `state`.
|
||||
pub fn state_name(state: u8) -> &'static str {
|
||||
match state {
|
||||
ROLLING => "rolling",
|
||||
SCARED => "scared",
|
||||
UNROLLING => "unrolling",
|
||||
_ => "idle",
|
||||
}
|
||||
}
|
||||
/// `ArmadilloState.CODEC` read back: a name that is no state, and no name at
|
||||
/// all, are `IDLE` (`read("state", CODEC).orElse(IDLE)`).
|
||||
pub fn state_by_name(name: Option<&str>) -> u8 {
|
||||
match name {
|
||||
Some("rolling") => ROLLING,
|
||||
Some("scared") => SCARED,
|
||||
Some("unrolling") => UNROLLING,
|
||||
_ => IDLE,
|
||||
}
|
||||
}
|
||||
/// `Armadillo.canStayRolledUp`.
|
||||
fn can_stay_rolled_up(m: &Mob) -> bool {
|
||||
!m.ai.panicking
|
||||
@@ -807,6 +827,65 @@ mod tests {
|
||||
assert!(!source.causes_panic());
|
||||
assert!(!environmental(DamageKind::Campfire));
|
||||
}
|
||||
/// `Armadillo.addAdditionalSaveData` writes `state` (the `ArmadilloState`
|
||||
/// name, always) and `readAdditionalSaveData` switches to it, `idle` when
|
||||
/// the save names none. Horizon models the state and wrote only
|
||||
/// `scute_time`, so a rolled-up armadillo unloaded with its chunk came back
|
||||
/// idle: hits it took for half the damage past the first point cost it the
|
||||
/// whole amount, and its shell went away.
|
||||
#[test]
|
||||
fn a_rolled_up_armadillo_keeps_its_state_through_the_chunk_save() {
|
||||
use crate::entity_nbt::{mob_from_nbt, mob_to_nbt};
|
||||
use horizon_world::nbt::Tag;
|
||||
fn armadillo_tag(state: Option<&str>) -> Tag {
|
||||
let mut fields = vec![
|
||||
("id", Tag::String("minecraft:armadillo".into())),
|
||||
(
|
||||
"Pos",
|
||||
Tag::List(vec![
|
||||
Tag::Double(0.5_f64.to_bits()),
|
||||
Tag::Double(64.0_f64.to_bits()),
|
||||
Tag::Double(0.5_f64.to_bits()),
|
||||
]),
|
||||
),
|
||||
];
|
||||
if let Some(state) = state {
|
||||
fields.push(("state", Tag::String(state.into())));
|
||||
}
|
||||
Tag::compound(fields)
|
||||
}
|
||||
let types = [("minecraft:armadillo".into(), armadillo().kind.clone())].into();
|
||||
// A ball takes half of the damage past the first point, and hides in
|
||||
// its shell once the roll is five ticks in.
|
||||
let mut ball = mob_from_nbt(&armadillo_tag(Some("rolling")), &types, 1, None).unwrap();
|
||||
assert_eq!(state(&ball), ROLLING, "the loaded state");
|
||||
assert_eq!(reduce_damage(&ball, 10.0), 4.5, "a rolling armadillo");
|
||||
ball.extra.armadillo.in_state_ticks = 6;
|
||||
assert!(hiding_in_shell(&ball), "six ticks into the roll");
|
||||
// The save names the state the armadillo is in now, not the one its
|
||||
// compound came in with (`store`, not `storeNullable`).
|
||||
let saved = mob_to_nbt(
|
||||
&ball,
|
||||
[0; 4],
|
||||
Some(&armadillo_tag(Some("idle"))),
|
||||
None,
|
||||
|_| None,
|
||||
);
|
||||
assert_eq!(saved.get("state"), Some(&Tag::String("rolling".into())));
|
||||
let back = mob_from_nbt(&saved, &types, 2, None).unwrap();
|
||||
assert_eq!(state(&back), ROLLING, "the reloaded state");
|
||||
assert_eq!(reduce_damage(&back, 10.0), 4.5);
|
||||
// `switchToState` restarts the time in the state
|
||||
// (`onSyncedDataUpdated`), so the shell goes in again six ticks later.
|
||||
assert_eq!(back.extra.armadillo.in_state_ticks, 0);
|
||||
assert!(!hiding_in_shell(&back), "a reload restarts the roll");
|
||||
// No state, or a name that is no state: `orElse(IDLE)`, full damage.
|
||||
for absent in [None, Some("sneaking")] {
|
||||
let idle = mob_from_nbt(&armadillo_tag(absent), &types, 3, None).unwrap();
|
||||
assert_eq!(state(&idle), IDLE, "{absent:?}");
|
||||
assert_eq!(reduce_damage(&idle, 10.0), 10.0, "{absent:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -35,6 +35,8 @@ AllayAi.shouldDepositItemsAtLikedNoteblock inline crates/horizon-client/src/mobs
|
||||
AllayAi.getLikedPlayerPositionTracker port crates/horizon-client/src/game/allays.rs::Game::allay_liked_player
|
||||
Armadillo.shouldSwitchToScaredState inline crates/horizon-client/src/mobs/armadillo.rs::ball_up_tick rolling past its animation length
|
||||
Armadillo.onSyncedDataUpdated port crates/horizon-client/src/mobs/armadillo.rs::set_state resets in_state_ticks on a state change
|
||||
Armadillo.addAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mob_to_nbt state is written as its ArmadilloState name, always, beside scute_time
|
||||
Armadillo.readAdditionalSaveData inline crates/horizon-client/src/entity_nbt.rs::mob_from_nbt state goes through switchToState, idle when the save names none; scute_time keeps the constructor's value when absent
|
||||
Armadillo.checkArmadilloSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::check_spawn_rules
|
||||
Armadillo.brushOffScute port crates/horizon-client/src/game/creatures.rs::Game::interact_armadillo brush use; dispenser.rs dispense_brush does the same for a dispenser
|
||||
ArmadilloAi.initCoreActivity inline crates/horizon-client/src/mobs/armadillo.rs::register float and panic goals; rolling out is in server_ai_step
|
||||
|
||||
|
Reference in new issue
Block a user