use cgmath::{Deg, EuclideanSpace, Matrix2, Rad, Vector2}; use nalgebra::vector; use rand::Rng; use rapier2d::dynamics::{RigidBody, RigidBodyBuilder}; use rapier2d::geometry::ColliderBuilder; use rapier2d::pipeline::ActiveEvents; use super::ProjectileBuilder; use crate::{ content, game::{outfits, util}, inputstatus::InputStatus, physics::ShipHandle, render::{Sprite, SpriteTexture}, }; 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 ShipTickResult { pub projectiles: Vec, } pub struct Ship { pub physics_handle: ShipHandle, outfits: outfits::ShipOutfits, sprite: SpriteTexture, size: f32, pub hull: f32, // TODO: replace with AI enum pub controls: ShipControls, } impl Ship { pub fn new( c: &content::Ship, outfits: Vec, physics_handle: ShipHandle, ) -> Self { let mut o = outfits::ShipOutfits::new(c); for x in outfits.into_iter() { o.add(x) } Ship { physics_handle, outfits: o, controls: ShipControls::new(), sprite: SpriteTexture(c.sprite.clone()), size: c.size, hull: c.hull, } } pub fn update_controls(&mut self, input: &InputStatus) { self.controls.thrust = input.key_thrust; self.controls.right = input.key_right; self.controls.left = input.key_left; self.controls.guns = input.key_guns; } 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_ang: Deg = util::rigidbody_angle(r); let ship_ang_rad: Rad = ship_ang.into(); let ship_vel = util::rigidbody_velocity(r); let pos = ship_pos + (Matrix2::from_angle(ship_ang) * p.pos.to_vec()); let vel = ship_vel + (Matrix2::from_angle( ship_ang + Deg(rng.gen_range(-(g.kind.spread.0 / 2.0)..=g.kind.spread.0 / 2.0)), ) * 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_rad.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: SpriteTexture(g.kind.projectile.sprite.clone()), 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 }) } return out; } pub fn tick(&mut self, r: &mut RigidBody, t: f32) -> ShipTickResult { let ship_ang = util::rigidbody_angle(r); let engine_force = Matrix2::from_angle(ship_ang) * Vector2 { x: 0.0, y: 1.0 } * 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_ang = util::rigidbody_angle(r); Sprite { pos: (ship_pos.x, ship_pos.y, 1.0).into(), texture: self.sprite.clone(), // TODO: sprite texture should be easy to clone angle: ship_ang, size: self.size, children: if self.controls.thrust { Some(self.outfits.get_engine_flares()) } else { None }, } } }