2024-01-23 21:21:15 -08:00

251 lines
6.2 KiB
Rust

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<f32>,
pub lifetime: TextOrFloat,
pub lifetime_rng: Option<f32>,
pub angle: Option<f32>,
pub angle_rng: Option<f32>,
pub angvel: Option<f32>,
pub angvel_rng: Option<f32>,
pub fade: Option<f32>,
pub fade_rng: Option<f32>,
pub velocity: EffectVelocity,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum EffectVelocity {
Sticky {
sticky: String,
},
Explicit {
scale_parent: Option<f32>,
scale_parent_rng: Option<f32>,
scale_target: Option<f32>,
scale_target_rng: Option<f32>,
direction_rng: Option<f32>,
},
}
#[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<EffectHandle> {
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 velocity = match self.velocity {
EffectVelocity::Explicit {
scale_parent,
scale_parent_rng,
scale_target,
scale_target_rng,
direction_rng,
} => super::EffectVelocity::Explicit {
scale_parent: scale_parent.unwrap_or(0.0),
scale_parent_rng: scale_parent_rng.unwrap_or(0.0),
scale_target: scale_target.unwrap_or(0.0),
scale_target_rng: scale_target_rng.unwrap_or(0.0),
direction_rng: direction_rng.unwrap_or(0.0) / 2.0,
},
EffectVelocity::Sticky { sticky } => {
if sticky == "parent" {
super::EffectVelocity::StickyParent
} else if sticky == "target" {
super::EffectVelocity::StickyTarget
} else {
bail!("bad sticky specification `{}`", sticky);
}
}
};
let handle = EffectHandle {
index: content.effects.len(),
};
content.effects.push(super::Effect {
handle,
sprite,
velocity,
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)),
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<EffectHandle> {
// 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,
/// Fade this effect out over this many seconds as it ends
pub fade: f32,
/// Random fade variation
pub fade_rng: f32,
/// How to compute this effect's velocity
pub velocity: EffectVelocity,
}
/// How to compute an effect's velocity
#[derive(Debug, Clone)]
pub enum EffectVelocity {
/// Stick to parent
StickyParent,
/// Stick to target.
/// Zero velocity if no target is given.
StickyTarget,
/// Compute velocity from parent and target
Explicit {
/// How much of the parent's velocity to inherit
scale_parent: f32,
/// Random variation of scale_parent
scale_parent_rng: f32,
/// How much of the target's velocity to keep
scale_target: f32,
/// Random variation of scale_target
scale_target_rng: f32,
/// Random variation of travel direction
direction_rng: f32,
},
}
impl crate::Build for Effect {
type InputSyntaxType = HashMap<String, syntax::Effect>;
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(());
}
}