Skip to main content

steel_core/entity/mob_effect/
heal_or_harm.rs

1//! `HealOrHarmMobEffect` behavior (Instant Health / Instant Damage).
2
3use steel_registry::vanilla_damage_types;
4
5use super::{InstantaneousMobEffect, MobEffectBehavior};
6use crate::entity::LivingEntity;
7use crate::entity::damage::DamageSource;
8use crate::world::World;
9
10/// Mirrors vanilla `HealOrHarmMobEffect`
11pub struct HealOrHarmBehavior {
12    /// `false` for Instant Health, `true` for Instant Damage.
13    pub is_harm: bool,
14}
15
16/// Base heal amount at amplifier 0, before the `<< amplifier` scaling (vanilla `4`).
17const BASE_HEAL_AMOUNT: i32 = 4;
18/// Base harm amount at amplifier 0, before the `<< amplifier` scaling (vanilla `6`).
19const BASE_HARM_AMOUNT: i32 = 6;
20
21impl MobEffectBehavior for HealOrHarmBehavior {
22    fn as_instantaneous(&self) -> Option<&dyn InstantaneousMobEffect> {
23        Some(self)
24    }
25
26    /// Reached through `ApplyStatusEffectsConsumeEffect` (e.g. a golden
27    /// apple-style item), one tick after `LivingEntity.addEffect`.
28    fn apply_effect_tick(&self, world: &World, user: &dyn LivingEntity, amplifier: i32) -> bool {
29        if self.is_harm == user.is_inverted_heal_and_harm() {
30            // Mirrors vanilla's `Math.max(4 << amplification, 0)` clamp
31            // against Java's signed-int overflow at extreme amplifiers.
32            let amount = BASE_HEAL_AMOUNT.wrapping_shl(amplifier as u32).max(0);
33            user.heal(amount as f32);
34        } else {
35            user.hurt(
36                world,
37                &DamageSource::environment(&vanilla_damage_types::MAGIC),
38                BASE_HARM_AMOUNT.wrapping_shl(amplifier as u32) as f32,
39            );
40        }
41        true
42    }
43}
44
45impl InstantaneousMobEffect for HealOrHarmBehavior {
46    /// Reached by drinking a potion directly, or a splash/lingering potion
47    /// once implemented. Unlike `apply_effect_tick`, the heal amount is
48    /// never clamped to zero, and damage is attributed via
49    /// `indirectMagic(source, owner)` when `direct_entity` is known.
50    fn apply_instantaneous(
51        &self,
52        world: &World,
53        user: &dyn LivingEntity,
54        amplifier: i32,
55        direct_entity: Option<i32>,
56        causing_entity: Option<i32>,
57        scale: f32,
58    ) {
59        if self.is_harm == user.is_inverted_heal_and_harm() {
60            let amount =
61                (scale * (BASE_HEAL_AMOUNT.wrapping_shl(amplifier as u32) as f32) + 0.5) as i32;
62            user.heal(amount as f32);
63        } else {
64            let mut source = DamageSource::environment(if direct_entity.is_some() {
65                &vanilla_damage_types::INDIRECT_MAGIC
66            } else {
67                &vanilla_damage_types::MAGIC
68            });
69            if let Some(entity_id) = direct_entity {
70                source = source.with_direct_entity(entity_id);
71            }
72            if let Some(entity_id) = causing_entity {
73                source = source.with_causing_entity(entity_id);
74            }
75            // Vanilla truncates via a Java `(int)` cast; `as i32` on a
76            // non-negative f32 truncates the same way.
77            let amount =
78                (scale * (BASE_HARM_AMOUNT.wrapping_shl(amplifier as u32) as f32) + 0.5) as i32;
79            user.hurt(world, &source, amount as f32);
80        }
81    }
82}