Ensure mob effect IDs are namespaced so tipped arrows tick damage on living mobs

This commit is contained in:
Mik00000 committed 2026-10-28 16:09:27 +02:00
1 parent 85f53198ce
commit 08b9765278
4 files changed
+63 -8

No files matched your search

+2 -1
View File
@@ -91,6 +91,7 @@ pub fn effect(id: &str) -> Option<&'static EffectDef> {
/// Namespaced effect ids for mob effect storage.
pub fn namespaced(id: &'static str) -> &'static str {
static IDS: OnceLock<HashMap<&'static str, &'static str>> = OnceLock::new();
let key = id.strip_prefix("minecraft:").unwrap_or(id);
IDS.get_or_init(|| {
EFFECTS
.iter()
@@ -102,7 +103,7 @@ pub fn namespaced(id: &'static str) -> &'static str {
})
.collect()
})
.get(id)
.get(key)
.copied()
.unwrap_or(id)
}
+6 -1
View File
@@ -2140,6 +2140,11 @@ pub(super) fn tipped_effect(stack: &ItemStack) -> Option<(&'static str, i32)> {
def.effects
.iter()
.find(|(id, _, _)| !horizon_client::brewing::effect(id).is_some_and(|e| e.instant))
.map(|(id, duration, _)| (*id, (duration / 8).max(1)))
.map(|(id, duration, _)| {
(
horizon_client::brewing::namespaced(id),
(duration / 8).max(1),
)
})
})
}
+10 -3
View File
@@ -2264,15 +2264,21 @@ impl Mob {
) * self.speed_bonus();
for (e, _, amp) in &self.extra.effects {
match *e {
"minecraft:speed" => v *= 1.0 + 0.2 * f64::from(amp + 1),
"minecraft:slowness" => v *= (1.0 - 0.15 * f64::from(amp + 1)).max(0.0),
"speed" | "minecraft:speed" => v *= 1.0 + 0.2 * f64::from(amp + 1),
"slowness" | "minecraft:slowness" => {
v *= (1.0 - 0.15 * f64::from(amp + 1)).max(0.0)
}
_ => {}
}
}
v
}
pub fn has_effect(&self, effect: &str) -> bool {
self.extra.effects.iter().any(|e| e.0 == effect)
let stripped = effect.strip_prefix("minecraft:").unwrap_or(effect);
self.extra
.effects
.iter()
.any(|e| e.0.strip_prefix("minecraft:").unwrap_or(e.0) == stripped)
}
/// `LivingEntity.isCurrentlyGlowing`: the Glowing effect or the `Glowing` tag (on a
/// server's client, `glowing` holds shared flag 6 instead).
@@ -2280,6 +2286,7 @@ impl Mob {
self.glowing || self.has_effect("minecraft:glowing")
}
pub fn add_effect(&mut self, effect: &'static str, ticks: i32, amplifier: i32) {
let effect = horizon_client_core::potions::namespaced(effect);
if let Some(e) = self.extra.effects.iter_mut().find(|e| e.0 == effect) {
// MobEffectInstance.update: a stronger effect replaces with its
// own duration, an equal one only extends it; a weaker one is
+45 -3
View File
@@ -1942,15 +1942,15 @@ fn tick_effects(m: &mut Mob) {
let infinite = *ticks == -1;
let count = if infinite { m.age as i32 } else { *ticks };
match *effect {
"minecraft:poison"
"poison" | "minecraft:poison"
if count % interval(25) == 0 && !m.kind.flags.undead && m.health > 1.0 =>
{
m.damage(1.0, Damage::of(DamageKind::Magic));
}
"minecraft:wither" if count % interval(40) == 0 => {
"wither" | "minecraft:wither" if count % interval(40) == 0 => {
m.damage(1.0, Damage::of(DamageKind::Magic));
}
"minecraft:regeneration"
"regeneration" | "minecraft:regeneration"
if count % interval(50) == 0 && !m.kind.flags.undead && m.alive() =>
{
m.health = (m.health + 1.0).min(m.attrs.max_health);
@@ -2175,3 +2175,45 @@ mod frog_tests {
);
}
}
#[cfg(test)]
mod poison_effect_tests {
use super::*;
use crate::mobs::MobTypes;
use std::sync::Arc;
#[test]
fn poison_effect_damages_living_mobs_but_not_undead() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../.local/horizon-client")
.join(horizon_core::VANILLA_VERSION);
if !root.join("entity-types.json").exists() {
return;
}
let data = MobTypes::load(&root).unwrap();
// Pig is not undead: should take damage from poison.
let pig_kind = Arc::new(data.types.get("minecraft:pig").unwrap().clone());
let mut pig = Mob::new(1, pig_kind, Vec3::new(0.0, 4.0, 0.0), 0.0);
pig.add_effect("poison", 100, 0);
assert_eq!(pig.extra.effects[0].0, "minecraft:poison");
let initial_pig_health = pig.health;
tick_effects(&mut pig);
assert_eq!(
pig.health,
initial_pig_health - 1.0,
"living mob should take 1.0 damage from poison"
);
// Zombie is undead: immune to poison damage.
let zombie_kind = Arc::new(data.types.get("minecraft:zombie").unwrap().clone());
let mut zombie = Mob::new(2, zombie_kind, Vec3::new(0.0, 4.0, 0.0), 0.0);
zombie.add_effect("poison", 100, 0);
let initial_zombie_health = zombie.health;
tick_effects(&mut zombie);
assert_eq!(
zombie.health, initial_zombie_health,
"undead mobs are immune to poison"
);
}
}