Merged guns and outfits

master
Mark 2024-01-09 17:22:52 -08:00
parent 7b264c7c3e
commit 0095a23dd6
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
6 changed files with 210 additions and 237 deletions

View File

@ -1,37 +0,0 @@
[gun."blaster"]
space.weapon = 10
# Average delay between shots
rate = 0.2
# Random rate variation (each cooldown is +- this)
rate_rng = 0.1
# TODO: apply force to ship on fire
projectile.sprite = "projectile::blaster"
# Height of projectile in game units
projectile.size = 6
projectile.size_rng = 0.0
# Speed of projectile, in game units/second
projectile.speed = 300
projectile.speed_rng = 10.0
# Lifetime of projectile, in seconds
projectile.lifetime = 2.0
projectile.lifetime_rng = 0.2
projectile.damage = 10.0
# Random variation of firing angle.
# If this is 0, the projectile always goes straight.
# If this is 10, projectiles will form a cone of 10 degrees
# ( 5 degrees on each side )
projectile.angle_rng = 2
# How much force this projectile will apply to an object it hits
projectile.force = 0.0
projectile.collider.ball.radius = 2.0
projectile.impact_effect = "blaster impact"
projectile.expire_effect.sprite = "particle::blaster"
projectile.expire_effect.lifetime = "inherit"
projectile.expire_effect.size = 3.0
projectile.expire_effect.velocity_scale_parent = 1.0

View File

@ -14,3 +14,42 @@ space.outfit = 5
shield.generation = 10 shield.generation = 10
shield.strength = 500 shield.strength = 500
shield.delay = 2.0 shield.delay = 2.0
[outfit."blaster"]
space.weapon = 10
# Average delay between shots
gun.rate = 0.2
# Random rate variation (each cooldown is +- this)
gun.rate_rng = 0.1
# TODO: apply force to ship on fire
gun.projectile.sprite = "projectile::blaster"
# Height of projectile in game units
gun.projectile.size = 6
gun.projectile.size_rng = 0.0
# Speed of projectile, in game units/second
gun.projectile.speed = 300
gun.projectile.speed_rng = 10.0
# Lifetime of projectile, in seconds
gun.projectile.lifetime = 2.0
gun.projectile.lifetime_rng = 0.2
gun.projectile.damage = 10.0
# Random variation of firing angle.
# If this is 0, the projectile always goes straight.
# If this is 10, projectiles will form a cone of 10 degrees
# ( 5 degrees on each side )
gun.projectile.angle_rng = 2
# How much force this projectile will apply to an object it hits
gun.projectile.force = 0.0
gun.projectile.collider.ball.radius = 2.0
gun.projectile.impact_effect = "blaster impact"
gun.projectile.expire_effect.sprite = "particle::blaster"
gun.projectile.expire_effect.lifetime = "inherit"
gun.projectile.expire_effect.size = 3.0
gun.projectile.expire_effect.velocity_scale_parent = 1.0

View File

@ -28,11 +28,10 @@ mod syntax {
use serde::Deserialize; use serde::Deserialize;
use std::{collections::HashMap, fmt::Display, hash::Hash}; use std::{collections::HashMap, fmt::Display, hash::Hash};
use crate::part::{effect, faction, gun, outfit, ship, sprite, system}; use crate::part::{effect, faction, outfit, ship, sprite, system};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Root { pub struct Root {
pub gun: Option<HashMap<String, gun::syntax::Gun>>,
pub ship: Option<HashMap<String, ship::syntax::Ship>>, pub ship: Option<HashMap<String, ship::syntax::Ship>>,
pub system: Option<HashMap<String, system::syntax::System>>, pub system: Option<HashMap<String, system::syntax::System>>,
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>, pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
@ -69,7 +68,6 @@ mod syntax {
impl Root { impl Root {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
gun: None,
ship: None, ship: None,
system: None, system: None,
outfit: None, outfit: None,
@ -80,7 +78,6 @@ mod syntax {
} }
pub fn merge(&mut self, other: Root) -> Result<()> { pub fn merge(&mut self, other: Root) -> Result<()> {
merge_hashmap(&mut self.gun, other.gun).with_context(|| "while merging guns")?;
merge_hashmap(&mut self.ship, other.ship).with_context(|| "while merging ships")?; merge_hashmap(&mut self.ship, other.ship).with_context(|| "while merging ships")?;
merge_hashmap(&mut self.system, other.system) merge_hashmap(&mut self.system, other.system)
.with_context(|| "while merging systems")?; .with_context(|| "while merging systems")?;
@ -241,9 +238,6 @@ impl Content {
if root.ship.is_some() { if root.ship.is_some() {
part::ship::Ship::build(root.ship.take().unwrap(), &mut build_context, &mut content)?; part::ship::Ship::build(root.ship.take().unwrap(), &mut build_context, &mut content)?;
} }
if root.gun.is_some() {
part::gun::Gun::build(root.gun.take().unwrap(), &mut build_context, &mut content)?;
}
if root.outfit.is_some() { if root.outfit.is_some() {
part::outfit::Outfit::build( part::outfit::Outfit::build(
root.outfit.take().unwrap(), root.outfit.take().unwrap(),

View File

@ -1,186 +0,0 @@
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(());
}
}

View File

@ -2,7 +2,6 @@
pub(crate) mod effect; pub(crate) mod effect;
pub(crate) mod faction; pub(crate) mod faction;
pub(crate) mod gun;
pub(crate) mod outfit; pub(crate) mod outfit;
pub(crate) mod outfitspace; pub(crate) mod outfitspace;
pub(crate) mod ship; pub(crate) mod ship;
@ -11,8 +10,7 @@ pub(crate) mod system;
pub use effect::Effect; pub use effect::Effect;
pub use faction::{Faction, Relationship}; pub use faction::{Faction, Relationship};
pub use gun::{Gun, Projectile, ProjectileCollider}; pub use outfit::{Gun, Outfit, Projectile, ProjectileCollider};
pub use outfit::Outfit;
pub use outfitspace::OutfitSpace; pub use outfitspace::OutfitSpace;
pub use ship::{ pub use ship::{
CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship, CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship,

View File

@ -1,11 +1,17 @@
use std::collections::HashMap; use std::collections::HashMap;
use anyhow::{bail, Result}; use anyhow::{bail, Context, Result};
use cgmath::Rad;
use serde::Deserialize;
use crate::{handle::SpriteHandle, Content, ContentBuildContext, OutfitHandle, OutfitSpace}; use crate::{
handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace,
};
pub(crate) mod syntax { pub(crate) mod syntax {
use crate::part::outfitspace; use crate::{effect, part::outfitspace, ContentBuildContext};
use anyhow::{bail, Result};
use cgmath::Deg;
use serde::Deserialize; use serde::Deserialize;
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.
@ -16,6 +22,7 @@ pub(crate) mod syntax {
pub steering: Option<Steering>, pub steering: Option<Steering>,
pub space: outfitspace::syntax::OutfitSpace, pub space: outfitspace::syntax::OutfitSpace,
pub shield: Option<Shield>, pub shield: Option<Shield>,
pub gun: Option<Gun>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -36,6 +43,79 @@ pub(crate) mod syntax {
pub struct Steering { pub struct Steering {
pub power: f32, pub power: f32,
} }
#[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.
angle_rng: Deg(self.projectile.angle_rng / 2.0).into(),
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,
}
} }
/// Represents an outfit that may be attached to a ship. /// Represents an outfit that may be attached to a ship.
@ -69,6 +149,82 @@ pub struct Outfit {
/// Wait this many seconds after taking damage before regenerating shields /// Wait this many seconds after taking damage before regenerating shields
pub shield_delay: f32, pub shield_delay: f32,
/// 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,
/// The angle variation of this projectile.
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 Outfit { impl crate::Build for Outfit {
@ -76,7 +232,7 @@ impl crate::Build for Outfit {
fn build( fn build(
outfits: Self::InputSyntaxType, outfits: Self::InputSyntaxType,
_build_context: &mut ContentBuildContext, build_context: &mut ContentBuildContext,
content: &mut Content, content: &mut Content,
) -> Result<()> { ) -> Result<()> {
for (outfit_name, outfit) in outfits { for (outfit_name, outfit) in outfits {
@ -84,7 +240,16 @@ impl crate::Build for Outfit {
index: content.outfits.len(), index: content.outfits.len(),
}; };
let gun = match outfit.gun {
None => None,
Some(g) => Some(
g.build(build_context, content)
.with_context(|| format!("in outfit {}", outfit_name))?,
),
};
let mut o = Self { let mut o = Self {
gun,
handle, handle,
name: outfit_name.clone(), name: outfit_name.clone(),
engine_thrust: 0.0, engine_thrust: 0.0,