mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Horses rear when hurt
AbstractHorse.hurtServer had no port: after a hurt that went through, vanilla draws random.nextInt(3) and on 0 calls standIfPossible, which rears the horse for 20 ticks unless canPerformRearing is false (llamas, camels). Mob::damage_with now ends with that roll for every equine, and make_mad shares the new stand_if_possible. The stand counter runs down in AbstractHorse.tick, with or without AI, but Horizon ran it down only in the AI step, so a NoAI horse that reared from a hit stayed reared for good. A mob with no AI now runs it down too. The mob_ai harness gets a llama_hurt scenario (the draw without the rear), which matched for 10 ticks before and all 201 after; the probe names MountPanic and LlamaHurtBy as vanilla does. Fixes #3432 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
11494e17f9
commit
a30b9525d1
5 files changed
+95
-5
No files matched your search
@@ -226,6 +226,8 @@ fn goal_name(m: &Mob, name: &str) -> String {
|
||||
"FreezeWhenLookedAt" => "EndermanFreezeWhenLookedAt",
|
||||
"LookForPlayer" => "EndermanLookForPlayerGoal",
|
||||
"HealRaiders" => "NearestHealableRaiderTargetGoal",
|
||||
"MountPanic" => "MountPanicGoal",
|
||||
"LlamaHurtBy" => "LlamaHurtByTargetGoal",
|
||||
other => return other.to_owned(),
|
||||
};
|
||||
vanilla.to_owned()
|
||||
|
||||
@@ -2234,6 +2234,10 @@ impl Mob {
|
||||
{
|
||||
aquatic::spawn_ink(self);
|
||||
}
|
||||
// AbstractHorse.hurtServer: the rear roll after the hurt.
|
||||
if equine::is_equine(self.species()) {
|
||||
equine::after_hurt_server(self);
|
||||
}
|
||||
true
|
||||
}
|
||||
/// A splash or cloud's harming effect on this mob: the potion's
|
||||
|
||||
@@ -493,6 +493,13 @@ pub fn tick(
|
||||
if m.species() == Species::Endermite {
|
||||
endermite_lifetime(m);
|
||||
}
|
||||
// AbstractHorse.tick runs the stand counter down with or without AI.
|
||||
if super::equine::is_equine(m.species())
|
||||
&& !super::camel::is_camel(m.species())
|
||||
&& !super::llama::is_llama(m.species())
|
||||
{
|
||||
super::equine::stand_tick(m);
|
||||
}
|
||||
} else {
|
||||
ai_step(m, w, level_random, events);
|
||||
}
|
||||
|
||||
@@ -283,15 +283,27 @@ impl Goal for RunAroundLikeCrazy {
|
||||
make_mad(m, cx.events);
|
||||
}
|
||||
}
|
||||
/// `AbstractHorse.standIfPossible`: `setStanding(20)` (which also stops the
|
||||
/// eating) when `canPerformRearing`.
|
||||
fn stand_if_possible(m: &mut Mob) {
|
||||
if can_rear(m.species()) {
|
||||
m.extra.counter = 20;
|
||||
m.extra.flag = false;
|
||||
}
|
||||
}
|
||||
/// `AbstractHorse.hurtServer`, after the hurt went through: one hit in three
|
||||
/// rears the horse. Llamas and camels make the draw too, but never rear.
|
||||
pub fn after_hurt_server(m: &mut Mob) {
|
||||
if m.random.next_int_bound(3).unwrap_or(1) == 0 {
|
||||
stand_if_possible(m);
|
||||
}
|
||||
}
|
||||
/// `AbstractHorse.makeMad`: rear with the angry sound.
|
||||
pub fn make_mad(m: &mut Mob, events: &mut Vec<MobEvent>) {
|
||||
if m.extra.counter > 0 {
|
||||
return;
|
||||
}
|
||||
if can_rear(m.species()) {
|
||||
m.extra.counter = 20;
|
||||
m.extra.flag = false;
|
||||
}
|
||||
stand_if_possible(m);
|
||||
// getAngrySound: null (silent) for the skeleton horse.
|
||||
let sound = match m.species() {
|
||||
Species::SkeletonHorse => return,
|
||||
@@ -591,10 +603,15 @@ pub fn ai_step(m: &mut Mob, w: &MobWorld, events: &mut Vec<MobEvent>) {
|
||||
m.extra.flag = false;
|
||||
}
|
||||
}
|
||||
stand_tick(m);
|
||||
animate(m);
|
||||
}
|
||||
/// `AbstractHorse.tick`: the stand counter runs down to `clearStanding`. It is
|
||||
/// in `tick`, not `aiStep`, so a horse with no AI stops rearing too.
|
||||
pub fn stand_tick(m: &mut Mob) {
|
||||
if m.extra.counter > 0 {
|
||||
m.extra.counter -= 1;
|
||||
}
|
||||
animate(m);
|
||||
}
|
||||
/// `AbstractHorse.tick`'s eat, stand and mouth animations, which follow the
|
||||
/// eating and standing flags (`flag` and `counter > 0`).
|
||||
@@ -784,3 +801,58 @@ pub fn anim(m: &Mob, s: &State, p: &mut Poses) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mobs::Damage;
|
||||
use crate::mobs::sensor_scan_tests::kind;
|
||||
use horizon_core::geometry::Vec3;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn equine(id: u32, species: Species, name: &str) -> Mob {
|
||||
let mut kind = kind();
|
||||
let k = Arc::make_mut(&mut kind);
|
||||
k.id = name.into();
|
||||
k.species = species;
|
||||
k.max_health = 30.0;
|
||||
Mob::new(id, kind, Vec3::new(f64::from(id), 64.0, 0.0), 0.0)
|
||||
}
|
||||
/// Hits `n` fresh mobs of a species once each; how many reared.
|
||||
fn rears(species: Species, name: &str, n: u32) -> u32 {
|
||||
(1..=n)
|
||||
.filter(|&id| {
|
||||
let mut m = equine(id, species, name);
|
||||
assert!(m.damage(1.0, Damage::player([0.0, 64.0, 0.0])));
|
||||
m.extra.counter == 20
|
||||
})
|
||||
.count() as u32
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hurt_horse_rears_one_hit_in_three() {
|
||||
// AbstractHorse.hurtServer: nextInt(3) == 0 -> standIfPossible (#3432).
|
||||
let n = rears(Species::Horse, "minecraft:horse", 300);
|
||||
assert!((60..=140).contains(&n), "{n} of 300 hits reared");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_rear_ends_after_twenty_ticks() {
|
||||
// AbstractHorse.tick: standCounter runs down, then clearStanding.
|
||||
let mut m = equine(1, Species::Horse, "minecraft:horse");
|
||||
stand_if_possible(&mut m);
|
||||
for _ in 0..19 {
|
||||
stand_tick(&mut m);
|
||||
}
|
||||
assert!(m.extra.counter > 0);
|
||||
stand_tick(&mut m);
|
||||
assert_eq!(m.extra.counter, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn llamas_and_camels_never_rear_when_hurt() {
|
||||
// Llama and Camel: canPerformRearing is false.
|
||||
assert_eq!(rears(Species::Llama, "minecraft:llama", 60), 0);
|
||||
assert_eq!(rears(Species::Camel, "minecraft:camel", 60), 0);
|
||||
}
|
||||
}
|
||||
@@ -278,6 +278,11 @@ def scenarios():
|
||||
('witch', 176), ('vindicator', 177), ('zombie', 178)]:
|
||||
add(f'{species}_deep_water', 160, ['fill -4 -64 -4 4 -61 4 minecraft:water',
|
||||
f'mob minecraft:{species} 0.5 -62 0.5 0 {seed} persistent'])
|
||||
# AbstractHorse.hurtServer (#3432): every hurt draws nextInt(3), and a 0 rears the horse
|
||||
# (standIfPossible). A llama makes the draw too but never rears (canPerformRearing). A horse
|
||||
# scenario waits for #2720: a rearing horse is immobile (AbstractHorse.isImmobile).
|
||||
add('llama_hurt', 200, ['player 4.5 -60 0.5', 'mob minecraft:llama 0.5 -60 0.5 0 182 persistent']
|
||||
+ [f'hurt {t} 0' for t in range(10, 190, 25)])
|
||||
# Axolotls (#64): idle swimming, hunting a cod, and playing dead after hits, in a pool sunk into
|
||||
# the ground so no water flows out of it (the probe doesn't tick fluids).
|
||||
POOL = 'fill -5 -63 -5 5 -61 5 minecraft:water'
|
||||
|
||||
Reference in new issue
Block a user