//! 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;
}