251 lines
6.2 KiB
Rust
Raw Normal View History

2024-01-03 13:19:10 -08:00
use anyhow::{bail, Context, Result};
2023-12-28 17:04:41 -08:00
use cgmath::Deg;
2024-01-03 13:19:10 -08:00
use serde::Deserialize;
use std::collections::HashMap;
2023-12-27 20:13:39 -08:00
use crate::{handle::SpriteHandle, Content};
2023-12-31 18:48:35 -08:00
use crate::OutfitSpace;
2023-12-30 21:05:06 -08:00
2023-12-30 16:57:03 -08:00
pub(crate) mod syntax {
2023-12-31 18:48:35 -08:00
use crate::part::shared;
2023-12-27 20:13:39 -08:00
use serde::Deserialize;
// Raw serde syntax structs.
// These are never seen by code outside this crate.
#[derive(Debug, Deserialize)]
pub struct Gun {
pub projectile: Projectile,
2023-12-28 17:04:41 -08:00
pub rate: f32,
pub rate_rng: f32,
2023-12-30 21:05:06 -08:00
pub space: shared::syntax::OutfitSpace,
2023-12-27 20:13:39 -08:00
}
#[derive(Debug, Deserialize)]
pub struct Projectile {
pub sprite: String,
2023-12-27 20:13:39 -08:00
pub size: f32,
2023-12-28 17:04:41 -08:00
pub size_rng: f32,
2023-12-27 20:13:39 -08:00
pub speed: f32,
2023-12-28 17:04:41 -08:00
pub speed_rng: f32,
2023-12-27 20:13:39 -08:00
pub lifetime: f32,
2023-12-28 17:04:41 -08:00
pub lifetime_rng: f32,
pub damage: f32,
2024-01-01 15:41:47 -08:00
pub angle_rng: f32,
2024-01-03 13:19:10 -08:00
pub impact: Option<ProjectileParticle>,
pub expire: Option<ProjectileParticle>,
pub collider: super::ProjectileCollider,
pub force: f32,
2023-12-27 20:13:39 -08:00
}
2024-01-03 13:19:10 -08:00
#[derive(Debug, Deserialize)]
pub struct ProjectileParticle {
pub sprite: String,
2024-01-03 13:19:10 -08:00
pub lifetime: ParticleLifetime,
pub inherit_velocity: super::ImpactInheritVelocity,
pub size: f32,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum ParticleLifetime {
Inherit(String),
Seconds(f32),
}
}
/// Defines a projectile's collider
#[derive(Debug, Deserialize, Clone)]
pub enum ProjectileCollider {
/// A ball collider
#[serde(rename = "ball")]
Ball(BallCollider),
}
#[derive(Debug, Deserialize, Clone)]
pub struct BallCollider {
pub radius: f32,
}
/// How we should set an impact particle'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,
2023-12-27 20:13:39 -08:00
}
2023-12-29 15:46:09 -08:00
/// Represents a gun outfit.
2023-12-27 20:13:39 -08:00
#[derive(Debug, Clone)]
pub struct Gun {
2023-12-29 15:46:09 -08:00
/// The name of this gun
2023-12-27 20:13:39 -08:00
pub name: String,
2023-12-29 15:46:09 -08:00
/// The projectile this gun produces
2023-12-27 20:13:39 -08:00
pub projectile: Projectile,
2023-12-29 15:46:09 -08:00
/// Average delay between projectiles, in seconds.
2023-12-28 17:04:41 -08:00
pub rate: f32,
2023-12-29 15:46:09 -08:00
/// Random variation of projectile delay, in seconds.
/// Each shot waits (rate += rate_rng).
2023-12-28 17:04:41 -08:00
pub rate_rng: f32,
2023-12-30 21:05:06 -08:00
/// How much space this gun uses
pub space: OutfitSpace,
2023-12-27 20:13:39 -08:00
}
2023-12-29 15:46:09 -08:00
/// Represents a projectile that a [`Gun`] produces.
2023-12-27 20:13:39 -08:00
#[derive(Debug, Clone)]
pub struct Projectile {
2023-12-29 15:46:09 -08:00
/// The projectile sprite
pub sprite: SpriteHandle,
2023-12-29 15:46:09 -08:00
/// The average size of this projectile
/// (height in game units)
2023-12-27 20:13:39 -08:00
pub size: f32,
2023-12-29 15:46:09 -08:00
/// Random size variation
2023-12-28 17:04:41 -08:00
pub size_rng: f32,
2023-12-29 15:46:09 -08:00
/// The speed of this projectile, in game units / second
2023-12-27 20:13:39 -08:00
pub speed: f32,
2023-12-29 15:46:09 -08:00
/// Random speed variation
2023-12-28 17:04:41 -08:00
pub speed_rng: f32,
2023-12-29 15:46:09 -08:00
/// The lifespan of this projectile.
/// It will vanish if it lives this long without hitting anything.
2023-12-27 20:13:39 -08:00
pub lifetime: f32,
2023-12-29 15:46:09 -08:00
/// Random lifetime variation
2023-12-28 17:04:41 -08:00
pub lifetime_rng: f32,
2023-12-29 15:46:09 -08:00
/// The damage this projectile does
pub damage: f32,
2024-01-01 15:41:47 -08:00
2024-01-03 13:19:10 -08:00
/// The force this projectile applies
pub force: f32,
2024-01-01 15:41:47 -08:00
/// The angle variation of this projectile.
/// Projectiles can be off center up to
/// `spread / 2` degrees in both directions.
///
/// (Forming a "fire cone" of `spread` degrees)
pub angle_rng: Deg<f32>,
2024-01-03 13:19:10 -08:00
/// The particle this projectile will spawn when it hits something
pub impact_particle: Option<ProjectileParticle>,
/// The particle this projectile will spawn when it expires
pub expire_particle: Option<ProjectileParticle>,
/// Collider parameters for this projectile
pub collider: ProjectileCollider,
}
/// The particle a projectile will spawn when it hits something
#[derive(Debug, Clone)]
pub struct ProjectileParticle {
/// The sprite to use for this particle.
2024-01-03 13:19:10 -08:00
/// This is most likely animated.
pub sprite: SpriteHandle,
2024-01-03 13:19:10 -08:00
/// 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,
}
fn parse_projectile_particle(
ct: &Content,
p: Option<syntax::ProjectileParticle>,
) -> Result<Option<ProjectileParticle>> {
if let Some(impact) = p {
let impact_sprite_handle = match ct.sprite_index.get(&impact.sprite) {
None => bail!("impact sprite `{}` doesn't exist", impact.sprite),
2024-01-03 13:19:10 -08:00
Some(t) => *t,
};
let impact_lifetime = match impact.lifetime {
syntax::ParticleLifetime::Seconds(s) => s,
syntax::ParticleLifetime::Inherit(s) => {
if s == "inherit" {
let sprite = ct.get_sprite(impact_sprite_handle);
sprite.fps * sprite.frames.len() as f32
2024-01-03 13:19:10 -08:00
} else {
bail!("bad impact lifetime, must be float or \"inherit\"",)
}
}
};
Ok(Some(ProjectileParticle {
sprite: impact_sprite_handle,
2024-01-03 13:19:10 -08:00
lifetime: impact_lifetime,
inherit_velocity: impact.inherit_velocity,
size: impact.size,
}))
} else {
Ok(None)
}
2023-12-27 20:13:39 -08:00
}
2023-12-30 16:57:03 -08:00
impl crate::Build for Gun {
type InputSyntaxType = HashMap<String, syntax::Gun>;
fn build(gun: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
2023-12-27 20:13:39 -08:00
for (gun_name, gun) in gun {
let projectile_sprite_handle = match ct.sprite_index.get(&gun.projectile.sprite) {
None => bail!(
"In gun `{}`: projectile sprite `{}` doesn't exist",
gun_name,
gun.projectile.sprite
),
Some(t) => *t,
};
2024-01-03 13:19:10 -08:00
let impact_particle = parse_projectile_particle(ct, gun.projectile.impact)
.with_context(|| format!("In gun `{}`", gun_name))?;
let expire_particle = parse_projectile_particle(ct, gun.projectile.expire)
.with_context(|| format!("In gun `{}`", gun_name))?;
ct.guns.push(Self {
name: gun_name,
2023-12-30 21:05:06 -08:00
space: gun.space.into(),
2023-12-28 17:04:41 -08:00
rate: gun.rate,
rate_rng: gun.rate_rng,
2023-12-27 20:13:39 -08:00
projectile: Projectile {
2024-01-03 13:19:10 -08:00
force: gun.projectile.force,
sprite: projectile_sprite_handle,
2023-12-27 20:13:39 -08:00
size: gun.projectile.size,
2023-12-28 17:04:41 -08:00
size_rng: gun.projectile.size_rng,
2023-12-27 20:13:39 -08:00
speed: gun.projectile.speed,
2023-12-28 17:04:41 -08:00
speed_rng: gun.projectile.speed_rng,
2023-12-27 20:13:39 -08:00
lifetime: gun.projectile.lifetime,
2023-12-28 17:04:41 -08:00
lifetime_rng: gun.projectile.lifetime_rng,
damage: gun.projectile.damage,
2024-01-01 15:41:47 -08:00
angle_rng: Deg(gun.projectile.angle_rng),
2024-01-03 13:19:10 -08:00
impact_particle,
expire_particle,
collider: gun.projectile.collider,
2023-12-27 20:13:39 -08:00
},
});
}
return Ok(());
2023-12-27 20:13:39 -08:00
}
}