use galactica_content::{Content, Relationship, SystemObjectHandle}; use nalgebra::{Isometry2, Point2, Rotation2, Vector2}; use rand::Rng; use rapier2d::{ dynamics::RigidBody, geometry::{ColliderHandle, Group, InteractionGroups}, }; 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; } let collider = self.collider_set.get_mut(collider).unwrap(); collider.set_collision_groups(InteractionGroups::new(Group::GROUP_1, Group::empty())); 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, collider: ColliderHandle) { 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 start_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 mut rng = rand::thread_rng(); let radius = rng.gen_range(500.0..=1500.0); let angle = rng.gen_range(0.0..std::f32::consts::TAU); let target_offset = Rotation2::new(angle) * Vector2::new(radius, 0.0); let target_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset; let target_pos = Isometry2::new(target_trans, angle); ship.data.start_unland_to(ct, target_pos); let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap(); r.set_enabled(true); r.set_position( Isometry2::new(Vector2::new(obj.pos.x, obj.pos.y), angle), true, ); } pub(super) fn finish_unland_ship(&mut self, collider: ColliderHandle) { let ship = self.ships.get_mut(&collider).unwrap(); ship.data.finish_unland_to(); self.collider_set .get_mut(ship.collider) .unwrap() .set_collision_groups(InteractionGroups::new( Group::GROUP_1, Group::GROUP_1 | Group::GROUP_2, )); } 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(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); } } }