More cleanup

master
Mark 2024-01-01 15:41:47 -08:00
parent b309a074dc
commit bcf10e416b
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
30 changed files with 693 additions and 614 deletions

21
Cargo.lock generated
View File

@ -582,9 +582,10 @@ dependencies = [
"crossbeam", "crossbeam",
"galactica-constants", "galactica-constants",
"galactica-content", "galactica-content",
"galactica-physics", "galactica-gameobject",
"galactica-render", "galactica-render",
"galactica-shipbehavior", "galactica-shipbehavior",
"galactica-world",
"image", "image",
"nalgebra", "nalgebra",
"pollster", "pollster",
@ -613,7 +614,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "galactica-physics" name = "galactica-gameobject"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"cgmath", "cgmath",
@ -646,7 +647,21 @@ version = "0.0.0"
dependencies = [ dependencies = [
"cgmath", "cgmath",
"galactica-content", "galactica-content",
"galactica-physics", "galactica-world",
]
[[package]]
name = "galactica-world"
version = "0.0.0"
dependencies = [
"cgmath",
"crossbeam",
"galactica-content",
"galactica-gameobject",
"galactica-render",
"nalgebra",
"rand",
"rapier2d",
] ]
[[package]] [[package]]

View File

@ -32,8 +32,9 @@ members = [
"crates/content", "crates/content",
"crates/render", "crates/render",
"crates/constants", "crates/constants",
"crates/physics", "crates/world",
"crates/shipbehavior", "crates/shipbehavior",
"crates/gameobject",
] ]
@ -42,8 +43,9 @@ members = [
galactica-content = { path = "crates/content" } galactica-content = { path = "crates/content" }
galactica-render = { path = "crates/render" } galactica-render = { path = "crates/render" }
galactica-constants = { path = "crates/constants" } galactica-constants = { path = "crates/constants" }
galactica-physics = { path = "crates/physics" } galactica-world = { path = "crates/world" }
galactica-shipbehavior = { path = "crates/shipbehavior" } galactica-shipbehavior = { path = "crates/shipbehavior" }
galactica-gameobject = { path = "crates/gameobject" }
# Files # Files
image = { version = "0.24", features = ["png"] } image = { version = "0.24", features = ["png"] }

View File

@ -2,10 +2,6 @@
space.weapon = 10 space.weapon = 10
# Angle of fire cone
# Smaller angle = more accurate
spread = 2
# Average delay between shots # Average delay between shots
rate = 0.2 rate = 0.2
# Random rate variation (+- this in both directions) # Random rate variation (+- this in both directions)
@ -23,3 +19,8 @@ projectile.speed_rng = 10.0
projectile.lifetime = 2.0 projectile.lifetime = 2.0
projectile.lifetime_rng = 0.2 projectile.lifetime_rng = 0.2
projectile.damage = 10.0 projectile.damage = 10.0
# Random variation of firing angle.
# If this is 0, the projectile always goes straight.
# If this is 10, projectiles will form a cone of 10 degrees
# ( 5 degrees on each side )
projectile.angle_rng = 2

View File

@ -1,6 +1,13 @@
//! This module defines lightweight handles for every content type.
//! This isn't strictly necessary (we could refer to them using their string keys),
//! but this approach doesn't require frequent string cloning, and
//! gives each content type a distinct Rust type.
//!
//! We could also use raw references to content types, but that creates a mess of lifetimes
//! in our code. It's managable, but the approach here is simpler and easier to understand.
use std::{cmp::Eq, hash::Hash}; use std::{cmp::Eq, hash::Hash};
/// Represents a specific texture defined in the content dir. /// A lightweight representation of a
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct TextureHandle { pub struct TextureHandle {
/// The index of this texture in content.textures /// The index of this texture in content.textures
@ -23,11 +30,63 @@ impl PartialEq for TextureHandle {
} }
} }
/// A lightweight representation of an outfit
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutfitHandle {
/// TODO
pub index: usize,
}
impl Hash for OutfitHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
/// A lightweight representation of a gun
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GunHandle {
/// TODO
pub index: usize,
}
impl Hash for GunHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
/// A lightweight representation of a ship
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ShipHandle {
/// TODO
pub index: usize,
}
impl Hash for ShipHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
/// A lightweight representation of a star system
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SystemHandle {
/// TODO
pub index: usize,
}
impl Hash for SystemHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
/// A lightweight representation of a faction /// A lightweight representation of a faction
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FactionHandle { pub struct FactionHandle {
/// The index of this faction in content.factions /// The index of this faction in content.factions
/// TODO: pub in crate, currently for debug. /// TODO: pub in crate, currently for debug (same with all other handles)
pub index: usize, pub index: usize,
} }

View File

@ -17,15 +17,16 @@ use std::{
use toml; use toml;
use walkdir::WalkDir; use walkdir::WalkDir;
pub use handle::{FactionHandle, TextureHandle}; pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SystemHandle, TextureHandle};
pub use part::{ pub use part::{
EnginePoint, Faction, Gun, GunPoint, Outfit, OutfitSpace, Relationship, Ship, System, Texture, EnginePoint, Faction, Gun, GunPoint, Outfit, OutfitSpace, Projectile, Relationship, Ship,
System, Texture,
}; };
mod syntax { mod syntax {
use anyhow::{bail, Result}; use anyhow::{bail, Context, Result};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::{collections::HashMap, fmt::Display, hash::Hash};
use crate::part::{faction, gun, outfit, ship, system, texture}; use crate::part::{faction, gun, outfit, ship, system, texture};
@ -39,6 +40,31 @@ mod syntax {
pub faction: Option<HashMap<String, faction::syntax::Faction>>, pub faction: Option<HashMap<String, faction::syntax::Faction>>,
} }
fn merge_hashmap<K, V>(
to: &mut Option<HashMap<K, V>>,
mut from: Option<HashMap<K, V>>,
) -> Result<()>
where
K: Hash + Eq + Display,
{
if to.is_none() {
*to = from.take();
return Ok(());
}
if let Some(from_inner) = from {
let to_inner = to.as_mut().unwrap();
for (k, v) in from_inner {
if to_inner.contains_key(&k) {
bail!("Found duplicate key `{k}`");
} else {
to_inner.insert(k, v);
}
}
}
return Ok(());
}
impl Root { impl Root {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
@ -52,98 +78,16 @@ mod syntax {
} }
pub fn merge(&mut self, other: Root) -> Result<()> { pub fn merge(&mut self, other: Root) -> Result<()> {
// Insert if not exists merge_hashmap(&mut self.gun, other.gun).with_context(|| "while merging guns")?;
// TODO: replace with a macro and try_insert once that is stable merge_hashmap(&mut self.ship, other.ship).with_context(|| "while merging ships")?;
if let Some(a) = other.gun { merge_hashmap(&mut self.system, other.system)
if self.gun.is_none() { .with_context(|| "while merging systems")?;
self.gun = Some(a); merge_hashmap(&mut self.outfit, other.outfit)
} else { .with_context(|| "while merging outfits")?;
let sg = self.gun.as_mut().unwrap(); merge_hashmap(&mut self.texture, other.texture)
for (k, v) in a { .with_context(|| "while merging textures")?;
if sg.contains_key(&k) { merge_hashmap(&mut self.faction, other.faction)
bail!("Repeated gun name {k}"); .with_context(|| "while merging factions")?;
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.ship {
if self.ship.is_none() {
self.ship = Some(a);
} else {
let sg = self.ship.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated ship name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.system {
if self.system.is_none() {
self.system = Some(a);
} else {
let sg = self.system.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated system name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.outfit {
if self.outfit.is_none() {
self.outfit = Some(a);
} else {
let sg = self.outfit.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated engine name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.texture {
if self.texture.is_none() {
self.texture = Some(a);
} else {
let sg = self.texture.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated texture name {k}");
} else {
sg.insert(k, v);
}
}
}
}
if let Some(a) = other.faction {
if self.faction.is_none() {
self.faction = Some(a);
} else {
let sg = self.faction.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated faction name {k}");
} else {
sg.insert(k, v);
}
}
}
}
return Ok(()); return Ok(());
} }
} }
@ -161,38 +105,35 @@ trait Build {
/// Represents static game content /// Represents static game content
#[derive(Debug)] #[derive(Debug)]
pub struct Content { pub struct Content {
/// Star systems /* Configuration values */
pub systems: Vec<part::system::System>, /// Root directory for textures
texture_root: PathBuf,
/// Ship bodies /// Name of starfield texture
pub ships: Vec<part::ship::Ship>, starfield_texture_name: String,
/// Ship guns
pub guns: Vec<part::gun::Gun>,
/// Outfits
pub outfits: Vec<part::outfit::Outfit>,
/// Textures /// Textures
pub textures: Vec<part::texture::Texture>, pub textures: Vec<part::texture::Texture>,
/// Factions
pub factions: Vec<part::faction::Faction>,
/// Map strings to texture handles
/// This is never used outside this crate.
texture_index: HashMap<String, handle::TextureHandle>, texture_index: HashMap<String, handle::TextureHandle>,
/// The texture to use for starfield stars /// The texture to use for starfield stars
starfield_handle: Option<handle::TextureHandle>, starfield_handle: Option<handle::TextureHandle>,
/// Root directory for textures /// Outfits
texture_root: PathBuf, outfits: Vec<part::outfit::Outfit>,
/// Name of starfield texture /// Ship guns
starfield_texture_name: String, guns: Vec<part::gun::Gun>,
/// Ship bodies
ships: Vec<part::ship::Ship>,
/// Star systems
pub systems: Vec<part::system::System>,
/// Factions
factions: Vec<part::faction::Faction>,
} }
// Loading methods
impl Content { impl Content {
fn try_parse(path: &Path) -> Result<syntax::Root> { fn try_parse(path: &Path) -> Result<syntax::Root> {
let mut file_string = String::new(); let mut file_string = String::new();
@ -201,32 +142,6 @@ impl Content {
return Ok(toml::from_str(&file_string)?); return Ok(toml::from_str(&file_string)?);
} }
/// Get the texture handle for the starfield texture
pub fn get_starfield_handle(&self) -> TextureHandle {
match self.starfield_handle {
Some(h) => h,
None => unreachable!("Starfield texture hasn't been loaded yet!"),
}
}
/// Get a texture from a handle
pub fn get_texture_handle(&self, name: &str) -> TextureHandle {
return *self.texture_index.get(name).unwrap();
// TODO: handle errors
}
/// Get a texture from a handle
pub fn get_texture(&self, h: TextureHandle) -> &Texture {
// In theory, this could fail if h has a bad index, but that shouldn't ever happen.
// The only TextureHandles that exist should be created by this crate.
return &self.textures[h.index];
}
/// Get a faction from a handle
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
return &self.factions[h.index];
}
/// Load content from a directory. /// Load content from a directory.
pub fn load_dir( pub fn load_dir(
path: PathBuf, path: PathBuf,
@ -296,3 +211,54 @@ impl Content {
return Ok(content); return Ok(content);
} }
} }
// Access methods
impl Content {
/// Get the texture handle for the starfield texture
pub fn get_starfield_handle(&self) -> TextureHandle {
match self.starfield_handle {
Some(h) => h,
None => unreachable!("Starfield texture hasn't been loaded yet!"),
}
}
/// Get a handle from a texture name
pub fn get_texture_handle(&self, name: &str) -> TextureHandle {
return match self.texture_index.get(name) {
Some(s) => *s,
None => unreachable!("get_texture_handle was called with a bad handle!"),
};
}
/// Get a texture from a handle
pub fn get_texture(&self, h: TextureHandle) -> &Texture {
// In theory, this could fail if h has a bad index, but that shouldn't ever happen.
// The only TextureHandles that exist should be created by this crate.
return &self.textures[h.index];
}
/// Get an outfit from a handle
pub fn get_outfit(&self, h: OutfitHandle) -> &Outfit {
return &self.outfits[h.index];
}
/// Get a gun from a handle
pub fn get_gun(&self, h: GunHandle) -> &Gun {
return &self.guns[h.index];
}
/// Get a texture from a handle
pub fn get_ship(&self, h: ShipHandle) -> &Ship {
return &self.ships[h.index];
}
/// Get a system from a handle
pub fn get_system(&self, h: SystemHandle) -> &System {
return &self.systems[h.index];
}
/// Get a faction from a handle
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
return &self.factions[h.index];
}
}

View File

@ -16,7 +16,6 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Gun { pub struct Gun {
pub projectile: Projectile, pub projectile: Projectile,
pub spread: f32,
pub rate: f32, pub rate: f32,
pub rate_rng: f32, pub rate_rng: f32,
pub space: shared::syntax::OutfitSpace, pub space: shared::syntax::OutfitSpace,
@ -32,6 +31,7 @@ pub(crate) mod syntax {
pub lifetime: f32, pub lifetime: f32,
pub lifetime_rng: f32, pub lifetime_rng: f32,
pub damage: f32, pub damage: f32,
pub angle_rng: f32,
} }
} }
@ -44,13 +44,6 @@ pub struct Gun {
/// The projectile this gun produces /// The projectile this gun produces
pub projectile: Projectile, pub projectile: Projectile,
/// The accuracy of this gun.
/// Projectiles can be off center up to
/// `spread / 2` degrees in both directions.
///
/// (Forming a "fire cone" of `spread` degrees)
pub spread: Deg<f32>,
/// Average delay between projectiles, in seconds. /// Average delay between projectiles, in seconds.
pub rate: f32, pub rate: f32,
@ -87,6 +80,13 @@ pub struct Projectile {
/// The damage this projectile does /// The damage this projectile does
pub damage: f32, pub damage: f32,
/// The angle variation of this projectile.
/// Projectiles can be off center up to
/// `spread / 2` degrees in both directions.
///
/// (Forming a "fire cone" of `spread` degrees)
pub angle_rng: Deg<f32>,
} }
impl crate::Build for Gun { impl crate::Build for Gun {
@ -106,7 +106,6 @@ impl crate::Build for Gun {
ct.guns.push(Self { ct.guns.push(Self {
name: gun_name, name: gun_name,
space: gun.space.into(), space: gun.space.into(),
spread: Deg(gun.spread),
rate: gun.rate, rate: gun.rate,
rate_rng: gun.rate_rng, rate_rng: gun.rate_rng,
projectile: Projectile { projectile: Projectile {
@ -118,6 +117,7 @@ impl crate::Build for Gun {
lifetime: gun.projectile.lifetime, lifetime: gun.projectile.lifetime,
lifetime_rng: gun.projectile.lifetime_rng, lifetime_rng: gun.projectile.lifetime_rng,
damage: gun.projectile.damage, damage: gun.projectile.damage,
angle_rng: Deg(gun.projectile.angle_rng),
}, },
}); });
} }

View File

@ -35,7 +35,7 @@ impl OutfitSpace {
} }
/// Does this outfit contain `smaller`? /// Does this outfit contain `smaller`?
pub fn can_contain(&self, smaller: Self) -> bool { pub fn can_contain(&self, smaller: &Self) -> bool {
self.outfit >= (smaller.outfit + smaller.weapon + smaller.engine) self.outfit >= (smaller.outfit + smaller.weapon + smaller.engine)
&& self.weapon >= smaller.weapon && self.weapon >= smaller.weapon
&& self.engine >= smaller.engine && self.engine >= smaller.engine

View File

@ -1,5 +1,5 @@
[package] [package]
name = "galactica-physics" name = "galactica-gameobject"
version = "0.0.0" version = "0.0.0"
edition = "2021" edition = "2021"

View File

@ -0,0 +1,13 @@
//! This module handles all game data: ship damage, outfit stats, etc.
//!
//! The code here handles game *data* exclusively: it keeps track of the status
//! of every ship in the game, but it has no understanding of physics.
//! That is done in `galactica_world`.
mod outfits;
mod projectile;
mod ship;
pub use outfits::{OutfitSet, OutfitStatSum, ShipGun};
pub use projectile::Projectile;
pub use ship::Ship;

View File

@ -17,9 +17,9 @@ pub struct ShipGun {
impl ShipGun { impl ShipGun {
/// Make a new shipgun /// Make a new shipgun
pub fn new(kind: content::Gun, point: usize) -> Self { pub fn new(kind: &content::Gun, point: usize) -> Self {
Self { Self {
kind: kind, kind: kind.clone(),
point, point,
cooldown: 0.0, cooldown: 0.0,
} }
@ -67,19 +67,17 @@ impl OutfitStatSum {
} }
} }
/// Represents all the outfits attached to a ship. /// Represents a set of outfits attached to a ship.
/// Keeps track of the sum of their stats, so it mustn't be re-computed every frame. /// Keeps track of the sum of their stats, so it mustn't be re-computed every frame.
/// TODO: rename (outfitset?)
/// TODO: ctrl-f outfitset in comments
#[derive(Debug)] #[derive(Debug)]
pub struct ShipOutfits { pub struct OutfitSet {
/// The total sum of the stats this set of outfits provides /// The total sum of the stats this set of outfits provides
/// TODO: this shouldn't be pub /// TODO: this shouldn't be pub
pub stats: OutfitStatSum, pub stats: OutfitStatSum,
//pub total_space: content::OutfitSpace, //pub total_space: content::OutfitSpace,
available_space: content::OutfitSpace, available_space: content::OutfitSpace,
outfits: Vec<content::Outfit>, outfits: Vec<content::OutfitHandle>,
guns: Vec<ShipGun>, guns: Vec<ShipGun>,
enginepoints: Vec<content::EnginePoint>, enginepoints: Vec<content::EnginePoint>,
gunpoints: Vec<content::GunPoint>, gunpoints: Vec<content::GunPoint>,
@ -89,7 +87,7 @@ pub struct ShipOutfits {
engine_flare_sprites: Vec<ObjectSubSprite>, engine_flare_sprites: Vec<ObjectSubSprite>,
} }
impl<'a> ShipOutfits { impl<'a> OutfitSet {
/// Make a new outfit array /// Make a new outfit array
pub fn new(content: &content::Ship) -> Self { pub fn new(content: &content::Ship) -> Self {
Self { Self {
@ -105,8 +103,8 @@ impl<'a> ShipOutfits {
} }
/// Does this outfit set contain the specified outfit? /// Does this outfit set contain the specified outfit?
pub fn contains_outfit(&self, o: &content::Outfit) -> bool { pub fn contains_outfit(&self, o: content::OutfitHandle) -> bool {
match self.outfits.iter().position(|x| x.name == o.name) { match self.outfits.iter().position(|x| *x == o) {
Some(_) => true, Some(_) => true,
None => false, None => false,
} }
@ -115,33 +113,35 @@ impl<'a> ShipOutfits {
/// Add an outfit to this ship. /// Add an outfit to this ship.
/// Returns true on success, and false on failure /// Returns true on success, and false on failure
/// TODO: failure reason enum /// TODO: failure reason enum
pub fn add(&mut self, o: content::Outfit) -> bool { pub fn add(&mut self, ct: &content::Content, o: content::OutfitHandle) -> bool {
if !self.available_space.can_contain(o.space) { let outfit = ct.get_outfit(o);
if !self.available_space.can_contain(&outfit.space) {
return false; return false;
} }
self.available_space.occupy(&o.space); self.available_space.occupy(&outfit.space);
self.stats.add(&o); self.stats.add(&outfit);
self.outfits.push(o); self.outfits.push(o);
self.update_engine_flares(); self.update_engine_flares();
return true; return true;
} }
/// TODO: is outfit in set? /// Remove an outfit from this set
pub fn remove(&mut self, o: content::Outfit) { pub fn remove(&mut self, ct: &content::Content, o: content::OutfitHandle) {
let i = match self.outfits.iter().position(|x| x.name == o.name) { let outfit = ct.get_outfit(o);
let i = match self.outfits.iter().position(|x| *x == o) {
Some(i) => i, Some(i) => i,
None => panic!("Removed non-existing outfit"), None => panic!("removed non-existing outfit"),
}; };
self.available_space.free(&o.space); self.available_space.free(&outfit.space);
self.outfits.remove(i); self.outfits.remove(i);
self.stats.remove(&o); self.stats.remove(&outfit);
self.update_engine_flares(); self.update_engine_flares();
} }
/// Add a gun to this outfit set. /// Add a gun to this outfit set.
/// This automatically attaches the gun to the first available gunpoint, /// This automatically attaches the gun to the first available gunpoint,
/// and returns false (applying no changes) if no points are available. /// and returns false (applying no changes) if no points are available.
pub fn add_gun(&mut self, gun: content::Gun) -> bool { pub fn add_gun(&mut self, ct: &content::Content, g: content::GunHandle) -> bool {
// Find first unused point // Find first unused point
let mut p = None; let mut p = None;
'outer: for i in 0..self.gunpoints.len() { 'outer: for i in 0..self.gunpoints.len() {
@ -153,6 +153,7 @@ impl<'a> ShipOutfits {
p = Some(i); p = Some(i);
break; break;
} }
let gun = ct.get_gun(g);
// All points are taken // All points are taken
if p.is_none() { if p.is_none() {

View File

@ -0,0 +1,18 @@
use galactica_content as content;
#[derive(Debug)]
pub struct Projectile {
pub content: content::Projectile,
pub lifetime: f32,
pub faction: content::FactionHandle,
}
impl Projectile {
pub fn tick(&mut self, t: f32) {
self.lifetime -= t;
}
pub fn is_expired(&self) -> bool {
return self.lifetime < 0.0;
}
}

View File

@ -0,0 +1,68 @@
use rand::Rng;
use crate::{OutfitSet, Projectile};
use galactica_content as content;
#[derive(Debug)]
pub struct Ship {
pub handle: content::ShipHandle,
pub faction: content::FactionHandle,
pub outfits: OutfitSet,
// TODO: unified ship stats struct, like space
// TODO: unified outfit stats struct: check
pub hull: f32,
}
impl Ship {
pub fn new(
ct: &content::Content,
handle: content::ShipHandle,
faction: content::FactionHandle,
outfits: OutfitSet,
) -> Self {
let s = ct.get_ship(handle);
Ship {
handle: handle,
faction,
outfits,
hull: s.hull,
}
}
pub fn handle_projectile_collision(&mut self, ct: &content::Content, p: &Projectile) -> bool {
let f = ct.get_faction(self.faction);
let r = f.relationships.get(&p.faction).unwrap();
match r {
content::Relationship::Hostile => {
// TODO: implement death and spawning, and enable damage
//s.hull -= p.damage;
return true;
}
_ => return false,
}
}
pub fn fire_guns(&mut self) -> Vec<(Projectile, content::GunPoint)> {
self.outfits
.iter_guns_points()
.filter(|(g, _)| g.cooldown > 0.0)
.map(|(g, p)| {
let mut rng = rand::thread_rng();
g.cooldown = g.kind.rate + &rng.gen_range(-g.kind.rate_rng..=g.kind.rate_rng);
(
Projectile {
content: g.kind.projectile.clone(),
lifetime: g.kind.projectile.lifetime
+ rng.gen_range(
-g.kind.projectile.lifetime_rng..=g.kind.projectile.lifetime_rng,
),
faction: self.faction,
},
p.clone(),
)
})
.collect()
}
}

View File

@ -1,18 +0,0 @@
#![warn(missing_docs)]
//! This module keeps track of all objects and interactions in the game:
//! Ships, projectiles, collisions, etc.
//! TODO: Rename & re-organize
pub mod objects;
mod physics;
pub mod util;
mod wrapper;
pub use physics::Physics;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
/// A lightweight handle for a specific ship in a physics system
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ShipHandle(pub RigidBodyHandle, ColliderHandle);

View File

@ -1,9 +0,0 @@
//! This module contains game objects that may interact with the physics engine.
mod outfits;
mod projectile;
mod ship;
pub use outfits::{ShipGun, ShipOutfits};
pub use projectile::{Projectile, ProjectileBuilder};
pub use ship::Ship;

View File

@ -1,105 +0,0 @@
use cgmath::{Deg, InnerSpace, Point3, Vector2};
use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
geometry::{ColliderBuilder, ColliderHandle},
};
use crate::util;
use galactica_content as content;
use galactica_render::ObjectSprite;
/// A helper struct that contains parameters for a projectile
/// TODO: decouple data from physics
pub struct ProjectileBuilder {
/// TODO
pub rigid_body: RigidBodyBuilder,
/// TODO
pub collider: ColliderBuilder,
/// TODO
pub sprite_texture: content::TextureHandle,
/// TODO
pub lifetime: f32,
/// TODO
pub size: f32,
/// TODO
pub damage: f32,
/// TODO
pub faction: content::FactionHandle,
}
/// A single projectile in the world
#[derive(Debug)]
pub struct Projectile {
/// TODO
pub rigid_body: RigidBodyHandle,
/// TODO
pub collider: ColliderHandle,
/// TODO
pub sprite_texture: content::TextureHandle,
/// TODO
pub lifetime: f32,
/// TODOd
pub size: f32,
/// TODO
pub damage: f32,
/// TODO
pub faction: content::FactionHandle,
}
impl Projectile {
/// Make a new projectile
pub fn new(b: ProjectileBuilder, r: RigidBodyHandle, c: ColliderHandle) -> Self {
Projectile {
rigid_body: r,
collider: c,
sprite_texture: b.sprite_texture,
lifetime: b.lifetime,
size: b.size,
damage: b.damage,
faction: b.faction,
}
}
/// Update this projectile's state after `t` seconds
pub fn tick(&mut self, t: f32) {
self.lifetime -= t;
}
/// Is this projectile expired?
pub fn is_expired(&self) -> bool {
return self.lifetime < 0.0;
}
/// Get this projectiles' sprite
pub fn get_sprite(&self, r: &RigidBody) -> ObjectSprite {
let pos = util::rigidbody_position(r);
let rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees
let ang: Deg<f32> = rot.angle(Vector2 { x: 1.0, y: 0.0 }).into();
ObjectSprite {
texture: self.sprite_texture,
pos: Point3 {
x: pos.x,
y: pos.y,
z: 1.0,
},
size: self.size,
angle: -ang,
children: None,
}
}
}

View File

@ -1,189 +0,0 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2};
use content::{FactionHandle, TextureHandle};
use nalgebra::vector;
use rand::Rng;
use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder},
geometry::ColliderBuilder,
pipeline::ActiveEvents,
};
use super::{ProjectileBuilder, ShipOutfits};
use crate::{util, ShipHandle};
use galactica_content as content;
use galactica_render::ObjectSprite;
pub struct ShipTickResult {
pub projectiles: Vec<ProjectileBuilder>,
}
pub struct ShipControls {
pub left: bool,
pub right: bool,
pub thrust: bool,
pub guns: bool,
}
impl ShipControls {
pub fn new() -> Self {
ShipControls {
left: false,
right: false,
thrust: false,
guns: false,
}
}
}
/// A ship instance in the physics system
/// TODO: Decouple ship data from physics
pub struct Ship {
/// TODO
pub physics_handle: ShipHandle,
/// TODO
pub faction: FactionHandle,
/// TODO
pub hull: f32,
/// TODO
pub controls: ShipControls,
outfits: ShipOutfits,
sprite_texture: TextureHandle,
size: f32,
}
impl Ship {
/// Make a new ship
pub fn new(
c: &content::Ship,
outfits: ShipOutfits,
physics_handle: ShipHandle,
faction: FactionHandle,
) -> Self {
Ship {
physics_handle,
outfits,
sprite_texture: c.sprite_texture,
size: c.size,
hull: c.hull,
controls: ShipControls::new(),
faction,
}
}
/// Fire this ship's guns. Called every frame, if this ship is firing.
pub fn fire_guns(&mut self, r: &RigidBody) -> Vec<ProjectileBuilder> {
let mut rng = rand::thread_rng();
let mut out = Vec::new();
for (g, p) in self.outfits.iter_guns_points() {
if g.cooldown > 0.0 {
continue;
}
g.cooldown = g.kind.rate + rng.gen_range(-g.kind.rate_rng..=g.kind.rate_rng);
let ship_pos = util::rigidbody_position(r);
let ship_rot = util::rigidbody_rotation(r);
let ship_vel = util::rigidbody_velocity(r);
let ship_ang = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 });
let pos = ship_pos + (Matrix2::from_angle(-ship_ang) * p.pos.to_vec());
let spread: Rad<f32> =
Deg(rng.gen_range(-(g.kind.spread.0 / 2.0)..=g.kind.spread.0 / 2.0)).into();
let vel = ship_vel
+ (Matrix2::from_angle(-ship_ang + spread)
* Vector2 {
x: 0.0,
y: g.kind.projectile.speed
+ rng.gen_range(
-g.kind.projectile.speed_rng..=g.kind.projectile.speed_rng,
),
});
let p_r = RigidBodyBuilder::kinematic_velocity_based()
.translation(vector![pos.x, pos.y])
.rotation(-ship_ang.0)
.linvel(vector![vel.x, vel.y]);
let p_c = ColliderBuilder::ball(5.0)
.sensor(true)
.active_events(ActiveEvents::COLLISION_EVENTS);
out.push(ProjectileBuilder {
rigid_body: p_r,
collider: p_c,
sprite_texture: g.kind.projectile.sprite_texture,
lifetime: g.kind.projectile.lifetime
+ rng.gen_range(
-g.kind.projectile.lifetime_rng..=g.kind.projectile.lifetime_rng,
),
size: g.kind.projectile.size
+ rng.gen_range(-g.kind.projectile.size_rng..=g.kind.projectile.size_rng),
damage: g.kind.projectile.damage, // TODO: kind as param to builder
faction: self.faction,
})
}
return out;
}
/// Apply the effects of all active controls
pub fn apply_controls(&mut self, r: &mut RigidBody, t: f32) -> ShipTickResult {
let ship_rot = util::rigidbody_rotation(r);
let engine_force = ship_rot * t;
if self.controls.thrust {
r.apply_impulse(
vector![engine_force.x, engine_force.y] * self.outfits.stats.engine_thrust,
true,
);
}
if self.controls.right {
r.apply_torque_impulse(self.outfits.stats.steer_power * -100.0 * t, true);
}
if self.controls.left {
r.apply_torque_impulse(self.outfits.stats.steer_power * 100.0 * t, true);
}
let p = if self.controls.guns {
self.fire_guns(r)
} else {
Vec::new()
};
for i in self.outfits.iter_guns() {
i.cooldown -= t;
}
return ShipTickResult { projectiles: p };
}
/// Get this ship's sprite
pub fn get_sprite(&self, r: &RigidBody) -> ObjectSprite {
let ship_pos = util::rigidbody_position(r);
let ship_rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees
let ship_ang: Deg<f32> = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }).into();
ObjectSprite {
pos: (ship_pos.x, ship_pos.y, 1.0).into(),
texture: self.sprite_texture,
angle: -ship_ang,
size: self.size,
children: if self.controls.thrust {
Some(self.outfits.get_engine_flares())
} else {
None
},
}
}
}

View File

@ -5,5 +5,5 @@ edition = "2021"
[dependencies] [dependencies]
galactica-content = { path = "../content" } galactica-content = { path = "../content" }
galactica-physics = { path = "../physics" } galactica-world = { path = "../world" }
cgmath = "0.18.0" cgmath = "0.18.0"

View File

@ -1,23 +1,23 @@
use crate::ShipBehavior; use crate::ShipBehavior;
use galactica_content as content; use galactica_content as content;
use galactica_physics::{Physics, ShipHandle}; use galactica_world::{ShipPhysicsHandle, World};
/// Dummy ship behavior. /// Dummy ship behavior.
/// Does nothing. /// Does nothing.
pub struct Dummy { pub struct Dummy {
_handle: ShipHandle, _handle: ShipPhysicsHandle,
} }
impl Dummy { impl Dummy {
/// Create a new ship controller /// Create a new ship controller
pub fn new(handle: ShipHandle) -> Box<Self> { pub fn new(handle: ShipPhysicsHandle) -> Box<Self> {
Box::new(Self { _handle: handle }) Box::new(Self { _handle: handle })
} }
} }
impl ShipBehavior for Dummy { impl ShipBehavior for Dummy {
fn update_controls(&mut self, _physics: &mut Physics, _content: &content::Content) {} fn update_controls(&mut self, _physics: &mut World, _content: &content::Content) {}
fn get_handle(&self) -> ShipHandle { fn get_handle(&self) -> ShipPhysicsHandle {
return self._handle; return self._handle;
} }
} }

View File

@ -1,11 +1,11 @@
use crate::ShipBehavior; use crate::ShipBehavior;
use galactica_content as content; use galactica_content as content;
use galactica_physics::{Physics, ShipHandle}; use galactica_world::{ShipPhysicsHandle, World};
/// Player ship behavior. /// Player ship behavior.
/// Controls a ship using controller input /// Controls a ship using controller input
pub struct Player { pub struct Player {
handle: ShipHandle, handle: ShipPhysicsHandle,
key_left: bool, key_left: bool,
key_right: bool, key_right: bool,
key_guns: bool, key_guns: bool,
@ -14,7 +14,7 @@ pub struct Player {
impl Player { impl Player {
/// Make a new ship controller /// Make a new ship controller
pub fn new(handle: ShipHandle) -> Box<Self> { pub fn new(handle: ShipPhysicsHandle) -> Box<Self> {
Box::new(Self { Box::new(Self {
handle, handle,
key_left: false, key_left: false,
@ -26,7 +26,7 @@ impl Player {
} }
impl ShipBehavior for Player { impl ShipBehavior for Player {
fn update_controls(&mut self, physics: &mut Physics, _content: &content::Content) { fn update_controls(&mut self, physics: &mut World, _content: &content::Content) {
let s = physics.get_ship_mut(&self.handle).unwrap(); let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = self.key_left; s.controls.left = self.key_left;
s.controls.right = self.key_right; s.controls.right = self.key_right;
@ -34,7 +34,7 @@ impl ShipBehavior for Player {
s.controls.thrust = self.key_thrust; s.controls.thrust = self.key_thrust;
} }
fn get_handle(&self) -> ShipHandle { fn get_handle(&self) -> ShipPhysicsHandle {
return self.handle; return self.handle;
} }
} }

View File

@ -2,23 +2,23 @@ use cgmath::{Deg, InnerSpace};
use crate::ShipBehavior; use crate::ShipBehavior;
use galactica_content as content; use galactica_content as content;
use galactica_physics::{util, Physics, ShipHandle}; use galactica_world::{util, ShipPhysicsHandle, World};
/// "Point" ship behavior. /// "Point" ship behavior.
/// Point and shoot towards the nearest enemy. /// Point and shoot towards the nearest enemy.
pub struct Point { pub struct Point {
handle: ShipHandle, handle: ShipPhysicsHandle,
} }
impl Point { impl Point {
/// Create a new ship controller /// Create a new ship controller
pub fn new(handle: ShipHandle) -> Box<Self> { pub fn new(handle: ShipPhysicsHandle) -> Box<Self> {
Box::new(Self { handle }) Box::new(Self { handle })
} }
} }
impl ShipBehavior for Point { impl ShipBehavior for Point {
fn update_controls(&mut self, physics: &mut Physics, content: &content::Content) { fn update_controls(&mut self, physics: &mut World, content: &content::Content) {
// Turn off all controls // Turn off all controls
let s = physics.get_ship_mut(&self.handle).unwrap(); let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = false; s.controls.left = false;
@ -30,12 +30,12 @@ impl ShipBehavior for Point {
let my_position = util::rigidbody_position(my_r); let my_position = util::rigidbody_position(my_r);
let my_rotation = util::rigidbody_rotation(my_r); let my_rotation = util::rigidbody_rotation(my_r);
let my_angvel = my_r.angvel(); let my_angvel = my_r.angvel();
let my_faction = content.get_faction(my_s.faction); let my_faction = content.get_faction(my_s.ship.faction);
// Iterate all possible targets // Iterate all possible targets
let mut it = physics let mut it = physics
.iter_ship_body() .iter_ship_body()
.filter(|(s, _)| match my_faction.relationships[&s.faction] { .filter(|(s, _)| match my_faction.relationships[&s.ship.faction] {
content::Relationship::Hostile => true, content::Relationship::Hostile => true,
_ => false, _ => false,
}) })
@ -74,7 +74,7 @@ impl ShipBehavior for Point {
s.controls.thrust = false; s.controls.thrust = false;
} }
fn get_handle(&self) -> ShipHandle { fn get_handle(&self) -> ShipPhysicsHandle {
return self.handle; return self.handle;
} }
} }

View File

@ -5,7 +5,7 @@
pub mod behavior; pub mod behavior;
use galactica_content as content; use galactica_content as content;
use galactica_physics::{Physics, ShipHandle}; use galactica_world::{ShipPhysicsHandle, World};
/// Main behavior trait. Any struct that implements this /// Main behavior trait. Any struct that implements this
/// may be used to control a ship. /// may be used to control a ship.
@ -14,8 +14,8 @@ where
Self: Send, Self: Send,
{ {
/// Update a ship's controls based on world state /// Update a ship's controls based on world state
fn update_controls(&mut self, physics: &mut Physics, content: &content::Content); fn update_controls(&mut self, physics: &mut World, content: &content::Content);
/// Get the ship this behavior is attached to /// Get the ship this behavior is attached to
fn get_handle(&self) -> ShipHandle; fn get_handle(&self) -> ShipPhysicsHandle;
} }

15
crates/world/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "galactica-world"
version = "0.0.0"
edition = "2021"
[dependencies]
galactica-render = { path = "../render" }
galactica-content = { path = "../content" }
galactica-gameobject = { path = "../gameobject" }
rapier2d = { version = "0.17.2" }
nalgebra = "0.32.3"
crossbeam = "0.8.3"
cgmath = "0.18.0"
rand = "0.8.5"

17
crates/world/src/lib.rs Normal file
View File

@ -0,0 +1,17 @@
#![warn(missing_docs)]
//! This module keeps track of the visible world.
//! Ships, projectiles, collisions, etc.
pub mod objects;
pub mod util;
mod world;
mod wrapper;
pub use world::World;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
/// A lightweight handle for a specific ship in our physics system
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ShipPhysicsHandle(pub RigidBodyHandle, ColliderHandle);

View File

@ -0,0 +1,6 @@
//! This module contains game objects that may interact with the physics engine.
mod projectile;
mod ship;
pub use projectile::ProjectileWorldObject;
pub use ship::ShipWorldObject;

View File

@ -0,0 +1,58 @@
use cgmath::{Deg, InnerSpace, Point3, Vector2};
use rapier2d::{
dynamics::{RigidBody, RigidBodyHandle},
geometry::ColliderHandle,
};
use crate::util;
use galactica_gameobject as object;
use galactica_render::ObjectSprite;
/// A single projectile in the world
#[derive(Debug)]
pub struct ProjectileWorldObject {
/// TODO
pub projectile: object::Projectile,
/// TODO
pub rigid_body: RigidBodyHandle,
/// TODO
pub collider: ColliderHandle,
}
impl ProjectileWorldObject {
/// Make a new projectile
pub fn new(
projectile: object::Projectile,
rigid_body: RigidBodyHandle,
collider: ColliderHandle,
) -> Self {
ProjectileWorldObject {
rigid_body,
collider,
projectile,
}
}
/// Get this projectiles' sprite
pub fn get_sprite(&self, r: &RigidBody) -> ObjectSprite {
let pos = util::rigidbody_position(r);
let rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees
let ang: Deg<f32> = rot.angle(Vector2 { x: 1.0, y: 0.0 }).into();
ObjectSprite {
texture: self.projectile.content.sprite_texture,
pos: Point3 {
x: pos.x,
y: pos.y,
z: 1.0,
},
size: self.projectile.content.size,
angle: -ang,
children: None,
}
}
}

View File

@ -0,0 +1,100 @@
use cgmath::{Deg, InnerSpace, Vector2};
use nalgebra::vector;
use rapier2d::dynamics::RigidBody;
use crate::{util, ShipPhysicsHandle};
use galactica_content as content;
use galactica_gameobject as object;
use galactica_render::ObjectSprite;
pub struct ShipControls {
pub left: bool,
pub right: bool,
pub thrust: bool,
pub guns: bool,
}
impl ShipControls {
pub fn new() -> Self {
ShipControls {
left: false,
right: false,
thrust: false,
guns: false,
}
}
}
/// A ship instance in the physics system
/// TODO: Decouple ship data from physics
pub struct ShipWorldObject {
/// TODO
pub physics_handle: ShipPhysicsHandle,
/// TODO
pub ship: object::Ship,
/// TODO
pub controls: ShipControls,
}
impl ShipWorldObject {
/// Make a new ship
pub fn new(ship: object::Ship, physics_handle: ShipPhysicsHandle) -> Self {
ShipWorldObject {
physics_handle,
ship,
controls: ShipControls::new(),
}
}
/// Apply the effects of all active controls
pub fn tick(&mut self, r: &mut RigidBody, t: f32) {
let ship_rot = util::rigidbody_rotation(r);
let engine_force = ship_rot * t;
if self.controls.thrust {
r.apply_impulse(
vector![engine_force.x, engine_force.y] * self.ship.outfits.stats.engine_thrust,
true,
);
}
if self.controls.right {
r.apply_torque_impulse(self.ship.outfits.stats.steer_power * -100.0 * t, true);
}
if self.controls.left {
r.apply_torque_impulse(self.ship.outfits.stats.steer_power * 100.0 * t, true);
}
for i in self.ship.outfits.iter_guns() {
i.cooldown -= t;
}
}
/// Get this ship's sprite
pub fn get_sprite(&self, ct: &content::Content, r: &RigidBody) -> ObjectSprite {
let ship_pos = util::rigidbody_position(r);
let ship_rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees
let ship_ang: Deg<f32> = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }).into();
let s = ct.get_ship(self.ship.handle);
ObjectSprite {
pos: (ship_pos.x, ship_pos.y, 1.0).into(),
texture: s.sprite_texture,
angle: -ship_ang,
size: s.size,
children: if self.controls.thrust {
Some(self.ship.outfits.get_engine_flares())
} else {
None
},
}
}
}

View File

@ -1,35 +1,32 @@
use cgmath::Point2; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2};
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use nalgebra::vector; use nalgebra::vector;
use rand::Rng;
use rapier2d::{ use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
geometry::{ColliderBuilder, ColliderHandle, CollisionEvent}, geometry::{ColliderBuilder, ColliderHandle, CollisionEvent},
pipeline::ChannelEventCollector, pipeline::{ActiveEvents, ChannelEventCollector},
}; };
use std::{collections::HashMap, f32::consts::PI}; use std::{collections::HashMap, f32::consts::PI};
use crate::{ use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle};
objects,
objects::{Projectile, ShipOutfits},
wrapper::Wrapper,
ShipHandle,
};
use galactica_content as content; use galactica_content as content;
use galactica_gameobject as object;
use galactica_render::ObjectSprite; use galactica_render::ObjectSprite;
/// Keeps track of all objects in the world that we can interact with. /// Keeps track of all objects in the world that we can interact with.
/// Also wraps our physics engine /// Also wraps our physics engine
pub struct Physics { pub struct World {
wrapper: Wrapper, wrapper: Wrapper,
projectiles: HashMap<ColliderHandle, objects::Projectile>, projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
ships: HashMap<ColliderHandle, objects::Ship>, ships: HashMap<ColliderHandle, objects::ShipWorldObject>,
collision_handler: ChannelEventCollector, collision_handler: ChannelEventCollector,
collision_queue: Receiver<CollisionEvent>, collision_queue: Receiver<CollisionEvent>,
} }
// Private methods // Private methods
impl Physics { impl<'a> World {
fn remove_projectile(&mut self, c: ColliderHandle) { fn remove_projectile(&mut self, c: ColliderHandle) {
let p = match self.projectiles.remove(&c) { let p = match self.projectiles.remove(&c) {
Some(p) => p, Some(p) => p,
@ -45,7 +42,7 @@ impl Physics {
); );
} }
fn remove_ship(&mut self, h: ShipHandle) { fn remove_ship(&mut self, h: ShipPhysicsHandle) {
self.wrapper.rigid_body_set.remove( self.wrapper.rigid_body_set.remove(
h.0, h.0,
&mut self.wrapper.im, &mut self.wrapper.im,
@ -57,20 +54,70 @@ impl Physics {
self.ships.remove(&h.1); self.ships.remove(&h.1);
} }
fn add_projectile(&mut self, pb: objects::ProjectileBuilder) -> ColliderHandle { /// Add a projectile fired from a ship
let r = self.wrapper.rigid_body_set.insert(pb.rigid_body.build()); fn add_projectiles(
let c = self.wrapper.collider_set.insert_with_parent( &mut self,
pb.collider.build(), s: &ShipPhysicsHandle,
r, p: Vec<(object::Projectile, content::GunPoint)>,
&mut self.wrapper.rigid_body_set, ) {
); let mut rng = rand::thread_rng();
self.projectiles.insert(c, Projectile::new(pb, r, c)); for (projectile, point) in p {
return c; let r = self.get_ship_body(&s).unwrap().1;
let ship_pos = util::rigidbody_position(r);
let ship_rot = util::rigidbody_rotation(r);
let ship_vel = util::rigidbody_velocity(r);
let ship_ang = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 });
let pos = ship_pos + (Matrix2::from_angle(-ship_ang) * point.pos.to_vec());
let spread: Rad<f32> = Deg(rng.gen_range(
-(projectile.content.angle_rng.0 / 2.0)..=projectile.content.angle_rng.0 / 2.0,
))
.into();
let vel = ship_vel
+ (Matrix2::from_angle(-ship_ang + spread)
* Vector2 {
x: 0.0,
y: projectile.content.speed
+ rng.gen_range(
-projectile.content.speed_rng..=projectile.content.speed_rng,
),
});
let rigid_body = RigidBodyBuilder::kinematic_velocity_based()
.translation(vector![pos.x, pos.y])
.rotation(-ship_ang.0)
.linvel(vector![vel.x, vel.y])
.build();
let collider = ColliderBuilder::ball(5.0)
.sensor(true)
.active_events(ActiveEvents::COLLISION_EVENTS)
.build();
let rigid_body = self.wrapper.rigid_body_set.insert(rigid_body);
let collider = self.wrapper.collider_set.insert_with_parent(
collider,
rigid_body,
&mut self.wrapper.rigid_body_set,
);
self.projectiles.insert(
collider.clone(),
ProjectileWorldObject {
projectile: projectile,
rigid_body,
collider,
},
);
}
} }
} }
// Public methods // Public methods
impl Physics { impl<'a> World {
/// Create a new physics system /// Create a new physics system
pub fn new() -> Self { pub fn new() -> Self {
let (collision_send, collision_queue) = crossbeam::channel::unbounded(); let (collision_send, collision_queue) = crossbeam::channel::unbounded();
@ -89,23 +136,23 @@ impl Physics {
/// TODO: decouple from Ship::new() /// TODO: decouple from Ship::new()
pub fn add_ship( pub fn add_ship(
&mut self, &mut self,
ct: &content::Ship, ct: &content::Content,
outfits: ShipOutfits, ship: object::Ship,
position: Point2<f32>, position: Point2<f32>,
faction: content::FactionHandle, ) -> ShipPhysicsHandle {
) -> ShipHandle { let ship_content = ct.get_ship(ship.handle);
let cl = ColliderBuilder::convex_decomposition( let cl = ColliderBuilder::convex_decomposition(
&ct.collision.points[..], &ship_content.collision.points[..],
&ct.collision.indices[..], &ship_content.collision.indices[..],
) )
// Rotate collider to match sprite // Rotate collider to match sprite
// (Collider starts pointing east, sprite starts pointing north.) // (Collider starts pointing east, sprite starts pointing north.)
.rotation(PI / -2.0) .rotation(PI / -2.0)
.mass(ct.mass); .mass(ship_content.mass);
let rb = RigidBodyBuilder::dynamic() let rb = RigidBodyBuilder::dynamic()
.angular_damping(ct.angular_drag) .angular_damping(ship_content.angular_drag)
.linear_damping(ct.linear_drag) .linear_damping(ship_content.linear_drag)
.translation(vector![position.x, position.y]) .translation(vector![position.x, position.y])
.can_sleep(false); .can_sleep(false);
@ -116,32 +163,33 @@ impl Physics {
&mut self.wrapper.rigid_body_set, &mut self.wrapper.rigid_body_set,
); );
let h = ShipHandle(r, c); let h = ShipPhysicsHandle(r, c);
self.ships self.ships.insert(c, objects::ShipWorldObject::new(ship, h));
.insert(c, objects::Ship::new(ct, outfits, h, faction));
return h; return h;
} }
/// Step this physics system by `t` seconds /// Step this physics system by `t` seconds
pub fn step(&mut self, t: f32, ct: &content::Content) { pub fn step(&mut self, t: f32, ct: &content::Content) {
// Run ship updates // Run ship updates
let mut res = Vec::new(); // TODO: Clean this mess
let mut ps = Vec::new();
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
for (_, s) in &mut self.ships { for (_, s) in &mut self.ships {
if s.hull <= 0.0 { if s.ship.hull <= 0.0 {
to_remove.push(s.physics_handle); to_remove.push(s.physics_handle);
continue; continue;
} }
let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0]; let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0];
res.push(s.apply_controls(r, t)); s.tick(r, t);
if s.controls.guns {
ps.push((s.physics_handle, s.ship.fire_guns()));
}
} }
for r in to_remove { for r in to_remove {
self.remove_ship(r); self.remove_ship(r);
} }
for r in res { for (s, p) in ps {
for p in r.projectiles { self.add_projectiles(&s, p);
self.add_projectile(p);
}
} }
// Update physics // Update physics
@ -153,7 +201,7 @@ impl Physics {
let a = &event.collider1(); let a = &event.collider1();
let b = &event.collider2(); let b = &event.collider2();
// If projectiles are a part of this collision, make sure // If projectiles are part of this collision, make sure
// `a` is one of them. // `a` is one of them.
let (a, b) = if self.projectiles.contains_key(b) { let (a, b) = if self.projectiles.contains_key(b) {
(b, a) (b, a)
@ -163,16 +211,9 @@ impl Physics {
if let Some(p) = self.projectiles.get(a) { if let Some(p) = self.projectiles.get(a) {
if let Some(s) = self.ships.get_mut(b) { if let Some(s) = self.ships.get_mut(b) {
let p_faction = ct.get_faction(p.faction); let hit = s.ship.handle_projectile_collision(ct, &p.projectile);
let r = p_faction.relationships[&s.faction]; if hit {
match r { self.remove_projectile(*a);
content::Relationship::Hostile => {
// TODO: implement death and spawning, and enable damage
//s.hull -= p.damage;
self.remove_projectile(*a);
self.remove_projectile(*b);
}
_ => {}
} }
} }
} }
@ -182,8 +223,8 @@ impl Physics {
// Delete projectiles // Delete projectiles
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
for (_, p) in &mut self.projectiles { for (_, p) in &mut self.projectiles {
p.tick(t); p.projectile.tick(t);
if p.is_expired() { if p.projectile.is_expired() {
to_remove.push(p.collider); to_remove.push(p.collider);
} }
} }
@ -198,18 +239,23 @@ impl Physics {
} }
/// Get a ship from a handle /// Get a ship from a handle
pub fn get_ship_mut(&mut self, s: &ShipHandle) -> Option<&mut objects::Ship> { pub fn get_ship_mut(&mut self, s: &ShipPhysicsHandle) -> Option<&mut objects::ShipWorldObject> {
self.ships.get_mut(&s.1) self.ships.get_mut(&s.1)
} }
/// Get a ship and its rigidbody from a handle /// Get a ship and its rigidbody from a handle
pub fn get_ship_body(&self, s: &ShipHandle) -> Option<(&objects::Ship, &RigidBody)> { pub fn get_ship_body(
&self,
s: &ShipPhysicsHandle,
) -> Option<(&objects::ShipWorldObject, &RigidBody)> {
// TODO: handle dead handles // TODO: handle dead handles
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?)) Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
} }
/// 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 = (&objects::Ship, &RigidBody)> + '_ { pub fn iter_ship_body(
&self,
) -> impl Iterator<Item = (&objects::ShipWorldObject, &RigidBody)> + '_ {
self.ships.values().map(|x| { self.ships.values().map(|x| {
( (
x, x,
@ -219,10 +265,13 @@ impl Physics {
} }
/// Iterate over all ship sprites in this physics system /// Iterate over all ship sprites in this physics system
pub fn get_ship_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ { pub fn get_ship_sprites(
&'a self,
ct: &'a content::Content,
) -> impl Iterator<Item = ObjectSprite> + '_ {
self.ships self.ships
.values() .values()
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.physics_handle.0])) .map(|x| x.get_sprite(ct, &self.wrapper.rigid_body_set[x.physics_handle.0]))
} }
/// Iterate over all projectile sprites in this physics system /// Iterate over all projectile sprites in this physics system

View File

@ -5,9 +5,10 @@ use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, Virt
use super::{camera::Camera, system::System}; use super::{camera::Camera, system::System};
use crate::{content, inputstatus::InputStatus}; use crate::{content, inputstatus::InputStatus};
use galactica_constants; use galactica_constants;
use galactica_physics::{objects::ShipOutfits, util, Physics, ShipHandle}; use galactica_gameobject as object;
use galactica_render::{AnchoredUiPosition, ObjectSprite, UiSprite}; use galactica_render::{AnchoredUiPosition, ObjectSprite, UiSprite};
use galactica_shipbehavior::{behavior, ShipBehavior}; use galactica_shipbehavior::{behavior, ShipBehavior};
use galactica_world::{util, ShipPhysicsHandle, World};
struct Ui {} struct Ui {}
@ -18,8 +19,8 @@ impl Ui {
fn build_radar( fn build_radar(
&self, &self,
player: &ShipHandle, player: &ShipPhysicsHandle,
physics: &Physics, physics: &World,
ct: &content::Content, ct: &content::Content,
) -> Vec<UiSprite> { ) -> Vec<UiSprite> {
let mut out = Vec::new(); let mut out = Vec::new();
@ -68,52 +69,63 @@ impl Ui {
pub struct Game { pub struct Game {
pub input: InputStatus, pub input: InputStatus,
pub last_update: Instant, pub last_update: Instant,
pub player: ShipHandle, pub player: ShipPhysicsHandle,
pub system: System, pub system: System,
pub camera: Camera, pub camera: Camera,
paused: bool, paused: bool,
pub time_scale: f32, pub time_scale: f32,
ui: Ui, ui: Ui,
physics: Physics, physics: World,
shipbehaviors: Vec<Box<dyn ShipBehavior>>, shipbehaviors: Vec<Box<dyn ShipBehavior>>,
content: content::Content, content: content::Content,
} }
impl Game { impl Game {
pub fn new(ct: content::Content) -> Self { pub fn new(ct: content::Content) -> Self {
let mut physics = Physics::new(); let mut physics = World::new();
let ss = ct.get_ship(content::ShipHandle { index: 0 });
let mut o1 = ShipOutfits::new(&ct.ships[0]); let mut o1 = object::OutfitSet::new(ss);
o1.add(ct.outfits[0].clone()); o1.add(&ct, content::OutfitHandle { index: 0 });
o1.add_gun(ct.guns[0].clone()); o1.add_gun(&ct, content::GunHandle { index: 0 });
o1.add_gun(ct.guns[0].clone()); o1.add_gun(&ct, content::GunHandle { index: 0 });
let h1 = physics.add_ship( let s = object::Ship::new(
&ct.ships[0], &ct,
content::ShipHandle { index: 0 },
content::FactionHandle { index: 0 },
o1, o1,
Point2 { x: 0.0, y: 0.0 },
content::FactionHandle { index: 0 },
); );
let h2 = physics.add_ship( let h1 = physics.add_ship(&ct, s, Point2 { x: 0.0, y: 0.0 });
&ct.ships[0],
ShipOutfits::new(&ct.ships[0]), let s = object::Ship::new(
Point2 { x: 300.0, y: 300.0 }, &ct,
content::ShipHandle { index: 0 },
content::FactionHandle { index: 0 }, content::FactionHandle { index: 0 },
object::OutfitSet::new(ss),
);
let h2 = physics.add_ship(&ct, s, Point2 { x: 300.0, y: 300.0 });
let mut o1 = object::OutfitSet::new(ss);
o1.add(&ct, content::OutfitHandle { index: 0 });
o1.add_gun(&ct, content::GunHandle { index: 0 });
let s = object::Ship::new(
&ct,
content::ShipHandle { index: 0 },
content::FactionHandle { index: 0 },
o1,
); );
let mut o2 = ShipOutfits::new(&ct.ships[0]);
o2.add(ct.outfits[0].clone());
o2.add_gun(ct.guns[0].clone());
let h3 = physics.add_ship( let h3 = physics.add_ship(
&ct.ships[0], &ct,
o2, s,
Point2 { Point2 {
x: -300.0, x: -300.0,
y: 300.0, y: 300.0,
}, },
content::FactionHandle { index: 1 },
); );
let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new(); let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new();
@ -198,7 +210,7 @@ impl Game {
let mut sprites: Vec<ObjectSprite> = Vec::new(); let mut sprites: Vec<ObjectSprite> = Vec::new();
sprites.append(&mut self.system.get_sprites()); sprites.append(&mut self.system.get_sprites());
sprites.extend(self.physics.get_ship_sprites()); sprites.extend(self.physics.get_ship_sprites(&self.content));
// Make sure sprites are drawn in the correct order // Make sure sprites are drawn in the correct order
// (note the reversed a, b in the comparator) // (note the reversed a, b in the comparator)