Skip to main content

steel_core/entity/mob_effect/
heal_or_harm.rs

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