Compare commits

..

No commits in common. "9f1db1c7b66126446894590bb35a08c0c633c2bf" and "5015040226232847f37b915f47b1f51240969e6d" have entirely different histories.

41 changed files with 612 additions and 443 deletions

20
Cargo.lock generated
View File

@ -643,9 +643,10 @@ dependencies = [
"anyhow", "anyhow",
"cgmath", "cgmath",
"galactica-content", "galactica-content",
"galactica-galaxy",
"galactica-playeragent", "galactica-playeragent",
"galactica-render", "galactica-render",
"galactica-system", "galactica-systemsim",
"galactica-util", "galactica-util",
"pollster", "pollster",
"rand", "rand",
@ -667,6 +668,15 @@ dependencies = [
"walkdir", "walkdir",
] ]
[[package]]
name = "galactica-galaxy"
version = "0.0.0"
dependencies = [
"cgmath",
"galactica-content",
"rand",
]
[[package]] [[package]]
name = "galactica-packer" name = "galactica-packer"
version = "0.0.0" version = "0.0.0"
@ -686,9 +696,9 @@ dependencies = [
"anyhow", "anyhow",
"cgmath", "cgmath",
"galactica-content", "galactica-content",
"galactica-galaxy",
"galactica-util", "galactica-util",
"pollster", "pollster",
"rapier2d",
"wgpu", "wgpu",
"winit", "winit",
] ]
@ -701,8 +711,9 @@ dependencies = [
"bytemuck", "bytemuck",
"cgmath", "cgmath",
"galactica-content", "galactica-content",
"galactica-galaxy",
"galactica-packer", "galactica-packer",
"galactica-system", "galactica-systemsim",
"galactica-util", "galactica-util",
"glyphon", "glyphon",
"image", "image",
@ -712,12 +723,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "galactica-system" name = "galactica-systemsim"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"cgmath", "cgmath",
"crossbeam", "crossbeam",
"galactica-content", "galactica-content",
"galactica-galaxy",
"galactica-playeragent", "galactica-playeragent",
"galactica-util", "galactica-util",
"nalgebra", "nalgebra",

View File

@ -45,7 +45,8 @@ readme = ""
galactica-util = { path = "crates/util" } galactica-util = { path = "crates/util" }
galactica-content = { path = "crates/content" } galactica-content = { path = "crates/content" }
galactica-render = { path = "crates/render" } galactica-render = { path = "crates/render" }
galactica-system = { path = "crates/system" } galactica-systemsim = { path = "crates/systemsim" }
galactica-galaxy = { path = "crates/galaxy" }
galactica-packer = { path = "crates/packer" } galactica-packer = { path = "crates/packer" }
galactica-playeragent = { path = "crates/playeragent" } galactica-playeragent = { path = "crates/playeragent" }
galactica = { path = "crates/galactica" } galactica = { path = "crates/galactica" }

View File

@ -207,7 +207,7 @@ impl crate::Build for System {
}); });
} }
objects.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); objects.sort_by(|a, b| a.pos.z.total_cmp(&b.pos.z));
content.systems.push(Self { content.systems.push(Self {
name: system_name, name: system_name,
objects, objects,

View File

@ -24,7 +24,8 @@ workspace = true
galactica-content = { workspace = true } galactica-content = { workspace = true }
galactica-render = { workspace = true } galactica-render = { workspace = true }
galactica-util = { workspace = true } galactica-util = { workspace = true }
galactica-system = { workspace = true } galactica-systemsim = { workspace = true }
galactica-galaxy = { workspace = true }
galactica-playeragent = { workspace = true } galactica-playeragent = { workspace = true }
rand = { workspace = true } rand = { workspace = true }

View File

@ -1,17 +1,16 @@
use cgmath::Point2; use cgmath::Point2;
use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle}; use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle};
use galactica_galaxy::{ship::ShipPersonality, Galaxy, GxShipHandle};
use galactica_playeragent::PlayerAgent; use galactica_playeragent::PlayerAgent;
use galactica_system::data::ShipPersonality; use galactica_systemsim::{ParticleBuilder, StepResources, SystemSim, Wrapper};
use galactica_system::phys::{
ParticleBuilder, PhysSim, PhysSimShipHandle, PhysStepResources, Wrapper,
};
use galactica_util::timing::Timing; use galactica_util::timing::Timing;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use std::time::Instant; use std::time::Instant;
#[derive(Clone)] #[derive(Clone)]
pub struct GameState { pub struct GameState {
pub systemsim: PhysSim, pub gx: Galaxy,
pub systemsim: SystemSim,
pub timing: Timing, pub timing: Timing,
pub start_instant: Instant, pub start_instant: Instant,
} }
@ -38,56 +37,57 @@ pub struct Game {
unsafe impl<'a> Send for Game {} unsafe impl<'a> Send for Game {}
impl<'a> Game { impl<'a> Game {
pub fn make_player(&mut self) -> PhysSimShipHandle { pub fn make_player(&mut self) -> GxShipHandle {
let player = self.state.systemsim.add_ship( let player = self.state.gx.create_ship(
&self.ct, &self.ct,
ShipHandle { index: 0 }, ShipHandle { index: 0 },
FactionHandle { index: 0 }, FactionHandle { index: 0 },
ShipPersonality::Player, ShipPersonality::Player,
Point2 { x: 0.0, y: 0.0 }, &SystemHandle { index: 0 },
); );
let s = self.state.gx.get_ship_mut(player).unwrap();
s.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 0 }));
s.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 1 }));
s.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 2 }));
let s = self.state.systemsim.get_ship_mut(&player).unwrap(); self.state
s.data .systemsim
.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 0 })); .add_ship(&self.ct, &s, Point2 { x: 0.0, y: 0.0 });
s.data
.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 1 }));
s.data
.add_outfit(&self.ct.get_outfit(OutfitHandle { index: 2 }));
return player; return player;
} }
pub fn new(ct: Content) -> Self { pub fn new(ct: Content) -> Self {
let mut systemsim = PhysSim::new(&ct, SystemHandle { index: 0 }); let mut gx = Galaxy::new(&ct);
let a = systemsim.add_ship( let a = gx.create_ship(
&ct, &ct,
ShipHandle { index: 0 }, ShipHandle { index: 0 },
FactionHandle { index: 1 }, FactionHandle { index: 1 },
ShipPersonality::Point, ShipPersonality::Dummy,
Point2 { x: 100.0, y: 0.0 }, &SystemHandle { index: 0 },
); );
let s = gx.get_ship_mut(a).unwrap();
s.add_outfit(&ct.get_outfit(OutfitHandle { index: 0 }));
s.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
let s = systemsim.get_ship_mut(&a).unwrap(); let a = gx.create_ship(
s.data.add_outfit(ct.get_outfit(OutfitHandle { index: 0 }));
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
let a = systemsim.add_ship(
&ct, &ct,
ShipHandle { index: 0 }, ShipHandle { index: 0 },
FactionHandle { index: 0 }, FactionHandle { index: 0 },
ShipPersonality::Point, ShipPersonality::Point,
Point2 { x: 0.0, y: 120.0 }, &SystemHandle { index: 0 },
); );
let s = gx.get_ship_mut(a).unwrap();
s.add_outfit(&ct.get_outfit(OutfitHandle { index: 0 }));
s.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
let s = systemsim.get_ship_mut(&a).unwrap(); let systemsim = SystemSim::new(&ct, &gx, SystemHandle { index: 0 });
s.data.add_outfit(ct.get_outfit(OutfitHandle { index: 0 }));
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
s.data.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
let state = GameState { let state = GameState {
gx,
systemsim, systemsim,
timing: Timing::new(), timing: Timing::new(),
start_instant: Instant::now(), start_instant: Instant::now(),
@ -121,8 +121,13 @@ impl<'a> Game {
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale; let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
self.new_particles.clear(); self.new_particles.clear();
self.state.systemsim.step(PhysStepResources { self.state.timing.start_galaxy();
self.state.gx.step(t);
self.state.timing.mark_galaxy();
self.state.systemsim.step(StepResources {
ct: &self.ct, ct: &self.ct,
gx: &mut self.state.gx,
particles: &mut self.new_particles, particles: &mut self.new_particles,
timing: &mut self.state.timing, timing: &mut self.state.timing,
wrapper: &mut self.wrapper, wrapper: &mut self.wrapper,

View File

@ -4,7 +4,7 @@ use anyhow::{bail, Result};
use galactica_content::{Content, SystemHandle}; use galactica_content::{Content, SystemHandle};
use galactica_playeragent::{PlayerAgent, PlayerStatus}; use galactica_playeragent::{PlayerAgent, PlayerStatus};
use galactica_render::RenderInput; use galactica_render::RenderInput;
use galactica_system::phys::{util, PhysSimShipHandle}; use galactica_systemsim::util;
use galactica_util::constants::ASSET_CACHE; use galactica_util::constants::ASSET_CACHE;
use std::{ use std::{
fs, fs,
@ -41,7 +41,7 @@ fn main() -> Result<()> {
let mut game = game::Game::new(content.clone()); let mut game = game::Game::new(content.clone());
let p = game.make_player(); let p = game.make_player();
let mut player = PlayerAgent::new(p.0); let mut player = PlayerAgent::new(p);
player.set_camera_aspect( player.set_camera_aspect(
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32, gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32,
); );
@ -56,7 +56,8 @@ fn main() -> Result<()> {
ct: &content, ct: &content,
systemsim: &game.get_state().systemsim, systemsim: &game.get_state().systemsim,
particles: game.get_particles(), particles: game.get_particles(),
player_ship: PhysSimShipHandle(player.ship.unwrap()), player_data: player.ship.unwrap(),
gx: &game.get_state().gx,
current_system: SystemHandle { index: 0 }, current_system: SystemHandle { index: 0 },
timing: game.get_state().timing.clone(), timing: game.get_state().timing.clone(),
}; };
@ -76,10 +77,7 @@ fn main() -> Result<()> {
let player_status = { let player_status = {
let pos = { let pos = {
let o = &game let o = &game.get_state().systemsim.get_ship(player.ship.unwrap());
.get_state()
.systemsim
.get_ship(&PhysSimShipHandle(player.ship.unwrap()));
if let Some(o) = o { if let Some(o) = o {
let r = &game.get_state().systemsim.get_rigid_body(o.rigid_body); let r = &game.get_state().systemsim.get_rigid_body(o.rigid_body);
if let Some(r) = r { if let Some(r) = r {

23
crates/galaxy/Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "galactica-galaxy"
description = "Galactica's game data manager"
categories = { workspace = true }
keywords = { workspace = true }
version = { workspace = true }
rust-version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
documentation = { workspace = true }
readme = { workspace = true }
[lints]
workspace = true
[dependencies]
galactica-content = { workspace = true }
cgmath = { workspace = true }
rand = { workspace = true }

View File

@ -0,0 +1,98 @@
use std::collections::HashMap;
use crate::{handles::GxShipHandle, ship::GxShip, ship::ShipPersonality};
use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle};
/// Keeps track of all objects in the galaxy.
/// This struct does NO physics, it keeps track of data exclusively.
#[derive(Debug, Clone)]
pub struct Galaxy {
/// Universal counter.
/// Used to create unique handles for game objects.
index: u64,
/// All ships in the galaxy
ships: HashMap<GxShipHandle, GxShip>,
/// Ships indexed by the system they're in.
/// A ship must always be in exactly one system.
system_ship_table: HashMap<SystemHandle, Vec<GxShipHandle>>,
/// Systems indexed by which ships they contain.
/// A ship must always be in exactly one system.
ship_system_table: HashMap<GxShipHandle, SystemHandle>,
}
impl Galaxy {
pub fn new(ct: &Content) -> Self {
Self {
system_ship_table: ct.iter_systems().map(|s| (s, Vec::new())).collect(),
ship_system_table: HashMap::new(),
index: 0,
ships: HashMap::new(),
}
}
/// Spawn a ship
pub fn create_ship(
&mut self,
ct: &Content,
ship: ShipHandle,
faction: FactionHandle,
personality: ShipPersonality,
system: &SystemHandle,
) -> GxShipHandle {
let handle = GxShipHandle {
index: self.index,
content: ship,
};
self.index += 1;
self.ships
.insert(handle, GxShip::new(ct, handle, ship, faction, personality));
self.system_ship_table.get_mut(system).unwrap().push(handle);
self.ship_system_table.insert(handle, *system);
return handle;
}
pub fn step(&mut self, t: f32) {
// TODO: don't allocate on step, need a better
// way to satisfy the borrow checker.
// Same needs to be done in the `systemsim` crate.
let mut to_remove = Vec::new();
for (_, s) in &mut self.ships {
s.step(t);
if s.is_dead() {
to_remove.push(s.get_handle());
}
}
// Remove dead ships
// No fancy animation here, that's handled by the physics system.
for i in to_remove {
let system = self.ship_system_table.remove(&i).unwrap();
self.system_ship_table
.get_mut(&system)
.unwrap()
.retain(|x| *x != i);
self.ships.remove(&i);
}
}
}
// Public getters
impl Galaxy {
pub fn get_ship(&self, handle: GxShipHandle) -> Option<&GxShip> {
self.ships.get(&handle)
}
pub fn get_ship_mut(&mut self, handle: GxShipHandle) -> Option<&mut GxShip> {
self.ships.get_mut(&handle)
}
pub fn iter_ships(&self) -> impl Iterator<Item = &GxShip> {
self.ships.values()
}
}

View File

@ -0,0 +1,33 @@
use std::hash::Hash;
use galactica_content::ShipHandle;
/// A lightweight representation of a ship in the galaxy
#[derive(Debug, Clone, Copy)]
pub struct GxShipHandle {
/// This ship's unique index
pub(crate) index: u64,
/// This ship's content handle
/// (technically redundant, but this greatly simplifies code)
pub(crate) content: ShipHandle,
}
impl GxShipHandle {
pub fn content_handle(&self) -> ShipHandle {
self.content
}
}
impl Hash for GxShipHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
impl Eq for GxShipHandle {}
impl PartialEq for GxShipHandle {
fn eq(&self, other: &Self) -> bool {
self.index.eq(&other.index)
}
}

11
crates/galaxy/src/lib.rs Normal file
View File

@ -0,0 +1,11 @@
//! This module keeps track of the galaxy state.
//! It is also responsible for ship stats, outfits, etc.
//!
//! `galaxy` does not provide any simulation logic---that is done in seperate crates.
mod galaxy;
mod handles;
pub mod ship;
pub use galaxy::*;
pub use handles::*;

View File

@ -160,8 +160,6 @@ impl OutfitSet {
} }
// TODO: pick these better // TODO: pick these better
/// Returns the flare sprite that should be shown when this
/// ship is using its thrusters
pub fn get_flare_sprite(&self, ct: &Content) -> Option<SpriteHandle> { pub fn get_flare_sprite(&self, ct: &Content) -> Option<SpriteHandle> {
for i in self.outfits.keys() { for i in self.outfits.keys() {
let c = ct.get_outfit(*i); let c = ct.get_outfit(*i);

View File

@ -1,81 +1,21 @@
use std::{collections::HashMap, time::Instant}; use std::{collections::HashMap, time::Instant};
use crate::GxShipHandle;
use super::{OutfitSet, ShipPersonality}; use super::{OutfitSet, ShipPersonality};
use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle}; use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle};
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
/// Ship state machine.
/// Any ship we keep track of is in one of these states.
/// Dead ships don't exist---they removed once their collapse
/// sequence fully plays out.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ShipState { pub struct GxShip {
/// This ship is alive and well in open space
Flying, // TODO: system, position (also in collapse)?
/// This ship has been destroyed, and is playing its collapse sequence.
Collapsing {
/// Total collapse sequence length, in seconds
total: f32,
/// How many seconds of the collapse sequence we've played
elapsed: f32,
},
}
impl ShipState {
/// True if this ship has been destroyed and has finished it's collapse sequence.
/// Ships are deleted once this is true.
pub fn should_be_removed(&self) -> bool {
match self {
Self::Collapsing { elapsed, total } => elapsed >= total,
_ => false,
}
}
/// Is this ship playing its collapse sequence?
pub fn is_collapsing(&self) -> bool {
match self {
Self::Collapsing { .. } => true,
_ => false,
}
}
/// If this ship is collapsing, return total collapse time and remaining collapse time.
/// Otherwise, return None
pub fn collapse_state(&self) -> Option<(f32, f32)> {
match self {
Self::Collapsing {
total,
elapsed: remaining,
} => Some((*total, *remaining)),
_ => None,
}
}
/// Is this ship flying in open space?
pub fn is_flying(&self) -> bool {
match self {
Self::Flying => true,
_ => false,
}
}
}
/// Represents all attributes of a single ship
#[derive(Debug, Clone)]
pub struct ShipData {
// Metadata values // Metadata values
handle: GxShipHandle,
ct_handle: ShipHandle, ct_handle: ShipHandle,
faction: FactionHandle, faction: FactionHandle,
outfits: OutfitSet, outfits: OutfitSet,
personality: ShipPersonality, personality: ShipPersonality,
/// Ship state machine. Keeps track of all possible ship state.
/// TODO: document this, draw a graph
state: ShipState,
// State values // State values
// TODO: unified ship stats struct, like outfit space // TODO: unified ship stats struct, like outfit space
hull: f32, hull: f32,
@ -88,16 +28,17 @@ pub struct ShipData {
last_hit: Instant, last_hit: Instant,
} }
impl ShipData { impl GxShip {
/// Create a new ShipData
pub(crate) fn new( pub(crate) fn new(
ct: &Content, ct: &Content,
handle: GxShipHandle,
ct_handle: ShipHandle, ct_handle: ShipHandle,
faction: FactionHandle, faction: FactionHandle,
personality: ShipPersonality, personality: ShipPersonality,
) -> Self { ) -> Self {
let s = ct.get_ship(ct_handle); let s = ct.get_ship(ct_handle);
ShipData { GxShip {
handle,
ct_handle, ct_handle,
faction, faction,
outfits: OutfitSet::new(s.space, &s.guns), outfits: OutfitSet::new(s.space, &s.guns),
@ -105,9 +46,6 @@ impl ShipData {
last_hit: Instant::now(), last_hit: Instant::now(),
rng: rand::thread_rng(), rng: rand::thread_rng(),
// TODO: ships must always start landed on planets
state: ShipState::Flying,
// Initial stats // Initial stats
hull: s.hull, hull: s.hull,
shields: 0.0, shields: 0.0,
@ -127,11 +65,16 @@ impl ShipData {
self.outfits.remove(o) self.outfits.remove(o)
} }
/// If this ship is dead, it will be removed from the game.
pub fn is_dead(&self) -> bool {
self.hull <= 0.0
}
/// Try to fire a gun. /// Try to fire a gun.
/// Will panic if `which` isn't a point on this ship. /// Will panic if `which` isn't a point on this ship.
/// Returns `true` if this gun was fired, /// Returns `true` if this gun was fired,
/// and `false` if it is on cooldown or empty. /// and `false` if it is on cooldown or empty.
pub(crate) fn fire_gun(&mut self, ct: &Content, which: &GunPoint) -> bool { pub fn fire_gun(&mut self, ct: &Content, which: &GunPoint) -> bool {
let c = self.gun_cooldowns.get_mut(which).unwrap(); let c = self.gun_cooldowns.get_mut(which).unwrap();
if *c > 0.0 { if *c > 0.0 {
@ -150,8 +93,8 @@ 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 fn apply_damage(&mut self, mut d: f32) {
if self.state.is_collapsing() { if self.is_dead() {
return; return;
} }
if self.shields >= d { if self.shields >= d {
@ -162,44 +105,26 @@ impl ShipData {
self.hull = 0f32.max(self.hull - d); self.hull = 0f32.max(self.hull - d);
} }
self.last_hit = Instant::now(); self.last_hit = Instant::now();
if self.hull <= 0.0 {
// This ship has been destroyed, update state
self.state = ShipState::Collapsing {
total: ct.get_ship(self.ct_handle).collapse.length,
elapsed: 0.0,
}
}
} }
/// 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 fn step(&mut self, t: f32) {
match self.state { // Cooldown guns
ShipState::Collapsing { for (_, c) in &mut self.gun_cooldowns {
ref mut elapsed, .. if *c > 0.0 {
} => { *c -= t;
*elapsed += t;
} }
}
ShipState::Flying => { // Regenerate shields
// Cooldown guns let time_since = self.last_hit.elapsed().as_secs_f32();
for (_, c) in &mut self.gun_cooldowns { if self.shields != self.outfits.get_shield_strength() {
if *c > 0.0 { for g in self.outfits.iter_shield_generators() {
*c -= t; if time_since >= g.delay {
} self.shields += g.generation * t;
} if self.shields > self.outfits.get_shield_strength() {
self.shields = self.outfits.get_shield_strength();
// Regenerate shields break;
let time_since = self.last_hit.elapsed().as_secs_f32();
if self.shields != self.outfits.get_shield_strength() {
for g in self.outfits.iter_shield_generators() {
if time_since >= g.delay {
self.shields += g.generation * t;
if self.shields > self.outfits.get_shield_strength() {
self.shields = self.outfits.get_shield_strength();
break;
}
}
} }
} }
} }
@ -208,10 +133,10 @@ impl ShipData {
} }
// Misc getters, so internal state is untouchable // Misc getters, so internal state is untouchable
impl ShipData { impl GxShip {
/// Get this ship's state /// Get a handle to this ship game object
pub fn get_state(&self) -> &ShipState { pub fn get_handle(&self) -> GxShipHandle {
&self.state self.handle
} }
/// Get a handle to this ship's content /// Get a handle to this ship's content

View File

@ -19,10 +19,10 @@ workspace = true
[dependencies] [dependencies]
galactica-content = { workspace = true } galactica-content = { workspace = true }
galactica-util = { workspace = true } galactica-util = { workspace = true }
galactica-galaxy = { workspace = true }
winit = { workspace = true } winit = { workspace = true }
wgpu = { workspace = true } wgpu = { workspace = true }
pollster = { workspace = true } pollster = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
cgmath = { workspace = true } cgmath = { workspace = true }
rapier2d = { workspace = true }

View File

@ -1,12 +1,12 @@
use galactica_content::Content; use galactica_content::Content;
use rapier2d::geometry::ColliderHandle; use galactica_galaxy::GxShipHandle;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus}; use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus};
pub struct PlayerAgent { pub struct PlayerAgent {
/// Which ship this player is controlling /// Which ship this player is controlling
pub ship: Option<ColliderHandle>, pub ship: Option<GxShipHandle>,
/// This player's camera /// This player's camera
pub camera: Camera, pub camera: Camera,
@ -19,7 +19,7 @@ pub struct PlayerAgent {
} }
impl PlayerAgent { impl PlayerAgent {
pub fn new(ship: ColliderHandle) -> Self { pub fn new(ship: GxShipHandle) -> Self {
Self { Self {
input: InputStatus::new(), input: InputStatus::new(),
camera: Camera::new(), camera: Camera::new(),

View File

@ -20,7 +20,8 @@ workspace = true
galactica-content = { workspace = true } galactica-content = { workspace = true }
galactica-util = { workspace = true } galactica-util = { workspace = true }
galactica-packer = { workspace = true } galactica-packer = { workspace = true }
galactica-system = { workspace = true } galactica-systemsim = { workspace = true }
galactica-galaxy = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
cgmath = { workspace = true } cgmath = { workspace = true }

View File

@ -1,6 +1,7 @@
use cgmath::Point2; use cgmath::Point2;
use galactica_content::{Content, SystemHandle}; use galactica_content::{Content, SystemHandle};
use galactica_system::phys::{ParticleBuilder, PhysSim, PhysSimShipHandle}; use galactica_galaxy::{Galaxy, GxShipHandle};
use galactica_systemsim::{ParticleBuilder, SystemSim};
use galactica_util::timing::Timing; use galactica_util::timing::Timing;
use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer}; use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer};
use std::rc::Rc; use std::rc::Rc;
@ -15,7 +16,7 @@ pub struct RenderInput<'a> {
pub camera_pos: Point2<f32>, pub camera_pos: Point2<f32>,
/// Player ship data /// Player ship data
pub player_ship: PhysSimShipHandle, pub player_data: GxShipHandle,
/// The system we're currently in /// The system we're currently in
pub current_system: SystemHandle, pub current_system: SystemHandle,
@ -24,7 +25,7 @@ pub struct RenderInput<'a> {
pub camera_zoom: f32, pub camera_zoom: f32,
/// The world state to render /// The world state to render
pub systemsim: &'a PhysSim, pub systemsim: &'a SystemSim,
// TODO: handle overflow. is it a problem? // TODO: handle overflow. is it a problem?
/// The current time, in seconds /// The current time, in seconds
@ -33,6 +34,9 @@ pub struct RenderInput<'a> {
/// Game content /// Game content
pub ct: &'a Content, pub ct: &'a Content,
/// Game data
pub gx: &'a Galaxy,
/// Particles to spawn during this frame /// Particles to spawn during this frame
pub particles: &'a Vec<ParticleBuilder>, pub particles: &'a Vec<ParticleBuilder>,

View File

@ -10,8 +10,8 @@ use crate::{
/// GPUState is very big, so its methods have been split /// GPUState is very big, so its methods have been split
/// among the following files. /// among the following files.
mod new; mod new;
mod phys;
mod render; mod render;
mod systemsim;
/// A high-level GPU wrapper. Reads game state (via RenderInput), /// A high-level GPU wrapper. Reads game state (via RenderInput),
/// produces pretty pictures. /// produces pretty pictures.

View File

@ -116,9 +116,9 @@ impl super::GPUState {
// Order matters, it determines what is drawn on top. // Order matters, it determines what is drawn on top.
// The order inside ships and projectiles doesn't matter, // The order inside ships and projectiles doesn't matter,
// but ships should always be under projectiles. // but ships should always be under projectiles.
self.phys_push_system(&input, (clip_ne, clip_sw)); self.sysim_push_system(&input, (clip_ne, clip_sw));
self.phys_push_ship(&input, (clip_ne, clip_sw)); self.sysim_push_ship(&input, (clip_ne, clip_sw));
self.phys_push_projectile(&input, (clip_ne, clip_sw)); self.sysim_push_projectile(&input, (clip_ne, clip_sw));
self.ui.draw(&input, &mut self.state); self.ui.draw(&input, &mut self.state);
// These should match the indices in each shader, // These should match the indices in each shader,

View File

@ -2,7 +2,7 @@
use bytemuck; use bytemuck;
use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2}; use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2};
use galactica_system::phys::util; use galactica_systemsim::util;
use galactica_util::constants::OBJECT_SPRITE_INSTANCE_LIMIT; use galactica_util::constants::OBJECT_SPRITE_INSTANCE_LIMIT;
use crate::{ use crate::{
@ -12,18 +12,18 @@ use crate::{
}; };
impl GPUState { impl GPUState {
pub(super) fn phys_push_ship( pub(super) fn sysim_push_ship(
&mut self, &mut self,
state: &RenderInput, state: &RenderInput,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
) { ) {
for ship in state.systemsim.iter_ships() { for s in state.systemsim.iter_ships() {
let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); let r = state.systemsim.get_rigid_body(s.rigid_body).unwrap();
let ship_pos = util::rigidbody_position(&r); let ship_pos = util::rigidbody_position(&r);
let ship_rot = util::rigidbody_rotation(r); let ship_rot = util::rigidbody_rotation(r);
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix! let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
let ship_cnt = state.ct.get_ship(ship.data.get_content()); let ship_cnt = state.ct.get_ship(s.data_handle.content_handle());
// Position adjusted for parallax // Position adjusted for parallax
// TODO: adjust parallax for zoom? // TODO: adjust parallax for zoom?
@ -80,8 +80,16 @@ impl GPUState {
); );
self.state.vertex_buffers.object_counter += 1; self.state.vertex_buffers.object_counter += 1;
let flare = ship.data.get_outfits().get_flare_sprite(state.ct); // This will be None if this ship is dead.
if ship.get_controls().thrust && flare.is_some() && ship.data.get_state().is_flying() { // (physics object stays around to complete the death animation)
// If that is the case, we're done, no flares to draw anyway!
let ship = match state.gx.get_ship(s.data_handle) {
None => continue,
Some(s) => s,
};
let flare = ship.get_outfits().get_flare_sprite(state.ct);
if s.get_controls().thrust && flare.is_some() {
for engine_point in &ship_cnt.engines { for engine_point in &ship_cnt.engines {
self.state.queue.write_buffer( self.state.queue.write_buffer(
&self.state.global_uniform.object_buffer, &self.state.global_uniform.object_buffer,
@ -120,7 +128,7 @@ impl GPUState {
} }
} }
pub(super) fn phys_push_projectile( pub(super) fn sysim_push_projectile(
&mut self, &mut self,
state: &RenderInput, state: &RenderInput,
// NE and SW corners of screen // NE and SW corners of screen
@ -190,7 +198,7 @@ impl GPUState {
} }
} }
pub(super) fn phys_push_system( pub(super) fn sysim_push_system(
&mut self, &mut self,
state: &RenderInput, state: &RenderInput,
// NE and SW corners of screen // NE and SW corners of screen

View File

@ -36,9 +36,9 @@ impl FpsIndicator {
self.buffer.set_text( self.buffer.set_text(
&mut state.text_font_system, &mut state.text_font_system,
&format!( &format!(
"Frame: {:#?} ({:05.00})\nShips: {:05.02}%\nPhys: {:05.02}%\n", "Frame: {:#?} ({:05.00})\nGame: {:05.02}%\nShips: {:05.02}%\nPhys: {:05.02}%\n",
input.timing.frame, input.timing.frame, 1.0 /input.timing.frame.as_secs_f32(),
1.0 / input.timing.frame.as_secs_f32(), 100.0 * (input.timing.galaxy.as_secs_f32() / input.timing.frame.as_secs_f32()),
100.0 * (input.timing.physics_sim.as_secs_f32() / input.timing.frame.as_secs_f32()), 100.0 * (input.timing.physics_sim.as_secs_f32() / input.timing.frame.as_secs_f32()),
100.0 100.0
* (input.timing.physics_ship.as_secs_f32() / input.timing.frame.as_secs_f32()), * (input.timing.physics_ship.as_secs_f32() / input.timing.frame.as_secs_f32()),

View File

@ -1,5 +1,5 @@
use cgmath::{Deg, InnerSpace, Point2, Rad, Vector2}; use cgmath::{Deg, InnerSpace, Point2, Rad, Vector2};
use galactica_system::{data::ShipState, phys::util}; use galactica_systemsim::util;
use galactica_util::constants::UI_SPRITE_INSTANCE_LIMIT; use galactica_util::constants::UI_SPRITE_INSTANCE_LIMIT;
use crate::{ use crate::{
@ -31,17 +31,13 @@ impl Radar {
// TODO: maybe a cleaner solution for last posititon? // TODO: maybe a cleaner solution for last posititon?
// This is necessary because the player may be dead or landed // This is necessary because the player may be dead or landed
let player_ship = input.systemsim.get_ship(&input.player_ship).unwrap(); if let Some(player_world_object) = input.systemsim.get_ship(input.player_data) {
let player_body = input
.systemsim
.get_rigid_body(player_world_object.rigid_body)
.unwrap();
match player_ship.data.get_state() { self.last_player_position = util::rigidbody_position(player_body)
ShipState::Flying | ShipState::Collapsing { .. } => {
let player_body = input
.systemsim
.get_rigid_body(player_ship.rigid_body)
.unwrap();
self.last_player_position = util::rigidbody_position(player_body)
}
}; };
let planet_sprite = input.ct.get_sprite_handle("ui::planetblip"); let planet_sprite = input.ct.get_sprite_handle("ui::planetblip");
@ -119,17 +115,17 @@ impl Radar {
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. // This will be None if this ship is dead.
// Stays around while the physics system runs a collapse sequence // Stays around while the physics system runs a collapse sequence
let color = match s.data.get_state() { let color = match input.gx.get_ship(s.data_handle) {
ShipState::Collapsing { .. } => { None => {
// TODO: configurable // TODO: configurable
[0.2, 0.2, 0.2, 1.0] [0.2, 0.2, 0.2, 1.0]
} }
ShipState::Flying => { Some(data) => {
let c = input.ct.get_faction(s.data.get_faction()).color; let c = input.ct.get_faction(data.get_faction()).color;
[c[0], c[1], c[2], 1.0] [c[0], c[1], c[2], 1.0]
} }
}; };
let ship = input.ct.get_ship(s.data.get_content()); let ship = input.ct.get_ship(s.data_handle.content_handle());
let size = (ship.size * ship.sprite.aspect) * ship_scale; let size = (ship.size * ship.sprite.aspect) * ship_scale;
let p = util::rigidbody_position(r); let p = util::rigidbody_position(r);
let d = (p - self.last_player_position) / radar_range; let d = (p - self.last_player_position) / radar_range;

View File

@ -1,6 +1,5 @@
use std::f32::consts::TAU; use std::f32::consts::TAU;
use galactica_system::data::ShipState;
use galactica_util::constants::{RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT}; use galactica_util::constants::{RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT};
use crate::{ use crate::{
@ -30,16 +29,19 @@ impl Status {
let current_shields; let current_shields;
let current_hull; let current_hull;
let max_hull; let max_hull;
let player_ship = input.systemsim.get_ship(&input.player_ship).unwrap(); if let Some(player_world_object) = input.systemsim.get_ship(input.player_data) {
let data = input.gx.get_ship(player_world_object.data_handle).unwrap();
match player_ship.data.get_state() { max_shields = data.get_outfits().get_shield_strength();
ShipState::Collapsing { .. } | ShipState::Flying => { current_shields = data.get_shields();
max_shields = player_ship.data.get_outfits().get_shield_strength(); current_hull = data.get_hull();
current_shields = player_ship.data.get_shields(); max_hull = input.ct.get_ship(data.get_content()).hull;
current_hull = player_ship.data.get_hull(); } else {
max_hull = input.ct.get_ship(player_ship.data.get_content()).hull; // Sensible defaults if ship doesn't exist (landed or dead)
} max_shields = 1.0;
} current_shields = 0.0;
current_hull = 0.0;
max_hull = 1.0;
};
state.queue.write_buffer( state.queue.write_buffer(
&state.vertex_buffers.ui.instances, &state.vertex_buffers.ui.instances,

View File

@ -1,4 +0,0 @@
//! This module contains data structs shared between all simulations
mod ship;
pub use ship::*;

View File

@ -1,8 +0,0 @@
#![warn(missing_docs)]
//! Galactica's system simulation.
//! data contains shared object data,
//! phys contains the physics system simulation (the one you see)
pub mod data;
pub mod phys;

View File

@ -1,6 +1,6 @@
[package] [package]
name = "galactica-system" name = "galactica-systemsim"
description = "Galactica's star system simulations" description = "Physics interactions for Galactica"
categories = { workspace = true } categories = { workspace = true }
keywords = { workspace = true } keywords = { workspace = true }
version = { workspace = true } version = { workspace = true }
@ -18,6 +18,7 @@ workspace = true
[dependencies] [dependencies]
galactica-content = { workspace = true } galactica-content = { workspace = true }
galactica-galaxy = { workspace = true }
galactica-util = { workspace = true } galactica-util = { workspace = true }
galactica-playeragent = { workspace = true } galactica-playeragent = { workspace = true }

View File

@ -6,12 +6,13 @@ mod point;
use null::*; use null::*;
use point::PointShipController; use point::PointShipController;
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle}; use galactica_galaxy::GxShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use std::{collections::HashMap, fmt::Debug}; use std::{collections::HashMap, fmt::Debug};
use super::{ use crate::{
objects::{PhysSimShip, ShipControls}, objects::{ShipControls, SySimShip},
PhysStepResources, StepResources,
}; };
/// Represents a ship controller /// Represents a ship controller
@ -38,14 +39,15 @@ impl ShipController {
/// Compute new ship controls from world state /// Compute new ship controls from world state
pub fn update_controls( pub fn update_controls(
&mut self, &mut self,
res: &PhysStepResources, res: &StepResources,
rigid_bodies: &RigidBodySet, rigid_bodies: &RigidBodySet,
ships: &HashMap<ColliderHandle, PhysSimShip>, ships: &HashMap<GxShipHandle, SySimShip>,
this_ship: ColliderHandle, this_ship: RigidBodyHandle,
this_data: GxShipHandle,
) -> Option<ShipControls> { ) -> Option<ShipControls> {
match self { match self {
Self::Null(n) => n.update_controls(res, rigid_bodies, ships, this_ship), Self::Null(n) => n.update_controls(res, rigid_bodies, ships, this_ship, this_data),
Self::Point(p) => p.update_controls(res, rigid_bodies, ships, this_ship), Self::Point(p) => p.update_controls(res, rigid_bodies, ships, this_ship, this_data),
} }
} }
} }
@ -61,9 +63,10 @@ where
/// or None if no change is to be made. /// or None if no change is to be made.
fn update_controls( fn update_controls(
&mut self, &mut self,
res: &PhysStepResources, res: &StepResources,
rigid_bodies: &RigidBodySet, rigid_bodies: &RigidBodySet,
ships: &HashMap<ColliderHandle, PhysSimShip>, ships: &HashMap<GxShipHandle, SySimShip>,
this_ship: ColliderHandle, this_ship: RigidBodyHandle,
this_data: GxShipHandle,
) -> Option<ShipControls>; ) -> Option<ShipControls>;
} }

View File

@ -1,12 +1,11 @@
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle}; use galactica_galaxy::GxShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use std::collections::HashMap; use std::collections::HashMap;
use super::{ use super::ShipControllerStruct;
super::{ use crate::{
objects::{PhysSimShip, ShipControls}, objects::{ShipControls, SySimShip},
PhysStepResources, StepResources,
},
ShipControllerStruct,
}; };
/// The Null controller is assigned to objects that are static or not controlled by the computer. /// The Null controller is assigned to objects that are static or not controlled by the computer.
@ -24,10 +23,11 @@ impl NullShipController {
impl ShipControllerStruct for NullShipController { impl ShipControllerStruct for NullShipController {
fn update_controls( fn update_controls(
&mut self, &mut self,
_res: &PhysStepResources, _res: &StepResources,
_rigid_bodies: &RigidBodySet, _rigid_bodies: &RigidBodySet,
_ships: &HashMap<ColliderHandle, PhysSimShip>, _ships: &HashMap<GxShipHandle, SySimShip>,
_this_ship: ColliderHandle, _this_ship: RigidBodyHandle,
_this_data: GxShipHandle,
) -> Option<ShipControls> { ) -> Option<ShipControls> {
None None
} }

View File

@ -1,14 +1,13 @@
use cgmath::{Deg, InnerSpace}; use cgmath::{Deg, InnerSpace};
use galactica_content::Relationship; use galactica_content::Relationship;
use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle}; use galactica_galaxy::GxShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use std::collections::HashMap; use std::collections::HashMap;
use super::{ use super::ShipControllerStruct;
super::{ use crate::{
objects::{PhysSimShip, ShipControls}, objects::{ShipControls, SySimShip},
util, PhysStepResources, util, StepResources,
},
ShipControllerStruct,
}; };
/// "Point" ship controller. /// "Point" ship controller.
@ -26,30 +25,37 @@ impl PointShipController {
impl ShipControllerStruct for PointShipController { impl ShipControllerStruct for PointShipController {
fn update_controls( fn update_controls(
&mut self, &mut self,
res: &PhysStepResources, res: &StepResources,
rigid_bodies: &RigidBodySet, rigid_bodies: &RigidBodySet,
ships: &HashMap<ColliderHandle, PhysSimShip>, ships: &HashMap<GxShipHandle, SySimShip>,
this_ship: ColliderHandle, this_ship: RigidBodyHandle,
this_data: GxShipHandle,
) -> Option<ShipControls> { ) -> Option<ShipControls> {
let mut controls = ShipControls::new(); let mut controls = ShipControls::new();
let my_ship = ships.get(&this_ship).unwrap(); let this_rigidbody = rigid_bodies.get(this_ship).unwrap();
let this_rigidbody = rigid_bodies.get(my_ship.rigid_body).unwrap(); let my_data = res.gx.get_ship(this_data).unwrap();
let my_position = util::rigidbody_position(this_rigidbody); let my_position = util::rigidbody_position(this_rigidbody);
let my_rotation = util::rigidbody_rotation(this_rigidbody); let my_rotation = util::rigidbody_rotation(this_rigidbody);
let my_angvel = this_rigidbody.angvel(); let my_angvel = this_rigidbody.angvel();
let my_faction = res.ct.get_faction(my_ship.data.get_faction()); let my_faction = res.ct.get_faction(my_data.get_faction());
// Iterate all possible targets // Iterate all possible targets
let mut hostile_ships = ships let mut hostile_ships = ships
.values() .values()
.filter( .filter(|s| {
// Target only flying ships we're hostile towards let data = res.gx.get_ship(s.data_handle);
|s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() { if let Some(data) = data {
Relationship::Hostile => s.data.get_state().is_flying(), match my_faction.relationships.get(&data.get_faction()).unwrap() {
_ => false, Relationship::Hostile => true,
}, _ => false,
) }
} else {
// This check is necessary---we don't want to target (or panic on)
// ships that don't have data (and are thus playing their collapse animation)
false
}
})
.map(|s| rigid_bodies.get(s.rigid_body).unwrap()); .map(|s| rigid_bodies.get(s.rigid_body).unwrap());
// Find the closest target // Find the closest target

View File

@ -1,4 +1,6 @@
//! This module provides a physics-based simulation of one system. #![warn(missing_docs)]
//! This module provides a physics-based simulation of one galaxy system.
pub mod controller; pub mod controller;
pub mod objects; pub mod objects;
@ -10,5 +12,5 @@ mod wrapper;
pub use particlebuilder::*; pub use particlebuilder::*;
pub use stepresources::*; pub use stepresources::*;
pub use systemsim::{PhysSim, PhysSimShipHandle}; pub use systemsim::SystemSim;
pub use wrapper::Wrapper; pub use wrapper::Wrapper;

View File

@ -1,37 +1,41 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
use galactica_content::{CollapseEvent, Ship}; use galactica_content::{CollapseEvent, Ship, ShipHandle};
use nalgebra::point; use nalgebra::point;
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
use rapier2d::{dynamics::RigidBody, geometry::Collider}; use rapier2d::{dynamics::RigidBody, geometry::Collider};
use super::super::{util, ParticleBuilder, PhysStepResources}; use crate::{util, ParticleBuilder, StepResources};
use crate::data::ShipData;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(super) struct ShipCollapseSequence { pub(super) struct ShipCollapseSequence {
total_length: f32,
time_elapsed: f32,
rng: ThreadRng, rng: ThreadRng,
/// The elapsed collapse duration when step()
/// was last called
last_call: f32,
} }
impl ShipCollapseSequence { impl ShipCollapseSequence {
pub(super) fn new() -> Self { pub(super) fn new(total_length: f32) -> Self {
Self { Self {
total_length,
time_elapsed: 0.0,
rng: rand::thread_rng(), rng: rand::thread_rng(),
last_call: 0.0,
} }
} }
/// Has this sequence been fully played out?
pub(super) fn is_done(&self) -> bool {
self.time_elapsed >= self.total_length
}
/// Pick a random points inside a ship's collider /// Pick a random points inside a ship's collider
fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2<f32> { fn random_in_ship(&mut self, ship_content: &Ship, collider: &Collider) -> Vector2<f32> {
let mut y = 0.0; let mut y = 0.0;
let mut x = 0.0; let mut x = 0.0;
let mut a = false; let mut a = false;
while !a { while !a {
y = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0; y = self.rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
x = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0; x = self.rng.gen_range(-1.0..=1.0) * ship_content.size * ship_content.sprite.aspect
/ 2.0;
a = collider.shape().contains_local_point(&point![x, y]); a = collider.shape().contains_local_point(&point![x, y]);
} }
Vector2 { x, y } Vector2 { x, y }
@ -40,32 +44,25 @@ impl ShipCollapseSequence {
/// Step this sequence `t` seconds /// Step this sequence `t` seconds
pub(super) fn step( pub(super) fn step(
&mut self, &mut self,
res: &mut PhysStepResources, res: &mut StepResources,
ship_data: &ShipData, ship_handle: ShipHandle,
rigid_body: &mut RigidBody, rigid_body: &mut RigidBody,
collider: &mut Collider, collider: &mut Collider,
) { ) {
let ship_content = res.ct.get_ship(ship_data.get_content()); let ship_content = res.ct.get_ship(ship_handle);
let ship_pos = util::rigidbody_position(rigid_body); let ship_pos = util::rigidbody_position(rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body); let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 }); let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
let (total, elapsed) = ship_data.get_state().collapse_state().unwrap();
// How much time has passed since step() was last called
let delta = elapsed - self.last_call;
// The fraction of this collapse sequence that has been played // The fraction of this collapse sequence that has been played
let frac_done = elapsed / total; let frac_done = self.time_elapsed / self.total_length;
// TODO: slight random offset for event particles
// Trigger collapse events // Trigger collapse events
for event in &ship_content.collapse.events { for event in &ship_content.collapse.events {
match event { match event {
CollapseEvent::Effect(event) => { CollapseEvent::Effect(event) => {
if (event.time > self.last_call && event.time <= elapsed) if (event.time > self.time_elapsed && event.time <= self.time_elapsed + res.t)
|| (event.time == 0.0 && self.last_call == 0.0) || (event.time == 0.0 && self.time_elapsed == 0.0)
// ^^ Don't miss events scheduled at the very start of the sequence!
{ {
for spawner in &event.effects { for spawner in &event.effects {
let effect = res.ct.get_effect(spawner.effect); let effect = res.ct.get_effect(spawner.effect);
@ -115,9 +112,7 @@ impl ShipCollapseSequence {
return y; return y;
}; };
// Notice that we don't use res.t here, since ship state is updated earlier let p_add = (res.t / self.total_length) * pdf(frac_done) * spawner.count;
// ( through self.data.step() )
let p_add = (delta / total) * pdf(frac_done) * spawner.count;
if self.rng.gen_range(0.0..=1.0) <= p_add { if self.rng.gen_range(0.0..=1.0) <= p_add {
let pos = if let Some(pos) = spawner.pos { let pos = if let Some(pos) = spawner.pos {
@ -143,6 +138,6 @@ impl ShipCollapseSequence {
} }
} }
self.last_call = elapsed; self.time_elapsed += res.t;
} }
} }

View File

@ -3,5 +3,5 @@ mod collapse;
mod projectile; mod projectile;
mod ship; mod ship;
pub use projectile::PhysProjectile; pub use projectile::SySimProjectile;
pub use ship::{PhysSimShip, ShipControls}; pub use ship::{ShipControls, SySimShip};

View File

@ -2,9 +2,9 @@ use galactica_content::{FactionHandle, Projectile};
use rand::Rng; use rand::Rng;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
/// A single projectile in this simulation /// A single projectile in this sim
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PhysProjectile { pub struct SySimProjectile {
/// This projectile's game data /// This projectile's game data
pub content: Projectile, pub content: Projectile,
@ -24,7 +24,7 @@ pub struct PhysProjectile {
pub size_rng: f32, pub size_rng: f32,
} }
impl PhysProjectile { impl SySimProjectile {
/// Create a new projectile /// Create a new projectile
pub fn new( pub fn new(
content: Projectile, // TODO: use a handle content: Projectile, // TODO: use a handle
@ -35,7 +35,7 @@ impl PhysProjectile {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let size_rng = content.size_rng; let size_rng = content.size_rng;
let lifetime = content.lifetime; let lifetime = content.lifetime;
PhysProjectile { SySimProjectile {
rigid_body, rigid_body,
collider, collider,
content, content,

View File

@ -1,5 +1,6 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
use galactica_content::{Content, FactionHandle, ShipHandle}; use galactica_content::{Content, FactionHandle};
use galactica_galaxy::GxShipHandle;
use nalgebra::{point, vector}; use nalgebra::{point, vector};
use rand::Rng; use rand::Rng;
use rapier2d::{ use rapier2d::{
@ -7,12 +8,9 @@ use rapier2d::{
geometry::{Collider, ColliderHandle}, geometry::{Collider, ColliderHandle},
}; };
use crate::data::{ShipData, ShipPersonality, ShipState}; use crate::{util, ParticleBuilder, StepResources};
use super::{ use super::collapse::ShipCollapseSequence;
super::{util, ParticleBuilder, PhysStepResources},
collapse::ShipCollapseSequence,
};
/// A ship's controls /// A ship's controls
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -44,7 +42,7 @@ impl ShipControls {
/// A ship instance in the physics system /// A ship instance in the physics system
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PhysSimShip { pub struct SySimShip {
/// This ship's physics handle /// This ship's physics handle
pub rigid_body: RigidBodyHandle, pub rigid_body: RigidBodyHandle,
@ -52,70 +50,83 @@ pub struct PhysSimShip {
pub collider: ColliderHandle, pub collider: ColliderHandle,
/// This ship's game data /// This ship's game data
pub data: ShipData, pub data_handle: GxShipHandle,
/// This ship's controls /// This ship's controls
pub(crate) controls: ShipControls, pub(crate) controls: ShipControls,
/// This ship's collapse sequence /// This ship's collapse sequence
collapse_sequence: Option<ShipCollapseSequence>, collapse_sequence: ShipCollapseSequence,
/// This ship's faction.
/// This is technically redundant, faction is also stored in
/// game data, but that's destroyed once the ship dies.
/// We need the faction for the collapse sequence!
faction: FactionHandle,
} }
impl PhysSimShip { impl SySimShip {
/// Make a new ship /// Make a new ship
pub(crate) fn new( pub(crate) fn new(
ct: &Content, ct: &Content,
handle: ShipHandle, data_handle: GxShipHandle,
personality: ShipPersonality,
faction: FactionHandle, faction: FactionHandle,
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
collider: ColliderHandle, collider: ColliderHandle,
) -> Self { ) -> Self {
PhysSimShip { let ship_content = ct.get_ship(data_handle.content_handle());
SySimShip {
rigid_body, rigid_body,
collider, collider,
data: ShipData::new(ct, handle, faction, personality), data_handle,
controls: ShipControls::new(), controls: ShipControls::new(),
collapse_sequence: Some(ShipCollapseSequence::new()), faction,
collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length),
} }
} }
/// If this is true, remove this ship from the physics system.
pub fn should_be_removed(&self) -> bool {
self.collapse_sequence.is_done()
}
/// Step this ship's state by t seconds /// Step this ship's state by t seconds
pub fn step( pub fn step(
&mut self, &mut self,
res: &mut PhysStepResources, res: &mut StepResources,
rigid_body: &mut RigidBody, rigid_body: &mut RigidBody,
collider: &mut Collider, collider: &mut Collider,
) { ) {
self.data.step(res.t); let ship_data = res.gx.get_ship(self.data_handle);
match self.data.get_state() { if ship_data.is_none() {
ShipState::Collapsing { .. } => { // If ship data is none, it has been removed because the ship has been destroyed.
// Borrow checker hack, so we may pass self.data // play collapse sequence.
// to the collapse sequence self.collapse_sequence.step(
let mut seq = self.collapse_sequence.take().unwrap(); res,
seq.step(res, &self.data, rigid_body, collider); self.data_handle.content_handle(),
self.collapse_sequence = Some(seq); rigid_body,
} collider,
ShipState::Flying => { );
return self.step_live(res, rigid_body, collider); } else {
} return self.step_live(res, rigid_body, collider);
} }
} }
/// Step this ship's state by t seconds (called when alive) /// Step this ship's state by t seconds (called when alive)
fn step_live( fn step_live(
&mut self, &mut self,
res: &mut PhysStepResources, res: &mut StepResources,
rigid_body: &mut RigidBody, rigid_body: &mut RigidBody,
collider: &mut Collider, collider: &mut Collider,
) { ) {
let ship_content = res.ct.get_ship(self.data.get_content()); let ship = res.gx.get_ship(self.data_handle).unwrap();
let ship_content = res.ct.get_ship(self.data_handle.content_handle());
let ship_pos = util::rigidbody_position(&rigid_body); let ship_pos = util::rigidbody_position(&rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body); let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 }); let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
if self.data.get_hull() <= ship_content.damage.hull { if ship.get_hull() <= ship_content.damage.hull {
for e in &ship_content.damage.effects { for e in &ship_content.damage.effects {
if rng.gen_range(0.0..=1.0) <= res.t / e.frequency { if rng.gen_range(0.0..=1.0) <= res.t / e.frequency {
let effect = res.ct.get_effect(e.effect); let effect = res.ct.get_effect(e.effect);
@ -160,31 +171,31 @@ impl PhysSimShip {
if self.controls.thrust { if self.controls.thrust {
rigid_body.apply_impulse( rigid_body.apply_impulse(
vector![engine_force.x, engine_force.y] vector![engine_force.x, engine_force.y] * ship.get_outfits().get_engine_thrust(),
* self.data.get_outfits().get_engine_thrust(),
true, true,
); );
} }
if self.controls.right { if self.controls.right {
rigid_body.apply_torque_impulse( rigid_body
self.data.get_outfits().get_steer_power() * -100.0 * res.t, .apply_torque_impulse(ship.get_outfits().get_steer_power() * -100.0 * res.t, true);
true,
);
} }
if self.controls.left { if self.controls.left {
rigid_body.apply_torque_impulse( rigid_body
self.data.get_outfits().get_steer_power() * 100.0 * res.t, .apply_torque_impulse(ship.get_outfits().get_steer_power() * 100.0 * res.t, true);
true,
);
} }
} }
} }
impl PhysSimShip { impl SySimShip {
/// Get this ship's control state /// Get this ship's control state
pub fn get_controls(&self) -> &ShipControls { pub fn get_controls(&self) -> &ShipControls {
&self.controls &self.controls
} }
/// Get this ship's faction
pub fn get_faction(&self) -> FactionHandle {
self.faction
}
} }

View File

@ -1,12 +1,16 @@
use super::{wrapper::Wrapper, ParticleBuilder}; use crate::{wrapper::Wrapper, ParticleBuilder};
use galactica_content::Content; use galactica_content::Content;
use galactica_galaxy::Galaxy;
use galactica_util::timing::Timing; use galactica_util::timing::Timing;
/// External resources we need to compute time steps /// External resources we need to compute time steps
pub struct PhysStepResources<'a> { pub struct StepResources<'a> {
/// Game content /// Game content
pub ct: &'a Content, pub ct: &'a Content,
/// Game data
pub gx: &'a mut Galaxy,
/// Length of time step /// Length of time step
pub t: f32, pub t: f32,

View File

@ -1,7 +1,10 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero};
use galactica_content::{ use galactica_content::{
Content, FactionHandle, GunPoint, OutfitHandle, ProjectileCollider, Relationship, ShipHandle, Content, GunPoint, OutfitHandle, ProjectileCollider, Relationship, SystemHandle,
SystemHandle, };
use galactica_galaxy::{
ship::{GxShip, ShipPersonality},
Galaxy, GxShipHandle,
}; };
use galactica_playeragent::PlayerAgent; use galactica_playeragent::PlayerAgent;
use nalgebra::{point, vector}; use nalgebra::{point, vector};
@ -13,41 +16,36 @@ use rapier2d::{
}; };
use std::{collections::HashMap, f32::consts::PI}; use std::{collections::HashMap, f32::consts::PI};
use crate::data::ShipPersonality; use crate::{
use super::{
controller::ShipController, controller::ShipController,
objects::{PhysProjectile, PhysSimShip}, objects::{SySimProjectile, SySimShip},
util, ParticleBuilder, PhysStepResources, util, ParticleBuilder, StepResources,
}; };
// 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 /// Manages the physics state of one system
#[derive(Clone)] #[derive(Clone)]
pub struct PhysSim { pub struct SystemSim {
/// The system this sim is attached to /// The system this sim is attached to
_system: SystemHandle, _system: SystemHandle,
rigid_body_set: RigidBodySet, rigid_body_set: RigidBodySet,
collider_set: ColliderSet, collider_set: ColliderSet,
projectiles: HashMap<ColliderHandle, PhysProjectile>, projectiles: HashMap<ColliderHandle, SySimProjectile>,
ships: HashMap<ColliderHandle, PhysSimShip>, ships: HashMap<GxShipHandle, SySimShip>,
ship_behaviors: HashMap<ColliderHandle, ShipController>,
// TODO: these don't need to be cloned each frame
ship_behaviors: HashMap<GxShipHandle, ShipController>,
collider_ship_table: HashMap<ColliderHandle, GxShipHandle>,
} }
// Private methods // Private methods
impl<'a> PhysSim { impl<'a> SystemSim {
fn remove_projectile( fn remove_projectile(
&mut self, &mut self,
res: &mut PhysStepResources, res: &mut StepResources,
c: ColliderHandle, c: ColliderHandle,
) -> Option<(RigidBody, PhysProjectile)> { ) -> Option<(RigidBody, SySimProjectile)> {
let p = match self.projectiles.remove(&c) { let p = match self.projectiles.remove(&c) {
Some(p) => p, Some(p) => p,
None => return None, None => return None,
@ -67,32 +65,38 @@ impl<'a> PhysSim {
return Some((r, p)); return Some((r, p));
} }
fn remove_ship(&mut self, res: &mut PhysStepResources, colliderhandle: ColliderHandle) { fn remove_ship(&mut self, res: &mut StepResources, s: ColliderHandle) {
let ship = match self.ships.get(&colliderhandle) { let s = match self.collider_ship_table.get(&s) {
None => return, None => return,
Some(s) => s, Some(s) => match self.ships.get(s) {
None => return,
Some(s) => s,
},
}; };
self.rigid_body_set.remove( self.rigid_body_set.remove(
ship.rigid_body, s.rigid_body,
&mut res.wrapper.im, &mut res.wrapper.im,
&mut self.collider_set, &mut self.collider_set,
&mut res.wrapper.ij, &mut res.wrapper.ij,
&mut res.wrapper.mj, &mut res.wrapper.mj,
true, true,
); );
self.ships.remove(&colliderhandle).unwrap(); let h = self.collider_ship_table.remove(&s.collider).unwrap();
self.ship_behaviors.remove(&colliderhandle); self.ship_behaviors.remove(&h);
self.ships.remove(&h);
} }
fn collide_projectile_ship( fn collide_projectile_ship(
&mut self, &mut self,
res: &mut PhysStepResources, res: &mut StepResources,
projectile_h: ColliderHandle, projectile_h: ColliderHandle,
ship_h: ColliderHandle, ship_h: ColliderHandle,
) { ) {
let projectile = self.projectiles.get(&projectile_h); let projectile = self.projectiles.get(&projectile_h);
let ship = self.ships.get_mut(&ship_h); let ship = self
.ships
.get_mut(self.collider_ship_table.get(&ship_h).unwrap());
if projectile.is_none() || ship.is_none() { if projectile.is_none() || ship.is_none() {
return; return;
} }
@ -100,10 +104,13 @@ impl<'a> PhysSim {
let ship = ship.unwrap(); let ship = ship.unwrap();
let f = res.ct.get_faction(projectile.faction); let f = res.ct.get_faction(projectile.faction);
let r = f.relationships.get(&ship.data.get_faction()).unwrap(); let r = f.relationships.get(&ship.get_faction()).unwrap();
let destory_projectile = match r { let destory_projectile = match r {
Relationship::Hostile => { Relationship::Hostile => {
ship.data.apply_damage(res.ct, projectile.content.damage); // We only apply damage if the target ship is alive
if let Some(ship_d) = res.gx.get_ship_mut(ship.data_handle) {
ship_d.apply_damage(projectile.content.damage);
}
true true
} }
_ => false, _ => false,
@ -152,29 +159,40 @@ impl<'a> PhysSim {
} }
// Public methods // Public methods
impl PhysSim { impl SystemSim {
/// Create a new physics system /// Create a new physics system
pub fn new(_ct: &Content, system: SystemHandle) -> Self { pub fn new(ct: &Content, gx: &Galaxy, system: SystemHandle) -> Self {
Self { let mut w = Self {
_system: system, _system: system,
rigid_body_set: RigidBodySet::new(), rigid_body_set: RigidBodySet::new(),
collider_set: ColliderSet::new(), collider_set: ColliderSet::new(),
projectiles: HashMap::new(), projectiles: HashMap::new(),
ships: HashMap::new(), ships: HashMap::new(),
ship_behaviors: HashMap::new(), ship_behaviors: HashMap::new(),
collider_ship_table: HashMap::new(),
};
// TODO: guarantee not touching
// TODO: add, remove ships each tick
// Maybe store position in galaxy crate?
let mut rng = rand::thread_rng();
for s in gx.iter_ships() {
w.add_ship(
ct,
s,
Point2 {
x: rng.gen_range(-500.0..=500.0),
y: rng.gen_range(-500.0..=500.0),
},
);
} }
return w;
} }
/// Add a ship to this physics system /// Add a ship to this physics system
pub fn add_ship( pub fn add_ship(&mut self, ct: &Content, ship: &GxShip, position: Point2<f32>) {
&mut self, let ship_content = ct.get_ship(ship.get_content());
ct: &Content,
handle: ShipHandle,
faction: FactionHandle,
personality: ShipPersonality,
position: Point2<f32>,
) -> PhysSimShipHandle {
let ship_content = ct.get_ship(handle);
let cl = ColliderBuilder::convex_decomposition( let cl = ColliderBuilder::convex_decomposition(
&ship_content.collision.points[..], &ship_content.collision.points[..],
&ship_content.collision.indices[..], &ship_content.collision.indices[..],
@ -191,25 +209,25 @@ impl PhysSim {
.translation(vector![position.x, position.y]) .translation(vector![position.x, position.y])
.can_sleep(false); .can_sleep(false);
let ridid_body = self.rigid_body_set.insert(rb.build()); let r = self.rigid_body_set.insert(rb.build());
let collider = let c = self
self.collider_set .collider_set
.insert_with_parent(cl.build(), ridid_body, &mut self.rigid_body_set); .insert_with_parent(cl.build(), r, &mut self.rigid_body_set);
self.collider_ship_table.insert(c, ship.get_handle());
self.ship_behaviors.insert( self.ship_behaviors.insert(
collider, ship.get_handle(),
match personality { match ship.get_personality() {
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(), ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
ShipPersonality::Point => ShipController::new_point(), ShipPersonality::Point => ShipController::new_point(),
}, },
); );
self.ships.insert( self.ships.insert(
collider, ship.get_handle(),
PhysSimShip::new(ct, handle, personality, faction, ridid_body, collider), SySimShip::new(ct, ship.get_handle(), ship.get_faction(), r, c),
); );
return PhysSimShipHandle(collider);
} }
/// Update a player ship's controls /// Update a player ship's controls
@ -230,65 +248,75 @@ impl PhysSim {
/// - updates ship controls (runs behaviors) /// - updates ship controls (runs behaviors)
/// - applies ship controls /// - applies ship controls
/// - creates projectiles /// - creates projectiles
fn step_ships(&mut self, res: &mut PhysStepResources) { fn step_ships(&mut self, res: &mut StepResources) {
// We can't apply these right away since self is borrowed // We can't apply these right away since self is borrowed
// by the iterator // by the iterator
// TODO: don't allocate! // TODO: don't allocate!
let mut projectiles = Vec::new(); let mut projectiles = Vec::new();
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
// Again, borrow checker hack. TODO: fix for (_, handle) in &self.collider_ship_table {
let keys: Vec<_> = self.ships.keys().map(|c| *c).collect();
for collider in keys {
// Borrow immutably for now... // Borrow immutably for now...
// (required to compute controls) // (required to compute controls)
let ship = self.ships.get(&collider).unwrap(); let ship_object = self.ships.get(handle).unwrap();
if ship.data.get_state().should_be_removed() { if ship_object.should_be_removed() {
to_remove.push(collider); to_remove.push(ship_object.collider);
continue; continue;
} }
// This ship is playing a collapse sequence // Short-circuit continue if this ship isn't in game data
// and needs no additional logic // (which means it's playing a collapse sequence)
if ship.data.get_state().is_collapsing() { if res.gx.get_ship(*handle).is_none() {
let ship = self.ships.get_mut(&collider).unwrap(); let ship_object = self.ships.get_mut(handle).unwrap();
ship.step( ship_object.step(
res, res,
&mut self.rigid_body_set[ship.rigid_body], &mut self.rigid_body_set[ship_object.rigid_body],
&mut self.collider_set[ship.collider], &mut self.collider_set[ship_object.collider],
); );
continue; continue;
} }
// Compute new controls // Compute new controls
let controls; let controls;
let b = self.ship_behaviors.get_mut(&collider).unwrap(); let b = self.ship_behaviors.get_mut(handle).unwrap();
controls = b.update_controls(&res, &self.rigid_body_set, &self.ships, ship.collider); controls = b.update_controls(
&res,
let ship = self.ships.get_mut(&collider).unwrap(); &self.rigid_body_set,
&self.ships,
ship_object.rigid_body,
*handle,
);
// Now re-borrow mutably to apply changes
let ship_object = self.ships.get_mut(handle).unwrap();
if let Some(controls) = controls { if let Some(controls) = controls {
ship.controls = controls; ship_object.controls = controls;
} }
ship.step( ship_object.step(
res, res,
&mut self.rigid_body_set[ship.rigid_body], &mut self.rigid_body_set[ship_object.rigid_body],
&mut self.collider_set[ship.collider], &mut self.collider_set[ship_object.collider],
); );
// If we're firing, try to fire each gun // If we're firing, try to fire each gun
if ship.controls.guns { if ship_object.controls.guns {
let ship_data = res.gx.get_ship_mut(ship_object.data_handle).unwrap();
// TODO: don't allocate here. This is a hack to satisfy the borrow checker, // TODO: don't allocate here. This is a hack to satisfy the borrow checker,
// convert this to a refcell or do the replace dance. // convert this to a refcell or do the replace dance.
let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = ship let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = ship_data
.data
.get_outfits() .get_outfits()
.iter_gun_points() .iter_gun_points()
.map(|(p, o)| (p.clone(), o.clone())) .map(|(p, o)| (p.clone(), o.clone()))
.collect(); .collect();
for (gun, outfit) in pairs { for (gun, outfit) in pairs {
if ship.data.fire_gun(res.ct, &gun) { if ship_data.fire_gun(res.ct, &gun) {
projectiles.push((ship.collider, gun.clone(), outfit.unwrap())); projectiles.push((
ship_object.data_handle,
ship_object.rigid_body,
gun.clone(),
outfit.unwrap(),
));
} }
} }
} }
@ -300,11 +328,11 @@ impl PhysSim {
} }
// Create projectiles // Create projectiles
for (collider, gun_point, outfit) in projectiles { for (ship_dat, rigid_body, gun_point, outfit) in projectiles {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let rigid_body = self.get_rigid_body(rigid_body).unwrap();
let ship = self.ships.get(&collider).unwrap(); let ship_dat = res.gx.get_ship(ship_dat).unwrap();
let rigid_body = self.get_rigid_body(ship.rigid_body).unwrap();
let ship_pos = util::rigidbody_position(rigid_body); let ship_pos = util::rigidbody_position(rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body); let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_vel = util::rigidbody_velocity(rigid_body); let ship_vel = util::rigidbody_velocity(rigid_body);
@ -351,10 +379,10 @@ impl PhysSim {
self.projectiles.insert( self.projectiles.insert(
collider.clone(), collider.clone(),
PhysProjectile::new( SySimProjectile::new(
outfit.projectile.clone(), outfit.projectile.clone(),
rigid_body, rigid_body,
ship.data.get_faction(), ship_dat.get_faction(),
collider, collider,
), ),
); );
@ -362,7 +390,7 @@ impl PhysSim {
} }
/// Step this physics system by `t` seconds /// Step this physics system by `t` seconds
pub fn step(&mut self, mut res: PhysStepResources) { pub fn step(&mut self, mut res: StepResources) {
res.timing.start_physics_ships(); res.timing.start_physics_ships();
self.step_ships(&mut res); self.step_ships(&mut res);
res.timing.mark_physics_ships(); res.timing.mark_physics_ships();
@ -388,7 +416,11 @@ impl PhysSim {
}; };
let p = self.projectiles.get(&a); let p = self.projectiles.get(&a);
let s = self.ships.get_mut(&b); let s = self.ships.get_mut(match self.collider_ship_table.get(&b) {
// If none, this ship is dead.
Some(x) => x,
None => continue,
});
if p.is_none() || s.is_none() { if p.is_none() || s.is_none() {
continue; continue;
} }
@ -442,15 +474,10 @@ impl PhysSim {
} }
// Public getters // Public getters
impl PhysSim { impl SystemSim {
/// Get a ship physics object /// Get a ship physics object
pub fn get_ship(&self, ship: &PhysSimShipHandle) -> Option<&PhysSimShip> { pub fn get_ship(&self, ship: GxShipHandle) -> Option<&SySimShip> {
self.ships.get(&ship.0) self.ships.get(&ship)
}
/// 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 /// Get a rigid body from a handle
@ -464,19 +491,19 @@ impl PhysSim {
} }
/// Iterate over all ships in this physics system /// Iterate over all ships in this physics system
pub fn iter_ship_body(&self) -> impl Iterator<Item = (&PhysSimShip, &RigidBody)> + '_ { pub fn iter_ship_body(&self) -> impl Iterator<Item = (&SySimShip, &RigidBody)> + '_ {
self.ships self.ships
.values() .values()
.map(|x| (x, self.rigid_body_set.get(x.rigid_body).unwrap())) .map(|x| (x, self.rigid_body_set.get(x.rigid_body).unwrap()))
} }
/// Iterate over all ships in this physics system /// Iterate over all ships in this physics system
pub fn iter_ships(&self) -> impl Iterator<Item = &PhysSimShip> + '_ { pub fn iter_ships(&self) -> impl Iterator<Item = &SySimShip> + '_ {
self.ships.values() self.ships.values()
} }
/// Iterate over all ships in this physics system /// Iterate over all ships in this physics system
pub fn iter_projectiles(&self) -> impl Iterator<Item = &PhysProjectile> + '_ { pub fn iter_projectiles(&self) -> impl Iterator<Item = &SySimProjectile> + '_ {
self.projectiles.values() self.projectiles.values()
} }
} }

View File

@ -9,6 +9,10 @@ pub struct Timing {
pub frame: Duration, pub frame: Duration,
frame_timer: Instant, frame_timer: Instant,
/// The time we spent simulating game state
pub galaxy: Duration,
galaxy_timer: Instant,
/// The time we spent simulating physics /// The time we spent simulating physics
pub physics_sim: Duration, pub physics_sim: Duration,
physics_sim_timer: Instant, physics_sim_timer: Instant,
@ -25,8 +29,10 @@ impl Timing {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
frame: Duration::ZERO, frame: Duration::ZERO,
galaxy: Duration::ZERO,
physics_sim: Duration::ZERO, physics_sim: Duration::ZERO,
physics_ship: Duration::ZERO, physics_ship: Duration::ZERO,
galaxy_timer: Instant::now(),
physics_sim_timer: Instant::now(), physics_sim_timer: Instant::now(),
physics_ship_timer: Instant::now(), physics_ship_timer: Instant::now(),
frame_timer: Instant::now(), frame_timer: Instant::now(),
@ -38,6 +44,11 @@ impl Timing {
self.frame_timer = Instant::now(); self.frame_timer = Instant::now();
} }
/// Start galaxy timer
pub fn start_galaxy(&mut self) {
self.galaxy_timer = Instant::now();
}
/// Start physics sim timer /// Start physics sim timer
pub fn start_physics_sim(&mut self) { pub fn start_physics_sim(&mut self) {
self.physics_sim_timer = Instant::now(); self.physics_sim_timer = Instant::now();
@ -48,11 +59,16 @@ impl Timing {
self.physics_ship_timer = Instant::now(); self.physics_ship_timer = Instant::now();
} }
/// Record total frame compute time /// Record galaxy simulation time.
pub fn mark_frame(&mut self) { pub fn mark_frame(&mut self) {
self.frame = self.frame_timer.elapsed(); self.frame = self.frame_timer.elapsed();
} }
/// Record galaxy simulation time.
pub fn mark_galaxy(&mut self) {
self.galaxy = self.galaxy_timer.elapsed();
}
/// Record physics simulation time /// Record physics simulation time
pub fn mark_physics_sim(&mut self) { pub fn mark_physics_sim(&mut self) {
self.physics_sim = self.physics_sim_timer.elapsed(); self.physics_sim = self.physics_sim_timer.elapsed();