td

Artifact [54abfeebea]
Login

Artifact [54abfeebea]

Artifact 54abfeebea84ce54df71912445677ae36738699ff24d80ed139c603540298e0b:


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)
}