diff --git a/content/engines.toml b/content/engines.toml deleted file mode 100644 index c21709b..0000000 --- a/content/engines.toml +++ /dev/null @@ -1,4 +0,0 @@ -[engine."plasma"] - -thrust = 100 -flare.sprite_texture = "flare::ion" diff --git a/content/outfits.toml b/content/outfits.toml new file mode 100644 index 0000000..d44b79a --- /dev/null +++ b/content/outfits.toml @@ -0,0 +1,4 @@ +[outfit."plasma engines"] +engine.thrust = 100 +engine.flare_texture = "flare::ion" +steering.power = 20 diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index 7d92d4c..cb255ea 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -18,21 +18,21 @@ use toml; use walkdir::WalkDir; 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 { use anyhow::{bail, Result}; use serde::Deserialize; 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)] pub struct Root { pub gun: Option>, pub ship: Option>, pub system: Option>, - pub engine: Option>, + pub outfit: Option>, pub texture: Option>, pub faction: Option>, } @@ -43,7 +43,7 @@ mod syntax { gun: None, ship: None, system: None, - engine: None, + outfit: None, texture: None, faction: None, } @@ -97,11 +97,11 @@ mod syntax { } } - if let Some(a) = other.engine { - if self.engine.is_none() { - self.engine = Some(a); + if let Some(a) = other.outfit { + if self.outfit.is_none() { + self.outfit = Some(a); } else { - let sg = self.engine.as_mut().unwrap(); + let sg = self.outfit.as_mut().unwrap(); for (k, v) in a { if sg.contains_key(&k) { bail!("Repeated engine name {k}"); @@ -165,11 +165,11 @@ pub struct Content { /// Ship bodies pub ships: Vec, - /// Gun outfits + /// Ship guns pub guns: Vec, - /// Engine outfits - pub engines: Vec, + /// Outfits + pub outfits: Vec, /// Textures pub textures: Vec, @@ -256,7 +256,7 @@ impl Content { systems: Vec::new(), ships: Vec::new(), guns: Vec::new(), - engines: Vec::new(), + outfits: Vec::new(), textures: Vec::new(), factions: Vec::new(), texture_index: HashMap::new(), @@ -275,8 +275,8 @@ impl Content { if root.gun.is_some() { part::gun::Gun::build(root.gun.take().unwrap(), &mut content)?; } - if root.engine.is_some() { - part::engine::Engine::build(root.engine.take().unwrap(), &mut content)?; + if root.outfit.is_some() { + part::outfit::Outfit::build(root.outfit.take().unwrap(), &mut content)?; } if root.system.is_some() { part::system::System::build(root.system.take().unwrap(), &mut content)?; diff --git a/crates/content/src/part/engine.rs b/crates/content/src/part/engine.rs deleted file mode 100644 index 27342eb..0000000 --- a/crates/content/src/part/engine.rs +++ /dev/null @@ -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; - - 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(()); - } -} diff --git a/crates/content/src/part/mod.rs b/crates/content/src/part/mod.rs index ccdff82..62b35c9 100644 --- a/crates/content/src/part/mod.rs +++ b/crates/content/src/part/mod.rs @@ -1,15 +1,15 @@ //! Content parts -pub mod engine; pub mod faction; pub mod gun; +pub mod outfit; pub mod ship; pub mod system; pub mod texture; -pub use engine::Engine; pub use faction::{Faction, Relationship}; pub use gun::{Gun, Projectile}; +pub use outfit::Outfit; pub use ship::{EnginePoint, GunPoint, Ship}; pub use system::{Object, System}; pub use texture::Texture; diff --git a/crates/content/src/part/outfit.rs b/crates/content/src/part/outfit.rs new file mode 100644 index 0000000..bda600d --- /dev/null +++ b/crates/content/src/part/outfit.rs @@ -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, + pub steering: Option, + } + + #[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, +} + +impl crate::Build for Outfit { + type InputSyntax = HashMap; + + 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(()); + } +} diff --git a/src/game/game.rs b/src/game/game.rs index ef6caa2..35f9a81 100644 --- a/src/game/game.rs +++ b/src/game/game.rs @@ -31,29 +31,32 @@ impl Game { pub fn new(ct: Content) -> Self { 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( &ct.ships[0], - vec![ - 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()), - ], + o1, Point2 { x: 0.0, y: 0.0 }, FactionHandle { index: 0 }, ); let h2 = physics.add_ship( &ct.ships[0], - vec![], + outfits::ShipOutfits::new(&ct.ships[0]), Point2 { x: 300.0, y: 300.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( &ct.ships[0], - vec![outfits::ShipOutfit::Gun(outfits::ShipGun::new( - &ct.guns[0], - 0, - ))], + o2, Point2 { x: -300.0, y: 300.0, diff --git a/src/game/outfits.rs b/src/game/outfits.rs index dd75669..981a7d3 100644 --- a/src/game/outfits.rs +++ b/src/game/outfits.rs @@ -1,8 +1,10 @@ use cgmath::{Deg, Point3}; +use content::TextureHandle; use crate::{content, render::SubSprite}; /// Represents a gun attached to a specific ship at a certain gunpoint. +#[derive(Debug)] pub struct ShipGun { pub kind: content::Gun, pub cooldown: f32, @@ -10,39 +12,63 @@ pub struct ShipGun { } impl ShipGun { - pub fn new(kind: &content::Gun, point: usize) -> Self { + pub fn new(kind: content::Gun, point: usize) -> Self { Self { - kind: kind.clone(), + kind: kind, point, cooldown: 0.0, } } } -/// Represents a specific outfit attached to a specific ship -pub enum ShipOutfit { - Gun(ShipGun), - Engine(content::Engine), +/// This struct keeps track of the combined stats of a set of outfits. +/// It does NOT check for sanity (e.g, removing an outfit that was never added) +/// That is handled by ShipOutfits. +#[derive(Debug)] +pub struct OutfitStatSum { + pub engine_thrust: f32, + pub steer_power: f32, + pub engine_flare_textures: Vec, } -impl ShipOutfit { - pub fn gun(&mut self) -> Option<&mut ShipGun> { - match self { - Self::Gun(g) => Some(g), - _ => None, +impl OutfitStatSum { + pub fn new() -> Self { + Self { + engine_thrust: 0.0, + steer_power: 0.0, + engine_flare_textures: Vec::new(), } } - pub fn engine(&self) -> Option<&content::Engine> { - match self { - Self::Engine(e) => Some(e), - _ => None, + pub fn add(&mut self, o: &content::Outfit) { + self.engine_thrust += o.engine_thrust; + if let Some(t) = o.engine_flare_texture { + 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 { - outfits: Vec, + pub stats: OutfitStatSum, + outfits: Vec, + guns: Vec, enginepoints: Vec, gunpoints: Vec, @@ -54,66 +80,90 @@ pub struct ShipOutfits { impl<'a> ShipOutfits { pub fn new(content: &content::Ship) -> Self { Self { + stats: OutfitStatSum::new(), outfits: Vec::new(), + guns: Vec::new(), enginepoints: content.engines.clone(), gunpoints: content.guns.clone(), 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.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 { - self.outfits - .iter_mut() - .map(|x| x.gun()) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) + self.guns.iter_mut() } pub fn iter_guns_points(&mut self) -> impl Iterator { - self.outfits + self.guns .iter_mut() - .map(|x| x.gun()) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) .map(|x| (&self.gunpoints[x.point], x)) .map(|(a, b)| (b, a)) } - pub fn iter_engines(&self) -> impl Iterator { - self.outfits - .iter() - .map(|x| x.engine()) - .filter(|x| x.is_some()) - .map(|x| x.unwrap()) - } - - pub fn iter_enginepoints(&self) -> impl Iterator { - self.enginepoints.iter() - } - pub fn update_engine_flares(&mut self) { // TODO: better way to pick flare texture self.engine_flare_sprites.clear(); - let t = if let Some(e) = self.iter_engines().next() { - e.flare_sprite_texture + let t = if let Some(e) = self.stats.engine_flare_textures.iter().next() { + e } else { return; }; self.engine_flare_sprites = self - .iter_enginepoints() + .enginepoints + .iter() .map(|p| SubSprite { pos: Point3 { x: p.pos.x, y: p.pos.y, z: 1.0, }, - texture: t, + texture: *t, angle: Deg(0.0), size: p.size, }) diff --git a/src/objects/ship.rs b/src/objects/ship.rs index 12ee383..2bcbf74 100644 --- a/src/objects/ship.rs +++ b/src/objects/ship.rs @@ -52,18 +52,13 @@ pub struct Ship { impl Ship { pub fn new( c: &content::Ship, - outfits: Vec, + outfits: outfits::ShipOutfits, physics_handle: ShipHandle, faction: FactionHandle, ) -> Self { - let mut o = outfits::ShipOutfits::new(c); - for x in outfits.into_iter() { - o.add(x) - } - Ship { physics_handle, - outfits: o, + outfits, sprite_texture: c.sprite_texture, size: c.size, hull: c.hull, @@ -135,17 +130,18 @@ impl Ship { let engine_force = ship_rot * t; if self.controls.thrust { - for e in self.outfits.iter_engines() { - r.apply_impulse(vector![engine_force.x, engine_force.y] * e.thrust, true); - } + r.apply_impulse( + vector![engine_force.x, engine_force.y] * self.outfits.stats.engine_thrust, + true, + ); } 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 { - 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 { diff --git a/src/physics/physics.rs b/src/physics/physics.rs index 1e3ffd5..ed6bac4 100644 --- a/src/physics/physics.rs +++ b/src/physics/physics.rs @@ -82,7 +82,7 @@ impl Physics { pub fn add_ship( &mut self, ct: &content::Ship, - outfits: Vec, + outfits: outfits::ShipOutfits, position: Point2, faction: FactionHandle, ) -> ShipHandle {