Compare commits

..

2 Commits

Author SHA1 Message Date
Mark eaa8a7383c
Renamed a few objects 2024-01-09 21:45:30 -08:00
Mark 5ceff9f039
Updated TODO 2024-01-09 21:45:13 -08:00
36 changed files with 399 additions and 420 deletions

14
Cargo.lock generated
View File

@ -581,9 +581,9 @@ dependencies = [
"cgmath", "cgmath",
"galactica-constants", "galactica-constants",
"galactica-content", "galactica-content",
"galactica-gameobject", "galactica-galaxy",
"galactica-render", "galactica-render",
"galactica-world", "galactica-systemsim",
"pollster", "pollster",
"wgpu", "wgpu",
"winit", "winit",
@ -608,7 +608,7 @@ dependencies = [
] ]
[[package]] [[package]]
name = "galactica-gameobject" name = "galactica-galaxy"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"cgmath", "cgmath",
@ -637,9 +637,9 @@ dependencies = [
"cgmath", "cgmath",
"galactica-constants", "galactica-constants",
"galactica-content", "galactica-content",
"galactica-gameobject", "galactica-galaxy",
"galactica-packer", "galactica-packer",
"galactica-world", "galactica-systemsim",
"image", "image",
"rand", "rand",
"wgpu", "wgpu",
@ -647,13 +647,13 @@ dependencies = [
] ]
[[package]] [[package]]
name = "galactica-world" name = "galactica-systemsim"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"cgmath", "cgmath",
"crossbeam", "crossbeam",
"galactica-content", "galactica-content",
"galactica-gameobject", "galactica-galaxy",
"nalgebra", "nalgebra",
"rand", "rand",
"rapier2d", "rapier2d",

View File

@ -45,8 +45,8 @@ readme = ""
galactica-constants = { path = "crates/constants" } galactica-constants = { path = "crates/constants" }
galactica-content = { path = "crates/content" } galactica-content = { path = "crates/content" }
galactica-render = { path = "crates/render" } galactica-render = { path = "crates/render" }
galactica-world = { path = "crates/world" } galactica-systemsim = { path = "crates/systemsim" }
galactica-gameobject = { path = "crates/gameobject" } galactica-galaxy = { path = "crates/galaxy" }
galactica-packer = { path = "crates/packer" } galactica-packer = { path = "crates/packer" }
galactica = { path = "crates/galactica" } galactica = { path = "crates/galactica" }

View File

@ -1,12 +1,7 @@
## Specific Jobs ## Specific Jobs
- Start documenting - Start documenting
- Check for handle leaks - Check for handle leaks
- Rename and crtl-f comments
- gameobject and world
- behavior and personality
- ship (content) / ship (data) / ship (world)
- Don't allocate each frame - Don't allocate each frame
- UI: text arranger - UI: text arranger
- Sound system - Sound system
- Ship death debris - Ship death debris

View File

@ -24,8 +24,8 @@ workspace = true
galactica-content = { workspace = true } galactica-content = { workspace = true }
galactica-render = { workspace = true } galactica-render = { workspace = true }
galactica-constants = { workspace = true } galactica-constants = { workspace = true }
galactica-world = { workspace = true } galactica-systemsim = { workspace = true }
galactica-gameobject = { workspace = true } galactica-galaxy = { workspace = true }
winit = { workspace = true } winit = { workspace = true }
wgpu = { workspace = true } wgpu = { workspace = true }

View File

@ -1,4 +1,5 @@
use object::{ship::ShipPersonality, GameData, GameShipHandle}; use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle};
use galactica_galaxy::{ship::ShipPersonality, Galaxy, GxShipHandle};
use std::time::Instant; use std::time::Instant;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
@ -6,70 +7,66 @@ use crate::camera::Camera;
use crate::inputstatus::InputStatus; use crate::inputstatus::InputStatus;
use galactica_constants; use galactica_constants;
use galactica_content as content;
use galactica_gameobject as object;
use galactica_render::RenderState; use galactica_render::RenderState;
use galactica_world::{objects::ShipControls, util, ParticleBuilder, StepResources, World}; use galactica_systemsim::{objects::ShipControls, util, ParticleBuilder, StepResources, SystemSim};
pub struct Game { pub struct Game {
input: InputStatus, input: InputStatus,
last_update: Instant, last_update: Instant,
player: GameShipHandle, player: GxShipHandle,
paused: bool, paused: bool,
time_scale: f32, time_scale: f32,
start_instant: Instant, start_instant: Instant,
camera: Camera, camera: Camera,
// TODO: include system in world galaxy: Galaxy,
//system: object::System, content: Content,
gamedata: GameData, systemsim: SystemSim,
content: content::Content,
world: World,
new_particles: Vec<ParticleBuilder>, new_particles: Vec<ParticleBuilder>,
} }
impl Game { impl Game {
pub fn new(ct: content::Content) -> Self { pub fn new(ct: Content) -> Self {
let mut gamedata = GameData::new(&ct); let mut galaxy = Galaxy::new(&ct);
let player = gamedata.create_ship( let player = galaxy.create_ship(
&ct, &ct,
content::ShipHandle { index: 0 }, ShipHandle { index: 0 },
content::FactionHandle { index: 0 }, FactionHandle { index: 0 },
ShipPersonality::Player, ShipPersonality::Player,
&content::SystemHandle { index: 0 }, &SystemHandle { index: 0 },
); );
let s = gamedata.get_ship_mut(player).unwrap(); let s = galaxy.get_ship_mut(player).unwrap();
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 0 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
let a = gamedata.create_ship( let a = galaxy.create_ship(
&ct, &ct,
content::ShipHandle { index: 0 }, ShipHandle { index: 0 },
content::FactionHandle { index: 1 }, FactionHandle { index: 1 },
ShipPersonality::Dummy, ShipPersonality::Dummy,
&content::SystemHandle { index: 0 }, &SystemHandle { index: 0 },
); );
let s = gamedata.get_ship_mut(a).unwrap(); let s = galaxy.get_ship_mut(a).unwrap();
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 0 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
let a = gamedata.create_ship( let a = galaxy.create_ship(
&ct, &ct,
content::ShipHandle { index: 0 }, ShipHandle { index: 0 },
content::FactionHandle { index: 0 }, FactionHandle { index: 0 },
ShipPersonality::Point, ShipPersonality::Point,
&content::SystemHandle { index: 0 }, &SystemHandle { index: 0 },
); );
let s = gamedata.get_ship_mut(a).unwrap(); let s = galaxy.get_ship_mut(a).unwrap();
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 0 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 1 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); s.add_outfit(&ct.get_outfit(OutfitHandle { index: 2 }));
let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 }); let physics = SystemSim::new(&ct, &galaxy, SystemHandle { index: 0 });
Game { Game {
last_update: Instant::now(), last_update: Instant::now(),
@ -85,8 +82,8 @@ impl Game {
//system: object::System::new(&ct, SystemHandle { index: 0 }), //system: object::System::new(&ct, SystemHandle { index: 0 }),
paused: false, paused: false,
time_scale: 1.0, time_scale: 1.0,
world: physics, systemsim: physics,
gamedata, galaxy,
content: ct, content: ct,
new_particles: Vec::new(), new_particles: Vec::new(),
} }
@ -120,9 +117,9 @@ 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.gamedata.step(t); self.galaxy.step(t);
self.world.step(StepResources { self.systemsim.step(StepResources {
player: self.player, player: self.player,
player_controls: ShipControls { player_controls: ShipControls {
left: self.input.key_left, left: self.input.key_left,
@ -131,7 +128,7 @@ impl Game {
guns: self.input.key_guns, guns: self.input.key_guns,
}, },
ct: &self.content, ct: &self.content,
dt: &mut self.gamedata, gx: &mut self.galaxy,
particles: &mut self.new_particles, particles: &mut self.new_particles,
t, t,
}); });
@ -143,8 +140,8 @@ impl Game {
} }
self.camera.pos = { self.camera.pos = {
let o = self.world.get_ship(self.player).unwrap(); let o = self.systemsim.get_ship(self.player).unwrap();
let r = self.world.get_rigid_body(o.rigid_body).unwrap(); let r = self.systemsim.get_rigid_body(o.rigid_body).unwrap();
util::rigidbody_position(r) util::rigidbody_position(r)
}; };
@ -157,11 +154,11 @@ impl Game {
camera_zoom: self.camera.zoom, camera_zoom: self.camera.zoom,
current_time: self.start_instant.elapsed().as_secs_f32(), current_time: self.start_instant.elapsed().as_secs_f32(),
content: &self.content, content: &self.content,
world: &self.world, // TODO: maybe system should be stored here? systemsim: &self.systemsim, // TODO: maybe system should be stored here?
particles: &mut self.new_particles, particles: &mut self.new_particles,
player_data: self.player, player_data: self.player,
data: &self.gamedata, data: &self.galaxy,
current_system: content::SystemHandle { index: 0 }, current_system: SystemHandle { index: 0 },
} }
} }
} }

View File

@ -2,10 +2,9 @@ mod camera;
mod game; mod game;
mod inputstatus; mod inputstatus;
pub use galactica_content as content;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use galactica_constants::{self, ASSET_CACHE}; use galactica_constants::{ASSET_CACHE, CONTENT_ROOT, IMAGE_ROOT, STARFIELD_SPRITE_NAME};
use galactica_content::Content;
use std::{ use std::{
fs, fs,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -27,11 +26,11 @@ fn main() -> Result<()> {
} }
// TODO: pretty error if missing // TODO: pretty error if missing
let content = content::Content::load_dir( let content = Content::load_dir(
PathBuf::from(galactica_constants::CONTENT_ROOT), PathBuf::from(CONTENT_ROOT),
PathBuf::from(galactica_constants::IMAGE_ROOT), PathBuf::from(IMAGE_ROOT),
atlas_index, atlas_index,
galactica_constants::STARFIELD_SPRITE_NAME.to_owned(), STARFIELD_SPRITE_NAME.to_owned(),
)?; )?;
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();

View File

@ -1,5 +1,5 @@
[package] [package]
name = "galactica-gameobject" name = "galactica-galaxy"
description = "Galactica's game data manager" description = "Galactica's game data manager"
categories = { workspace = true } categories = { workspace = true }
keywords = { workspace = true } keywords = { workspace = true }

View File

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

View File

@ -4,7 +4,7 @@ use galactica_content::ShipHandle;
/// A lightweight representation of a ship in the galaxy /// A lightweight representation of a ship in the galaxy
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct GameShipHandle { pub struct GxShipHandle {
/// This ship's unique index /// This ship's unique index
pub(crate) index: u64, pub(crate) index: u64,
@ -13,20 +13,20 @@ pub struct GameShipHandle {
pub(crate) content: ShipHandle, pub(crate) content: ShipHandle,
} }
impl GameShipHandle { impl GxShipHandle {
pub fn content_handle(&self) -> ShipHandle { pub fn content_handle(&self) -> ShipHandle {
self.content self.content
} }
} }
impl Hash for GameShipHandle { impl Hash for GxShipHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state) self.index.hash(state)
} }
} }
impl Eq for GameShipHandle {} impl Eq for GxShipHandle {}
impl PartialEq for GameShipHandle { impl PartialEq for GxShipHandle {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.index.eq(&other.index) self.index.eq(&other.index)
} }

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

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

View File

@ -1,7 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use content::{GunPoint, OutfitHandle, OutfitSpace, SpriteHandle}; use galactica_content::{Content, GunPoint, Outfit, OutfitHandle, OutfitSpace, SpriteHandle};
use galactica_content as content;
/// Possible outcomes when adding an outfit /// Possible outcomes when adding an outfit
pub enum OutfitAddResult { pub enum OutfitAddResult {
@ -89,7 +88,7 @@ impl OutfitSet {
} }
} }
pub(super) fn add(&mut self, o: &content::Outfit) -> OutfitAddResult { pub(super) fn add(&mut self, o: &Outfit) -> OutfitAddResult {
if !(self.total_space - self.used_space).can_contain(&o.space) { if !(self.total_space - self.used_space).can_contain(&o.space) {
return OutfitAddResult::NotEnoughSpace("TODO".to_string()); return OutfitAddResult::NotEnoughSpace("TODO".to_string());
} }
@ -129,7 +128,7 @@ impl OutfitSet {
return OutfitAddResult::Ok; return OutfitAddResult::Ok;
} }
pub(super) fn remove(&mut self, o: &content::Outfit) -> OutfitRemoveResult { pub(super) fn remove(&mut self, o: &Outfit) -> OutfitRemoveResult {
if !self.outfits.contains_key(&o.handle) { if !self.outfits.contains_key(&o.handle) {
return OutfitRemoveResult::NotExist; return OutfitRemoveResult::NotExist;
} else { } else {
@ -161,7 +160,7 @@ impl OutfitSet {
} }
// TODO: pick these better // TODO: pick these better
pub fn get_flare_sprite(&self, ct: &content::Content) -> Option<SpriteHandle> { pub fn get_flare_sprite(&self, ct: &Content) -> Option<SpriteHandle> {
for i in self.outfits.keys() { for i in self.outfits.keys() {
let c = ct.get_outfit(*i); let c = ct.get_outfit(*i);
if c.engine_flare_sprite.is_some() { if c.engine_flare_sprite.is_some() {

View File

@ -1,6 +1,5 @@
/// Computer-controlled ship behavior variants. /// Computer-controlled ship behavior variants.
/// This is just a list, actual physics-aware /// This is just a list, actual state-aware behaviors are implemented in each simulation crate.
/// behaviors are implemented in [`galactica-behavior`]
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShipPersonality { pub enum ShipPersonality {
/// This ship is controlled by a player /// This ship is controlled by a player

View File

@ -1,18 +1,17 @@
use std::{collections::HashMap, time::Instant}; use std::{collections::HashMap, time::Instant};
use crate::GameShipHandle; use crate::GxShipHandle;
use super::{OutfitSet, ShipPersonality}; use super::{OutfitSet, ShipPersonality};
use content::GunPoint; use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle};
use galactica_content as content;
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
#[derive(Debug)] #[derive(Debug)]
pub struct Ship { pub struct GxShip {
// Metadata values // Metadata values
handle: GameShipHandle, handle: GxShipHandle,
ct_handle: content::ShipHandle, ct_handle: ShipHandle,
faction: content::FactionHandle, faction: FactionHandle,
outfits: OutfitSet, outfits: OutfitSet,
personality: ShipPersonality, personality: ShipPersonality,
@ -29,16 +28,16 @@ pub struct Ship {
last_hit: Instant, last_hit: Instant,
} }
impl Ship { impl GxShip {
pub(crate) fn new( pub(crate) fn new(
ct: &content::Content, ct: &Content,
handle: GameShipHandle, handle: GxShipHandle,
ct_handle: content::ShipHandle, ct_handle: ShipHandle,
faction: content::FactionHandle, faction: FactionHandle,
personality: ShipPersonality, personality: ShipPersonality,
) -> Self { ) -> Self {
let s = ct.get_ship(ct_handle); let s = ct.get_ship(ct_handle);
Ship { GxShip {
handle, handle,
ct_handle, ct_handle,
faction, faction,
@ -55,14 +54,14 @@ impl Ship {
} }
/// Add an outfit to this ship /// Add an outfit to this ship
pub fn add_outfit(&mut self, o: &content::Outfit) -> super::OutfitAddResult { pub fn add_outfit(&mut self, o: &Outfit) -> super::OutfitAddResult {
let r = self.outfits.add(o); let r = self.outfits.add(o);
self.shields = self.outfits.get_shield_strength(); self.shields = self.outfits.get_shield_strength();
return r; return r;
} }
/// Remove an outfit from this ship /// Remove an outfit from this ship
pub fn remove_outfit(&mut self, o: &content::Outfit) -> super::OutfitRemoveResult { pub fn remove_outfit(&mut self, o: &Outfit) -> super::OutfitRemoveResult {
self.outfits.remove(o) self.outfits.remove(o)
} }
@ -75,7 +74,7 @@ impl Ship {
/// Will panic if `which` isn't a point on this ship. /// Will panic if `which` isn't a point on this ship.
/// Returns `true` if this gun was fired, /// Returns `true` if this gun was fired,
/// and `false` if it is on cooldown or empty. /// and `false` if it is on cooldown or empty.
pub fn fire_gun(&mut self, ct: &content::Content, which: &GunPoint) -> bool { pub fn fire_gun(&mut self, ct: &Content, which: &GunPoint) -> bool {
let c = self.gun_cooldowns.get_mut(which).unwrap(); let c = self.gun_cooldowns.get_mut(which).unwrap();
if *c > 0.0 { if *c > 0.0 {
@ -134,14 +133,14 @@ impl Ship {
} }
// Misc getters, so internal state is untouchable // Misc getters, so internal state is untouchable
impl Ship { impl GxShip {
/// Get a handle to this ship game object /// Get a handle to this ship game object
pub fn get_handle(&self) -> GameShipHandle { pub fn get_handle(&self) -> GxShipHandle {
self.handle self.handle
} }
/// Get a handle to this ship's content /// Get a handle to this ship's content
pub fn get_content(&self) -> content::ShipHandle { pub fn get_content(&self) -> ShipHandle {
self.ct_handle self.ct_handle
} }
@ -168,12 +167,12 @@ impl Ship {
} }
/// Get this ship's faction /// Get this ship's faction
pub fn get_faction(&self) -> content::FactionHandle { pub fn get_faction(&self) -> FactionHandle {
self.faction self.faction
} }
/// Get this ship's content handle /// Get this ship's content handle
pub fn get_ship(&self) -> content::ShipHandle { pub fn get_ship(&self) -> ShipHandle {
self.ct_handle self.ct_handle
} }
} }

View File

@ -1,12 +0,0 @@
//! 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 gamedata;
mod handles;
pub mod ship;
pub use gamedata::*;
pub use handles::*;

View File

@ -20,8 +20,8 @@ workspace = true
galactica-content = { workspace = true } galactica-content = { workspace = true }
galactica-constants = { workspace = true } galactica-constants = { workspace = true }
galactica-packer = { workspace = true } galactica-packer = { workspace = true }
galactica-world = { workspace = true } galactica-systemsim = { workspace = true }
galactica-gameobject = { workspace = true } galactica-galaxy = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
cgmath = { workspace = true } cgmath = { workspace = true }

View File

@ -3,7 +3,7 @@
use std::f32::consts::TAU; use std::f32::consts::TAU;
use cgmath::{Deg, InnerSpace, Point2, Rad, Vector2}; use cgmath::{Deg, InnerSpace, Point2, Rad, Vector2};
use galactica_world::util; use galactica_systemsim::util;
use crate::{ use crate::{
vertexbuffer::{ vertexbuffer::{
@ -22,9 +22,9 @@ impl GPUState {
let system_object_scale = 1.0 / 600.0; let system_object_scale = 1.0 / 600.0;
let ship_scale = 1.0 / 10.0; let ship_scale = 1.0 / 10.0;
let player_world_object = state.world.get_ship(state.player_data).unwrap(); let player_world_object = state.systemsim.get_ship(state.player_data).unwrap();
let player_body = state let player_body = state
.world .systemsim
.get_rigid_body(player_world_object.rigid_body) .get_rigid_body(player_world_object.rigid_body)
.unwrap(); .unwrap();
@ -103,7 +103,7 @@ impl GPUState {
} }
// Draw ships // Draw ships
for (s, r) in state.world.iter_ship_body() { for (s, r) in state.systemsim.iter_ship_body() {
// This will be None if this ship is dead. // This will be None if this ship is dead.
// Stays around while the physics system runs a collapse sequence // Stays around while the physics system runs a collapse sequence
let color = match state.data.get_ship(s.data_handle) { let color = match state.data.get_ship(s.data_handle) {
@ -291,7 +291,7 @@ impl GPUState {
panic!("UI limit exceeded!") panic!("UI limit exceeded!")
} }
let player_world_object = state.world.get_ship(state.player_data).unwrap(); let player_world_object = state.systemsim.get_ship(state.player_data).unwrap();
let data = state let data = state
.data .data

View File

@ -3,13 +3,13 @@ use bytemuck;
use cgmath::Point2; use cgmath::Point2;
use galactica_constants; use galactica_constants;
use galactica_content::Content;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use std::{iter, rc::Rc}; use std::{iter, rc::Rc};
use wgpu::{self, BufferAddress}; use wgpu::{self, BufferAddress};
use winit::{self, window::Window}; use winit::{self, window::Window};
use crate::{ use crate::{
content,
globaluniform::{GlobalDataContent, GlobalUniform}, globaluniform::{GlobalDataContent, GlobalUniform},
pipeline::PipelineBuilder, pipeline::PipelineBuilder,
starfield::Starfield, starfield::Starfield,
@ -27,7 +27,7 @@ use crate::{
// Additional implementaitons for GPUState // Additional implementaitons for GPUState
mod hud; mod hud;
mod world; mod systemsim;
/// A high-level GPU wrapper. Consumes game state, /// A high-level GPU wrapper. Consumes game state,
/// produces pretty pictures. /// produces pretty pictures.
@ -108,7 +108,7 @@ fn preprocess_shader(
impl GPUState { impl GPUState {
/// Make a new GPUState that draws on `window` /// Make a new GPUState that draws on `window`
pub async fn new(window: Window, ct: &content::Content) -> Result<Self> { pub async fn new(window: Window, ct: &Content) -> Result<Self> {
let window_size = window.inner_size(); let window_size = window.inner_size();
let window_aspect = window_size.width as f32 / window_size.height as f32; let window_aspect = window_size.width as f32 / window_size.height as f32;
@ -364,9 +364,9 @@ impl GPUState {
// Order matters, it determines what is drawn on top. // Order matters, it determines what is drawn on top.
// The order inside ships and projectiles doesn't matter, // The order inside ships and projectiles doesn't matter,
// but ships should always be under projectiles. // but ships should always be under projectiles.
self.world_push_system(state, (clip_ne, clip_sw)); self.sysim_push_system(state, (clip_ne, clip_sw));
self.world_push_ship(state, (clip_ne, clip_sw)); self.sysim_push_ship(state, (clip_ne, clip_sw));
self.world_push_projectile(state, (clip_ne, clip_sw)); self.sysim_push_projectile(state, (clip_ne, clip_sw));
self.hud_add_radar(state); self.hud_add_radar(state);
self.hud_add_status(state); self.hud_add_status(state);

View File

@ -1,8 +1,8 @@
//! GPUState routines for drawing the world //! GPUState routines for drawing items in a systemsim
use bytemuck; use bytemuck;
use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2}; use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2};
use galactica_world::util; use galactica_systemsim::util;
use crate::{ use crate::{
globaluniform::ObjectData, globaluniform::ObjectData,
@ -11,14 +11,14 @@ use crate::{
}; };
impl GPUState { impl GPUState {
pub(super) fn world_push_ship( pub(super) fn sysim_push_ship(
&mut self, &mut self,
state: &RenderState, state: &RenderState,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
) { ) {
for s in state.world.iter_ships() { for s in state.systemsim.iter_ships() {
let r = state.world.get_rigid_body(s.rigid_body).unwrap(); let r = state.systemsim.get_rigid_body(s.rigid_body).unwrap();
let ship_pos = util::rigidbody_position(&r); let ship_pos = util::rigidbody_position(&r);
let ship_rot = util::rigidbody_rotation(r); let ship_rot = util::rigidbody_rotation(r);
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix! let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
@ -129,14 +129,14 @@ impl GPUState {
} }
} }
pub(super) fn world_push_projectile( pub(super) fn sysim_push_projectile(
&mut self, &mut self,
state: &RenderState, state: &RenderState,
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
) { ) {
for p in state.world.iter_projectiles() { for p in state.systemsim.iter_projectiles() {
let r = state.world.get_rigid_body(p.rigid_body).unwrap(); let r = state.systemsim.get_rigid_body(p.rigid_body).unwrap();
let proj_pos = util::rigidbody_position(&r); let proj_pos = util::rigidbody_position(&r);
let proj_rot = util::rigidbody_rotation(r); let proj_rot = util::rigidbody_rotation(r);
let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 }); let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 });
@ -201,7 +201,7 @@ impl GPUState {
} }
} }
pub(super) fn world_push_system( pub(super) fn sysim_push_system(
&mut self, &mut self,
state: &RenderState, state: &RenderState,
// NE and SW corners of screen // NE and SW corners of screen

View File

@ -17,7 +17,6 @@ mod texturearray;
mod vertexbuffer; mod vertexbuffer;
pub use anchoredposition::PositionAnchor; pub use anchoredposition::PositionAnchor;
use galactica_content as content;
pub use gpustate::GPUState; pub use gpustate::GPUState;
pub use renderstate::RenderState; pub use renderstate::RenderState;

View File

@ -1,7 +1,7 @@
use cgmath::Point2; use cgmath::Point2;
use galactica_content::{Content, SystemHandle}; use galactica_content::{Content, SystemHandle};
use galactica_gameobject::{GameData, GameShipHandle}; use galactica_galaxy::{Galaxy, GxShipHandle};
use galactica_world::{ParticleBuilder, World}; use galactica_systemsim::{ParticleBuilder, SystemSim};
/// Bundles parameters passed to a single call to GPUState::render /// Bundles parameters passed to a single call to GPUState::render
pub struct RenderState<'a> { pub struct RenderState<'a> {
@ -9,7 +9,7 @@ pub struct RenderState<'a> {
pub camera_pos: Point2<f32>, pub camera_pos: Point2<f32>,
/// Player ship data /// Player ship data
pub player_data: GameShipHandle, pub player_data: GxShipHandle,
/// The system we're currently in /// The system we're currently in
pub current_system: SystemHandle, pub current_system: SystemHandle,
@ -18,7 +18,7 @@ pub struct RenderState<'a> {
pub camera_zoom: f32, pub camera_zoom: f32,
/// The world state to render /// The world state to render
pub world: &'a World, pub systemsim: &'a SystemSim,
// TODO: handle overflow. is it a problem? // TODO: handle overflow. is it a problem?
/// The current time, in seconds /// The current time, in seconds
@ -28,7 +28,7 @@ pub struct RenderState<'a> {
pub content: &'a Content, pub content: &'a Content,
/// Game data /// Game data
pub data: &'a GameData, pub data: &'a Galaxy,
/// Particles to spawn during this frame /// Particles to spawn during this frame
pub particles: &'a mut Vec<ParticleBuilder>, pub particles: &'a mut Vec<ParticleBuilder>,

View File

@ -1,10 +1,8 @@
use crate::{ use crate::globaluniform::{AtlasArray, AtlasImageLocation, SpriteData, SpriteDataArray};
content,
globaluniform::{AtlasArray, AtlasImageLocation, SpriteData, SpriteDataArray},
};
use anyhow::Result; use anyhow::Result;
use bytemuck::Zeroable; use bytemuck::Zeroable;
use galactica_constants::ASSET_CACHE; use galactica_constants::ASSET_CACHE;
use galactica_content::Content;
use galactica_packer::SpriteAtlasImage; use galactica_packer::SpriteAtlasImage;
use image::GenericImageView; use image::GenericImageView;
use std::{fs::File, io::Read, num::NonZeroU32, path::Path}; use std::{fs::File, io::Read, num::NonZeroU32, path::Path};
@ -51,7 +49,7 @@ impl RawTexture {
view_formats: &[], view_formats: &[],
}); });
let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); let view = texture.create_view(&Default::default());
queue.write_texture( queue.write_texture(
wgpu::ImageCopyTexture { wgpu::ImageCopyTexture {
@ -91,7 +89,7 @@ pub struct TextureArray {
} }
impl TextureArray { impl TextureArray {
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &content::Content) -> Result<Self> { pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &Content) -> Result<Self> {
// Load all textures // Load all textures
let mut texture_data = Vec::new(); let mut texture_data = Vec::new();

View File

@ -1,6 +1,6 @@
[package] [package]
name = "galactica-world" name = "galactica-systemsim"
description = "World interactions for Galactica" description = "Physics interactions for Galactica"
categories = { workspace = true } categories = { workspace = true }
keywords = { workspace = true } keywords = { workspace = true }
version = { workspace = true } version = { workspace = true }
@ -18,7 +18,7 @@ workspace = true
[dependencies] [dependencies]
galactica-content = { workspace = true } galactica-content = { workspace = true }
galactica-gameobject = { workspace = true } galactica-galaxy = { workspace = true }
rapier2d = { workspace = true } rapier2d = { workspace = true }
nalgebra = { workspace = true } nalgebra = { workspace = true }

View File

@ -3,30 +3,30 @@
mod null; mod null;
mod point; mod point;
use std::collections::HashMap;
use galactica_gameobject::GameShipHandle;
pub use null::*; pub use null::*;
pub use point::Point; pub use point::Point;
use galactica_galaxy::GxShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet}; use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use std::collections::HashMap;
use crate::{ use crate::{
objects::{ShipControls, ShipWorldObject}, objects::{ShipControls, SySimShip},
StepResources, StepResources,
}; };
/// Main behavior trait. Any struct that implements this /// Ship controller trait. Any struct that implements this
/// may be used to control a ship. /// may be used to control a ship.
pub trait ShipBehavior { pub trait ShipController {
/// Update a ship's controls based on world state. /// Update a ship's controls based on system state.
/// This method does not return anything, it modifies /// This method does not return anything, it modifies
/// the ship's controls in-place. /// the ship's controls in-place.
fn update_controls( fn update_controls(
&mut self, &mut self,
res: &StepResources, res: &StepResources,
rigid_bodies: &RigidBodySet, rigid_bodies: &RigidBodySet,
ships: &HashMap<GameShipHandle, ShipWorldObject>, ships: &HashMap<GxShipHandle, SySimShip>,
this_ship: RigidBodyHandle, this_ship: RigidBodyHandle,
this_data: GameShipHandle, this_data: GxShipHandle,
) -> ShipControls; ) -> ShipControls;
} }

View File

@ -1,16 +1,15 @@
use galactica_galaxy::GxShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use std::collections::HashMap; use std::collections::HashMap;
use galactica_gameobject::GameShipHandle; use super::ShipController;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use super::ShipBehavior;
use crate::{ use crate::{
objects::{ShipControls, ShipWorldObject}, objects::{ShipControls, SySimShip},
StepResources, StepResources,
}; };
/// The Null behaviors is assigned to objects that are not controlled by the computer. /// The Null controller is assigned to objects that are static or not controlled by the computer.
/// Most notably, the player's ship has a Null behavior. /// Most notably, the player's ship has a Null controller.
pub struct Null {} pub struct Null {}
impl Null { impl Null {
@ -20,14 +19,14 @@ impl Null {
} }
} }
impl ShipBehavior for Null { impl ShipController for Null {
fn update_controls( fn update_controls(
&mut self, &mut self,
_res: &StepResources, _res: &StepResources,
_rigid_bodies: &RigidBodySet, _rigid_bodies: &RigidBodySet,
_ships: &HashMap<GameShipHandle, ShipWorldObject>, _ships: &HashMap<GxShipHandle, SySimShip>,
_this_ship: RigidBodyHandle, _this_ship: RigidBodyHandle,
_this_data: GameShipHandle, _this_data: GxShipHandle,
) -> ShipControls { ) -> ShipControls {
ShipControls::new() ShipControls::new()
} }

View File

@ -1,17 +1,16 @@
use cgmath::{Deg, InnerSpace}; use cgmath::{Deg, InnerSpace};
use galactica_content::Relationship;
use galactica_galaxy::GxShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet}; use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use std::collections::HashMap; use std::collections::HashMap;
use super::ShipController;
use crate::{ use crate::{
objects::{ShipControls, ShipWorldObject}, objects::{ShipControls, SySimShip},
util, StepResources, util, StepResources,
}; };
use galactica_content as content;
use galactica_gameobject::GameShipHandle;
use super::ShipBehavior; /// "Point" ship controller.
/// "Point" ship behavior.
/// Point and shoot towards the nearest enemy. /// Point and shoot towards the nearest enemy.
pub struct Point {} pub struct Point {}
@ -22,19 +21,19 @@ impl Point {
} }
} }
impl ShipBehavior for Point { impl ShipController for Point {
fn update_controls( fn update_controls(
&mut self, &mut self,
res: &StepResources, res: &StepResources,
rigid_bodies: &RigidBodySet, rigid_bodies: &RigidBodySet,
ships: &HashMap<GameShipHandle, ShipWorldObject>, ships: &HashMap<GxShipHandle, SySimShip>,
this_ship: RigidBodyHandle, this_ship: RigidBodyHandle,
this_data: GameShipHandle, this_data: GxShipHandle,
) -> ShipControls { ) -> ShipControls {
let mut controls = ShipControls::new(); let mut controls = ShipControls::new();
let this_rigidbody = rigid_bodies.get(this_ship).unwrap(); let this_rigidbody = rigid_bodies.get(this_ship).unwrap();
let my_data = res.dt.get_ship(this_data).unwrap(); let my_data = res.gx.get_ship(this_data).unwrap();
let my_position = util::rigidbody_position(this_rigidbody); let my_position = util::rigidbody_position(this_rigidbody);
let my_rotation = util::rigidbody_rotation(this_rigidbody); let my_rotation = util::rigidbody_rotation(this_rigidbody);
let my_angvel = this_rigidbody.angvel(); let my_angvel = this_rigidbody.angvel();
@ -44,10 +43,10 @@ impl ShipBehavior for Point {
let mut hostile_ships = ships let mut hostile_ships = ships
.values() .values()
.filter(|s| { .filter(|s| {
let data = res.dt.get_ship(s.data_handle); let data = res.gx.get_ship(s.data_handle);
if let Some(data) = data { if let Some(data) = data {
match my_faction.relationships.get(&data.get_faction()).unwrap() { match my_faction.relationships.get(&data.get_faction()).unwrap() {
content::Relationship::Hostile => true, Relationship::Hostile => true,
_ => false, _ => false,
} }
} else { } else {

View File

@ -1,16 +1,15 @@
#![warn(missing_docs)] #![warn(missing_docs)]
//! This module keeps track of the visible world. //! This module provides a physics-based simulation of one galaxy system.
//! Ships, projectiles, collisions, etc.
pub mod behavior; pub mod controller;
pub mod objects; pub mod objects;
mod particlebuilder; mod particlebuilder;
mod stepresources; mod stepresources;
mod systemsim;
pub mod util; pub mod util;
mod world;
mod wrapper; mod wrapper;
pub use particlebuilder::*; pub use particlebuilder::*;
pub use stepresources::*; pub use stepresources::*;
pub use world::World; pub use systemsim::SystemSim;

View File

@ -0,0 +1,143 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
use galactica_content::{CollapseEvent, Ship, ShipHandle};
use nalgebra::point;
use rand::{rngs::ThreadRng, Rng};
use rapier2d::{dynamics::RigidBody, geometry::Collider};
use crate::{util, ParticleBuilder, StepResources};
#[derive(Debug)]
pub(super) struct ShipCollapseSequence {
total_length: f32,
time_elapsed: f32,
rng: ThreadRng,
}
impl ShipCollapseSequence {
pub(super) fn new(total_length: f32) -> Self {
Self {
total_length,
time_elapsed: 0.0,
rng: rand::thread_rng(),
}
}
/// Has this sequence been fully played out?
pub(super) fn is_done(&self) -> bool {
self.time_elapsed >= self.total_length
}
/// Pick a random points inside a ship's collider
fn random_in_ship(&mut self, ship_content: &Ship, collider: &Collider) -> Vector2<f32> {
let mut y = 0.0;
let mut x = 0.0;
let mut a = false;
while !a {
y = self.rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
x = self.rng.gen_range(-1.0..=1.0) * ship_content.size * ship_content.sprite.aspect
/ 2.0;
a = collider.shape().contains_local_point(&point![x, y]);
}
Vector2 { x, y }
}
/// Step this sequence `t` seconds
pub(super) fn step(
&mut self,
res: &mut StepResources,
ship_handle: ShipHandle,
rigid_body: &mut RigidBody,
collider: &mut Collider,
) {
let ship_content = res.ct.get_ship(ship_handle);
let ship_pos = util::rigidbody_position(rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
// The fraction of this collapse sequence that has been played
let frac_done = self.time_elapsed / self.total_length;
// Trigger collapse events
for event in &ship_content.collapse.events {
match event {
CollapseEvent::Effect(event) => {
if (event.time > self.time_elapsed && event.time <= self.time_elapsed + res.t)
|| (event.time == 0.0 && self.time_elapsed == 0.0)
{
for spawner in &event.effects {
let effect = res.ct.get_effect(spawner.effect);
for _ in 0..spawner.count as usize {
let pos = if let Some(pos) = spawner.pos {
pos.to_vec()
} else {
self.random_in_ship(ship_content, collider)
};
let pos = ship_pos
+ (Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos);
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
res.particles.push(ParticleBuilder::from_content(
effect,
pos,
Rad::zero(),
Vector2 {
x: velocity.x,
y: velocity.y,
},
Vector2::zero(),
))
}
}
}
}
}
}
// Create collapse effects
for spawner in &ship_content.collapse.effects {
let effect = res.ct.get_effect(spawner.effect);
// Probability of adding a particle this frame.
// The area of this function over [0, 1] should be 1.
let pdf = |x: f32| {
let f = 0.2;
let y = if x < (1.0 - f) {
let x = x / (1.0 - f);
(x * x + 0.2) * 1.8 - f
} else {
1.0
};
return y;
};
let p_add = (res.t / self.total_length) * pdf(frac_done) * spawner.count;
if self.rng.gen_range(0.0..=1.0) <= p_add {
let pos = if let Some(pos) = spawner.pos {
pos.to_vec()
} else {
self.random_in_ship(ship_content, collider)
};
// Position, adjusted for ship rotation
let pos = ship_pos + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
let vel = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
res.particles.push(ParticleBuilder {
sprite: effect.sprite,
pos,
velocity: Vector2 { x: vel.x, y: vel.y },
angle: Rad::zero(),
angvel: Rad::zero(),
lifetime: effect.lifetime,
size: effect.size,
fade: 0.0,
});
}
}
self.time_elapsed += res.t;
}
}

View File

@ -1,6 +1,7 @@
//! 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 collapse;
mod projectile; mod projectile;
mod ship; mod ship;
pub use projectile::ProjectileWorldObject; pub use projectile::SySimProjectile;
pub use ship::{ShipControls, ShipWorldObject}; pub use ship::{ShipControls, SySimShip};

View File

@ -1,19 +1,18 @@
use galactica_content::{FactionHandle, Projectile};
use rand::Rng; use rand::Rng;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
use galactica_content as content; /// A single projectile in this sim
/// A single projectile in the world
#[derive(Debug)] #[derive(Debug)]
pub struct ProjectileWorldObject { pub struct SySimProjectile {
/// This projectile's game data /// This projectile's game data
pub content: content::Projectile, pub content: Projectile,
/// The remaining lifetime of this projectile, in seconds /// The remaining lifetime of this projectile, in seconds
pub lifetime: f32, pub lifetime: f32,
/// The faction this projectile belongs to /// The faction this projectile belongs to
pub faction: content::FactionHandle, pub faction: FactionHandle,
/// This projectile's rigidbody /// This projectile's rigidbody
pub rigid_body: RigidBodyHandle, pub rigid_body: RigidBodyHandle,
@ -25,18 +24,18 @@ pub struct ProjectileWorldObject {
pub size_rng: f32, pub size_rng: f32,
} }
impl ProjectileWorldObject { impl SySimProjectile {
/// Create a new projectile /// Create a new projectile
pub fn new( pub fn new(
content: content::Projectile, // TODO: use a handle content: Projectile, // TODO: use a handle
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
faction: content::FactionHandle, faction: FactionHandle,
collider: ColliderHandle, collider: ColliderHandle,
) -> Self { ) -> Self {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let size_rng = content.size_rng; let size_rng = content.size_rng;
let lifetime = content.lifetime; let lifetime = content.lifetime;
ProjectileWorldObject { SySimProjectile {
rigid_body, rigid_body,
collider, collider,
content, content,

View File

@ -1,16 +1,16 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
use content::{FactionHandle, ShipHandle}; use galactica_content::{Content, FactionHandle};
use galactica_galaxy::GxShipHandle;
use nalgebra::{point, vector}; use nalgebra::{point, vector};
use object::GameShipHandle; use rand::Rng;
use rand::{rngs::ThreadRng, Rng};
use rapier2d::{ use rapier2d::{
dynamics::{RigidBody, RigidBodyHandle}, dynamics::{RigidBody, RigidBodyHandle},
geometry::{Collider, ColliderHandle}, geometry::{Collider, ColliderHandle},
}; };
use crate::{util, ParticleBuilder, StepResources}; use crate::{util, ParticleBuilder, StepResources};
use galactica_content as content;
use galactica_gameobject as object; use super::collapse::ShipCollapseSequence;
/// A ship's controls /// A ship's controls
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -40,149 +40,9 @@ impl ShipControls {
} }
} }
#[derive(Debug)]
struct ShipCollapseSequence {
total_length: f32,
time_elapsed: f32,
rng: ThreadRng,
}
impl ShipCollapseSequence {
fn new(total_length: f32) -> Self {
Self {
total_length,
time_elapsed: 0.0,
rng: rand::thread_rng(),
}
}
/// Has this sequence been fully played out?
fn is_done(&self) -> bool {
self.time_elapsed >= self.total_length
}
/// Pick a random points inside a ship's collider
fn random_in_ship(
&mut self,
ship_content: &content::Ship,
collider: &Collider,
) -> Vector2<f32> {
let mut y = 0.0;
let mut x = 0.0;
let mut a = false;
while !a {
y = self.rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
x = self.rng.gen_range(-1.0..=1.0) * ship_content.size * ship_content.sprite.aspect
/ 2.0;
a = collider.shape().contains_local_point(&point![x, y]);
}
Vector2 { x, y }
}
/// Step this sequence `t` seconds
fn step(
&mut self,
res: &mut StepResources,
ship_handle: ShipHandle,
rigid_body: &mut RigidBody,
collider: &mut Collider,
) {
let ship_content = res.ct.get_ship(ship_handle);
let ship_pos = util::rigidbody_position(rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
// The fraction of this collapse sequence that has been played
let frac_done = self.time_elapsed / self.total_length;
// Trigger collapse events
for event in &ship_content.collapse.events {
match event {
content::CollapseEvent::Effect(event) => {
if (event.time > self.time_elapsed && event.time <= self.time_elapsed + res.t)
|| (event.time == 0.0 && self.time_elapsed == 0.0)
{
for spawner in &event.effects {
let effect = res.ct.get_effect(spawner.effect);
for _ in 0..spawner.count as usize {
let pos = if let Some(pos) = spawner.pos {
pos.to_vec()
} else {
self.random_in_ship(ship_content, collider)
};
let pos = ship_pos
+ (Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos);
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
res.particles.push(ParticleBuilder::from_content(
effect,
pos,
Rad::zero(),
Vector2 {
x: velocity.x,
y: velocity.y,
},
Vector2::zero(),
))
}
}
}
}
}
}
// Create collapse effects
for spawner in &ship_content.collapse.effects {
let effect = res.ct.get_effect(spawner.effect);
// Probability of adding a particle this frame.
// The area of this function over [0, 1] should be 1.
let pdf = |x: f32| {
let f = 0.2;
let y = if x < (1.0 - f) {
let x = x / (1.0 - f);
(x * x + 0.2) * 1.8 - f
} else {
1.0
};
return y;
};
let p_add = (res.t / self.total_length) * pdf(frac_done) * spawner.count;
if self.rng.gen_range(0.0..=1.0) <= p_add {
let pos = if let Some(pos) = spawner.pos {
pos.to_vec()
} else {
self.random_in_ship(ship_content, collider)
};
// Position, adjusted for ship rotation
let pos = ship_pos + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
let vel = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
res.particles.push(ParticleBuilder {
sprite: effect.sprite,
pos,
velocity: Vector2 { x: vel.x, y: vel.y },
angle: Rad::zero(),
angvel: Rad::zero(),
lifetime: effect.lifetime,
size: effect.size,
fade: 0.0,
});
}
}
self.time_elapsed += res.t;
}
}
/// A ship instance in the physics system /// A ship instance in the physics system
#[derive(Debug)] #[derive(Debug)]
pub struct ShipWorldObject { pub struct SySimShip {
/// This ship's physics handle /// This ship's physics handle
pub rigid_body: RigidBodyHandle, pub rigid_body: RigidBodyHandle,
@ -190,7 +50,7 @@ pub struct ShipWorldObject {
pub collider: ColliderHandle, pub collider: ColliderHandle,
/// This ship's game data /// This ship's game data
pub data_handle: GameShipHandle, pub data_handle: GxShipHandle,
/// This ship's controls /// This ship's controls
pub(crate) controls: ShipControls, pub(crate) controls: ShipControls,
@ -205,17 +65,17 @@ pub struct ShipWorldObject {
faction: FactionHandle, faction: FactionHandle,
} }
impl ShipWorldObject { impl SySimShip {
/// Make a new ship /// Make a new ship
pub(crate) fn new( pub(crate) fn new(
ct: &content::Content, ct: &Content,
data_handle: GameShipHandle, data_handle: GxShipHandle,
faction: FactionHandle, faction: FactionHandle,
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
collider: ColliderHandle, collider: ColliderHandle,
) -> Self { ) -> Self {
let ship_content = ct.get_ship(data_handle.content_handle()); let ship_content = ct.get_ship(data_handle.content_handle());
ShipWorldObject { SySimShip {
rigid_body, rigid_body,
collider, collider,
data_handle, data_handle,
@ -237,7 +97,7 @@ impl ShipWorldObject {
rigid_body: &mut RigidBody, rigid_body: &mut RigidBody,
collider: &mut Collider, collider: &mut Collider,
) { ) {
let ship_data = res.dt.get_ship(self.data_handle); let ship_data = res.gx.get_ship(self.data_handle);
if ship_data.is_none() { if ship_data.is_none() {
// If ship data is none, it has been removed because the ship has been destroyed. // If ship data is none, it has been removed because the ship has been destroyed.
// play collapse sequence. // play collapse sequence.
@ -259,7 +119,7 @@ impl ShipWorldObject {
rigid_body: &mut RigidBody, rigid_body: &mut RigidBody,
collider: &mut Collider, collider: &mut Collider,
) { ) {
let ship = res.dt.get_ship(self.data_handle).unwrap(); let ship = res.gx.get_ship(self.data_handle).unwrap();
let ship_content = res.ct.get_ship(self.data_handle.content_handle()); let ship_content = res.ct.get_ship(self.data_handle.content_handle());
let ship_pos = util::rigidbody_position(&rigid_body); let ship_pos = util::rigidbody_position(&rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body); let ship_rot = util::rigidbody_rotation(rigid_body);
@ -328,7 +188,7 @@ impl ShipWorldObject {
} }
} }
impl ShipWorldObject { impl SySimShip {
/// Get this ship's control state /// Get this ship's control state
pub fn get_controls(&self) -> &ShipControls { pub fn get_controls(&self) -> &ShipControls {
&self.controls &self.controls

View File

@ -1,12 +1,12 @@
use cgmath::{Matrix2, Point2, Rad, Vector2}; use cgmath::{Matrix2, Point2, Rad, Vector2};
use galactica_content as content; use galactica_content::{Effect, SpriteHandle};
use rand::Rng; use rand::Rng;
/// Instructions to create a new particle /// Instructions to create a new particle
#[derive(Debug)] #[derive(Debug)]
pub struct ParticleBuilder { pub struct ParticleBuilder {
/// The sprite to use for this particle /// The sprite to use for this particle
pub sprite: content::SpriteHandle, pub sprite: SpriteHandle,
/// This object's center, in world coordinates. /// This object's center, in world coordinates.
pub pos: Point2<f32>, pub pos: Point2<f32>,
@ -34,7 +34,7 @@ pub struct ParticleBuilder {
impl ParticleBuilder { impl ParticleBuilder {
/// Create a ParticleBuilder from an Effect /// Create a ParticleBuilder from an Effect
pub fn from_content( pub fn from_content(
effect: &content::Effect, effect: &Effect,
pos: Point2<f32>, pos: Point2<f32>,
parent_angle: Rad<f32>, parent_angle: Rad<f32>,
parent_velocity: Vector2<f32>, parent_velocity: Vector2<f32>,

View File

@ -1,6 +1,6 @@
use crate::{objects::ShipControls, ParticleBuilder}; use crate::{objects::ShipControls, ParticleBuilder};
use galactica_content::Content; use galactica_content::Content;
use galactica_gameobject::{GameData, GameShipHandle}; use galactica_galaxy::{Galaxy, GxShipHandle};
/// External resources we need to compute time steps /// External resources we need to compute time steps
#[derive(Debug)] #[derive(Debug)]
@ -9,7 +9,7 @@ pub struct StepResources<'a> {
pub ct: &'a Content, pub ct: &'a Content,
/// Game data /// Game data
pub dt: &'a mut GameData, pub gx: &'a mut Galaxy,
/// Length of time step /// Length of time step
pub t: f32, pub t: f32,
@ -21,5 +21,5 @@ pub struct StepResources<'a> {
pub player_controls: ShipControls, pub player_controls: ShipControls,
/// The ship that the player controls /// The ship that the player controls
pub player: GameShipHandle, pub player: GxShipHandle,
} }

View File

@ -1,8 +1,10 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero};
use content::{GunPoint, OutfitHandle};
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use galactica_content::{
Content, GunPoint, OutfitHandle, ProjectileCollider, Relationship, SystemHandle,
};
use galactica_galaxy::{ship::GxShip, Galaxy, GxShipHandle};
use nalgebra::{point, vector}; use nalgebra::{point, vector};
use object::{ship::Ship, GameData, GameShipHandle};
use rand::Rng; use rand::Rng;
use rapier2d::{ use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
@ -12,37 +14,32 @@ use rapier2d::{
use std::{collections::HashMap, f32::consts::PI}; use std::{collections::HashMap, f32::consts::PI};
use crate::{ use crate::{
behavior::{self, ShipBehavior}, controller::{self, ShipController},
objects, objects,
objects::{ProjectileWorldObject, ShipWorldObject}, objects::{SySimProjectile, SySimShip},
util, util,
wrapper::Wrapper, wrapper::Wrapper,
ParticleBuilder, StepResources, ParticleBuilder, StepResources,
}; };
use galactica_content as content;
use galactica_gameobject as object;
/// Manages the physics state of one system /// Manages the physics state of one system
pub struct World { pub struct SystemSim {
/// The system this world is attached to /// The system this sim is attached to
_system: content::SystemHandle, _system: SystemHandle,
wrapper: Wrapper, wrapper: Wrapper,
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>, projectiles: HashMap<ColliderHandle, objects::SySimProjectile>,
ships: HashMap<GameShipHandle, objects::ShipWorldObject>, ships: HashMap<GxShipHandle, objects::SySimShip>,
ship_behaviors: HashMap<GameShipHandle, Box<dyn ShipBehavior>>, ship_behaviors: HashMap<GxShipHandle, Box<dyn ShipController>>,
collider_ship_table: HashMap<ColliderHandle, GameShipHandle>, collider_ship_table: HashMap<ColliderHandle, GxShipHandle>,
collision_handler: ChannelEventCollector, collision_handler: ChannelEventCollector,
collision_queue: Receiver<CollisionEvent>, collision_queue: Receiver<CollisionEvent>,
} }
// Private methods // Private methods
impl<'a> World { impl<'a> SystemSim {
fn remove_projectile( fn remove_projectile(&mut self, c: ColliderHandle) -> Option<(RigidBody, SySimProjectile)> {
&mut self,
c: ColliderHandle,
) -> Option<(RigidBody, ProjectileWorldObject)> {
let p = match self.projectiles.remove(&c) { let p = match self.projectiles.remove(&c) {
Some(p) => p, Some(p) => p,
None => return None, None => return None,
@ -104,9 +101,9 @@ impl<'a> World {
let f = res.ct.get_faction(projectile.faction); let f = res.ct.get_faction(projectile.faction);
let r = f.relationships.get(&ship.get_faction()).unwrap(); let r = f.relationships.get(&ship.get_faction()).unwrap();
let destory_projectile = match r { let destory_projectile = match r {
content::Relationship::Hostile => { Relationship::Hostile => {
// We only apply damage if the target ship is alive // We only apply damage if the target ship is alive
if let Some(ship_d) = res.dt.get_ship_mut(ship.data_handle) { if let Some(ship_d) = res.gx.get_ship_mut(ship.data_handle) {
ship_d.apply_damage(projectile.content.damage); ship_d.apply_damage(projectile.content.damage);
} }
true true
@ -169,9 +166,9 @@ impl<'a> World {
} }
// Public methods // Public methods
impl World { impl SystemSim {
/// Create a new physics system /// Create a new physics system
pub fn new(ct: &content::Content, dt: &GameData, system: content::SystemHandle) -> Self { pub fn new(ct: &Content, gx: &Galaxy, system: SystemHandle) -> Self {
let (collision_send, collision_queue) = crossbeam::channel::unbounded(); let (collision_send, collision_queue) = crossbeam::channel::unbounded();
let (contact_force_send, _) = crossbeam::channel::unbounded(); let (contact_force_send, _) = crossbeam::channel::unbounded();
@ -188,9 +185,9 @@ impl World {
// TODO: guarantee not touching // TODO: guarantee not touching
// TODO: add, remove ships each tick // TODO: add, remove ships each tick
// Maybe store position in gamedata? // Maybe store position in galaxy crate?
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
for s in dt.iter_ships() { for s in gx.iter_ships() {
w.add_ship( w.add_ship(
ct, ct,
s, s,
@ -205,7 +202,7 @@ impl World {
} }
/// Add a ship to this physics system /// Add a ship to this physics system
pub fn add_ship(&mut self, ct: &content::Content, ship: &Ship, position: Point2<f32>) { pub fn add_ship(&mut self, ct: &Content, ship: &GxShip, position: Point2<f32>) {
let ship_content = ct.get_ship(ship.get_content()); let ship_content = ct.get_ship(ship.get_content());
let cl = ColliderBuilder::convex_decomposition( let cl = ColliderBuilder::convex_decomposition(
&ship_content.collision.points[..], &ship_content.collision.points[..],
@ -232,10 +229,10 @@ impl World {
self.collider_ship_table.insert(c, ship.get_handle()); self.collider_ship_table.insert(c, ship.get_handle());
self.ship_behaviors self.ship_behaviors
.insert(ship.get_handle(), Box::new(behavior::Point::new())); .insert(ship.get_handle(), Box::new(controller::Point::new()));
self.ships.insert( self.ships.insert(
ship.get_handle(), ship.get_handle(),
objects::ShipWorldObject::new(ct, ship.get_handle(), ship.get_faction(), r, c), objects::SySimShip::new(ct, ship.get_handle(), ship.get_faction(), r, c),
); );
} }
@ -260,7 +257,7 @@ impl World {
// Short-circuit continue if this ship isn't in game data // Short-circuit continue if this ship isn't in game data
// (which means it's playing a collapse sequence) // (which means it's playing a collapse sequence)
if res.dt.get_ship(*handle).is_none() { if res.gx.get_ship(*handle).is_none() {
let ship_object = self.ships.get_mut(handle).unwrap(); let ship_object = self.ships.get_mut(handle).unwrap();
ship_object.step( ship_object.step(
res, res,
@ -296,7 +293,7 @@ impl World {
// If we're firing, try to fire each gun // If we're firing, try to fire each gun
if ship_object.controls.guns { if ship_object.controls.guns {
let ship_data = res.dt.get_ship_mut(ship_object.data_handle).unwrap(); let ship_data = res.gx.get_ship_mut(ship_object.data_handle).unwrap();
// TODO: don't allocate here. This is a hack to satisfy the borrow checker, // TODO: don't allocate here. This is a hack to satisfy the borrow checker,
// convert this to a refcell or do the replace dance. // convert this to a refcell or do the replace dance.
@ -329,7 +326,7 @@ impl World {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let rigid_body = self.get_rigid_body(rigid_body).unwrap(); let rigid_body = self.get_rigid_body(rigid_body).unwrap();
let ship_dat = res.dt.get_ship(ship_dat).unwrap(); let ship_dat = res.gx.get_ship(ship_dat).unwrap();
let ship_pos = util::rigidbody_position(rigid_body); let ship_pos = util::rigidbody_position(rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body); let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_vel = util::rigidbody_velocity(rigid_body); let ship_vel = util::rigidbody_velocity(rigid_body);
@ -361,7 +358,7 @@ impl World {
.build(); .build();
let collider = match &outfit.projectile.collider { let collider = match &outfit.projectile.collider {
content::ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius) ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
.sensor(true) .sensor(true)
.active_events(ActiveEvents::COLLISION_EVENTS) .active_events(ActiveEvents::COLLISION_EVENTS)
.build(), .build(),
@ -376,7 +373,7 @@ impl World {
self.projectiles.insert( self.projectiles.insert(
collider.clone(), collider.clone(),
ProjectileWorldObject::new( SySimProjectile::new(
outfit.projectile.clone(), outfit.projectile.clone(),
rigid_body, rigid_body,
ship_dat.get_faction(), ship_dat.get_faction(),
@ -464,9 +461,9 @@ impl World {
} }
// Public getters // Public getters
impl World { impl SystemSim {
/// Get a ship physics object /// Get a ship physics object
pub fn get_ship(&self, ship: GameShipHandle) -> Option<&ShipWorldObject> { pub fn get_ship(&self, ship: GxShipHandle) -> Option<&SySimShip> {
self.ships.get(&ship) self.ships.get(&ship)
} }
@ -481,21 +478,19 @@ impl World {
} }
/// Iterate over all ships in this physics system /// Iterate over all ships in this physics system
pub fn iter_ship_body( pub fn iter_ship_body(&self) -> impl Iterator<Item = (&objects::SySimShip, &RigidBody)> + '_ {
&self,
) -> impl Iterator<Item = (&objects::ShipWorldObject, &RigidBody)> + '_ {
self.ships self.ships
.values() .values()
.map(|x| (x, self.wrapper.rigid_body_set.get(x.rigid_body).unwrap())) .map(|x| (x, self.wrapper.rigid_body_set.get(x.rigid_body).unwrap()))
} }
/// Iterate over all ships in this physics system /// Iterate over all ships in this physics system
pub fn iter_ships(&self) -> impl Iterator<Item = &ShipWorldObject> + '_ { pub fn iter_ships(&self) -> impl Iterator<Item = &SySimShip> + '_ {
self.ships.values() self.ships.values()
} }
/// Iterate over all ships in this physics system /// Iterate over all ships in this physics system
pub fn iter_projectiles(&self) -> impl Iterator<Item = &ProjectileWorldObject> + '_ { pub fn iter_projectiles(&self) -> impl Iterator<Item = &SySimProjectile> + '_ {
self.projectiles.values() self.projectiles.values()
} }
} }