Split systemsim into a module
parent
a4222abdc5
commit
d5e2820f7d
|
@ -1,636 +0,0 @@
|
|||
use galactica_content::{
|
||||
Content, FactionHandle, GunPoint, OutfitHandle, ProjectileCollider, Relationship, ShipHandle,
|
||||
SystemHandle, SystemObjectHandle,
|
||||
};
|
||||
use galactica_playeragent::PlayerAgent;
|
||||
use nalgebra::{Isometry2, Point2, Rotation2, Vector2};
|
||||
use rand::Rng;
|
||||
use rapier2d::{
|
||||
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle, RigidBodySet},
|
||||
geometry::{ColliderBuilder, ColliderHandle, ColliderSet},
|
||||
pipeline::ActiveEvents,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::data::{ShipAutoPilot, ShipPersonality, ShipState};
|
||||
|
||||
use super::{
|
||||
controller::{autopilot, ShipController},
|
||||
objects::{PhysProjectile, PhysSimShip},
|
||||
ParticleBuilder, PhysStepResources,
|
||||
};
|
||||
|
||||
// TODO: replace with a more generic handle
|
||||
/// A handle for a ship in this simulation
|
||||
/// This lets other crates reference ships
|
||||
/// without including `rapier2d`.
|
||||
pub struct PhysSimShipHandle(pub ColliderHandle);
|
||||
|
||||
/// Manages the physics state of one system
|
||||
#[derive(Clone)]
|
||||
pub struct PhysSim {
|
||||
/// The system this sim is attached to
|
||||
_system: SystemHandle,
|
||||
|
||||
rigid_body_set: RigidBodySet,
|
||||
collider_set: ColliderSet,
|
||||
|
||||
projectiles: HashMap<ColliderHandle, PhysProjectile>,
|
||||
ships: HashMap<ColliderHandle, PhysSimShip>,
|
||||
ship_behaviors: HashMap<ColliderHandle, ShipController>,
|
||||
}
|
||||
|
||||
// Private methods
|
||||
impl<'a> PhysSim {
|
||||
fn remove_projectile(
|
||||
&mut self,
|
||||
res: &mut PhysStepResources,
|
||||
c: ColliderHandle,
|
||||
) -> Option<(RigidBody, PhysProjectile)> {
|
||||
let p = match self.projectiles.remove(&c) {
|
||||
Some(p) => p,
|
||||
None => return None,
|
||||
};
|
||||
let r = self
|
||||
.rigid_body_set
|
||||
.remove(
|
||||
p.rigid_body,
|
||||
&mut res.wrapper.im,
|
||||
&mut self.collider_set,
|
||||
&mut res.wrapper.ij,
|
||||
&mut res.wrapper.mj,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
return Some((r, p));
|
||||
}
|
||||
|
||||
/// Try to land the given ship on the given planet.
|
||||
/// State changes on success, and nothing happens on failure.
|
||||
/// returns `true` if landing was successful.
|
||||
fn try_land_ship(
|
||||
&mut self,
|
||||
ct: &Content,
|
||||
collider: ColliderHandle,
|
||||
target_handle: SystemObjectHandle,
|
||||
) -> bool {
|
||||
let ship = self.ships.get_mut(&collider).unwrap();
|
||||
let r = self.rigid_body_set.get(ship.rigid_body).unwrap();
|
||||
|
||||
let target = ct.get_system_object(target_handle);
|
||||
let t_pos = Vector2::new(target.pos.x, target.pos.y);
|
||||
let s_pos = Vector2::new(r.position().translation.x, r.position().translation.y);
|
||||
|
||||
// TODO: deactivate collider when landing.
|
||||
// Can't just set_active(false), since we still need that collider's mass.
|
||||
|
||||
// We're in land range...
|
||||
if (t_pos - s_pos).magnitude() > target.size / 2.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// And we'll stay in land range long enough.
|
||||
if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0)).magnitude()
|
||||
> target.size / 2.0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ship.data.start_land_on(target_handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Finish landing this ship on a planet.
|
||||
/// Called after the landing animation finishes.
|
||||
fn finish_land_ship(
|
||||
&mut self,
|
||||
_ct: &Content,
|
||||
collider: ColliderHandle,
|
||||
_target: SystemObjectHandle,
|
||||
) {
|
||||
let ship = self.ships.get_mut(&collider).unwrap();
|
||||
ship.data.finish_land_on();
|
||||
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||
r.set_enabled(false);
|
||||
r.set_angvel(0.0, false);
|
||||
r.set_linvel(nalgebra::Vector2::new(0.0, 0.0), false);
|
||||
}
|
||||
|
||||
fn unland_ship(&mut self, ct: &Content, collider: ColliderHandle) {
|
||||
let ship = self.ships.get_mut(&collider).unwrap();
|
||||
let obj = ship.data.get_state().landed_on().unwrap();
|
||||
let obj = ct.get_system_object(obj);
|
||||
|
||||
let target_pos = Isometry2::new(Vector2::new(obj.pos.x + 100.0, obj.pos.y + 100.0), 1.0);
|
||||
|
||||
ship.data.unland(target_pos);
|
||||
|
||||
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||
r.set_enabled(true);
|
||||
r.set_position(target_pos, true);
|
||||
|
||||
self.collider_set
|
||||
.get_mut(ship.collider)
|
||||
.unwrap()
|
||||
.set_enabled(true);
|
||||
}
|
||||
|
||||
fn remove_ship(&mut self, res: &mut PhysStepResources, colliderhandle: ColliderHandle) {
|
||||
let ship = match self.ships.get(&colliderhandle) {
|
||||
None => return,
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
self.rigid_body_set.remove(
|
||||
ship.rigid_body,
|
||||
&mut res.wrapper.im,
|
||||
&mut self.collider_set,
|
||||
&mut res.wrapper.ij,
|
||||
&mut res.wrapper.mj,
|
||||
true,
|
||||
);
|
||||
self.ships.remove(&colliderhandle).unwrap();
|
||||
self.ship_behaviors.remove(&colliderhandle);
|
||||
}
|
||||
|
||||
fn collide_projectile_ship(
|
||||
&mut self,
|
||||
res: &mut PhysStepResources,
|
||||
projectile_h: ColliderHandle,
|
||||
ship_h: ColliderHandle,
|
||||
) {
|
||||
let projectile = self.projectiles.get(&projectile_h);
|
||||
let ship = self.ships.get_mut(&ship_h);
|
||||
if projectile.is_none() || ship.is_none() {
|
||||
return;
|
||||
}
|
||||
let projectile = projectile.unwrap();
|
||||
let ship = ship.unwrap();
|
||||
|
||||
let f = res.ct.get_faction(projectile.faction);
|
||||
let r = f.relationships.get(&ship.data.get_faction()).unwrap();
|
||||
let destory_projectile = match r {
|
||||
Relationship::Hostile => match ship.data.get_state() {
|
||||
ShipState::Flying { .. } => {
|
||||
ship.data.apply_damage(res.ct, projectile.content.damage);
|
||||
true
|
||||
}
|
||||
ShipState::Collapsing { .. } => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if destory_projectile {
|
||||
let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap();
|
||||
let v =
|
||||
pr.velocity_at_point(pr.center_of_mass()).normalize() * projectile.content.force;
|
||||
let pos = *pr.translation();
|
||||
let _ = pr;
|
||||
|
||||
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||
r.apply_impulse_at_point(Vector2::new(v.x, v.y), Point2::new(pos.x, pos.y), true);
|
||||
|
||||
// Borrow again, we can only have one at a time
|
||||
let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap();
|
||||
let pos = *pr.translation();
|
||||
let angle = pr.rotation().angle();
|
||||
|
||||
match &projectile.content.impact_effect {
|
||||
None => {}
|
||||
Some(x) => {
|
||||
let effect = res.ct.get_effect(*x);
|
||||
let r = ship.rigid_body;
|
||||
let sr = self.get_rigid_body(r).unwrap();
|
||||
let parent_velocity = pr.velocity_at_point(pr.center_of_mass());
|
||||
let target_velocity =
|
||||
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
||||
|
||||
res.particles.push(ParticleBuilder::from_content(
|
||||
effect,
|
||||
pos.into(),
|
||||
-angle,
|
||||
parent_velocity,
|
||||
target_velocity,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
self.remove_projectile(res, projectile.collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public methods
|
||||
impl PhysSim {
|
||||
/// Create a new physics system
|
||||
pub fn new(_ct: &Content, system: SystemHandle) -> Self {
|
||||
Self {
|
||||
_system: system,
|
||||
rigid_body_set: RigidBodySet::new(),
|
||||
collider_set: ColliderSet::new(),
|
||||
projectiles: HashMap::new(),
|
||||
ships: HashMap::new(),
|
||||
ship_behaviors: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a ship to this physics system
|
||||
pub fn add_ship(
|
||||
&mut self,
|
||||
ct: &Content,
|
||||
handle: ShipHandle,
|
||||
faction: FactionHandle,
|
||||
personality: ShipPersonality,
|
||||
position: Point2<f32>,
|
||||
) -> PhysSimShipHandle {
|
||||
let ship_content = ct.get_ship(handle);
|
||||
let cl = ship_content.collider.0.clone();
|
||||
// TODO: additonal ship mass from outfits and cargo
|
||||
|
||||
let rb = RigidBodyBuilder::dynamic()
|
||||
.angular_damping(ship_content.angular_drag)
|
||||
.linear_damping(ship_content.linear_drag)
|
||||
.translation(Vector2::new(position.x, position.y))
|
||||
.can_sleep(false);
|
||||
|
||||
let ridid_body = self.rigid_body_set.insert(rb.build());
|
||||
let collider =
|
||||
self.collider_set
|
||||
.insert_with_parent(cl, ridid_body, &mut self.rigid_body_set);
|
||||
|
||||
self.ship_behaviors.insert(
|
||||
collider,
|
||||
match personality {
|
||||
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
|
||||
ShipPersonality::Point => ShipController::new_point(),
|
||||
},
|
||||
);
|
||||
|
||||
self.ships.insert(
|
||||
collider,
|
||||
PhysSimShip::new(ct, handle, personality, faction, ridid_body, collider),
|
||||
);
|
||||
|
||||
return PhysSimShipHandle(collider);
|
||||
}
|
||||
|
||||
/// Update a player ship's controls
|
||||
pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) {
|
||||
if player.ship.is_none() {
|
||||
return;
|
||||
}
|
||||
let ship_object = self.ships.get_mut(&player.ship.unwrap());
|
||||
if let Some(ship_object) = ship_object {
|
||||
match ship_object.data.get_state() {
|
||||
ShipState::Landing { .. }
|
||||
| ShipState::UnLanding { .. }
|
||||
| ShipState::Collapsing { .. }
|
||||
| ShipState::Dead => {}
|
||||
|
||||
ShipState::Flying {
|
||||
autopilot: ShipAutoPilot::None,
|
||||
} => {
|
||||
ship_object.controls.guns = player.input.pressed_guns();
|
||||
ship_object.controls.left = player.input.pressed_left();
|
||||
ship_object.controls.right = player.input.pressed_right();
|
||||
ship_object.controls.thrust = player.input.pressed_thrust();
|
||||
|
||||
if player.input.pressed_land() {
|
||||
ship_object.data.set_autopilot(ShipAutoPilot::Landing {
|
||||
target: player.selection.get_planet().unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ShipState::Flying { .. } => {
|
||||
// Any input automatically releases autopilot
|
||||
if player.input.pressed_left()
|
||||
|| player.input.pressed_right()
|
||||
|| player.input.pressed_thrust()
|
||||
|| player.input.pressed_guns()
|
||||
{
|
||||
ship_object.data.set_autopilot(ShipAutoPilot::None);
|
||||
}
|
||||
}
|
||||
|
||||
ShipState::Landed { .. } => {
|
||||
if player.input.pressed_land() {
|
||||
self.unland_ship(ct, player.ship.unwrap());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 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::UnLanding { .. }
|
||||
| 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::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,
|
||||
*target,
|
||||
);
|
||||
|
||||
let current_z = *current_z;
|
||||
let target = *target;
|
||||
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(res.ct, collider, target);
|
||||
} 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 controls = autopilot::auto_landing(
|
||||
&res,
|
||||
&self.rigid_body_set,
|
||||
&self.ships,
|
||||
ship.collider,
|
||||
*target_handle,
|
||||
);
|
||||
|
||||
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 collider = match &outfit.projectile.collider {
|
||||
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
||||
.sensor(true)
|
||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||
.build(),
|
||||
};
|
||||
|
||||
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(
|
||||
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.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();
|
||||
}
|
||||
}
|
||||
|
||||
// Public getters
|
||||
impl PhysSim {
|
||||
/// Get a ship physics object
|
||||
pub fn get_ship(&self, ship: &PhysSimShipHandle) -> Option<&PhysSimShip> {
|
||||
self.ships.get(&ship.0)
|
||||
}
|
||||
|
||||
/// Get a ship physics object
|
||||
pub fn get_ship_mut(&mut self, ship: &PhysSimShipHandle) -> Option<&mut PhysSimShip> {
|
||||
self.ships.get_mut(&ship.0)
|
||||
}
|
||||
|
||||
/// Get a rigid body from a handle
|
||||
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> Option<&RigidBody> {
|
||||
self.rigid_body_set.get(r)
|
||||
}
|
||||
|
||||
/// Get a rigid body from a handle
|
||||
pub fn get_rigid_body_mut(&mut self, r: RigidBodyHandle) -> Option<&mut RigidBody> {
|
||||
self.rigid_body_set.get_mut(r)
|
||||
}
|
||||
|
||||
/// Iterate over all ships in this physics system
|
||||
pub fn iter_ship_body(&self) -> impl Iterator<Item = (&PhysSimShip, &RigidBody)> + '_ {
|
||||
self.ships
|
||||
.values()
|
||||
.map(|x| (x, self.rigid_body_set.get(x.rigid_body).unwrap()))
|
||||
}
|
||||
|
||||
/// Iterate over all ships in this physics system
|
||||
pub fn iter_ships(&self) -> impl Iterator<Item = &PhysSimShip> + '_ {
|
||||
self.ships.values()
|
||||
}
|
||||
|
||||
/// Iterate over all ships in this physics system
|
||||
pub fn iter_projectiles(&self) -> impl Iterator<Item = &PhysProjectile> + '_ {
|
||||
self.projectiles.values()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle};
|
||||
use galactica_playeragent::PlayerAgent;
|
||||
use nalgebra::{Point2, Vector2};
|
||||
|
||||
use rapier2d::{
|
||||
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle, RigidBodySet},
|
||||
geometry::{ColliderHandle, ColliderSet},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::data::{ShipAutoPilot, ShipPersonality, ShipState};
|
||||
|
||||
use super::{
|
||||
controller::ShipController,
|
||||
objects::{PhysProjectile, PhysSimShip},
|
||||
};
|
||||
|
||||
// TODO: replace with a more generic handle
|
||||
/// A handle for a ship in this simulation
|
||||
/// This lets other crates reference ships
|
||||
/// without including `rapier2d`.
|
||||
pub struct PhysSimShipHandle(pub ColliderHandle);
|
||||
|
||||
/// Manages the physics state of one system
|
||||
#[derive(Clone)]
|
||||
pub struct PhysSim {
|
||||
/// The system this sim is attached to
|
||||
_system: SystemHandle,
|
||||
|
||||
rigid_body_set: RigidBodySet,
|
||||
collider_set: ColliderSet,
|
||||
|
||||
projectiles: HashMap<ColliderHandle, PhysProjectile>,
|
||||
ships: HashMap<ColliderHandle, PhysSimShip>,
|
||||
ship_behaviors: HashMap<ColliderHandle, ShipController>,
|
||||
}
|
||||
|
||||
mod step;
|
||||
mod steputil;
|
||||
|
||||
// Public methods
|
||||
impl PhysSim {
|
||||
/// Create a new physics system
|
||||
pub fn new(_ct: &Content, system: SystemHandle) -> Self {
|
||||
Self {
|
||||
_system: system,
|
||||
rigid_body_set: RigidBodySet::new(),
|
||||
collider_set: ColliderSet::new(),
|
||||
projectiles: HashMap::new(),
|
||||
ships: HashMap::new(),
|
||||
ship_behaviors: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a ship to this physics system
|
||||
pub fn add_ship(
|
||||
&mut self,
|
||||
ct: &Content,
|
||||
handle: ShipHandle,
|
||||
faction: FactionHandle,
|
||||
personality: ShipPersonality,
|
||||
position: Point2<f32>,
|
||||
) -> PhysSimShipHandle {
|
||||
let ship_content = ct.get_ship(handle);
|
||||
let cl = ship_content.collider.0.clone();
|
||||
// TODO: additonal ship mass from outfits and cargo
|
||||
|
||||
let rb = RigidBodyBuilder::dynamic()
|
||||
.angular_damping(ship_content.angular_drag)
|
||||
.linear_damping(ship_content.linear_drag)
|
||||
.translation(Vector2::new(position.x, position.y))
|
||||
.can_sleep(false);
|
||||
|
||||
let ridid_body = self.rigid_body_set.insert(rb.build());
|
||||
let collider =
|
||||
self.collider_set
|
||||
.insert_with_parent(cl, ridid_body, &mut self.rigid_body_set);
|
||||
|
||||
self.ship_behaviors.insert(
|
||||
collider,
|
||||
match personality {
|
||||
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
|
||||
ShipPersonality::Point => ShipController::new_point(),
|
||||
},
|
||||
);
|
||||
|
||||
self.ships.insert(
|
||||
collider,
|
||||
PhysSimShip::new(ct, handle, personality, faction, ridid_body, collider),
|
||||
);
|
||||
|
||||
return PhysSimShipHandle(collider);
|
||||
}
|
||||
|
||||
/// Update a player ship's controls
|
||||
pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) {
|
||||
if player.ship.is_none() {
|
||||
return;
|
||||
}
|
||||
let ship_object = self.ships.get_mut(&player.ship.unwrap());
|
||||
if let Some(ship_object) = ship_object {
|
||||
match ship_object.data.get_state() {
|
||||
ShipState::Landing { .. }
|
||||
| ShipState::UnLanding { .. }
|
||||
| ShipState::Collapsing { .. }
|
||||
| ShipState::Dead => {}
|
||||
|
||||
ShipState::Flying {
|
||||
autopilot: ShipAutoPilot::None,
|
||||
} => {
|
||||
ship_object.controls.guns = player.input.pressed_guns();
|
||||
ship_object.controls.left = player.input.pressed_left();
|
||||
ship_object.controls.right = player.input.pressed_right();
|
||||
ship_object.controls.thrust = player.input.pressed_thrust();
|
||||
|
||||
if player.input.pressed_land() {
|
||||
ship_object.data.set_autopilot(ShipAutoPilot::Landing {
|
||||
target: player.selection.get_planet().unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ShipState::Flying { .. } => {
|
||||
// Any input automatically releases autopilot
|
||||
if player.input.pressed_left()
|
||||
|| player.input.pressed_right()
|
||||
|| player.input.pressed_thrust()
|
||||
|| player.input.pressed_guns()
|
||||
{
|
||||
ship_object.data.set_autopilot(ShipAutoPilot::None);
|
||||
}
|
||||
}
|
||||
|
||||
ShipState::Landed { .. } => {
|
||||
if player.input.pressed_land() {
|
||||
self.unland_ship(ct, player.ship.unwrap());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Public getters
|
||||
impl PhysSim {
|
||||
/// Get a ship physics object
|
||||
pub fn get_ship(&self, ship: &PhysSimShipHandle) -> Option<&PhysSimShip> {
|
||||
self.ships.get(&ship.0)
|
||||
}
|
||||
|
||||
/// Get a ship physics object
|
||||
pub fn get_ship_mut(&mut self, ship: &PhysSimShipHandle) -> Option<&mut PhysSimShip> {
|
||||
self.ships.get_mut(&ship.0)
|
||||
}
|
||||
|
||||
/// Get a rigid body from a handle
|
||||
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> Option<&RigidBody> {
|
||||
self.rigid_body_set.get(r)
|
||||
}
|
||||
|
||||
/// Get a rigid body from a handle
|
||||
pub fn get_rigid_body_mut(&mut self, r: RigidBodyHandle) -> Option<&mut RigidBody> {
|
||||
self.rigid_body_set.get_mut(r)
|
||||
}
|
||||
|
||||
/// Iterate over all ships in this physics system
|
||||
pub fn iter_ship_body(&self) -> impl Iterator<Item = (&PhysSimShip, &RigidBody)> + '_ {
|
||||
self.ships
|
||||
.values()
|
||||
.map(|x| (x, self.rigid_body_set.get(x.rigid_body).unwrap()))
|
||||
}
|
||||
|
||||
/// Iterate over all ships in this physics system
|
||||
pub fn iter_ships(&self) -> impl Iterator<Item = &PhysSimShip> + '_ {
|
||||
self.ships.values()
|
||||
}
|
||||
|
||||
/// Iterate over all ships in this physics system
|
||||
pub fn iter_projectiles(&self) -> impl Iterator<Item = &PhysProjectile> + '_ {
|
||||
self.projectiles.values()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,283 @@
|
|||
use galactica_content::{GunPoint, OutfitHandle, ProjectileCollider};
|
||||
use nalgebra::{Rotation2, Vector2};
|
||||
use rand::Rng;
|
||||
use rapier2d::{dynamics::RigidBodyBuilder, geometry::ColliderBuilder, pipeline::ActiveEvents};
|
||||
|
||||
use crate::{
|
||||
data::{ShipAutoPilot, ShipState},
|
||||
phys::{controller::autopilot, objects::PhysProjectile, 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::UnLanding { .. }
|
||||
| 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::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,
|
||||
*target,
|
||||
);
|
||||
|
||||
let current_z = *current_z;
|
||||
let target = *target;
|
||||
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(res.ct, collider, target);
|
||||
} 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 controls = autopilot::auto_landing(
|
||||
&res,
|
||||
&self.rigid_body_set,
|
||||
&self.ships,
|
||||
ship.collider,
|
||||
*target_handle,
|
||||
);
|
||||
|
||||
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 collider = match &outfit.projectile.collider {
|
||||
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
||||
.sensor(true)
|
||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||
.build(),
|
||||
};
|
||||
|
||||
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(
|
||||
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.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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
use galactica_content::{Content, Relationship, SystemObjectHandle};
|
||||
use nalgebra::{Isometry2, Point2, Vector2};
|
||||
use rapier2d::{dynamics::RigidBody, geometry::ColliderHandle};
|
||||
|
||||
use crate::{
|
||||
data::ShipState,
|
||||
phys::{objects::PhysProjectile, ParticleBuilder, PhysStepResources},
|
||||
};
|
||||
|
||||
use super::PhysSim;
|
||||
|
||||
// Private methods
|
||||
impl PhysSim {
|
||||
pub(super) fn remove_projectile(
|
||||
&mut self,
|
||||
res: &mut PhysStepResources,
|
||||
c: ColliderHandle,
|
||||
) -> Option<(RigidBody, PhysProjectile)> {
|
||||
let p = match self.projectiles.remove(&c) {
|
||||
Some(p) => p,
|
||||
None => return None,
|
||||
};
|
||||
let r = self
|
||||
.rigid_body_set
|
||||
.remove(
|
||||
p.rigid_body,
|
||||
&mut res.wrapper.im,
|
||||
&mut self.collider_set,
|
||||
&mut res.wrapper.ij,
|
||||
&mut res.wrapper.mj,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
return Some((r, p));
|
||||
}
|
||||
|
||||
/// Try to land the given ship on the given planet.
|
||||
/// State changes on success, and nothing happens on failure.
|
||||
/// returns `true` if landing was successful.
|
||||
pub(super) fn try_land_ship(
|
||||
&mut self,
|
||||
ct: &Content,
|
||||
collider: ColliderHandle,
|
||||
target_handle: SystemObjectHandle,
|
||||
) -> bool {
|
||||
let ship = self.ships.get_mut(&collider).unwrap();
|
||||
let r = self.rigid_body_set.get(ship.rigid_body).unwrap();
|
||||
|
||||
let target = ct.get_system_object(target_handle);
|
||||
let t_pos = Vector2::new(target.pos.x, target.pos.y);
|
||||
let s_pos = Vector2::new(r.position().translation.x, r.position().translation.y);
|
||||
|
||||
// TODO: deactivate collider when landing.
|
||||
// Can't just set_active(false), since we still need that collider's mass.
|
||||
|
||||
// We're in land range...
|
||||
if (t_pos - s_pos).magnitude() > target.size / 2.0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// And we'll stay in land range long enough.
|
||||
if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0)).magnitude()
|
||||
> target.size / 2.0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ship.data.start_land_on(target_handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Finish landing this ship on a planet.
|
||||
/// Called after the landing animation finishes.
|
||||
pub(super) fn finish_land_ship(
|
||||
&mut self,
|
||||
_ct: &Content,
|
||||
collider: ColliderHandle,
|
||||
_target: SystemObjectHandle,
|
||||
) {
|
||||
let ship = self.ships.get_mut(&collider).unwrap();
|
||||
ship.data.finish_land_on();
|
||||
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||
r.set_enabled(false);
|
||||
r.set_angvel(0.0, false);
|
||||
r.set_linvel(nalgebra::Vector2::new(0.0, 0.0), false);
|
||||
}
|
||||
|
||||
pub(super) fn unland_ship(&mut self, ct: &Content, collider: ColliderHandle) {
|
||||
let ship = self.ships.get_mut(&collider).unwrap();
|
||||
let obj = ship.data.get_state().landed_on().unwrap();
|
||||
let obj = ct.get_system_object(obj);
|
||||
|
||||
let target_pos = Isometry2::new(Vector2::new(obj.pos.x + 100.0, obj.pos.y + 100.0), 1.0);
|
||||
|
||||
ship.data.unland(target_pos);
|
||||
|
||||
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||
r.set_enabled(true);
|
||||
r.set_position(target_pos, true);
|
||||
|
||||
self.collider_set
|
||||
.get_mut(ship.collider)
|
||||
.unwrap()
|
||||
.set_enabled(true);
|
||||
}
|
||||
|
||||
pub(super) fn remove_ship(
|
||||
&mut self,
|
||||
res: &mut PhysStepResources,
|
||||
colliderhandle: ColliderHandle,
|
||||
) {
|
||||
let ship = match self.ships.get(&colliderhandle) {
|
||||
None => return,
|
||||
Some(s) => s,
|
||||
};
|
||||
|
||||
self.rigid_body_set.remove(
|
||||
ship.rigid_body,
|
||||
&mut res.wrapper.im,
|
||||
&mut self.collider_set,
|
||||
&mut res.wrapper.ij,
|
||||
&mut res.wrapper.mj,
|
||||
true,
|
||||
);
|
||||
self.ships.remove(&colliderhandle).unwrap();
|
||||
self.ship_behaviors.remove(&colliderhandle);
|
||||
}
|
||||
|
||||
pub(super) fn collide_projectile_ship(
|
||||
&mut self,
|
||||
res: &mut PhysStepResources,
|
||||
projectile_h: ColliderHandle,
|
||||
ship_h: ColliderHandle,
|
||||
) {
|
||||
let projectile = self.projectiles.get(&projectile_h);
|
||||
let ship = self.ships.get_mut(&ship_h);
|
||||
if projectile.is_none() || ship.is_none() {
|
||||
return;
|
||||
}
|
||||
let projectile = projectile.unwrap();
|
||||
let ship = ship.unwrap();
|
||||
|
||||
let f = res.ct.get_faction(projectile.faction);
|
||||
let r = f.relationships.get(&ship.data.get_faction()).unwrap();
|
||||
let destory_projectile = match r {
|
||||
Relationship::Hostile => match ship.data.get_state() {
|
||||
ShipState::Flying { .. } => {
|
||||
ship.data.apply_damage(res.ct, projectile.content.damage);
|
||||
true
|
||||
}
|
||||
ShipState::Collapsing { .. } => true,
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if destory_projectile {
|
||||
let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap();
|
||||
let v =
|
||||
pr.velocity_at_point(pr.center_of_mass()).normalize() * projectile.content.force;
|
||||
let pos = *pr.translation();
|
||||
let _ = pr;
|
||||
|
||||
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
|
||||
r.apply_impulse_at_point(Vector2::new(v.x, v.y), Point2::new(pos.x, pos.y), true);
|
||||
|
||||
// Borrow again, we can only have one at a time
|
||||
let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap();
|
||||
let pos = *pr.translation();
|
||||
let angle = pr.rotation().angle();
|
||||
|
||||
match &projectile.content.impact_effect {
|
||||
None => {}
|
||||
Some(x) => {
|
||||
let effect = res.ct.get_effect(*x);
|
||||
let r = ship.rigid_body;
|
||||
let sr = self.get_rigid_body(r).unwrap();
|
||||
let parent_velocity = pr.velocity_at_point(pr.center_of_mass());
|
||||
let target_velocity =
|
||||
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
|
||||
|
||||
res.particles.push(ParticleBuilder::from_content(
|
||||
effect,
|
||||
pos.into(),
|
||||
-angle,
|
||||
parent_velocity,
|
||||
target_velocity,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
self.remove_projectile(res, projectile.collider);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue