mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 17:19:15 -10:00
Merge pull request #9233 from glitchsnake/fix/8703-breeze-invulnerable-to-breeze
🤖 Agent: matches Breeze.isInvulnerableTo (source.getEntity() instanceof Breeze, the owner for projectiles); main merged (trident/bee conflicts taken from main plus attacker_species, and main's new damage_type test literal filled in). The older wind-charge special case in game/wind.rs now duplicates breeze::accepts_damage: follow-up. verify green at this head.
This commit is contained in:
20 files changed
+243
-6
No files matched your search
@@ -236,6 +236,7 @@ pub fn hurt_mobs_with_kind(mobs: &mut [Mob], position: Vec3, amount: f32, kind:
|
||||
kind,
|
||||
attacker: None,
|
||||
from: Some([position.x, position.y, position.z]),
|
||||
attacker_species: None,
|
||||
};
|
||||
for mob in mobs
|
||||
.iter_mut()
|
||||
|
||||
@@ -210,10 +210,15 @@ impl Game<'_> {
|
||||
Target::Mob(i) => Some(Who::Mob(i)),
|
||||
_ => None,
|
||||
});
|
||||
let attacker_species = match attacker {
|
||||
Some(Who::Mob(i)) => self.mobs.iter().find(|m| m.id == i).map(|m| m.species()),
|
||||
_ => None,
|
||||
};
|
||||
let damage = Damage {
|
||||
kind: Self::damage_kind(&ty),
|
||||
attacker,
|
||||
from,
|
||||
attacker_species,
|
||||
};
|
||||
// DamageSource.getEntity is the causing player, even when
|
||||
// another client issued the command or supplied the direct entity.
|
||||
|
||||
@@ -1723,7 +1723,12 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
/// Melee or ranged damage to another mob; whether the hit landed.
|
||||
fn mob_hits_mob(&mut self, target: u32, amount: f32, source: Damage) -> bool {
|
||||
fn mob_hits_mob(&mut self, target: u32, amount: f32, mut source: Damage) -> bool {
|
||||
if source.attacker_species.is_none()
|
||||
&& let Some(Who::Mob(id)) = source.attacker
|
||||
{
|
||||
source.attacker_species = self.mobs.iter().find(|m| m.id == id).map(|m| m.species());
|
||||
}
|
||||
let mut killed = false;
|
||||
let mut hurt = false;
|
||||
let mut infested = false;
|
||||
@@ -2402,6 +2407,7 @@ impl Game<'_> {
|
||||
kind: DamageKind::SonicBoom,
|
||||
attacker: Some(Who::Mob(attacker)),
|
||||
from: Some(from),
|
||||
attacker_species: Some(Species::Warden),
|
||||
};
|
||||
let hurt = self.mob_hits_mob(id, 10.0, source);
|
||||
let resistance = self
|
||||
@@ -4733,10 +4739,15 @@ impl Game<'_> {
|
||||
}
|
||||
_ => DamageKind::Thrown,
|
||||
};
|
||||
let attacker_species = match p.owner {
|
||||
Some(Who::Mob(id)) => self.mobs.iter().find(|m| m.id == id).map(|m| m.species()),
|
||||
_ => None,
|
||||
};
|
||||
let source = Damage {
|
||||
kind: source_kind,
|
||||
attacker: p.owner,
|
||||
from: Some([p.previous.x, p.previous.y, p.previous.z]),
|
||||
attacker_species,
|
||||
};
|
||||
let aim = self.attacker_aim(source.attacker);
|
||||
match (&p.kind, hit) {
|
||||
@@ -7956,6 +7967,7 @@ mod damage_type_tests {
|
||||
kind: DamageKind::Named(named),
|
||||
attacker,
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
}),
|
||||
"example:fan_fire"
|
||||
);
|
||||
@@ -7970,6 +7982,7 @@ mod damage_type_tests {
|
||||
kind: DamageKind::Explosion,
|
||||
attacker,
|
||||
from: Some([0.0; 3]),
|
||||
attacker_species: None,
|
||||
};
|
||||
let creeper = blast(Some(Who::Mob(7)));
|
||||
assert_eq!(source_damage_type(creeper), "minecraft:player_explosion");
|
||||
|
||||
@@ -462,6 +462,7 @@ fn is_fire(kind: DamageKind) -> bool {
|
||||
kind,
|
||||
attacker: None,
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
};
|
||||
source.is_fire()
|
||||
}
|
||||
@@ -1366,6 +1367,7 @@ impl Game<'_> {
|
||||
kind,
|
||||
attacker: by_player.then_some(Who::Player),
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
});
|
||||
// `damageSources().explosion(crystal, player)`: the crystal is the direct entity.
|
||||
if kind == DamageKind::Explosion {
|
||||
|
||||
@@ -666,6 +666,7 @@ impl Game<'_> {
|
||||
.filter(|&o| o != m.id)
|
||||
.map(horizon_client::mobs::Who::Mob),
|
||||
from: Some(center),
|
||||
attacker_species: cause,
|
||||
};
|
||||
// `EXPLOSION_KNOCKBACK_RESISTANCE`, not `KNOCKBACK_RESISTANCE`:
|
||||
// its base is 0 for every mob type, so iron golems and
|
||||
|
||||
@@ -167,6 +167,7 @@ impl Game<'_> {
|
||||
kind: horizon_client::mobs::DamageKind::Magic,
|
||||
attacker: owner.map(horizon_client::mobs::Who::Mob),
|
||||
from: Some(f.pos),
|
||||
attacker_species: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -547,6 +547,7 @@ impl Game<'_> {
|
||||
kind: DamageKind::Generic,
|
||||
attacker: None,
|
||||
from: Some(b.at),
|
||||
attacker_species: None,
|
||||
};
|
||||
let mut struck = Vec::new();
|
||||
let mut events = Vec::new();
|
||||
|
||||
@@ -10993,6 +10993,7 @@ mod tests {
|
||||
kind,
|
||||
attacker,
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
});
|
||||
(s.damage_type, s.causing, s.direct)
|
||||
};
|
||||
|
||||
@@ -775,6 +775,7 @@ impl Game<'_> {
|
||||
kind: DamageKind::Arrow,
|
||||
attacker: Some(Who::Player),
|
||||
from,
|
||||
attacker_species: None,
|
||||
},
|
||||
aim,
|
||||
[t.velocity.x, t.velocity.z],
|
||||
|
||||
@@ -156,6 +156,7 @@ impl Game<'_> {
|
||||
kind: DamageKind::Thrown,
|
||||
attacker: p.owner,
|
||||
from: Some([p.previous.x, p.previous.y, p.previous.z]),
|
||||
attacker_species: owner_breeze.then_some(Species::Breeze),
|
||||
};
|
||||
if let Some(m) = self.mobs.iter_mut().find(|m| m.id == id) {
|
||||
// Breeze.isInvulnerableTo: its own kind's charges.
|
||||
|
||||
@@ -192,14 +192,15 @@ impl Game<'_> {
|
||||
self.hit_special(id, amount, by_player, kind, Some(&p.kind));
|
||||
}
|
||||
Some(Who::Mob(id)) => {
|
||||
let owner_species = owner
|
||||
.and_then(|o| self.mobs.iter().find(|m| m.id == o))
|
||||
.map(|m| m.species());
|
||||
let source = Damage {
|
||||
kind,
|
||||
attacker: live_owner,
|
||||
from: skull.from,
|
||||
attacker_species: owner_species,
|
||||
};
|
||||
let owner_species = owner
|
||||
.and_then(|o| self.mobs.iter().find(|m| m.id == o))
|
||||
.map(|m| m.species());
|
||||
let killed = match self.mobs.iter_mut().find(|m| m.id == id) {
|
||||
// `WitherBoss.hurtServer`: nothing from a wither friend.
|
||||
Some(m)
|
||||
|
||||
@@ -1222,6 +1222,7 @@ mod tests {
|
||||
kind: DamageKind::PlayerAttack,
|
||||
attacker: None,
|
||||
from: Some([3.0, 0.0, 0.0]),
|
||||
attacker_species: None,
|
||||
};
|
||||
// Canceled: no damage, cooldown, sound or knockback.
|
||||
let mut kept = mob(9001);
|
||||
|
||||
@@ -755,6 +755,7 @@ pub struct Damage {
|
||||
pub kind: DamageKind,
|
||||
pub attacker: Option<Who>,
|
||||
pub from: Option<[f64; 3]>,
|
||||
pub attacker_species: Option<Species>,
|
||||
}
|
||||
impl Damage {
|
||||
pub fn fall() -> Self {
|
||||
@@ -762,6 +763,7 @@ impl Damage {
|
||||
kind: DamageKind::Fall,
|
||||
attacker: None,
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
pub fn of(kind: DamageKind) -> Self {
|
||||
@@ -769,6 +771,7 @@ impl Damage {
|
||||
kind,
|
||||
attacker: None,
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
pub fn player(from: [f64; 3]) -> Self {
|
||||
@@ -776,6 +779,7 @@ impl Damage {
|
||||
kind: DamageKind::PlayerAttack,
|
||||
attacker: Some(Who::Player),
|
||||
from: Some(from),
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
/// `DamageSources.mace`: a player's smash attack.
|
||||
@@ -784,6 +788,7 @@ impl Damage {
|
||||
kind: DamageKind::MaceSmash,
|
||||
attacker: Some(Who::Player),
|
||||
from: Some(from),
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
/// `DamageSources.spear`: a player's stab with a spear.
|
||||
@@ -792,6 +797,7 @@ impl Damage {
|
||||
kind: DamageKind::Spear,
|
||||
attacker: Some(Who::Player),
|
||||
from: Some(from),
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
/// `DamageSources.indirectMagic(potion, owner)`: `attacker` is the owner
|
||||
@@ -802,6 +808,7 @@ impl Damage {
|
||||
kind: DamageKind::IndirectMagic,
|
||||
attacker,
|
||||
from: Some(from),
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
/// `DamageSources.fireworks(rocket, owner)`: `attacker` is the owner
|
||||
@@ -812,6 +819,7 @@ impl Damage {
|
||||
kind: DamageKind::Fireworks,
|
||||
attacker,
|
||||
from: Some(from),
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
pub fn mob(id: u32, from: [f64; 3]) -> Self {
|
||||
@@ -819,8 +827,13 @@ impl Damage {
|
||||
kind: DamageKind::MobAttack,
|
||||
attacker: Some(Who::Mob(id)),
|
||||
from: Some(from),
|
||||
attacker_species: None,
|
||||
}
|
||||
}
|
||||
pub fn with_species(mut self, species: Species) -> Self {
|
||||
self.attacker_species = Some(species);
|
||||
self
|
||||
}
|
||||
/// DamageTypeTags.PANIC_CAUSES
|
||||
pub fn causes_panic(&self) -> bool {
|
||||
if let DamageKind::Named(named) = self.kind {
|
||||
@@ -4732,6 +4745,7 @@ mod fire_resistance_damage_tests {
|
||||
kind: DamageKind::Fireball,
|
||||
attacker: Some(Who::Player),
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
};
|
||||
assert!(mob.damage(1.0, source));
|
||||
assert_eq!(mob.health, 10.0);
|
||||
@@ -4817,6 +4831,7 @@ mod magic_absorb_tests {
|
||||
kind: DamageKind::IndirectMagic,
|
||||
attacker: Some(Who::Mob(m.id)),
|
||||
from: None,
|
||||
attacker_species: Some(Species::Witch),
|
||||
};
|
||||
m.damage(20.0, own);
|
||||
assert_eq!(m.health, 10.0);
|
||||
@@ -4885,6 +4900,7 @@ mod armor_hurt_tests {
|
||||
kind: DamageKind::MobAttack,
|
||||
attacker: None,
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
};
|
||||
let mut m = horse();
|
||||
m.extra.body = Some("minecraft:diamond_horse_armor".into());
|
||||
|
||||
@@ -2436,6 +2436,7 @@ pub fn walk_target_value(m: &Mob, w: &MobWorld, p: [i32; 3]) -> f32 {
|
||||
/// Species damage filters (LivingEntity.hurtServer overrides).
|
||||
pub fn accepts_damage(m: &mut Mob, source: &Damage, _amount: f32) -> bool {
|
||||
match m.species() {
|
||||
Species::Breeze => super::breeze::accepts_damage(m, source),
|
||||
// Allay.hurtServer: its liked player cannot hurt it.
|
||||
Species::Allay if m.extra.tame && source.attacker == Some(Who::Player) => false,
|
||||
Species::Creaking => super::creaking::accepts_damage(m, source),
|
||||
@@ -2526,7 +2527,7 @@ pub fn do_hurt_target(m: &mut Mob, who: Who, cx: &mut Cx) {
|
||||
}
|
||||
let mut amount = super::gear::attack_damage(m, cx.w.items);
|
||||
let from = [m.position.x, m.position.y, m.position.z];
|
||||
let source = Damage::mob(m.id, from);
|
||||
let source = Damage::mob(m.id, from).with_species(m.species());
|
||||
let first_event = cx.events.len();
|
||||
// `CaveSpider`, `Husk` and `WitherSkeleton.doHurtTarget` all test
|
||||
// `target instanceof LivingEntity`, so the effect reaches a mob the hit
|
||||
|
||||
@@ -349,6 +349,7 @@ pub fn sting(m: &mut Mob, who: Who, cx: &mut Cx) {
|
||||
kind: DamageKind::Sting,
|
||||
attacker: Some(Who::Mob(m.id)),
|
||||
from: Some(from),
|
||||
attacker_species: Some(Species::Bee),
|
||||
};
|
||||
match who {
|
||||
Who::Player => {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! arithmetic, the sounds of `Breeze.tick` and `BreezeModel`'s layers. The
|
||||
//! brain (`BreezeAi` and its behaviours) is in `monster_brain`.
|
||||
use super::{
|
||||
Mob, MobEvent, MobWorld, Species,
|
||||
Damage, Mob, MobEvent, MobWorld, Species, Who,
|
||||
anim::{Poses, State},
|
||||
keyframes::Animation,
|
||||
};
|
||||
@@ -29,6 +29,10 @@ pub fn register(m: &mut Mob) {
|
||||
m.set_malus(super::path::PathType::OnTopOfTrapdoor, -1.0);
|
||||
m.set_malus(super::path::PathType::Fire, -1.0);
|
||||
}
|
||||
/// `Breeze.isInvulnerableTo`: another breeze (or itself).
|
||||
pub fn accepts_damage(m: &Mob, source: &Damage) -> bool {
|
||||
!(source.attacker == Some(Who::Mob(m.id)) || source.attacker_species == Some(Species::Breeze))
|
||||
}
|
||||
pub fn pose(m: &Mob) -> u8 {
|
||||
m.extra.variant3
|
||||
}
|
||||
@@ -479,3 +483,184 @@ mod ground_particle_tests {
|
||||
let _ = ran;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod invulnerability_tests {
|
||||
use super::*;
|
||||
use crate::mobs::{DamageKind, sensor_scan_tests::kind};
|
||||
|
||||
fn test_breeze(id: u32) -> Mob {
|
||||
let mut kind = kind();
|
||||
let k = std::sync::Arc::make_mut(&mut kind);
|
||||
k.id = "minecraft:breeze".into();
|
||||
k.species = Species::Breeze;
|
||||
Mob::new(id, kind, Vec3::ZERO, 0.0)
|
||||
}
|
||||
|
||||
/// Scenario 1: Default/legacy behavior preserved:
|
||||
/// Breeze takes normal damage from player, generic damage, fall damage.
|
||||
#[test]
|
||||
fn breeze_takes_normal_damage_from_player_fall_and_generic() {
|
||||
let mut b = test_breeze(1);
|
||||
let initial_health = b.health;
|
||||
assert!(initial_health > 0.0);
|
||||
|
||||
// Player attack
|
||||
let player_hit = Damage::player([0.0, 0.0, 0.0]);
|
||||
assert!(
|
||||
b.damage(2.0, player_hit),
|
||||
"player attack should hurt breeze"
|
||||
);
|
||||
assert_eq!(b.health, initial_health - 2.0);
|
||||
|
||||
// Fall damage
|
||||
b.invulnerable = 0;
|
||||
let health_before_fall = b.health;
|
||||
assert!(
|
||||
b.damage(3.0, Damage::fall()),
|
||||
"fall damage should hurt breeze"
|
||||
);
|
||||
assert_eq!(b.health, health_before_fall - 3.0);
|
||||
|
||||
// Generic damage
|
||||
b.invulnerable = 0;
|
||||
let health_before_generic = b.health;
|
||||
assert!(
|
||||
b.damage(1.0, Damage::of(DamageKind::Generic)),
|
||||
"generic damage should hurt breeze"
|
||||
);
|
||||
assert_eq!(b.health, health_before_generic - 1.0);
|
||||
}
|
||||
|
||||
/// Scenario 2: Blocking/rejection of disallowed actions:
|
||||
/// Breeze rejects damage when attacker is another Breeze (`source.attacker_species == Some(Species::Breeze)`),
|
||||
/// returning `false` from `m.damage(...)` and taking no damage.
|
||||
#[test]
|
||||
fn breeze_rejects_damage_from_another_breeze() {
|
||||
let mut b = test_breeze(1);
|
||||
let initial_health = b.health;
|
||||
|
||||
// Direct mob attack from another breeze
|
||||
let other_breeze_mob = Damage::mob(2, [0.0, 0.0, 0.0]).with_species(Species::Breeze);
|
||||
assert!(
|
||||
!b.damage(5.0, other_breeze_mob),
|
||||
"breeze should reject damage from another breeze"
|
||||
);
|
||||
assert_eq!(b.health, initial_health, "health must not change");
|
||||
|
||||
// Thrown wind charge or projectile from another breeze
|
||||
let other_breeze_proj = Damage {
|
||||
kind: DamageKind::Thrown,
|
||||
attacker: Some(Who::Mob(2)),
|
||||
from: Some([1.0, 0.0, 0.0]),
|
||||
attacker_species: Some(Species::Breeze),
|
||||
};
|
||||
assert!(
|
||||
!b.damage(3.0, other_breeze_proj),
|
||||
"breeze should reject projectile damage from another breeze"
|
||||
);
|
||||
assert_eq!(b.health, initial_health, "health must remain intact");
|
||||
|
||||
// Directly verify breeze::accepts_damage
|
||||
assert!(!accepts_damage(&b, &other_breeze_mob));
|
||||
assert!(!accepts_damage(&b, &other_breeze_proj));
|
||||
}
|
||||
|
||||
/// Scenario 3: Successful execution when requirements are met:
|
||||
/// Breeze accepts damage from other mob species (e.g. Zombie, Skeleton) when `source.attacker_species` is Zombie/Skeleton.
|
||||
#[test]
|
||||
fn breeze_accepts_damage_from_other_mob_species() {
|
||||
let mut b = test_breeze(1);
|
||||
let initial_health = b.health;
|
||||
|
||||
// Attack from Zombie
|
||||
let zombie_hit = Damage::mob(5, [0.0, 0.0, 0.0]).with_species(Species::Zombie);
|
||||
assert!(
|
||||
accepts_damage(&b, &zombie_hit),
|
||||
"breeze accepts damage from zombie"
|
||||
);
|
||||
assert!(
|
||||
b.damage(4.0, zombie_hit),
|
||||
"breeze must take damage from zombie"
|
||||
);
|
||||
assert_eq!(b.health, initial_health - 4.0);
|
||||
|
||||
// Attack from Skeleton
|
||||
b.invulnerable = 0;
|
||||
let skeleton_hit = Damage::mob(6, [0.0, 0.0, 0.0]).with_species(Species::Skeleton);
|
||||
assert!(
|
||||
accepts_damage(&b, &skeleton_hit),
|
||||
"breeze accepts damage from skeleton"
|
||||
);
|
||||
assert!(
|
||||
b.damage(2.0, skeleton_hit),
|
||||
"breeze must take damage from skeleton"
|
||||
);
|
||||
assert_eq!(b.health, initial_health - 6.0);
|
||||
}
|
||||
|
||||
/// Scenario 4: Special / edge cases:
|
||||
/// Breeze rejects self-inflicted damage (`source.attacker == Some(Who::Mob(m.id))`),
|
||||
/// and verify bypasses_invulnerability behavior if applicable.
|
||||
#[test]
|
||||
fn breeze_rejects_self_damage_and_respects_invulnerability_flags() {
|
||||
let mut b = test_breeze(1);
|
||||
let initial_health = b.health;
|
||||
|
||||
// Self-inflicted damage with no species tag
|
||||
let self_hit_no_species = Damage::mob(b.id, [0.0, 0.0, 0.0]);
|
||||
assert!(
|
||||
!accepts_damage(&b, &self_hit_no_species),
|
||||
"breeze rejects self-hit even without species"
|
||||
);
|
||||
assert!(
|
||||
!b.damage(5.0, self_hit_no_species),
|
||||
"breeze takes no damage from itself"
|
||||
);
|
||||
assert_eq!(b.health, initial_health);
|
||||
|
||||
// Self-inflicted damage with species tag
|
||||
let self_hit_with_species =
|
||||
Damage::mob(b.id, [0.0, 0.0, 0.0]).with_species(Species::Breeze);
|
||||
assert!(
|
||||
!accepts_damage(&b, &self_hit_with_species),
|
||||
"breeze rejects self-hit with species"
|
||||
);
|
||||
assert!(
|
||||
!b.damage(5.0, self_hit_with_species),
|
||||
"breeze takes no damage from itself"
|
||||
);
|
||||
assert_eq!(b.health, initial_health);
|
||||
|
||||
// Invulnerable flag on Mob:
|
||||
b.invulnerable_flag = true;
|
||||
// Normal damage does not bypass invulnerability flag
|
||||
let normal_player = Damage::player([0.0, 0.0, 0.0]);
|
||||
assert!(
|
||||
!b.damage(5.0, normal_player),
|
||||
"invulnerable_flag blocks normal player damage"
|
||||
);
|
||||
assert_eq!(b.health, initial_health);
|
||||
|
||||
// GenericKill bypasses invulnerability flag
|
||||
let kill_damage = Damage::of(DamageKind::GenericKill);
|
||||
assert!(kill_damage.kind.bypasses_invulnerability());
|
||||
assert!(
|
||||
b.damage(1.0, kill_damage),
|
||||
"GenericKill bypasses invulnerability flag"
|
||||
);
|
||||
assert_eq!(b.health, initial_health - 1.0);
|
||||
|
||||
// But another Breeze with GenericKill is still rejected by Breeze.isInvulnerableTo
|
||||
b.invulnerable = 0;
|
||||
let breeze_kill = Damage::of(DamageKind::GenericKill).with_species(Species::Breeze);
|
||||
assert!(
|
||||
!accepts_damage(&b, &breeze_kill),
|
||||
"Breeze.isInvulnerableTo rejects breeze attacker even for GenericKill"
|
||||
);
|
||||
assert!(
|
||||
!b.damage(1.0, breeze_kill),
|
||||
"breeze takes no damage from breeze GenericKill"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,7 @@ mod tests {
|
||||
kind: DamageKind::MobAttack,
|
||||
attacker,
|
||||
from: None,
|
||||
attacker_species: None,
|
||||
},
|
||||
damage: 2.0,
|
||||
in_water: false,
|
||||
|
||||
@@ -25,6 +25,7 @@ pub fn contact(m: &mut Mob, f: &Frame, teams_allow: bool) {
|
||||
kind: DamageKind::MobAttack,
|
||||
attacker: Some(Who::Mob(f.dragon_id)),
|
||||
from: Some([f.origin.x, f.origin.y, f.origin.z]),
|
||||
attacker_species: None,
|
||||
};
|
||||
for wing in &f.wings {
|
||||
if wing.intersects(bounds) {
|
||||
|
||||
@@ -140,6 +140,7 @@ impl Goal for GuardianAttack {
|
||||
kind: DamageKind::Magic,
|
||||
attacker: Some(Who::Mob(m.id)),
|
||||
from: Some(from),
|
||||
attacker_species: Some(m.species()),
|
||||
},
|
||||
}),
|
||||
}
|
||||
@@ -313,6 +314,7 @@ pub fn ai_step(m: &mut Mob, w: &MobWorld, events: &mut Vec<MobEvent>) {
|
||||
kind: DamageKind::Thorns,
|
||||
attacker: Some(Who::Mob(m.id)),
|
||||
from: Some(from),
|
||||
attacker_species: Some(m.species()),
|
||||
},
|
||||
}),
|
||||
None => {}
|
||||
|
||||
@@ -242,6 +242,7 @@ fn stab_attack(
|
||||
kind: DamageKind::Spear,
|
||||
attacker: Some(Who::Mob(m.id)),
|
||||
from: Some(from),
|
||||
attacker_species: Some(m.species()),
|
||||
};
|
||||
let mut affected = deals_knockback;
|
||||
if deals_damage {
|
||||
|
||||
Reference in new issue
Block a user