2023-12-30 16:57:03 -08:00

185 lines
4.2 KiB
Rust

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<ProjectileBuilder>,
}
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<outfits::ShipOutfit>,
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<ProjectileBuilder> {
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<f32> =
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<f32> = 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
},
}
}
}