use std::collections::HashMap;
use macroquad::{color::Color, math::Vec2};
use crate::{registry::AnimationID, world::EntityId};
pub mod visual_slots {
pub const IDLE: &str = "idle";
pub const MOVEMENT: &str = "movement";
pub const END: &str = "end";
}
pub struct VisualPriority;
impl VisualPriority {
pub const DEFAULT: i32 = 0;
pub const OVERRIDE: i32 = 100;
}
#[derive(Debug, Clone, Copy)]
struct AnimationChoice {
animation: AnimationID,
priority: i32,
}
#[derive(Debug, Clone)]
pub struct AnimationSet {
clips: HashMap<String, AnimationChoice>,
}
impl AnimationSet {
pub fn new<S, I>(clips: I) -> Self
where
S: Into<String>,
I: IntoIterator<Item = (S, AnimationID)>,
{
let mut set = Self {
clips: HashMap::new(),
};
for (slot, animation) in clips {
set.insert(slot, animation, i32::MIN);
}
set
}
/// Adds a candidate for a slot, replacing the current clip only at a higher priority.
pub fn insert(&mut self, slot: impl Into<String>, animation: AnimationID, priority: i32) {
let slot = slot.into();
let replace = self
.clips
.get(&slot)
.is_none_or(|current| priority > current.priority);
if replace {
self.clips.insert(
slot,
AnimationChoice {
animation,
priority,
},
);
}
}
pub fn get(&self, slot: &str) -> Option<AnimationID> {
self.clips.get(slot).map(|choice| choice.animation)
}
}
/// Rendering input shared by persistent entity visuals and transient effects.
#[derive(Clone, Copy)]
pub struct AnimationFrame {
pub pos: Vec2,
pub radius: f32,
pub elapsed: f32,
pub duration: f32,
pub health_fraction: f32,
}
impl AnimationFrame {
pub fn progress(&self) -> f32 {
(self.elapsed / self.duration).clamp(0.0, 1.0)
}
}
/// Registry-owned animation data. The same clip can render an entity or a spawned effect.
#[derive(Clone, Copy)]
pub struct AnimationClip {
pub duration: f32,
pub draw: fn(&AnimationFrame) -> FrameOutput,
}
#[derive(Debug, Clone, Copy)]
pub struct AnimationSpawn {
pub pos: Vec2,
pub radius: f32,
}
/// Runtime state for a transient animation such as a death effect.
#[derive(Debug, Clone)]
pub struct Animation {
pub id: EntityId,
pub kind: AnimationID,
pub duration: f32,
pub elapsed: f32,
pub pos: Vec2,
pub radius: f32,
}
impl Animation {
pub fn frame(&self) -> AnimationFrame {
AnimationFrame {
pos: self.pos,
radius: self.radius,
elapsed: self.elapsed,
duration: self.duration,
health_fraction: 1.0,
}
}
}
pub enum Shape {
Circle {
pos: Vec2,
radius: f32,
color: Color,
},
}
pub struct DrawItem {
pub shape: Shape,
pub tint_color: Color,
pub tint_amount: f32,
pub alpha: f32,
pub scale: f32,
}
pub struct FrameOutput {
pub items: Vec<DrawItem>,
}
pub fn lerp(a: Color, b: Color, t: f32) -> Color {
Color::from_vec(a.to_vec().lerp(b.to_vec(), t))
}
pub fn resolve(base: Color, item: &DrawItem) -> Color {
let t = item.tint_amount.clamp(0.0, 1.0);
lerp(base, item.tint_color, t)
}