use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2}; use content::{FactionHandle, TextureHandle}; use nalgebra::vector; use rand::Rng; use rapier2d::{ dynamics::{RigidBody, RigidBodyBuilder}, geometry::ColliderBuilder, pipeline::ActiveEvents, }; use super::ProjectileBuilder; use crate::{ content, game::outfits, physics::{util, ShipHandle}, render::Sprite, }; pub struct ShipTickResult { pub projectiles: Vec, } pub struct ShipControls { pub left: bool, pub right: bool, pub thrust: bool, pub guns: bool, } impl ShipControls { pub fn new() -> Self { ShipControls { left: false, right: false, thrust: false, guns: false, } } } pub struct Ship { pub physics_handle: ShipHandle, pub faction: FactionHandle, pub hull: f32, pub controls: ShipControls, outfits: outfits::ShipOutfits, sprite_texture: TextureHandle, size: f32, } impl Ship { pub fn new( c: &content::Ship, outfits: Vec, 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, sprite_texture: c.sprite_texture, size: c.size, hull: c.hull, controls: ShipControls::new(), faction, } } pub fn fire_guns(&mut self, r: &RigidBody) -> Vec { let mut rng = rand::thread_rng(); let mut out = Vec::new(); for (g, p) in self.outfits.iter_guns_points() { if g.cooldown > 0.0 { continue; } g.cooldown = g.kind.rate + rng.gen_range(-g.kind.rate_rng..=g.kind.rate_rng); let ship_pos = util::rigidbody_position(r); let ship_rot = util::rigidbody_rotation(r); let ship_vel = util::rigidbody_velocity(r); let ship_ang = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); let pos = ship_pos + (Matrix2::from_angle(-ship_ang) * p.pos.to_vec()); let spread: Rad = Deg(rng.gen_range(-(g.kind.spread.0 / 2.0)..=g.kind.spread.0 / 2.0)).into(); let vel = ship_vel + (Matrix2::from_angle(-ship_ang + spread) * Vector2 { x: 0.0, y: g.kind.projectile.speed + rng.gen_range( -g.kind.projectile.speed_rng..=g.kind.projectile.speed_rng, ), }); let p_r = RigidBodyBuilder::kinematic_velocity_based() .translation(vector![pos.x, pos.y]) .rotation(-ship_ang.0) .linvel(vector![vel.x, vel.y]); let p_c = ColliderBuilder::ball(5.0) .sensor(true) .active_events(ActiveEvents::COLLISION_EVENTS); out.push(ProjectileBuilder { rigid_body: p_r, collider: p_c, sprite_texture: g.kind.projectile.sprite_texture, lifetime: g.kind.projectile.lifetime + rng.gen_range( -g.kind.projectile.lifetime_rng..=g.kind.projectile.lifetime_rng, ), size: g.kind.projectile.size + rng.gen_range(-g.kind.projectile.size_rng..=g.kind.projectile.size_rng), damage: g.kind.projectile.damage, // TODO: kind as param to builder faction: self.faction, }) } return out; } /// Apply the effects of all active controls pub fn apply_controls(&mut self, r: &mut RigidBody, t: f32) -> ShipTickResult { let ship_rot = util::rigidbody_rotation(r); 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); } } if self.controls.right { r.apply_torque_impulse(-500.0 * t, true); } if self.controls.left { r.apply_torque_impulse(500.0 * t, true); } let p = if self.controls.guns { self.fire_guns(r) } else { Vec::new() }; for i in self.outfits.iter_guns() { i.cooldown -= t; } return ShipTickResult { projectiles: p }; } pub fn get_sprite(&self, r: &RigidBody) -> Sprite { let ship_pos = util::rigidbody_position(r); let ship_rot = util::rigidbody_rotation(r); // Sprites point north at 0 degrees let ship_ang: Deg = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }).into(); Sprite { pos: (ship_pos.x, ship_pos.y, 1.0).into(), texture: self.sprite_texture, angle: -ship_ang, size: self.size, children: if self.controls.thrust { Some(self.outfits.get_engine_flares()) } else { None }, } } }