From e8c96228327adbfb6d5433334ef59b18fb48636e Mon Sep 17 00:00:00 2001 From: Mark Date: Mon, 5 Feb 2024 18:29:05 -0800 Subject: [PATCH] Replaced handles with Arcs, added display names --- Cargo.lock | 1 + Cargo.toml | 1 + content/outfits.toml | 3 + content/ship.toml | 7 +- content/system.toml | 2 +- content/ui/flying.rhai | 5 +- content/ui/landed.rhai | 2 +- content/ui/outfitter.rhai | 2 +- crates/content/Cargo.toml | 1 + crates/content/src/handle.rs | 64 ---- crates/content/src/lib.rs | 176 ++++------ crates/content/src/part/effect.rs | 58 ++-- crates/content/src/part/faction.rs | 62 ++-- crates/content/src/part/outfit.rs | 95 +++--- crates/content/src/part/ship.rs | 108 +++--- crates/content/src/part/sprite.rs | 322 +++++++----------- crates/content/src/part/system.rs | 158 ++++----- crates/content/src/spriteautomaton.rs | 121 +++---- crates/galactica/src/game.rs | 127 ++++--- crates/galactica/src/main.rs | 10 +- crates/playeragent/src/playeragent.rs | 30 +- crates/render/src/gpustate.rs | 52 ++- crates/render/src/renderinput.rs | 4 +- crates/render/src/renderstate.rs | 4 +- crates/render/src/starfield.rs | 22 +- crates/render/src/ui/api/functions/sprite.rs | 27 +- crates/render/src/ui/api/state.rs | 83 +++-- crates/render/src/ui/elements/fpsindicator.rs | 2 +- crates/render/src/ui/elements/radialbar.rs | 4 +- crates/render/src/ui/elements/sprite.rs | 28 +- crates/render/src/ui/elements/textbox.rs | 6 +- crates/render/src/ui/executor.rs | 10 +- crates/render/src/ui/state.rs | 6 +- crates/system/src/data/ship/outfitset.rs | 46 +-- crates/system/src/data/ship/ship.rs | 61 ++-- crates/system/src/data/ship/shipstate.rs | 17 +- crates/system/src/phys/objects/effect.rs | 14 +- crates/system/src/phys/objects/projectile.rs | 35 +- .../system/src/phys/objects/ship/collapse.rs | 19 +- .../src/phys/objects/ship/controller/point.rs | 6 +- crates/system/src/phys/objects/ship/ship.rs | 144 ++++---- crates/system/src/phys/physsim.rs | 59 ++-- 42 files changed, 913 insertions(+), 1091 deletions(-) delete mode 100644 crates/content/src/handle.rs diff --git a/Cargo.lock b/Cargo.lock index 9efe3e1..eab89df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -816,6 +816,7 @@ dependencies = [ "rapier2d", "rhai", "serde", + "smartstring", "toml", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index f308ded..492a6a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,3 +75,4 @@ clap = { version = "4.4.18", features = ["derive"] } log = "0.4.20" log4rs = { version = "1.2.0", features = ["console_appender"] } rhai = { version = "1.17.1", features = ["f32_float", "no_custom_syntax"] } +smartstring = { version = "1.0.1" } diff --git a/content/outfits.toml b/content/outfits.toml index acc373b..078c39c 100644 --- a/content/outfits.toml +++ b/content/outfits.toml @@ -1,4 +1,5 @@ [outfit."plasma engines"] +name = "Plasma Engines" thumbnail = "icon::engine" space.engine = 20 @@ -11,6 +12,7 @@ steering.power = 20 [outfit."shield generator"] thumbnail = "icon::shield" +name = "Shield Generator" space.outfit = 5 shield.generation = 10 @@ -20,6 +22,7 @@ shield.delay = 2.0 [outfit."blaster"] thumbnail = "icon::shield" +name = "Blaster" space.weapon = 10 diff --git a/content/ship.toml b/content/ship.toml index 561cb8c..0e9261e 100644 --- a/content/ship.toml +++ b/content/ship.toml @@ -1,4 +1,5 @@ -[ship."Gypsum"] +[ship."gypsum"] +name = "Gypsum" sprite = "ship::gypsum" thumbnail = "icon::gypsum" size = 100 @@ -65,7 +66,7 @@ collision = [ # Scripted explosion -[[ship."Gypsum".collapse.event]] +[[ship."gypsum".collapse.event]] time = 4.9 effects = [ #[rustfmt:skip], @@ -76,7 +77,7 @@ effects = [ ] # Scripted explosion -[[ship."Gypsum".collapse.event]] +[[ship."gypsum".collapse.event]] time = 0.0 effects = [ #[rustfmt:skip], diff --git a/content/system.toml b/content/system.toml index bf12268..0496ea1 100644 --- a/content/system.toml +++ b/content/system.toml @@ -1,7 +1,7 @@ # TODO: big objects in one config [system."12 Autumn Above"] - +name = "12 Autumn Above" object.star.sprite = "star::star" object.star.position = [0.0, 0.0, 30.0] object.star.size = 2000 diff --git a/content/ui/flying.rhai b/content/ui/flying.rhai index e03d074..18231e7 100644 --- a/content/ui/flying.rhai +++ b/content/ui/flying.rhai @@ -131,7 +131,7 @@ fn step(state) { // Ships { for s in state.ships() { - let uid = s.get_uid(); + let uid = s.phys_uid(); let sprite_name = `radar.ship.${uid}`; if ( @@ -200,7 +200,8 @@ fn step(state) { // System objects { for o in state.objects() { - let sprite_name = `radar.object.${o.get_label()}`; + let l = o.get_index(); + let sprite_name = `radar.object.${l}`; if !o.is_some() { if sprite::exists(sprite_name) { diff --git a/content/ui/landed.rhai b/content/ui/landed.rhai index 82b2724..ec9384c 100644 --- a/content/ui/landed.rhai +++ b/content/ui/landed.rhai @@ -55,7 +55,7 @@ fn init(state) { textbox::font_serif("title"); textbox::weight_bold("title"); if player.is_landed() { - textbox::set_text("title", player.landed_on().name()); + textbox::set_text("title", player.landed_on().display_name()); } textbox::add( diff --git a/content/ui/outfitter.rhai b/content/ui/outfitter.rhai index 1c3642c..190ce8a 100644 --- a/content/ui/outfitter.rhai +++ b/content/ui/outfitter.rhai @@ -84,7 +84,7 @@ fn init(state) { textbox::font_sans("ship_type"); textbox::align_center("ship_type"); if state.player_ship().is_some() { - textbox::set_text("ship_type", state.player_ship().name()); + textbox::set_text("ship_type", state.player_ship().display_name()); } diff --git a/crates/content/Cargo.toml b/crates/content/Cargo.toml index 191a2c0..0c8f131 100644 --- a/crates/content/Cargo.toml +++ b/crates/content/Cargo.toml @@ -30,3 +30,4 @@ rapier2d = { workspace = true } lazy_static = { workspace = true } log = { workspace = true } rhai = { workspace = true } +smartstring = { workspace = true } diff --git a/crates/content/src/handle.rs b/crates/content/src/handle.rs deleted file mode 100644 index 3e40b89..0000000 --- a/crates/content/src/handle.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! 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}; - -/// A lightweight representation of a sprite -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SpriteHandle { - pub(crate) index: usize, -} - -/// A lightweight representation of system body -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct SystemObjectHandle { - /// TODO: pub in crate - pub system_handle: SystemHandle, - /// The index of this object in system.objects - pub body_index: usize, -} - -/// A lightweight representation of an outfit -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct OutfitHandle { - /// TODO: pub in crate, currently for debug (same with all other handles) - pub index: usize, -} - -/// A lightweight representation of a gun -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -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 { - /// TODO - pub index: usize, -} - -/// A lightweight representation of an effect -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct EffectHandle { - pub(crate) index: usize, -} diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index 1efba2e..7ba8c10 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -1,9 +1,6 @@ #![warn(missing_docs)] -//! This subcrate is responsible for loading, parsing, validating game content, -//! which is usually stored in `./content`. - -mod handle; +//! This subcrate is responsible for loading, parsing, validating game content. mod part; mod spriteautomaton; mod util; @@ -11,18 +8,21 @@ mod util; use anyhow::{bail, Context, Result}; use galactica_packer::{SpriteAtlas, SpriteAtlasImage}; use log::warn; +use rhai::ImmutableString; +use serde::{Deserialize, Deserializer}; +use smartstring::{LazyCompact, SmartString}; use std::{ - //cell::OnceCell, collections::HashMap, + fmt::Display, fs::File, io::Read, num::NonZeroU32, path::{Path, PathBuf}, + sync::Arc, }; use toml; use walkdir::WalkDir; -pub use handle::*; pub use part::*; pub use spriteautomaton::*; @@ -123,11 +123,50 @@ trait Build { Self: Sized; } +/// The type we use to index content objects +/// These are only unique WITHIN each "type" of object! +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct ContentIndex(Arc>); + +impl From for ContentIndex { + fn from(value: ImmutableString) -> Self { + Self::new(&value) + } +} + +impl ContentIndex { + /// Make a new ContentIndex from a strign + pub fn new(s: &str) -> Self { + Self(SmartString::from(s).into()) + } + + /// Get a &str to this index's content + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl<'d> Deserialize<'d> for ContentIndex { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'d>, + { + let s = String::deserialize(deserializer)?; + Ok(Self::new(&s)) + } +} + +impl Display for ContentIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + /// Stores temporary data while building context objects #[derive(Debug)] pub(crate) struct ContentBuildContext { /// Map effect names to handles - pub effect_index: HashMap, + pub effect_index: HashMap>, } impl ContentBuildContext { @@ -141,22 +180,29 @@ impl ContentBuildContext { /// Represents static game content #[derive(Debug)] pub struct Content { - /// Sprites - pub sprites: Vec, - /// Map strings to texture names. - /// This is only necessary because we need to hard-code a few texture names for UI elements. - sprite_index: HashMap, - /// Keeps track of which images are in which texture sprite_atlas: SpriteAtlas, - outfits: Vec, - guns: Vec, // TODO: merge with outfit - ships: Vec, - systems: Vec, - factions: Vec, - effects: Vec, - config: Config, + /// Sprites defined in this game + pub sprites: HashMap>, + + /// Outfits defined in this game + pub outfits: HashMap>, + + /// Ships defined in this game + pub ships: HashMap>, + + /// Systems defined in this game + pub systems: HashMap>, + + /// Factions defined in this game + pub factions: HashMap>, + + /// Effects defined in this game + pub effects: HashMap>, + + /// Game configuration + pub config: Config, } // Loading methods @@ -224,14 +270,12 @@ impl Content { }, sprite_atlas: atlas, - systems: Vec::new(), - ships: Vec::new(), - guns: Vec::new(), - outfits: Vec::new(), - sprites: Vec::new(), - factions: Vec::new(), - effects: Vec::new(), - sprite_index: HashMap::new(), + systems: HashMap::new(), + ships: HashMap::new(), + outfits: HashMap::new(), + sprites: HashMap::new(), + factions: HashMap::new(), + effects: HashMap::new(), }; // TODO: enforce sprite and image limits @@ -285,23 +329,6 @@ impl Content { // Access methods impl Content { - /// Iterate over all valid system handles - pub fn iter_systems(&self) -> impl Iterator { - (0..self.systems.len()).map(|x| SystemHandle { index: x }) - } - - /// Get a handle from a sprite name - pub fn get_sprite_handle(&self, name: &str) -> Option { - self.sprite_index.get(name).map(|x| *x) - } - - /// Get a sprite from a handle - pub fn get_sprite(&self, h: SpriteHandle) -> &Sprite { - // In theory, this could fail if h has a bad index, but that shouldn't ever happen. - // The only handles that exist should be created by this crate. - return &self.sprites[h.index as usize]; - } - /// Get the list of atlas files we may use pub fn atlas_files(&self) -> &Vec { return &self.sprite_atlas.atlas_list; @@ -316,63 +343,4 @@ impl Content { pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage { &self.sprite_atlas.get_by_idx(idx) } - - /// 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 ship 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 system object from a handle - pub fn get_system_object(&self, h: SystemObjectHandle) -> &SystemObject { - return &self.get_system(h.system_handle).objects[h.body_index]; - } - - /// Get a faction from a handle - pub fn get_faction(&self, h: FactionHandle) -> &Faction { - return &self.factions[h.index]; - } - - /// Get an effect from a handle - pub fn get_effect(&self, h: EffectHandle) -> &Effect { - return &self.effects[h.index]; - } - - /// Get content configuration - pub fn get_config(&self) -> &Config { - return &self.config; - } } - -/* -TODO: don't pass content around? -static mut CONTENT: OnceCell = OnceCell::new(); - -/// Initialize content::CONTENT with the given paths -pub fn init(content_dir: PathBuf, asset_dir: PathBuf, atlas_index: PathBuf) -> Result<()> { - let content = Content::load_dir(content_dir, asset_dir, atlas_index)?; - unsafe { - match CONTENT.set(content) { - Ok(()) => {} - Err(_) => { - bail!("cannot initialize content, already set.") - } - }; - } - return Ok(()); -} -*/ diff --git a/crates/content/src/part/effect.rs b/crates/content/src/part/effect.rs index dffd7d0..26f7c33 100644 --- a/crates/content/src/part/effect.rs +++ b/crates/content/src/part/effect.rs @@ -1,20 +1,22 @@ use anyhow::{Context, Result}; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; -use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle}; +use crate::{Content, ContentBuildContext, Sprite}; pub(crate) mod syntax { + use std::sync::Arc; + use anyhow::{bail, Result}; use galactica_util::to_radians; use serde::Deserialize; - use crate::{Content, ContentBuildContext, EffectHandle, StartEdge}; + use crate::{Content, ContentBuildContext, ContentIndex, StartEdge}; // Raw serde syntax structs. // These are never seen by code outside this crate. #[derive(Debug, Deserialize)] pub struct Effect { - pub sprite: String, + pub sprite: ContentIndex, pub size: f32, pub size_rng: Option, pub lifetime: TextOrFloat, @@ -59,10 +61,11 @@ pub(crate) mod syntax { self, _build_context: &mut ContentBuildContext, content: &mut Content, - ) -> Result { - let sprite = match content.sprite_index.get(&self.sprite) { + name: &str, + ) -> Result> { + let sprite = match content.sprites.get(&self.sprite) { None => bail!("sprite `{}` doesn't exist", self.sprite), - Some(t) => *t, + Some(t) => t.clone(), }; let lifetime = match self.lifetime { @@ -70,12 +73,11 @@ pub(crate) mod syntax { TextOrFloat::Text(s) => { if s == "inherit" { // Match lifetime of first section of sprite - let sprite = content.get_sprite(sprite); - let sec = match sprite.start_at { - StartEdge::Top { section } => sprite.get_section(section), - StartEdge::Bot { section } => sprite.get_section(section), - }; - sec.frame_duration * sec.frames.len() as f32 + match &sprite.start_at { + StartEdge::Top { section } | StartEdge::Bot { section } => { + section.frame_duration * section.frames.len() as f32 + } + } } else { bail!("bad effect lifetime, must be float or \"inherit\"",) } @@ -107,11 +109,8 @@ pub(crate) mod syntax { } }; - let handle = EffectHandle { - index: content.effects.len(), - }; - content.effects.push(super::Effect { - handle, + let e = Arc::new(super::Effect { + name: name.to_string(), sprite, velocity, size: self.size, @@ -126,7 +125,11 @@ pub(crate) mod syntax { fade_rng: self.fade_rng.unwrap_or(0.0), }); - return Ok(handle); + content + .effects + .insert(ContentIndex::new(&e.name), e.clone()); + + return Ok(e); } } @@ -144,13 +147,14 @@ pub(crate) mod syntax { self, build_context: &mut ContentBuildContext, content: &mut Content, - ) -> Result { + name: &str, + ) -> Result> { // We do not insert anything into build_context here, // since inline effects cannot be referenced by name. Ok(match self { - Self::Effect(e) => e.add_to(build_context, content)?, + Self::Effect(e) => e.add_to(build_context, content, name)?, Self::Label(l) => match build_context.effect_index.get(&l) { - Some(h) => *h, + Some(h) => h.clone(), None => bail!("no effect named `{}`", l), }, }) @@ -161,11 +165,11 @@ pub(crate) mod syntax { /// The effect a projectile will spawn when it hits something #[derive(Debug, Clone)] pub struct Effect { - /// This effect's handle - pub handle: EffectHandle, - /// The sprite to use for this effect. - pub sprite: SpriteHandle, + pub sprite: Arc, + + /// This effect's name + pub name: String, /// The height of this effect, in game units. pub size: f32, @@ -240,7 +244,7 @@ impl crate::Build for Effect { ) -> Result<()> { for (effect_name, effect) in effects { let h = effect - .add_to(build_context, content) + .add_to(build_context, content, &effect_name) .with_context(|| format!("while evaluating effect `{}`", effect_name))?; build_context.effect_index.insert(effect_name, h); } diff --git a/crates/content/src/part/faction.rs b/crates/content/src/part/faction.rs index 418b930..75e781a 100644 --- a/crates/content/src/part/faction.rs +++ b/crates/content/src/part/faction.rs @@ -1,13 +1,15 @@ use anyhow::{bail, Result}; use serde::Deserialize; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; -use crate::{handle::FactionHandle, Content, ContentBuildContext}; +use crate::{Content, ContentBuildContext, ContentIndex}; pub(crate) mod syntax { use std::collections::HashMap; use serde::Deserialize; + + use crate::ContentIndex; // Raw serde syntax structs. // These are never seen by code outside this crate. @@ -15,7 +17,7 @@ pub(crate) mod syntax { pub struct Faction { pub display_name: String, pub color: [f32; 3], - pub relationship: HashMap, + pub relationship: HashMap, } } @@ -44,18 +46,18 @@ pub enum Relationship { #[derive(Debug, Clone)] pub struct Faction { /// The name of this faction - pub name: String, + pub index: ContentIndex, + + /// The pretty name of this faction + pub display_name: String, /// This faction's color. /// Format is RGB, with each color between 0 and 1. pub color: [f32; 3], - /// This faction's handle - pub handle: FactionHandle, - /// Relationships between this faction and other factions /// This is guaranteed to contain an entry for ALL factions. - pub relationships: HashMap, + pub relationships: HashMap, } impl crate::Build for Faction { @@ -66,34 +68,21 @@ impl crate::Build for Faction { _build_context: &mut ContentBuildContext, content: &mut Content, ) -> Result<()> { - // Keeps track of position in faction array. - // This lets us build FactionHandles before finishing all factions. - let faction_names: Vec = factions.keys().map(|x| x.to_owned()).collect(); - - // Indexing will break if this is false. - assert!(content.factions.len() == 0); - - for f_idx in 0..faction_names.len() { - let faction_name = &faction_names[f_idx]; - let faction = &factions[faction_name]; - - // Handle for this faction - let h = FactionHandle { index: f_idx }; - + for (faction_name, faction) in &factions { // Compute relationships let mut relationships = HashMap::new(); - for i in 0..faction_names.len() { - let f_other = &faction_names[i]; - let h_other = FactionHandle { index: i }; - if let Some(r) = faction.relationship.get(f_other) { - relationships.insert(h_other, *r); + for (other_name, other_faction) in &factions { + if let Some(r) = faction.relationship.get(&ContentIndex::new(other_name)) { + relationships.insert(ContentIndex::new(other_name), *r); } else { // Default relationship, if not specified // Look at reverse direction... - let other = factions[f_other].relationship.get(faction_name); + let other = other_faction + .relationship + .get(&ContentIndex::new(&faction_name)); relationships.insert( - h_other, + ContentIndex::new(other_name), // ... and pick a relationship based on that. match other { Some(Relationship::Hostile) => Relationship::Hostile {}, @@ -116,12 +105,15 @@ impl crate::Build for Faction { ); } - content.factions.push(Self { - name: faction_name.to_owned(), - handle: h, - relationships, - color: faction.color, - }); + content.factions.insert( + ContentIndex::new(faction_name), + Arc::new(Self { + index: ContentIndex::new(faction_name), + display_name: faction.display_name.to_owned(), + relationships, + color: faction.color, + }), + ); } return Ok(()); diff --git a/crates/content/src/part/outfit.rs b/crates/content/src/part/outfit.rs index 6ac560c..300a560 100644 --- a/crates/content/src/part/outfit.rs +++ b/crates/content/src/part/outfit.rs @@ -1,14 +1,18 @@ use anyhow::{anyhow, Context, Result}; use serde::Deserialize; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use crate::{ - handle::SpriteHandle, resolve_edge_as_edge, Content, ContentBuildContext, EffectHandle, - OutfitHandle, OutfitSpace, SectionEdge, + resolve_edge_as_edge, Content, ContentBuildContext, ContentIndex, Effect, OutfitSpace, + SectionEdge, Sprite, }; pub(crate) mod syntax { - use crate::{effect, part::outfitspace, sprite::syntax::SectionEdge, ContentBuildContext}; + use std::sync::Arc; + + use crate::{ + effect, part::outfitspace, sprite::syntax::SectionEdge, ContentBuildContext, ContentIndex, + }; use anyhow::{bail, Result}; use galactica_util::to_radians; use serde::Deserialize; @@ -17,7 +21,8 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] pub struct Outfit { - pub thumbnail: String, + pub thumbnail: ContentIndex, + pub name: String, pub engine: Option, pub steering: Option, pub space: outfitspace::syntax::OutfitSpace, @@ -41,7 +46,7 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] pub struct EngineFlare { - pub sprite: String, + pub sprite: ContentIndex, pub on_start: Option, pub on_stop: Option, } @@ -64,30 +69,33 @@ pub(crate) mod syntax { build_context: &mut ContentBuildContext, content: &mut crate::Content, ) -> Result { - let projectile_sprite_handle = match content.sprite_index.get(&self.projectile.sprite) { + let projectile_sprite = match content + .sprites + .get(&ContentIndex::new(&self.projectile.sprite)) + { None => bail!( "projectile sprite `{}` doesn't exist", self.projectile.sprite, ), - Some(t) => *t, + Some(t) => t.clone(), }; let impact_effect = match self.projectile.impact_effect { - Some(e) => Some(e.to_handle(build_context, content)?), + Some(e) => Some(e.to_handle(build_context, content, "")?), None => None, }; let expire_effect = match self.projectile.expire_effect { - Some(e) => Some(e.to_handle(build_context, content)?), + Some(e) => Some(e.to_handle(build_context, content, "")?), None => None, }; return Ok(super::Gun { rate: self.rate, rate_rng: self.rate_rng.unwrap_or(0.0), - projectile: super::Projectile { + projectile: Arc::new(super::Projectile { force: self.projectile.force, - sprite: projectile_sprite_handle, + sprite: projectile_sprite, size: self.projectile.size, size_rng: self.projectile.size_rng, speed: self.projectile.speed, @@ -102,7 +110,7 @@ pub(crate) mod syntax { impact_effect, expire_effect, collider: self.projectile.collider, - }, + }), }); } } @@ -129,17 +137,17 @@ pub(crate) mod syntax { #[derive(Debug, Clone)] pub struct Outfit { /// This outfit's thumbnail - pub thumbnail: SpriteHandle, + pub thumbnail: Arc, /// How much space this outfit requires pub space: OutfitSpace, - /// This outfit's handle - pub handle: OutfitHandle, - /// The name of this outfit pub name: String, + /// Thie outfit's index + pub index: ContentIndex, + /// How much engine thrust this outfit produces pub engine_thrust: f32, @@ -149,7 +157,7 @@ pub struct Outfit { /// The engine flare sprite this outfit creates. /// Its location and size is determined by a ship's /// engine points. - pub engine_flare_sprite: Option, + pub engine_flare_sprite: Option>, /// Jump to this edge when engines turn on pub engine_flare_on_start: Option, @@ -191,7 +199,7 @@ pub struct BallCollider { #[derive(Debug, Clone)] pub struct Gun { /// The projectile this gun produces - pub projectile: Projectile, + pub projectile: Arc, /// Average delay between projectiles, in seconds. pub rate: f32, @@ -205,7 +213,7 @@ pub struct Gun { #[derive(Debug, Clone)] pub struct Projectile { /// The projectile sprite - pub sprite: SpriteHandle, + pub sprite: Arc, /// The average size of this projectile /// (height in game units) @@ -234,10 +242,10 @@ pub struct Projectile { pub angle_rng: f32, /// The effect this projectile will spawn when it hits something - pub impact_effect: Option, + pub impact_effect: Option>, /// The effect this projectile will spawn when it expires - pub expire_effect: Option, + pub expire_effect: Option>, /// Collider parameters for this projectile pub collider: ProjectileCollider, @@ -252,10 +260,6 @@ impl crate::Build for Outfit { content: &mut Content, ) -> Result<()> { for (outfit_name, outfit) in outfits { - let handle = OutfitHandle { - index: content.outfits.len(), - }; - let gun = match outfit.gun { None => None, Some(g) => Some( @@ -264,7 +268,7 @@ impl crate::Build for Outfit { ), }; - let thumb_handle = match content.sprite_index.get(&outfit.thumbnail) { + let thumbnail = match content.sprites.get(&outfit.thumbnail) { None => { return Err(anyhow!( "thumbnail sprite `{}` doesn't exist", @@ -272,14 +276,14 @@ impl crate::Build for Outfit { )) .with_context(|| format!("in outfit `{}`", outfit_name)); } - Some(t) => *t, + Some(t) => t.clone(), }; let mut o = Self { - thumbnail: thumb_handle, + index: ContentIndex::new(&outfit_name), + name: outfit.name, + thumbnail, gun, - handle, - name: outfit_name.clone(), engine_thrust: 0.0, steer_power: 0.0, engine_flare_sprite: None, @@ -293,7 +297,7 @@ impl crate::Build for Outfit { // Engine stats if let Some(engine) = outfit.engine { - let sprite_handle = match content.sprite_index.get(&engine.flare.sprite) { + let sprite = match content.sprites.get(&engine.flare.sprite) { None => { return Err(anyhow!( "flare sprite `{}` doesn't exist", @@ -301,11 +305,10 @@ impl crate::Build for Outfit { )) .with_context(|| format!("in outfit `{}`", outfit_name)); } - Some(t) => *t, + Some(t) => t.clone(), }; o.engine_thrust = engine.thrust; - o.engine_flare_sprite = Some(sprite_handle); - let sprite = content.get_sprite(sprite_handle); + o.engine_flare_sprite = Some(sprite.clone()); // Flare animation will traverse this edge when the player presses the thrust key // This leads from the idle animation to the transition animation @@ -315,11 +318,9 @@ impl crate::Build for Outfit { None } else { let x = x.unwrap(); - let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| { - sprite.get_section_handle_by_name(x) - }) - .with_context(|| format!("in outfit `{}`", outfit_name))?; - match e { + let mut e = resolve_edge_as_edge(&sprite.sections, &x.val, 0.0) + .with_context(|| format!("in outfit `{}`", outfit_name))?; + match &mut e { // Inherit duration from transition sequence SectionEdge::Top { section, @@ -329,7 +330,7 @@ impl crate::Build for Outfit { section, ref mut duration, } => { - *duration = sprite.get_section(section).frame_duration; + *duration = section.frame_duration; } _ => { return Err(anyhow!( @@ -351,11 +352,9 @@ impl crate::Build for Outfit { None } else { let x = x.unwrap(); - let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| { - sprite.get_section_handle_by_name(x) - }) - .with_context(|| format!("in outfit `{}`", outfit_name))?; - match e { + let mut e = resolve_edge_as_edge(&sprite.sections, &x.val, 0.0) + .with_context(|| format!("in outfit `{}`", outfit_name))?; + match &mut e { // Inherit duration from transition sequence SectionEdge::Top { section, @@ -365,7 +364,7 @@ impl crate::Build for Outfit { section, ref mut duration, } => { - *duration = sprite.get_section(section).frame_duration; + *duration = section.frame_duration; } _ => { return Err(anyhow!( @@ -392,7 +391,7 @@ impl crate::Build for Outfit { o.shield_strength = shield.strength.unwrap_or(0.0); } - content.outfits.push(o); + content.outfits.insert(o.index.clone(), Arc::new(o)); } return Ok(()); diff --git a/crates/content/src/part/ship.rs b/crates/content/src/part/ship.rs index 887fbb0..ac6d195 100644 --- a/crates/content/src/part/ship.rs +++ b/crates/content/src/part/ship.rs @@ -2,12 +2,15 @@ use anyhow::{bail, Context, Result}; use galactica_util::to_radians; use nalgebra::{Point2, Rotation2, Vector2}; use rapier2d::geometry::{Collider, ColliderBuilder}; -use std::{collections::HashMap, fmt::Debug, hash::Hash}; +use std::{collections::HashMap, fmt::Debug, hash::Hash, sync::Arc}; -use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitSpace}; +use crate::{Content, ContentBuildContext, ContentIndex, Effect, OutfitSpace, Sprite}; pub(crate) mod syntax { - use crate::part::{effect::syntax::EffectReference, outfitspace}; + use crate::{ + part::{effect::syntax::EffectReference, outfitspace}, + ContentIndex, + }; use serde::Deserialize; // Raw serde syntax structs. @@ -15,8 +18,9 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] pub struct Ship { - pub sprite: String, - pub thumbnail: String, + pub name: String, + pub sprite: ContentIndex, + pub thumbnail: ContentIndex, pub size: f32, pub engines: Vec, pub guns: Vec, @@ -91,14 +95,17 @@ pub(crate) mod syntax { /// Represents a ship chassis. #[derive(Debug, Clone)] pub struct Ship { - /// This ship's name - pub name: String, + /// This ship's display name + pub display_name: String, + + /// This object's index + pub index: ContentIndex, /// This ship's sprite - pub sprite: SpriteHandle, + pub sprite: Arc, /// This ship's thumbnail - pub thumbnail: SpriteHandle, + pub thumbnail: Arc, /// The size of this ship. /// Measured as unrotated height, @@ -122,9 +129,6 @@ pub struct Ship { /// Collision shape for this ship pub collider: CollisionDebugWrapper, - /// Remove later - pub aspect: f32, - /// Reduction in angular velocity over time pub angular_drag: f32, @@ -217,7 +221,7 @@ pub struct ShipDamage { #[derive(Debug, Clone)] pub struct DamageEffectSpawner { /// The effect to create - pub effect: EffectHandle, + pub effect: Arc, /// How often to create this effect pub frequency: f32, @@ -231,7 +235,7 @@ pub struct DamageEffectSpawner { #[derive(Debug, Clone)] pub struct CollapseEffectSpawner { /// The effect to create - pub effect: EffectHandle, + pub effect: Arc, /// How many effects to create pub count: f32, @@ -267,26 +271,25 @@ impl crate::Build for Ship { ct: &mut Content, ) -> Result<()> { for (ship_name, ship) in ship { - let sprite = match ct.sprite_index.get(&ship.sprite) { + let sprite = match ct.sprites.get(&ship.sprite) { None => bail!( "In ship `{}`: sprite `{}` doesn't exist", ship_name, ship.sprite ), - Some(t) => *t, + Some(t) => t.clone(), }; - let thumbnail = match ct.sprite_index.get(&ship.thumbnail) { + let thumbnail = match ct.sprites.get(&ship.thumbnail) { None => bail!( "In ship `{}`: thumbnail sprite `{}` doesn't exist", ship_name, ship.thumbnail ), - Some(t) => *t, + Some(t) => t.clone(), }; let size = ship.size; - let aspect = ct.get_sprite(sprite).aspect; let collapse = { if let Some(c) = ship.collapse { @@ -295,11 +298,11 @@ impl crate::Build for Ship { effects.push(CollapseEffectSpawner { effect: e .effect - .to_handle(build_context, ct) + .to_handle(build_context, ct, "") .with_context(|| format!("while loading ship `{}`", ship_name))?, count: e.count, pos: e.pos.map(|p| { - Point2::new(p[0] * (size / 2.0) * aspect, p[1] * size / 2.0) + Point2::new(p[0] * (size / 2.0) * sprite.aspect, p[1] * size / 2.0) }), }); } @@ -313,14 +316,14 @@ impl crate::Build for Ship { effects.push(CollapseEffectSpawner { effect: g .effect - .to_handle(build_context, ct) + .to_handle(build_context, ct, "") .with_context(|| { format!("while loading ship `{}`", ship_name) })?, count: g.count, pos: g.pos.map(|p| { Point2::new( - p[0] * (size / 2.0) * aspect, + p[0] * (size / 2.0) * sprite.aspect, p[1] * size / 2.0, ) }), @@ -357,11 +360,11 @@ impl crate::Build for Ship { effects.push(DamageEffectSpawner { effect: e .effect - .to_handle(build_context, ct) + .to_handle(build_context, ct, "") .with_context(|| format!("while loading ship `{}`", ship_name))?, frequency: e.frequency, pos: e.pos.map(|p| { - Point2::new(p[0] * (size / 2.0) * aspect, p[1] * size / 2.0) + Point2::new(p[0] * (size / 2.0) * sprite.aspect, p[1] * size / 2.0) }), }); } @@ -388,7 +391,7 @@ impl crate::Build for Ship { // Angle adjustment, since sprites point north // and 0 degrees is east in the game pos: Rotation2::new(to_radians(-90.0)) - * Vector2::new(g.x * size * aspect / 2.0, g.y * size / 2.0), + * Vector2::new(g.x * size * sprite.aspect / 2.0, g.y * size / 2.0), }) } @@ -418,7 +421,7 @@ impl crate::Build for Ship { // If we don't, rapier2 will compute local points pre-rotation, // which will break effect placement on top of ships (i.e, collapse effects) Rotation2::new(to_radians(-90.0)) - * Point2::new(x[0] * (size / 2.0) * aspect, x[1] * size / 2.0) + * Point2::new(x[0] * (size / 2.0) * sprite.aspect, x[1] * size / 2.0) }) .collect(); @@ -427,33 +430,36 @@ impl crate::Build for Ship { .build() }; - ct.ships.push(Self { - sprite, - thumbnail, - aspect, - collapse, - damage, - name: ship_name, - mass: ship.mass, - space: OutfitSpace::from(ship.space), - angular_drag: ship.angular_drag, - linear_drag: ship.linear_drag, - size, - hull: ship.hull, + ct.ships.insert( + ContentIndex::new(&ship_name), + Arc::new(Self { + sprite: sprite.clone(), + thumbnail, + collapse, + damage, + index: ContentIndex::new(&ship_name), + display_name: ship.name, + mass: ship.mass, + space: OutfitSpace::from(ship.space), + angular_drag: ship.angular_drag, + linear_drag: ship.linear_drag, + size, + hull: ship.hull, - engines: ship - .engines - .iter() - .map(|e| EnginePoint { - pos: Vector2::new(e.x * size * aspect / 2.0, e.y * size / 2.0), - size: e.size, - }) - .collect(), + engines: ship + .engines + .iter() + .map(|e| EnginePoint { + pos: Vector2::new(e.x * size * sprite.aspect / 2.0, e.y * size / 2.0), + size: e.size, + }) + .collect(), - guns, + guns, - collider: CollisionDebugWrapper(collider), - }); + collider: CollisionDebugWrapper(collider), + }), + ); } return Ok(()); diff --git a/crates/content/src/part/sprite.rs b/crates/content/src/part/sprite.rs index 82d9c50..51afd0f 100644 --- a/crates/content/src/part/sprite.rs +++ b/crates/content/src/part/sprite.rs @@ -1,14 +1,14 @@ use anyhow::{anyhow, bail, Context, Result}; use lazy_static::lazy_static; -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; -use crate::{handle::SpriteHandle, Content, ContentBuildContext}; +use crate::{Content, ContentBuildContext, ContentIndex}; pub(crate) mod syntax { - use crate::{AnimSectionHandle, Content}; + use crate::{Content, ContentIndex}; use anyhow::{bail, Ok, Result}; use serde::Deserialize; - use std::{collections::HashMap, path::PathBuf}; + use std::{collections::HashMap, path::PathBuf, sync::Arc}; // Raw serde syntax structs. // These are never seen by code outside this crate. @@ -49,7 +49,7 @@ pub(crate) mod syntax { /// The proper, full sprite definition #[derive(Debug, Deserialize)] pub struct CompleteSprite { - pub section: HashMap, + pub section: HashMap, pub start_at: SectionEdge, } @@ -63,14 +63,28 @@ pub(crate) mod syntax { } impl SpriteSection { - pub fn add_to( + pub fn resolve_edges( &self, - ct: &mut Content, - get_handle: F, - ) -> Result<((u32, u32), super::SpriteSection)> - where - F: Fn(&str) -> Option, - { + sections: &HashMap>, + sec: &mut super::SpriteSection, + ) -> Result<()> { + let edge_top = match &self.top { + Some(x) => super::resolve_edge_as_edge(sections, &x.val, sec.frame_duration)?, + None => super::SectionEdge::Stop, + }; + + let edge_bot = match &self.bot { + Some(x) => super::resolve_edge_as_edge(sections, &x.val, sec.frame_duration)?, + None => super::SectionEdge::Stop, + }; + + sec.edge_bot = edge_bot; + sec.edge_top = edge_top; + + return Ok(()); + } + + pub fn add_to(&self, ct: &mut Content) -> Result<((u32, u32), super::SpriteSection)> { // Make sure all frames have the same size and add them // to the frame vector let mut dim = None; @@ -111,23 +125,14 @@ pub(crate) mod syntax { bail!("frame duration must be positive (and therefore nonzero).") } - let edge_top = match &self.top { - Some(x) => super::resolve_edge_as_edge(&x.val, frame_duration, &get_handle)?, - None => super::SectionEdge::Stop, - }; - - let edge_bot = match &self.bot { - Some(x) => super::resolve_edge_as_edge(&x.val, frame_duration, &get_handle)?, - None => super::SectionEdge::Stop, - }; - return Ok(( dim, super::SpriteSection { frames, frame_duration, - edge_top, - edge_bot, + // These are changed later, after all sections are built + edge_top: super::SectionEdge::Stop, + edge_bot: super::SectionEdge::Stop, }, )); } @@ -141,27 +146,8 @@ pub(crate) mod syntax { } } -/// A handle for an animation section inside a sprite -#[derive(Debug, Copy, Clone)] -pub enum AnimSectionHandle { - /// The hidden section - Hidden, - - /// An index into this sprite's section array - Idx(usize), -} - -impl AnimSectionHandle { - fn get_idx(&self) -> Option { - match self { - Self::Hidden => None, - Self::Idx(idx) => Some(*idx), - } - } -} - /// An edge between two animation sections -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum SectionEdge { /// Stop at the last frame of this section Stop, @@ -169,7 +155,7 @@ pub enum SectionEdge { /// Play the given section from the bottm Bot { /// The section to play - section: AnimSectionHandle, + section: Arc, /// The length of this edge, in seconds duration: f32, @@ -178,7 +164,7 @@ pub enum SectionEdge { /// Play the given section from the top Top { /// The section to play - section: AnimSectionHandle, + section: Arc, /// The length of this edge, in seconds duration: f32, @@ -198,18 +184,18 @@ pub enum SectionEdge { } /// Where to start an animation -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum StartEdge { /// Play the given section from the bottm Bot { /// The section to play - section: AnimSectionHandle, + section: Arc, }, /// Play the given section from the top Top { /// The section to play - section: AnimSectionHandle, + section: Arc, }, } @@ -231,80 +217,50 @@ impl Into for StartEdge { /// Represents a sprite that may be used in the game. #[derive(Debug, Clone)] pub struct Sprite { - /// The name of this sprite - pub name: String, - - /// This sprite's handle - pub handle: SpriteHandle, + /// This object's index + pub index: ContentIndex, /// Where this sprite starts playing pub start_at: StartEdge, /// This sprite's animation sections - sections: Vec, - - /// Allows us to get sprite sections by name - sections_by_name: HashMap, + pub sections: HashMap>, /// Aspect ratio of this sprite (width / height) pub aspect: f32, } lazy_static! { - static ref HIDDEN_SECTION: SpriteSection = SpriteSection { + /// The universal "hidden" sprite section, available in all sprites. + /// A SpriteAutomaton in this section will not be drawn. + pub static ref HIDDEN_SPRITE_SECTION: Arc = Arc::new(SpriteSection { frames: vec![0], frame_duration: 0.0, edge_bot: SectionEdge::Stop, edge_top: SectionEdge::Stop, - }; + }); } impl Sprite { - /// Get an animation section from a handle - pub fn get_section(&self, section: AnimSectionHandle) -> &SpriteSection { - match section { - AnimSectionHandle::Hidden => &HIDDEN_SECTION, - AnimSectionHandle::Idx(idx) => &self.sections[idx], - } - } - - /// Get an animation section by name. - /// Returns None for invalid names - pub fn get_section_by_name(&self, name: &str) -> Option<&SpriteSection> { - match self.sections_by_name.get(name) { - None => return None, - Some(h) => Some(self.get_section(*h)), - } - } - - /// Get an animation section's handle by name. - /// Returns None for invalid names - pub fn get_section_handle_by_name(&self, name: &str) -> Option { - match self.sections_by_name.get(name) { - None => return None, - Some(h) => Some(*h), - } - } - /// Get the index of the texture of this sprite's first frame pub fn get_first_frame(&self) -> u32 { - match self.start_at { - StartEdge::Bot { section } => *self.get_section(section).frames.last().unwrap(), - StartEdge::Top { section } => *self.get_section(section).frames.first().unwrap(), + match &self.start_at { + StartEdge::Bot { section } => *section.frames.last().unwrap(), + StartEdge::Top { section } => *section.frames.first().unwrap(), } } /// Get this sprite's starting section - pub fn get_start_section(&self) -> AnimSectionHandle { - match self.start_at { - StartEdge::Bot { section } => section, - StartEdge::Top { section } => section, + pub fn get_start_section(&self) -> Arc { + match &self.start_at { + StartEdge::Bot { section } => section.clone(), + StartEdge::Top { section } => section.clone(), } } /// Iterate this sprite's sections - pub fn iter_sections(&self) -> impl Iterator { - self.sections.iter() + pub fn iter_sections(&self) -> impl Iterator> { + self.sections.values() } } @@ -327,66 +283,73 @@ pub struct SpriteSection { } /// Resolve an edge specification string as a StartEdge -pub fn resolve_edge_as_start(s: &str, get_handle: F) -> Result -where - F: Fn(&str) -> Option, -{ - let e = resolve_edge_as_edge(s, 0.0, get_handle) +pub fn resolve_edge_as_start( + sections: &HashMap>, + edge_string: &str, +) -> Result { + let e = resolve_edge_as_edge(sections, edge_string, 0.0) .with_context(|| format!("while resolving start edge"))?; match e { super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }), super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { section }), _ => { - bail!("bad section start specification `{}`", s); + bail!("bad section start specification `{}`", edge_string); } } } /// Resolve an edge specifiation string as a SectionEdge -pub fn resolve_edge_as_edge(s: &str, duration: f32, get_handle: F) -> Result -where - F: Fn(&str) -> Option, -{ - if s == "hidden" { +pub fn resolve_edge_as_edge( + sections: &HashMap>, + edge_string: &str, + duration: f32, +) -> Result { + if edge_string == "hidden" { return Ok(super::SectionEdge::Top { - section: crate::AnimSectionHandle::Hidden, + section: crate::HIDDEN_SPRITE_SECTION.clone(), duration, }); } - if s == "stop" { + if edge_string == "stop" { return Ok(super::SectionEdge::Stop); } - if s == "reverse" { + if edge_string == "reverse" { return Ok(super::SectionEdge::Reverse { duration }); } - if s == "repeat" { + if edge_string == "repeat" { return Ok(super::SectionEdge::Repeat { duration }); } - let (s, p) = match s.split_once(":") { + let (section_name, start_point) = match edge_string.split_once(":") { Some(x) => x, None => { - bail!("bad section edge specification `{}`", s); + bail!("bad section edge specification `{}`", edge_string); } }; - let section = match get_handle(s) { + let section = match sections.get(&ContentIndex::new(section_name)) { Some(s) => s, None => { - return Err(anyhow!("bad section edge specification `{}`", s)) - .with_context(|| format!("section `{}` doesn't exist", s)); + return Err(anyhow!("bad section edge specification `{}`", section_name)) + .with_context(|| format!("section `{}` doesn't exist", section_name)); } }; - match p { - "top" => Ok(super::SectionEdge::Top { section, duration }), - "bot" => Ok(super::SectionEdge::Bot { section, duration }), + match start_point { + "top" => Ok(super::SectionEdge::Top { + section: section.clone(), + duration, + }), + "bot" => Ok(super::SectionEdge::Bot { + section: section.clone(), + duration, + }), _ => { - return Err(anyhow!("bad section edge specification `{}`", s)) - .with_context(|| format!("invalid target `{}`", p)); + return Err(anyhow!("bad section edge specification `{}`", section_name)) + .with_context(|| format!("invalid target `{}`", start_point)); } } } @@ -419,93 +382,60 @@ impl crate::Build for Sprite { let img = &ct.sprite_atlas.get_by_idx(idx); let aspect = img.w / img.h; - let h = SpriteHandle { - index: ct.sprites.len(), - }; + let section = Arc::new(SpriteSection { + frames: vec![img.idx.into()], + // We implement unanimated sprites with a very fast framerate + // and STOP endpoints. + frame_duration: 0.01, + edge_top: SectionEdge::Stop, + edge_bot: SectionEdge::Stop, + }); - ct.sprite_index.insert(sprite_name.clone(), h); - - ct.sprites.push(Self { - name: sprite_name, + let sprite = Arc::new(Self { + index: ContentIndex::new(&sprite_name), start_at: StartEdge::Top { - section: AnimSectionHandle::Idx(0), + section: section.clone(), + }, + sections: { + let mut h = HashMap::new(); + h.insert(ContentIndex::new("anim"), section); + h }, - sections: vec![SpriteSection { - frames: vec![img.idx.into()], - // We implement unanimated sprites with a very fast framerate - // and STOP endpoints. - frame_duration: 0.01, - edge_top: SectionEdge::Stop, - edge_bot: SectionEdge::Stop, - }], - sections_by_name: HashMap::new(), - handle: h, aspect, }); + ct.sprites.insert(sprite.index.clone(), sprite); } syntax::Sprite::OneSection(s) => { - let sprite_handle = SpriteHandle { - index: ct.sprites.len(), - }; - ct.sprite_index.insert(sprite_name.clone(), sprite_handle); - let (dim, section) = s - .add_to(ct, |s| { - if s == "anim" { - Some(AnimSectionHandle::Idx(0)) - } else { - None - } - }) + .add_to(ct) .with_context(|| format!("while parsing sprite `{}`", sprite_name))?; let aspect = dim.0 as f32 / dim.1 as f32; - let mut sections = Vec::new(); - sections.push(section); + let section = Arc::new(section); - ct.sprites.push(Self { - name: sprite_name, - sections, + let sprite = Arc::new(Self { + index: ContentIndex::new(&sprite_name), start_at: StartEdge::Top { - section: AnimSectionHandle::Idx(0), + section: section.clone(), }, - sections_by_name: { + sections: { let mut h = HashMap::new(); - h.insert("anim".to_string(), AnimSectionHandle::Idx(0)); + h.insert(ContentIndex::new("anim"), section.clone()); h }, - handle: sprite_handle, aspect, }); + ct.sprites.insert(sprite.index.clone(), sprite); } syntax::Sprite::Complete(s) => { - let mut section_names = HashMap::new(); - for (name, _) in &s.section { - section_names - .insert(name.to_owned(), AnimSectionHandle::Idx(section_names.len())); - } + let mut sections = HashMap::new(); - let sprite_handle = SpriteHandle { - index: ct.sprites.len(), - }; - ct.sprite_index.insert(sprite_name.clone(), sprite_handle); - - let start_at = - resolve_edge_as_start(&s.start_at.val, |x| section_names.get(x).copied()) - .with_context(|| format!("while loading sprite `{}`", sprite_name))?; - - let mut sections = Vec::with_capacity(section_names.len()); let mut dim = None; - // Make sure we add sections in order - let mut names = section_names.iter().collect::>(); - names.sort_by(|a, b| (a.1).get_idx().unwrap().cmp(&(b.1).get_idx().unwrap())); - - for (k, _) in names { - let v = s.section.get(k).unwrap(); - let (d, s) = v - .add_to(ct, |x| section_names.get(x).copied()) - .with_context(|| format!("while parsing section `{}`", k)) + for (name, sec) in &s.section { + let (d, s) = sec + .add_to(ct) + .with_context(|| format!("while parsing section `{}`", name)) .with_context(|| format!("while parsing sprite `{}`", sprite_name))?; // Make sure all dimensions are the same @@ -515,23 +445,35 @@ impl crate::Build for Sprite { bail!( "could not load sprite `{}`, image sizes in section `{}` are different", sprite_name, - k + name ); } - sections.push(s); + sections.insert(name.clone(), Arc::new(s)); } + + // We need to clone this here, thanks to self-reference. + let tmp_sections = sections.clone(); + + for (name, sec) in &s.section { + let parsed_sec = sections.get_mut(name).unwrap(); + let parsed_sec = Arc::make_mut(parsed_sec); + sec.resolve_edges(&tmp_sections, parsed_sec)?; + } + let dim = dim.unwrap(); let aspect = dim.0 as f32 / dim.1 as f32; - ct.sprites.push(Self { - name: sprite_name, - sections, + let start_at = resolve_edge_as_start(§ions, &s.start_at.val) + .with_context(|| format!("while loading sprite `{}`", sprite_name))?; + + let sprite = Arc::new(Self { + index: ContentIndex::new(&sprite_name), start_at, - handle: sprite_handle, - sections_by_name: section_names, + sections, aspect, }); + ct.sprites.insert(sprite.index.clone(), sprite); } } } diff --git a/crates/content/src/part/system.rs b/crates/content/src/part/system.rs index b5e97df..acb1dc0 100644 --- a/crates/content/src/part/system.rs +++ b/crates/content/src/part/system.rs @@ -1,27 +1,30 @@ use anyhow::{anyhow, bail, Context, Result}; use galactica_util::to_radians; use nalgebra::{Point2, Point3}; -use std::collections::{HashMap, HashSet}; - -use crate::{ - handle::SpriteHandle, util::Polar, Content, ContentBuildContext, SystemHandle, - SystemObjectHandle, +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, }; +use crate::{util::Polar, Content, ContentBuildContext, ContentIndex, Sprite}; + pub(crate) mod syntax { use serde::Deserialize; use std::collections::HashMap; + + use crate::ContentIndex; // Raw serde syntax structs. // These are never seen by code outside this crate. #[derive(Debug, Deserialize)] pub struct System { - pub object: HashMap, + pub name: String, + pub object: HashMap, } #[derive(Debug, Deserialize)] pub struct Object { - pub sprite: String, + pub sprite: ContentIndex, pub position: Position, pub size: f32, @@ -31,7 +34,7 @@ pub(crate) mod syntax { pub landable: Option, pub name: Option, pub desc: Option, - pub image: Option, + pub image: Option, } #[derive(Debug, Deserialize)] @@ -52,14 +55,14 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum CoordinatesTwo { - Label(String), + Label(ContentIndex), Coords([f32; 2]), } impl ToString for CoordinatesTwo { fn to_string(&self) -> String { match self { - Self::Label(s) => s.to_owned(), + Self::Label(s) => s.to_string(), Self::Coords(v) => format!("{:?}", v), } } @@ -68,14 +71,14 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] #[serde(untagged)] pub enum CoordinatesThree { - Label(String), + Label(ContentIndex), Coords([f32; 3]), } impl ToString for CoordinatesThree { fn to_string(&self) -> String { match self { - Self::Label(s) => s.to_owned(), + Self::Label(s) => s.to_string(), Self::Coords(v) => format!("{:?}", v), } } @@ -88,14 +91,14 @@ pub(crate) mod syntax { /// Represents a star system #[derive(Debug, Clone)] pub struct System { - /// This star system's name - pub name: String, + /// This object's name + pub display_name: String, - /// This star system's handle - pub handle: SystemHandle, + /// This object's index + pub index: ContentIndex, /// Objects in this system - pub objects: Vec, + pub objects: HashMap>, } /// Represents an orbiting body in a star system @@ -104,11 +107,14 @@ pub struct System { /// System objects to not interact with the physics engine. #[derive(Debug, Clone)] pub struct SystemObject { - /// This object's sprite - pub sprite: SpriteHandle, + /// This object's name + pub display_name: Option, - /// This object's handle - pub handle: SystemObjectHandle, + /// This object's index + pub index: ContentIndex, + + /// This object's sprite + pub sprite: Arc, /// This object's size. /// Measured as height in game units. @@ -126,24 +132,18 @@ pub struct SystemObject { /// If true, ships may land on this object pub landable: bool, - /// The pretty display name of this object - pub name: Option, - - /// The system-unique label of this object - pub label: String, - /// The description of this object (shown on landed ui) pub desc: Option, /// This object's image (shown on landed ui) - pub image: Option, + pub image: Option>, } /// Helper function for resolve_position, never called on its own. fn resolve_coordinates( - objects: &HashMap, + objects: &HashMap, cor: &syntax::CoordinatesThree, - mut cycle_detector: HashSet, + mut cycle_detector: HashSet, ) -> Result> { match cor { syntax::CoordinatesThree::Coords(c) => Ok((*c).into()), @@ -160,7 +160,7 @@ fn resolve_coordinates( }) ); } - cycle_detector.insert(l.to_owned()); + cycle_detector.insert(l.clone()); let p = match objects.get(l) { Some(p) => p, @@ -174,9 +174,9 @@ fn resolve_coordinates( /// Given an object, resolve its position as a Point3. fn resolve_position( - objects: &HashMap, + objects: &HashMap, obj: &syntax::Object, - cycle_detector: HashSet, + cycle_detector: HashSet, ) -> Result> { match &obj.position { syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?), @@ -208,33 +208,29 @@ impl crate::Build for System { content: &mut Content, ) -> Result<()> { for (system_name, system) in system { - let mut objects = Vec::new(); + let mut objects = HashMap::new(); - let system_handle = SystemHandle { - index: content.systems.len(), - }; - - for (label, obj) in &system.object { + for (index, obj) in &system.object { let mut cycle_detector = HashSet::new(); - cycle_detector.insert(label.clone()); + cycle_detector.insert(index.clone()); - let sprite_handle = match content.sprite_index.get(&obj.sprite) { + let sprite = match content.sprites.get(&obj.sprite) { None => bail!( "In system `{}`: sprite `{}` doesn't exist", system_name, obj.sprite ), - Some(t) => *t, + Some(t) => t.clone(), }; - let image_handle = match &obj.image { - Some(x) => match content.sprite_index.get(x) { + let image = match &obj.image { + Some(x) => match content.sprites.get(x) { None => bail!( "In system `{}`: sprite `{}` doesn't exist", system_name, obj.sprite ), - Some(t) => Some(*t), + Some(t) => Some(t.clone()), }, None => None, }; @@ -242,7 +238,7 @@ impl crate::Build for System { if obj.landable.unwrap_or(false) { if obj.name.is_none() { return Err(anyhow!("if an object is landable, it must have a name")) - .with_context(|| format!("in object labeled `{}`", label)) + .with_context(|| format!("in object labeled `{}`", index)) .with_context(|| format!("in system `{}`", system_name)); } @@ -250,57 +246,47 @@ impl crate::Build for System { return Err(anyhow!( "if an object is landable, it must have a description" )) - .with_context(|| format!("in object labeled `{}`", label)) + .with_context(|| format!("in object labeled `{}`", index)) .with_context(|| format!("in system `{}`", system_name)); } if obj.image.is_none() { return Err(anyhow!("if an object is landable, it must have an image")) - .with_context(|| format!("in object labeled `{}`", label)) + .with_context(|| format!("in object labeled `{}`", index)) .with_context(|| format!("in system `{}`", system_name)); } } - objects.push(SystemObject { - label: label.clone(), - sprite: sprite_handle, - image: image_handle, - pos: resolve_position(&system.object, &obj, cycle_detector) - .with_context(|| format!("in object {:#?}", label))?, - size: obj.size, - angle: to_radians(obj.angle.unwrap_or(0.0)), - handle: SystemObjectHandle { - system_handle, - body_index: 0, - }, - landable: obj.landable.unwrap_or(false), - name: obj.name.as_ref().map(|x| x.clone()), - // TODO: better linebreaks, handle double spaces - // Tabs - desc: obj - .desc - .as_ref() - .map(|x| x.replace("\n", " ").replace("
", "\n")), - }); + objects.insert( + index.clone(), + Arc::new(SystemObject { + index: index.clone(), + sprite, + image, + pos: resolve_position(&system.object, &obj, cycle_detector) + .with_context(|| format!("in object {:#?}", index))?, + size: obj.size, + angle: to_radians(obj.angle.unwrap_or(0.0)), + landable: obj.landable.unwrap_or(false), + display_name: obj.name.as_ref().map(|x| x.clone()), + // TODO: better linebreaks, handle double spaces + // Tabs + desc: obj + .desc + .as_ref() + .map(|x| x.replace("\n", " ").replace("
", "\n")), + }), + ); } - // Sort by z-distance. This is important, since these are - // rendered in this order. We need far objects to be behind - // near objects! - objects.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); - - // Update object handles - let mut i = 0; - for o in &mut objects { - o.handle.body_index = i; - i += 1; - } - - content.systems.push(Self { - handle: system_handle, - name: system_name, - objects, - }); + content.systems.insert( + ContentIndex::new(&system_name), + Arc::new(Self { + index: ContentIndex::new(&system_name), + display_name: system.name, + objects, + }), + ); } return Ok(()); diff --git a/crates/content/src/spriteautomaton.rs b/crates/content/src/spriteautomaton.rs index a0ce422..06eeafc 100644 --- a/crates/content/src/spriteautomaton.rs +++ b/crates/content/src/spriteautomaton.rs @@ -1,4 +1,5 @@ -use crate::{AnimSectionHandle, Content, SectionEdge, SpriteHandle, StartEdge}; +use crate::{SectionEdge, Sprite, SpriteSection, StartEdge}; +use std::sync::Arc; /// A single frame's state #[derive(Debug, Clone)] @@ -43,11 +44,11 @@ enum AnimDirection { #[derive(Debug, Clone)] pub struct SpriteAutomaton { /// The sprite we're animating - sprite: SpriteHandle, + sprite: Arc, /// Which animation section we're on /// This MUST be a section from this Automaton's sprite - current_section: AnimSectionHandle, + current_section: Arc, /// Which frame we're on current_frame: usize, @@ -75,39 +76,35 @@ pub struct SpriteAutomaton { impl SpriteAutomaton { /// Create a new AnimAutomaton - pub fn new(ct: &Content, sprite_handle: SpriteHandle) -> Self { - let sprite = ct.get_sprite(sprite_handle); - - let (current_section, texture, current_direction) = match sprite.start_at { - StartEdge::Top { section } => ( - section, - *sprite.get_section(section).frames.first().unwrap(), - AnimDirection::Down, - ), - StartEdge::Bot { section } => ( - section, - *sprite.get_section(section).frames.last().unwrap(), - AnimDirection::Up, - ), + pub fn new(sprite: Arc) -> Self { + let (current_section, texture, current_direction) = { + match &sprite.start_at { + StartEdge::Top { section } => ( + section, + *section.frames.first().unwrap(), + AnimDirection::Down, + ), + StartEdge::Bot { section } => { + (section, *section.frames.last().unwrap(), AnimDirection::Up) + } + } }; - let sec = sprite.get_section(current_section); - Self { - sprite: sprite.handle, + sprite: sprite.clone(), current_frame: 0, current_edge_progress: match current_direction { AnimDirection::Down => 0.0, - AnimDirection::Up => sec.frame_duration, + AnimDirection::Up => current_section.frame_duration, AnimDirection::Stop => unreachable!("how'd you get here?"), }, - current_edge_duration: sec.frame_duration, + current_edge_duration: current_section.frame_duration, next_edge_override: None, current_direction, - current_section, + current_section: current_section.clone(), last_texture: texture, next_texture: texture, } @@ -119,14 +116,11 @@ impl SpriteAutomaton { } /// Force a transition to the given section right now - pub fn jump_to(&mut self, ct: &Content, start: SectionEdge) { - self.take_edge(ct, start); + pub fn jump_to(&mut self, start: &SectionEdge) { + self.take_edge(start); } - fn take_edge(&mut self, ct: &Content, e: SectionEdge) { - let sprite = ct.get_sprite(self.sprite); - let current_section = sprite.get_section(self.current_section); - + fn take_edge(&mut self, e: &SectionEdge) { let last = match self.current_direction { AnimDirection::Stop => self.next_texture, AnimDirection::Down => self.next_texture, @@ -141,7 +135,7 @@ impl SpriteAutomaton { self.current_frame = 0; } AnimDirection::Down => { - self.current_frame = current_section.frames.len() - 1; + self.current_frame = self.current_section.frames.len() - 1; } } @@ -149,29 +143,28 @@ impl SpriteAutomaton { self.current_direction = AnimDirection::Stop; } SectionEdge::Top { section, duration } => { - self.current_section = section; - self.current_edge_duration = duration; + self.current_section = section.clone(); + self.current_edge_duration = *duration; self.current_frame = 0; self.current_direction = AnimDirection::Down; } SectionEdge::Bot { section, duration } => { - let s = sprite.get_section(section); - self.current_section = section; - self.current_frame = s.frames.len() - 1; - self.current_edge_duration = duration; + self.current_section = section.clone(); + self.current_frame = section.frames.len() - 1; + self.current_edge_duration = *duration; self.current_direction = AnimDirection::Up; } SectionEdge::Repeat { duration } => { match self.current_direction { AnimDirection::Stop => {} AnimDirection::Up => { - self.current_frame = current_section.frames.len() - 1; + self.current_frame = self.current_section.frames.len() - 1; } AnimDirection::Down => { self.current_frame = 0; } } - self.current_edge_duration = duration; + self.current_edge_duration = *duration; } SectionEdge::Reverse { duration } => { match self.current_direction { @@ -180,7 +173,7 @@ impl SpriteAutomaton { // Jump to SECOND frame, since we've already shown the // first during the fade transition self.current_frame = { - if current_section.frames.len() == 1 { + if self.current_section.frames.len() == 1 { 0 } else { 1 @@ -190,45 +183,39 @@ impl SpriteAutomaton { } AnimDirection::Down => { self.current_frame = { - if current_section.frames.len() == 1 { + if self.current_section.frames.len() == 1 { 0 } else { - current_section.frames.len() - 2 + self.current_section.frames.len() - 2 } }; self.current_direction = AnimDirection::Up; } } - self.current_edge_duration = duration; + self.current_edge_duration = *duration; } } match self.current_direction { AnimDirection::Stop => { - let current_section = sprite.get_section(self.current_section); - self.next_texture = current_section.frames[self.current_frame]; - self.last_texture = current_section.frames[self.current_frame]; + self.next_texture = self.current_section.frames[self.current_frame]; + self.last_texture = self.current_section.frames[self.current_frame]; } AnimDirection::Down => { - let current_section = sprite.get_section(self.current_section); self.last_texture = last; - self.next_texture = current_section.frames[self.current_frame]; + self.next_texture = self.current_section.frames[self.current_frame]; self.current_edge_progress = 0.0; } AnimDirection::Up => { - let current_section = sprite.get_section(self.current_section); self.next_texture = last; - self.last_texture = current_section.frames[self.current_frame]; + self.last_texture = self.current_section.frames[self.current_frame]; self.current_edge_progress = self.current_edge_duration; } } } /// Step this animation by `t` seconds - pub fn step(&mut self, ct: &Content, t: f32) { - let sprite = ct.get_sprite(self.sprite); - let current_section = sprite.get_section(self.current_section); - + pub fn step(&mut self, t: f32) { // Current_fade and current_frame keep track of where we are in the current section. // current_frame indexes this section frames. When it exceeds the number of frames // or falls below zero (when moving in reverse), we switch to the next section. @@ -240,7 +227,7 @@ impl SpriteAutomaton { // Note that frame_duration may be zero! // This is only possible in the hidden texture, since // user-provided sections are always checked to be positive. - assert!(current_section.frame_duration >= 0.0); + assert!(self.current_section.frame_duration >= 0.0); match self.current_direction { AnimDirection::Stop => { @@ -248,7 +235,7 @@ impl SpriteAutomaton { // we should transition right away. if let Some(e) = self.next_edge_override.take() { - self.take_edge(ct, e); + self.take_edge(&e); } return; @@ -259,21 +246,21 @@ impl SpriteAutomaton { // We're stepping foward and finished this frame if self.current_edge_progress > self.current_edge_duration { - if self.current_frame < current_section.frames.len() - 1 { + if self.current_frame < self.current_section.frames.len() - 1 { self.current_frame += 1; self.last_texture = self.next_texture; - self.next_texture = current_section.frames[self.current_frame]; + self.next_texture = self.current_section.frames[self.current_frame]; self.current_edge_progress = 0.0; - self.current_edge_duration = current_section.frame_duration; + self.current_edge_duration = self.current_section.frame_duration; } else { let e = { if self.next_edge_override.is_some() { self.next_edge_override.take().unwrap() } else { - current_section.edge_bot.clone() + self.current_section.edge_bot.clone() } }; - self.take_edge(ct, e); + self.take_edge(&e); } } } @@ -286,18 +273,18 @@ impl SpriteAutomaton { if self.current_frame > 0 { self.current_frame -= 1; self.next_texture = self.last_texture; - self.last_texture = current_section.frames[self.current_frame]; - self.current_edge_progress = current_section.frame_duration; - self.current_edge_duration = current_section.frame_duration; + self.last_texture = self.current_section.frames[self.current_frame]; + self.current_edge_progress = self.current_section.frame_duration; + self.current_edge_duration = self.current_section.frame_duration; } else { let e = { if self.next_edge_override.is_some() { self.next_edge_override.take().unwrap() } else { - current_section.edge_top.clone() + self.current_section.edge_top.clone() } }; - self.take_edge(ct, e); + self.take_edge(&e); } } } @@ -314,7 +301,7 @@ impl SpriteAutomaton { } /// Get the sprite this automaton is using - pub fn get_sprite(&self) -> SpriteHandle { - self.sprite + pub fn get_sprite(&self) -> Arc { + self.sprite.clone() } } diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index effa176..e8dc25a 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -1,4 +1,4 @@ -use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle}; +use galactica_content::{Content, ContentIndex}; use galactica_playeragent::PlayerAgent; use galactica_system::data::ShipPersonality; use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepResources}; @@ -24,82 +24,101 @@ unsafe impl<'a> Send for Game {} impl<'a> Game { pub fn make_player(&mut self) -> PhysSimShipHandle { let player = self.phys_sim.add_ship( - &self.ct, - ShipHandle { index: 0 }, - FactionHandle { index: 0 }, + self.ct + .ships + .get(&ContentIndex::new("gypsum")) + .unwrap() + .clone(), + self.ct + .factions + .get(&ContentIndex::new("player")) + .unwrap() + .clone(), ShipPersonality::Player, Point2::new(0.0, 4000.0), ); let s = self.phys_sim.get_ship_mut(&player).unwrap(); - s.add_outfits( - &self.ct, - [ - OutfitHandle { index: 0 }, - OutfitHandle { index: 1 }, - OutfitHandle { index: 2 }, - ], - ); + s.add_outfits([ + self.ct + .outfits + .get(&ContentIndex::new("plasma engines")) + .unwrap() + .clone(), + self.ct + .outfits + .get(&ContentIndex::new("shield generator")) + .unwrap() + .clone(), + self.ct + .outfits + .get(&ContentIndex::new("blaster")) + .unwrap() + .clone(), + self.ct + .outfits + .get(&ContentIndex::new("blaster")) + .unwrap() + .clone(), + ]); return player; } pub fn new(ct: Arc) -> Self { - let mut phys_sim = PhysSim::new(&ct, SystemHandle { index: 0 }); + let mut phys_sim = PhysSim::new(); let a = phys_sim.add_ship( - &ct, - ShipHandle { index: 0 }, - FactionHandle { index: 1 }, - ShipPersonality::Point, - Point2::new(1000.0, 0.0), - ); - - let s = phys_sim.get_ship_mut(&a).unwrap(); - s.add_outfits( - &ct, - [ - OutfitHandle { index: 0 }, - OutfitHandle { index: 1 }, - OutfitHandle { index: 2 }, - ], - ); - - let a = phys_sim.add_ship( - &ct, - ShipHandle { index: 0 }, - FactionHandle { index: 1 }, + ct.ships.get(&ContentIndex::new("gypsum")).unwrap().clone(), + ct.factions + .get(&ContentIndex::new("enemy")) + .unwrap() + .clone(), ShipPersonality::Point, Point2::new(1000.0, 4000.0), ); let s = phys_sim.get_ship_mut(&a).unwrap(); - s.add_outfits( - &ct, - [ - OutfitHandle { index: 0 }, - OutfitHandle { index: 1 }, - OutfitHandle { index: 2 }, - ], - ); + s.add_outfits([ + ct.outfits + .get(&ContentIndex::new("plasma engines")) + .unwrap() + .clone(), + ct.outfits + .get(&ContentIndex::new("shield generator")) + .unwrap() + .clone(), + ct.outfits + .get(&ContentIndex::new("blaster")) + .unwrap() + .clone(), + ]); let a = phys_sim.add_ship( - &ct, - ShipHandle { index: 0 }, - FactionHandle { index: 0 }, + ct.ships.get(&ContentIndex::new("gypsum")).unwrap().clone(), + ct.factions + .get(&ContentIndex::new("player")) + .unwrap() + .clone(), ShipPersonality::Dummy, Point2::new(200.0, 2000.0), ); let s = phys_sim.get_ship_mut(&a).unwrap(); - s.add_outfits( - &ct, - [ - OutfitHandle { index: 0 }, - OutfitHandle { index: 1 }, - OutfitHandle { index: 2 }, - ], - ); + s.add_outfits([ + ct.outfits + .get(&ContentIndex::new("plasma engines")) + .unwrap() + .clone(), + ct.outfits + .get(&ContentIndex::new("shield generator")) + .unwrap() + .clone(), + ct.outfits + .get(&ContentIndex::new("blaster")) + .unwrap() + .clone(), + ]); Game { ct, @@ -112,7 +131,7 @@ impl<'a> Game { } pub fn update_player_controls(&mut self, player: &mut PlayerAgent) { - self.phys_sim.update_player_controls(&self.ct, player) + self.phys_sim.update_player_controls(player) } pub fn step(&mut self, phys_img: &PhysImage) { diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index 75c64a5..fc22536 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -2,7 +2,7 @@ mod game; use anyhow::{bail, Result}; use clap::Parser; -use galactica_content::{Content, SystemHandle}; +use galactica_content::Content; use galactica_playeragent::{PlayerAgent, PlayerStatus}; use galactica_render::RenderInput; use galactica_system::{ @@ -128,7 +128,7 @@ fn try_main() -> Result<()> { let mut game = game::Game::new(content.clone()); let p = game.make_player(); - let mut player = Arc::new(PlayerAgent::new(p.0)); + let mut player = Arc::new(PlayerAgent::new(&content, p.0)); Arc::get_mut(&mut player).unwrap().set_camera_aspect( gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32, ); @@ -147,7 +147,8 @@ fn try_main() -> Result<()> { phys_img: phys_img.clone(), player: player.clone(), time_since_last_run: last_run.elapsed().as_secs_f32(), - current_system: SystemHandle { index: 0 }, + // TODO: this is a hack for testing. + current_system: content.systems.values().next().unwrap().clone(), timing: game.get_timing().clone(), }; last_run = Instant::now(); @@ -178,8 +179,7 @@ fn try_main() -> Result<()> { | ShipState::Flying { .. } => Some(*o.rigidbody.translation()), ShipState::Landed { target } => { - let b = content.get_system_object(*target); - Some(Vector2::new(b.pos.x, b.pos.y)) + Some(Vector2::new(target.pos.x, target.pos.y)) } ShipState::Dead => None, diff --git a/crates/playeragent/src/playeragent.rs b/crates/playeragent/src/playeragent.rs index fcfa168..6494ec2 100644 --- a/crates/playeragent/src/playeragent.rs +++ b/crates/playeragent/src/playeragent.rs @@ -1,25 +1,27 @@ -use galactica_content::{Content, SystemHandle, SystemObjectHandle}; +use std::sync::Arc; + +use galactica_content::{Content, ContentIndex, SystemObject}; use rapier2d::geometry::ColliderHandle; use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus}; /// What the player has selected -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub enum PlayerSelection { /// We have nothing selected None, /// We have a system body selected - OrbitingBody(SystemObjectHandle), + OrbitingBody(Arc), /// We have a ship selected Ship(ColliderHandle), } impl PlayerSelection { - pub fn get_planet(&self) -> Option { + pub fn get_planet(&self) -> Option<&Arc> { match self { - Self::OrbitingBody(h) => Some(*h), + Self::OrbitingBody(h) => Some(h), _ => None, } } @@ -41,15 +43,21 @@ pub struct PlayerAgent { } impl PlayerAgent { - pub fn new(ship: ColliderHandle) -> Self { + pub fn new(ct: &Content, ship: ColliderHandle) -> Self { Self { input: InputStatus::new(), camera: Camera::new(), ship: Some(ship), - selection: PlayerSelection::OrbitingBody(SystemObjectHandle { - system_handle: SystemHandle { index: 0 }, - body_index: 1, - }), + selection: PlayerSelection::OrbitingBody( + ct.systems + .values() + .next() + .unwrap() + .objects + .get(&ContentIndex::new("earth")) + .unwrap() + .clone(), + ), } } @@ -60,7 +68,7 @@ impl PlayerAgent { pub fn step(&mut self, ct: &Content, status: PlayerStatus) { if self.input.get_v_scroll() != 0.0 { self.camera.zoom = (self.camera.zoom + self.input.get_v_scroll()) - .clamp(ct.get_config().zoom_min, ct.get_config().zoom_max); + .clamp(ct.config.zoom_min, ct.config.zoom_max); } if status.pos.is_some() { diff --git a/crates/render/src/gpustate.rs b/crates/render/src/gpustate.rs index f792588..2e79d3f 100644 --- a/crates/render/src/gpustate.rs +++ b/crates/render/src/gpustate.rs @@ -120,21 +120,20 @@ impl GPUState { glyphon::fontdb::Database::new(), ); - let conf = ct.get_config(); - for font in &conf.font_files { + for font in &ct.config.font_files { text_font_system.db_mut().load_font_file(font)?; } // TODO: nice error if no family with this name is found text_font_system .db_mut() - .set_sans_serif_family(conf.font_sans.clone()); + .set_sans_serif_family(ct.config.font_sans.clone()); text_font_system .db_mut() - .set_serif_family(conf.font_serif.clone()); + .set_serif_family(ct.config.font_serif.clone()); text_font_system .db_mut() - .set_monospace_family(conf.font_mono.clone()); + .set_monospace_family(ct.config.font_mono.clone()); //text_font_system // .db_mut() // .set_cursive_family(conf.font_cursive.clone()); @@ -287,16 +286,16 @@ impl GPUState { camera_position_x: input.camera_pos.x, camera_position_y: input.camera_pos.y, camera_zoom: input.camera_zoom, - camera_zoom_min: input.ct.get_config().zoom_min, - camera_zoom_max: input.ct.get_config().zoom_max, + camera_zoom_min: input.ct.config.zoom_min, + camera_zoom_max: input.ct.config.zoom_max, window_size_w: self.state.window_size.width as f32, window_size_h: self.state.window_size.height as f32, window_scale: self.state.window.scale_factor() as f32, window_aspect: self.state.window_aspect, - starfield_sprite: input.ct.get_config().starfield_texture.into(), - starfield_tile_size: input.ct.get_config().starfield_size, - starfield_size_min: input.ct.get_config().starfield_min_size, - starfield_size_max: input.ct.get_config().starfield_max_size, + starfield_sprite: input.ct.config.starfield_texture.into(), + starfield_tile_size: input.ct.config.starfield_size, + starfield_size_min: input.ct.config.starfield_min_size, + starfield_size_max: input.ct.config.starfield_max_size, }]), ); @@ -468,7 +467,7 @@ impl GPUState { ship_pos = Point3::new(pos.x, pos.y, 1.0); let ship_rot = r.rotation(); ship_ang = ship_rot.angle(); - ship_cnt = input.ct.get_ship(s.ship.get_data().get_content()); + ship_cnt = s.ship.get_data().get_content(); } ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => { @@ -477,7 +476,7 @@ impl GPUState { ship_pos = Point3::new(pos.x, pos.y, *current_z); let ship_rot = r.rotation(); ship_ang = ship_rot.angle(); - ship_cnt = input.ct.get_ship(s.ship.get_data().get_content()); + ship_cnt = s.ship.get_data().get_content(); } } @@ -492,8 +491,7 @@ impl GPUState { // This is in game units. // // We take the maximum to account for rotated sprites. - let m = - (ship_cnt.size / ship_pos.z) * input.ct.get_sprite(ship_cnt.sprite).aspect.max(1.0); + let m = (ship_cnt.size / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0); // Don't draw sprites that are off the screen if pos.x < screen_clip.0.x - m @@ -597,7 +595,7 @@ impl GPUState { // This is in game units. // // We take the maximum to account for rotated sprites. - let m = (proj_cnt.size / 1.0) * input.ct.get_sprite(proj_cnt.sprite).aspect.max(1.0); + let m = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0); // Don't draw sprites that are off the screen if pos.x < screen_clip.0.x - m @@ -641,9 +639,15 @@ impl GPUState { // NE and SW corners of screen screen_clip: (Point2, Point2), ) { - let system = input.ct.get_system(input.current_system); + // TODO: sort once, give objects state - for o in &system.objects { + // Sort by z-distance. This is important, since these are + // rendered in this order. We need far objects to be behind + // near objects! + let mut v: Vec<_> = input.current_system.objects.values().collect(); + v.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); + + for o in v { // Position adjusted for parallax let pos: Point2 = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.pos.z; @@ -652,7 +656,7 @@ impl GPUState { // This is in game units. // // We take the maximum to account for rotated sprites. - let m = (o.size / o.pos.z) * input.ct.get_sprite(o.sprite).aspect.max(1.0); + let m = (o.size / o.pos.z) * o.sprite.aspect.max(1.0); // Don't draw sprites that are off the screen if pos.x < screen_clip.0.x - m @@ -680,8 +684,7 @@ impl GPUState { }]), ); - let sprite = input.ct.get_sprite(o.sprite); - let texture_a = sprite.get_first_frame(); // ANIMATE + let texture_a = o.sprite.get_first_frame(); // ANIMATE // Push this object's instance self.state.push_object_buffer(ObjectInstance { @@ -715,12 +718,7 @@ impl GPUState { // This is in game units. // // We take the maximum to account for rotated sprites. - let m = (p.effect.size / 1.0) - * input - .ct - .get_sprite(p.effect.anim.get_sprite()) - .aspect - .max(1.0); + let m = (p.effect.size / 1.0) * p.effect.anim.get_sprite().aspect.max(1.0); // Don't draw sprites that are off the screen if adjusted_pos.x < screen_clip.0.x - m diff --git a/crates/render/src/renderinput.rs b/crates/render/src/renderinput.rs index f19e04a..71eecc8 100644 --- a/crates/render/src/renderinput.rs +++ b/crates/render/src/renderinput.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use galactica_content::{Content, SystemHandle}; +use galactica_content::{Content, System}; use galactica_playeragent::PlayerAgent; use galactica_system::phys::PhysImage; use galactica_util::timing::Timing; @@ -16,7 +16,7 @@ pub struct RenderInput { pub player: Arc, /// The system we're currently in - pub current_system: SystemHandle, + pub current_system: Arc, /// Height of screen, in world units pub camera_zoom: f32, diff --git a/crates/render/src/renderstate.rs b/crates/render/src/renderstate.rs index fa16909..d0836d6 100644 --- a/crates/render/src/renderstate.rs +++ b/crates/render/src/renderstate.rs @@ -39,7 +39,7 @@ impl<'a> VertexBuffers { ui_counter: 0, radialbar_counter: 0, starfield_counter: 0, - starfield_limit: ct.get_config().starfield_instance_limit, + starfield_limit: ct.config.starfield_instance_limit, object: VertexBuffer::new::( "object", @@ -54,7 +54,7 @@ impl<'a> VertexBuffers { &device, Some(SPRITE_VERTICES), Some(SPRITE_INDICES), - ct.get_config().starfield_instance_limit, + ct.config.starfield_instance_limit, ), ui: VertexBuffer::new::( diff --git a/crates/render/src/starfield.rs b/crates/render/src/starfield.rs index 6119c26..7ca9a09 100644 --- a/crates/render/src/starfield.rs +++ b/crates/render/src/starfield.rs @@ -30,36 +30,32 @@ impl Starfield { pub fn regenerate(&mut self, ct: &Content) { // TODO: save seed in system, regenerate on jump let mut rng = rand::thread_rng(); - let sz = ct.get_config().starfield_size as f32 / 2.0; - self.stars = (0..ct.get_config().starfield_count) + let sz = ct.config.starfield_size as f32 / 2.0; + self.stars = (0..ct.config.starfield_count) .map(|_| StarfieldStar { pos: Point3::new( rng.gen_range(-sz..=sz), rng.gen_range(-sz..=sz), - rng.gen_range( - ct.get_config().starfield_min_dist..=ct.get_config().starfield_max_dist, - ), - ), - size: rng.gen_range( - ct.get_config().starfield_min_size..ct.get_config().starfield_max_size, + rng.gen_range(ct.config.starfield_min_dist..=ct.config.starfield_max_dist), ), + size: rng.gen_range(ct.config.starfield_min_size..ct.config.starfield_max_size), tint: Vector2::new(rng.gen_range(0.0..=1.0), rng.gen_range(0.0..=1.0)), }) .collect(); } pub fn update_buffer(&mut self, ct: &Content, state: &mut RenderState) { - let sz = ct.get_config().starfield_size as f32; + let sz = ct.config.starfield_size as f32; // Compute window size in starfield tiles let mut nw_tile = { // Game coordinates (relative to camera) of nw corner of screen. - let clip_nw = Point2::new(state.window_aspect, 1.0) * ct.get_config().zoom_max; + let clip_nw = Point2::new(state.window_aspect, 1.0) * ct.config.zoom_max; // Parallax correction. // Also, adjust v for mod to work properly // (v is centered at 0) - let v: Point2 = clip_nw * ct.get_config().starfield_min_dist; + let v: Point2 = clip_nw * ct.config.starfield_min_dist; let v_adj = Point2::new(v.x + (sz / 2.0), v.y + (sz / 2.0)); #[rustfmt::skip] @@ -83,8 +79,8 @@ impl Starfield { // Truncate tile grid to buffer size // (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) * ct.get_config().starfield_count as i32) - > ct.get_config().starfield_instance_limit as i32 + while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * ct.config.starfield_count as i32) + > ct.config.starfield_instance_limit as i32 { nw_tile -= Vector2::new(1, 1); } diff --git a/crates/render/src/ui/api/functions/sprite.rs b/crates/render/src/ui/api/functions/sprite.rs index ddf6bff..bd5c2a6 100644 --- a/crates/render/src/ui/api/functions/sprite.rs +++ b/crates/render/src/ui/api/functions/sprite.rs @@ -3,7 +3,7 @@ use log::error; use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module}; use std::{cell::RefCell, rc::Rc, sync::Arc}; -use crate::ui::{elements::Sprite, UiElement, UiState}; +use crate::ui::{elements::UiSprite, UiElement, UiState}; use super::super::{Color, Rect}; @@ -20,7 +20,7 @@ pub fn build_sprite_module(ct_src: Arc, state_src: Rc> move |name: ImmutableString, sprite: ImmutableString, rect: Rect| { let mut ui_state = state.borrow_mut(); - let sprite_handle = ct.get_sprite_handle(sprite.as_str()); + let sprite_handle = ct.sprites.get(&sprite.clone().into()); if sprite_handle.is_none() { error!("made a sprite using an invalid source `{sprite}`"); return; @@ -34,7 +34,11 @@ pub fn build_sprite_module(ct_src: Arc, state_src: Rc> ui_state.names.push(name.clone()); ui_state.elements.insert( name.clone(), - UiElement::Sprite(Sprite::new(&ct, name.clone(), sprite_handle.unwrap(), rect)), + UiElement::Sprite(UiSprite::new( + name.clone(), + sprite_handle.unwrap().clone(), + rect, + )), ); }, ); @@ -74,12 +78,12 @@ pub fn build_sprite_module(ct_src: Arc, state_src: Rc> match ui_state.get_mut_by_name(&name) { Some(UiElement::Sprite(x)) => { - let m = ct.get_sprite_handle(mask.as_str()); + let m = ct.sprites.get(&mask.clone().into()).clone(); if m.is_none() { error!("called `set_sprite_mask` with an invalid mask `{mask}`"); return; } - x.set_mask(m) + x.set_mask(m.cloned()) } _ => { @@ -90,7 +94,6 @@ pub fn build_sprite_module(ct_src: Arc, state_src: Rc> ); let state = state_src.clone(); - let ct = ct_src.clone(); let _ = FuncRegistration::new("take_edge") .with_namespace(FnNamespace::Internal) .set_into_module( @@ -100,24 +103,22 @@ pub fn build_sprite_module(ct_src: Arc, state_src: Rc> match ui_state.get_mut_by_name(&name) { Some(UiElement::Sprite(x)) => { - let sprite_handle = x.anim.get_sprite(); - let sprite = &ct.get_sprite(sprite_handle); + let sprite = x.anim.get_sprite(); - let edge = resolve_edge_as_edge(edge_name.as_str(), duration, |x| { - sprite.get_section_handle_by_name(x) - }); + let edge = + resolve_edge_as_edge(&sprite.sections, edge_name.as_str(), duration); let edge = match edge { Err(_) => { error!( "called `sprite::take_edge` on an invalid edge `{}` on sprite `{}`", - edge_name, sprite.name + edge_name, sprite.index ); return; } Ok(s) => s, }; - x.anim.jump_to(&ct, edge); + x.anim.jump_to(&edge); } _ => { error!("called `sprite::take_edge` on an invalid name `{name}`") diff --git a/crates/render/src/ui/api/state.rs b/crates/render/src/ui/api/state.rs index 0163370..4ea09f3 100644 --- a/crates/render/src/ui/api/state.rs +++ b/crates/render/src/ui/api/state.rs @@ -1,4 +1,4 @@ -use galactica_content::{Ship, SystemObject, SystemObjectHandle}; +use galactica_content::{Ship, SystemObject}; use galactica_system::{ data::{self}, phys::{objects::PhysShip, PhysSimShipHandle}, @@ -26,7 +26,7 @@ impl ShipState { .get_ship(self.ship.as_ref().unwrap()) .unwrap(); let handle = ship.ship.get_data().get_content(); - self.input.ct.get_ship(handle) + handle } fn get_ship(&mut self) -> &PhysShip { @@ -48,20 +48,13 @@ impl ShipState { } fn landed_on(&mut self) -> SystemObjectState { - let input = self.input.clone(); match self.get_ship().get_data().get_state() { data::ShipState::Landed { target } => { return SystemObjectState { - input, - object: Some(*target), - } - } - _ => { - return SystemObjectState { - input, - object: None, + object: Some(target.clone()), } } + _ => return SystemObjectState { object: None }, }; } } @@ -89,8 +82,15 @@ impl CustomType for ShipState { .with_fn("is_collapsing", |s: &mut Self| { s.get_ship().get_data().get_state().is_collapsing() }) - .with_fn("name", |s: &mut Self| s.get_content().name.clone()) - .with_fn("thumbnail", |s: &mut Self| s.get_content().thumbnail) + .with_fn("display_name", |s: &mut Self| { + s.get_content().display_name.clone() + }) + .with_fn("content_index", |s: &mut Self| { + s.get_content().display_name.clone() + }) + .with_fn("thumbnail", |s: &mut Self| { + s.get_content().thumbnail.clone() + }) .with_fn("landed_on", |s: &mut Self| s.landed_on()) .with_fn("get_shields", |s: &mut Self| { s.get_ship().get_data().get_shields() @@ -103,14 +103,14 @@ impl CustomType for ShipState { s.get_ship().get_data().get_hull() }) .with_fn("get_size", |s: &mut Self| s.get_content().size) - .with_fn("get_uid", |s: &mut Self| format!("{}", s.get_ship().uid)) + .with_fn("phys_uid", |s: &mut Self| format!("{}", s.get_ship().uid)) .with_fn("get_pos", |s: &mut Self| { let t = s.get_body().translation(); UiVector::new(t.x, t.y) }) .with_fn("get_faction_color", |s: &mut Self| { let h = s.get_ship().get_data().get_faction(); - let c = s.input.ct.get_faction(h).color; + let c = h.color; Color::new(c[0], c[1], c[2], 1.0) }); } @@ -118,15 +118,10 @@ impl CustomType for ShipState { #[derive(Debug, Clone)] pub struct SystemObjectState { - object: Option, - input: Arc, + object: Option>, } -impl SystemObjectState { - fn get_content(&mut self) -> &SystemObject { - self.input.ct.get_system_object(self.object.unwrap()) - } -} +impl SystemObjectState {} impl CustomType for SystemObjectState { fn build(mut builder: TypeBuilder) { @@ -134,9 +129,11 @@ impl CustomType for SystemObjectState { .with_name("SystemObjectState") // // Get landable name - .with_fn("name", |s: &mut Self| { - s.get_content() - .name + .with_fn("display_name", |s: &mut Self| { + s.object + .as_ref() + .unwrap() + .display_name .as_ref() .map(|x| x.to_string()) .unwrap_or_else(|| { @@ -147,7 +144,9 @@ impl CustomType for SystemObjectState { // // Get landable description .with_fn("desc", |s: &mut Self| { - s.get_content() + s.object + .as_ref() + .unwrap() .desc .as_ref() .map(|x| x.to_string()) @@ -159,31 +158,31 @@ impl CustomType for SystemObjectState { // // Get landable landscape image .with_fn("image", |s: &mut Self| { - let handle = s.get_content().image; - if let Some(handle) = handle { - s.input.ct.get_sprite(handle).name.clone() + if let Some(sprite) = &s.object.as_ref().unwrap().image { + sprite.index.to_string() } else { error!("UI called `image()` on a system object which doesn't provide one"); "".to_string() } }) .with_fn("is_some", |s: &mut Self| s.object.is_some()) - .with_fn("==", |a: &mut Self, b: Self| a.object == b.object) - .with_fn("get_size", |s: &mut Self| s.get_content().size) - .with_fn("get_label", |s: &mut Self| { - ImmutableString::from(&s.get_content().label) + .with_fn("==", |a: &mut Self, b: Self| match (&a.object, &b.object) { + (None, _) => false, + (_, None) => false, + (Some(a), Some(b)) => a.index == b.index, + }) + .with_fn("get_size", |s: &mut Self| s.object.as_ref().unwrap().size) + .with_fn("get_index", |s: &mut Self| { + ImmutableString::from(s.object.as_ref().unwrap().index.as_str()) }) .with_fn("get_angle", |s: &mut Self| { - to_degrees(s.get_content().angle) + to_degrees(s.object.as_ref().unwrap().angle) }) .with_fn("get_pos", |s: &mut Self| { - let t = s.get_content().pos; + let t = s.object.as_ref().unwrap().pos; UiVector::new(t.x, t.y) }) - .with_fn("get_pos_z", |s: &mut Self| { - let t = s.get_content().pos; - t.z - }); + .with_fn("get_pos_z", |s: &mut Self| s.object.as_ref().unwrap().pos.z); } } @@ -225,11 +224,9 @@ impl State { pub fn objects(&mut self) -> Array { let mut a = Array::new(); - let s = self.input.current_system; - for o in &self.input.ct.get_system(s).objects { + for (_, o) in &self.input.current_system.objects { a.push(Dynamic::from(SystemObjectState { - input: self.input.clone(), - object: Some(o.handle), + object: Some(o.clone()), })); } return a; diff --git a/crates/render/src/ui/elements/fpsindicator.rs b/crates/render/src/ui/elements/fpsindicator.rs index fbef304..566c83f 100644 --- a/crates/render/src/ui/elements/fpsindicator.rs +++ b/crates/render/src/ui/elements/fpsindicator.rs @@ -51,7 +51,7 @@ impl<'a, 'b: 'a> FpsIndicator { buffer: &self.buffer, left: 10.0, top: 400.0, - scale: input.ct.get_config().ui_scale, + scale: input.ct.config.ui_scale, bounds: TextBounds { left: 10, top: 400, diff --git a/crates/render/src/ui/elements/radialbar.rs b/crates/render/src/ui/elements/radialbar.rs index fe5c8ff..6b45d74 100644 --- a/crates/render/src/ui/elements/radialbar.rs +++ b/crates/render/src/ui/elements/radialbar.rs @@ -38,12 +38,12 @@ impl RadialBar { pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { let rect = self .rect - .to_centered(&state.window, input.ct.get_config().ui_scale); + .to_centered(&state.window, input.ct.config.ui_scale); state.push_radialbar_buffer(RadialBarInstance { position: [rect.pos.x, rect.pos.y], diameter: rect.dim.x.min(rect.dim.y), - stroke: self.stroke * input.ct.get_config().ui_scale, + stroke: self.stroke * input.ct.config.ui_scale, color: self.color.as_array(), angle: self.progress * TAU, }); diff --git a/crates/render/src/ui/elements/sprite.rs b/crates/render/src/ui/elements/sprite.rs index 85c69f1..1f8a20b 100644 --- a/crates/render/src/ui/elements/sprite.rs +++ b/crates/render/src/ui/elements/sprite.rs @@ -1,15 +1,17 @@ +use std::sync::Arc; + use super::super::api::Rect; use crate::{ ui::{api::Color, event::Event}, vertexbuffer::types::UiInstance, RenderInput, RenderState, }; -use galactica_content::{Content, SpriteAutomaton, SpriteHandle}; +use galactica_content::{Sprite, SpriteAutomaton}; use galactica_util::to_radians; use rhai::ImmutableString; #[derive(Debug, Clone)] -pub struct Sprite { +pub struct UiSprite { pub anim: SpriteAutomaton, pub name: ImmutableString, @@ -21,7 +23,7 @@ pub struct Sprite { preserve_aspect: bool, rect: Rect, - mask: Option, + mask: Option>, color: Color, /// If true, ignore mouse events until click is released @@ -30,11 +32,11 @@ pub struct Sprite { has_click: bool, } -impl Sprite { - pub fn new(ct: &Content, name: ImmutableString, sprite: SpriteHandle, rect: Rect) -> Self { +impl UiSprite { + pub fn new(name: ImmutableString, sprite: Arc, rect: Rect) -> Self { Self { name, - anim: SpriteAutomaton::new(&ct, sprite), + anim: SpriteAutomaton::new(sprite), rect, angle: 0.0, color: Color::new(1.0, 1.0, 1.0, 1.0), @@ -46,7 +48,7 @@ impl Sprite { } } - pub fn set_mask(&mut self, mask: Option) { + pub fn set_mask(&mut self, mask: Option>) { self.mask = mask; } @@ -69,11 +71,11 @@ impl Sprite { pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { let mut rect = self .rect - .to_centered(&state.window, input.ct.get_config().ui_scale); + .to_centered(&state.window, input.ct.config.ui_scale); if self.preserve_aspect { let rect_aspect = rect.dim.x / rect.dim.y; - let sprite_aspect = input.ct.get_sprite(self.anim.get_sprite()).aspect; + let sprite_aspect = self.anim.get_sprite().aspect; // "wide rect" case => match height, reduce width if rect_aspect > sprite_aspect { @@ -97,9 +99,9 @@ impl Sprite { texture_fade: anim_state.fade, mask_index: self .mask + .as_ref() .map(|x| { - let sprite = input.ct.get_sprite(x); - let texture_b = sprite.get_first_frame(); // TODO: animate? + let texture_b = x.get_first_frame(); // TODO: animate? [1, texture_b] }) .unwrap_or([0, 0]), @@ -109,7 +111,7 @@ impl Sprite { pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event { let r = self .rect - .to_centered(&state.window, input.ct.get_config().ui_scale); + .to_centered(&state.window, input.ct.config.ui_scale); if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() { self.waiting_for_release = false; @@ -155,6 +157,6 @@ impl Sprite { } pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) { - self.anim.step(&input.ct, input.time_since_last_run); + self.anim.step(input.time_since_last_run); } } diff --git a/crates/render/src/ui/elements/textbox.rs b/crates/render/src/ui/elements/textbox.rs index a3bcc69..a6ee45c 100644 --- a/crates/render/src/ui/elements/textbox.rs +++ b/crates/render/src/ui/elements/textbox.rs @@ -86,9 +86,7 @@ impl TextBox { impl<'a, 'b: 'a> TextBox { pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> { - let rect = self - .rect - .to_centered(window, input.ct.get_config().ui_scale); + let rect = self.rect.to_centered(window, input.ct.config.ui_scale); // Glypon works with physical pixels, so we must do some conversion let fac = window.scale_factor() as f32; @@ -103,7 +101,7 @@ impl<'a, 'b: 'a> TextBox { buffer: &self.buffer, top: corner_ne.y, left: corner_ne.x, - scale: input.ct.get_config().ui_scale, + scale: input.ct.config.ui_scale, bounds: TextBounds { top: (corner_ne.y) as i32, bottom: (corner_sw.y) as i32, diff --git a/crates/render/src/ui/executor.rs b/crates/render/src/ui/executor.rs index 8337d8a..c04668e 100644 --- a/crates/render/src/ui/executor.rs +++ b/crates/render/src/ui/executor.rs @@ -92,7 +92,7 @@ impl UiScriptExecutor { rhai_error_to_anyhow( self.engine.call_fn( &mut self.scope, - ct.get_config() + ct.config .ui_scenes .get(current_scene.as_ref().unwrap().as_str()) .unwrap(), @@ -114,7 +114,7 @@ impl UiScriptExecutor { if (*self.state).borrow().get_scene().is_none() { (*self.state) .borrow_mut() - .set_scene(ImmutableString::from(&ct.get_config().start_ui_scene)); + .set_scene(ImmutableString::from(&ct.config.start_ui_scene)); } self.set_scene(state, input.clone())?; let current_scene = (*self.state).borrow().get_scene().clone(); @@ -124,7 +124,7 @@ impl UiScriptExecutor { // Run step() (if it is defined) let ast = ct - .get_config() + .config .ui_scenes .get(current_scene.as_ref().unwrap().as_str()) .unwrap(); @@ -161,7 +161,7 @@ impl UiScriptExecutor { rhai_error_to_anyhow( self.engine.call_fn( &mut self.scope, - ct.get_config() + ct.config .ui_scenes .get(current_scene.as_ref().unwrap().as_str()) .unwrap(), @@ -221,7 +221,7 @@ impl UiScriptExecutor { rhai_error_to_anyhow( self.engine.call_fn( &mut self.scope, - ct.get_config() + ct.config .ui_scenes .get(current_scene.as_ref().unwrap().as_str()) .unwrap(), diff --git a/crates/render/src/ui/state.rs b/crates/render/src/ui/state.rs index 32c1a04..0cbae65 100644 --- a/crates/render/src/ui/state.rs +++ b/crates/render/src/ui/state.rs @@ -6,12 +6,12 @@ use std::collections::HashMap; use std::sync::Arc; use winit::window::Window; -use super::elements::{FpsIndicator, RadialBar, Sprite, TextBox}; +use super::elements::{FpsIndicator, RadialBar, TextBox, UiSprite}; use crate::{RenderInput, RenderState}; #[derive(Debug)] pub enum UiElement { - Sprite(Sprite), + Sprite(UiSprite), RadialBar(RadialBar), Text(TextBox), } @@ -95,7 +95,7 @@ impl UiState { } pub fn set_scene(&mut self, scene: ImmutableString) { - if !self.ct.get_config().ui_scenes.contains_key(scene.as_str()) { + if !self.ct.config.ui_scenes.contains_key(scene.as_str()) { error!("tried to switch to ui scene `{scene}`, which doesn't exist"); return; } diff --git a/crates/system/src/data/ship/outfitset.rs b/crates/system/src/data/ship/outfitset.rs index 0cb3c67..503bb3d 100644 --- a/crates/system/src/data/ship/outfitset.rs +++ b/crates/system/src/data/ship/outfitset.rs @@ -1,6 +1,6 @@ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; -use galactica_content::{GunPoint, Outfit, OutfitHandle, OutfitSpace}; +use galactica_content::{ContentIndex, GunPoint, Outfit, OutfitSpace}; /// Possible outcomes when adding an outfit pub enum OutfitAddResult { @@ -34,7 +34,7 @@ pub enum OutfitRemoveResult { /// A simple data class, used to keep track of delayed shield generators #[derive(Debug, Clone)] pub(crate) struct ShieldGenerator { - pub outfit: OutfitHandle, + pub outfit: Arc, pub delay: f32, pub generation: f32, } @@ -46,7 +46,7 @@ pub(crate) struct ShieldGenerator { #[derive(Debug, Clone)] pub struct OutfitSet { /// What outfits does this statsum contain? - outfits: HashMap, + outfits: HashMap, u32)>, /// Space available in this outfitset. /// set at creation and never changes. @@ -59,7 +59,7 @@ pub struct OutfitSet { /// The gun points available in this ship. /// If value is None, this point is free. /// if value is Some, this point is taken. - gun_points: HashMap>, + gun_points: HashMap>>, /// Outfit values /// This isn't strictly necessary, but we don't want to @@ -89,7 +89,7 @@ impl OutfitSet { } } - pub(super) fn add(&mut self, o: &Outfit) -> OutfitAddResult { + pub(super) fn add(&mut self, o: &Arc) -> OutfitAddResult { if !(self.total_space - self.used_space).can_contain(&o.space) { return OutfitAddResult::NotEnoughSpace("TODO".to_string()); } @@ -100,7 +100,7 @@ impl OutfitSet { let mut added = false; for (_, outfit) in &mut self.gun_points { if outfit.is_none() { - *outfit = Some(o.handle); + *outfit = Some(o.clone()); added = true; } } @@ -115,29 +115,29 @@ impl OutfitSet { self.steer_power += o.steer_power; self.shield_strength += o.shield_strength; self.shield_generators.push(ShieldGenerator { - outfit: o.handle, + outfit: o.clone(), delay: o.shield_delay, generation: o.shield_generation, }); - if self.outfits.contains_key(&o.handle) { - *self.outfits.get_mut(&o.handle).unwrap() += 1; + if self.outfits.contains_key(&o.index) { + self.outfits.get_mut(&o.index).unwrap().1 += 1; } else { - self.outfits.insert(o.handle, 1); + self.outfits.insert(o.index.clone(), (o.clone(), 1)); } return OutfitAddResult::Ok; } - pub(super) fn remove(&mut self, o: &Outfit) -> OutfitRemoveResult { - if !self.outfits.contains_key(&o.handle) { + pub(super) fn remove(&mut self, o: &Arc) -> OutfitRemoveResult { + if !self.outfits.contains_key(&o.index) { return OutfitRemoveResult::NotExist; } else { - let n = *self.outfits.get(&o.handle).unwrap(); - if n == 1u32 { - self.outfits.remove(&o.handle); + let n = self.outfits.get_mut(&o.index).unwrap(); + if n.1 == 1u32 { + self.outfits.remove(&o.index); } else { - *self.outfits.get_mut(&o.handle).unwrap() -= 1; + self.outfits.get_mut(&o.index).unwrap().1 -= 1; } } @@ -152,7 +152,7 @@ impl OutfitSet { let index = self .shield_generators .iter() - .position(|g| g.outfit == o.handle) + .position(|g| g.outfit.index == o.index) .unwrap(); self.shield_generators.remove(index); } @@ -165,16 +165,16 @@ impl OutfitSet { impl OutfitSet { /// The number of outfits in this set pub fn len(&self) -> u32 { - self.outfits.iter().map(|(_, x)| x).sum() + self.outfits.iter().map(|(_, (_, x))| x).sum() } /// Iterate over all outfits - pub fn iter_outfits(&self) -> impl Iterator { - self.outfits.iter() + pub fn iter_outfits(&self) -> impl Iterator, u32)> { + self.outfits.values() } /// Iterate over all gun points - pub fn iter_gun_points(&self) -> impl Iterator)> { + pub fn iter_gun_points(&self) -> impl Iterator>)> { self.gun_points.iter() } @@ -190,7 +190,7 @@ impl OutfitSet { /// Get the outfit attached to the given gun point /// Will panic if this gunpoint is not in this outfit set. - pub fn get_gun(&self, point: &GunPoint) -> Option { + pub fn get_gun(&self, point: &GunPoint) -> Option> { self.gun_points.get(point).unwrap().clone() } diff --git a/crates/system/src/data/ship/ship.rs b/crates/system/src/data/ship/ship.rs index e27a9fa..c16f673 100644 --- a/crates/system/src/data/ship/ship.rs +++ b/crates/system/src/data/ship/ship.rs @@ -1,7 +1,7 @@ -use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle}; +use galactica_content::{Faction, GunPoint, Outfit, Ship, SystemObject}; use nalgebra::Isometry2; use rand::{rngs::ThreadRng, Rng}; -use std::{collections::HashMap, time::Instant}; +use std::{collections::HashMap, sync::Arc, time::Instant}; use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState}; @@ -9,8 +9,8 @@ use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState}; #[derive(Debug, Clone)] pub struct ShipData { // Metadata values - ct_handle: ShipHandle, - faction: FactionHandle, + ship: Arc, + faction: Arc, outfits: OutfitSet, personality: ShipPersonality, @@ -34,16 +34,14 @@ pub struct ShipData { impl ShipData { /// Create a new ShipData pub(crate) fn new( - ct: &Content, - ct_handle: ShipHandle, - faction: FactionHandle, + ship: Arc, + faction: Arc, personality: ShipPersonality, ) -> Self { - let s = ct.get_ship(ct_handle); ShipData { - ct_handle, + ship: ship.clone(), faction, - outfits: OutfitSet::new(s.space, &s.guns), + outfits: OutfitSet::new(ship.space, &ship.guns), personality, last_hit: Instant::now(), rng: rand::thread_rng(), @@ -54,9 +52,9 @@ impl ShipData { }, // Initial stats - hull: s.hull, + hull: ship.hull, shields: 0.0, - gun_cooldowns: s.guns.iter().map(|x| (x.clone(), 0.0)).collect(), + gun_cooldowns: ship.guns.iter().map(|x| (x.clone(), 0.0)).collect(), } } @@ -80,7 +78,7 @@ impl ShipData { /// That is the simulation's responsiblity. /// /// Will panic if we're not flying. - pub fn start_land_on(&mut self, target_handle: SystemObjectHandle) { + pub fn start_land_on(&mut self, target_handle: Arc) { match self.state { ShipState::Flying { .. } => { self.state = ShipState::Landing { @@ -108,9 +106,11 @@ impl ShipData { /// Finish landing sequence /// Will panic if we're not landing pub fn finish_land_on(&mut self) { - match self.state { + match &self.state { ShipState::Landing { target, .. } => { - self.state = ShipState::Landed { target }; + self.state = ShipState::Landed { + target: target.clone(), + }; } _ => { unreachable!("Called `finish_land_on` on a ship that isn't landing!") @@ -123,14 +123,13 @@ impl ShipData { /// That is the simulation's responsiblity. /// /// Will panic if we're not flying. - pub fn start_unland_to(&mut self, ct: &Content, to_position: Isometry2) { - match self.state { + pub fn start_unland_to(&mut self, to_position: Isometry2) { + match &self.state { ShipState::Landed { target } => { - let obj = ct.get_system_object(target); self.state = ShipState::UnLanding { to_position, - from: target, - current_z: obj.pos.z, + from: target.clone(), + current_z: target.pos.z, }; } _ => { @@ -177,14 +176,14 @@ impl ShipData { } /// Add an outfit to this ship - pub fn add_outfit(&mut self, o: &Outfit) -> super::OutfitAddResult { + pub fn add_outfit(&mut self, o: &Arc) -> super::OutfitAddResult { let r = self.outfits.add(o); self.shields = self.outfits.get_total_shields(); return r; } /// Remove an outfit from this ship - pub fn remove_outfit(&mut self, o: &Outfit) -> super::OutfitRemoveResult { + pub fn remove_outfit(&mut self, o: &Arc) -> super::OutfitRemoveResult { self.outfits.remove(o) } @@ -192,16 +191,14 @@ impl ShipData { /// Will panic if `which` isn't a point on this ship. /// Returns `true` if this gun was fired, /// and `false` if it is on cooldown or empty. - pub(crate) fn fire_gun(&mut self, ct: &Content, which: &GunPoint) -> bool { + pub(crate) fn fire_gun(&mut self, which: &GunPoint) -> bool { let c = self.gun_cooldowns.get_mut(which).unwrap(); if *c > 0.0 { return false; } - let g = self.outfits.get_gun(which); - if g.is_some() { - let g = ct.get_outfit(g.unwrap()); + if let Some(g) = self.outfits.get_gun(which) { let gun = g.gun.as_ref().unwrap(); *c = 0f32.max(gun.rate + self.rng.gen_range(-gun.rate_rng..=gun.rate_rng)); return true; @@ -289,8 +286,8 @@ impl ShipData { } /// Get a handle to this ship's content - pub fn get_content(&self) -> ShipHandle { - self.ct_handle + pub fn get_content(&self) -> &Arc { + &self.ship } /// Get this ship's current hull. @@ -316,12 +313,12 @@ impl ShipData { } /// Get this ship's faction - pub fn get_faction(&self) -> FactionHandle { - self.faction + pub fn get_faction(&self) -> &Arc { + &self.faction } /// Get this ship's content handle - pub fn get_ship(&self) -> ShipHandle { - self.ct_handle + pub fn get_ship(&self) -> &Arc { + &self.ship } } diff --git a/crates/system/src/data/ship/shipstate.rs b/crates/system/src/data/ship/shipstate.rs index 9061b52..cc317c4 100644 --- a/crates/system/src/data/ship/shipstate.rs +++ b/crates/system/src/data/ship/shipstate.rs @@ -1,7 +1,6 @@ -use std::num::NonZeroU32; - -use galactica_content::SystemObjectHandle; +use galactica_content::SystemObject; use rapier2d::math::Isometry; +use std::{num::NonZeroU32, sync::Arc}; /// A ship autopilot. /// An autopilot is a lightweight ShipController that @@ -14,7 +13,7 @@ pub enum ShipAutoPilot { /// Automatically arrange for landing on the given object Landing { /// The body we want to land on - target: SystemObjectHandle, + target: Arc, }, } @@ -40,14 +39,14 @@ pub enum ShipState { /// This ship is landed on a planet Landed { /// The planet this ship is landed on - target: SystemObjectHandle, + target: Arc, }, /// This ship is landing on a planet /// (playing the animation) Landing { /// The planet we're landing on - target: SystemObjectHandle, + target: Arc, /// Our current z-coordinate current_z: f32, @@ -60,7 +59,7 @@ pub enum ShipState { to_position: Isometry, /// The planet we're taking off from - from: SystemObjectHandle, + from: Arc, /// Our current z-coordinate current_z: f32, @@ -69,9 +68,9 @@ pub enum ShipState { impl ShipState { /// What planet is this ship landed on? - pub fn landed_on(&self) -> Option { + pub fn landed_on(&self) -> Option> { match self { - Self::Landed { target } => Some(*target), + Self::Landed { target } => Some(target.clone()), _ => None, } } diff --git a/crates/system/src/phys/objects/effect.rs b/crates/system/src/phys/objects/effect.rs index 98d0dbc..a9bb700 100644 --- a/crates/system/src/phys/objects/effect.rs +++ b/crates/system/src/phys/objects/effect.rs @@ -1,7 +1,8 @@ -use galactica_content::{Content, EffectHandle, EffectVelocity, SpriteAutomaton}; +use galactica_content::{Effect, EffectVelocity, SpriteAutomaton}; use nalgebra::{Point2, Rotation2, Vector2}; use rand::Rng; use rapier2d::dynamics::{RevoluteJointBuilder, RigidBodyBuilder, RigidBodyHandle, RigidBodyType}; +use std::sync::Arc; use crate::phys::{PhysStepResources, PhysWrapper}; @@ -32,16 +33,13 @@ pub struct PhysEffect { impl PhysEffect { /// Create a new effect inside `Wrapper` pub fn new( - ct: &Content, wrapper: &mut PhysWrapper, - effect: EffectHandle, + effect: Arc, // Where to spawn the particle, in world space. pos: Vector2, parent: RigidBodyHandle, target: Option, ) -> Self { - let effect = ct.get_effect(effect); - let mut rng = rand::thread_rng(); let parent_body = wrapper.get_rigid_body(parent).unwrap(); let parent_angle = parent_body.rotation().angle(); @@ -113,7 +111,7 @@ impl PhysEffect { }; PhysEffect { - anim: SpriteAutomaton::new(ct, effect.sprite), + anim: SpriteAutomaton::new(effect.sprite.clone()), rigid_body, lifetime: 0f32.max( effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng), @@ -153,7 +151,7 @@ impl PhysEffect { ); PhysEffect { - anim: SpriteAutomaton::new(ct, effect.sprite), + anim: SpriteAutomaton::new(effect.sprite.clone()), rigid_body, lifetime: 0f32.max( effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng), @@ -174,7 +172,7 @@ impl PhysEffect { return; } - self.anim.step(&res.ct, res.t); + self.anim.step(res.t); self.lifetime -= res.t; if self.lifetime <= 0.0 { diff --git a/crates/system/src/phys/objects/projectile.rs b/crates/system/src/phys/objects/projectile.rs index ffbd6fd..f46f8ea 100644 --- a/crates/system/src/phys/objects/projectile.rs +++ b/crates/system/src/phys/objects/projectile.rs @@ -1,4 +1,6 @@ -use galactica_content::{AnimationState, Content, FactionHandle, Projectile, SpriteAutomaton}; +use std::sync::Arc; + +use galactica_content::{AnimationState, Faction, Projectile, SpriteAutomaton}; use rand::Rng; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; @@ -10,7 +12,7 @@ use super::PhysEffect; #[derive(Debug, Clone)] pub struct PhysProjectile { /// This projectile's game data - pub content: Projectile, + pub content: Arc, /// This projectile's sprite animation state anim: SpriteAutomaton, @@ -19,7 +21,7 @@ pub struct PhysProjectile { lifetime: f32, /// The faction this projectile belongs to - pub faction: FactionHandle, + pub faction: Arc, /// This projectile's rigidbody pub rigid_body: RigidBodyHandle, @@ -38,17 +40,16 @@ pub struct PhysProjectile { impl PhysProjectile { /// Create a new projectile pub fn new( - ct: &Content, - content: Projectile, // TODO: use a handle? + content: Arc, rigid_body: RigidBodyHandle, - faction: FactionHandle, + faction: Arc, collider: ColliderHandle, ) -> Self { let mut rng = rand::thread_rng(); let size_rng = content.size_rng; let lifetime = content.lifetime; PhysProjectile { - anim: SpriteAutomaton::new(ct, content.sprite), + anim: SpriteAutomaton::new(content.sprite.clone()), rigid_body, collider, content, @@ -67,31 +68,24 @@ impl PhysProjectile { wrapper: &mut PhysWrapper, ) { self.lifetime -= res.t; - self.anim.step(&res.ct, res.t); + self.anim.step(res.t); if self.lifetime <= 0.0 { - self.destroy(res, new, wrapper, true); + self.destroy(new, wrapper, true); } } /// Destroy this projectile without creating an expire effect pub(in crate::phys) fn destroy_silent( &mut self, - res: &PhysStepResources, new: &mut NewObjects, wrapper: &mut PhysWrapper, ) { - self.destroy(res, new, wrapper, false); + self.destroy(new, wrapper, false); } /// Destroy this projectile - fn destroy( - &mut self, - res: &PhysStepResources, - new: &mut NewObjects, - wrapper: &mut PhysWrapper, - expire: bool, - ) { + fn destroy(&mut self, new: &mut NewObjects, wrapper: &mut PhysWrapper, expire: bool) { if self.is_destroyed { return; } @@ -100,11 +94,10 @@ impl PhysProjectile { if expire { match &self.content.expire_effect { None => {} - Some(handle) => { + Some(effect) => { new.effects.push(PhysEffect::new( - &res.ct, wrapper, - *handle, + effect.clone(), *rb.translation(), self.rigid_body, None, diff --git a/crates/system/src/phys/objects/ship/collapse.rs b/crates/system/src/phys/objects/ship/collapse.rs index affb6ee..7e77544 100644 --- a/crates/system/src/phys/objects/ship/collapse.rs +++ b/crates/system/src/phys/objects/ship/collapse.rs @@ -1,4 +1,4 @@ -use galactica_content::{CollapseEvent, Content, Ship}; +use galactica_content::{CollapseEvent, Ship}; use nalgebra::{Point2, Vector2}; use rand::{rngs::ThreadRng, Rng}; use rapier2d::{ @@ -37,14 +37,13 @@ impl ShipCollapseSequence { } /// Pick a random points inside a ship's collider - fn random_in_ship(&mut self, ct: &Content, ship: &Ship, collider: &Collider) -> Vector2 { + fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2 { let mut y = 0.0; let mut x = 0.0; let mut a = false; while !a { x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0; - y = self.rng.gen_range(-1.0..=1.0) * ship.size * ct.get_sprite(ship.sprite).aspect - / 2.0; + y = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0; a = collider.shape().contains_local_point(&Point2::new(x, y)); } Vector2::new(x, y) @@ -62,7 +61,7 @@ impl ShipCollapseSequence { ) { let rigid_body = wrapper.get_rigid_body(rigid_body_handle).unwrap().clone(); let collider = wrapper.get_collider(collider_handle).unwrap().clone(); - let ship_content = res.ct.get_ship(ship_data.get_content()); + let ship_content = ship_data.get_content(); let ship_pos = rigid_body.translation(); let ship_rot = rigid_body.rotation(); @@ -84,14 +83,13 @@ impl ShipCollapseSequence { let pos: Vector2 = if let Some(pos) = spawner.pos { Vector2::new(pos.x, pos.y) } else { - self.random_in_ship(&res.ct, ship_content, &collider) + self.random_in_ship(&ship_content, &collider) }; let pos = ship_pos + (ship_rot * pos); new.effects.push(PhysEffect::new( - &res.ct, wrapper, - spawner.effect, + spawner.effect.clone(), pos, rigid_body_handle, None, @@ -124,15 +122,14 @@ impl ShipCollapseSequence { let pos = if let Some(pos) = spawner.pos { Vector2::new(pos.x, pos.y) } else { - self.random_in_ship(&res.ct, ship_content, &collider) + self.random_in_ship(&ship_content, &collider) }; // Position, adjusted for ship rotation let pos = ship_pos + (ship_rot * pos); new.effects.push(PhysEffect::new( - &res.ct, wrapper, - spawner.effect, + spawner.effect.clone(), pos, rigid_body_handle, None, diff --git a/crates/system/src/phys/objects/ship/controller/point.rs b/crates/system/src/phys/objects/ship/controller/point.rs index 3249c46..a2552ef 100644 --- a/crates/system/src/phys/objects/ship/controller/point.rs +++ b/crates/system/src/phys/objects/ship/controller/point.rs @@ -25,7 +25,7 @@ impl PointShipController { impl ShipControllerStruct for PointShipController { fn update_controls( &mut self, - res: &PhysStepResources, + _res: &PhysStepResources, img: &PhysImage, this_ship: PhysSimShipHandle, ) -> Option { @@ -40,14 +40,14 @@ impl ShipControllerStruct for PointShipController { let my_position = this_rigidbody.translation(); let my_rotation = this_rigidbody.rotation(); let my_angvel = this_rigidbody.angvel(); - let my_faction = res.ct.get_faction(my_ship.ship.data.get_faction()); + let my_faction = my_ship.ship.data.get_faction(); // Iterate all possible targets let mut hostile_ships = img.iter_ships().filter( // Target only flying ships we're hostile towards |s| match my_faction .relationships - .get(&s.ship.data.get_faction()) + .get(&s.ship.data.get_faction().index) .unwrap() { Relationship::Hostile => match s.ship.data.get_state() { diff --git a/crates/system/src/phys/objects/ship/ship.rs b/crates/system/src/phys/objects/ship/ship.rs index 5053cf3..9bbcffa 100644 --- a/crates/system/src/phys/objects/ship/ship.rs +++ b/crates/system/src/phys/objects/ship/ship.rs @@ -1,6 +1,8 @@ +use std::sync::Arc; + use galactica_content::{ - AnimationState, Content, EnginePoint, FactionHandle, GunPoint, OutfitHandle, - ProjectileCollider, ShipHandle, SpriteAutomaton, + AnimationState, EnginePoint, Faction, GunPoint, Outfit, ProjectileCollider, Ship, + SpriteAutomaton, }; use nalgebra::{vector, Point2, Rotation2, Vector2}; use rand::Rng; @@ -66,24 +68,22 @@ pub struct PhysShip { impl PhysShip { /// Make a new ship pub(crate) fn new( - ct: &Content, - handle: ShipHandle, + ship: Arc, personality: ShipPersonality, - faction: FactionHandle, + faction: Arc, rigid_body: RigidBodyHandle, collider: ColliderHandle, ) -> Self { - let ship_ct = ct.get_ship(handle); PhysShip { uid: get_phys_id(), - anim: SpriteAutomaton::new(ct, ship_ct.sprite), + anim: SpriteAutomaton::new(ship.sprite.clone()), rigid_body, collider, - data: ShipData::new(ct, handle, faction, personality), + data: ShipData::new(ship.clone(), faction, personality), engine_anim: Vec::new(), controls: ShipControls::new(), last_controls: ShipControls::new(), - collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)), + collapse_sequence: Some(ShipCollapseSequence::new(ship.collapse.length)), is_destroyed: false, controller: match personality { ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(), @@ -105,32 +105,28 @@ impl PhysShip { } self.data.step(res.t); - self.anim.step(&res.ct, res.t); + self.anim.step(res.t); for (_, e) in &mut self.engine_anim { - e.step(&res.ct, res.t); + e.step(res.t); } // Flare animations if !self.controls.thrust && self.last_controls.thrust { - let flare = self.get_flare(&res.ct); - if flare.is_some() { - let flare_outfit = flare.unwrap(); - let flare = res.ct.get_outfit(flare_outfit); + let flare = self.get_flare(); + if let Some(flare) = flare { if flare.engine_flare_on_stop.is_some() { for (_, e) in &mut self.engine_anim { - e.jump_to(&res.ct, flare.engine_flare_on_stop.unwrap()); + e.jump_to(flare.engine_flare_on_stop.as_ref().unwrap()); } } }; } else if self.controls.thrust && !self.last_controls.thrust { - let flare = self.get_flare(&res.ct); - if flare.is_some() { - let flare_outfit = flare.unwrap(); - let flare = res.ct.get_outfit(flare_outfit); + let flare = self.get_flare(); + if let Some(flare) = flare { if flare.engine_flare_on_start.is_some() { for (_, e) in &mut self.engine_anim { - e.jump_to(&res.ct, flare.engine_flare_on_start.unwrap()); + e.jump_to(flare.engine_flare_on_start.as_ref().unwrap()); } } }; @@ -162,12 +158,11 @@ impl PhysShip { // Compute new controls let controls = match autopilot { ShipAutoPilot::Landing { target } => { - let target_obj = res.ct.get_system_object(*target); let controls = autopilot::auto_landing( res, img, PhysSimShipHandle(self.collider), - Vector2::new(target_obj.pos.x, target_obj.pos.y), + Vector2::new(target.pos.x, target.pos.y), ); // Try to land the ship. @@ -176,7 +171,7 @@ impl PhysShip { let landed = 'landed: { let r = wrapper.get_rigid_body(self.rigid_body).unwrap(); - let t_pos = Vector2::new(target_obj.pos.x, target_obj.pos.y); + let t_pos = Vector2::new(target.pos.x, target.pos.y); let s_pos = Vector2::new( r.position().translation.x, r.position().translation.y, @@ -186,13 +181,13 @@ impl PhysShip { // Can't just set_active(false), since we still need that collider's mass. // We're in land range... - if (t_pos - s_pos).magnitude() > target_obj.size / 2.0 { + if (t_pos - s_pos).magnitude() > target.size / 2.0 { break 'landed false; } // And we'll stay in land range long enough. if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0)) - .magnitude() > target_obj.size / 2.0 + .magnitude() > target.size / 2.0 { break 'landed false; } @@ -202,7 +197,7 @@ impl PhysShip { Group::GROUP_1, Group::empty(), )); - self.data.start_land_on(*target); + self.data.start_land_on(target.clone()); break 'landed true; }; @@ -229,7 +224,7 @@ impl PhysShip { if self.controls.guns { // TODO: don't allocate here. This is a hack to satisfy the borrow checker, // convert this to a refcell or do the replace dance. - let pairs: Vec<(GunPoint, Option)> = self + let pairs: Vec<(GunPoint, Option>)> = self .data .get_outfits() .iter_gun_points() @@ -237,7 +232,7 @@ impl PhysShip { .collect(); for (gun_point, outfit) in pairs { - if self.data.fire_gun(&res.ct, &gun_point) { + if self.data.fire_gun(&gun_point) { let outfit = outfit.unwrap(); let mut rng = rand::thread_rng(); @@ -250,20 +245,17 @@ impl PhysShip { rigid_body.velocity_at_point(rigid_body.center_of_mass()); let pos = ship_pos + (ship_rot * gun_point.pos); + let gun = outfit.gun.as_ref().unwrap(); - let outfit = res.ct.get_outfit(outfit); - let outfit = outfit.gun.as_ref().unwrap(); - - let spread = rng.gen_range( - -outfit.projectile.angle_rng..=outfit.projectile.angle_rng, - ); + let spread = + rng.gen_range(-gun.projectile.angle_rng..=gun.projectile.angle_rng); let vel = ship_vel + (Rotation2::new(ship_ang + spread) * Vector2::new( - outfit.projectile.speed + gun.projectile.speed + rng.gen_range( - -outfit.projectile.speed_rng - ..=outfit.projectile.speed_rng, + -gun.projectile.speed_rng + ..=gun.projectile.speed_rng, ), 0.0, )); @@ -274,7 +266,7 @@ impl PhysShip { .linvel(vel) .build(); - let mut collider = match &outfit.projectile.collider { + let mut collider = match &gun.projectile.collider { ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius) .sensor(true) .active_events(ActiveEvents::COLLISION_EVENTS) @@ -290,10 +282,9 @@ impl PhysShip { let collider = wrapper.insert_collider(collider, rigid_body); new.projectiles.push(PhysProjectile::new( - &res.ct, - outfit.projectile.clone(), + gun.projectile.clone(), rigid_body, - self.data.get_faction(), + self.data.get_faction().clone(), collider, )); } @@ -306,8 +297,6 @@ impl PhysShip { current_z, from, } => { - let from_obj = res.ct.get_system_object(*from); - let controls = autopilot::auto_landing( &res, img, @@ -315,7 +304,7 @@ impl PhysShip { Vector2::new(to_position.translation.x, to_position.translation.y), ); let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap(); - let max_d = (Vector2::new(from_obj.pos.x, from_obj.pos.y) + let max_d = (Vector2::new(from.pos.x, from.pos.y) - Vector2::new(to_position.translation.x, to_position.translation.y)) .magnitude(); let now_d = (r.translation() @@ -324,7 +313,7 @@ impl PhysShip { let f = now_d / max_d; let current_z = *current_z; - let zdist = 1.0 - from_obj.pos.z; + let zdist = 1.0 - from.pos.z; if current_z <= 1.0 { // Finish unlanding ship @@ -351,18 +340,17 @@ impl PhysShip { } ShipState::Landing { target, current_z } => { - let target_obj = res.ct.get_system_object(*target); let controls = autopilot::auto_landing( &res, img, PhysSimShipHandle(self.collider), - Vector2::new(target_obj.pos.x, target_obj.pos.y), + Vector2::new(target.pos.x, target.pos.y), ); let current_z = *current_z; - let zdist = target_obj.pos.z - 1.0; + let zdist = target.pos.z - 1.0; - if current_z >= target_obj.pos.z { + if current_z >= target.pos.z { // Finish landing ship self.data.finish_land_on(); let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap(); @@ -432,7 +420,7 @@ impl PhysShip { let rigid_body = wrapper.get_rigid_body(self.rigid_body).unwrap().clone(); let collider = wrapper.get_collider(self.collider).unwrap().clone(); - let ship_content = res.ct.get_ship(self.data.get_content()); + let ship_content = self.data.get_content(); let ship_pos = rigid_body.translation(); let ship_rot = rigid_body.rotation(); let ship_ang = ship_rot.angle(); @@ -451,7 +439,7 @@ impl PhysShip { while !a { x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0; y = rng.gen_range(-1.0..=1.0) - * ship_content.size * res.ct.get_sprite(ship_content.sprite).aspect + * ship_content.size * ship_content.sprite.aspect / 2.0; a = collider.shape().contains_local_point(&Point2::new(x, y)); } @@ -461,9 +449,8 @@ impl PhysShip { let pos = ship_pos + (Rotation2::new(ship_ang) * pos); new.effects.push(PhysEffect::new( - &res.ct, wrapper, - e.effect, + e.effect.clone(), pos, self.rigid_body, None, @@ -476,12 +463,11 @@ impl PhysShip { /// Public mutable impl PhysShip { - fn get_flare(&mut self, ct: &Content) -> Option { + fn get_flare(&mut self) -> Option> { // TODO: better way to pick flare sprite for (h, _) in self.data.get_outfits().iter_outfits() { - let c = ct.get_outfit(*h); - if c.engine_flare_sprite.is_some() { - return Some(*h); + if h.engine_flare_sprite.is_some() { + return Some(h.clone()); } } return None; @@ -489,37 +475,47 @@ impl PhysShip { /// Re-create this ship's engine flare animations /// Should be called whenever we change outfits - fn update_flares(&mut self, ct: &Content) { - let flare_outfit = self.get_flare(ct); - if flare_outfit.is_none() { + fn update_flares(&mut self) { + let flare = self.get_flare(); + if flare.is_none() { self.engine_anim = Vec::new(); return; } - let flare = ct - .get_outfit(flare_outfit.unwrap()) - .engine_flare_sprite - .unwrap(); - self.engine_anim = ct - .get_ship(self.data.get_content()) + self.engine_anim = self + .data + .get_content() .engines .iter() - .map(|e| (e.clone(), SpriteAutomaton::new(ct, flare))) + .map(|e| { + ( + e.clone(), + SpriteAutomaton::new( + flare + .as_ref() + .unwrap() + .engine_flare_sprite + .as_ref() + .unwrap() + .clone(), + ), + ) + }) .collect(); } /// Add one outfit to this ship - pub fn add_outfit(&mut self, ct: &Content, o: OutfitHandle) { - self.data.add_outfit(ct.get_outfit(o)); - self.update_flares(ct); + pub fn add_outfit(&mut self, o: Arc) { + self.data.add_outfit(&o); + self.update_flares(); } /// Add many outfits to this ship - pub fn add_outfits(&mut self, ct: &Content, outfits: impl IntoIterator) { + pub fn add_outfits(&mut self, outfits: impl IntoIterator>) { for o in outfits { - self.data.add_outfit(ct.get_outfit(o)); + self.data.add_outfit(&o); } - self.update_flares(ct); + self.update_flares(); } } diff --git a/crates/system/src/phys/physsim.rs b/crates/system/src/phys/physsim.rs index f4a5599..1315e0e 100644 --- a/crates/system/src/phys/physsim.rs +++ b/crates/system/src/phys/physsim.rs @@ -1,5 +1,6 @@ +use galactica_content::Faction; use galactica_content::Relationship; -use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle}; +use galactica_content::Ship; use galactica_playeragent::PlayerAgent; use nalgebra::{Isometry2, Point2, Rotation2, Vector2}; use rand::Rng; @@ -8,6 +9,7 @@ use rapier2d::{ geometry::{ColliderHandle, Group, InteractionGroups}, }; use std::collections::HashMap; +use std::sync::Arc; use super::PhysEffectImage; use super::{ @@ -49,13 +51,8 @@ impl NewObjects { /// Manages the physics state of one system pub struct PhysSim { - /// The system this sim is attached to - _system: SystemHandle, - wrapper: PhysWrapper, - new: NewObjects, - effects: Vec, projectiles: HashMap, ships: HashMap, @@ -63,10 +60,9 @@ pub struct PhysSim { // Private methods impl PhysSim { - pub(super) fn start_unland_ship(&mut self, ct: &Content, collider: ColliderHandle) { + pub(super) fn start_unland_ship(&mut self, collider: ColliderHandle) { let ship = self.ships.get_mut(&collider).unwrap(); let obj = ship.data.get_state().landed_on().unwrap(); - let obj = ct.get_system_object(obj); let mut rng = rand::thread_rng(); let radius = rng.gen_range(500.0..=1500.0); @@ -75,7 +71,7 @@ impl PhysSim { let target_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset; let target_pos = Isometry2::new(target_trans, angle); - ship.data.start_unland_to(ct, target_pos); + ship.data.start_unland_to(target_pos); let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap(); r.set_enabled(true); @@ -87,7 +83,6 @@ impl PhysSim { pub(super) fn collide_projectile_ship( &mut self, - res: &mut PhysStepResources, projectile_h: ColliderHandle, ship_h: ColliderHandle, ) { @@ -99,8 +94,11 @@ impl PhysSim { let projectile = projectile.unwrap(); let ship = ship.unwrap(); - let f = res.ct.get_faction(projectile.faction); - let r = f.relationships.get(&ship.data.get_faction()).unwrap(); + let r = projectile + .faction + .relationships + .get(&ship.data.get_faction().index) + .unwrap(); let destory_projectile = match r { Relationship::Hostile => match ship.data.get_state() { ShipState::Flying { .. } => { @@ -131,9 +129,8 @@ impl PhysSim { None => {} Some(x) => { self.effects.push(PhysEffect::new( - &res.ct, &mut self.wrapper, - *x, + x.clone(), pos, projectile.rigid_body, Some(ship.rigid_body), @@ -141,7 +138,7 @@ impl PhysSim { } }; - projectile.destroy_silent(res, &mut self.new, &mut self.wrapper); + projectile.destroy_silent(&mut self.new, &mut self.wrapper); } } } @@ -149,11 +146,9 @@ impl PhysSim { // Public methods impl PhysSim { /// Create a new physics system - pub fn new(_ct: &Content, system: SystemHandle) -> Self { + pub fn new() -> Self { Self { - _system: system, wrapper: PhysWrapper::new(), - new: NewObjects::new(), effects: Vec::new(), projectiles: HashMap::new(), @@ -164,19 +159,17 @@ impl PhysSim { /// Add a ship to this physics system pub fn add_ship( &mut self, - ct: &Content, - handle: ShipHandle, - faction: FactionHandle, + ship: Arc, + faction: Arc, personality: ShipPersonality, position: Point2, ) -> PhysSimShipHandle { - let ship_content = ct.get_ship(handle); - let mut cl = ship_content.collider.0.clone(); + let mut cl = ship.collider.0.clone(); // TODO: additonal ship mass from outfits and cargo let rb = RigidBodyBuilder::dynamic() - .angular_damping(ship_content.angular_drag) - .linear_damping(ship_content.linear_drag) + .angular_damping(ship.angular_drag) + .linear_damping(ship.linear_drag) .translation(Vector2::new(position.x, position.y)) .can_sleep(false); @@ -190,14 +183,14 @@ impl PhysSim { self.ships.insert( collider, - PhysShip::new(ct, handle, personality, faction, ridid_body, collider), + PhysShip::new(ship, personality, faction, ridid_body, collider), ); return PhysSimShipHandle(collider); } /// Update a player ship's controls - pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) { + pub fn update_player_controls(&mut self, player: &PlayerAgent) { if player.ship.is_none() { return; } @@ -218,9 +211,11 @@ impl PhysSim { ship_object.controls.thrust = player.input.pressed_thrust(); if player.input.pressed_land() { - ship_object.data.set_autopilot(ShipAutoPilot::Landing { - target: player.selection.get_planet().unwrap(), - }) + if let Some(target) = player.selection.get_planet() { + ship_object.data.set_autopilot(ShipAutoPilot::Landing { + target: target.clone(), + }) + } } } @@ -237,7 +232,7 @@ impl PhysSim { ShipState::Landed { .. } => { if player.input.pressed_land() { - self.start_unland_ship(ct, player.ship.unwrap()); + self.start_unland_ship(player.ship.unwrap()); } } }; @@ -271,7 +266,7 @@ impl PhysSim { if p.is_none() || s.is_none() { continue; } - self.collide_projectile_ship(&mut res, a, b); + self.collide_projectile_ship(a, b); } } res.timing.mark_physics_step();