2024-01-08 21:02:02 -08:00

187 lines
4.7 KiB
Rust

use anyhow::{bail, Context, Result};
use cgmath::{Deg, Rad};
use serde::Deserialize;
use std::collections::HashMap;
use crate::{handle::SpriteHandle, Content};
use crate::{ContentBuildContext, EffectHandle, OutfitSpace};
pub(crate) mod syntax {
use crate::part::effect;
use crate::part::outfitspace;
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,
pub rate: f32,
pub rate_rng: f32,
pub space: outfitspace::syntax::OutfitSpace,
}
#[derive(Debug, Deserialize)]
pub struct Projectile {
pub sprite: String,
pub size: f32,
pub size_rng: f32,
pub speed: f32,
pub speed_rng: f32,
pub lifetime: f32,
pub lifetime_rng: f32,
pub damage: f32,
pub angle_rng: f32,
pub impact_effect: Option<effect::syntax::EffectReference>,
pub expire_effect: Option<effect::syntax::EffectReference>,
pub collider: super::ProjectileCollider,
pub force: f32,
}
}
/// Defines a projectile's collider
#[derive(Debug, Deserialize, Clone)]
pub enum ProjectileCollider {
/// A ball collider
#[serde(rename = "ball")]
Ball(BallCollider),
}
/// A simple ball-shaped collider, centered at the object's position
#[derive(Debug, Deserialize, Clone)]
pub struct BallCollider {
/// The radius of this ball
pub radius: f32,
}
/// Represents a gun outfit.
#[derive(Debug, Clone)]
pub struct Gun {
/// The name of this gun
pub name: String,
/// The projectile this gun produces
pub projectile: Projectile,
/// Average delay between projectiles, in seconds.
pub rate: f32,
/// Random variation of projectile delay, in seconds.
/// Each shot waits (rate += rate_rng).
pub rate_rng: f32,
/// How much space this gun uses
pub space: OutfitSpace,
}
/// Represents a projectile that a [`Gun`] produces.
#[derive(Debug, Clone)]
pub struct Projectile {
/// The projectile sprite
pub sprite: SpriteHandle,
/// The average size of this projectile
/// (height in game units)
pub size: f32,
/// Random size variation
pub size_rng: f32,
/// The speed of this projectile, in game units / second
pub speed: f32,
/// Random speed variation
pub speed_rng: f32,
/// The lifespan of this projectile.
/// It will vanish if it lives this long without hitting anything.
pub lifetime: f32,
/// Random lifetime variation
pub lifetime_rng: f32,
/// The damage this projectile does
pub damage: f32,
/// The force this projectile applies
pub force: f32,
/// 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: Rad<f32>,
/// The particle this projectile will spawn when it hits something
pub impact_effect: Option<EffectHandle>,
/// The particle this projectile will spawn when it expires
pub expire_effect: Option<EffectHandle>,
/// Collider parameters for this projectile
pub collider: ProjectileCollider,
}
impl crate::Build for Gun {
type InputSyntaxType = HashMap<String, syntax::Gun>;
fn build(
gun: Self::InputSyntaxType,
build_context: &mut ContentBuildContext,
content: &mut Content,
) -> Result<()> {
for (gun_name, gun) in gun {
let projectile_sprite_handle = match content.sprite_index.get(&gun.projectile.sprite) {
None => bail!(
"projectile sprite `{}` doesn't exist in gun `{}`",
gun.projectile.sprite,
gun_name,
),
Some(t) => *t,
};
let impact_effect = match gun.projectile.impact_effect {
Some(e) => Some(
e.to_handle(build_context, content)
.with_context(|| format!("while loading gun `{}`", gun_name))?,
),
None => None,
};
let expire_effect = match gun.projectile.expire_effect {
Some(e) => Some(
e.to_handle(build_context, content)
.with_context(|| format!("while loading gun `{}`", gun_name))?,
),
None => None,
};
content.guns.push(Self {
name: gun_name,
space: gun.space.into(),
rate: gun.rate,
rate_rng: gun.rate_rng,
projectile: Projectile {
force: gun.projectile.force,
sprite: projectile_sprite_handle,
size: gun.projectile.size,
size_rng: gun.projectile.size_rng,
speed: gun.projectile.speed,
speed_rng: gun.projectile.speed_rng,
lifetime: gun.projectile.lifetime,
lifetime_rng: gun.projectile.lifetime_rng,
damage: gun.projectile.damage,
// Divide by 2, so the angle matches the angle of the fire cone.
// This should ALWAYS be done in the content parser.
angle_rng: Deg(gun.projectile.angle_rng / 2.0).into(),
impact_effect,
expire_effect,
collider: gun.projectile.collider,
},
});
}
return Ok(());
}
}