use std::{collections::HashMap, time::Instant}; use crate::GxShipHandle; use super::{OutfitSet, ShipPersonality}; use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle}; use rand::{rngs::ThreadRng, Rng}; #[derive(Debug)] pub struct GxShip { // Metadata values handle: GxShipHandle, ct_handle: ShipHandle, faction: FactionHandle, outfits: OutfitSet, personality: ShipPersonality, // State values // TODO: unified ship stats struct, like outfit space hull: f32, shields: f32, gun_cooldowns: HashMap, rng: ThreadRng, // Utility values /// The last time this ship was damaged last_hit: Instant, } impl GxShip { pub(crate) fn new( ct: &Content, handle: GxShipHandle, ct_handle: ShipHandle, faction: FactionHandle, personality: ShipPersonality, ) -> Self { let s = ct.get_ship(ct_handle); GxShip { handle, ct_handle, faction, outfits: OutfitSet::new(s.space, &s.guns), personality, last_hit: Instant::now(), rng: rand::thread_rng(), // Initial stats hull: s.hull, shields: 0.0, gun_cooldowns: s.guns.iter().map(|x| (x.clone(), 0.0)).collect(), } } /// Add an outfit to this ship pub fn add_outfit(&mut self, o: &Outfit) -> super::OutfitAddResult { let r = self.outfits.add(o); self.shields = self.outfits.get_shield_strength(); return r; } /// Remove an outfit from this ship pub fn remove_outfit(&mut self, o: &Outfit) -> super::OutfitRemoveResult { self.outfits.remove(o) } /// If this ship is dead, it will be removed from the game. pub fn is_dead(&self) -> bool { self.hull <= 0.0 } /// Try to fire a gun. /// Will panic if `which` isn't a point on this ship. /// Returns `true` if this gun was fired, /// and `false` if it is on cooldown or empty. pub fn fire_gun(&mut self, ct: &Content, which: &GunPoint) -> bool { let c = self.gun_cooldowns.get_mut(which).unwrap(); if *c > 0.0 { return false; } let g = self.outfits.get_gun(which); if g.is_some() { let g = ct.get_outfit(g.unwrap()); let gun = g.gun.as_ref().unwrap(); *c = 0f32.max(gun.rate + self.rng.gen_range(-gun.rate_rng..=gun.rate_rng)); return true; } else { return false; } } /// Hit this ship with the given amount of damage pub fn apply_damage(&mut self, mut d: f32) { if self.is_dead() { return; } if self.shields >= d { self.shields -= d } else { d -= self.shields; self.shields = 0.0; self.hull = 0f32.max(self.hull - d); } self.last_hit = Instant::now(); } /// Update this ship's state by `t` seconds pub fn step(&mut self, t: f32) { // Cooldown guns for (_, c) in &mut self.gun_cooldowns { if *c > 0.0 { *c -= t; } } // Regenerate shields let time_since = self.last_hit.elapsed().as_secs_f32(); if self.shields != self.outfits.get_shield_strength() { for g in self.outfits.iter_shield_generators() { if time_since >= g.delay { self.shields += g.generation * t; if self.shields > self.outfits.get_shield_strength() { self.shields = self.outfits.get_shield_strength(); break; } } } } } } // Misc getters, so internal state is untouchable impl GxShip { /// Get a handle to this ship game object pub fn get_handle(&self) -> GxShipHandle { self.handle } /// Get a handle to this ship's content pub fn get_content(&self) -> ShipHandle { self.ct_handle } /// Get this ship's current hull. /// Use content handle to get maximum hull pub fn get_hull(&self) -> f32 { self.hull } /// Get this ship's current shields. /// Use get_outfits() for maximum shields pub fn get_shields(&self) -> f32 { self.shields } /// Get all outfits on this ship pub fn get_outfits(&self) -> &OutfitSet { &self.outfits } /// Get this ship's personality pub fn get_personality(&self) -> ShipPersonality { self.personality } /// Get this ship's faction pub fn get_faction(&self) -> FactionHandle { self.faction } /// Get this ship's content handle pub fn get_ship(&self) -> ShipHandle { self.ct_handle } }