335 lines
8.8 KiB
Rust
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();
|
|
}
|
|
}
|