enshwick 1.0.0-alpha.11

The Enshwick Loader's modding API for Enshrouded: the one crate mods are written against
Documentation
//! Damage filters: hits by type, falls and the shroud, as the loader runs
//! them.

#[cfg(test)]
mod tests {
    use enshwick::abi::{DamageCheck, Header, Status};
    use enshwick::damage::{DamageCause, DamageTypeNew};
    use enshwick::{Load, Mod, Off};

    struct Soft;

    impl Mod for Soft {
        fn manifest() -> &'static enshwick::abi::Manifest {
            enshwick::manifest!(id: "you.soft", side: SERVER)
        }

        fn init(load: &mut Load<'_>) -> Result<Self, Off> {
            load.on_damage(|damage| match damage.cause() {
                DamageCause::FallDamage => damage.scale(0.5),
                DamageCause::DeadlyFog => damage.cancel(),
                DamageCause::Impact if damage.source() == Some(damage.target()) => damage.cancel(),
                DamageCause::Impact => {
                    damage.scale_type(DamageTypeNew::MagicalFire, 2.0);
                    let cut = damage.amount(DamageTypeNew::PhysicalCut);
                    damage.set(DamageTypeNew::PhysicalCut, cut.min(10.0));
                }
                _ => {}
            })?;
            Ok(Self)
        }
    }

    enshwick::declare_mod!(Soft);

    fn check(cause: u32, target: u32, source: u32) -> DamageCheck {
        DamageCheck {
            header: Header::of::<DamageCheck>(),
            cause,
            target: enshwick::Entity(target),
            source: enshwick::Entity(source),
            attacker: enshwick::Entity(0),
            amounts: [0.0; enshwick::abi::DAMAGE_TYPES],
            total: 0.0,
        }
    }

    #[test]
    fn filters_scale_set_and_cancel_by_cause_and_type() {
        let loader = enshwick::test::FakeHost::new().install();
        // SAFETY: both functions are the exports of this file's `declare_mod!(Soft)`.
        let status = unsafe { loader.start(enshwick_mod_init, enshwick_mod_ready) };
        assert_eq!(status, Status::OK);

        let mut hit = check(DamageCheck::IMPACT, 2, 9);
        hit.amounts[DamageTypeNew::PhysicalCut.index()] = 30.0;
        hit.amounts[DamageTypeNew::MagicalFire.index()] = 5.0;
        hit.amounts[DamageTypeNew::Healing.index()] = 4.0;
        let after = loader.damage(hit);
        assert!(
            (after.amounts[DamageTypeNew::PhysicalCut.index()] - 10.0).abs() < f32::EPSILON,
            "cut capped"
        );
        assert!(
            (after.amounts[DamageTypeNew::MagicalFire.index()] - 10.0).abs() < f32::EPSILON,
            "fire doubled"
        );
        assert!(
            (after.amounts[DamageTypeNew::Healing.index()] - 4.0).abs() < f32::EPSILON,
            "healing kept"
        );

        let mut own = check(DamageCheck::IMPACT, 2, 2);
        own.amounts[DamageTypeNew::PhysicalBlunt.index()] = 12.0;
        own.amounts[DamageTypeNew::Healing.index()] = 3.0;
        let after = loader.damage(own);
        assert!(
            after.amounts[DamageTypeNew::PhysicalBlunt.index()].abs() < f32::EPSILON,
            "cancelled"
        );
        assert!(
            (after.amounts[DamageTypeNew::Healing.index()] - 3.0).abs() < f32::EPSILON,
            "a cancel keeps healing"
        );

        let mut fall = check(DamageCheck::FALL_DAMAGE, 2, 0);
        fall.total = 40.0;
        assert!((loader.damage(fall).total - 20.0).abs() < f32::EPSILON);

        let mut death = check(DamageCheck::DEADLY_FOG, 2, 0);
        death.total = 300.0;
        assert!(loader.damage(death).total.abs() < f32::EPSILON);
    }
}