Reworked ship landing, added autopilot

master
Mark 2024-01-13 18:57:21 -08:00
parent 45e3513ed3
commit a4222abdc5
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
11 changed files with 369 additions and 219 deletions

View File

@ -85,7 +85,9 @@ fn main() -> Result<()> {
.get_ship(&PhysSimShipHandle(player.ship.unwrap())); .get_ship(&PhysSimShipHandle(player.ship.unwrap()));
if let Some(o) = o { if let Some(o) = o {
match o.data.get_state() { match o.data.get_state() {
ShipState::Collapsing { .. } | ShipState::Flying => { ShipState::Landing { .. }
| ShipState::Collapsing { .. }
| ShipState::Flying { .. } => {
let r = let r =
&game.get_state().systemsim.get_rigid_body(o.rigid_body); &game.get_state().systemsim.get_rigid_body(o.rigid_body);
if let Some(r) = r { if let Some(r) = r {
@ -99,11 +101,7 @@ fn main() -> Result<()> {
o.data.get_state().unlanding_position(&content).unwrap(); o.data.get_state().unlanding_position(&content).unwrap();
Some(Vector2::new(pos.x, pos.y)) 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 } => { ShipState::Landed { target } => {
let b = content.get_system_object(*target); let b = content.get_system_object(*target);
Some(Vector2::new(b.pos.x, b.pos.y)) Some(Vector2::new(b.pos.x, b.pos.y))

View File

@ -3,7 +3,7 @@
use bytemuck; use bytemuck;
use galactica_system::data::ShipState; use galactica_system::data::ShipState;
use galactica_util::{constants::OBJECT_SPRITE_INSTANCE_LIMIT, to_radians}; use galactica_util::{constants::OBJECT_SPRITE_INSTANCE_LIMIT, to_radians};
use nalgebra::{Point2, Point3, Vector2}; use nalgebra::{Point2, Point3};
use crate::{ use crate::{
globaluniform::ObjectData, globaluniform::ObjectData,
@ -27,7 +27,7 @@ impl GPUState {
match ship.data.get_state() { match ship.data.get_state() {
ShipState::Dead | ShipState::Landed { .. } => continue, 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 r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap();
let pos = *r.translation(); let pos = *r.translation();
ship_pos = Point3::new(pos.x, pos.y, 1.0); ship_pos = Point3::new(pos.x, pos.y, 1.0);
@ -35,30 +35,20 @@ impl GPUState {
ship_ang = ship_rot.angle(); ship_ang = ship_rot.angle();
ship_cnt = state.ct.get_ship(ship.data.get_content()); 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; ShipState::Landing { current_z, .. } => {
let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap();
ship_pos = ship.data.get_state().landing_position(state.ct).unwrap(); let pos = *r.translation();
ship_pos = Point3::new(pos.x, pos.y, *current_z);
let target_angle = diff.angle(&Vector2::new(1.0, 0.0)); let ship_rot = r.rotation();
ship_ang = ship_rot.angle();
ship_ang = from_angle + ((target_angle - from_angle) * 1f32.min(elapsed / 1.0));
ship_cnt = state.ct.get_ship(ship.data.get_content()); ship_cnt = state.ct.get_ship(ship.data.get_content());
} }
ShipState::UnLanding { ShipState::UnLanding { .. } => {
to_angle, elapsed, ..
} => {
ship_pos = ship.data.get_state().unlanding_position(state.ct).unwrap(); 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()); 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); let flare = ship.data.get_outfits().get_flare_sprite(state.ct);
if { if {
let is_flying = match ship.data.get_state() { let is_flying = match ship.data.get_state() {
ShipState::Flying => true, ShipState::Flying { .. } => true,
ShipState::Landing { .. } => true,
_ => false, _ => false,
}; };
ship.get_controls().thrust && flare.is_some() && is_flying ship.get_controls().thrust && flare.is_some() && is_flying

View File

@ -41,15 +41,6 @@ impl Radar {
match player_ship.data.get_state() { match player_ship.data.get_state() {
ShipState::Dead => {} 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 { .. } => { ShipState::UnLanding { .. } => {
let pos = player_ship let pos = player_ship
.data .data
@ -63,7 +54,8 @@ impl Radar {
let landed_body = input.ct.get_system_object(*target); let landed_body = input.ct.get_system_object(*target);
self.last_player_position = Point2::new(landed_body.pos.x, landed_body.pos.y); 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 let player_body = input
.systemsim .systemsim
.get_rigid_body(player_ship.rigid_body) .get_rigid_body(player_ship.rigid_body)
@ -142,28 +134,35 @@ impl Radar {
// Draw ships // Draw ships
for (s, r) in input.systemsim.iter_ship_body() { for (s, r) in input.systemsim.iter_ship_body() {
// This will be None if this ship is dead. let ship = input.ct.get_ship(s.data.get_content());
// Stays around while the physics system runs a collapse sequence let (color, z_scale) = match s.data.get_state() {
let color = match s.data.get_state() {
ShipState::Dead | ShipState::Landed { .. } => { ShipState::Dead | ShipState::Landed { .. } => {
continue; continue;
} }
// TODO: different color for landing? // TODO: different color for landing?
ShipState::UnLanding { .. } // TODO: scale blip for ship z-position
| ShipState::Landing { .. } ShipState::Landing { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0),
| ShipState::Collapsing { .. } => {
ShipState::UnLanding { .. } => ([0.2, 0.2, 0.2, 1.0], 1.0),
ShipState::Collapsing { .. } => {
// TODO: configurable // 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; 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<f32> = {
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<f32> = (*r.translation()).into();
let d = (p - self.last_player_position) / radar_range; let d = (p - self.last_player_position) / radar_range;
let m = d.magnitude() + (size / (2.0 * radar_size)); let m = d.magnitude() + (size / (2.0 * radar_size));
if m < hide_range { if m < hide_range {

View File

@ -47,7 +47,7 @@ impl Status {
| ShipState::Landing { .. } | ShipState::Landing { .. }
| ShipState::Landed { .. } | ShipState::Landed { .. }
| ShipState::Collapsing { .. } | ShipState::Collapsing { .. }
| ShipState::Flying => { | ShipState::Flying { .. } => {
current_shields = player_ship.data.get_shields(); current_shields = player_ship.data.get_shields();
current_hull = player_ship.data.get_hull(); current_hull = player_ship.data.get_hull();
max_shields = player_ship.data.get_outfits().get_shield_strength(); max_shields = player_ship.data.get_outfits().get_shield_strength();

View File

@ -1,10 +1,26 @@
use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle}; use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle};
use nalgebra::{Point2, Point3}; use nalgebra::{Point2, Point3};
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
use rapier2d::math::Isometry;
use std::{collections::HashMap, time::Instant}; use std::{collections::HashMap, time::Instant};
use super::{OutfitSet, ShipPersonality}; 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. /// Ship state machine.
/// Any ship we keep track of is in one of these states. /// Any ship we keep track of is in one of these states.
/// Dead ships don't exist---they removed once their collapse /// Dead ships don't exist---they removed once their collapse
@ -15,7 +31,11 @@ pub enum ShipState {
Dead, Dead,
/// This ship is alive and well in open space /// 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. /// This ship has been destroyed, and is playing its collapse sequence.
Collapsing { Collapsing {
@ -35,30 +55,18 @@ pub enum ShipState {
/// This ship is landing on a planet /// This ship is landing on a planet
/// (playing the animation) /// (playing the animation)
Landing { Landing {
/// The point, in world coordinates, where we started
from_position: Point2<f32>,
/// The ship's angle when we started landing, in radians
from_angle: f32,
/// The planet we're landing on /// The planet we're landing on
target: SystemObjectHandle, target: SystemObjectHandle,
/// The total amount of time, in seconds, we will spend landing /// Our current z-coordinate
total: f32, current_z: f32,
/// The amount of time we've already spent playing this landing sequence
elapsed: f32,
}, },
/// This ship is taking off from a planet /// This ship is taking off from a planet
/// (playing the animation) /// (playing the animation)
UnLanding { UnLanding {
/// The point, in world coordinates, to which we're going /// The point, in world coordinates, to which we're going
to_position: Point2<f32>, to_position: Isometry<f32>,
/// The angle we'll be at when we arrive, in radians
to_angle: f32,
/// The planet we're taking off from /// The planet we're taking off from
from: SystemObjectHandle, 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<Point3<f32>> {
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 /// Compute position of this ship's sprite during its unlanding sequence
pub fn unlanding_position(&self, ct: &Content) -> Option<Point3<f32>> { pub fn unlanding_position(&self, ct: &Content) -> Option<Point3<f32>> {
match self { match self {
@ -139,7 +112,8 @@ impl ShipState {
} => Some({ } => Some({
let from = ct.get_system_object(*from); 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; //let diff = diff - diff.normalize() * (target.size / 2.0) * 0.8;
// TODO: improve animation // TODO: improve animation
@ -207,7 +181,9 @@ impl ShipData {
rng: rand::thread_rng(), rng: rand::thread_rng(),
// TODO: ships must always start landed on planets // TODO: ships must always start landed on planets
state: ShipState::Flying, state: ShipState::Flying {
autopilot: ShipAutoPilot::None,
},
// Initial stats // Initial stats
hull: s.hull, hull: s.hull,
@ -216,40 +192,72 @@ impl ShipData {
} }
} }
// TODO: position in data? /// Set this ship's autopilot.
/// Land this ship on `target` /// Panics if we're not flying.
pub fn land_on( pub fn set_autopilot(&mut self, autopilot: ShipAutoPilot) {
&mut self,
target: SystemObjectHandle,
from_position: Point2<f32>,
from_angle: f32,
) -> bool {
match self.state { match self.state {
ShipState::Flying => { ShipState::Flying {
self.state = ShipState::Landing { autopilot: ref mut pilot,
elapsed: 0.0, } => {
total: 5.0, *pilot = autopilot;
target,
from_position,
from_angle,
};
return true;
} }
_ => { _ => {
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` /// Take off from `target`
pub fn unland(&mut self, to_position: Point2<f32>) { pub fn unland(&mut self, to_position: Isometry<f32>) {
match self.state { match self.state {
ShipState::Landed { target } => { ShipState::Landed { target } => {
self.state = ShipState::UnLanding { self.state = ShipState::UnLanding {
to_position, to_position,
to_angle: 1.0,
from: target, from: target,
total: 5.0, total: 2.0,
elapsed: 0.0, elapsed: 0.0,
}; };
} }
@ -296,7 +304,7 @@ impl ShipData {
/// Hit this ship with the given amount of damage /// Hit this ship with the given amount of damage
pub(crate) fn apply_damage(&mut self, ct: &Content, mut d: f32) { pub(crate) fn apply_damage(&mut self, ct: &Content, mut d: f32) {
match self.state { match self.state {
ShipState::Flying => {} ShipState::Flying { .. } => {}
_ => { _ => {
unreachable!("Cannot apply damage to a ship that is not 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 /// Update this ship's state by `t` seconds
pub(crate) fn step(&mut self, t: f32) { pub(crate) fn step(&mut self, t: f32) {
match self.state { match self.state {
ShipState::Landing { ShipState::Landing { .. } => {}
ref mut elapsed,
total,
target,
..
} => {
*elapsed += t;
if *elapsed >= total {
self.state = ShipState::Landed { target };
}
}
ShipState::UnLanding { ShipState::UnLanding {
ref mut elapsed, ref mut elapsed,
@ -342,7 +340,9 @@ impl ShipData {
} => { } => {
*elapsed += t; *elapsed += t;
if *elapsed >= total { 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 // Cooldown guns
for (_, c) in &mut self.gun_cooldowns { for (_, c) in &mut self.gun_cooldowns {
if *c > 0.0 { if *c > 0.0 {

View File

@ -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<ColliderHandle, PhysSimShip>,
this_ship: ColliderHandle,
target_handle: SystemObjectHandle,
) -> Option<ShipControls> {
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);
}

View File

@ -0,0 +1,3 @@
mod landing;
pub use landing::*;

View File

@ -1,5 +1,6 @@
//! Various implementations of [`crate::ShipBehavior`] //! Various implementations of [`crate::ShipBehavior`]
pub(crate) mod autopilot;
mod null; mod null;
mod point; mod point;
@ -50,9 +51,7 @@ impl ShipController {
} }
} }
/// Ship controller trait. Any struct that implements this trait ShipControllerStruct
/// may be used to control a ship.
pub trait ShipControllerStruct
where where
Self: Debug + Clone, Self: Debug + Clone,
{ {

View File

@ -49,7 +49,7 @@ impl ShipControllerStruct for PointShipController {
// Target only flying ships we're hostile towards // Target only flying ships we're hostile towards
|s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() { |s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() {
Relationship::Hostile => match s.data.get_state() { Relationship::Hostile => match s.data.get_state() {
ShipState::Flying => true, ShipState::Flying { .. } => true,
_ => false, _ => false,
}, },
_ => false, _ => false,

View File

@ -95,18 +95,54 @@ impl PhysSimShip {
seq.step(res, &self.data, rigid_body, collider); seq.step(res, &self.data, rigid_body, collider);
self.collapse_sequence = Some(seq); self.collapse_sequence = Some(seq);
} }
ShipState::Flying => { ShipState::Flying { .. } => {
return self.step_live(res, rigid_body, collider); self.step_physics(res, rigid_body, collider);
self.step_effects(res, rigid_body, collider);
} }
ShipState::UnLanding { .. }
| ShipState::Dead ShipState::Landing { .. } => {
| ShipState::Landing { .. } self.step_physics(res, rigid_body, collider);
| ShipState::Landed { .. } => {} }
ShipState::UnLanding { .. } | ShipState::Dead | ShipState::Landed { .. } => {}
} }
} }
/// Step this ship's state by t seconds (called when alive) /// Update this frame's physics
fn step_live( 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, &mut self,
res: &mut PhysStepResources, res: &mut PhysStepResources,
rigid_body: &mut RigidBody, 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,
);
}
} }
} }

View File

@ -3,7 +3,7 @@ use galactica_content::{
SystemHandle, SystemObjectHandle, SystemHandle, SystemObjectHandle,
}; };
use galactica_playeragent::PlayerAgent; use galactica_playeragent::PlayerAgent;
use nalgebra::{point, vector, Point2, Rotation2, Vector2}; use nalgebra::{Isometry2, Point2, Rotation2, Vector2};
use rand::Rng; use rand::Rng;
use rapier2d::{ use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle, RigidBodySet}, dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle, RigidBodySet},
@ -12,10 +12,10 @@ use rapier2d::{
}; };
use std::collections::HashMap; use std::collections::HashMap;
use crate::data::{ShipPersonality, ShipState}; use crate::data::{ShipAutoPilot, ShipPersonality, ShipState};
use super::{ use super::{
controller::ShipController, controller::{autopilot, ShipController},
objects::{PhysProjectile, PhysSimShip}, objects::{PhysProjectile, PhysSimShip},
ParticleBuilder, PhysStepResources, ParticleBuilder, PhysStepResources,
}; };
@ -66,22 +66,55 @@ impl<'a> PhysSim {
return Some((r, p)); 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 ship = self.ships.get_mut(&collider).unwrap();
let r = self.rigid_body_set.get(ship.rigid_body).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(); let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
r.set_enabled(false); r.set_enabled(false);
r.set_angvel(0.0, false); r.set_angvel(0.0, false);
r.set_linvel(nalgebra::Vector2::new(0.0, 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) { 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 = ship.data.get_state().landed_on().unwrap();
let obj = ct.get_system_object(obj); 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); ship.data.unland(target_pos);
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap(); let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap();
r.set_enabled(true); r.set_enabled(true);
r.set_position(target_pos, 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);
self.collider_set self.collider_set
.get_mut(ship.collider) .get_mut(ship.collider)
@ -145,7 +172,7 @@ impl<'a> PhysSim {
let r = f.relationships.get(&ship.data.get_faction()).unwrap(); let r = f.relationships.get(&ship.data.get_faction()).unwrap();
let destory_projectile = match r { let destory_projectile = match r {
Relationship::Hostile => match ship.data.get_state() { Relationship::Hostile => match ship.data.get_state() {
ShipState::Flying => { ShipState::Flying { .. } => {
ship.data.apply_damage(res.ct, projectile.content.damage); ship.data.apply_damage(res.ct, projectile.content.damage);
true true
} }
@ -163,7 +190,7 @@ impl<'a> PhysSim {
let _ = pr; let _ = pr;
let r = self.rigid_body_set.get_mut(ship.rigid_body).unwrap(); 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 // Borrow again, we can only have one at a time
let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap(); let pr = self.rigid_body_set.get(projectile.rigid_body).unwrap();
@ -225,7 +252,7 @@ impl PhysSim {
let rb = RigidBodyBuilder::dynamic() let rb = RigidBodyBuilder::dynamic()
.angular_damping(ship_content.angular_drag) .angular_damping(ship_content.angular_drag)
.linear_damping(ship_content.linear_drag) .linear_damping(ship_content.linear_drag)
.translation(vector![position.x, position.y]) .translation(Vector2::new(position.x, position.y))
.can_sleep(false); .can_sleep(false);
let ridid_body = self.rigid_body_set.insert(rb.build()); 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()); let ship_object = self.ships.get_mut(&player.ship.unwrap());
if let Some(ship_object) = ship_object { if let Some(ship_object) = ship_object {
ship_object.controls.guns = player.input.pressed_guns(); match ship_object.data.get_state() {
ship_object.controls.left = player.input.pressed_left(); ShipState::Landing { .. }
ship_object.controls.right = player.input.pressed_right(); | ShipState::UnLanding { .. }
ship_object.controls.thrust = player.input.pressed_thrust(); | ShipState::Collapsing { .. }
| ShipState::Dead => {}
if player.input.pressed_land() { ShipState::Flying {
match ship_object.data.get_state() { autopilot: ShipAutoPilot::None,
ShipState::Flying => { } => {
self.land_ship( ship_object.controls.guns = player.input.pressed_guns();
player.ship.unwrap(), ship_object.controls.left = player.input.pressed_left();
player.selection.get_planet().unwrap(), 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()); self.unland_ship(ct, player.ship.unwrap());
} }
_ => {}
} }
} };
} }
} }
@ -300,7 +346,6 @@ impl PhysSim {
} }
ShipState::UnLanding { .. } ShipState::UnLanding { .. }
| ShipState::Landing { .. }
| ShipState::Landed { .. } | ShipState::Landed { .. }
| ShipState::Collapsing { .. } => { | ShipState::Collapsing { .. } => {
let ship = self.ships.get_mut(&collider).unwrap(); 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 // Compute new controls
// This is why we borrow immutably first // This is why we borrow immutably first
let controls; let controls = match autopilot {
let b = self.ship_behaviors.get_mut(&collider).unwrap(); ShipAutoPilot::Landing {
controls = target: target_handle,
b.update_controls(&res, &self.rigid_body_set, &self.ships, ship.collider); } => {
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 // Re-borrow mutably to apply changes
let ship = self.ships.get_mut(&collider).unwrap(); let ship = self.ships.get_mut(&collider).unwrap();
@ -385,7 +484,7 @@ impl PhysSim {
)); ));
let rigid_body = RigidBodyBuilder::kinematic_velocity_based() let rigid_body = RigidBodyBuilder::kinematic_velocity_based()
.translation(vector![pos.x, pos.y]) .translation(pos)
.rotation(ship_ang) .rotation(ship_ang)
.linvel(vel) .linvel(vel)
.build(); .build();