diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index c0a4526..493c73c 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -85,7 +85,9 @@ fn main() -> Result<()> { .get_ship(&PhysSimShipHandle(player.ship.unwrap())); if let Some(o) = o { match o.data.get_state() { - ShipState::Collapsing { .. } | ShipState::Flying => { + ShipState::Landing { .. } + | ShipState::Collapsing { .. } + | ShipState::Flying { .. } => { let r = &game.get_state().systemsim.get_rigid_body(o.rigid_body); if let Some(r) = r { @@ -99,11 +101,7 @@ fn main() -> Result<()> { o.data.get_state().unlanding_position(&content).unwrap(); Some(Vector2::new(pos.x, pos.y)) } - ShipState::Landing { .. } => { - let pos = - o.data.get_state().landing_position(&content).unwrap(); - Some(Vector2::new(pos.x, pos.y)) - } + ShipState::Landed { target } => { let b = content.get_system_object(*target); Some(Vector2::new(b.pos.x, b.pos.y)) diff --git a/crates/render/src/gpustate/phys.rs b/crates/render/src/gpustate/phys.rs index ba8137c..b5be8d7 100644 --- a/crates/render/src/gpustate/phys.rs +++ b/crates/render/src/gpustate/phys.rs @@ -3,7 +3,7 @@ use bytemuck; use galactica_system::data::ShipState; use galactica_util::{constants::OBJECT_SPRITE_INSTANCE_LIMIT, to_radians}; -use nalgebra::{Point2, Point3, Vector2}; +use nalgebra::{Point2, Point3}; use crate::{ globaluniform::ObjectData, @@ -27,7 +27,7 @@ impl GPUState { match ship.data.get_state() { ShipState::Dead | ShipState::Landed { .. } => continue, - ShipState::Collapsing { .. } | ShipState::Flying => { + ShipState::Collapsing { .. } | ShipState::Flying { .. } => { let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); let pos = *r.translation(); ship_pos = Point3::new(pos.x, pos.y, 1.0); @@ -35,30 +35,20 @@ impl GPUState { ship_ang = ship_rot.angle(); ship_cnt = state.ct.get_ship(ship.data.get_content()); } - ShipState::Landing { - from_position, - from_angle, - target, - total: _total, - elapsed, - } => { - let target = state.ct.get_system_object(*target); - let diff = Point2::new(target.pos.x, target.pos.y) - from_position; - - ship_pos = ship.data.get_state().landing_position(state.ct).unwrap(); - - let target_angle = diff.angle(&Vector2::new(1.0, 0.0)); - - ship_ang = from_angle + ((target_angle - from_angle) * 1f32.min(elapsed / 1.0)); + ShipState::Landing { current_z, .. } => { + let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); + let pos = *r.translation(); + ship_pos = Point3::new(pos.x, pos.y, *current_z); + let ship_rot = r.rotation(); + ship_ang = ship_rot.angle(); ship_cnt = state.ct.get_ship(ship.data.get_content()); } - ShipState::UnLanding { - to_angle, elapsed, .. - } => { + ShipState::UnLanding { .. } => { ship_pos = ship.data.get_state().unlanding_position(state.ct).unwrap(); - ship_ang = 0.0 + ((to_angle - 0.0) * 1f32.min(elapsed / 1.0)); + //ship_ang = 0.0 + ((to_angle - 0.0) * 1f32.min(elapsed / 1.0)); + ship_ang = 0.0; ship_cnt = state.ct.get_ship(ship.data.get_content()); } } @@ -122,7 +112,8 @@ impl GPUState { let flare = ship.data.get_outfits().get_flare_sprite(state.ct); if { let is_flying = match ship.data.get_state() { - ShipState::Flying => true, + ShipState::Flying { .. } => true, + ShipState::Landing { .. } => true, _ => false, }; ship.get_controls().thrust && flare.is_some() && is_flying diff --git a/crates/render/src/ui/radar.rs b/crates/render/src/ui/radar.rs index e606fb4..e03a470 100644 --- a/crates/render/src/ui/radar.rs +++ b/crates/render/src/ui/radar.rs @@ -41,15 +41,6 @@ impl Radar { match player_ship.data.get_state() { ShipState::Dead => {} - ShipState::Landing { .. } => { - let pos = player_ship - .data - .get_state() - .landing_position(&input.ct) - .unwrap(); - self.last_player_position = Point2::new(pos.x, pos.y) - } - ShipState::UnLanding { .. } => { let pos = player_ship .data @@ -63,7 +54,8 @@ impl Radar { let landed_body = input.ct.get_system_object(*target); self.last_player_position = Point2::new(landed_body.pos.x, landed_body.pos.y); } - ShipState::Flying | ShipState::Collapsing { .. } => { + + ShipState::Landing { .. } | ShipState::Flying { .. } | ShipState::Collapsing { .. } => { let player_body = input .systemsim .get_rigid_body(player_ship.rigid_body) @@ -142,28 +134,35 @@ impl Radar { // Draw ships for (s, r) in input.systemsim.iter_ship_body() { - // This will be None if this ship is dead. - // Stays around while the physics system runs a collapse sequence - let color = match s.data.get_state() { + let ship = input.ct.get_ship(s.data.get_content()); + let (color, z_scale) = match s.data.get_state() { ShipState::Dead | ShipState::Landed { .. } => { continue; } // TODO: different color for landing? - ShipState::UnLanding { .. } - | ShipState::Landing { .. } - | ShipState::Collapsing { .. } => { + // TODO: scale blip for ship z-position + ShipState::Landing { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0), + + ShipState::UnLanding { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0), + + ShipState::Collapsing { .. } => { // TODO: configurable - [0.2, 0.2, 0.2, 1.0] + ([0.2, 0.2, 0.2, 1.0], 1.0) } - ShipState::Flying => { + ShipState::Flying { .. } => { let c = input.ct.get_faction(s.data.get_faction()).color; - [c[0], c[1], c[2], 1.0] + ([c[0], c[1], c[2], 1.0], 1.0) + } + }; + let size = (ship.size * ship.sprite.aspect) * ship_scale * z_scale; + let p: Point2 = { + if s.collider == input.player.ship.unwrap() { + self.last_player_position + } else { + (*r.translation()).into() } }; - let ship = input.ct.get_ship(s.data.get_content()); - let size = (ship.size * ship.sprite.aspect) * ship_scale; - let p: Point2 = (*r.translation()).into(); let d = (p - self.last_player_position) / radar_range; let m = d.magnitude() + (size / (2.0 * radar_size)); if m < hide_range { diff --git a/crates/render/src/ui/status.rs b/crates/render/src/ui/status.rs index 8934637..3a1cf47 100644 --- a/crates/render/src/ui/status.rs +++ b/crates/render/src/ui/status.rs @@ -47,7 +47,7 @@ impl Status { | ShipState::Landing { .. } | ShipState::Landed { .. } | ShipState::Collapsing { .. } - | ShipState::Flying => { + | ShipState::Flying { .. } => { current_shields = player_ship.data.get_shields(); current_hull = player_ship.data.get_hull(); max_shields = player_ship.data.get_outfits().get_shield_strength(); diff --git a/crates/system/src/data/ship/ship.rs b/crates/system/src/data/ship/ship.rs index 8c0ba1b..006554d 100644 --- a/crates/system/src/data/ship/ship.rs +++ b/crates/system/src/data/ship/ship.rs @@ -1,10 +1,26 @@ use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle}; use nalgebra::{Point2, Point3}; use rand::{rngs::ThreadRng, Rng}; +use rapier2d::math::Isometry; use std::{collections::HashMap, time::Instant}; use super::{OutfitSet, ShipPersonality}; +/// A ship autopilot. +/// An autopilot is a lightweight ShipController that +/// temporarily has control over a ship. +#[derive(Debug, Clone)] +pub enum ShipAutoPilot { + /// No autopilot, use usual behavior. + None, + + /// Automatically arrange for landing on the given object + Landing { + /// The body we want to land on + target: SystemObjectHandle, + }, +} + /// Ship state machine. /// Any ship we keep track of is in one of these states. /// Dead ships don't exist---they removed once their collapse @@ -15,7 +31,11 @@ pub enum ShipState { Dead, /// This ship is alive and well in open space - Flying, // TODO: system, position (also in collapse)? + Flying { + /// The autopilot we're using. + /// Overrides ship controller. + autopilot: ShipAutoPilot, + }, /// This ship has been destroyed, and is playing its collapse sequence. Collapsing { @@ -35,30 +55,18 @@ pub enum ShipState { /// This ship is landing on a planet /// (playing the animation) Landing { - /// The point, in world coordinates, where we started - from_position: Point2, - - /// The ship's angle when we started landing, in radians - from_angle: f32, - /// The planet we're landing on target: SystemObjectHandle, - /// The total amount of time, in seconds, we will spend landing - total: f32, - - /// The amount of time we've already spent playing this landing sequence - elapsed: f32, + /// Our current z-coordinate + current_z: f32, }, /// This ship is taking off from a planet /// (playing the animation) UnLanding { /// The point, in world coordinates, to which we're going - to_position: Point2, - - /// The angle we'll be at when we arrive, in radians - to_angle: f32, + to_position: Isometry, /// The planet we're taking off from from: SystemObjectHandle, @@ -92,41 +100,6 @@ impl ShipState { } } - /// Compute position of this ship's sprite during its landing sequence - pub fn landing_position(&self, ct: &Content) -> Option> { - match self { - Self::Landing { - from_position, - target, - total, - elapsed, - .. - } => Some({ - let target = ct.get_system_object(*target); - - let diff = Point2::new(target.pos.x, target.pos.y) - from_position; - let diff = diff - diff.normalize() * (target.size / 2.0) * 0.8; - - // TODO: improve animation - // TODO: fade - // TODO: atmosphere burn - // TODO: land at random point - // TODO: don't jump camera - // TODO: time by distance - // TODO: keep momentum - - let pos = from_position + (diff * (elapsed / total)); - - Point3::new( - pos.x, - pos.y, - 1.0 + ((target.pos.z - 1.0) * (elapsed / total)), - ) - }), - _ => None, - } - } - /// Compute position of this ship's sprite during its unlanding sequence pub fn unlanding_position(&self, ct: &Content) -> Option> { match self { @@ -139,7 +112,8 @@ impl ShipState { } => Some({ let from = ct.get_system_object(*from); - let diff = to_position - Point2::new(from.pos.x, from.pos.y); + let t = Point2::new(to_position.translation.x, to_position.translation.y); + let diff = t - Point2::new(from.pos.x, from.pos.y); //let diff = diff - diff.normalize() * (target.size / 2.0) * 0.8; // TODO: improve animation @@ -207,7 +181,9 @@ impl ShipData { rng: rand::thread_rng(), // TODO: ships must always start landed on planets - state: ShipState::Flying, + state: ShipState::Flying { + autopilot: ShipAutoPilot::None, + }, // Initial stats hull: s.hull, @@ -216,40 +192,72 @@ impl ShipData { } } - // TODO: position in data? - /// Land this ship on `target` - pub fn land_on( - &mut self, - target: SystemObjectHandle, - from_position: Point2, - from_angle: f32, - ) -> bool { + /// Set this ship's autopilot. + /// Panics if we're not flying. + pub fn set_autopilot(&mut self, autopilot: ShipAutoPilot) { match self.state { - ShipState::Flying => { - self.state = ShipState::Landing { - elapsed: 0.0, - total: 5.0, - target, - from_position, - from_angle, - }; - return true; + ShipState::Flying { + autopilot: ref mut pilot, + } => { + *pilot = autopilot; } _ => { - unreachable!("Called `land_on` on a ship that isn't flying!") + unreachable!("Called `set_autopilot` on a ship that isn't flying!") + } + }; + } + + /// Land this ship on `target` + /// This does NO checks (speed, range, etc). + /// That is the simulation's responsiblity. + /// + /// Will panic if we're not flying. + pub fn start_land_on(&mut self, target_handle: SystemObjectHandle) { + match self.state { + ShipState::Flying { .. } => { + self.state = ShipState::Landing { + target: target_handle, + current_z: 1.0, + }; + } + _ => { + unreachable!("Called `start_land_on` on a ship that isn't flying!") + } + }; + } + + /// When landing, update z position. + /// Will panic if we're not landing + pub fn set_landing_z(&mut self, z: f32) { + match &mut self.state { + ShipState::Landing { + ref mut current_z, .. + } => *current_z = z, + _ => unreachable!("Called `set_landing_z` on a ship that isn't landing!"), + } + } + + /// Finish landing sequence + /// Will panic if we're not landing + pub fn finish_land_on(&mut self) { + match self.state { + ShipState::Landing { target, .. } => { + self.state = ShipState::Landed { target }; + } + _ => { + unreachable!("Called `finish_land_on` on a ship that isn't flying!") } }; } /// Take off from `target` - pub fn unland(&mut self, to_position: Point2) { + pub fn unland(&mut self, to_position: Isometry) { match self.state { ShipState::Landed { target } => { self.state = ShipState::UnLanding { to_position, - to_angle: 1.0, from: target, - total: 5.0, + total: 2.0, elapsed: 0.0, }; } @@ -296,7 +304,7 @@ impl ShipData { /// Hit this ship with the given amount of damage pub(crate) fn apply_damage(&mut self, ct: &Content, mut d: f32) { match self.state { - ShipState::Flying => {} + ShipState::Flying { .. } => {} _ => { unreachable!("Cannot apply damage to a ship that is not flying!") } @@ -323,17 +331,7 @@ impl ShipData { /// Update this ship's state by `t` seconds pub(crate) fn step(&mut self, t: f32) { match self.state { - ShipState::Landing { - ref mut elapsed, - total, - target, - .. - } => { - *elapsed += t; - if *elapsed >= total { - self.state = ShipState::Landed { target }; - } - } + ShipState::Landing { .. } => {} ShipState::UnLanding { ref mut elapsed, @@ -342,7 +340,9 @@ impl ShipData { } => { *elapsed += t; if *elapsed >= total { - self.state = ShipState::Flying; + self.state = ShipState::Flying { + autopilot: ShipAutoPilot::None, + }; } } @@ -360,7 +360,7 @@ impl ShipData { } } - ShipState::Flying => { + ShipState::Flying { .. } => { // Cooldown guns for (_, c) in &mut self.gun_cooldowns { if *c > 0.0 { diff --git a/crates/system/src/phys/controller/autopilot/landing.rs b/crates/system/src/phys/controller/autopilot/landing.rs new file mode 100644 index 0000000..cefb7d7 --- /dev/null +++ b/crates/system/src/phys/controller/autopilot/landing.rs @@ -0,0 +1,49 @@ +use std::collections::HashMap; + +use galactica_content::SystemObjectHandle; +use galactica_util::to_radians; +use nalgebra::Vector2; +use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle}; + +use crate::phys::{ + objects::{PhysSimShip, ShipControls}, + PhysStepResources, +}; + +// TODO: no wobble +// TODO: slow down when near planet +// TODO: avoid obstacles + +/// Land this ship on the given object +pub fn auto_landing( + res: &PhysStepResources, + rigid_bodies: &RigidBodySet, + ships: &HashMap, + this_ship: ColliderHandle, + target_handle: SystemObjectHandle, +) -> Option { + let rigid_body_handle = ships.get(&this_ship).unwrap().rigid_body; + let rigid_body = rigid_bodies.get(rigid_body_handle).unwrap(); + let target_obj = res.ct.get_system_object(target_handle); + let target_pos = Vector2::new(target_obj.pos.x, target_obj.pos.y); + let my_pos = *rigid_body.translation(); + let my_rot = rigid_body.rotation() * Vector2::new(1.0, 0.0); + let my_vel = rigid_body.linvel(); + let my_angvel = rigid_body.angvel(); + let v_t = target_pos - my_pos; // Vector to target + let v_d = v_t - my_vel; // Desired thrust vector + let angle_delta = (my_rot.x * v_d.y - v_d.x * my_rot.y).atan2(my_rot.dot(&v_d)); + let mut controls = ShipControls::new(); + + if angle_delta < 0.0 && my_angvel > -0.3 { + controls.right = true; + } else if angle_delta > 0.0 && my_angvel < 0.3 { + controls.left = true; + } + + if my_rot.angle(&v_d) <= to_radians(15.0) { + controls.thrust = true; + } + + return Some(controls); +} diff --git a/crates/system/src/phys/controller/autopilot/mod.rs b/crates/system/src/phys/controller/autopilot/mod.rs new file mode 100644 index 0000000..fd54457 --- /dev/null +++ b/crates/system/src/phys/controller/autopilot/mod.rs @@ -0,0 +1,3 @@ +mod landing; + +pub use landing::*; diff --git a/crates/system/src/phys/controller/mod.rs b/crates/system/src/phys/controller/mod.rs index 8faa44e..a713cce 100644 --- a/crates/system/src/phys/controller/mod.rs +++ b/crates/system/src/phys/controller/mod.rs @@ -1,5 +1,6 @@ //! Various implementations of [`crate::ShipBehavior`] +pub(crate) mod autopilot; mod null; mod point; @@ -50,9 +51,7 @@ impl ShipController { } } -/// Ship controller trait. Any struct that implements this -/// may be used to control a ship. -pub trait ShipControllerStruct +trait ShipControllerStruct where Self: Debug + Clone, { diff --git a/crates/system/src/phys/controller/point.rs b/crates/system/src/phys/controller/point.rs index a6df781..a9e8724 100644 --- a/crates/system/src/phys/controller/point.rs +++ b/crates/system/src/phys/controller/point.rs @@ -49,7 +49,7 @@ impl ShipControllerStruct for PointShipController { // Target only flying ships we're hostile towards |s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() { Relationship::Hostile => match s.data.get_state() { - ShipState::Flying => true, + ShipState::Flying { .. } => true, _ => false, }, _ => false, diff --git a/crates/system/src/phys/objects/ship.rs b/crates/system/src/phys/objects/ship.rs index 6041137..e54907f 100644 --- a/crates/system/src/phys/objects/ship.rs +++ b/crates/system/src/phys/objects/ship.rs @@ -95,18 +95,54 @@ impl PhysSimShip { seq.step(res, &self.data, rigid_body, collider); self.collapse_sequence = Some(seq); } - ShipState::Flying => { - return self.step_live(res, rigid_body, collider); + ShipState::Flying { .. } => { + self.step_physics(res, rigid_body, collider); + self.step_effects(res, rigid_body, collider); } - ShipState::UnLanding { .. } - | ShipState::Dead - | ShipState::Landing { .. } - | ShipState::Landed { .. } => {} + + ShipState::Landing { .. } => { + self.step_physics(res, rigid_body, collider); + } + + ShipState::UnLanding { .. } | ShipState::Dead | ShipState::Landed { .. } => {} } } - /// Step this ship's state by t seconds (called when alive) - fn step_live( + /// Update this frame's physics + fn step_physics( + &mut self, + res: &mut PhysStepResources, + rigid_body: &mut RigidBody, + _collider: &mut Collider, + ) { + let ship_rot = rigid_body.rotation(); + let engine_force = ship_rot * (Vector2::new(1.0, 0.0) * res.t); + + if self.controls.thrust { + rigid_body.apply_impulse( + vector![engine_force.x, engine_force.y] + * self.data.get_outfits().get_engine_thrust(), + true, + ); + } + + if self.controls.right { + rigid_body.apply_torque_impulse( + self.data.get_outfits().get_steer_power() * -100.0 * res.t, + true, + ); + } + + if self.controls.left { + rigid_body.apply_torque_impulse( + self.data.get_outfits().get_steer_power() * 100.0 * res.t, + true, + ); + } + } + + /// Spawn this frame's particles + fn step_effects( &mut self, res: &mut PhysStepResources, rigid_body: &mut RigidBody, @@ -153,30 +189,6 @@ impl PhysSimShip { } } } - - let engine_force = ship_rot * (Vector2::new(1.0, 0.0) * res.t); - - if self.controls.thrust { - rigid_body.apply_impulse( - vector![engine_force.x, engine_force.y] - * self.data.get_outfits().get_engine_thrust(), - true, - ); - } - - if self.controls.right { - rigid_body.apply_torque_impulse( - self.data.get_outfits().get_steer_power() * -100.0 * res.t, - true, - ); - } - - if self.controls.left { - rigid_body.apply_torque_impulse( - self.data.get_outfits().get_steer_power() * 100.0 * res.t, - true, - ); - } } } diff --git a/crates/system/src/phys/systemsim.rs b/crates/system/src/phys/systemsim.rs index d26e640..ca6de77 100644 --- a/crates/system/src/phys/systemsim.rs +++ b/crates/system/src/phys/systemsim.rs @@ -3,7 +3,7 @@ use galactica_content::{ SystemHandle, SystemObjectHandle, }; use galactica_playeragent::PlayerAgent; -use nalgebra::{point, vector, Point2, Rotation2, Vector2}; +use nalgebra::{Isometry2, Point2, Rotation2, Vector2}; use rand::Rng; use rapier2d::{ dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle, RigidBodySet}, @@ -12,10 +12,10 @@ use rapier2d::{ }; use std::collections::HashMap; -use crate::data::{ShipPersonality, ShipState}; +use crate::data::{ShipAutoPilot, ShipPersonality, ShipState}; use super::{ - controller::ShipController, + controller::{autopilot, ShipController}, objects::{PhysProjectile, PhysSimShip}, ParticleBuilder, PhysStepResources, }; @@ -66,22 +66,55 @@ impl<'a> PhysSim { return Some((r, p)); } - fn land_ship(&mut self, collider: ColliderHandle, target: SystemObjectHandle) { + /// 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(); - ship.data - .land_on(target, (*r.translation()).into(), r.rotation().angle()); + 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); - - self.collider_set - .get_mut(ship.collider) - .unwrap() - .set_enabled(false); } fn unland_ship(&mut self, ct: &Content, collider: ColliderHandle) { @@ -89,19 +122,13 @@ impl<'a> PhysSim { let obj = ship.data.get_state().landed_on().unwrap(); let obj = ct.get_system_object(obj); - let target_pos = Point2::new(obj.pos.x + 100.0, obj.pos.y + 100.0); + 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( - nalgebra::Vector2::new(target_pos.x, target_pos.y).into(), - true, - ); - - r.set_rotation(nalgebra::Rotation2::new(1.0).into(), false); + r.set_position(target_pos, true); self.collider_set .get_mut(ship.collider) @@ -145,7 +172,7 @@ impl<'a> PhysSim { let r = f.relationships.get(&ship.data.get_faction()).unwrap(); let destory_projectile = match r { Relationship::Hostile => match ship.data.get_state() { - ShipState::Flying => { + ShipState::Flying { .. } => { ship.data.apply_damage(res.ct, projectile.content.damage); true } @@ -163,7 +190,7 @@ impl<'a> PhysSim { let _ = pr; let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap(); - r.apply_impulse_at_point(vector![v.x, v.y], point![pos.x, pos.y], true); + 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(); @@ -225,7 +252,7 @@ impl PhysSim { let rb = RigidBodyBuilder::dynamic() .angular_damping(ship_content.angular_drag) .linear_damping(ship_content.linear_drag) - .translation(vector![position.x, position.y]) + .translation(Vector2::new(position.x, position.y)) .can_sleep(false); let ridid_body = self.rigid_body_set.insert(rb.build()); @@ -256,25 +283,44 @@ impl PhysSim { } let ship_object = self.ships.get_mut(&player.ship.unwrap()); if let Some(ship_object) = ship_object { - 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(); + match ship_object.data.get_state() { + ShipState::Landing { .. } + | ShipState::UnLanding { .. } + | ShipState::Collapsing { .. } + | ShipState::Dead => {} - if player.input.pressed_land() { - match ship_object.data.get_state() { - ShipState::Flying => { - self.land_ship( - player.ship.unwrap(), - player.selection.get_planet().unwrap(), - ); + 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::Landed { .. } => { + } + + 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()); } - _ => {} } - } + }; } } @@ -300,7 +346,6 @@ impl PhysSim { } ShipState::UnLanding { .. } - | ShipState::Landing { .. } | ShipState::Landed { .. } | ShipState::Collapsing { .. } => { let ship = self.ships.get_mut(&collider).unwrap(); @@ -311,13 +356,67 @@ impl PhysSim { ); } - ShipState::Flying => { + 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; - let b = self.ship_behaviors.get_mut(&collider).unwrap(); - controls = - b.update_controls(&res, &self.rigid_body_set, &self.ships, ship.collider); + 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(); @@ -385,7 +484,7 @@ impl PhysSim { )); let rigid_body = RigidBodyBuilder::kinematic_velocity_based() - .translation(vector![pos.x, pos.y]) + .translation(pos) .rotation(ship_ang) .linvel(vel) .build();