use anyhow::{Context, Result}; use serde::Deserialize; use std::collections::HashMap; use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle}; pub(crate) mod syntax { use anyhow::{bail, Result}; use serde::Deserialize; use crate::{Content, ContentBuildContext, EffectHandle}; // Raw serde syntax structs. // These are never seen by code outside this crate. #[derive(Debug, Deserialize)] pub struct Effect { pub sprite: String, pub lifetime: EffectLifetime, pub inherit_velocity: super::ImpactInheritVelocity, pub size: f32, } // We implement building here instead of in super::Effect because // effects may be defined inline (see EffectReference). impl Effect { pub fn add_to( self, _build_context: &mut ContentBuildContext, content: &mut Content, ) -> Result { let sprite = match content.sprite_index.get(&self.sprite) { None => bail!("sprite `{}` doesn't exist", self.sprite), Some(t) => *t, }; let lifetime = match self.lifetime { EffectLifetime::Seconds(s) => s, EffectLifetime::Inherit(s) => { if s == "inherit" { let sprite = content.get_sprite(sprite); sprite.fps * sprite.frames.len() as f32 } else { bail!("bad effect lifetime, must be float or \"inherit\"",) } } }; let handle = EffectHandle { index: content.effects.len(), }; content.effects.push(super::Effect { sprite, lifetime, handle, inherit_velocity: self.inherit_velocity, size: self.size, }); return Ok(handle); } } #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum EffectLifetime { Inherit(String), Seconds(f32), } // This isn't used here, but is pulled in by other content items. /// A reference to an effect by name, or an inline definition. #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum EffectReference { Label(String), Effect(Effect), } impl EffectReference { pub fn to_handle( self, build_context: &mut ContentBuildContext, content: &mut Content, ) -> Result { // We do not insert anything into build_context here, // since inline effects cannot be referenced by name. Ok(match self { Self::Effect(e) => e.add_to(build_context, content)?, Self::Label(l) => match build_context.effect_index.get(&l) { Some(h) => *h, None => bail!("no effect named `{}`", l), }, }) } } } /// How we should set an effect's velocity #[derive(Debug, Deserialize, Clone)] pub enum ImpactInheritVelocity { /// Don't inherit any velocity. /// This impact particle will be still. #[serde(rename = "don't")] Dont, /// Inherit target velocity. /// This impact particle will stick to the object it hits. #[serde(rename = "target")] Target, /// Inherit projectile velocity. /// This impact particle will continue on its projectile's path. #[serde(rename = "projectile")] Projectile, } /// The particle a projectile will spawn when it hits something #[derive(Debug, Clone)] pub struct Effect { /// The sprite to use for this particle. /// This is most likely animated. pub sprite: SpriteHandle, /// This effect's handle pub handle: EffectHandle, /// How many seconds this particle should live pub lifetime: f32, /// How we should set this particle's velocity pub inherit_velocity: ImpactInheritVelocity, /// The height of this particle, in game units. pub size: f32, } impl crate::Build for Effect { type InputSyntaxType = HashMap; fn build( effects: Self::InputSyntaxType, build_context: &mut ContentBuildContext, content: &mut Content, ) -> Result<()> { for (effect_name, effect) in effects { let h = effect .add_to(build_context, content) .with_context(|| format!("while evaluating effect `{}`", effect_name))?; build_context.effect_index.insert(effect_name, h); } return Ok(()); } }