mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-10 10:31:29 -10:00
Review: the death forgiveness follows tellNeutralMobsThatIDied
Three corrections from the PR review, checked against 26.3: - The area is the dying player's block cell grown 32 blocks sideways and 10 up and down (new AABB(blockPosition()).inflate(32, 10, 32)), not the player's box grown 10 on every axis, which never reached the mobs 11 to 32 blocks out. - Only a mob whose persistent anger is at the dying player is forgiven (NeutralMob.playerDied's getPersistentAngerTarget gate): a wolf angry at a skeleton keeps its anger instead of losing the timer behind a live target, a state vanilla never has. The clear is then full — the last hurt by, the persistent target, the target and the end time — with the polar bear ending its flag2 through its own stop. - The NeutralMob classes are 26.3's six: bee, enderman, iron golem, polar bear, wolf, zombified piglin. The panda, spider and trader llama are not implementers any more. The enderman's gate is its player target (its stare aggro), the polar bear's is flag2 behind the target, the rest anger and a player target, the bee its own state's angry_at. Co-Authored-By: JustKarkat's AI agent (ZCode)
This commit is contained in:
1 parent
bdac04d6ca
commit
8d93530931
3 files changed
+105
-46
No files matched your search
@@ -5499,24 +5499,20 @@ impl Game<'_> {
|
||||
// Inventory.dropAll: every stack thrown in a random direction.
|
||||
let feet = [player.position.x, player.position.y, player.position.z];
|
||||
let at = [feet[0], feet[1] + 1.32, feet[2]];
|
||||
// ServerPlayer.die: the mobs of NeutralMob classes within 10
|
||||
// blocks of the dying player forget it when the gamerule allows.
|
||||
let w = f64::from(player.dimensions.width) / 2.0;
|
||||
let forgiving = self.ui.rules.bool("forgive_dead_players").then(|| {
|
||||
Aabb::new(
|
||||
Vec3::new(feet[0] - w, feet[1], feet[2] - w),
|
||||
Vec3::new(
|
||||
feet[0] + w,
|
||||
feet[1] + f64::from(player.dimensions.height),
|
||||
feet[2] + w,
|
||||
),
|
||||
)
|
||||
.inflate(Vec3::new(10.0, 10.0, 10.0))
|
||||
});
|
||||
// ServerPlayer.die: tellNeutralMobsThatIDied — every NeutralMob
|
||||
// in the dying player's block cell grown 32 sideways and 10 up
|
||||
// and down stops being angry at the player, when the gamerule
|
||||
// lets it and the mob's anger is at this player.
|
||||
let forgiving = self
|
||||
.ui
|
||||
.rules
|
||||
.bool("forgive_dead_players")
|
||||
.then(|| horizon_client::mobs::ai::forgiveness_area(feet));
|
||||
let killer = self.combat_killer_kind();
|
||||
if let Some(area) = forgiving {
|
||||
for m in &mut self.mobs {
|
||||
if horizon_client::mobs::ai::is_neutral(m.species())
|
||||
&& horizon_client::mobs::ai::angry_at_player(m)
|
||||
&& area.intersects(m.bounds())
|
||||
{
|
||||
horizon_client::mobs::ai::stop_being_angry(m);
|
||||
|
||||
@@ -13,7 +13,7 @@ use super::{
|
||||
};
|
||||
use crate::blocks::Fluid;
|
||||
use horizon_core::{
|
||||
geometry::Vec3,
|
||||
geometry::{Aabb, Vec3},
|
||||
random::{LegacyRandom, RandomSource},
|
||||
};
|
||||
|
||||
@@ -1802,33 +1802,53 @@ pub fn after_hurt(m: &mut Mob, source: &Damage) {
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
/// The `NeutralMob` classes: they stop being angry when the player they are
|
||||
/// angry at dies nearby (`forgive_dead_players`).
|
||||
/// The `NeutralMob` classes of 26.3: they stop being angry when the player
|
||||
/// they are angry at dies nearby (`forgive_dead_players`).
|
||||
pub fn is_neutral(s: Species) -> bool {
|
||||
matches!(
|
||||
s,
|
||||
Species::Bee
|
||||
| Species::Enderman
|
||||
| Species::IronGolem
|
||||
| Species::Panda
|
||||
| Species::PolarBear
|
||||
| Species::Spider
|
||||
| Species::TraderLlama
|
||||
| Species::Wolf
|
||||
| Species::ZombifiedPiglin
|
||||
)
|
||||
}
|
||||
/// `NeutralMob.stopBeingAngry`: the anger runs out at once and the target is
|
||||
/// forgotten. Bees keep their anger in their own state; a target that is not
|
||||
/// the player (an iron golem defending its village) is not the anger.
|
||||
/// Whether the mob's persistent anger is at the player
|
||||
/// (`NeutralMob.getPersistentAngerTarget`), `playerDied`'s gate.
|
||||
pub fn angry_at_player(m: &Mob) -> bool {
|
||||
match m.species() {
|
||||
Species::Bee => super::bee::get(m).is_some_and(|b| b.angry_at == Some(Who::Player)),
|
||||
// The polar bear's and enderman's anger is their player target: the
|
||||
// bear marks it with flag2, the enderman holds it while stared at.
|
||||
Species::PolarBear => m.extra.flag2 && m.ai.target == Some(Who::Player),
|
||||
Species::Enderman => m.ai.target == Some(Who::Player),
|
||||
_ => m.extra.anger > 0 && m.ai.target == Some(Who::Player),
|
||||
}
|
||||
}
|
||||
/// `NeutralMob.stopBeingAngry`, for a mob `angry_at_player` let through: the
|
||||
/// last hurt by, the persistent target and the target all go, the end time
|
||||
/// is -1. Bees keep their anger in their own state, and the polar bear ends
|
||||
/// its own flags with it.
|
||||
pub fn stop_being_angry(m: &mut Mob) {
|
||||
if m.species() == Species::Bee {
|
||||
return super::bee::stop_being_angry(m);
|
||||
match m.species() {
|
||||
Species::Bee => return super::bee::stop_being_angry(m),
|
||||
Species::PolarBear => return super::polar_bear::stop_being_angry(m),
|
||||
_ => {}
|
||||
}
|
||||
m.ai.last_hurt_by = None;
|
||||
m.extra.anger = 0;
|
||||
if m.ai.target == Some(Who::Player) {
|
||||
m.ai.target = None;
|
||||
}
|
||||
m.extra.anger_end = -1;
|
||||
m.extra.angry_at = None;
|
||||
m.ai.target = None;
|
||||
}
|
||||
/// `ServerPlayer.tellNeutralMobsThatIDied`'s area: the dying player's block
|
||||
/// cell grown 32 blocks sideways and 10 up and down.
|
||||
pub fn forgiveness_area(feet: [f64; 3]) -> Aabb {
|
||||
let (x, y, z) = (feet[0].floor(), feet[1].floor(), feet[2].floor());
|
||||
Aabb::new(Vec3::new(x, y, z), Vec3::new(x + 1.0, y + 1.0, z + 1.0))
|
||||
.inflate(Vec3::new(32.0, 10.0, 32.0))
|
||||
}
|
||||
/// Mob.doHurtTarget: attack damage (+ species extras) to the target.
|
||||
pub fn do_hurt_target(m: &mut Mob, who: Who, cx: &mut Cx) {
|
||||
@@ -2302,48 +2322,74 @@ mod forgiveness_tests {
|
||||
Mob::new(1, Arc::new(t), Vec3::ZERO, 0.0)
|
||||
}
|
||||
|
||||
/// The dying player's forgiveness covers the `NeutralMob` classes and no
|
||||
/// others: piglins' anger is Brain-based and zombies are always hostile.
|
||||
/// The dying player's forgiveness covers the six `NeutralMob` classes of
|
||||
/// 26.3 and no others: a panda, a spider and a trader llama are not one
|
||||
/// any more, piglins' anger is Brain-based and zombies are always
|
||||
/// hostile.
|
||||
#[test]
|
||||
fn the_neutral_classes_match_vanilla() {
|
||||
for s in [
|
||||
Species::Bee,
|
||||
Species::Enderman,
|
||||
Species::IronGolem,
|
||||
Species::Panda,
|
||||
Species::PolarBear,
|
||||
Species::Spider,
|
||||
Species::TraderLlama,
|
||||
Species::Wolf,
|
||||
Species::ZombifiedPiglin,
|
||||
] {
|
||||
assert!(is_neutral(s), "{s:?}");
|
||||
}
|
||||
for s in [Species::Piglin, Species::Zombie, Species::Cow] {
|
||||
for s in [
|
||||
Species::Panda,
|
||||
Species::Spider,
|
||||
Species::TraderLlama,
|
||||
Species::Piglin,
|
||||
Species::Zombie,
|
||||
Species::Cow,
|
||||
] {
|
||||
assert!(!is_neutral(s), "{s:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// `NeutralMob.stopBeingAngry`: the anger and a player target go, but a
|
||||
/// village-defence target (an iron golem's zombie) is not the anger.
|
||||
/// `NeutralMob.playerDied` only forgives a mob whose persistent anger is
|
||||
/// at the dying player; `stopBeingAngry` then clears the hurt by, the
|
||||
/// persistent target, the target and the timer fully.
|
||||
#[test]
|
||||
fn stopping_the_anger_keeps_other_targets() {
|
||||
fn only_the_mobs_angry_at_the_player_are_forgiven() {
|
||||
let mut wolf = mob(Species::Wolf);
|
||||
wolf.extra.anger = 500;
|
||||
// A wolf angry at a skeleton keeps its anger.
|
||||
wolf.ai.target = Some(Who::Mob(7));
|
||||
assert!(!angry_at_player(&wolf));
|
||||
|
||||
wolf.ai.target = Some(Who::Player);
|
||||
wolf.ai.last_hurt_by = Some(Who::Player);
|
||||
assert!(angry_at_player(&wolf));
|
||||
stop_being_angry(&mut wolf);
|
||||
assert_eq!(wolf.extra.anger, 0);
|
||||
assert_eq!(wolf.extra.anger_end, -1);
|
||||
assert_eq!(wolf.extra.angry_at, None);
|
||||
assert_eq!(wolf.ai.target, None);
|
||||
|
||||
let mut golem = mob(Species::IronGolem);
|
||||
golem.extra.anger = 500;
|
||||
golem.ai.target = Some(Who::Mob(7));
|
||||
stop_being_angry(&mut golem);
|
||||
assert_eq!(golem.extra.anger, 0);
|
||||
assert_eq!(golem.ai.target, Some(Who::Mob(7)));
|
||||
assert_eq!(wolf.ai.last_hurt_by, None);
|
||||
}
|
||||
|
||||
/// A bee keeps its anger in its own state: the same call ends it there.
|
||||
/// The polar bear's anger is its player target behind flag2, and its own
|
||||
/// stop ends the flag with the rest.
|
||||
#[test]
|
||||
fn a_polar_bears_flag_ends_with_the_rest() {
|
||||
let mut bear = mob(Species::PolarBear);
|
||||
bear.extra.anger = 500;
|
||||
bear.extra.flag2 = true;
|
||||
bear.ai.target = Some(Who::Player);
|
||||
bear.ai.last_hurt_by = Some(Who::Player);
|
||||
assert!(angry_at_player(&bear));
|
||||
stop_being_angry(&mut bear);
|
||||
assert!(!bear.extra.flag2);
|
||||
assert_eq!(bear.extra.anger, 0);
|
||||
assert_eq!(bear.ai.target, None);
|
||||
assert_eq!(bear.ai.last_hurt_by, None);
|
||||
}
|
||||
|
||||
/// A bee's anger is its own state's target: the same call ends it there.
|
||||
#[test]
|
||||
fn a_bees_anger_ends_with_the_same_call() {
|
||||
let mut bee = mob(Species::Bee);
|
||||
@@ -2356,4 +2402,21 @@ mod forgiveness_tests {
|
||||
assert_eq!(b.angry_at, None);
|
||||
assert_eq!(b.anger_end, -1);
|
||||
}
|
||||
|
||||
/// `tellNeutralMobsThatIDied`'s area: the block cell grown 32 sideways
|
||||
/// and 10 up and down, so a wolf 20 blocks out is in it, one 34 out or
|
||||
/// 12 up is not.
|
||||
#[test]
|
||||
fn the_area_reaches_twenty_blocks_out_but_not_twelve_up() {
|
||||
let area = forgiveness_area([0.0, 64.0, 0.0]);
|
||||
let mut wolf = mob(Species::Wolf);
|
||||
wolf.position = Vec3::new(20.0, 64.0, 0.0);
|
||||
assert!(area.intersects(wolf.bounds()));
|
||||
wolf.position = Vec3::new(34.0, 64.0, 0.0);
|
||||
assert!(!area.intersects(wolf.bounds()));
|
||||
wolf.position = Vec3::new(0.0, 76.0, 0.0);
|
||||
assert!(!area.intersects(wolf.bounds()));
|
||||
wolf.position = Vec3::new(0.0, 69.0, 0.0);
|
||||
assert!(area.intersects(wolf.bounds()));
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ pub fn ai_step(m: &mut Mob, w: &MobWorld) {
|
||||
}
|
||||
}
|
||||
/// `NeutralMob.stopBeingAngry`.
|
||||
fn stop_being_angry(m: &mut Mob) {
|
||||
pub(super) fn stop_being_angry(m: &mut Mob) {
|
||||
m.ai.last_hurt_by = None;
|
||||
m.ai.target = None;
|
||||
m.extra.anger = 0;
|
||||
|
||||
Reference in new issue
Block a user