use anyhow::{Context, Result}; use std::collections::HashMap; use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle}; pub(crate) mod syntax { use anyhow::{bail, Result}; use galactica_util::to_radians; use serde::Deserialize; use crate::{Content, ContentBuildContext, EffectHandle, StartEdge}; // Raw serde syntax structs. // These are never seen by code outside this crate. #[derive(Debug, Deserialize)] pub struct Effect { pub sprite: String, pub size: f32, pub size_rng: Option, pub lifetime: TextOrFloat, pub lifetime_rng: Option, pub angle: Option, pub angle_rng: Option, pub angvel: Option, pub angvel_rng: Option, pub velocity_scale_parent: Option, pub velocity_scale_parent_rng: Option, pub velocity_scale_target: Option, pub velocity_scale_target_rng: Option, pub direction_rng: Option, pub fade: Option, pub fade_rng: Option, } #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum TextOrFloat { Text(String), Float(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 { TextOrFloat::Float(f) => f, TextOrFloat::Text(s) => { if s == "inherit" { // Match lifetime of first section of sprite let sprite = content.get_sprite(sprite); let sec = match sprite.start_at { StartEdge::Top { section } => sprite.get_section(section), StartEdge::Bot { section } => sprite.get_section(section), }; sec.frame_duration * sec.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 { handle, sprite, size: self.size, size_rng: self.size_rng.unwrap_or(0.0), lifetime, lifetime_rng: self.lifetime_rng.unwrap_or(0.0), angle: to_radians(self.angle.unwrap_or(0.0) / 2.0), angle_rng: to_radians(self.angle_rng.unwrap_or(0.0) / 2.0), angvel: to_radians(self.angvel.unwrap_or(0.0)), angvel_rng: to_radians(self.angvel_rng.unwrap_or(0.0)), velocity_scale_parent: self.velocity_scale_parent.unwrap_or(0.0), velocity_scale_parent_rng: self.velocity_scale_parent_rng.unwrap_or(0.0), velocity_scale_target: self.velocity_scale_target.unwrap_or(0.0), velocity_scale_target_rng: self.velocity_scale_target_rng.unwrap_or(0.0), direction_rng: self.direction_rng.unwrap_or(0.0) / 2.0, fade: self.fade.unwrap_or(0.0), fade_rng: self.fade_rng.unwrap_or(0.0), }); return Ok(handle); } } // 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), }, }) } } } /// The effect a projectile will spawn when it hits something #[derive(Debug, Clone)] pub struct Effect { /// This effect's handle pub handle: EffectHandle, /// The sprite to use for this effect. pub sprite: SpriteHandle, /// The height of this effect, in game units. pub size: f32, /// Random size variation pub size_rng: f32, /// How many seconds this effect should live pub lifetime: f32, /// Random lifetime variation pub lifetime_rng: f32, /// The angle this effect points once spawned, in radians pub angle: f32, /// Random angle variation, in radians pub angle_rng: f32, /// How fast this effect spins, in radians/sec pub angvel: f32, /// Random angvel variation pub angvel_rng: f32, /// The amount of this effect's parent's velocity to inherit pub velocity_scale_parent: f32, /// Parent velocity random variation pub velocity_scale_parent_rng: f32, /// The amount of this effect's parent's target velocity to inherit. /// If there is no target, this is zero. pub velocity_scale_target: f32, /// Target velocity random variation pub velocity_scale_target_rng: f32, /// Travel direction random variation pub direction_rng: f32, /// Fade this effect out over this many seconds as it ends pub fade: f32, /// Random fade ariation pub fade_rng: 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(()); } }