td

status.rs at trunk
Login

status.rs at trunk

File src/behavior/status.rs artifact 55b702c756 on branch trunk


use crate::{
    behavior::{dyn_clone_trait, projectile::BehaviorPriority},
    context::{DamageMap, TickContext},
    enemy::Enemy,
    registry::DamageID,
    types::{DamageDelivery, Source},
};

pub trait StatusBehavior: StatusClone + Send + Sync {
    fn on_tick(&mut self, _e: &mut Enemy, _c: &mut TickContext) -> bool {
        false
    }
    fn priority(&self) -> i32 {
        BehaviorPriority::DEFAULT
    }
}

dyn_clone_trait!(StatusClone, StatusBehavior);

#[derive(Clone)]
pub struct PeriodDamage {
    kind: DamageID,
    amount: f32,
    interval: f32,
    lifetime: f32,
    duration: f32,
    // Captured from whoever applied this (e.g. the projectile's `Source::Tower`) at the
    // moment of application — NOT read from `TickContext.source` on every tick, since the
    // generic per-enemy tick pass (`tick_enemies`) runs with `Source::None`. Using that
    // would silently make every DoT invisible to tower-attributed tracking (the DPS meter,
    // kill credit) regardless of who actually applied it.
    source: Source,
}

impl PeriodDamage {
    pub fn make(
        kind: DamageID,
        amount: f32,
        interval: f32,
        lifetime: f32,
        source: Source,
    ) -> Box<dyn StatusBehavior> {
        Box::new(PeriodDamage {
            kind,
            amount,
            interval,
            lifetime,
            duration: 0.0,
            source,
        })
    }
}

impl StatusBehavior for PeriodDamage {
    fn on_tick(&mut self, e: &mut Enemy, c: &mut TickContext) -> bool {
        let dmg = (self.amount / self.interval) * c.dt;
        self.duration += c.dt;
        let mut dm = DamageMap::default();
        dm.flat(self.kind, dmg);
        // Queued like everything else instead of mutating `e` directly: a status ticking
        // itself into the enemy bypassed the one shared choke point every other damage
        // source goes through, which means a future global modifier (e.g. "+30% all
        // damage") would need special-cased access to the status system to see it, instead
        // of just hooking `Command::Damage` once.
        c.cmd.damage(e.id, dm, self.source, DamageDelivery::Dot);
        self.duration > self.lifetime
    }
}

pub fn dispatch_status_tick(e: &mut Enemy, c: &mut TickContext) {
    let mut statuses = std::mem::take(&mut e.statuses);
    statuses.retain_mut(|s| !s.on_tick(e, c));
    e.statuses = statuses;
}