diff --git a/Cargo.lock b/Cargo.lock index 2b02ad1..4be0554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,9 +582,10 @@ dependencies = [ "crossbeam", "galactica-constants", "galactica-content", - "galactica-physics", + "galactica-gameobject", "galactica-render", "galactica-shipbehavior", + "galactica-world", "image", "nalgebra", "pollster", @@ -613,7 +614,7 @@ dependencies = [ ] [[package]] -name = "galactica-physics" +name = "galactica-gameobject" version = "0.0.0" dependencies = [ "cgmath", @@ -646,7 +647,21 @@ version = "0.0.0" dependencies = [ "cgmath", "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]] diff --git a/Cargo.toml b/Cargo.toml index 9620c13..2ada59f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,8 +32,9 @@ members = [ "crates/content", "crates/render", "crates/constants", - "crates/physics", + "crates/world", "crates/shipbehavior", + "crates/gameobject", ] @@ -42,8 +43,9 @@ members = [ galactica-content = { path = "crates/content" } galactica-render = { path = "crates/render" } galactica-constants = { path = "crates/constants" } -galactica-physics = { path = "crates/physics" } +galactica-world = { path = "crates/world" } galactica-shipbehavior = { path = "crates/shipbehavior" } +galactica-gameobject = { path = "crates/gameobject" } # Files image = { version = "0.24", features = ["png"] } diff --git a/content/guns.toml b/content/guns.toml index 30ecf15..ccdffba 100644 --- a/content/guns.toml +++ b/content/guns.toml @@ -2,10 +2,6 @@ space.weapon = 10 -# Angle of fire cone -# Smaller angle = more accurate -spread = 2 - # Average delay between shots rate = 0.2 # Random rate variation (+- this in both directions) @@ -23,3 +19,8 @@ projectile.speed_rng = 10.0 projectile.lifetime = 2.0 projectile.lifetime_rng = 0.2 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 diff --git a/crates/content/src/handle.rs b/crates/content/src/handle.rs index 054e417..e7d0951 100644 --- a/crates/content/src/handle.rs +++ b/crates/content/src/handle.rs @@ -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}; -/// Represents a specific texture defined in the content dir. +/// A lightweight representation of a #[derive(Debug, Clone, Copy)] pub struct TextureHandle { /// 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(&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(&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(&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(&self, state: &mut H) { + self.index.hash(state) + } +} + /// A lightweight representation of a faction #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct FactionHandle { /// 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, } diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index d0c1300..c2bb64e 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -17,15 +17,16 @@ use std::{ use toml; use walkdir::WalkDir; -pub use handle::{FactionHandle, TextureHandle}; +pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SystemHandle, TextureHandle}; 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 { - use anyhow::{bail, Result}; + use anyhow::{bail, Context, Result}; use serde::Deserialize; - use std::collections::HashMap; + use std::{collections::HashMap, fmt::Display, hash::Hash}; use crate::part::{faction, gun, outfit, ship, system, texture}; @@ -39,6 +40,31 @@ mod syntax { pub faction: Option>, } + fn merge_hashmap( + to: &mut Option>, + mut from: Option>, + ) -> 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 { pub fn new() -> Self { Self { @@ -52,98 +78,16 @@ mod syntax { } pub fn merge(&mut self, other: Root) -> Result<()> { - // Insert if not exists - // TODO: replace with a macro and try_insert once that is stable - if let Some(a) = other.gun { - if self.gun.is_none() { - self.gun = Some(a); - } else { - let sg = self.gun.as_mut().unwrap(); - for (k, v) in a { - if sg.contains_key(&k) { - bail!("Repeated gun name {k}"); - } 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); - } - } - } - } - + merge_hashmap(&mut self.gun, other.gun).with_context(|| "while merging guns")?; + merge_hashmap(&mut self.ship, other.ship).with_context(|| "while merging ships")?; + merge_hashmap(&mut self.system, other.system) + .with_context(|| "while merging systems")?; + merge_hashmap(&mut self.outfit, other.outfit) + .with_context(|| "while merging outfits")?; + merge_hashmap(&mut self.texture, other.texture) + .with_context(|| "while merging textures")?; + merge_hashmap(&mut self.faction, other.faction) + .with_context(|| "while merging factions")?; return Ok(()); } } @@ -161,38 +105,35 @@ trait Build { /// Represents static game content #[derive(Debug)] pub struct Content { - /// Star systems - pub systems: Vec, - - /// Ship bodies - pub ships: Vec, - - /// Ship guns - pub guns: Vec, - - /// Outfits - pub outfits: Vec, + /* Configuration values */ + /// Root directory for textures + texture_root: PathBuf, + /// Name of starfield texture + starfield_texture_name: String, /// Textures pub textures: Vec, - - /// Factions - pub factions: Vec, - - /// Map strings to texture handles - /// This is never used outside this crate. texture_index: HashMap, - /// The texture to use for starfield stars starfield_handle: Option, - /// Root directory for textures - texture_root: PathBuf, + /// Outfits + outfits: Vec, - /// Name of starfield texture - starfield_texture_name: String, + /// Ship guns + guns: Vec, + + /// Ship bodies + ships: Vec, + + /// Star systems + pub systems: Vec, + + /// Factions + factions: Vec, } +// Loading methods impl Content { fn try_parse(path: &Path) -> Result { let mut file_string = String::new(); @@ -201,32 +142,6 @@ impl Content { 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. pub fn load_dir( path: PathBuf, @@ -296,3 +211,54 @@ impl 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]; + } +} diff --git a/crates/content/src/part/gun.rs b/crates/content/src/part/gun.rs index 8f389dd..69f90e3 100644 --- a/crates/content/src/part/gun.rs +++ b/crates/content/src/part/gun.rs @@ -16,7 +16,6 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] pub struct Gun { pub projectile: Projectile, - pub spread: f32, pub rate: f32, pub rate_rng: f32, pub space: shared::syntax::OutfitSpace, @@ -32,6 +31,7 @@ pub(crate) mod syntax { pub lifetime: f32, pub lifetime_rng: f32, pub damage: f32, + pub angle_rng: f32, } } @@ -44,13 +44,6 @@ pub struct Gun { /// The projectile this gun produces 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, - /// Average delay between projectiles, in seconds. pub rate: f32, @@ -87,6 +80,13 @@ pub struct Projectile { /// The damage this projectile does 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, } impl crate::Build for Gun { @@ -106,7 +106,6 @@ impl crate::Build for Gun { ct.guns.push(Self { name: gun_name, space: gun.space.into(), - spread: Deg(gun.spread), rate: gun.rate, rate_rng: gun.rate_rng, projectile: Projectile { @@ -118,6 +117,7 @@ impl crate::Build for Gun { lifetime: gun.projectile.lifetime, lifetime_rng: gun.projectile.lifetime_rng, damage: gun.projectile.damage, + angle_rng: Deg(gun.projectile.angle_rng), }, }); } diff --git a/crates/content/src/part/shared.rs b/crates/content/src/part/shared.rs index 70036eb..c12aaf5 100644 --- a/crates/content/src/part/shared.rs +++ b/crates/content/src/part/shared.rs @@ -35,7 +35,7 @@ impl OutfitSpace { } /// 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.weapon >= smaller.weapon && self.engine >= smaller.engine diff --git a/crates/physics/Cargo.toml b/crates/gameobject/Cargo.toml similarity index 89% rename from crates/physics/Cargo.toml rename to crates/gameobject/Cargo.toml index b6c9c35..c0f2c8b 100644 --- a/crates/physics/Cargo.toml +++ b/crates/gameobject/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "galactica-physics" +name = "galactica-gameobject" version = "0.0.0" edition = "2021" diff --git a/crates/gameobject/src/lib.rs b/crates/gameobject/src/lib.rs new file mode 100644 index 0000000..9f8984c --- /dev/null +++ b/crates/gameobject/src/lib.rs @@ -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; diff --git a/crates/physics/src/objects/outfits.rs b/crates/gameobject/src/outfits.rs similarity index 81% rename from crates/physics/src/objects/outfits.rs rename to crates/gameobject/src/outfits.rs index 592b9ee..3e189b2 100644 --- a/crates/physics/src/objects/outfits.rs +++ b/crates/gameobject/src/outfits.rs @@ -17,9 +17,9 @@ pub struct ShipGun { impl ShipGun { /// Make a new shipgun - pub fn new(kind: content::Gun, point: usize) -> Self { + pub fn new(kind: &content::Gun, point: usize) -> Self { Self { - kind: kind, + kind: kind.clone(), point, 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. -/// TODO: rename (outfitset?) -/// TODO: ctrl-f outfitset in comments #[derive(Debug)] -pub struct ShipOutfits { +pub struct OutfitSet { /// The total sum of the stats this set of outfits provides /// TODO: this shouldn't be pub pub stats: OutfitStatSum, //pub total_space: content::OutfitSpace, available_space: content::OutfitSpace, - outfits: Vec, + outfits: Vec, guns: Vec, enginepoints: Vec, gunpoints: Vec, @@ -89,7 +87,7 @@ pub struct ShipOutfits { engine_flare_sprites: Vec, } -impl<'a> ShipOutfits { +impl<'a> OutfitSet { /// Make a new outfit array pub fn new(content: &content::Ship) -> Self { Self { @@ -105,8 +103,8 @@ impl<'a> ShipOutfits { } /// Does this outfit set contain the specified outfit? - pub fn contains_outfit(&self, o: &content::Outfit) -> bool { - match self.outfits.iter().position(|x| x.name == o.name) { + pub fn contains_outfit(&self, o: content::OutfitHandle) -> bool { + match self.outfits.iter().position(|x| *x == o) { Some(_) => true, None => false, } @@ -115,33 +113,35 @@ impl<'a> ShipOutfits { /// Add an outfit to this ship. /// Returns true on success, and false on failure /// TODO: failure reason enum - pub fn add(&mut self, o: content::Outfit) -> bool { - if !self.available_space.can_contain(o.space) { + pub fn add(&mut self, ct: &content::Content, o: content::OutfitHandle) -> bool { + let outfit = ct.get_outfit(o); + if !self.available_space.can_contain(&outfit.space) { return false; } - self.available_space.occupy(&o.space); - self.stats.add(&o); + self.available_space.occupy(&outfit.space); + self.stats.add(&outfit); self.outfits.push(o); self.update_engine_flares(); return true; } - /// TODO: is outfit in set? - pub fn remove(&mut self, o: content::Outfit) { - let i = match self.outfits.iter().position(|x| x.name == o.name) { + /// Remove an outfit from this set + pub fn remove(&mut self, ct: &content::Content, o: content::OutfitHandle) { + let outfit = ct.get_outfit(o); + let i = match self.outfits.iter().position(|x| *x == o) { 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.stats.remove(&o); + self.stats.remove(&outfit); self.update_engine_flares(); } /// Add a gun to this outfit set. /// This automatically attaches the gun to the first available gunpoint, /// 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 let mut p = None; 'outer: for i in 0..self.gunpoints.len() { @@ -153,6 +153,7 @@ impl<'a> ShipOutfits { p = Some(i); break; } + let gun = ct.get_gun(g); // All points are taken if p.is_none() { diff --git a/crates/gameobject/src/projectile.rs b/crates/gameobject/src/projectile.rs new file mode 100644 index 0000000..6697295 --- /dev/null +++ b/crates/gameobject/src/projectile.rs @@ -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; + } +} diff --git a/crates/gameobject/src/ship.rs b/crates/gameobject/src/ship.rs new file mode 100644 index 0000000..510cb25 --- /dev/null +++ b/crates/gameobject/src/ship.rs @@ -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() + } +} diff --git a/crates/physics/src/lib.rs b/crates/physics/src/lib.rs deleted file mode 100644 index 444a780..0000000 --- a/crates/physics/src/lib.rs +++ /dev/null @@ -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); diff --git a/crates/physics/src/objects/mod.rs b/crates/physics/src/objects/mod.rs deleted file mode 100644 index 0933418..0000000 --- a/crates/physics/src/objects/mod.rs +++ /dev/null @@ -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; diff --git a/crates/physics/src/objects/projectile.rs b/crates/physics/src/objects/projectile.rs deleted file mode 100644 index 8a6162d..0000000 --- a/crates/physics/src/objects/projectile.rs +++ /dev/null @@ -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 = 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, - } - } -} diff --git a/crates/physics/src/objects/ship.rs b/crates/physics/src/objects/ship.rs deleted file mode 100644 index c507041..0000000 --- a/crates/physics/src/objects/ship.rs +++ /dev/null @@ -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, -} - -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 { - 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 = - 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 = 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 - }, - } - } -} diff --git a/crates/shipbehavior/Cargo.toml b/crates/shipbehavior/Cargo.toml index 815e375..c60fa1d 100644 --- a/crates/shipbehavior/Cargo.toml +++ b/crates/shipbehavior/Cargo.toml @@ -5,5 +5,5 @@ edition = "2021" [dependencies] galactica-content = { path = "../content" } -galactica-physics = { path = "../physics" } +galactica-world = { path = "../world" } cgmath = "0.18.0" diff --git a/crates/shipbehavior/src/behavior/dummy.rs b/crates/shipbehavior/src/behavior/dummy.rs index d9b22f4..1f8ec4b 100644 --- a/crates/shipbehavior/src/behavior/dummy.rs +++ b/crates/shipbehavior/src/behavior/dummy.rs @@ -1,23 +1,23 @@ use crate::ShipBehavior; use galactica_content as content; -use galactica_physics::{Physics, ShipHandle}; +use galactica_world::{ShipPhysicsHandle, World}; /// Dummy ship behavior. /// Does nothing. pub struct Dummy { - _handle: ShipHandle, + _handle: ShipPhysicsHandle, } impl Dummy { /// Create a new ship controller - pub fn new(handle: ShipHandle) -> Box { + pub fn new(handle: ShipPhysicsHandle) -> Box { Box::new(Self { _handle: handle }) } } impl ShipBehavior for Dummy { - fn update_controls(&mut self, _physics: &mut Physics, _content: &content::Content) {} - fn get_handle(&self) -> ShipHandle { + fn update_controls(&mut self, _physics: &mut World, _content: &content::Content) {} + fn get_handle(&self) -> ShipPhysicsHandle { return self._handle; } } diff --git a/crates/shipbehavior/src/behavior/player.rs b/crates/shipbehavior/src/behavior/player.rs index 269f0c3..26a1639 100644 --- a/crates/shipbehavior/src/behavior/player.rs +++ b/crates/shipbehavior/src/behavior/player.rs @@ -1,11 +1,11 @@ use crate::ShipBehavior; use galactica_content as content; -use galactica_physics::{Physics, ShipHandle}; +use galactica_world::{ShipPhysicsHandle, World}; /// Player ship behavior. /// Controls a ship using controller input pub struct Player { - handle: ShipHandle, + handle: ShipPhysicsHandle, key_left: bool, key_right: bool, key_guns: bool, @@ -14,7 +14,7 @@ pub struct Player { impl Player { /// Make a new ship controller - pub fn new(handle: ShipHandle) -> Box { + pub fn new(handle: ShipPhysicsHandle) -> Box { Box::new(Self { handle, key_left: false, @@ -26,7 +26,7 @@ impl 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(); s.controls.left = self.key_left; s.controls.right = self.key_right; @@ -34,7 +34,7 @@ impl ShipBehavior for Player { s.controls.thrust = self.key_thrust; } - fn get_handle(&self) -> ShipHandle { + fn get_handle(&self) -> ShipPhysicsHandle { return self.handle; } } diff --git a/crates/shipbehavior/src/behavior/point.rs b/crates/shipbehavior/src/behavior/point.rs index 0f01373..c27ef83 100644 --- a/crates/shipbehavior/src/behavior/point.rs +++ b/crates/shipbehavior/src/behavior/point.rs @@ -2,23 +2,23 @@ use cgmath::{Deg, InnerSpace}; use crate::ShipBehavior; use galactica_content as content; -use galactica_physics::{util, Physics, ShipHandle}; +use galactica_world::{util, ShipPhysicsHandle, World}; /// "Point" ship behavior. /// Point and shoot towards the nearest enemy. pub struct Point { - handle: ShipHandle, + handle: ShipPhysicsHandle, } impl Point { /// Create a new ship controller - pub fn new(handle: ShipHandle) -> Box { + pub fn new(handle: ShipPhysicsHandle) -> Box { Box::new(Self { handle }) } } 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 let s = physics.get_ship_mut(&self.handle).unwrap(); s.controls.left = false; @@ -30,12 +30,12 @@ impl ShipBehavior for Point { let my_position = util::rigidbody_position(my_r); let my_rotation = util::rigidbody_rotation(my_r); 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 let mut it = physics .iter_ship_body() - .filter(|(s, _)| match my_faction.relationships[&s.faction] { + .filter(|(s, _)| match my_faction.relationships[&s.ship.faction] { content::Relationship::Hostile => true, _ => false, }) @@ -74,7 +74,7 @@ impl ShipBehavior for Point { s.controls.thrust = false; } - fn get_handle(&self) -> ShipHandle { + fn get_handle(&self) -> ShipPhysicsHandle { return self.handle; } } diff --git a/crates/shipbehavior/src/lib.rs b/crates/shipbehavior/src/lib.rs index b1520ad..394e1cf 100644 --- a/crates/shipbehavior/src/lib.rs +++ b/crates/shipbehavior/src/lib.rs @@ -5,7 +5,7 @@ pub mod behavior; use galactica_content as content; -use galactica_physics::{Physics, ShipHandle}; +use galactica_world::{ShipPhysicsHandle, World}; /// Main behavior trait. Any struct that implements this /// may be used to control a ship. @@ -14,8 +14,8 @@ where Self: Send, { /// 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 - fn get_handle(&self) -> ShipHandle; + fn get_handle(&self) -> ShipPhysicsHandle; } diff --git a/crates/world/Cargo.toml b/crates/world/Cargo.toml new file mode 100644 index 0000000..e30b966 --- /dev/null +++ b/crates/world/Cargo.toml @@ -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" diff --git a/crates/world/src/lib.rs b/crates/world/src/lib.rs new file mode 100644 index 0000000..887004f --- /dev/null +++ b/crates/world/src/lib.rs @@ -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); diff --git a/crates/world/src/objects/mod.rs b/crates/world/src/objects/mod.rs new file mode 100644 index 0000000..105c6c8 --- /dev/null +++ b/crates/world/src/objects/mod.rs @@ -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; diff --git a/crates/world/src/objects/projectile.rs b/crates/world/src/objects/projectile.rs new file mode 100644 index 0000000..cedfa48 --- /dev/null +++ b/crates/world/src/objects/projectile.rs @@ -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 = 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, + } + } +} diff --git a/crates/world/src/objects/ship.rs b/crates/world/src/objects/ship.rs new file mode 100644 index 0000000..b15edb7 --- /dev/null +++ b/crates/world/src/objects/ship.rs @@ -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 = 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 + }, + } + } +} diff --git a/crates/physics/src/util.rs b/crates/world/src/util.rs similarity index 100% rename from crates/physics/src/util.rs rename to crates/world/src/util.rs diff --git a/crates/physics/src/physics.rs b/crates/world/src/world.rs similarity index 53% rename from crates/physics/src/physics.rs rename to crates/world/src/world.rs index e13391c..d5d69e4 100644 --- a/crates/physics/src/physics.rs +++ b/crates/world/src/world.rs @@ -1,35 +1,32 @@ -use cgmath::Point2; +use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2}; use crossbeam::channel::Receiver; use nalgebra::vector; +use rand::Rng; use rapier2d::{ dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, geometry::{ColliderBuilder, ColliderHandle, CollisionEvent}, - pipeline::ChannelEventCollector, + pipeline::{ActiveEvents, ChannelEventCollector}, }; use std::{collections::HashMap, f32::consts::PI}; -use crate::{ - objects, - objects::{Projectile, ShipOutfits}, - wrapper::Wrapper, - ShipHandle, -}; +use crate::{objects, objects::ProjectileWorldObject, util, wrapper::Wrapper, ShipPhysicsHandle}; use galactica_content as content; +use galactica_gameobject as object; use galactica_render::ObjectSprite; /// Keeps track of all objects in the world that we can interact with. /// Also wraps our physics engine -pub struct Physics { +pub struct World { wrapper: Wrapper, - projectiles: HashMap, - ships: HashMap, + projectiles: HashMap, + ships: HashMap, collision_handler: ChannelEventCollector, collision_queue: Receiver, } // Private methods -impl Physics { +impl<'a> World { fn remove_projectile(&mut self, c: ColliderHandle) { let p = match self.projectiles.remove(&c) { 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( h.0, &mut self.wrapper.im, @@ -57,20 +54,70 @@ impl Physics { self.ships.remove(&h.1); } - fn add_projectile(&mut self, pb: objects::ProjectileBuilder) -> ColliderHandle { - let r = self.wrapper.rigid_body_set.insert(pb.rigid_body.build()); - let c = self.wrapper.collider_set.insert_with_parent( - pb.collider.build(), - r, - &mut self.wrapper.rigid_body_set, - ); - self.projectiles.insert(c, Projectile::new(pb, r, c)); - return c; + /// Add a projectile fired from a ship + fn add_projectiles( + &mut self, + s: &ShipPhysicsHandle, + p: Vec<(object::Projectile, content::GunPoint)>, + ) { + let mut rng = rand::thread_rng(); + for (projectile, point) in p { + 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 = 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 -impl Physics { +impl<'a> World { /// Create a new physics system pub fn new() -> Self { let (collision_send, collision_queue) = crossbeam::channel::unbounded(); @@ -89,23 +136,23 @@ impl Physics { /// TODO: decouple from Ship::new() pub fn add_ship( &mut self, - ct: &content::Ship, - outfits: ShipOutfits, + ct: &content::Content, + ship: object::Ship, position: Point2, - faction: content::FactionHandle, - ) -> ShipHandle { + ) -> ShipPhysicsHandle { + let ship_content = ct.get_ship(ship.handle); let cl = ColliderBuilder::convex_decomposition( - &ct.collision.points[..], - &ct.collision.indices[..], + &ship_content.collision.points[..], + &ship_content.collision.indices[..], ) // Rotate collider to match sprite // (Collider starts pointing east, sprite starts pointing north.) .rotation(PI / -2.0) - .mass(ct.mass); + .mass(ship_content.mass); let rb = RigidBodyBuilder::dynamic() - .angular_damping(ct.angular_drag) - .linear_damping(ct.linear_drag) + .angular_damping(ship_content.angular_drag) + .linear_damping(ship_content.linear_drag) .translation(vector![position.x, position.y]) .can_sleep(false); @@ -116,32 +163,33 @@ impl Physics { &mut self.wrapper.rigid_body_set, ); - let h = ShipHandle(r, c); - self.ships - .insert(c, objects::Ship::new(ct, outfits, h, faction)); + let h = ShipPhysicsHandle(r, c); + self.ships.insert(c, objects::ShipWorldObject::new(ship, h)); return h; } /// Step this physics system by `t` seconds pub fn step(&mut self, t: f32, ct: &content::Content) { // Run ship updates - let mut res = Vec::new(); + // TODO: Clean this mess + let mut ps = Vec::new(); let mut to_remove = Vec::new(); for (_, s) in &mut self.ships { - if s.hull <= 0.0 { + if s.ship.hull <= 0.0 { to_remove.push(s.physics_handle); continue; } 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 { self.remove_ship(r); } - for r in res { - for p in r.projectiles { - self.add_projectile(p); - } + for (s, p) in ps { + self.add_projectiles(&s, p); } // Update physics @@ -153,7 +201,7 @@ impl Physics { let a = &event.collider1(); 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. let (a, b) = if self.projectiles.contains_key(b) { (b, a) @@ -163,16 +211,9 @@ impl Physics { if let Some(p) = self.projectiles.get(a) { if let Some(s) = self.ships.get_mut(b) { - let p_faction = ct.get_faction(p.faction); - let r = p_faction.relationships[&s.faction]; - match r { - content::Relationship::Hostile => { - // TODO: implement death and spawning, and enable damage - //s.hull -= p.damage; - self.remove_projectile(*a); - self.remove_projectile(*b); - } - _ => {} + let hit = s.ship.handle_projectile_collision(ct, &p.projectile); + if hit { + self.remove_projectile(*a); } } } @@ -182,8 +223,8 @@ impl Physics { // Delete projectiles let mut to_remove = Vec::new(); for (_, p) in &mut self.projectiles { - p.tick(t); - if p.is_expired() { + p.projectile.tick(t); + if p.projectile.is_expired() { to_remove.push(p.collider); } } @@ -198,18 +239,23 @@ impl Physics { } /// 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) } /// 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 Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?)) } /// Iterate over all ships in this physics system - pub fn iter_ship_body(&self) -> impl Iterator + '_ { + pub fn iter_ship_body( + &self, + ) -> impl Iterator + '_ { self.ships.values().map(|x| { ( x, @@ -219,10 +265,13 @@ impl Physics { } /// Iterate over all ship sprites in this physics system - pub fn get_ship_sprites(&self) -> impl Iterator + '_ { + pub fn get_ship_sprites( + &'a self, + ct: &'a content::Content, + ) -> impl Iterator + '_ { self.ships .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 diff --git a/crates/physics/src/wrapper.rs b/crates/world/src/wrapper.rs similarity index 100% rename from crates/physics/src/wrapper.rs rename to crates/world/src/wrapper.rs diff --git a/src/game/game.rs b/src/game/game.rs index 39f4b6a..687e783 100644 --- a/src/game/game.rs +++ b/src/game/game.rs @@ -5,9 +5,10 @@ use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, Virt use super::{camera::Camera, system::System}; use crate::{content, inputstatus::InputStatus}; use galactica_constants; -use galactica_physics::{objects::ShipOutfits, util, Physics, ShipHandle}; +use galactica_gameobject as object; use galactica_render::{AnchoredUiPosition, ObjectSprite, UiSprite}; use galactica_shipbehavior::{behavior, ShipBehavior}; +use galactica_world::{util, ShipPhysicsHandle, World}; struct Ui {} @@ -18,8 +19,8 @@ impl Ui { fn build_radar( &self, - player: &ShipHandle, - physics: &Physics, + player: &ShipPhysicsHandle, + physics: &World, ct: &content::Content, ) -> Vec { let mut out = Vec::new(); @@ -68,52 +69,63 @@ impl Ui { pub struct Game { pub input: InputStatus, pub last_update: Instant, - pub player: ShipHandle, + pub player: ShipPhysicsHandle, pub system: System, pub camera: Camera, paused: bool, pub time_scale: f32, ui: Ui, - physics: Physics, + physics: World, shipbehaviors: Vec>, content: content::Content, } impl Game { 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]); - o1.add(ct.outfits[0].clone()); - o1.add_gun(ct.guns[0].clone()); - o1.add_gun(ct.guns[0].clone()); + let mut o1 = object::OutfitSet::new(ss); + o1.add(&ct, content::OutfitHandle { index: 0 }); + o1.add_gun(&ct, content::GunHandle { index: 0 }); + o1.add_gun(&ct, content::GunHandle { index: 0 }); - let h1 = physics.add_ship( - &ct.ships[0], + let s = object::Ship::new( + &ct, + content::ShipHandle { index: 0 }, + content::FactionHandle { index: 0 }, o1, - Point2 { x: 0.0, y: 0.0 }, - content::FactionHandle { index: 0 }, ); - let h2 = physics.add_ship( - &ct.ships[0], - ShipOutfits::new(&ct.ships[0]), - Point2 { x: 300.0, y: 300.0 }, + let h1 = physics.add_ship(&ct, s, Point2 { x: 0.0, y: 0.0 }); + + let s = object::Ship::new( + &ct, + content::ShipHandle { 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( - &ct.ships[0], - o2, + &ct, + s, Point2 { x: -300.0, y: 300.0, }, - content::FactionHandle { index: 1 }, ); let mut shipbehaviors: Vec> = Vec::new(); @@ -198,7 +210,7 @@ impl Game { let mut sprites: Vec = Vec::new(); 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 // (note the reversed a, b in the comparator)