Compare commits
9 Commits
2fda557959
...
3784aa3e08
Author | SHA1 | Date |
---|---|---|
Mark | 3784aa3e08 | |
Mark | fef4e5ed23 | |
Mark | fa992d3fb5 | |
Mark | d5f0cbd678 | |
Mark | bcf10e416b | |
Mark | b309a074dc | |
Mark | afa6bd6690 | |
Mark | bd8651b02a | |
Mark | 02220902ca |
|
@ -580,8 +580,12 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cgmath",
|
"cgmath",
|
||||||
"crossbeam",
|
"crossbeam",
|
||||||
|
"galactica-constants",
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
|
"galactica-gameobject",
|
||||||
"galactica-render",
|
"galactica-render",
|
||||||
|
"galactica-shipbehavior",
|
||||||
|
"galactica-world",
|
||||||
"image",
|
"image",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
@ -592,6 +596,10 @@ dependencies = [
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "galactica-constants"
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "galactica-content"
|
name = "galactica-content"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -605,6 +613,19 @@ dependencies = [
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "galactica-gameobject"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"cgmath",
|
||||||
|
"crossbeam",
|
||||||
|
"galactica-content",
|
||||||
|
"galactica-render",
|
||||||
|
"nalgebra",
|
||||||
|
"rand",
|
||||||
|
"rapier2d",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "galactica-render"
|
name = "galactica-render"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -612,6 +633,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cgmath",
|
"cgmath",
|
||||||
|
"galactica-constants",
|
||||||
"galactica-content",
|
"galactica-content",
|
||||||
"image",
|
"image",
|
||||||
"rand",
|
"rand",
|
||||||
|
@ -619,6 +641,29 @@ dependencies = [
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "galactica-shipbehavior"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"cgmath",
|
||||||
|
"galactica-content",
|
||||||
|
"galactica-world",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "galactica-world"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"cgmath",
|
||||||
|
"crossbeam",
|
||||||
|
"galactica-content",
|
||||||
|
"galactica-gameobject",
|
||||||
|
"galactica-render",
|
||||||
|
"nalgebra",
|
||||||
|
"rand",
|
||||||
|
"rapier2d",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -28,13 +28,24 @@ rpath = false
|
||||||
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["crates/content", "crates/render"]
|
members = [
|
||||||
|
"crates/content",
|
||||||
|
"crates/render",
|
||||||
|
"crates/constants",
|
||||||
|
"crates/world",
|
||||||
|
"crates/shipbehavior",
|
||||||
|
"crates/gameobject",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Internal crates
|
# Internal crates
|
||||||
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-world = { path = "crates/world" }
|
||||||
|
galactica-shipbehavior = { path = "crates/shipbehavior" }
|
||||||
|
galactica-gameobject = { path = "crates/gameobject" }
|
||||||
|
|
||||||
# Files
|
# Files
|
||||||
image = { version = "0.24", features = ["png"] }
|
image = { version = "0.24", features = ["png"] }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
[package]
|
||||||
|
name = "galactica-constants"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
|
@ -1,14 +1,24 @@
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
//! Compile-time parameters
|
||||||
|
|
||||||
|
/// Minimum zoom level
|
||||||
pub const ZOOM_MIN: f32 = 200.0;
|
pub const ZOOM_MIN: f32 = 200.0;
|
||||||
|
/// Maximum zoom level
|
||||||
pub const ZOOM_MAX: f32 = 2000.0;
|
pub const ZOOM_MAX: f32 = 2000.0;
|
||||||
|
|
||||||
/// Z-axis range for starfield stars
|
/// Z-axis range for starfield stars
|
||||||
/// This does not affect scale.
|
/// This does not affect scale.
|
||||||
pub const STARFIELD_Z_MIN: f32 = 100.0;
|
pub const STARFIELD_Z_MIN: f32 = 100.0;
|
||||||
|
/// Z-axis range for starfield stars
|
||||||
|
/// This does not affect scale.
|
||||||
pub const STARFIELD_Z_MAX: f32 = 200.0;
|
pub const STARFIELD_Z_MAX: f32 = 200.0;
|
||||||
|
|
||||||
/// Size range for starfield stars, in game units.
|
/// Size range for starfield stars, in game units.
|
||||||
/// This is scaled for zoom, but NOT for distance.
|
/// This is scaled for zoom, but NOT for distance.
|
||||||
pub const STARFIELD_SIZE_MIN: f32 = 0.2;
|
pub const STARFIELD_SIZE_MIN: f32 = 0.2;
|
||||||
|
/// Size range for starfield stars, in game units.
|
||||||
|
/// This is scaled for zoom, but NOT for distance.
|
||||||
pub const STARFIELD_SIZE_MAX: f32 = 1.8;
|
pub const STARFIELD_SIZE_MAX: f32 = 1.8;
|
||||||
|
|
||||||
/// Size of a square starfield tile, in game units.
|
/// Size of a square starfield tile, in game units.
|
||||||
|
@ -24,3 +34,18 @@ pub const STARFIELD_DENSITY: f64 = 0.01;
|
||||||
/// Number of stars in one starfield tile
|
/// Number of stars in one starfield tile
|
||||||
/// Must fit inside an i32
|
/// Must fit inside an i32
|
||||||
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
|
pub const STARFIELD_COUNT: u64 = (STARFIELD_SIZE as f64 * STARFIELD_DENSITY) as u64;
|
||||||
|
|
||||||
|
/// Name of starfield texture
|
||||||
|
pub const STARFIELD_TEXTURE_NAME: &'static str = "starfield";
|
||||||
|
|
||||||
|
/// Root directory of game content
|
||||||
|
pub const CONTENT_ROOT: &'static str = "./content";
|
||||||
|
|
||||||
|
/// Root directory of game textures
|
||||||
|
pub const TEXTURE_ROOT: &'static str = "./assets/render";
|
||||||
|
|
||||||
|
/// We can draw at most this many sprites on the screen.
|
||||||
|
pub const SPRITE_INSTANCE_LIMIT: u64 = 500;
|
||||||
|
|
||||||
|
/// Must be small enough to fit in an i32
|
||||||
|
pub const STARFIELD_INSTANCE_LIMIT: u64 = STARFIELD_COUNT * 24;
|
|
@ -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,16 +30,38 @@ impl PartialEq for TextureHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A lightweight representation of a faction
|
/// A lightweight representation of an outfit
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct FactionHandle {
|
pub struct OutfitHandle {
|
||||||
/// The index of this faction in content.factions
|
/// TODO
|
||||||
/// TODO: pub in crate, currently for debug.
|
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for FactionHandle {
|
/// A lightweight representation of a gun
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
self.index.hash(state)
|
pub struct GunHandle {
|
||||||
}
|
/// TODO
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A lightweight representation of a ship
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ShipHandle {
|
||||||
|
/// TODO
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A lightweight representation of a star system
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct SystemHandle {
|
||||||
|
/// TODO
|
||||||
|
pub index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A lightweight representation of a faction
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct FactionHandle {
|
||||||
|
/// The index of this faction in content.factions
|
||||||
|
/// TODO: pub in crate, currently for debug (same with all other handles)
|
||||||
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,37 @@ 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>,
|
||||||
|
/// Map strings to texture names.
|
||||||
/// Factions
|
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
||||||
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
|
||||||
|
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 +144,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 +213,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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "galactica-gameobject"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
galactica-render = { path = "../render" }
|
||||||
|
galactica-content = { path = "../content" }
|
||||||
|
|
||||||
|
rapier2d = { version = "0.17.2" }
|
||||||
|
nalgebra = "0.32.3"
|
||||||
|
crossbeam = "0.8.3"
|
||||||
|
cgmath = "0.18.0"
|
||||||
|
rand = "0.8.5"
|
|
@ -0,0 +1,17 @@
|
||||||
|
//! 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;
|
||||||
|
mod system;
|
||||||
|
mod systemobject;
|
||||||
|
|
||||||
|
pub use outfits::{OutfitSet, OutfitStatSum, ShipGun};
|
||||||
|
pub use projectile::Projectile;
|
||||||
|
pub use ship::Ship;
|
||||||
|
pub use system::System;
|
||||||
|
pub use systemobject::SystemObject;
|
|
@ -1,20 +1,25 @@
|
||||||
use cgmath::{Deg, Point3};
|
use cgmath::{Deg, Point3};
|
||||||
|
use galactica_content as content;
|
||||||
use galactica_render::ObjectSubSprite;
|
use galactica_render::ObjectSubSprite;
|
||||||
|
|
||||||
use crate::content;
|
|
||||||
|
|
||||||
/// Represents a gun attached to a specific ship at a certain gunpoint.
|
/// Represents a gun attached to a specific ship at a certain gunpoint.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ShipGun {
|
pub struct ShipGun {
|
||||||
|
/// The kind of gun this is
|
||||||
pub kind: content::Gun,
|
pub kind: content::Gun,
|
||||||
|
|
||||||
|
/// How many seconds we must wait before this gun can fire
|
||||||
pub cooldown: f32,
|
pub cooldown: f32,
|
||||||
|
|
||||||
|
/// Index of the gunpoint this gun is attached to
|
||||||
pub point: usize,
|
pub point: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShipGun {
|
impl ShipGun {
|
||||||
pub fn new(kind: content::Gun, point: usize) -> Self {
|
/// Make a new shipgun
|
||||||
|
pub fn new(kind: &content::Gun, point: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: kind,
|
kind: kind.clone(),
|
||||||
point,
|
point,
|
||||||
cooldown: 0.0,
|
cooldown: 0.0,
|
||||||
}
|
}
|
||||||
|
@ -62,15 +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.
|
||||||
#[derive(Debug)]
|
#[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 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>,
|
||||||
|
@ -80,14 +87,15 @@ pub struct ShipOutfits {
|
||||||
engine_flare_sprites: Vec<ObjectSubSprite>,
|
engine_flare_sprites: Vec<ObjectSubSprite>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ShipOutfits {
|
impl<'a> OutfitSet {
|
||||||
|
/// Make a new outfit array
|
||||||
pub fn new(content: &content::Ship) -> Self {
|
pub fn new(content: &content::Ship) -> Self {
|
||||||
Self {
|
Self {
|
||||||
stats: OutfitStatSum::new(),
|
stats: OutfitStatSum::new(),
|
||||||
outfits: Vec::new(),
|
outfits: Vec::new(),
|
||||||
guns: Vec::new(),
|
guns: Vec::new(),
|
||||||
available_space: content.space.clone(),
|
available_space: content.space.clone(),
|
||||||
total_space: content.space.clone(),
|
//total_space: content.space.clone(),
|
||||||
enginepoints: content.engines.clone(),
|
enginepoints: content.engines.clone(),
|
||||||
gunpoints: content.guns.clone(),
|
gunpoints: content.guns.clone(),
|
||||||
engine_flare_sprites: vec![],
|
engine_flare_sprites: vec![],
|
||||||
|
@ -95,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,
|
||||||
}
|
}
|
||||||
|
@ -105,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() {
|
||||||
|
@ -143,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() {
|
||||||
|
@ -154,10 +165,12 @@ impl<'a> ShipOutfits {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate over all guns in this outfitset
|
||||||
pub fn iter_guns(&mut self) -> impl Iterator<Item = &mut ShipGun> {
|
pub fn iter_guns(&mut self) -> impl Iterator<Item = &mut ShipGun> {
|
||||||
self.guns.iter_mut()
|
self.guns.iter_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterate over all guns and the gunpoints they're attached to
|
||||||
pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &content::GunPoint)> {
|
pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &content::GunPoint)> {
|
||||||
self.guns
|
self.guns
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
|
@ -165,6 +178,7 @@ impl<'a> ShipOutfits {
|
||||||
.map(|(a, b)| (b, a))
|
.map(|(a, b)| (b, a))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update engine flare sprites
|
||||||
pub fn update_engine_flares(&mut self) {
|
pub fn update_engine_flares(&mut self) {
|
||||||
// TODO: better way to pick flare texture
|
// TODO: better way to pick flare texture
|
||||||
self.engine_flare_sprites.clear();
|
self.engine_flare_sprites.clear();
|
||||||
|
@ -190,6 +204,7 @@ impl<'a> ShipOutfits {
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the sprites we should show if this ship is firing its engines
|
||||||
pub fn get_engine_flares(&self) -> Vec<ObjectSubSprite> {
|
pub fn get_engine_flares(&self) -> Vec<ObjectSubSprite> {
|
||||||
return self.engine_flare_sprites.clone();
|
return self.engine_flare_sprites.clone();
|
||||||
}
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
|
use crate::SystemObject;
|
||||||
|
use galactica_content as content;
|
||||||
use galactica_render::ObjectSprite;
|
use galactica_render::ObjectSprite;
|
||||||
|
|
||||||
use super::SystemObject;
|
|
||||||
use crate::content;
|
|
||||||
|
|
||||||
pub struct System {
|
pub struct System {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
bodies: Vec<SystemObject>,
|
bodies: Vec<SystemObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl System {
|
impl System {
|
||||||
pub fn new(ct: &content::System) -> Self {
|
pub fn new(ct: &content::Content, handle: content::SystemHandle) -> Self {
|
||||||
|
let sys = ct.get_system(handle);
|
||||||
let mut s = System {
|
let mut s = System {
|
||||||
name: ct.name.clone(),
|
name: sys.name.clone(),
|
||||||
bodies: Vec::new(),
|
bodies: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for o in &ct.objects {
|
for o in &sys.objects {
|
||||||
s.bodies.push(SystemObject {
|
s.bodies.push(SystemObject {
|
||||||
pos: o.position,
|
pos: o.position,
|
||||||
sprite_texture: o.sprite_texture,
|
sprite_texture: o.sprite_texture,
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::content;
|
|
||||||
use cgmath::{Deg, Point3};
|
use cgmath::{Deg, Point3};
|
||||||
|
|
||||||
|
use galactica_content as content;
|
||||||
use galactica_render::ObjectSprite;
|
use galactica_render::ObjectSprite;
|
||||||
|
|
||||||
pub struct SystemObject {
|
pub struct SystemObject {
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Internal crates
|
# Internal crates
|
||||||
galactica-content = { path = "../content" }
|
galactica-content = { path = "../content" }
|
||||||
|
galactica-constants = { path = "../constants" }
|
||||||
|
|
||||||
# Misc helpers
|
# Misc helpers
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
use crate::consts_main;
|
|
||||||
use cgmath::Matrix4;
|
|
||||||
|
|
||||||
// We can draw at most this many sprites on the screen.
|
|
||||||
// TODO: compile-time option or config file
|
|
||||||
pub const SPRITE_INSTANCE_LIMIT: u64 = 500;
|
|
||||||
|
|
||||||
// Must be small enough to fit in an i32
|
|
||||||
pub const STARFIELD_INSTANCE_LIMIT: u64 = consts_main::STARFIELD_COUNT * 24;
|
|
||||||
|
|
||||||
/// Shader entry points
|
|
||||||
pub const SHADER_MAIN_VERTEX: &'static str = "vertex_main";
|
|
||||||
pub const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main";
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
|
||||||
1.0, 0.0, 0.0, 0.0,
|
|
||||||
0.0, 1.0, 0.0, 0.0,
|
|
||||||
0.0, 0.0, 0.5, 0.5,
|
|
||||||
0.0, 0.0, 0.0, 1.0,
|
|
||||||
);
|
|
|
@ -1,13 +1,13 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector3};
|
use cgmath::{Deg, EuclideanSpace, Matrix4, Point2, Vector3};
|
||||||
|
use galactica_constants;
|
||||||
use std::{iter, rc::Rc};
|
use std::{iter, rc::Rc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit::{self, dpi::LogicalSize, window::Window};
|
use winit::{self, dpi::LogicalSize, window::Window};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts::{OPENGL_TO_WGPU_MATRIX, SPRITE_INSTANCE_LIMIT, STARFIELD_INSTANCE_LIMIT},
|
content,
|
||||||
consts_main, content,
|
|
||||||
globaldata::{GlobalData, GlobalDataContent},
|
globaldata::{GlobalData, GlobalDataContent},
|
||||||
pipeline::PipelineBuilder,
|
pipeline::PipelineBuilder,
|
||||||
sprite::ObjectSubSprite,
|
sprite::ObjectSubSprite,
|
||||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
types::{SpriteInstance, StarfieldInstance, TexturedVertex},
|
types::{SpriteInstance, StarfieldInstance, TexturedVertex},
|
||||||
VertexBuffer,
|
VertexBuffer,
|
||||||
},
|
},
|
||||||
ObjectSprite, UiSprite,
|
ObjectSprite, UiSprite, OPENGL_TO_WGPU_MATRIX,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A high-level GPU wrapper. Consumes game state,
|
/// A high-level GPU wrapper. Consumes game state,
|
||||||
|
@ -122,7 +122,7 @@ impl GPUState {
|
||||||
&device,
|
&device,
|
||||||
Some(SPRITE_VERTICES),
|
Some(SPRITE_VERTICES),
|
||||||
Some(SPRITE_INDICES),
|
Some(SPRITE_INDICES),
|
||||||
SPRITE_INSTANCE_LIMIT,
|
galactica_constants::SPRITE_INSTANCE_LIMIT,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
|
starfield: Rc::new(VertexBuffer::new::<TexturedVertex, StarfieldInstance>(
|
||||||
|
@ -130,7 +130,7 @@ impl GPUState {
|
||||||
&device,
|
&device,
|
||||||
Some(SPRITE_VERTICES),
|
Some(SPRITE_VERTICES),
|
||||||
Some(SPRITE_INDICES),
|
Some(SPRITE_INDICES),
|
||||||
STARFIELD_INSTANCE_LIMIT,
|
galactica_constants::STARFIELD_INSTANCE_LIMIT,
|
||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -409,7 +409,7 @@ impl GPUState {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce sprite limit
|
// Enforce sprite limit
|
||||||
if instances.len() as u64 > SPRITE_INSTANCE_LIMIT {
|
if instances.len() as u64 > galactica_constants::SPRITE_INSTANCE_LIMIT {
|
||||||
// TODO: no panic, handle this better.
|
// TODO: no panic, handle this better.
|
||||||
panic!("Sprite limit exceeded!")
|
panic!("Sprite limit exceeded!")
|
||||||
}
|
}
|
||||||
|
@ -476,17 +476,17 @@ impl GPUState {
|
||||||
bytemuck::cast_slice(&[GlobalDataContent {
|
bytemuck::cast_slice(&[GlobalDataContent {
|
||||||
camera_position: camera_pos.into(),
|
camera_position: camera_pos.into(),
|
||||||
camera_zoom: [camera_zoom, 0.0],
|
camera_zoom: [camera_zoom, 0.0],
|
||||||
camera_zoom_limits: [consts_main::ZOOM_MIN, consts_main::ZOOM_MAX],
|
camera_zoom_limits: [galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX],
|
||||||
window_size: [
|
window_size: [
|
||||||
self.window_size.width as f32,
|
self.window_size.width as f32,
|
||||||
self.window_size.height as f32,
|
self.window_size.height as f32,
|
||||||
],
|
],
|
||||||
window_aspect: [self.window_aspect, 0.0],
|
window_aspect: [self.window_aspect, 0.0],
|
||||||
starfield_texture: [self.texture_array.get_starfield_texture().index, 0],
|
starfield_texture: [self.texture_array.get_starfield_texture().index, 0],
|
||||||
starfield_tile_size: [consts_main::STARFIELD_SIZE as f32, 0.0],
|
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
||||||
starfield_size_limits: [
|
starfield_size_limits: [
|
||||||
consts_main::STARFIELD_SIZE_MIN,
|
galactica_constants::STARFIELD_SIZE_MIN,
|
||||||
consts_main::STARFIELD_SIZE_MAX,
|
galactica_constants::STARFIELD_SIZE_MAX,
|
||||||
],
|
],
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
//! and the only one external code should interact with.
|
//! and the only one external code should interact with.
|
||||||
//! (Excluding data structs, like [`ObjectSprite`])
|
//! (Excluding data structs, like [`ObjectSprite`])
|
||||||
|
|
||||||
mod consts;
|
|
||||||
mod globaldata;
|
mod globaldata;
|
||||||
mod gpustate;
|
mod gpustate;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
@ -16,9 +15,20 @@ mod starfield;
|
||||||
mod texturearray;
|
mod texturearray;
|
||||||
mod vertexbuffer;
|
mod vertexbuffer;
|
||||||
|
|
||||||
// TODO: remove
|
|
||||||
mod consts_main;
|
|
||||||
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
pub use gpustate::GPUState;
|
pub use gpustate::GPUState;
|
||||||
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, UiSprite};
|
pub use sprite::{AnchoredUiPosition, ObjectSprite, ObjectSubSprite, UiSprite};
|
||||||
|
|
||||||
|
use cgmath::Matrix4;
|
||||||
|
|
||||||
|
/// Shader entry points
|
||||||
|
pub(crate) const SHADER_MAIN_VERTEX: &'static str = "vertex_main";
|
||||||
|
pub(crate) const SHADER_MAIN_FRAGMENT: &'static str = "fragment_main";
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
pub(crate) const OPENGL_TO_WGPU_MATRIX: Matrix4<f32> = Matrix4::new(
|
||||||
|
1.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 0.5, 0.5,
|
||||||
|
0.0, 0.0, 0.0, 1.0,
|
||||||
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use wgpu;
|
use wgpu;
|
||||||
|
|
||||||
use crate::consts::{SHADER_MAIN_FRAGMENT, SHADER_MAIN_VERTEX};
|
|
||||||
use crate::vertexbuffer::VertexBuffer;
|
use crate::vertexbuffer::VertexBuffer;
|
||||||
|
use crate::{SHADER_MAIN_FRAGMENT, SHADER_MAIN_VERTEX};
|
||||||
|
|
||||||
pub struct PipelineBuilder<'a> {
|
pub struct PipelineBuilder<'a> {
|
||||||
// These are provided with new()
|
// These are provided with new()
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use cgmath::{Point2, Point3, Vector2, Vector3};
|
use cgmath::{Point2, Point3, Vector2, Vector3};
|
||||||
|
use galactica_constants;
|
||||||
use rand::{self, Rng};
|
use rand::{self, Rng};
|
||||||
|
|
||||||
use crate::{consts, consts_main, vertexbuffer::types::StarfieldInstance};
|
use crate::vertexbuffer::types::StarfieldInstance;
|
||||||
|
|
||||||
pub(crate) struct StarfieldStar {
|
pub(crate) struct StarfieldStar {
|
||||||
/// Star coordinates, in world space.
|
/// Star coordinates, in world space.
|
||||||
|
@ -33,16 +34,20 @@ impl Starfield {
|
||||||
pub fn regenerate(&mut self) {
|
pub fn regenerate(&mut self) {
|
||||||
// TODO: save seed in system, regenerate on jump
|
// TODO: save seed in system, regenerate on jump
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
let sz = consts_main::STARFIELD_SIZE as f32 / 2.0;
|
let sz = galactica_constants::STARFIELD_SIZE as f32 / 2.0;
|
||||||
self.stars = (0..consts_main::STARFIELD_COUNT)
|
self.stars = (0..galactica_constants::STARFIELD_COUNT)
|
||||||
.map(|_| StarfieldStar {
|
.map(|_| StarfieldStar {
|
||||||
pos: Point3 {
|
pos: Point3 {
|
||||||
x: rng.gen_range(-sz..=sz),
|
x: rng.gen_range(-sz..=sz),
|
||||||
y: rng.gen_range(-sz..=sz),
|
y: rng.gen_range(-sz..=sz),
|
||||||
z: rng.gen_range(consts_main::STARFIELD_Z_MIN..consts_main::STARFIELD_Z_MAX),
|
z: rng.gen_range(
|
||||||
|
galactica_constants::STARFIELD_Z_MIN..galactica_constants::STARFIELD_Z_MAX,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
size: rng
|
size: rng.gen_range(
|
||||||
.gen_range(consts_main::STARFIELD_SIZE_MIN..consts_main::STARFIELD_SIZE_MAX),
|
galactica_constants::STARFIELD_SIZE_MIN
|
||||||
|
..galactica_constants::STARFIELD_SIZE_MAX,
|
||||||
|
),
|
||||||
tint: Vector2 {
|
tint: Vector2 {
|
||||||
x: rng.gen_range(0.0..=1.0),
|
x: rng.gen_range(0.0..=1.0),
|
||||||
y: rng.gen_range(0.0..=1.0),
|
y: rng.gen_range(0.0..=1.0),
|
||||||
|
@ -52,17 +57,17 @@ impl Starfield {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_instances(&mut self, aspect: f32) -> Vec<StarfieldInstance> {
|
pub fn make_instances(&mut self, aspect: f32) -> Vec<StarfieldInstance> {
|
||||||
let sz = consts_main::STARFIELD_SIZE as f32;
|
let sz = galactica_constants::STARFIELD_SIZE as f32;
|
||||||
|
|
||||||
// Compute window size in starfield tiles
|
// Compute window size in starfield tiles
|
||||||
let mut nw_tile: Point2<i32> = {
|
let mut nw_tile: Point2<i32> = {
|
||||||
// Game coordinates (relative to camera) of nw corner of screen.
|
// Game coordinates (relative to camera) of nw corner of screen.
|
||||||
let clip_nw = Point2::from((aspect, 1.0)) * consts_main::ZOOM_MAX;
|
let clip_nw = Point2::from((aspect, 1.0)) * galactica_constants::ZOOM_MAX;
|
||||||
|
|
||||||
// Parallax correction.
|
// Parallax correction.
|
||||||
// Also, adjust v for mod to work properly
|
// Also, adjust v for mod to work properly
|
||||||
// (v is centered at 0)
|
// (v is centered at 0)
|
||||||
let v: Point2<f32> = clip_nw * consts_main::STARFIELD_Z_MIN;
|
let v: Point2<f32> = clip_nw * galactica_constants::STARFIELD_Z_MIN;
|
||||||
let v_adj: Point2<f32> = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into();
|
let v_adj: Point2<f32> = (v.x + (sz / 2.0), v.y + (sz / 2.0)).into();
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
@ -86,8 +91,10 @@ impl Starfield {
|
||||||
|
|
||||||
// Truncate tile grid to buffer size
|
// Truncate tile grid to buffer size
|
||||||
// (The window won't be full of stars if our instance limit is too small)
|
// (The window won't be full of stars if our instance limit is too small)
|
||||||
while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * consts_main::STARFIELD_COUNT as i32)
|
while ((nw_tile.x * 2 + 1)
|
||||||
> consts::STARFIELD_INSTANCE_LIMIT as i32
|
* (nw_tile.y * 2 + 1)
|
||||||
|
* galactica_constants::STARFIELD_COUNT as i32)
|
||||||
|
> galactica_constants::STARFIELD_INSTANCE_LIMIT as i32
|
||||||
{
|
{
|
||||||
nw_tile -= Vector2::from((1, 1));
|
nw_tile -= Vector2::from((1, 1));
|
||||||
}
|
}
|
||||||
|
@ -112,7 +119,7 @@ impl Starfield {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce starfield limit
|
// Enforce starfield limit
|
||||||
if instances.len() as u64 > consts::STARFIELD_INSTANCE_LIMIT {
|
if instances.len() as u64 > galactica_constants::STARFIELD_INSTANCE_LIMIT {
|
||||||
unreachable!("Starfield limit exceeded!")
|
unreachable!("Starfield limit exceeded!")
|
||||||
}
|
}
|
||||||
self.instance_count = instances.len() as u32;
|
self.instance_count = instances.len() as u32;
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "galactica-shipbehavior"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
galactica-content = { path = "../content" }
|
||||||
|
galactica-world = { path = "../world" }
|
||||||
|
cgmath = "0.18.0"
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::ShipBehavior;
|
||||||
|
use galactica_content as content;
|
||||||
|
use galactica_world::{ShipPhysicsHandle, World};
|
||||||
|
|
||||||
|
/// Dummy ship behavior.
|
||||||
|
/// Does nothing.
|
||||||
|
pub struct Dummy {
|
||||||
|
_handle: ShipPhysicsHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Dummy {
|
||||||
|
/// Create a new ship controller
|
||||||
|
pub fn new(handle: ShipPhysicsHandle) -> Self {
|
||||||
|
Self { _handle: handle }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShipBehavior for Dummy {
|
||||||
|
fn update_controls(&mut self, _physics: &mut World, _content: &content::Content) {}
|
||||||
|
fn get_handle(&self) -> ShipPhysicsHandle {
|
||||||
|
return self._handle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
//! Various implementations of [`crate::ShipBehavior`]
|
||||||
|
|
||||||
|
mod dummy;
|
||||||
|
mod player;
|
||||||
|
mod point;
|
||||||
|
|
||||||
|
pub use dummy::Dummy;
|
||||||
|
pub use player::Player;
|
||||||
|
pub use point::Point;
|
|
@ -0,0 +1,48 @@
|
||||||
|
use crate::ShipBehavior;
|
||||||
|
use galactica_content as content;
|
||||||
|
use galactica_world::{ShipPhysicsHandle, World};
|
||||||
|
|
||||||
|
/// Player ship behavior.
|
||||||
|
/// Controls a ship using controller input
|
||||||
|
pub struct Player {
|
||||||
|
handle: ShipPhysicsHandle,
|
||||||
|
|
||||||
|
/// Turn left
|
||||||
|
pub key_left: bool,
|
||||||
|
|
||||||
|
/// Turn right
|
||||||
|
pub key_right: bool,
|
||||||
|
|
||||||
|
/// Fire guns
|
||||||
|
pub key_guns: bool,
|
||||||
|
|
||||||
|
/// Foward thrust
|
||||||
|
pub key_thrust: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
/// Make a new ship controller
|
||||||
|
pub fn new(handle: ShipPhysicsHandle) -> Self {
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
key_left: false,
|
||||||
|
key_right: false,
|
||||||
|
key_guns: false,
|
||||||
|
key_thrust: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShipBehavior for Player {
|
||||||
|
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;
|
||||||
|
s.controls.guns = self.key_guns;
|
||||||
|
s.controls.thrust = self.key_thrust;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_handle(&self) -> ShipPhysicsHandle {
|
||||||
|
return self.handle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
use cgmath::{Deg, InnerSpace};
|
||||||
|
|
||||||
|
use crate::ShipBehavior;
|
||||||
|
use galactica_content as content;
|
||||||
|
use galactica_world::{util, ShipPhysicsHandle, World};
|
||||||
|
|
||||||
|
/// "Point" ship behavior.
|
||||||
|
/// Point and shoot towards the nearest enemy.
|
||||||
|
pub struct Point {
|
||||||
|
handle: ShipPhysicsHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
/// Create a new ship controller
|
||||||
|
pub fn new(handle: ShipPhysicsHandle) -> Self {
|
||||||
|
Self { handle }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShipBehavior for Point {
|
||||||
|
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;
|
||||||
|
s.controls.right = false;
|
||||||
|
s.controls.guns = false;
|
||||||
|
s.controls.thrust = false;
|
||||||
|
|
||||||
|
let (my_s, my_r) = physics.get_ship_body(&self.handle).unwrap();
|
||||||
|
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.ship.faction);
|
||||||
|
|
||||||
|
// Iterate all possible targets
|
||||||
|
let mut it = physics
|
||||||
|
.iter_ship_body()
|
||||||
|
.filter(
|
||||||
|
|(s, _)| match my_faction.relationships.get(&s.ship.faction).unwrap() {
|
||||||
|
content::Relationship::Hostile => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.map(|(_, r)| r);
|
||||||
|
|
||||||
|
// Find the closest target
|
||||||
|
let mut closest_enemy_position = match it.next() {
|
||||||
|
Some(c) => util::rigidbody_position(c),
|
||||||
|
None => return, // Do nothing if no targets are available
|
||||||
|
};
|
||||||
|
let mut d = (my_position - closest_enemy_position).magnitude();
|
||||||
|
for r in it {
|
||||||
|
let p = util::rigidbody_position(r);
|
||||||
|
let new_d = (my_position - p).magnitude();
|
||||||
|
if new_d < d {
|
||||||
|
d = new_d;
|
||||||
|
closest_enemy_position = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let angle_delta: Deg<f32> = (my_rotation)
|
||||||
|
.angle(closest_enemy_position - my_position)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let s = physics.get_ship_mut(&self.handle).unwrap();
|
||||||
|
s.controls.left = false;
|
||||||
|
s.controls.right = false;
|
||||||
|
|
||||||
|
if angle_delta < Deg(0.0) && my_angvel > -0.3 {
|
||||||
|
s.controls.right = true;
|
||||||
|
} else if angle_delta > Deg(0.0) && my_angvel < 0.3 {
|
||||||
|
s.controls.left = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.controls.guns = true;
|
||||||
|
s.controls.thrust = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_handle(&self) -> ShipPhysicsHandle {
|
||||||
|
return self.handle;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
|
//! Computer-controlled ship behaviors
|
||||||
|
|
||||||
|
pub mod behavior;
|
||||||
|
|
||||||
|
use galactica_content as content;
|
||||||
|
use galactica_world::{ShipPhysicsHandle, World};
|
||||||
|
|
||||||
|
/// Main behavior trait. Any struct that implements this
|
||||||
|
/// may be used to control a ship.
|
||||||
|
pub trait ShipBehavior
|
||||||
|
where
|
||||||
|
Self: Send,
|
||||||
|
{
|
||||||
|
/// Update a ship's controls based on world state
|
||||||
|
fn update_controls(&mut self, physics: &mut World, content: &content::Content);
|
||||||
|
|
||||||
|
/// Get the ship this behavior is attached to
|
||||||
|
fn get_handle(&self) -> ShipPhysicsHandle;
|
||||||
|
}
|
|
@ -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"
|
|
@ -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);
|
|
@ -1,7 +1,6 @@
|
||||||
//! This module contains game objects that may interact with the physics engine.
|
//! This module contains game objects that may interact with the physics engine.
|
||||||
|
|
||||||
mod projectile;
|
mod projectile;
|
||||||
mod ship;
|
mod ship;
|
||||||
|
|
||||||
pub use projectile::{Projectile, ProjectileBuilder};
|
pub use projectile::ProjectileWorldObject;
|
||||||
pub use ship::Ship;
|
pub use ship::ShipWorldObject;
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,12 @@
|
||||||
|
//! Conversion utilities
|
||||||
|
|
||||||
use cgmath::{Point2, Vector2};
|
use cgmath::{Point2, Vector2};
|
||||||
use nalgebra;
|
use nalgebra;
|
||||||
use rapier2d::dynamics::RigidBody;
|
use rapier2d::dynamics::RigidBody;
|
||||||
|
|
||||||
|
// TODO: Migrate to nalgebra fully, remove these converters
|
||||||
|
|
||||||
|
/// Convert a rigidbody position to a position in game coordinates
|
||||||
pub fn rigidbody_position(r: &RigidBody) -> cgmath::Point2<f32> {
|
pub fn rigidbody_position(r: &RigidBody) -> cgmath::Point2<f32> {
|
||||||
Point2 {
|
Point2 {
|
||||||
x: r.translation()[0],
|
x: r.translation()[0],
|
||||||
|
@ -9,6 +14,7 @@ pub fn rigidbody_position(r: &RigidBody) -> cgmath::Point2<f32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a rigidbody rotation to a rotation in game coordinates
|
||||||
pub fn rigidbody_rotation(r: &RigidBody) -> Vector2<f32> {
|
pub fn rigidbody_rotation(r: &RigidBody) -> Vector2<f32> {
|
||||||
Vector2 {
|
Vector2 {
|
||||||
x: r.rotation().re,
|
x: r.rotation().re,
|
||||||
|
@ -16,6 +22,7 @@ pub fn rigidbody_rotation(r: &RigidBody) -> Vector2<f32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a rigidbody velocity to a velocity in game coordinates
|
||||||
pub fn rigidbody_velocity(r: &RigidBody) -> cgmath::Vector2<f32> {
|
pub fn rigidbody_velocity(r: &RigidBody) -> cgmath::Vector2<f32> {
|
||||||
let v = r.velocity_at_point(&nalgebra::Point2::new(
|
let v = r.velocity_at_point(&nalgebra::Point2::new(
|
||||||
r.translation()[0],
|
r.translation()[0],
|
|
@ -0,0 +1,283 @@
|
||||||
|
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::{ActiveEvents, ChannelEventCollector},
|
||||||
|
};
|
||||||
|
use std::{collections::HashMap, f32::consts::PI};
|
||||||
|
|
||||||
|
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 World {
|
||||||
|
wrapper: Wrapper,
|
||||||
|
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
|
||||||
|
ships: HashMap<ColliderHandle, objects::ShipWorldObject>,
|
||||||
|
|
||||||
|
collision_handler: ChannelEventCollector,
|
||||||
|
collision_queue: Receiver<CollisionEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private methods
|
||||||
|
impl<'a> World {
|
||||||
|
fn remove_projectile(&mut self, c: ColliderHandle) {
|
||||||
|
let p = match self.projectiles.remove(&c) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
self.wrapper.rigid_body_set.remove(
|
||||||
|
p.rigid_body,
|
||||||
|
&mut self.wrapper.im,
|
||||||
|
&mut self.wrapper.collider_set,
|
||||||
|
&mut self.wrapper.ij,
|
||||||
|
&mut self.wrapper.mj,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_ship(&mut self, h: ShipPhysicsHandle) {
|
||||||
|
self.wrapper.rigid_body_set.remove(
|
||||||
|
h.0,
|
||||||
|
&mut self.wrapper.im,
|
||||||
|
&mut self.wrapper.collider_set,
|
||||||
|
&mut self.wrapper.ij,
|
||||||
|
&mut self.wrapper.mj,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
self.ships.remove(&h.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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<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
|
||||||
|
impl<'a> World {
|
||||||
|
/// Create a new physics system
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
||||||
|
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
wrapper: Wrapper::new(),
|
||||||
|
projectiles: HashMap::new(),
|
||||||
|
ships: HashMap::new(),
|
||||||
|
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
|
||||||
|
collision_queue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a ship to this physics system
|
||||||
|
/// TODO: decouple from Ship::new()
|
||||||
|
pub fn add_ship(
|
||||||
|
&mut self,
|
||||||
|
ct: &content::Content,
|
||||||
|
ship: object::Ship,
|
||||||
|
position: Point2<f32>,
|
||||||
|
) -> ShipPhysicsHandle {
|
||||||
|
let ship_content = ct.get_ship(ship.handle);
|
||||||
|
let cl = ColliderBuilder::convex_decomposition(
|
||||||
|
&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(ship_content.mass);
|
||||||
|
|
||||||
|
let rb = RigidBodyBuilder::dynamic()
|
||||||
|
.angular_damping(ship_content.angular_drag)
|
||||||
|
.linear_damping(ship_content.linear_drag)
|
||||||
|
.translation(vector![position.x, position.y])
|
||||||
|
.can_sleep(false);
|
||||||
|
|
||||||
|
let r = self.wrapper.rigid_body_set.insert(rb.build());
|
||||||
|
let c = self.wrapper.collider_set.insert_with_parent(
|
||||||
|
cl.build(),
|
||||||
|
r,
|
||||||
|
&mut self.wrapper.rigid_body_set,
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
|
// TODO: Clean this mess
|
||||||
|
let mut ps = Vec::new();
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
for (_, s) in &mut self.ships {
|
||||||
|
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];
|
||||||
|
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 (s, p) in ps {
|
||||||
|
self.add_projectiles(&s, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update physics
|
||||||
|
self.wrapper.step(t, &self.collision_handler);
|
||||||
|
|
||||||
|
// Handle collision events
|
||||||
|
while let Ok(event) = &self.collision_queue.try_recv() {
|
||||||
|
if event.started() {
|
||||||
|
let a = &event.collider1();
|
||||||
|
let b = &event.collider2();
|
||||||
|
|
||||||
|
// If projectiles are part of this collision, make sure
|
||||||
|
// `a` is one of them.
|
||||||
|
let (a, b) = if self.projectiles.contains_key(b) {
|
||||||
|
(b, a)
|
||||||
|
} else {
|
||||||
|
(a, b)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(p) = self.projectiles.get(a) {
|
||||||
|
if let Some(s) = self.ships.get_mut(b) {
|
||||||
|
let hit = s.ship.handle_projectile_collision(ct, &p.projectile);
|
||||||
|
if hit {
|
||||||
|
self.remove_projectile(*a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete projectiles
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
for (_, p) in &mut self.projectiles {
|
||||||
|
p.projectile.tick(t);
|
||||||
|
if p.projectile.is_expired() {
|
||||||
|
to_remove.push(p.collider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in to_remove {
|
||||||
|
self.remove_projectile(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a rigid body from a handle
|
||||||
|
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> &RigidBody {
|
||||||
|
&self.wrapper.rigid_body_set[r]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a ship from a handle
|
||||||
|
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: &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<Item = (&objects::ShipWorldObject, &RigidBody)> + '_ {
|
||||||
|
self.ships.values().map(|x| {
|
||||||
|
(
|
||||||
|
x,
|
||||||
|
self.wrapper.rigid_body_set.get(x.physics_handle.0).unwrap(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all ship sprites in this physics system
|
||||||
|
pub fn get_ship_sprites(
|
||||||
|
&'a self,
|
||||||
|
ct: &'a content::Content,
|
||||||
|
) -> impl Iterator<Item = ObjectSprite> + '_ {
|
||||||
|
self.ships
|
||||||
|
.values()
|
||||||
|
.map(|x| x.get_sprite(ct, &self.wrapper.rigid_body_set[x.physics_handle.0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all projectile sprites in this physics system
|
||||||
|
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
|
||||||
|
self.projectiles
|
||||||
|
.values()
|
||||||
|
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ use rapier2d::{
|
||||||
pipeline::{EventHandler, PhysicsPipeline},
|
pipeline::{EventHandler, PhysicsPipeline},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(super) struct Wrapper {
|
pub(crate) struct Wrapper {
|
||||||
pub rigid_body_set: RigidBodySet,
|
pub rigid_body_set: RigidBodySet,
|
||||||
pub collider_set: ColliderSet,
|
pub collider_set: ColliderSet,
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
pub const ZOOM_MIN: f32 = 200.0;
|
|
||||||
pub const ZOOM_MAX: f32 = 2000.0;
|
|
||||||
|
|
||||||
/// Name of starfield texture
|
|
||||||
pub const STARFIELD_TEXTURE_NAME: &'static str = "starfield";
|
|
||||||
|
|
||||||
/// Root directory of game content
|
|
||||||
pub const CONTENT_ROOT: &'static str = "./content";
|
|
||||||
|
|
||||||
/// Root directory of game textures
|
|
||||||
pub const TEXTURE_ROOT: &'static str = "./assets/render";
|
|
100
src/game/game.rs
100
src/game/game.rs
|
@ -1,15 +1,15 @@
|
||||||
use cgmath::{Deg, InnerSpace, Point2};
|
use cgmath::{Deg, InnerSpace, Point2};
|
||||||
use galactica_render::{AnchoredUiPosition, ObjectSprite, UiSprite};
|
use content::SystemHandle;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
||||||
|
|
||||||
use super::{camera::Camera, outfits, system::System};
|
use super::camera::Camera;
|
||||||
use crate::{
|
use crate::{content, inputstatus::InputStatus};
|
||||||
consts, content,
|
use galactica_constants;
|
||||||
inputstatus::InputStatus,
|
use galactica_gameobject as object;
|
||||||
physics::{util, Physics, ShipHandle},
|
use galactica_render::{AnchoredUiPosition, ObjectSprite, UiSprite};
|
||||||
shipbehavior::{self, ShipBehavior},
|
use galactica_shipbehavior::{behavior, ShipBehavior};
|
||||||
};
|
use galactica_world::{util, ShipPhysicsHandle, World};
|
||||||
|
|
||||||
struct Ui {}
|
struct Ui {}
|
||||||
|
|
||||||
|
@ -20,8 +20,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();
|
||||||
|
@ -70,58 +70,69 @@ 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: object::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>>,
|
||||||
|
playerbehavior: behavior::Player,
|
||||||
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 = outfits::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],
|
|
||||||
outfits::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: 1 },
|
||||||
|
o1,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut o2 = outfits::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();
|
||||||
shipbehaviors.push(shipbehavior::Player::new(h1));
|
shipbehaviors.push(Box::new(behavior::Dummy::new(h2)));
|
||||||
shipbehaviors.push(shipbehavior::Dummy::new(h2));
|
shipbehaviors.push(Box::new(behavior::Point::new(h3)));
|
||||||
shipbehaviors.push(shipbehavior::Point::new(h3));
|
|
||||||
|
|
||||||
Game {
|
Game {
|
||||||
last_update: Instant::now(),
|
last_update: Instant::now(),
|
||||||
|
@ -132,13 +143,14 @@ impl Game {
|
||||||
pos: (0.0, 0.0).into(),
|
pos: (0.0, 0.0).into(),
|
||||||
zoom: 500.0,
|
zoom: 500.0,
|
||||||
},
|
},
|
||||||
system: System::new(&ct.systems[0]),
|
system: object::System::new(&ct, SystemHandle { index: 0 }),
|
||||||
|
|
||||||
paused: false,
|
paused: false,
|
||||||
time_scale: 1.0,
|
time_scale: 1.0,
|
||||||
physics,
|
physics,
|
||||||
shipbehaviors,
|
shipbehaviors,
|
||||||
content: ct,
|
content: ct,
|
||||||
|
playerbehavior: behavior::Player::new(h1),
|
||||||
ui: Ui::new(),
|
ui: Ui::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,11 +179,19 @@ impl Game {
|
||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
||||||
|
|
||||||
|
self.playerbehavior.key_guns = self.input.key_guns;
|
||||||
|
self.playerbehavior.key_thrust = self.input.key_thrust;
|
||||||
|
self.playerbehavior.key_right = self.input.key_right;
|
||||||
|
self.playerbehavior.key_left = self.input.key_left;
|
||||||
|
self.playerbehavior
|
||||||
|
.update_controls(&mut self.physics, &self.content);
|
||||||
|
|
||||||
self.shipbehaviors.retain_mut(|b| {
|
self.shipbehaviors.retain_mut(|b| {
|
||||||
|
// Remove shipbehaviors of destroyed ships
|
||||||
if self.physics.get_ship_mut(&b.get_handle()).is_none() {
|
if self.physics.get_ship_mut(&b.get_handle()).is_none() {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
b.update_controls(&mut self.physics, &self.input, &self.content);
|
b.update_controls(&mut self.physics, &self.content);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -179,8 +199,8 @@ impl Game {
|
||||||
self.physics.step(t, &self.content);
|
self.physics.step(t, &self.content);
|
||||||
|
|
||||||
if self.input.v_scroll != 0.0 {
|
if self.input.v_scroll != 0.0 {
|
||||||
self.camera.zoom =
|
self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
|
||||||
(self.camera.zoom + self.input.v_scroll).clamp(consts::ZOOM_MIN, consts::ZOOM_MAX);
|
.clamp(galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX);
|
||||||
self.input.v_scroll = 0.0;
|
self.input.v_scroll = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +220,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)
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
//! This module contains high-level game control routines.
|
//! This module is responsible for high-level game control logic.
|
||||||
|
|
||||||
mod camera;
|
mod camera;
|
||||||
mod game;
|
mod game;
|
||||||
pub mod outfits;
|
|
||||||
mod system;
|
|
||||||
mod systemobject;
|
|
||||||
|
|
||||||
pub use game::Game;
|
pub use game::Game;
|
||||||
pub use systemobject::SystemObject;
|
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -1,13 +1,10 @@
|
||||||
mod consts;
|
|
||||||
mod game;
|
mod game;
|
||||||
mod inputstatus;
|
mod inputstatus;
|
||||||
mod objects;
|
|
||||||
mod physics;
|
|
||||||
mod shipbehavior;
|
|
||||||
|
|
||||||
pub use galactica_content as content;
|
pub use galactica_content as content;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use galactica_constants;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Event, KeyboardInput, WindowEvent},
|
event::{Event, KeyboardInput, WindowEvent},
|
||||||
|
@ -18,9 +15,9 @@ use winit::{
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
// TODO: error if missing
|
// TODO: error if missing
|
||||||
let content = content::Content::load_dir(
|
let content = content::Content::load_dir(
|
||||||
PathBuf::from(consts::CONTENT_ROOT),
|
PathBuf::from(galactica_constants::CONTENT_ROOT),
|
||||||
PathBuf::from(consts::TEXTURE_ROOT),
|
PathBuf::from(galactica_constants::TEXTURE_ROOT),
|
||||||
consts::STARFIELD_TEXTURE_NAME.to_owned(),
|
galactica_constants::STARFIELD_TEXTURE_NAME.to_owned(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
use cgmath::{Deg, InnerSpace, Point3, Vector2};
|
|
||||||
use galactica_render::ObjectSprite;
|
|
||||||
use rapier2d::{
|
|
||||||
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
|
|
||||||
geometry::{ColliderBuilder, ColliderHandle},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{content, physics::util};
|
|
||||||
|
|
||||||
pub struct ProjectileBuilder {
|
|
||||||
pub rigid_body: RigidBodyBuilder,
|
|
||||||
pub collider: ColliderBuilder,
|
|
||||||
pub sprite_texture: content::TextureHandle,
|
|
||||||
pub lifetime: f32,
|
|
||||||
pub size: f32,
|
|
||||||
pub damage: f32,
|
|
||||||
pub faction: content::FactionHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProjectileBuilder {
|
|
||||||
pub fn build(self, r: RigidBodyHandle, c: ColliderHandle) -> Projectile {
|
|
||||||
Projectile {
|
|
||||||
rigid_body: r,
|
|
||||||
collider: c,
|
|
||||||
sprite_texture: self.sprite_texture,
|
|
||||||
lifetime: self.lifetime,
|
|
||||||
size: self.size,
|
|
||||||
damage: self.damage,
|
|
||||||
faction: self.faction,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Projectile {
|
|
||||||
pub rigid_body: RigidBodyHandle,
|
|
||||||
pub collider: ColliderHandle,
|
|
||||||
pub sprite_texture: content::TextureHandle,
|
|
||||||
pub lifetime: f32,
|
|
||||||
pub size: f32,
|
|
||||||
pub damage: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,180 +0,0 @@
|
||||||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2};
|
|
||||||
use content::{FactionHandle, TextureHandle};
|
|
||||||
use galactica_render::ObjectSprite;
|
|
||||||
use nalgebra::vector;
|
|
||||||
use rand::Rng;
|
|
||||||
use rapier2d::{
|
|
||||||
dynamics::{RigidBody, RigidBodyBuilder},
|
|
||||||
geometry::ColliderBuilder,
|
|
||||||
pipeline::ActiveEvents,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::ProjectileBuilder;
|
|
||||||
use crate::{
|
|
||||||
content,
|
|
||||||
game::outfits,
|
|
||||||
physics::{util, ShipHandle},
|
|
||||||
};
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Ship {
|
|
||||||
pub physics_handle: ShipHandle,
|
|
||||||
pub faction: FactionHandle,
|
|
||||||
pub hull: f32,
|
|
||||||
pub controls: ShipControls,
|
|
||||||
|
|
||||||
outfits: outfits::ShipOutfits,
|
|
||||||
sprite_texture: TextureHandle,
|
|
||||||
size: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ship {
|
|
||||||
pub fn new(
|
|
||||||
c: &content::Ship,
|
|
||||||
outfits: 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
mod physics;
|
|
||||||
pub mod util;
|
|
||||||
mod wrapper;
|
|
||||||
|
|
||||||
pub use physics::Physics;
|
|
||||||
|
|
||||||
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
pub struct ShipHandle(pub(super) RigidBodyHandle, ColliderHandle);
|
|
|
@ -1,219 +0,0 @@
|
||||||
use cgmath::Point2;
|
|
||||||
use crossbeam::channel::Receiver;
|
|
||||||
use galactica_render::ObjectSprite;
|
|
||||||
use nalgebra::vector;
|
|
||||||
use rapier2d::{
|
|
||||||
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
|
|
||||||
geometry::{ColliderBuilder, ColliderHandle, CollisionEvent},
|
|
||||||
pipeline::ChannelEventCollector,
|
|
||||||
};
|
|
||||||
use std::{collections::HashMap, f32::consts::PI};
|
|
||||||
|
|
||||||
use super::{wrapper::Wrapper, ShipHandle};
|
|
||||||
use crate::{content, game::outfits, objects};
|
|
||||||
|
|
||||||
/// Keeps track of all objects in the world that we can interact with.
|
|
||||||
/// Also wraps our physics engine
|
|
||||||
pub struct Physics {
|
|
||||||
wrapper: Wrapper,
|
|
||||||
projectiles: HashMap<ColliderHandle, objects::Projectile>,
|
|
||||||
ships: HashMap<ColliderHandle, objects::Ship>,
|
|
||||||
|
|
||||||
collision_handler: ChannelEventCollector,
|
|
||||||
collision_queue: Receiver<CollisionEvent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Private methods
|
|
||||||
impl Physics {
|
|
||||||
fn remove_projectile(&mut self, c: ColliderHandle) {
|
|
||||||
let p = match self.projectiles.remove(&c) {
|
|
||||||
Some(p) => p,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
self.wrapper.rigid_body_set.remove(
|
|
||||||
p.rigid_body,
|
|
||||||
&mut self.wrapper.im,
|
|
||||||
&mut self.wrapper.collider_set,
|
|
||||||
&mut self.wrapper.ij,
|
|
||||||
&mut self.wrapper.mj,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_ship(&mut self, h: ShipHandle) {
|
|
||||||
self.wrapper.rigid_body_set.remove(
|
|
||||||
h.0,
|
|
||||||
&mut self.wrapper.im,
|
|
||||||
&mut self.wrapper.collider_set,
|
|
||||||
&mut self.wrapper.ij,
|
|
||||||
&mut self.wrapper.mj,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
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, pb.build(r, c));
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public methods
|
|
||||||
impl Physics {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
|
||||||
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
wrapper: Wrapper::new(),
|
|
||||||
projectiles: HashMap::new(),
|
|
||||||
ships: HashMap::new(),
|
|
||||||
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
|
|
||||||
collision_queue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_ship(
|
|
||||||
&mut self,
|
|
||||||
ct: &content::Ship,
|
|
||||||
outfits: outfits::ShipOutfits,
|
|
||||||
position: Point2<f32>,
|
|
||||||
faction: content::FactionHandle,
|
|
||||||
) -> ShipHandle {
|
|
||||||
let cl = ColliderBuilder::convex_decomposition(
|
|
||||||
&ct.collision.points[..],
|
|
||||||
&ct.collision.indices[..],
|
|
||||||
)
|
|
||||||
// Rotate collider to match sprite
|
|
||||||
// (Collider starts pointing east, sprite starts pointing north.)
|
|
||||||
.rotation(PI / -2.0)
|
|
||||||
.mass(ct.mass);
|
|
||||||
|
|
||||||
let rb = RigidBodyBuilder::dynamic()
|
|
||||||
.angular_damping(ct.angular_drag)
|
|
||||||
.linear_damping(ct.linear_drag)
|
|
||||||
.translation(vector![position.x, position.y])
|
|
||||||
.can_sleep(false);
|
|
||||||
|
|
||||||
let r = self.wrapper.rigid_body_set.insert(rb.build());
|
|
||||||
let c = self.wrapper.collider_set.insert_with_parent(
|
|
||||||
cl.build(),
|
|
||||||
r,
|
|
||||||
&mut self.wrapper.rigid_body_set,
|
|
||||||
);
|
|
||||||
|
|
||||||
let h = ShipHandle(r, c);
|
|
||||||
self.ships
|
|
||||||
.insert(c, objects::Ship::new(ct, outfits, h, faction));
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn step(&mut self, t: f32, ct: &content::Content) {
|
|
||||||
// Run ship updates
|
|
||||||
let mut res = Vec::new();
|
|
||||||
let mut to_remove = Vec::new();
|
|
||||||
for (_, s) in &mut self.ships {
|
|
||||||
if s.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));
|
|
||||||
}
|
|
||||||
for r in to_remove {
|
|
||||||
self.remove_ship(r);
|
|
||||||
}
|
|
||||||
for r in res {
|
|
||||||
for p in r.projectiles {
|
|
||||||
self.add_projectile(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update physics
|
|
||||||
self.wrapper.step(t, &self.collision_handler);
|
|
||||||
|
|
||||||
// Handle collision events
|
|
||||||
while let Ok(event) = &self.collision_queue.try_recv() {
|
|
||||||
if event.started() {
|
|
||||||
let a = &event.collider1();
|
|
||||||
let b = &event.collider2();
|
|
||||||
|
|
||||||
// If projectiles are a part of this collision, make sure
|
|
||||||
// `a` is one of them.
|
|
||||||
let (a, b) = if self.projectiles.contains_key(b) {
|
|
||||||
(b, a)
|
|
||||||
} else {
|
|
||||||
(a, b)
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete projectiles
|
|
||||||
let mut to_remove = Vec::new();
|
|
||||||
for (_, p) in &mut self.projectiles {
|
|
||||||
p.tick(t);
|
|
||||||
if p.is_expired() {
|
|
||||||
to_remove.push(p.collider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i in to_remove {
|
|
||||||
self.remove_projectile(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_rigid_body(&self, r: RigidBodyHandle) -> &RigidBody {
|
|
||||||
&self.wrapper.rigid_body_set[r]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_ship_mut(&mut self, s: &ShipHandle) -> Option<&mut objects::Ship> {
|
|
||||||
self.ships.get_mut(&s.1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_ship_body(&self, s: &ShipHandle) -> Option<(&objects::Ship, &RigidBody)> {
|
|
||||||
// TODO: handle dead handles
|
|
||||||
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_ship_body(&self) -> impl Iterator<Item = (&objects::Ship, &RigidBody)> + '_ {
|
|
||||||
self.ships.values().map(|x| {
|
|
||||||
(
|
|
||||||
x,
|
|
||||||
self.wrapper.rigid_body_set.get(x.physics_handle.0).unwrap(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_ship_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
|
|
||||||
self.ships
|
|
||||||
.values()
|
|
||||||
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.physics_handle.0]))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
|
|
||||||
self.projectiles
|
|
||||||
.values()
|
|
||||||
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,149 +0,0 @@
|
||||||
use cgmath::{Deg, InnerSpace};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
content,
|
|
||||||
inputstatus::InputStatus,
|
|
||||||
physics::{util, Physics, ShipHandle},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub trait ShipBehavior
|
|
||||||
where
|
|
||||||
Self: Send,
|
|
||||||
{
|
|
||||||
fn update_controls(
|
|
||||||
&mut self,
|
|
||||||
physics: &mut Physics,
|
|
||||||
input: &InputStatus,
|
|
||||||
content: &content::Content,
|
|
||||||
);
|
|
||||||
fn get_handle(&self) -> ShipHandle;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Dummy {
|
|
||||||
_handle: ShipHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dummy {
|
|
||||||
pub fn new(handle: ShipHandle) -> Box<Self> {
|
|
||||||
Box::new(Self { _handle: handle })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipBehavior for Dummy {
|
|
||||||
fn update_controls(
|
|
||||||
&mut self,
|
|
||||||
_physics: &mut Physics,
|
|
||||||
_input: &InputStatus,
|
|
||||||
_content: &content::Content,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
fn get_handle(&self) -> ShipHandle {
|
|
||||||
return self._handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Player {
|
|
||||||
handle: ShipHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Player {
|
|
||||||
pub fn new(handle: ShipHandle) -> Box<Self> {
|
|
||||||
Box::new(Self { handle })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipBehavior for Player {
|
|
||||||
fn update_controls(
|
|
||||||
&mut self,
|
|
||||||
physics: &mut Physics,
|
|
||||||
input: &InputStatus,
|
|
||||||
_content: &content::Content,
|
|
||||||
) {
|
|
||||||
let s = physics.get_ship_mut(&self.handle).unwrap();
|
|
||||||
s.controls.left = input.key_left;
|
|
||||||
s.controls.right = input.key_right;
|
|
||||||
s.controls.guns = input.key_guns;
|
|
||||||
s.controls.thrust = input.key_thrust;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_handle(&self) -> ShipHandle {
|
|
||||||
return self.handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Point {
|
|
||||||
handle: ShipHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Point {
|
|
||||||
pub fn new(handle: ShipHandle) -> Box<Self> {
|
|
||||||
Box::new(Self { handle })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipBehavior for Point {
|
|
||||||
fn update_controls(
|
|
||||||
&mut self,
|
|
||||||
physics: &mut Physics,
|
|
||||||
_input: &InputStatus,
|
|
||||||
content: &content::Content,
|
|
||||||
) {
|
|
||||||
// Turn off all controls
|
|
||||||
let s = physics.get_ship_mut(&self.handle).unwrap();
|
|
||||||
s.controls.left = false;
|
|
||||||
s.controls.right = false;
|
|
||||||
s.controls.guns = false;
|
|
||||||
s.controls.thrust = false;
|
|
||||||
|
|
||||||
let (my_s, my_r) = physics.get_ship_body(&self.handle).unwrap();
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Iterate all possible targets
|
|
||||||
let mut it = physics
|
|
||||||
.iter_ship_body()
|
|
||||||
.filter(|(s, _)| match my_faction.relationships[&s.faction] {
|
|
||||||
content::Relationship::Hostile => true,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.map(|(_, r)| r);
|
|
||||||
|
|
||||||
// Find the closest target
|
|
||||||
let mut closest_enemy_position = match it.next() {
|
|
||||||
Some(c) => util::rigidbody_position(c),
|
|
||||||
None => return, // Do nothing if no targets are available
|
|
||||||
};
|
|
||||||
let mut d = (my_position - closest_enemy_position).magnitude();
|
|
||||||
for r in it {
|
|
||||||
let p = util::rigidbody_position(r);
|
|
||||||
let new_d = (my_position - p).magnitude();
|
|
||||||
if new_d < d {
|
|
||||||
d = new_d;
|
|
||||||
closest_enemy_position = p;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let angle_delta: Deg<f32> = (my_rotation)
|
|
||||||
.angle(closest_enemy_position - my_position)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let s = physics.get_ship_mut(&self.handle).unwrap();
|
|
||||||
s.controls.left = false;
|
|
||||||
s.controls.right = false;
|
|
||||||
|
|
||||||
if angle_delta < Deg(0.0) && my_angvel > -0.3 {
|
|
||||||
s.controls.right = true;
|
|
||||||
} else if angle_delta > Deg(0.0) && my_angvel < 0.3 {
|
|
||||||
s.controls.left = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
s.controls.guns = true;
|
|
||||||
s.controls.thrust = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_handle(&self) -> ShipHandle {
|
|
||||||
return self.handle;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue