td

projectile.rs at [1c4f2f89eb]
Login

projectile.rs at [1c4f2f89eb]

File src/behavior/projectile.rs artifact a3437bac36 part of check-in 1c4f2f89eb


//! The composable projectile behavior system: the `ProjectileBehavior` trait, its priority
//! tiers, the `HitResponse` fold type, and the dispatch runners that drive a projectile's
//! whole behavior list through one event. Concrete weapons implementing this trait live in
//! `crate::weapons`.

use macroquad::math::Vec2;

use crate::{
    behavior::dyn_clone_trait,
    context::{ExpireContext, FireContext, HitContext, TickContext},
    enemy::Enemy,
    world::Projectile,
};

pub struct BehaviorPriority;

impl BehaviorPriority {
    pub const PRE: i32 = -200; // targeting / setup
    pub const MODIFY: i32 = -100; // bump HitContext.damage_mult / flat_bonus
    pub const DEFAULT: i32 = 0; // most effects: Acid, Bounce, spawn-shaping
    pub const IMPACT: i32 = 100; // reads the accumulator → must be AFTER MODIFY
    pub const POST: i32 = 200; // reactions / cleanup
}

pub trait ProjectileBehavior: ProjectileClone + Send + Sync {
    // spawn-shaping: runs once at fire time, can add/modify shots
    fn on_fire(&self, _shots: &mut Vec<Projectile>, _ctx: &FireContext) {}

    // per-projectile events: &mut self so a behavior carries its own counter

    fn on_hit_enemy(
        &mut self,
        _p: &mut Projectile,
        _e: &mut Enemy,
        _c: &mut HitContext,
    ) -> HitResponse {
        HitResponse::PASS
    }
    fn on_hit_wall(&mut self, _p: &mut Projectile, _c: &mut HitContext) -> HitResponse {
        HitResponse::PASS
    }
    fn on_tick(&mut self, _p: &mut Projectile, _c: &mut TickContext) {}
    fn on_expire(&mut self, _p: &mut Projectile, _c: &mut ExpireContext) {}
    fn priority(&self) -> i32 {
        BehaviorPriority::DEFAULT
    }
}

dyn_clone_trait!(ProjectileClone, ProjectileBehavior);

pub struct HitResponse {
    pub pierce: bool,
    pub reflect: Option<Vec2>,
}
impl HitResponse {
    pub const PASS: Self = Self {
        pierce: false,
        reflect: None,
    };
}

pub struct DamageResponse {
    pub damage: i32,
}

impl DamageResponse {
    pub const PASS: Self = Self { damage: 0 };
}

pub fn dispatch_enemy_hit(p: &mut Projectile, e: &mut Enemy, c: &mut HitContext) -> bool {
    let mut behaviors = std::mem::take(&mut p.behaviors);
    let (mut reflect, mut pierce) = (None, false);
    for b in behaviors.iter_mut() {
        let r = b.on_hit_enemy(p, e, c);
        if r.reflect.is_some() {
            reflect = r.reflect;
        }
        pierce |= r.pierce;
    }
    p.behaviors = behaviors;
    match reflect {
        Some(v) => {
            p.vel = v;
            false // bounced → keep the projectile
        }
        None => !pierce, // consume unless something asked to pierce
    }
}

pub fn dispatch_wall_hit(p: &mut Projectile, c: &mut HitContext) -> bool {
    let mut behaviors = std::mem::take(&mut p.behaviors);
    let (mut reflect, mut pierce) = (None, false);
    for b in behaviors.iter_mut() {
        let r = b.on_hit_wall(p, c);
        if r.reflect.is_some() {
            reflect = r.reflect;
        }
        pierce |= r.pierce;
    }
    p.behaviors = behaviors;
    match reflect {
        Some(v) => {
            p.vel = v;
            false
        }
        None => !pierce,
    }
}

pub fn dispatch_projectile_tick(p: &mut Projectile, c: &mut TickContext) {
    let mut behaviors = std::mem::take(&mut p.behaviors);
    for b in behaviors.iter_mut() {
        b.on_tick(p, c);
    }
    p.behaviors = behaviors;
}

pub fn dispatch_expire(p: &mut Projectile, c: &mut ExpireContext) {
    let mut behaviors = std::mem::take(&mut p.behaviors);
    for b in behaviors.iter_mut() {
        b.on_expire(p, c);
    }
    p.behaviors = behaviors;
}