use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero}; use galactica_content::{Content, FactionHandle, ShipHandle}; use nalgebra::{point, vector}; use rand::Rng; use rapier2d::{ dynamics::{RigidBody, RigidBodyHandle}, geometry::{Collider, ColliderHandle}, }; use crate::data::{ShipData, ShipPersonality, ShipState}; use super::{ super::{util, ParticleBuilder, PhysStepResources}, collapse::ShipCollapseSequence, }; /// A ship's controls #[derive(Debug, Clone)] pub struct ShipControls { /// True if turning left pub left: bool, /// True if turning right pub right: bool, /// True if foward thrust pub thrust: bool, /// True if firing guns pub guns: bool, } impl ShipControls { /// Create a new, empty ShipControls pub fn new() -> Self { ShipControls { left: false, right: false, thrust: false, guns: false, } } } /// A ship instance in the physics system #[derive(Debug, Clone)] pub struct PhysSimShip { /// This ship's physics handle pub rigid_body: RigidBodyHandle, /// This ship's collider pub collider: ColliderHandle, /// This ship's game data pub data: ShipData, /// This ship's controls pub(crate) controls: ShipControls, /// This ship's collapse sequence collapse_sequence: Option, } impl PhysSimShip { /// Make a new ship pub(crate) fn new( ct: &Content, handle: ShipHandle, personality: ShipPersonality, faction: FactionHandle, rigid_body: RigidBodyHandle, collider: ColliderHandle, ) -> Self { PhysSimShip { rigid_body, collider, data: ShipData::new(ct, handle, faction, personality), controls: ShipControls::new(), collapse_sequence: Some(ShipCollapseSequence::new()), } } /// Step this ship's state by t seconds pub fn step( &mut self, res: &mut PhysStepResources, rigid_body: &mut RigidBody, collider: &mut Collider, ) { self.data.step(res.t); match self.data.get_state() { ShipState::Collapsing { .. } => { // Borrow checker hack, so we may pass self.data // to the collapse sequence let mut seq = self.collapse_sequence.take().unwrap(); seq.step(res, &self.data, rigid_body, collider); self.collapse_sequence = Some(seq); } ShipState::Flying => { return self.step_live(res, rigid_body, collider); } } } /// Step this ship's state by t seconds (called when alive) fn step_live( &mut self, res: &mut PhysStepResources, rigid_body: &mut RigidBody, collider: &mut Collider, ) { let ship_content = res.ct.get_ship(self.data.get_content()); let ship_pos = util::rigidbody_position(&rigid_body); let ship_rot = util::rigidbody_rotation(rigid_body); let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 }); let mut rng = rand::thread_rng(); if self.data.get_hull() <= ship_content.damage.hull { for e in &ship_content.damage.effects { if rng.gen_range(0.0..=1.0) <= res.t / e.frequency { let effect = res.ct.get_effect(e.effect); let pos = if let Some(pos) = e.pos { pos.to_vec() } else { // Pick a random point inside this ship's collider let mut y = 0.0; let mut x = 0.0; let mut a = false; while !a { y = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0; x = rng.gen_range(-1.0..=1.0) * ship_content.size * ship_content.sprite.aspect / 2.0; a = collider.shape().contains_local_point(&point![x, y]); } Vector2 { x, y } }; let pos = ship_pos + (Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos); let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]); res.particles.push(ParticleBuilder::from_content( effect, pos, Rad::zero(), Vector2 { x: velocity.x, y: velocity.y, }, Vector2::zero(), )) } } } let engine_force = ship_rot * res.t; if self.controls.thrust { rigid_body.apply_impulse( vector![engine_force.x, engine_force.y] * self.data.get_outfits().get_engine_thrust(), true, ); } if self.controls.right { rigid_body.apply_torque_impulse( self.data.get_outfits().get_steer_power() * -100.0 * res.t, true, ); } if self.controls.left { rigid_body.apply_torque_impulse( self.data.get_outfits().get_steer_power() * 100.0 * res.t, true, ); } } } impl PhysSimShip { /// Get this ship's control state pub fn get_controls(&self) -> &ShipControls { &self.controls } }