Compare commits
4 Commits
d014e085d5
...
d5e2820f7d
Author | SHA1 | Date |
---|---|---|
Mark | d5e2820f7d | |
Mark | a4222abdc5 | |
Mark | 45e3513ed3 | |
Mark | ac4eb7d67d |
|
@ -13,6 +13,9 @@ object.earth.position.angle = 0
|
||||||
object.earth.position.z = 10.0
|
object.earth.position.z = 10.0
|
||||||
object.earth.size = 1000
|
object.earth.size = 1000
|
||||||
|
|
||||||
|
# TODO: satisfy conditions to land
|
||||||
|
object.earth.landable = true
|
||||||
|
|
||||||
object.luna.sprite = "planet::luna"
|
object.luna.sprite = "planet::luna"
|
||||||
object.luna.position.center = "earth"
|
object.luna.position.center = "earth"
|
||||||
object.luna.position.radius = 1600
|
object.luna.position.radius = 1600
|
||||||
|
|
|
@ -28,6 +28,7 @@ pub(crate) mod syntax {
|
||||||
|
|
||||||
pub radius: Option<f32>,
|
pub radius: Option<f32>,
|
||||||
pub angle: Option<f32>,
|
pub angle: Option<f32>,
|
||||||
|
pub landable: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -118,6 +119,9 @@ pub struct SystemObject {
|
||||||
|
|
||||||
/// This object's sprite's angle, in radians
|
/// This object's sprite's angle, in radians
|
||||||
pub angle: f32,
|
pub angle: f32,
|
||||||
|
|
||||||
|
/// If true, ships may land on this object
|
||||||
|
pub landable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function for resolve_position, never called on its own.
|
/// Helper function for resolve_position, never called on its own.
|
||||||
|
@ -218,6 +222,7 @@ impl crate::Build for System {
|
||||||
system_handle,
|
system_handle,
|
||||||
body_index: 0,
|
body_index: 0,
|
||||||
},
|
},
|
||||||
|
landable: obj.landable.unwrap_or(false),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -30,7 +30,10 @@ var sampler_array: binding_array<sampler>;
|
||||||
|
|
||||||
fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, sprite_index: u32) -> vec4<f32> {
|
fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, sprite_index: u32) -> vec4<f32> {
|
||||||
// Object scale
|
// Object scale
|
||||||
let scale = obj.size / (global_data.camera_zoom.x * obj.zpos);
|
var scale: f32 = obj.size / (global_data.camera_zoom.x * obj.zpos);
|
||||||
|
if obj.is_child == 1u {
|
||||||
|
scale /= objects[obj.parent].zpos;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply scale and sprite aspect
|
// Apply scale and sprite aspect
|
||||||
// Note that our mesh starts centered at (0, 0). This is important!
|
// Note that our mesh starts centered at (0, 0). This is important!
|
||||||
|
@ -70,14 +73,15 @@ fn transform_vertex(obj: ObjectData, vertex_position: vec2<f32>, sprite_index: u
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.is_child == u32(1) {
|
if obj.is_child == u32(1) {
|
||||||
|
|
||||||
|
let parent = objects[obj.parent];
|
||||||
|
|
||||||
// Apply translation relative to parent
|
// Apply translation relative to parent
|
||||||
// Note that obj.zpos is ignored
|
// Note that obj.zpos is ignored
|
||||||
pos = pos + vec2(
|
pos = pos + vec2(
|
||||||
obj.xpos / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
|
obj.xpos / (global_data.camera_zoom.x / 2.0) / global_data.window_aspect.x,
|
||||||
obj.ypos / (global_data.camera_zoom.x / 2.0)
|
obj.ypos / (global_data.camera_zoom.x / 2.0)
|
||||||
);
|
) / parent.zpos;
|
||||||
|
|
||||||
let parent = objects[obj.parent];
|
|
||||||
|
|
||||||
// Apply parent's rotation
|
// Apply parent's rotation
|
||||||
pos = mat2x2(
|
pos = mat2x2(
|
||||||
|
|
|
@ -66,9 +66,11 @@ fn vertex_main(
|
||||||
vertex.position.x * scale * global_sprites[instance.sprite_index].aspect,
|
vertex.position.x * scale * global_sprites[instance.sprite_index].aspect,
|
||||||
vertex.position.y * scale
|
vertex.position.y * scale
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Correct angle, since sprites point north and zero degrees is east
|
||||||
pos = mat2x2(
|
pos = mat2x2(
|
||||||
vec2(cos(angle), sin(angle)),
|
vec2(cos(angle - 1.5708), sin(angle - 1.5708)),
|
||||||
vec2(-sin(angle), cos(angle))
|
vec2(-sin(angle - 1.5708), cos(angle - 1.5708))
|
||||||
) * pos;
|
) * pos;
|
||||||
|
|
||||||
var trans: vec2<f32> = (instance.position + (instance.velocity * age) - global_data.camera_position);
|
var trans: vec2<f32> = (instance.position + (instance.velocity * age) - global_data.camera_position);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod landing;
|
||||||
|
|
||||||
|
pub use landing::*;
|
|
@ -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,
|
||||||
{
|
{
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,537 +0,0 @@
|
||||||
use galactica_content::{
|
|
||||||
Content, FactionHandle, GunPoint, OutfitHandle, ProjectileCollider, Relationship, ShipHandle,
|
|
||||||
SystemHandle, SystemObjectHandle,
|
|
||||||
};
|
|
||||||
use galactica_playeragent::PlayerAgent;
|
|
||||||
use nalgebra::{point, vector, 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::{ShipPersonality, ShipState};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
controller::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));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn land_ship(&mut self, collider: ColliderHandle, target: SystemObjectHandle) {
|
|
||||||
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 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) {
|
|
||||||
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 = Point2::new(obj.pos.x + 100.0, obj.pos.y + 100.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);
|
|
||||||
|
|
||||||
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(vector![v.x, v.y], point![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(vector![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 {
|
|
||||||
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() {
|
|
||||||
match ship_object.data.get_state() {
|
|
||||||
ShipState::Flying => {
|
|
||||||
self.land_ship(
|
|
||||||
player.ship.unwrap(),
|
|
||||||
player.selection.get_planet().unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ShipState::Landed { .. } => {
|
|
||||||
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::Landing { .. }
|
|
||||||
| 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::Flying => {
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
// 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(vector![pos.x, pos.y])
|
|
||||||
.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