294 lines
7.2 KiB
Rust
Raw Normal View History

2024-01-09 17:22:52 -08:00
use anyhow::{bail, Context, Result};
use serde::Deserialize;
2024-01-12 22:47:40 -08:00
use std::collections::HashMap;
2023-12-30 20:27:53 -08:00
2024-01-09 17:22:52 -08:00
use crate::{
handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace,
};
2023-12-30 21:05:06 -08:00
2023-12-30 20:27:53 -08:00
pub(crate) mod syntax {
2024-01-09 17:22:52 -08:00
use crate::{effect, part::outfitspace, ContentBuildContext};
use anyhow::{bail, Result};
2024-01-12 22:47:40 -08:00
use galactica_util::to_radians;
2023-12-30 20:27:53 -08:00
use serde::Deserialize;
// Raw serde syntax structs.
// These are never seen by code outside this crate.
#[derive(Debug, Deserialize)]
pub struct Outfit {
pub engine: Option<Engine>,
pub steering: Option<Steering>,
2024-01-05 12:09:59 -08:00
pub space: outfitspace::syntax::OutfitSpace,
2024-01-08 20:43:58 -08:00
pub shield: Option<Shield>,
2024-01-09 17:22:52 -08:00
pub gun: Option<Gun>,
2024-01-08 20:43:58 -08:00
}
#[derive(Debug, Deserialize)]
pub struct Shield {
pub strength: Option<f32>,
pub generation: Option<f32>,
pub delay: Option<f32>,
// more stats: permiability, shield armor, ramp, etc
2023-12-30 20:27:53 -08:00
}
#[derive(Debug, Deserialize)]
pub struct Engine {
pub thrust: f32,
pub flare_sprite: String,
2023-12-30 20:27:53 -08:00
}
#[derive(Debug, Deserialize)]
pub struct Steering {
pub power: f32,
}
2024-01-09 17:22:52 -08:00
#[derive(Debug, Deserialize)]
pub struct Gun {
pub projectile: Projectile,
pub rate: f32,
pub rate_rng: Option<f32>,
}
impl Gun {
pub fn build(
self,
build_context: &mut ContentBuildContext,
content: &mut crate::Content,
) -> Result<super::Gun> {
let projectile_sprite_handle = match content.sprite_index.get(&self.projectile.sprite) {
None => bail!(
"projectile sprite `{}` doesn't exist",
self.projectile.sprite,
),
Some(t) => *t,
};
let impact_effect = match self.projectile.impact_effect {
Some(e) => Some(e.to_handle(build_context, content)?),
None => None,
};
let expire_effect = match self.projectile.expire_effect {
Some(e) => Some(e.to_handle(build_context, content)?),
None => None,
};
return Ok(super::Gun {
rate: self.rate,
rate_rng: self.rate_rng.unwrap_or(0.0),
projectile: super::Projectile {
force: self.projectile.force,
sprite: projectile_sprite_handle,
size: self.projectile.size,
size_rng: self.projectile.size_rng,
speed: self.projectile.speed,
speed_rng: self.projectile.speed_rng,
lifetime: self.projectile.lifetime,
lifetime_rng: self.projectile.lifetime_rng,
damage: self.projectile.damage,
// Divide by 2, so the angle matches the angle of the fire cone.
// This should ALWAYS be done in the content parser.
2024-01-12 22:47:40 -08:00
angle_rng: to_radians(self.projectile.angle_rng / 2.0).into(),
2024-01-09 17:22:52 -08:00
impact_effect,
expire_effect,
collider: self.projectile.collider,
},
});
}
}
#[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,
}
2023-12-30 20:27:53 -08:00
}
/// Represents an outfit that may be attached to a ship.
#[derive(Debug, Clone)]
pub struct Outfit {
2024-01-08 20:43:58 -08:00
/// How much space this outfit requires
pub space: OutfitSpace,
/// This outfit's handle
pub handle: OutfitHandle,
2023-12-30 20:27:53 -08:00
/// The name of this outfit
pub name: String,
/// How much engine thrust this outfit produces
pub engine_thrust: f32,
/// How much steering power this outfit provids
pub steer_power: f32,
/// The engine flare sprite this outfit creates.
/// Its location and size is determined by a ship's
/// engine points.
pub engine_flare_sprite: Option<SpriteHandle>,
2023-12-30 21:05:06 -08:00
2024-01-08 20:43:58 -08:00
/// Shield hit points
pub shield_strength: f32,
/// Shield regeneration rate, per second
pub shield_generation: f32,
/// Wait this many seconds after taking damage before regenerating shields
pub shield_delay: f32,
2024-01-09 17:22:52 -08:00
/// This outfit's gun stats.
/// If this is some, this outfit requires a gun point.
pub gun: Option<Gun>,
}
/// 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 gun stats of an outfit.
/// If an outfit has this value, it requires a gun point.
#[derive(Debug, Clone)]
pub struct Gun {
/// 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,
}
/// 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,
2024-01-12 22:47:40 -08:00
/// The angle variation of this projectile, in radians
pub angle_rng: f32,
2024-01-09 17:22:52 -08:00
/// 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,
2023-12-30 20:27:53 -08:00
}
impl crate::Build for Outfit {
type InputSyntaxType = HashMap<String, syntax::Outfit>;
2023-12-30 20:27:53 -08:00
2024-01-05 12:09:59 -08:00
fn build(
outfits: Self::InputSyntaxType,
2024-01-09 17:22:52 -08:00
build_context: &mut ContentBuildContext,
2024-01-05 12:09:59 -08:00
content: &mut Content,
) -> Result<()> {
2023-12-30 20:27:53 -08:00
for (outfit_name, outfit) in outfits {
2024-01-08 20:43:58 -08:00
let handle = OutfitHandle {
index: content.outfits.len(),
};
2024-01-09 17:22:52 -08:00
let gun = match outfit.gun {
None => None,
Some(g) => Some(
g.build(build_context, content)
.with_context(|| format!("in outfit {}", outfit_name))?,
),
};
2023-12-30 20:27:53 -08:00
let mut o = Self {
2024-01-09 17:22:52 -08:00
gun,
2024-01-08 20:43:58 -08:00
handle,
2023-12-30 20:27:53 -08:00
name: outfit_name.clone(),
engine_thrust: 0.0,
steer_power: 0.0,
engine_flare_sprite: None,
2023-12-30 21:05:06 -08:00
space: OutfitSpace::from(outfit.space),
2024-01-08 20:43:58 -08:00
shield_delay: 0.0,
shield_generation: 0.0,
shield_strength: 0.0,
2023-12-30 20:27:53 -08:00
};
// Engine stats
if let Some(engine) = outfit.engine {
2024-01-05 12:09:59 -08:00
let th = match content.sprite_index.get(&engine.flare_sprite) {
2023-12-30 20:27:53 -08:00
None => bail!(
"In outfit `{}`: flare sprite `{}` doesn't exist",
2023-12-30 20:27:53 -08:00
outfit_name,
engine.flare_sprite
2023-12-30 20:27:53 -08:00
),
Some(t) => *t,
};
o.engine_thrust = engine.thrust;
o.engine_flare_sprite = Some(th);
2023-12-30 20:27:53 -08:00
}
// Steering stats
if let Some(steer) = outfit.steering {
o.steer_power = steer.power;
}
2024-01-08 20:43:58 -08:00
// Shield stats
if let Some(shield) = outfit.shield {
o.shield_delay = shield.delay.unwrap_or(0.0);
o.shield_generation = shield.generation.unwrap_or(0.0);
o.shield_strength = shield.strength.unwrap_or(0.0);
}
2024-01-05 12:09:59 -08:00
content.outfits.push(o);
2023-12-30 20:27:53 -08:00
}
return Ok(());
}
}