Galactica/crates/gameobject/src/outfits.rs

215 lines
5.3 KiB
Rust

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_sprites: Vec<content::SpriteHandle>,
}
impl OutfitStatSum {
pub fn new() -> Self {
Self {
engine_thrust: 0.0,
steer_power: 0.0,
engine_flare_sprites: Vec::new(),
}
}
pub fn add(&mut self, o: &content::Outfit) {
self.engine_thrust += o.engine_thrust;
if let Some(t) = o.engine_flare_sprite {
self.engine_flare_sprites.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_sprite {
self.engine_flare_sprites.remove(
self.engine_flare_sprites
.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
stats: OutfitStatSum,
//pub total_space: content::OutfitSpace,
available_space: content::OutfitSpace,
outfits: Vec<content::OutfitHandle>,
guns: Vec<ShipGun>,
enginepoints: Vec<content::EnginePoint>,
gunpoints: Vec<content::GunPoint>,
// Minor performance optimization, since we
// rarely need to re-compute these.
engine_flare_sprites: Vec<ObjectSubSprite>,
}
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,
}
}
pub fn stat_sum(&self) -> &OutfitStatSum {
&self.stats
}
/// 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<Item = &mut ShipGun> {
self.guns.iter_mut()
}
/// Iterate over all guns and the gunpoints they're attached to
pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &content::GunPoint)> {
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 s = if let Some(e) = self.stats.engine_flare_sprites.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,
},
sprite: *s,
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<ObjectSubSprite> {
return self.engine_flare_sprites.clone();
}
}