2024-01-20 10:04:31 -08:00

335 lines
8.8 KiB
Rust

use galactica_content::{GunPoint, OutfitHandle, ProjectileCollider};
use nalgebra::{Rotation2, Vector2};
use rand::Rng;
use rapier2d::{
dynamics::RigidBodyBuilder,
geometry::{ColliderBuilder, Group, InteractionGroups},
pipeline::ActiveEvents,
};
use crate::{
data::{ShipAutoPilot, ShipState},
phys::{
controller::autopilot,
objects::{PhysProjectile, ShipControls},
ParticleBuilder, PhysStepResources,
},
};
use super::PhysSim;
impl PhysSim {
/// Run ship updates:
/// - updates ship controls (runs behaviors)
/// - applies ship controls
/// - creates projectiles
fn step_ships(&mut self, res: &mut PhysStepResources) {
// We can't apply these right away since self is borrowed
// by the iterator
// TODO: don't allocate!
let mut projectiles = Vec::new();
let mut to_remove = Vec::new();
// Again, borrow checker hack. TODO: fix
let keys: Vec<_> = self.ships.keys().map(|c| *c).collect();
for collider in keys {
// Borrow immutably for now...
// (required to compute controls)
let ship = self.ships.get(&collider).unwrap();
match ship.data.get_state() {
ShipState::Dead => {
to_remove.push(collider);
}
ShipState::Landed { .. } | ShipState::Collapsing { .. } => {
let ship = self.ships.get_mut(&collider).unwrap();
ship.step(
res,
&mut self.rigid_body_set[ship.rigid_body],
&mut self.collider_set[ship.collider],
);
}
ShipState::UnLanding {
to_position,
current_z,
from,
} => {
let from_obj = res.ct.get_system_object(*from);
let controls = autopilot::auto_landing(
&res,
&self.rigid_body_set,
&self.ships,
ship.collider,
Vector2::new(to_position.translation.x, to_position.translation.y),
);
let r = &mut self.rigid_body_set[ship.rigid_body];
let max_d = (Vector2::new(from_obj.pos.x, from_obj.pos.y)
- Vector2::new(to_position.translation.x, to_position.translation.y))
.magnitude();
let now_d = (r.translation()
- Vector2::new(to_position.translation.x, to_position.translation.y))
.magnitude();
let f = now_d / max_d;
let current_z = *current_z;
let ship = self.ships.get_mut(&collider).unwrap();
let zdist = 1.0 - from_obj.pos.z;
if current_z <= 1.0 {
self.finish_unland_ship(collider);
} else if current_z <= 1.5 {
ship.data
.set_unlanding_z(1f32.max(current_z - (0.5 * res.t) / 0.5));
ship.controls = ShipControls::new();
} else {
ship.data.set_unlanding_z(1.0 - zdist * f);
if let Some(controls) = controls {
ship.controls = controls;
}
ship.step(res, r, &mut self.collider_set[ship.collider])
};
}
ShipState::Landing { target, current_z } => {
let target_obj = res.ct.get_system_object(*target);
let controls = autopilot::auto_landing(
&res,
&self.rigid_body_set,
&self.ships,
ship.collider,
Vector2::new(target_obj.pos.x, target_obj.pos.y),
);
let current_z = *current_z;
let ship = self.ships.get_mut(&collider).unwrap();
let r = &mut self.rigid_body_set[ship.rigid_body];
let zdist = target_obj.pos.z - 1.0;
if current_z >= target_obj.pos.z {
self.finish_land_ship(collider);
} else {
ship.data.set_landing_z(current_z + zdist * res.t / 2.0);
if let Some(controls) = controls {
ship.controls = controls;
}
ship.step(res, r, &mut self.collider_set[ship.collider])
};
}
ShipState::Flying { autopilot } => {
// Compute new controls
// This is why we borrow immutably first
let controls = match autopilot {
ShipAutoPilot::Landing {
target: target_handle,
} => {
let target_obj = res.ct.get_system_object(*target_handle);
let controls = autopilot::auto_landing(
&res,
&self.rigid_body_set,
&self.ships,
ship.collider,
Vector2::new(target_obj.pos.x, target_obj.pos.y),
);
let landed = self.try_land_ship(res.ct, collider, *target_handle);
if landed {
None
} else {
controls
}
}
ShipAutoPilot::None => {
let b = self.ship_behaviors.get_mut(&collider).unwrap();
b.update_controls(
&res,
&self.rigid_body_set,
&self.ships,
ship.collider,
)
}
};
// Re-borrow mutably to apply changes
let ship = self.ships.get_mut(&collider).unwrap();
if let Some(controls) = controls {
ship.controls = controls;
}
ship.step(
res,
&mut self.rigid_body_set[ship.rigid_body],
&mut self.collider_set[ship.collider],
);
// If we're firing, try to fire each gun
if ship.controls.guns {
// TODO: don't allocate here. This is a hack to satisfy the borrow checker,
// convert this to a refcell or do the replace dance.
let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = ship
.data
.get_outfits()
.iter_gun_points()
.map(|(p, o)| (p.clone(), o.clone()))
.collect();
for (gun, outfit) in pairs {
if ship.data.fire_gun(res.ct, &gun) {
projectiles.push((ship.collider, gun.clone(), outfit.unwrap()));
}
}
}
}
}
}
// Remove ships that don't exist
for c in to_remove {
self.remove_ship(res, c);
}
// Create projectiles
for (collider, gun_point, outfit) in projectiles {
let mut rng = rand::thread_rng();
let ship = self.ships.get(&collider).unwrap();
let rigid_body = self.get_rigid_body(ship.rigid_body).unwrap();
let ship_pos = rigid_body.translation();
let ship_rot = rigid_body.rotation();
let ship_ang = ship_rot.angle();
let ship_vel = rigid_body.velocity_at_point(rigid_body.center_of_mass());
let pos = ship_pos + (ship_rot * gun_point.pos);
let outfit = res.ct.get_outfit(outfit);
let outfit = outfit.gun.as_ref().unwrap();
let spread = rng.gen_range(-outfit.projectile.angle_rng..=outfit.projectile.angle_rng);
let vel = ship_vel
+ (Rotation2::new(ship_ang + spread)
* Vector2::new(
outfit.projectile.speed
+ rng.gen_range(
-outfit.projectile.speed_rng..=outfit.projectile.speed_rng,
),
0.0,
));
let rigid_body = RigidBodyBuilder::kinematic_velocity_based()
.translation(pos)
.rotation(ship_ang)
.linvel(vel)
.build();
let mut collider = match &outfit.projectile.collider {
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
.sensor(true)
.active_events(ActiveEvents::COLLISION_EVENTS)
.build(),
};
collider.set_collision_groups(InteractionGroups::new(Group::GROUP_2, Group::GROUP_1));
let rigid_body = self.rigid_body_set.insert(rigid_body);
let collider = self.collider_set.insert_with_parent(
collider,
rigid_body,
&mut self.rigid_body_set,
);
self.projectiles.insert(
collider.clone(),
PhysProjectile::new(
res.ct,
outfit.projectile.clone(),
rigid_body,
ship.data.get_faction(),
collider,
),
);
}
}
/// Step this physics system by `t` seconds
pub fn step(&mut self, mut res: PhysStepResources) {
res.timing.start_physics_ships();
self.step_ships(&mut res);
res.timing.mark_physics_ships();
res.timing.start_physics_sim();
// Update physics
res.wrapper
.step(res.t, &mut self.rigid_body_set, &mut self.collider_set);
// Handle collision events
while let Ok(event) = &res.wrapper.collision_queue.try_recv() {
if event.started() {
let a = event.collider1();
let b = event.collider2();
// If projectiles are part of this collision, make sure
// `a` is one of them.
let (a, b) = if self.projectiles.contains_key(&b) {
(b, a)
} else {
(a, b)
};
let p = self.projectiles.get(&a);
let s = self.ships.get_mut(&b);
if p.is_none() || s.is_none() {
continue;
}
self.collide_projectile_ship(&mut res, a, b);
}
}
// Delete projectiles
let mut to_remove = Vec::new();
for (c, p) in &mut self.projectiles {
p.tick(res.ct, res.t);
if p.is_expired() {
to_remove.push(*c);
}
}
let mut rng = rand::thread_rng();
for c in to_remove {
let (pr, p) = self.remove_projectile(&mut res, c).unwrap();
match &p.content.expire_effect {
None => {}
Some(x) => {
let x = res.ct.get_effect(*x);
let pos = *pr.translation();
let vel = pr.velocity_at_point(pr.center_of_mass());
let angle = pr.rotation().angle();
let velocity = {
let a = rng
.gen_range(-x.velocity_scale_parent_rng..=x.velocity_scale_parent_rng);
let velocity = (x.velocity_scale_parent + a) * vel;
velocity
};
res.particles.push(ParticleBuilder::from_content(
x,
pos.into(),
-angle,
velocity,
Vector2::new(0.0, 0.0),
));
}
};
}
res.timing.mark_physics_sim();
}
}