Reworked outfits

master
Mark 2023-12-30 20:27:53 -08:00
parent ca3f289de5
commit 19fd1a91f2
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
10 changed files with 218 additions and 147 deletions

View File

@ -1,4 +0,0 @@
[engine."plasma"]
thrust = 100
flare.sprite_texture = "flare::ion"

4
content/outfits.toml Normal file
View File

@ -0,0 +1,4 @@
[outfit."plasma engines"]
engine.thrust = 100
engine.flare_texture = "flare::ion"
steering.power = 20

View File

@ -18,21 +18,21 @@ use toml;
use walkdir::WalkDir; use walkdir::WalkDir;
pub use handle::{FactionHandle, TextureHandle}; pub use handle::{FactionHandle, TextureHandle};
pub use part::{Engine, EnginePoint, Faction, Gun, GunPoint, Relationship, Ship, System, Texture}; pub use part::{EnginePoint, Faction, Gun, GunPoint, Outfit, Relationship, Ship, System, Texture};
mod syntax { mod syntax {
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use crate::part::{engine, faction, gun, ship, system, texture}; use crate::part::{faction, gun, outfit, ship, system, texture};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Root { pub struct Root {
pub gun: Option<HashMap<String, gun::syntax::Gun>>, 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 engine: Option<HashMap<String, engine::syntax::Engine>>, pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
pub texture: Option<HashMap<String, texture::syntax::Texture>>, pub texture: Option<HashMap<String, texture::syntax::Texture>>,
pub faction: Option<HashMap<String, faction::syntax::Faction>>, pub faction: Option<HashMap<String, faction::syntax::Faction>>,
} }
@ -43,7 +43,7 @@ mod syntax {
gun: None, gun: None,
ship: None, ship: None,
system: None, system: None,
engine: None, outfit: None,
texture: None, texture: None,
faction: None, faction: None,
} }
@ -97,11 +97,11 @@ mod syntax {
} }
} }
if let Some(a) = other.engine { if let Some(a) = other.outfit {
if self.engine.is_none() { if self.outfit.is_none() {
self.engine = Some(a); self.outfit = Some(a);
} else { } else {
let sg = self.engine.as_mut().unwrap(); let sg = self.outfit.as_mut().unwrap();
for (k, v) in a { for (k, v) in a {
if sg.contains_key(&k) { if sg.contains_key(&k) {
bail!("Repeated engine name {k}"); bail!("Repeated engine name {k}");
@ -165,11 +165,11 @@ pub struct Content {
/// Ship bodies /// Ship bodies
pub ships: Vec<part::ship::Ship>, pub ships: Vec<part::ship::Ship>,
/// Gun outfits /// Ship guns
pub guns: Vec<part::gun::Gun>, pub guns: Vec<part::gun::Gun>,
/// Engine outfits /// Outfits
pub engines: Vec<part::engine::Engine>, pub outfits: Vec<part::outfit::Outfit>,
/// Textures /// Textures
pub textures: Vec<part::texture::Texture>, pub textures: Vec<part::texture::Texture>,
@ -256,7 +256,7 @@ impl Content {
systems: Vec::new(), systems: Vec::new(),
ships: Vec::new(), ships: Vec::new(),
guns: Vec::new(), guns: Vec::new(),
engines: Vec::new(), outfits: Vec::new(),
textures: Vec::new(), textures: Vec::new(),
factions: Vec::new(), factions: Vec::new(),
texture_index: HashMap::new(), texture_index: HashMap::new(),
@ -275,8 +275,8 @@ impl Content {
if root.gun.is_some() { if root.gun.is_some() {
part::gun::Gun::build(root.gun.take().unwrap(), &mut content)?; part::gun::Gun::build(root.gun.take().unwrap(), &mut content)?;
} }
if root.engine.is_some() { if root.outfit.is_some() {
part::engine::Engine::build(root.engine.take().unwrap(), &mut content)?; part::outfit::Outfit::build(root.outfit.take().unwrap(), &mut content)?;
} }
if root.system.is_some() { if root.system.is_some() {
part::system::System::build(root.system.take().unwrap(), &mut content)?; part::system::System::build(root.system.take().unwrap(), &mut content)?;

View File

@ -1,62 +0,0 @@
use std::collections::HashMap;
use anyhow::{bail, Result};
use crate::{handle::TextureHandle, Content};
pub(crate) mod syntax {
use serde::Deserialize;
// Raw serde syntax structs.
// These are never seen by code outside this crate.
#[derive(Debug, Deserialize)]
pub struct Engine {
pub thrust: f32,
pub flare: Flare,
}
#[derive(Debug, Deserialize)]
pub struct Flare {
pub sprite_texture: String,
}
}
/// Represents an (foward) engine outfit that may be attached to a ship.
#[derive(Debug, Clone)]
pub struct Engine {
/// The name of this outfit
pub name: String,
/// How much thrust this engine produces
pub thrust: f32,
/// The flare sprite this engine creates.
/// Its location and size is determined by a ship's
/// engine points.
pub flare_sprite_texture: TextureHandle,
}
impl crate::Build for Engine {
type InputSyntax = HashMap<String, syntax::Engine>;
fn build(engine: Self::InputSyntax, ct: &mut Content) -> Result<()> {
for (engine_name, engine) in engine {
let th = match ct.texture_index.get(&engine.flare.sprite_texture) {
None => bail!(
"In engine `{}`: texture `{}` doesn't exist",
engine_name,
engine.flare.sprite_texture
),
Some(t) => *t,
};
ct.engines.push(Self {
name: engine_name,
thrust: engine.thrust,
flare_sprite_texture: th,
});
}
return Ok(());
}
}

View File

@ -1,15 +1,15 @@
//! Content parts //! Content parts
pub mod engine;
pub mod faction; pub mod faction;
pub mod gun; pub mod gun;
pub mod outfit;
pub mod ship; pub mod ship;
pub mod system; pub mod system;
pub mod texture; pub mod texture;
pub use engine::Engine;
pub use faction::{Faction, Relationship}; pub use faction::{Faction, Relationship};
pub use gun::{Gun, Projectile}; pub use gun::{Gun, Projectile};
pub use outfit::Outfit;
pub use ship::{EnginePoint, GunPoint, Ship}; pub use ship::{EnginePoint, GunPoint, Ship};
pub use system::{Object, System}; pub use system::{Object, System};
pub use texture::Texture; pub use texture::Texture;

View File

@ -0,0 +1,84 @@
use std::collections::HashMap;
use anyhow::{bail, Result};
use crate::{handle::TextureHandle, Content};
pub(crate) mod syntax {
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>,
}
#[derive(Debug, Deserialize)]
pub struct Engine {
pub thrust: f32,
pub flare_texture: String,
}
#[derive(Debug, Deserialize)]
pub struct Steering {
pub power: f32,
}
}
/// Represents an outfit that may be attached to a ship.
#[derive(Debug, Clone)]
pub struct Outfit {
/// 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_texture: Option<TextureHandle>,
}
impl crate::Build for Outfit {
type InputSyntax = HashMap<String, syntax::Outfit>;
fn build(outfits: Self::InputSyntax, ct: &mut Content) -> Result<()> {
for (outfit_name, outfit) in outfits {
let mut o = Self {
name: outfit_name.clone(),
engine_thrust: 0.0,
steer_power: 0.0,
engine_flare_texture: None,
};
// Engine stats
if let Some(engine) = outfit.engine {
let th = match ct.texture_index.get(&engine.flare_texture) {
None => bail!(
"In outfit `{}`: texture `{}` doesn't exist",
outfit_name,
engine.flare_texture
),
Some(t) => *t,
};
o.engine_thrust = engine.thrust;
o.engine_flare_texture = Some(th);
}
// Steering stats
if let Some(steer) = outfit.steering {
o.steer_power = steer.power;
}
ct.outfits.push(o);
}
return Ok(());
}
}

View File

@ -31,29 +31,32 @@ impl Game {
pub fn new(ct: Content) -> Self { pub fn new(ct: Content) -> Self {
let mut physics = Physics::new(); let mut physics = Physics::new();
let mut o1 = outfits::ShipOutfits::new(&ct.ships[0]);
o1.add(ct.outfits[0].clone());
o1.add_gun(ct.guns[0].clone());
o1.add_gun(ct.guns[0].clone());
let h1 = physics.add_ship( let h1 = physics.add_ship(
&ct.ships[0], &ct.ships[0],
vec![ o1,
outfits::ShipOutfit::Gun(outfits::ShipGun::new(&ct.guns[0], 1)),
outfits::ShipOutfit::Gun(outfits::ShipGun::new(&ct.guns[0], 2)),
outfits::ShipOutfit::Engine(ct.engines[0].clone()),
],
Point2 { x: 0.0, y: 0.0 }, Point2 { x: 0.0, y: 0.0 },
FactionHandle { index: 0 }, FactionHandle { index: 0 },
); );
let h2 = physics.add_ship( let h2 = physics.add_ship(
&ct.ships[0], &ct.ships[0],
vec![], outfits::ShipOutfits::new(&ct.ships[0]),
Point2 { x: 300.0, y: 300.0 }, Point2 { x: 300.0, y: 300.0 },
FactionHandle { index: 0 }, FactionHandle { index: 0 },
); );
let mut o2 = outfits::ShipOutfits::new(&ct.ships[0]);
o2.add(ct.outfits[0].clone());
o2.add_gun(ct.guns[0].clone());
println!("{:?}", o2);
let h3 = physics.add_ship( let h3 = physics.add_ship(
&ct.ships[0], &ct.ships[0],
vec![outfits::ShipOutfit::Gun(outfits::ShipGun::new( o2,
&ct.guns[0],
0,
))],
Point2 { Point2 {
x: -300.0, x: -300.0,
y: 300.0, y: 300.0,

View File

@ -1,8 +1,10 @@
use cgmath::{Deg, Point3}; use cgmath::{Deg, Point3};
use content::TextureHandle;
use crate::{content, render::SubSprite}; use crate::{content, render::SubSprite};
/// Represents a gun attached to a specific ship at a certain gunpoint. /// Represents a gun attached to a specific ship at a certain gunpoint.
#[derive(Debug)]
pub struct ShipGun { pub struct ShipGun {
pub kind: content::Gun, pub kind: content::Gun,
pub cooldown: f32, pub cooldown: f32,
@ -10,39 +12,63 @@ pub struct ShipGun {
} }
impl ShipGun { impl ShipGun {
pub fn new(kind: &content::Gun, point: usize) -> Self { pub fn new(kind: content::Gun, point: usize) -> Self {
Self { Self {
kind: kind.clone(), kind: kind,
point, point,
cooldown: 0.0, cooldown: 0.0,
} }
} }
} }
/// Represents a specific outfit attached to a specific ship /// This struct keeps track of the combined stats of a set of outfits.
pub enum ShipOutfit { /// It does NOT check for sanity (e.g, removing an outfit that was never added)
Gun(ShipGun), /// That is handled by ShipOutfits.
Engine(content::Engine), #[derive(Debug)]
pub struct OutfitStatSum {
pub engine_thrust: f32,
pub steer_power: f32,
pub engine_flare_textures: Vec<TextureHandle>,
} }
impl ShipOutfit { impl OutfitStatSum {
pub fn gun(&mut self) -> Option<&mut ShipGun> { pub fn new() -> Self {
match self { Self {
Self::Gun(g) => Some(g), engine_thrust: 0.0,
_ => None, steer_power: 0.0,
engine_flare_textures: Vec::new(),
} }
} }
pub fn engine(&self) -> Option<&content::Engine> { pub fn add(&mut self, o: &content::Outfit) {
match self { self.engine_thrust += o.engine_thrust;
Self::Engine(e) => Some(e), if let Some(t) = o.engine_flare_texture {
_ => None, self.engine_flare_textures.push(t);
};
self.steer_power += o.steer_power;
}
pub fn remove(&mut self, o: &content::Outfit) {
self.engine_thrust -= o.engine_thrust;
if let Some(t) = o.engine_flare_texture {
self.engine_flare_textures.remove(
self.engine_flare_textures
.iter()
.position(|x| *x == t)
.unwrap(),
);
} }
self.steer_power -= o.steer_power;
} }
} }
/// Represents all the outfits attached to a ship.
/// Keeps track of the sum of their stats, so it mustn't be re-computed every frame.
#[derive(Debug)]
pub struct ShipOutfits { pub struct ShipOutfits {
outfits: Vec<ShipOutfit>, pub stats: OutfitStatSum,
outfits: Vec<content::Outfit>,
guns: Vec<ShipGun>,
enginepoints: Vec<content::EnginePoint>, enginepoints: Vec<content::EnginePoint>,
gunpoints: Vec<content::GunPoint>, gunpoints: Vec<content::GunPoint>,
@ -54,66 +80,90 @@ pub struct ShipOutfits {
impl<'a> ShipOutfits { impl<'a> ShipOutfits {
pub fn new(content: &content::Ship) -> Self { pub fn new(content: &content::Ship) -> Self {
Self { Self {
stats: OutfitStatSum::new(),
outfits: Vec::new(), outfits: Vec::new(),
guns: Vec::new(),
enginepoints: content.engines.clone(), enginepoints: content.engines.clone(),
gunpoints: content.guns.clone(), gunpoints: content.guns.clone(),
engine_flare_sprites: vec![], engine_flare_sprites: vec![],
} }
} }
pub fn add(&mut self, o: ShipOutfit) { /// Add an outfit to this ship.
/// Returns true on success, and false on failure
/// TODO: failure reason enum
pub fn add(&mut self, o: content::Outfit) -> bool {
self.stats.add(&o);
self.outfits.push(o); self.outfits.push(o);
self.update_engine_flares(); self.update_engine_flares();
return true;
}
/// TODO: is outfit in set?
/// TODO: don't remove nonexisting outfit
pub fn remove(&mut self, o: content::Outfit) {
self.outfits
.remove(self.outfits.iter().position(|x| x.name == o.name).unwrap());
self.stats.remove(&o);
self.update_engine_flares();
}
/// Add a gun to this outfit set.
/// This automatically attaches the gun to the first available gunpoint,
/// and returns false (applying no changes) if no points are available.
pub fn add_gun(&mut self, gun: content::Gun) -> bool {
// Find first unused point
let mut p = None;
'outer: for i in 0..self.gunpoints.len() {
for g in &self.guns {
if g.point == i {
continue 'outer;
}
}
p = Some(i);
break;
}
// All points are taken
if p.is_none() {
return false;
}
let sg = ShipGun::new(gun, p.unwrap());
self.guns.push(sg);
return true;
} }
pub fn iter_guns(&mut self) -> impl Iterator<Item = &mut ShipGun> { pub fn iter_guns(&mut self) -> impl Iterator<Item = &mut ShipGun> {
self.outfits self.guns.iter_mut()
.iter_mut()
.map(|x| x.gun())
.filter(|x| x.is_some())
.map(|x| x.unwrap())
} }
pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &content::GunPoint)> { pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &content::GunPoint)> {
self.outfits self.guns
.iter_mut() .iter_mut()
.map(|x| x.gun())
.filter(|x| x.is_some())
.map(|x| x.unwrap())
.map(|x| (&self.gunpoints[x.point], x)) .map(|x| (&self.gunpoints[x.point], x))
.map(|(a, b)| (b, a)) .map(|(a, b)| (b, a))
} }
pub fn iter_engines(&self) -> impl Iterator<Item = &content::Engine> {
self.outfits
.iter()
.map(|x| x.engine())
.filter(|x| x.is_some())
.map(|x| x.unwrap())
}
pub fn iter_enginepoints(&self) -> impl Iterator<Item = &content::EnginePoint> {
self.enginepoints.iter()
}
pub fn update_engine_flares(&mut self) { pub fn update_engine_flares(&mut self) {
// TODO: better way to pick flare texture // TODO: better way to pick flare texture
self.engine_flare_sprites.clear(); self.engine_flare_sprites.clear();
let t = if let Some(e) = self.iter_engines().next() { let t = if let Some(e) = self.stats.engine_flare_textures.iter().next() {
e.flare_sprite_texture e
} else { } else {
return; return;
}; };
self.engine_flare_sprites = self self.engine_flare_sprites = self
.iter_enginepoints() .enginepoints
.iter()
.map(|p| SubSprite { .map(|p| SubSprite {
pos: Point3 { pos: Point3 {
x: p.pos.x, x: p.pos.x,
y: p.pos.y, y: p.pos.y,
z: 1.0, z: 1.0,
}, },
texture: t, texture: *t,
angle: Deg(0.0), angle: Deg(0.0),
size: p.size, size: p.size,
}) })

View File

@ -52,18 +52,13 @@ pub struct Ship {
impl Ship { impl Ship {
pub fn new( pub fn new(
c: &content::Ship, c: &content::Ship,
outfits: Vec<outfits::ShipOutfit>, outfits: outfits::ShipOutfits,
physics_handle: ShipHandle, physics_handle: ShipHandle,
faction: FactionHandle, faction: FactionHandle,
) -> Self { ) -> Self {
let mut o = outfits::ShipOutfits::new(c);
for x in outfits.into_iter() {
o.add(x)
}
Ship { Ship {
physics_handle, physics_handle,
outfits: o, outfits,
sprite_texture: c.sprite_texture, sprite_texture: c.sprite_texture,
size: c.size, size: c.size,
hull: c.hull, hull: c.hull,
@ -135,17 +130,18 @@ impl Ship {
let engine_force = ship_rot * t; let engine_force = ship_rot * t;
if self.controls.thrust { if self.controls.thrust {
for e in self.outfits.iter_engines() { r.apply_impulse(
r.apply_impulse(vector![engine_force.x, engine_force.y] * e.thrust, true); vector![engine_force.x, engine_force.y] * self.outfits.stats.engine_thrust,
} true,
);
} }
if self.controls.right { if self.controls.right {
r.apply_torque_impulse(-1000.0 * t, true); r.apply_torque_impulse(self.outfits.stats.steer_power * -100.0 * t, true);
} }
if self.controls.left { if self.controls.left {
r.apply_torque_impulse(1000.0 * t, true); r.apply_torque_impulse(self.outfits.stats.steer_power * 100.0 * t, true);
} }
let p = if self.controls.guns { let p = if self.controls.guns {

View File

@ -82,7 +82,7 @@ impl Physics {
pub fn add_ship( pub fn add_ship(
&mut self, &mut self,
ct: &content::Ship, ct: &content::Ship,
outfits: Vec<outfits::ShipOutfit>, outfits: outfits::ShipOutfits,
position: Point2<f32>, position: Point2<f32>,
faction: FactionHandle, faction: FactionHandle,
) -> ShipHandle { ) -> ShipHandle {