use cgmath::{Deg, Point3}; use galactica_content as content; use galactica_render::ObjectSubSprite; /// Represents a gun attached to a specific ship at a certain gunpoint. #[derive(Debug)] pub struct ShipGun { /// The kind of gun this is pub kind: content::Gun, /// How many seconds we must wait before this gun can fire pub cooldown: f32, /// Index of the gunpoint this gun is attached to pub point: usize, } impl ShipGun { /// Make a new shipgun pub fn new(kind: &content::Gun, point: usize) -> Self { Self { kind: kind.clone(), point, cooldown: 0.0, } } } /// 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 OutfitStatSum { pub fn new() -> Self { Self { engine_thrust: 0.0, steer_power: 0.0, engine_flare_textures: Vec::new(), } } 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 a set of 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 OutfitSet { /// The total sum of the stats this set of outfits provides /// TODO: this shouldn't be pub pub stats: OutfitStatSum, //pub total_space: content::OutfitSpace, available_space: content::OutfitSpace, outfits: Vec, guns: Vec, enginepoints: Vec, gunpoints: Vec, // Minor performance optimization, since we // rarely need to re-compute these. engine_flare_sprites: Vec, } impl<'a> OutfitSet { /// Make a new outfit array pub fn new(content: &content::Ship) -> Self { Self { stats: OutfitStatSum::new(), outfits: Vec::new(), guns: Vec::new(), available_space: content.space.clone(), //total_space: content.space.clone(), enginepoints: content.engines.clone(), gunpoints: content.guns.clone(), engine_flare_sprites: vec![], } } /// Does this outfit set contain the specified outfit? pub fn contains_outfit(&self, o: content::OutfitHandle) -> bool { match self.outfits.iter().position(|x| *x == o) { Some(_) => true, None => false, } } /// Add an outfit to this ship. /// Returns true on success, and false on failure /// TODO: failure reason enum pub fn add(&mut self, ct: &content::Content, o: content::OutfitHandle) -> bool { let outfit = ct.get_outfit(o); if !self.available_space.can_contain(&outfit.space) { return false; } self.available_space.occupy(&outfit.space); self.stats.add(&outfit); self.outfits.push(o); self.update_engine_flares(); return true; } /// Remove an outfit from this set pub fn remove(&mut self, ct: &content::Content, o: content::OutfitHandle) { let outfit = ct.get_outfit(o); let i = match self.outfits.iter().position(|x| *x == o) { Some(i) => i, None => panic!("removed non-existing outfit"), }; self.available_space.free(&outfit.space); self.outfits.remove(i); self.stats.remove(&outfit); 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, ct: &content::Content, g: content::GunHandle) -> 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; } let gun = ct.get_gun(g); // All points are taken if p.is_none() { return false; } let sg = ShipGun::new(gun, p.unwrap()); self.guns.push(sg); return true; } /// Iterate over all guns in this outfitset pub fn iter_guns(&mut self) -> impl Iterator { self.guns.iter_mut() } /// Iterate over all guns and the gunpoints they're attached to pub fn iter_guns_points(&mut self) -> impl Iterator { self.guns .iter_mut() .map(|x| (&self.gunpoints[x.point], x)) .map(|(a, b)| (b, a)) } /// Update engine flare sprites 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.stats.engine_flare_textures.iter().next() { e } else { return; }; self.engine_flare_sprites = self .enginepoints .iter() .map(|p| ObjectSubSprite { pos: Point3 { x: p.pos.x, y: p.pos.y, z: 1.0, }, texture: *t, angle: Deg(0.0), size: p.size, }) .collect(); } /// Get the sprites we should show if this ship is firing its engines pub fn get_engine_flares(&self) -> Vec { return self.engine_flare_sprites.clone(); } }