Replaced handles with Arcs, added display names

master
Mark 2024-02-05 18:29:05 -08:00
parent b64d9c12f6
commit e8c9622832
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
42 changed files with 913 additions and 1091 deletions

1
Cargo.lock generated
View File

@ -816,6 +816,7 @@ dependencies = [
"rapier2d", "rapier2d",
"rhai", "rhai",
"serde", "serde",
"smartstring",
"toml", "toml",
"walkdir", "walkdir",
] ]

View File

@ -75,3 +75,4 @@ clap = { version = "4.4.18", features = ["derive"] }
log = "0.4.20" log = "0.4.20"
log4rs = { version = "1.2.0", features = ["console_appender"] } log4rs = { version = "1.2.0", features = ["console_appender"] }
rhai = { version = "1.17.1", features = ["f32_float", "no_custom_syntax"] } rhai = { version = "1.17.1", features = ["f32_float", "no_custom_syntax"] }
smartstring = { version = "1.0.1" }

View File

@ -1,4 +1,5 @@
[outfit."plasma engines"] [outfit."plasma engines"]
name = "Plasma Engines"
thumbnail = "icon::engine" thumbnail = "icon::engine"
space.engine = 20 space.engine = 20
@ -11,6 +12,7 @@ steering.power = 20
[outfit."shield generator"] [outfit."shield generator"]
thumbnail = "icon::shield" thumbnail = "icon::shield"
name = "Shield Generator"
space.outfit = 5 space.outfit = 5
shield.generation = 10 shield.generation = 10
@ -20,6 +22,7 @@ shield.delay = 2.0
[outfit."blaster"] [outfit."blaster"]
thumbnail = "icon::shield" thumbnail = "icon::shield"
name = "Blaster"
space.weapon = 10 space.weapon = 10

View File

@ -1,4 +1,5 @@
[ship."Gypsum"] [ship."gypsum"]
name = "Gypsum"
sprite = "ship::gypsum" sprite = "ship::gypsum"
thumbnail = "icon::gypsum" thumbnail = "icon::gypsum"
size = 100 size = 100
@ -65,7 +66,7 @@ collision = [
# Scripted explosion # Scripted explosion
[[ship."Gypsum".collapse.event]] [[ship."gypsum".collapse.event]]
time = 4.9 time = 4.9
effects = [ effects = [
#[rustfmt:skip], #[rustfmt:skip],
@ -76,7 +77,7 @@ effects = [
] ]
# Scripted explosion # Scripted explosion
[[ship."Gypsum".collapse.event]] [[ship."gypsum".collapse.event]]
time = 0.0 time = 0.0
effects = [ effects = [
#[rustfmt:skip], #[rustfmt:skip],

View File

@ -1,7 +1,7 @@
# TODO: big objects in one config # TODO: big objects in one config
[system."12 Autumn Above"] [system."12 Autumn Above"]
name = "12 Autumn Above"
object.star.sprite = "star::star" object.star.sprite = "star::star"
object.star.position = [0.0, 0.0, 30.0] object.star.position = [0.0, 0.0, 30.0]
object.star.size = 2000 object.star.size = 2000

View File

@ -131,7 +131,7 @@ fn step(state) {
// Ships // Ships
{ {
for s in state.ships() { for s in state.ships() {
let uid = s.get_uid(); let uid = s.phys_uid();
let sprite_name = `radar.ship.${uid}`; let sprite_name = `radar.ship.${uid}`;
if ( if (
@ -200,7 +200,8 @@ fn step(state) {
// System objects // System objects
{ {
for o in state.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 !o.is_some() {
if sprite::exists(sprite_name) { if sprite::exists(sprite_name) {

View File

@ -55,7 +55,7 @@ fn init(state) {
textbox::font_serif("title"); textbox::font_serif("title");
textbox::weight_bold("title"); textbox::weight_bold("title");
if player.is_landed() { if player.is_landed() {
textbox::set_text("title", player.landed_on().name()); textbox::set_text("title", player.landed_on().display_name());
} }
textbox::add( textbox::add(

View File

@ -84,7 +84,7 @@ fn init(state) {
textbox::font_sans("ship_type"); textbox::font_sans("ship_type");
textbox::align_center("ship_type"); textbox::align_center("ship_type");
if state.player_ship().is_some() { 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());
} }

View File

@ -30,3 +30,4 @@ rapier2d = { workspace = true }
lazy_static = { workspace = true } lazy_static = { workspace = true }
log = { workspace = true } log = { workspace = true }
rhai = { workspace = true } rhai = { workspace = true }
smartstring = { workspace = true }

View File

@ -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,
}

View File

@ -1,9 +1,6 @@
#![warn(missing_docs)] #![warn(missing_docs)]
//! This subcrate is responsible for loading, parsing, validating game content, //! This subcrate is responsible for loading, parsing, validating game content.
//! which is usually stored in `./content`.
mod handle;
mod part; mod part;
mod spriteautomaton; mod spriteautomaton;
mod util; mod util;
@ -11,18 +8,21 @@ mod util;
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use galactica_packer::{SpriteAtlas, SpriteAtlasImage}; use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
use log::warn; use log::warn;
use rhai::ImmutableString;
use serde::{Deserialize, Deserializer};
use smartstring::{LazyCompact, SmartString};
use std::{ use std::{
//cell::OnceCell,
collections::HashMap, collections::HashMap,
fmt::Display,
fs::File, fs::File,
io::Read, io::Read,
num::NonZeroU32, num::NonZeroU32,
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc,
}; };
use toml; use toml;
use walkdir::WalkDir; use walkdir::WalkDir;
pub use handle::*;
pub use part::*; pub use part::*;
pub use spriteautomaton::*; pub use spriteautomaton::*;
@ -123,11 +123,50 @@ trait Build {
Self: Sized; 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<SmartString<LazyCompact>>);
impl From<ImmutableString> 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<D>(deserializer: D) -> Result<Self, D::Error>
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 /// Stores temporary data while building context objects
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ContentBuildContext { pub(crate) struct ContentBuildContext {
/// Map effect names to handles /// Map effect names to handles
pub effect_index: HashMap<String, EffectHandle>, pub effect_index: HashMap<String, Arc<Effect>>,
} }
impl ContentBuildContext { impl ContentBuildContext {
@ -141,22 +180,29 @@ impl ContentBuildContext {
/// Represents static game content /// Represents static game content
#[derive(Debug)] #[derive(Debug)]
pub struct Content { pub struct Content {
/// Sprites
pub sprites: Vec<Sprite>,
/// 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<String, SpriteHandle>,
/// Keeps track of which images are in which texture /// Keeps track of which images are in which texture
sprite_atlas: SpriteAtlas, sprite_atlas: SpriteAtlas,
outfits: Vec<Outfit>, /// Sprites defined in this game
guns: Vec<Gun>, // TODO: merge with outfit pub sprites: HashMap<ContentIndex, Arc<Sprite>>,
ships: Vec<Ship>,
systems: Vec<System>, /// Outfits defined in this game
factions: Vec<Faction>, pub outfits: HashMap<ContentIndex, Arc<Outfit>>,
effects: Vec<Effect>,
config: Config, /// Ships defined in this game
pub ships: HashMap<ContentIndex, Arc<Ship>>,
/// Systems defined in this game
pub systems: HashMap<ContentIndex, Arc<System>>,
/// Factions defined in this game
pub factions: HashMap<ContentIndex, Arc<Faction>>,
/// Effects defined in this game
pub effects: HashMap<ContentIndex, Arc<Effect>>,
/// Game configuration
pub config: Config,
} }
// Loading methods // Loading methods
@ -224,14 +270,12 @@ impl Content {
}, },
sprite_atlas: atlas, sprite_atlas: atlas,
systems: Vec::new(), systems: HashMap::new(),
ships: Vec::new(), ships: HashMap::new(),
guns: Vec::new(), outfits: HashMap::new(),
outfits: Vec::new(), sprites: HashMap::new(),
sprites: Vec::new(), factions: HashMap::new(),
factions: Vec::new(), effects: HashMap::new(),
effects: Vec::new(),
sprite_index: HashMap::new(),
}; };
// TODO: enforce sprite and image limits // TODO: enforce sprite and image limits
@ -285,23 +329,6 @@ impl Content {
// Access methods // Access methods
impl Content { impl Content {
/// Iterate over all valid system handles
pub fn iter_systems(&self) -> impl Iterator<Item = SystemHandle> {
(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<SpriteHandle> {
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 /// Get the list of atlas files we may use
pub fn atlas_files(&self) -> &Vec<String> { pub fn atlas_files(&self) -> &Vec<String> {
return &self.sprite_atlas.atlas_list; return &self.sprite_atlas.atlas_list;
@ -316,63 +343,4 @@ impl Content {
pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage { pub fn get_image(&self, idx: NonZeroU32) -> &SpriteAtlasImage {
&self.sprite_atlas.get_by_idx(idx) &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<Content> = 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(());
}
*/

View File

@ -1,20 +1,22 @@
use anyhow::{Context, Result}; 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 { pub(crate) mod syntax {
use std::sync::Arc;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use galactica_util::to_radians; use galactica_util::to_radians;
use serde::Deserialize; use serde::Deserialize;
use crate::{Content, ContentBuildContext, EffectHandle, StartEdge}; use crate::{Content, ContentBuildContext, ContentIndex, StartEdge};
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Effect { pub struct Effect {
pub sprite: String, pub sprite: ContentIndex,
pub size: f32, pub size: f32,
pub size_rng: Option<f32>, pub size_rng: Option<f32>,
pub lifetime: TextOrFloat, pub lifetime: TextOrFloat,
@ -59,10 +61,11 @@ pub(crate) mod syntax {
self, self,
_build_context: &mut ContentBuildContext, _build_context: &mut ContentBuildContext,
content: &mut Content, content: &mut Content,
) -> Result<EffectHandle> { name: &str,
let sprite = match content.sprite_index.get(&self.sprite) { ) -> Result<Arc<super::Effect>> {
let sprite = match content.sprites.get(&self.sprite) {
None => bail!("sprite `{}` doesn't exist", self.sprite), None => bail!("sprite `{}` doesn't exist", self.sprite),
Some(t) => *t, Some(t) => t.clone(),
}; };
let lifetime = match self.lifetime { let lifetime = match self.lifetime {
@ -70,12 +73,11 @@ pub(crate) mod syntax {
TextOrFloat::Text(s) => { TextOrFloat::Text(s) => {
if s == "inherit" { if s == "inherit" {
// Match lifetime of first section of sprite // Match lifetime of first section of sprite
let sprite = content.get_sprite(sprite); match &sprite.start_at {
let sec = match sprite.start_at { StartEdge::Top { section } | StartEdge::Bot { section } => {
StartEdge::Top { section } => sprite.get_section(section), section.frame_duration * section.frames.len() as f32
StartEdge::Bot { section } => sprite.get_section(section), }
}; }
sec.frame_duration * sec.frames.len() as f32
} else { } else {
bail!("bad effect lifetime, must be float or \"inherit\"",) bail!("bad effect lifetime, must be float or \"inherit\"",)
} }
@ -107,11 +109,8 @@ pub(crate) mod syntax {
} }
}; };
let handle = EffectHandle { let e = Arc::new(super::Effect {
index: content.effects.len(), name: name.to_string(),
};
content.effects.push(super::Effect {
handle,
sprite, sprite,
velocity, velocity,
size: self.size, size: self.size,
@ -126,7 +125,11 @@ pub(crate) mod syntax {
fade_rng: self.fade_rng.unwrap_or(0.0), 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, self,
build_context: &mut ContentBuildContext, build_context: &mut ContentBuildContext,
content: &mut Content, content: &mut Content,
) -> Result<EffectHandle> { name: &str,
) -> Result<Arc<super::Effect>> {
// We do not insert anything into build_context here, // We do not insert anything into build_context here,
// since inline effects cannot be referenced by name. // since inline effects cannot be referenced by name.
Ok(match self { 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) { Self::Label(l) => match build_context.effect_index.get(&l) {
Some(h) => *h, Some(h) => h.clone(),
None => bail!("no effect named `{}`", l), None => bail!("no effect named `{}`", l),
}, },
}) })
@ -161,11 +165,11 @@ pub(crate) mod syntax {
/// The effect a projectile will spawn when it hits something /// The effect a projectile will spawn when it hits something
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Effect { pub struct Effect {
/// This effect's handle
pub handle: EffectHandle,
/// The sprite to use for this effect. /// The sprite to use for this effect.
pub sprite: SpriteHandle, pub sprite: Arc<Sprite>,
/// This effect's name
pub name: String,
/// The height of this effect, in game units. /// The height of this effect, in game units.
pub size: f32, pub size: f32,
@ -240,7 +244,7 @@ impl crate::Build for Effect {
) -> Result<()> { ) -> Result<()> {
for (effect_name, effect) in effects { for (effect_name, effect) in effects {
let h = effect let h = effect
.add_to(build_context, content) .add_to(build_context, content, &effect_name)
.with_context(|| format!("while evaluating effect `{}`", effect_name))?; .with_context(|| format!("while evaluating effect `{}`", effect_name))?;
build_context.effect_index.insert(effect_name, h); build_context.effect_index.insert(effect_name, h);
} }

View File

@ -1,13 +1,15 @@
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use serde::Deserialize; 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 { pub(crate) mod syntax {
use std::collections::HashMap; use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use crate::ContentIndex;
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.
@ -15,7 +17,7 @@ pub(crate) mod syntax {
pub struct Faction { pub struct Faction {
pub display_name: String, pub display_name: String,
pub color: [f32; 3], pub color: [f32; 3],
pub relationship: HashMap<String, super::Relationship>, pub relationship: HashMap<ContentIndex, super::Relationship>,
} }
} }
@ -44,18 +46,18 @@ pub enum Relationship {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Faction { pub struct Faction {
/// The name of this 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. /// This faction's color.
/// Format is RGB, with each color between 0 and 1. /// Format is RGB, with each color between 0 and 1.
pub color: [f32; 3], pub color: [f32; 3],
/// This faction's handle
pub handle: FactionHandle,
/// Relationships between this faction and other factions /// Relationships between this faction and other factions
/// This is guaranteed to contain an entry for ALL factions. /// This is guaranteed to contain an entry for ALL factions.
pub relationships: HashMap<FactionHandle, Relationship>, pub relationships: HashMap<ContentIndex, Relationship>,
} }
impl crate::Build for Faction { impl crate::Build for Faction {
@ -66,34 +68,21 @@ impl crate::Build for Faction {
_build_context: &mut ContentBuildContext, _build_context: &mut ContentBuildContext,
content: &mut Content, content: &mut Content,
) -> Result<()> { ) -> Result<()> {
// Keeps track of position in faction array. for (faction_name, faction) in &factions {
// This lets us build FactionHandles before finishing all factions.
let faction_names: Vec<String> = 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 };
// Compute relationships // Compute relationships
let mut relationships = HashMap::new(); let mut relationships = HashMap::new();
for i in 0..faction_names.len() { for (other_name, other_faction) in &factions {
let f_other = &faction_names[i]; if let Some(r) = faction.relationship.get(&ContentIndex::new(other_name)) {
let h_other = FactionHandle { index: i }; relationships.insert(ContentIndex::new(other_name), *r);
if let Some(r) = faction.relationship.get(f_other) {
relationships.insert(h_other, *r);
} else { } else {
// Default relationship, if not specified // Default relationship, if not specified
// Look at reverse direction... // 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( relationships.insert(
h_other, ContentIndex::new(other_name),
// ... and pick a relationship based on that. // ... and pick a relationship based on that.
match other { match other {
Some(Relationship::Hostile) => Relationship::Hostile {}, Some(Relationship::Hostile) => Relationship::Hostile {},
@ -116,12 +105,15 @@ impl crate::Build for Faction {
); );
} }
content.factions.push(Self { content.factions.insert(
name: faction_name.to_owned(), ContentIndex::new(faction_name),
handle: h, Arc::new(Self {
relationships, index: ContentIndex::new(faction_name),
color: faction.color, display_name: faction.display_name.to_owned(),
}); relationships,
color: faction.color,
}),
);
} }
return Ok(()); return Ok(());

View File

@ -1,14 +1,18 @@
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::{collections::HashMap, sync::Arc};
use crate::{ use crate::{
handle::SpriteHandle, resolve_edge_as_edge, Content, ContentBuildContext, EffectHandle, resolve_edge_as_edge, Content, ContentBuildContext, ContentIndex, Effect, OutfitSpace,
OutfitHandle, OutfitSpace, SectionEdge, SectionEdge, Sprite,
}; };
pub(crate) mod syntax { 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 anyhow::{bail, Result};
use galactica_util::to_radians; use galactica_util::to_radians;
use serde::Deserialize; use serde::Deserialize;
@ -17,7 +21,8 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Outfit { pub struct Outfit {
pub thumbnail: String, pub thumbnail: ContentIndex,
pub name: String,
pub engine: Option<Engine>, pub engine: Option<Engine>,
pub steering: Option<Steering>, pub steering: Option<Steering>,
pub space: outfitspace::syntax::OutfitSpace, pub space: outfitspace::syntax::OutfitSpace,
@ -41,7 +46,7 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct EngineFlare { pub struct EngineFlare {
pub sprite: String, pub sprite: ContentIndex,
pub on_start: Option<SectionEdge>, pub on_start: Option<SectionEdge>,
pub on_stop: Option<SectionEdge>, pub on_stop: Option<SectionEdge>,
} }
@ -64,30 +69,33 @@ pub(crate) mod syntax {
build_context: &mut ContentBuildContext, build_context: &mut ContentBuildContext,
content: &mut crate::Content, content: &mut crate::Content,
) -> Result<super::Gun> { ) -> Result<super::Gun> {
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!( None => bail!(
"projectile sprite `{}` doesn't exist", "projectile sprite `{}` doesn't exist",
self.projectile.sprite, self.projectile.sprite,
), ),
Some(t) => *t, Some(t) => t.clone(),
}; };
let impact_effect = match self.projectile.impact_effect { 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, None => None,
}; };
let expire_effect = match self.projectile.expire_effect { 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, None => None,
}; };
return Ok(super::Gun { return Ok(super::Gun {
rate: self.rate, rate: self.rate,
rate_rng: self.rate_rng.unwrap_or(0.0), rate_rng: self.rate_rng.unwrap_or(0.0),
projectile: super::Projectile { projectile: Arc::new(super::Projectile {
force: self.projectile.force, force: self.projectile.force,
sprite: projectile_sprite_handle, sprite: projectile_sprite,
size: self.projectile.size, size: self.projectile.size,
size_rng: self.projectile.size_rng, size_rng: self.projectile.size_rng,
speed: self.projectile.speed, speed: self.projectile.speed,
@ -102,7 +110,7 @@ pub(crate) mod syntax {
impact_effect, impact_effect,
expire_effect, expire_effect,
collider: self.projectile.collider, collider: self.projectile.collider,
}, }),
}); });
} }
} }
@ -129,17 +137,17 @@ pub(crate) mod syntax {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Outfit { pub struct Outfit {
/// This outfit's thumbnail /// This outfit's thumbnail
pub thumbnail: SpriteHandle, pub thumbnail: Arc<Sprite>,
/// How much space this outfit requires /// How much space this outfit requires
pub space: OutfitSpace, pub space: OutfitSpace,
/// This outfit's handle
pub handle: OutfitHandle,
/// The name of this outfit /// The name of this outfit
pub name: String, pub name: String,
/// Thie outfit's index
pub index: ContentIndex,
/// How much engine thrust this outfit produces /// How much engine thrust this outfit produces
pub engine_thrust: f32, pub engine_thrust: f32,
@ -149,7 +157,7 @@ pub struct Outfit {
/// The engine flare sprite this outfit creates. /// The engine flare sprite this outfit creates.
/// Its location and size is determined by a ship's /// Its location and size is determined by a ship's
/// engine points. /// engine points.
pub engine_flare_sprite: Option<SpriteHandle>, pub engine_flare_sprite: Option<Arc<Sprite>>,
/// Jump to this edge when engines turn on /// Jump to this edge when engines turn on
pub engine_flare_on_start: Option<SectionEdge>, pub engine_flare_on_start: Option<SectionEdge>,
@ -191,7 +199,7 @@ pub struct BallCollider {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Gun { pub struct Gun {
/// The projectile this gun produces /// The projectile this gun produces
pub projectile: Projectile, pub projectile: Arc<Projectile>,
/// Average delay between projectiles, in seconds. /// Average delay between projectiles, in seconds.
pub rate: f32, pub rate: f32,
@ -205,7 +213,7 @@ pub struct Gun {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Projectile { pub struct Projectile {
/// The projectile sprite /// The projectile sprite
pub sprite: SpriteHandle, pub sprite: Arc<Sprite>,
/// The average size of this projectile /// The average size of this projectile
/// (height in game units) /// (height in game units)
@ -234,10 +242,10 @@ pub struct Projectile {
pub angle_rng: f32, pub angle_rng: f32,
/// The effect this projectile will spawn when it hits something /// The effect this projectile will spawn when it hits something
pub impact_effect: Option<EffectHandle>, pub impact_effect: Option<Arc<Effect>>,
/// The effect this projectile will spawn when it expires /// The effect this projectile will spawn when it expires
pub expire_effect: Option<EffectHandle>, pub expire_effect: Option<Arc<Effect>>,
/// Collider parameters for this projectile /// Collider parameters for this projectile
pub collider: ProjectileCollider, pub collider: ProjectileCollider,
@ -252,10 +260,6 @@ impl crate::Build for Outfit {
content: &mut Content, content: &mut Content,
) -> Result<()> { ) -> Result<()> {
for (outfit_name, outfit) in outfits { for (outfit_name, outfit) in outfits {
let handle = OutfitHandle {
index: content.outfits.len(),
};
let gun = match outfit.gun { let gun = match outfit.gun {
None => None, None => None,
Some(g) => Some( 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 => { None => {
return Err(anyhow!( return Err(anyhow!(
"thumbnail sprite `{}` doesn't exist", "thumbnail sprite `{}` doesn't exist",
@ -272,14 +276,14 @@ impl crate::Build for Outfit {
)) ))
.with_context(|| format!("in outfit `{}`", outfit_name)); .with_context(|| format!("in outfit `{}`", outfit_name));
} }
Some(t) => *t, Some(t) => t.clone(),
}; };
let mut o = Self { let mut o = Self {
thumbnail: thumb_handle, index: ContentIndex::new(&outfit_name),
name: outfit.name,
thumbnail,
gun, gun,
handle,
name: outfit_name.clone(),
engine_thrust: 0.0, engine_thrust: 0.0,
steer_power: 0.0, steer_power: 0.0,
engine_flare_sprite: None, engine_flare_sprite: None,
@ -293,7 +297,7 @@ impl crate::Build for Outfit {
// Engine stats // Engine stats
if let Some(engine) = outfit.engine { 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 => { None => {
return Err(anyhow!( return Err(anyhow!(
"flare sprite `{}` doesn't exist", "flare sprite `{}` doesn't exist",
@ -301,11 +305,10 @@ impl crate::Build for Outfit {
)) ))
.with_context(|| format!("in outfit `{}`", outfit_name)); .with_context(|| format!("in outfit `{}`", outfit_name));
} }
Some(t) => *t, Some(t) => t.clone(),
}; };
o.engine_thrust = engine.thrust; o.engine_thrust = engine.thrust;
o.engine_flare_sprite = Some(sprite_handle); o.engine_flare_sprite = Some(sprite.clone());
let sprite = content.get_sprite(sprite_handle);
// Flare animation will traverse this edge when the player presses the thrust key // Flare animation will traverse this edge when the player presses the thrust key
// This leads from the idle animation to the transition animation // This leads from the idle animation to the transition animation
@ -315,11 +318,9 @@ impl crate::Build for Outfit {
None None
} else { } else {
let x = x.unwrap(); let x = x.unwrap();
let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| { let mut e = resolve_edge_as_edge(&sprite.sections, &x.val, 0.0)
sprite.get_section_handle_by_name(x) .with_context(|| format!("in outfit `{}`", outfit_name))?;
}) match &mut e {
.with_context(|| format!("in outfit `{}`", outfit_name))?;
match e {
// Inherit duration from transition sequence // Inherit duration from transition sequence
SectionEdge::Top { SectionEdge::Top {
section, section,
@ -329,7 +330,7 @@ impl crate::Build for Outfit {
section, section,
ref mut duration, ref mut duration,
} => { } => {
*duration = sprite.get_section(section).frame_duration; *duration = section.frame_duration;
} }
_ => { _ => {
return Err(anyhow!( return Err(anyhow!(
@ -351,11 +352,9 @@ impl crate::Build for Outfit {
None None
} else { } else {
let x = x.unwrap(); let x = x.unwrap();
let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| { let mut e = resolve_edge_as_edge(&sprite.sections, &x.val, 0.0)
sprite.get_section_handle_by_name(x) .with_context(|| format!("in outfit `{}`", outfit_name))?;
}) match &mut e {
.with_context(|| format!("in outfit `{}`", outfit_name))?;
match e {
// Inherit duration from transition sequence // Inherit duration from transition sequence
SectionEdge::Top { SectionEdge::Top {
section, section,
@ -365,7 +364,7 @@ impl crate::Build for Outfit {
section, section,
ref mut duration, ref mut duration,
} => { } => {
*duration = sprite.get_section(section).frame_duration; *duration = section.frame_duration;
} }
_ => { _ => {
return Err(anyhow!( return Err(anyhow!(
@ -392,7 +391,7 @@ impl crate::Build for Outfit {
o.shield_strength = shield.strength.unwrap_or(0.0); o.shield_strength = shield.strength.unwrap_or(0.0);
} }
content.outfits.push(o); content.outfits.insert(o.index.clone(), Arc::new(o));
} }
return Ok(()); return Ok(());

View File

@ -2,12 +2,15 @@ use anyhow::{bail, Context, Result};
use galactica_util::to_radians; use galactica_util::to_radians;
use nalgebra::{Point2, Rotation2, Vector2}; use nalgebra::{Point2, Rotation2, Vector2};
use rapier2d::geometry::{Collider, ColliderBuilder}; 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 { pub(crate) mod syntax {
use crate::part::{effect::syntax::EffectReference, outfitspace}; use crate::{
part::{effect::syntax::EffectReference, outfitspace},
ContentIndex,
};
use serde::Deserialize; use serde::Deserialize;
// Raw serde syntax structs. // Raw serde syntax structs.
@ -15,8 +18,9 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Ship { pub struct Ship {
pub sprite: String, pub name: String,
pub thumbnail: String, pub sprite: ContentIndex,
pub thumbnail: ContentIndex,
pub size: f32, pub size: f32,
pub engines: Vec<Engine>, pub engines: Vec<Engine>,
pub guns: Vec<Gun>, pub guns: Vec<Gun>,
@ -91,14 +95,17 @@ pub(crate) mod syntax {
/// Represents a ship chassis. /// Represents a ship chassis.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Ship { pub struct Ship {
/// This ship's name /// This ship's display name
pub name: String, pub display_name: String,
/// This object's index
pub index: ContentIndex,
/// This ship's sprite /// This ship's sprite
pub sprite: SpriteHandle, pub sprite: Arc<Sprite>,
/// This ship's thumbnail /// This ship's thumbnail
pub thumbnail: SpriteHandle, pub thumbnail: Arc<Sprite>,
/// The size of this ship. /// The size of this ship.
/// Measured as unrotated height, /// Measured as unrotated height,
@ -122,9 +129,6 @@ pub struct Ship {
/// Collision shape for this ship /// Collision shape for this ship
pub collider: CollisionDebugWrapper, pub collider: CollisionDebugWrapper,
/// Remove later
pub aspect: f32,
/// Reduction in angular velocity over time /// Reduction in angular velocity over time
pub angular_drag: f32, pub angular_drag: f32,
@ -217,7 +221,7 @@ pub struct ShipDamage {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct DamageEffectSpawner { pub struct DamageEffectSpawner {
/// The effect to create /// The effect to create
pub effect: EffectHandle, pub effect: Arc<Effect>,
/// How often to create this effect /// How often to create this effect
pub frequency: f32, pub frequency: f32,
@ -231,7 +235,7 @@ pub struct DamageEffectSpawner {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CollapseEffectSpawner { pub struct CollapseEffectSpawner {
/// The effect to create /// The effect to create
pub effect: EffectHandle, pub effect: Arc<Effect>,
/// How many effects to create /// How many effects to create
pub count: f32, pub count: f32,
@ -267,26 +271,25 @@ impl crate::Build for Ship {
ct: &mut Content, ct: &mut Content,
) -> Result<()> { ) -> Result<()> {
for (ship_name, ship) in ship { 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!( None => bail!(
"In ship `{}`: sprite `{}` doesn't exist", "In ship `{}`: sprite `{}` doesn't exist",
ship_name, ship_name,
ship.sprite 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!( None => bail!(
"In ship `{}`: thumbnail sprite `{}` doesn't exist", "In ship `{}`: thumbnail sprite `{}` doesn't exist",
ship_name, ship_name,
ship.thumbnail ship.thumbnail
), ),
Some(t) => *t, Some(t) => t.clone(),
}; };
let size = ship.size; let size = ship.size;
let aspect = ct.get_sprite(sprite).aspect;
let collapse = { let collapse = {
if let Some(c) = ship.collapse { if let Some(c) = ship.collapse {
@ -295,11 +298,11 @@ impl crate::Build for Ship {
effects.push(CollapseEffectSpawner { effects.push(CollapseEffectSpawner {
effect: e effect: e
.effect .effect
.to_handle(build_context, ct) .to_handle(build_context, ct, "")
.with_context(|| format!("while loading ship `{}`", ship_name))?, .with_context(|| format!("while loading ship `{}`", ship_name))?,
count: e.count, count: e.count,
pos: e.pos.map(|p| { 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 { effects.push(CollapseEffectSpawner {
effect: g effect: g
.effect .effect
.to_handle(build_context, ct) .to_handle(build_context, ct, "")
.with_context(|| { .with_context(|| {
format!("while loading ship `{}`", ship_name) format!("while loading ship `{}`", ship_name)
})?, })?,
count: g.count, count: g.count,
pos: g.pos.map(|p| { pos: g.pos.map(|p| {
Point2::new( Point2::new(
p[0] * (size / 2.0) * aspect, p[0] * (size / 2.0) * sprite.aspect,
p[1] * size / 2.0, p[1] * size / 2.0,
) )
}), }),
@ -357,11 +360,11 @@ impl crate::Build for Ship {
effects.push(DamageEffectSpawner { effects.push(DamageEffectSpawner {
effect: e effect: e
.effect .effect
.to_handle(build_context, ct) .to_handle(build_context, ct, "")
.with_context(|| format!("while loading ship `{}`", ship_name))?, .with_context(|| format!("while loading ship `{}`", ship_name))?,
frequency: e.frequency, frequency: e.frequency,
pos: e.pos.map(|p| { 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 // Angle adjustment, since sprites point north
// and 0 degrees is east in the game // and 0 degrees is east in the game
pos: Rotation2::new(to_radians(-90.0)) 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, // If we don't, rapier2 will compute local points pre-rotation,
// which will break effect placement on top of ships (i.e, collapse effects) // which will break effect placement on top of ships (i.e, collapse effects)
Rotation2::new(to_radians(-90.0)) 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(); .collect();
@ -427,33 +430,36 @@ impl crate::Build for Ship {
.build() .build()
}; };
ct.ships.push(Self { ct.ships.insert(
sprite, ContentIndex::new(&ship_name),
thumbnail, Arc::new(Self {
aspect, sprite: sprite.clone(),
collapse, thumbnail,
damage, collapse,
name: ship_name, damage,
mass: ship.mass, index: ContentIndex::new(&ship_name),
space: OutfitSpace::from(ship.space), display_name: ship.name,
angular_drag: ship.angular_drag, mass: ship.mass,
linear_drag: ship.linear_drag, space: OutfitSpace::from(ship.space),
size, angular_drag: ship.angular_drag,
hull: ship.hull, linear_drag: ship.linear_drag,
size,
hull: ship.hull,
engines: ship engines: ship
.engines .engines
.iter() .iter()
.map(|e| EnginePoint { .map(|e| EnginePoint {
pos: Vector2::new(e.x * size * aspect / 2.0, e.y * size / 2.0), pos: Vector2::new(e.x * size * sprite.aspect / 2.0, e.y * size / 2.0),
size: e.size, size: e.size,
}) })
.collect(), .collect(),
guns, guns,
collider: CollisionDebugWrapper(collider), collider: CollisionDebugWrapper(collider),
}); }),
);
} }
return Ok(()); return Ok(());

View File

@ -1,14 +1,14 @@
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use lazy_static::lazy_static; 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 { pub(crate) mod syntax {
use crate::{AnimSectionHandle, Content}; use crate::{Content, ContentIndex};
use anyhow::{bail, Ok, Result}; use anyhow::{bail, Ok, Result};
use serde::Deserialize; use serde::Deserialize;
use std::{collections::HashMap, path::PathBuf}; use std::{collections::HashMap, path::PathBuf, sync::Arc};
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.
@ -49,7 +49,7 @@ pub(crate) mod syntax {
/// The proper, full sprite definition /// The proper, full sprite definition
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct CompleteSprite { pub struct CompleteSprite {
pub section: HashMap<String, SpriteSection>, pub section: HashMap<ContentIndex, SpriteSection>,
pub start_at: SectionEdge, pub start_at: SectionEdge,
} }
@ -63,14 +63,28 @@ pub(crate) mod syntax {
} }
impl SpriteSection { impl SpriteSection {
pub fn add_to<F>( pub fn resolve_edges(
&self, &self,
ct: &mut Content, sections: &HashMap<ContentIndex, Arc<super::SpriteSection>>,
get_handle: F, sec: &mut super::SpriteSection,
) -> Result<((u32, u32), super::SpriteSection)> ) -> Result<()> {
where let edge_top = match &self.top {
F: Fn(&str) -> Option<AnimSectionHandle>, 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 // Make sure all frames have the same size and add them
// to the frame vector // to the frame vector
let mut dim = None; let mut dim = None;
@ -111,23 +125,14 @@ pub(crate) mod syntax {
bail!("frame duration must be positive (and therefore nonzero).") 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(( return Ok((
dim, dim,
super::SpriteSection { super::SpriteSection {
frames, frames,
frame_duration, frame_duration,
edge_top, // These are changed later, after all sections are built
edge_bot, 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<usize> {
match self {
Self::Hidden => None,
Self::Idx(idx) => Some(*idx),
}
}
}
/// An edge between two animation sections /// An edge between two animation sections
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub enum SectionEdge { pub enum SectionEdge {
/// Stop at the last frame of this section /// Stop at the last frame of this section
Stop, Stop,
@ -169,7 +155,7 @@ pub enum SectionEdge {
/// Play the given section from the bottm /// Play the given section from the bottm
Bot { Bot {
/// The section to play /// The section to play
section: AnimSectionHandle, section: Arc<SpriteSection>,
/// The length of this edge, in seconds /// The length of this edge, in seconds
duration: f32, duration: f32,
@ -178,7 +164,7 @@ pub enum SectionEdge {
/// Play the given section from the top /// Play the given section from the top
Top { Top {
/// The section to play /// The section to play
section: AnimSectionHandle, section: Arc<SpriteSection>,
/// The length of this edge, in seconds /// The length of this edge, in seconds
duration: f32, duration: f32,
@ -198,18 +184,18 @@ pub enum SectionEdge {
} }
/// Where to start an animation /// Where to start an animation
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub enum StartEdge { pub enum StartEdge {
/// Play the given section from the bottm /// Play the given section from the bottm
Bot { Bot {
/// The section to play /// The section to play
section: AnimSectionHandle, section: Arc<SpriteSection>,
}, },
/// Play the given section from the top /// Play the given section from the top
Top { Top {
/// The section to play /// The section to play
section: AnimSectionHandle, section: Arc<SpriteSection>,
}, },
} }
@ -231,80 +217,50 @@ impl Into<SectionEdge> for StartEdge {
/// Represents a sprite that may be used in the game. /// Represents a sprite that may be used in the game.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Sprite { pub struct Sprite {
/// The name of this sprite /// This object's index
pub name: String, pub index: ContentIndex,
/// This sprite's handle
pub handle: SpriteHandle,
/// Where this sprite starts playing /// Where this sprite starts playing
pub start_at: StartEdge, pub start_at: StartEdge,
/// This sprite's animation sections /// This sprite's animation sections
sections: Vec<SpriteSection>, pub sections: HashMap<ContentIndex, Arc<SpriteSection>>,
/// Allows us to get sprite sections by name
sections_by_name: HashMap<String, AnimSectionHandle>,
/// Aspect ratio of this sprite (width / height) /// Aspect ratio of this sprite (width / height)
pub aspect: f32, pub aspect: f32,
} }
lazy_static! { 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<SpriteSection> = Arc::new(SpriteSection {
frames: vec![0], frames: vec![0],
frame_duration: 0.0, frame_duration: 0.0,
edge_bot: SectionEdge::Stop, edge_bot: SectionEdge::Stop,
edge_top: SectionEdge::Stop, edge_top: SectionEdge::Stop,
}; });
} }
impl Sprite { 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<AnimSectionHandle> {
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 /// Get the index of the texture of this sprite's first frame
pub fn get_first_frame(&self) -> u32 { pub fn get_first_frame(&self) -> u32 {
match self.start_at { match &self.start_at {
StartEdge::Bot { section } => *self.get_section(section).frames.last().unwrap(), StartEdge::Bot { section } => *section.frames.last().unwrap(),
StartEdge::Top { section } => *self.get_section(section).frames.first().unwrap(), StartEdge::Top { section } => *section.frames.first().unwrap(),
} }
} }
/// Get this sprite's starting section /// Get this sprite's starting section
pub fn get_start_section(&self) -> AnimSectionHandle { pub fn get_start_section(&self) -> Arc<SpriteSection> {
match self.start_at { match &self.start_at {
StartEdge::Bot { section } => section, StartEdge::Bot { section } => section.clone(),
StartEdge::Top { section } => section, StartEdge::Top { section } => section.clone(),
} }
} }
/// Iterate this sprite's sections /// Iterate this sprite's sections
pub fn iter_sections(&self) -> impl Iterator<Item = &SpriteSection> { pub fn iter_sections(&self) -> impl Iterator<Item = &Arc<SpriteSection>> {
self.sections.iter() self.sections.values()
} }
} }
@ -327,66 +283,73 @@ pub struct SpriteSection {
} }
/// Resolve an edge specification string as a StartEdge /// Resolve an edge specification string as a StartEdge
pub fn resolve_edge_as_start<F>(s: &str, get_handle: F) -> Result<super::StartEdge> pub fn resolve_edge_as_start(
where sections: &HashMap<ContentIndex, Arc<SpriteSection>>,
F: Fn(&str) -> Option<AnimSectionHandle>, edge_string: &str,
{ ) -> Result<super::StartEdge> {
let e = resolve_edge_as_edge(s, 0.0, get_handle) let e = resolve_edge_as_edge(sections, edge_string, 0.0)
.with_context(|| format!("while resolving start edge"))?; .with_context(|| format!("while resolving start edge"))?;
match e { match e {
super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }), super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }),
super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { 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 /// Resolve an edge specifiation string as a SectionEdge
pub fn resolve_edge_as_edge<F>(s: &str, duration: f32, get_handle: F) -> Result<super::SectionEdge> pub fn resolve_edge_as_edge(
where sections: &HashMap<ContentIndex, Arc<SpriteSection>>,
F: Fn(&str) -> Option<AnimSectionHandle>, edge_string: &str,
{ duration: f32,
if s == "hidden" { ) -> Result<super::SectionEdge> {
if edge_string == "hidden" {
return Ok(super::SectionEdge::Top { return Ok(super::SectionEdge::Top {
section: crate::AnimSectionHandle::Hidden, section: crate::HIDDEN_SPRITE_SECTION.clone(),
duration, duration,
}); });
} }
if s == "stop" { if edge_string == "stop" {
return Ok(super::SectionEdge::Stop); return Ok(super::SectionEdge::Stop);
} }
if s == "reverse" { if edge_string == "reverse" {
return Ok(super::SectionEdge::Reverse { duration }); return Ok(super::SectionEdge::Reverse { duration });
} }
if s == "repeat" { if edge_string == "repeat" {
return Ok(super::SectionEdge::Repeat { duration }); 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, Some(x) => x,
None => { 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, Some(s) => s,
None => { None => {
return Err(anyhow!("bad section edge specification `{}`", s)) return Err(anyhow!("bad section edge specification `{}`", section_name))
.with_context(|| format!("section `{}` doesn't exist", s)); .with_context(|| format!("section `{}` doesn't exist", section_name));
} }
}; };
match p { match start_point {
"top" => Ok(super::SectionEdge::Top { section, duration }), "top" => Ok(super::SectionEdge::Top {
"bot" => Ok(super::SectionEdge::Bot { section, duration }), section: section.clone(),
duration,
}),
"bot" => Ok(super::SectionEdge::Bot {
section: section.clone(),
duration,
}),
_ => { _ => {
return Err(anyhow!("bad section edge specification `{}`", s)) return Err(anyhow!("bad section edge specification `{}`", section_name))
.with_context(|| format!("invalid target `{}`", p)); .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 img = &ct.sprite_atlas.get_by_idx(idx);
let aspect = img.w / img.h; let aspect = img.w / img.h;
let h = SpriteHandle { let section = Arc::new(SpriteSection {
index: ct.sprites.len(), 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); let sprite = Arc::new(Self {
index: ContentIndex::new(&sprite_name),
ct.sprites.push(Self {
name: sprite_name,
start_at: StartEdge::Top { 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, aspect,
}); });
ct.sprites.insert(sprite.index.clone(), sprite);
} }
syntax::Sprite::OneSection(s) => { 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 let (dim, section) = s
.add_to(ct, |s| { .add_to(ct)
if s == "anim" {
Some(AnimSectionHandle::Idx(0))
} else {
None
}
})
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?; .with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
let aspect = dim.0 as f32 / dim.1 as f32; let aspect = dim.0 as f32 / dim.1 as f32;
let mut sections = Vec::new(); let section = Arc::new(section);
sections.push(section);
ct.sprites.push(Self { let sprite = Arc::new(Self {
name: sprite_name, index: ContentIndex::new(&sprite_name),
sections,
start_at: StartEdge::Top { start_at: StartEdge::Top {
section: AnimSectionHandle::Idx(0), section: section.clone(),
}, },
sections_by_name: { sections: {
let mut h = HashMap::new(); let mut h = HashMap::new();
h.insert("anim".to_string(), AnimSectionHandle::Idx(0)); h.insert(ContentIndex::new("anim"), section.clone());
h h
}, },
handle: sprite_handle,
aspect, aspect,
}); });
ct.sprites.insert(sprite.index.clone(), sprite);
} }
syntax::Sprite::Complete(s) => { syntax::Sprite::Complete(s) => {
let mut section_names = HashMap::new(); let mut sections = HashMap::new();
for (name, _) in &s.section {
section_names
.insert(name.to_owned(), AnimSectionHandle::Idx(section_names.len()));
}
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; let mut dim = None;
// Make sure we add sections in order for (name, sec) in &s.section {
let mut names = section_names.iter().collect::<Vec<_>>(); let (d, s) = sec
names.sort_by(|a, b| (a.1).get_idx().unwrap().cmp(&(b.1).get_idx().unwrap())); .add_to(ct)
.with_context(|| format!("while parsing section `{}`", name))
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))
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?; .with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
// Make sure all dimensions are the same // Make sure all dimensions are the same
@ -515,23 +445,35 @@ impl crate::Build for Sprite {
bail!( bail!(
"could not load sprite `{}`, image sizes in section `{}` are different", "could not load sprite `{}`, image sizes in section `{}` are different",
sprite_name, 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 dim = dim.unwrap();
let aspect = dim.0 as f32 / dim.1 as f32; let aspect = dim.0 as f32 / dim.1 as f32;
ct.sprites.push(Self { let start_at = resolve_edge_as_start(&sections, &s.start_at.val)
name: sprite_name, .with_context(|| format!("while loading sprite `{}`", sprite_name))?;
sections,
let sprite = Arc::new(Self {
index: ContentIndex::new(&sprite_name),
start_at, start_at,
handle: sprite_handle, sections,
sections_by_name: section_names,
aspect, aspect,
}); });
ct.sprites.insert(sprite.index.clone(), sprite);
} }
} }
} }

View File

@ -1,27 +1,30 @@
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use galactica_util::to_radians; use galactica_util::to_radians;
use nalgebra::{Point2, Point3}; use nalgebra::{Point2, Point3};
use std::collections::{HashMap, HashSet}; use std::{
collections::{HashMap, HashSet},
use crate::{ sync::Arc,
handle::SpriteHandle, util::Polar, Content, ContentBuildContext, SystemHandle,
SystemObjectHandle,
}; };
use crate::{util::Polar, Content, ContentBuildContext, ContentIndex, Sprite};
pub(crate) mod syntax { pub(crate) mod syntax {
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use crate::ContentIndex;
// Raw serde syntax structs. // Raw serde syntax structs.
// These are never seen by code outside this crate. // These are never seen by code outside this crate.
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct System { pub struct System {
pub object: HashMap<String, Object>, pub name: String,
pub object: HashMap<ContentIndex, Object>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Object { pub struct Object {
pub sprite: String, pub sprite: ContentIndex,
pub position: Position, pub position: Position,
pub size: f32, pub size: f32,
@ -31,7 +34,7 @@ pub(crate) mod syntax {
pub landable: Option<bool>, pub landable: Option<bool>,
pub name: Option<String>, pub name: Option<String>,
pub desc: Option<String>, pub desc: Option<String>,
pub image: Option<String>, pub image: Option<ContentIndex>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -52,14 +55,14 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum CoordinatesTwo { pub enum CoordinatesTwo {
Label(String), Label(ContentIndex),
Coords([f32; 2]), Coords([f32; 2]),
} }
impl ToString for CoordinatesTwo { impl ToString for CoordinatesTwo {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
Self::Label(s) => s.to_owned(), Self::Label(s) => s.to_string(),
Self::Coords(v) => format!("{:?}", v), Self::Coords(v) => format!("{:?}", v),
} }
} }
@ -68,14 +71,14 @@ pub(crate) mod syntax {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(untagged)] #[serde(untagged)]
pub enum CoordinatesThree { pub enum CoordinatesThree {
Label(String), Label(ContentIndex),
Coords([f32; 3]), Coords([f32; 3]),
} }
impl ToString for CoordinatesThree { impl ToString for CoordinatesThree {
fn to_string(&self) -> String { fn to_string(&self) -> String {
match self { match self {
Self::Label(s) => s.to_owned(), Self::Label(s) => s.to_string(),
Self::Coords(v) => format!("{:?}", v), Self::Coords(v) => format!("{:?}", v),
} }
} }
@ -88,14 +91,14 @@ pub(crate) mod syntax {
/// Represents a star system /// Represents a star system
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct System { pub struct System {
/// This star system's name /// This object's name
pub name: String, pub display_name: String,
/// This star system's handle /// This object's index
pub handle: SystemHandle, pub index: ContentIndex,
/// Objects in this system /// Objects in this system
pub objects: Vec<SystemObject>, pub objects: HashMap<ContentIndex, Arc<SystemObject>>,
} }
/// Represents an orbiting body in a star system /// Represents an orbiting body in a star system
@ -104,11 +107,14 @@ pub struct System {
/// System objects to not interact with the physics engine. /// System objects to not interact with the physics engine.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SystemObject { pub struct SystemObject {
/// This object's sprite /// This object's name
pub sprite: SpriteHandle, pub display_name: Option<String>,
/// This object's handle /// This object's index
pub handle: SystemObjectHandle, pub index: ContentIndex,
/// This object's sprite
pub sprite: Arc<Sprite>,
/// This object's size. /// This object's size.
/// Measured as height in game units. /// Measured as height in game units.
@ -126,24 +132,18 @@ pub struct SystemObject {
/// If true, ships may land on this object /// If true, ships may land on this object
pub landable: bool, pub landable: bool,
/// The pretty display name of this object
pub name: Option<String>,
/// The system-unique label of this object
pub label: String,
/// The description of this object (shown on landed ui) /// The description of this object (shown on landed ui)
pub desc: Option<String>, pub desc: Option<String>,
/// This object's image (shown on landed ui) /// This object's image (shown on landed ui)
pub image: Option<SpriteHandle>, pub image: Option<Arc<Sprite>>,
} }
/// Helper function for resolve_position, never called on its own. /// Helper function for resolve_position, never called on its own.
fn resolve_coordinates( fn resolve_coordinates(
objects: &HashMap<String, syntax::Object>, objects: &HashMap<ContentIndex, syntax::Object>,
cor: &syntax::CoordinatesThree, cor: &syntax::CoordinatesThree,
mut cycle_detector: HashSet<String>, mut cycle_detector: HashSet<ContentIndex>,
) -> Result<Point3<f32>> { ) -> Result<Point3<f32>> {
match cor { match cor {
syntax::CoordinatesThree::Coords(c) => Ok((*c).into()), 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) { let p = match objects.get(l) {
Some(p) => p, Some(p) => p,
@ -174,9 +174,9 @@ fn resolve_coordinates(
/// Given an object, resolve its position as a Point3. /// Given an object, resolve its position as a Point3.
fn resolve_position( fn resolve_position(
objects: &HashMap<String, syntax::Object>, objects: &HashMap<ContentIndex, syntax::Object>,
obj: &syntax::Object, obj: &syntax::Object,
cycle_detector: HashSet<String>, cycle_detector: HashSet<ContentIndex>,
) -> Result<Point3<f32>> { ) -> Result<Point3<f32>> {
match &obj.position { match &obj.position {
syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?), syntax::Position::Cartesian(c) => Ok(resolve_coordinates(objects, &c, cycle_detector)?),
@ -208,33 +208,29 @@ impl crate::Build for System {
content: &mut Content, content: &mut Content,
) -> Result<()> { ) -> Result<()> {
for (system_name, system) in system { for (system_name, system) in system {
let mut objects = Vec::new(); let mut objects = HashMap::new();
let system_handle = SystemHandle { for (index, obj) in &system.object {
index: content.systems.len(),
};
for (label, obj) in &system.object {
let mut cycle_detector = HashSet::new(); 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!( None => bail!(
"In system `{}`: sprite `{}` doesn't exist", "In system `{}`: sprite `{}` doesn't exist",
system_name, system_name,
obj.sprite obj.sprite
), ),
Some(t) => *t, Some(t) => t.clone(),
}; };
let image_handle = match &obj.image { let image = match &obj.image {
Some(x) => match content.sprite_index.get(x) { Some(x) => match content.sprites.get(x) {
None => bail!( None => bail!(
"In system `{}`: sprite `{}` doesn't exist", "In system `{}`: sprite `{}` doesn't exist",
system_name, system_name,
obj.sprite obj.sprite
), ),
Some(t) => Some(*t), Some(t) => Some(t.clone()),
}, },
None => None, None => None,
}; };
@ -242,7 +238,7 @@ impl crate::Build for System {
if obj.landable.unwrap_or(false) { if obj.landable.unwrap_or(false) {
if obj.name.is_none() { if obj.name.is_none() {
return Err(anyhow!("if an object is landable, it must have a name")) 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)); .with_context(|| format!("in system `{}`", system_name));
} }
@ -250,57 +246,47 @@ impl crate::Build for System {
return Err(anyhow!( return Err(anyhow!(
"if an object is landable, it must have a description" "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)); .with_context(|| format!("in system `{}`", system_name));
} }
if obj.image.is_none() { if obj.image.is_none() {
return Err(anyhow!("if an object is landable, it must have an image")) 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)); .with_context(|| format!("in system `{}`", system_name));
} }
} }
objects.push(SystemObject { objects.insert(
label: label.clone(), index.clone(),
sprite: sprite_handle, Arc::new(SystemObject {
image: image_handle, index: index.clone(),
pos: resolve_position(&system.object, &obj, cycle_detector) sprite,
.with_context(|| format!("in object {:#?}", label))?, image,
size: obj.size, pos: resolve_position(&system.object, &obj, cycle_detector)
angle: to_radians(obj.angle.unwrap_or(0.0)), .with_context(|| format!("in object {:#?}", index))?,
handle: SystemObjectHandle { size: obj.size,
system_handle, angle: to_radians(obj.angle.unwrap_or(0.0)),
body_index: 0, landable: obj.landable.unwrap_or(false),
}, display_name: obj.name.as_ref().map(|x| x.clone()),
landable: obj.landable.unwrap_or(false), // TODO: better linebreaks, handle double spaces
name: obj.name.as_ref().map(|x| x.clone()), // Tabs
// TODO: better linebreaks, handle double spaces desc: obj
// Tabs .desc
desc: obj .as_ref()
.desc .map(|x| x.replace("\n", " ").replace("<br>", "\n")),
.as_ref() }),
.map(|x| x.replace("\n", " ").replace("<br>", "\n")), );
});
} }
// Sort by z-distance. This is important, since these are content.systems.insert(
// rendered in this order. We need far objects to be behind ContentIndex::new(&system_name),
// near objects! Arc::new(Self {
objects.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); index: ContentIndex::new(&system_name),
display_name: system.name,
// Update object handles objects,
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,
});
} }
return Ok(()); return Ok(());

View File

@ -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 /// A single frame's state
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -43,11 +44,11 @@ enum AnimDirection {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SpriteAutomaton { pub struct SpriteAutomaton {
/// The sprite we're animating /// The sprite we're animating
sprite: SpriteHandle, sprite: Arc<Sprite>,
/// Which animation section we're on /// Which animation section we're on
/// This MUST be a section from this Automaton's sprite /// This MUST be a section from this Automaton's sprite
current_section: AnimSectionHandle, current_section: Arc<SpriteSection>,
/// Which frame we're on /// Which frame we're on
current_frame: usize, current_frame: usize,
@ -75,39 +76,35 @@ pub struct SpriteAutomaton {
impl SpriteAutomaton { impl SpriteAutomaton {
/// Create a new AnimAutomaton /// Create a new AnimAutomaton
pub fn new(ct: &Content, sprite_handle: SpriteHandle) -> Self { pub fn new(sprite: Arc<Sprite>) -> Self {
let sprite = ct.get_sprite(sprite_handle); let (current_section, texture, current_direction) = {
match &sprite.start_at {
let (current_section, texture, current_direction) = match sprite.start_at { StartEdge::Top { section } => (
StartEdge::Top { section } => ( section,
section, *section.frames.first().unwrap(),
*sprite.get_section(section).frames.first().unwrap(), AnimDirection::Down,
AnimDirection::Down, ),
), StartEdge::Bot { section } => {
StartEdge::Bot { section } => ( (section, *section.frames.last().unwrap(), AnimDirection::Up)
section, }
*sprite.get_section(section).frames.last().unwrap(), }
AnimDirection::Up,
),
}; };
let sec = sprite.get_section(current_section);
Self { Self {
sprite: sprite.handle, sprite: sprite.clone(),
current_frame: 0, current_frame: 0,
current_edge_progress: match current_direction { current_edge_progress: match current_direction {
AnimDirection::Down => 0.0, AnimDirection::Down => 0.0,
AnimDirection::Up => sec.frame_duration, AnimDirection::Up => current_section.frame_duration,
AnimDirection::Stop => unreachable!("how'd you get here?"), 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, next_edge_override: None,
current_direction, current_direction,
current_section, current_section: current_section.clone(),
last_texture: texture, last_texture: texture,
next_texture: texture, next_texture: texture,
} }
@ -119,14 +116,11 @@ impl SpriteAutomaton {
} }
/// Force a transition to the given section right now /// Force a transition to the given section right now
pub fn jump_to(&mut self, ct: &Content, start: SectionEdge) { pub fn jump_to(&mut self, start: &SectionEdge) {
self.take_edge(ct, start); self.take_edge(start);
} }
fn take_edge(&mut self, ct: &Content, e: SectionEdge) { fn take_edge(&mut self, e: &SectionEdge) {
let sprite = ct.get_sprite(self.sprite);
let current_section = sprite.get_section(self.current_section);
let last = match self.current_direction { let last = match self.current_direction {
AnimDirection::Stop => self.next_texture, AnimDirection::Stop => self.next_texture,
AnimDirection::Down => self.next_texture, AnimDirection::Down => self.next_texture,
@ -141,7 +135,7 @@ impl SpriteAutomaton {
self.current_frame = 0; self.current_frame = 0;
} }
AnimDirection::Down => { 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; self.current_direction = AnimDirection::Stop;
} }
SectionEdge::Top { section, duration } => { SectionEdge::Top { section, duration } => {
self.current_section = section; self.current_section = section.clone();
self.current_edge_duration = duration; self.current_edge_duration = *duration;
self.current_frame = 0; self.current_frame = 0;
self.current_direction = AnimDirection::Down; self.current_direction = AnimDirection::Down;
} }
SectionEdge::Bot { section, duration } => { SectionEdge::Bot { section, duration } => {
let s = sprite.get_section(section); self.current_section = section.clone();
self.current_section = section; self.current_frame = section.frames.len() - 1;
self.current_frame = s.frames.len() - 1; self.current_edge_duration = *duration;
self.current_edge_duration = duration;
self.current_direction = AnimDirection::Up; self.current_direction = AnimDirection::Up;
} }
SectionEdge::Repeat { duration } => { SectionEdge::Repeat { duration } => {
match self.current_direction { match self.current_direction {
AnimDirection::Stop => {} AnimDirection::Stop => {}
AnimDirection::Up => { AnimDirection::Up => {
self.current_frame = current_section.frames.len() - 1; self.current_frame = self.current_section.frames.len() - 1;
} }
AnimDirection::Down => { AnimDirection::Down => {
self.current_frame = 0; self.current_frame = 0;
} }
} }
self.current_edge_duration = duration; self.current_edge_duration = *duration;
} }
SectionEdge::Reverse { duration } => { SectionEdge::Reverse { duration } => {
match self.current_direction { match self.current_direction {
@ -180,7 +173,7 @@ impl SpriteAutomaton {
// Jump to SECOND frame, since we've already shown the // Jump to SECOND frame, since we've already shown the
// first during the fade transition // first during the fade transition
self.current_frame = { self.current_frame = {
if current_section.frames.len() == 1 { if self.current_section.frames.len() == 1 {
0 0
} else { } else {
1 1
@ -190,45 +183,39 @@ impl SpriteAutomaton {
} }
AnimDirection::Down => { AnimDirection::Down => {
self.current_frame = { self.current_frame = {
if current_section.frames.len() == 1 { if self.current_section.frames.len() == 1 {
0 0
} else { } else {
current_section.frames.len() - 2 self.current_section.frames.len() - 2
} }
}; };
self.current_direction = AnimDirection::Up; self.current_direction = AnimDirection::Up;
} }
} }
self.current_edge_duration = duration; self.current_edge_duration = *duration;
} }
} }
match self.current_direction { match self.current_direction {
AnimDirection::Stop => { AnimDirection::Stop => {
let current_section = sprite.get_section(self.current_section); self.next_texture = self.current_section.frames[self.current_frame];
self.next_texture = current_section.frames[self.current_frame]; self.last_texture = self.current_section.frames[self.current_frame];
self.last_texture = current_section.frames[self.current_frame];
} }
AnimDirection::Down => { AnimDirection::Down => {
let current_section = sprite.get_section(self.current_section);
self.last_texture = last; 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; self.current_edge_progress = 0.0;
} }
AnimDirection::Up => { AnimDirection::Up => {
let current_section = sprite.get_section(self.current_section);
self.next_texture = last; 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; self.current_edge_progress = self.current_edge_duration;
} }
} }
} }
/// Step this animation by `t` seconds /// Step this animation by `t` seconds
pub fn step(&mut self, ct: &Content, t: f32) { pub fn step(&mut self, t: f32) {
let sprite = ct.get_sprite(self.sprite);
let current_section = sprite.get_section(self.current_section);
// Current_fade and current_frame keep track of where we are in the current section. // 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 // 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. // 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! // Note that frame_duration may be zero!
// This is only possible in the hidden texture, since // This is only possible in the hidden texture, since
// user-provided sections are always checked to be positive. // 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 { match self.current_direction {
AnimDirection::Stop => { AnimDirection::Stop => {
@ -248,7 +235,7 @@ impl SpriteAutomaton {
// we should transition right away. // we should transition right away.
if let Some(e) = self.next_edge_override.take() { if let Some(e) = self.next_edge_override.take() {
self.take_edge(ct, e); self.take_edge(&e);
} }
return; return;
@ -259,21 +246,21 @@ impl SpriteAutomaton {
// We're stepping foward and finished this frame // We're stepping foward and finished this frame
if self.current_edge_progress > self.current_edge_duration { 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.current_frame += 1;
self.last_texture = self.next_texture; 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_progress = 0.0;
self.current_edge_duration = current_section.frame_duration; self.current_edge_duration = self.current_section.frame_duration;
} else { } else {
let e = { let e = {
if self.next_edge_override.is_some() { if self.next_edge_override.is_some() {
self.next_edge_override.take().unwrap() self.next_edge_override.take().unwrap()
} else { } 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 { if self.current_frame > 0 {
self.current_frame -= 1; self.current_frame -= 1;
self.next_texture = self.last_texture; self.next_texture = self.last_texture;
self.last_texture = current_section.frames[self.current_frame]; self.last_texture = self.current_section.frames[self.current_frame];
self.current_edge_progress = current_section.frame_duration; self.current_edge_progress = self.current_section.frame_duration;
self.current_edge_duration = current_section.frame_duration; self.current_edge_duration = self.current_section.frame_duration;
} else { } else {
let e = { let e = {
if self.next_edge_override.is_some() { if self.next_edge_override.is_some() {
self.next_edge_override.take().unwrap() self.next_edge_override.take().unwrap()
} else { } 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 /// Get the sprite this automaton is using
pub fn get_sprite(&self) -> SpriteHandle { pub fn get_sprite(&self) -> Arc<Sprite> {
self.sprite self.sprite.clone()
} }
} }

View File

@ -1,4 +1,4 @@
use galactica_content::{Content, FactionHandle, OutfitHandle, ShipHandle, SystemHandle}; use galactica_content::{Content, ContentIndex};
use galactica_playeragent::PlayerAgent; use galactica_playeragent::PlayerAgent;
use galactica_system::data::ShipPersonality; use galactica_system::data::ShipPersonality;
use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepResources}; use galactica_system::phys::{PhysImage, PhysSim, PhysSimShipHandle, PhysStepResources};
@ -24,82 +24,101 @@ unsafe impl<'a> Send for Game {}
impl<'a> Game { impl<'a> Game {
pub fn make_player(&mut self) -> PhysSimShipHandle { pub fn make_player(&mut self) -> PhysSimShipHandle {
let player = self.phys_sim.add_ship( let player = self.phys_sim.add_ship(
&self.ct, self.ct
ShipHandle { index: 0 }, .ships
FactionHandle { index: 0 }, .get(&ContentIndex::new("gypsum"))
.unwrap()
.clone(),
self.ct
.factions
.get(&ContentIndex::new("player"))
.unwrap()
.clone(),
ShipPersonality::Player, ShipPersonality::Player,
Point2::new(0.0, 4000.0), Point2::new(0.0, 4000.0),
); );
let s = self.phys_sim.get_ship_mut(&player).unwrap(); let s = self.phys_sim.get_ship_mut(&player).unwrap();
s.add_outfits( s.add_outfits([
&self.ct, self.ct
[ .outfits
OutfitHandle { index: 0 }, .get(&ContentIndex::new("plasma engines"))
OutfitHandle { index: 1 }, .unwrap()
OutfitHandle { index: 2 }, .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; return player;
} }
pub fn new(ct: Arc<Content>) -> Self { pub fn new(ct: Arc<Content>) -> Self {
let mut phys_sim = PhysSim::new(&ct, SystemHandle { index: 0 }); let mut phys_sim = PhysSim::new();
let a = phys_sim.add_ship( let a = phys_sim.add_ship(
&ct, ct.ships.get(&ContentIndex::new("gypsum")).unwrap().clone(),
ShipHandle { index: 0 }, ct.factions
FactionHandle { index: 1 }, .get(&ContentIndex::new("enemy"))
ShipPersonality::Point, .unwrap()
Point2::new(1000.0, 0.0), .clone(),
);
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 },
ShipPersonality::Point, ShipPersonality::Point,
Point2::new(1000.0, 4000.0), Point2::new(1000.0, 4000.0),
); );
let s = phys_sim.get_ship_mut(&a).unwrap(); let s = phys_sim.get_ship_mut(&a).unwrap();
s.add_outfits( s.add_outfits([
&ct, ct.outfits
[ .get(&ContentIndex::new("plasma engines"))
OutfitHandle { index: 0 }, .unwrap()
OutfitHandle { index: 1 }, .clone(),
OutfitHandle { index: 2 }, ct.outfits
], .get(&ContentIndex::new("shield generator"))
); .unwrap()
.clone(),
ct.outfits
.get(&ContentIndex::new("blaster"))
.unwrap()
.clone(),
]);
let a = phys_sim.add_ship( let a = phys_sim.add_ship(
&ct, ct.ships.get(&ContentIndex::new("gypsum")).unwrap().clone(),
ShipHandle { index: 0 }, ct.factions
FactionHandle { index: 0 }, .get(&ContentIndex::new("player"))
.unwrap()
.clone(),
ShipPersonality::Dummy, ShipPersonality::Dummy,
Point2::new(200.0, 2000.0), Point2::new(200.0, 2000.0),
); );
let s = phys_sim.get_ship_mut(&a).unwrap(); let s = phys_sim.get_ship_mut(&a).unwrap();
s.add_outfits( s.add_outfits([
&ct, ct.outfits
[ .get(&ContentIndex::new("plasma engines"))
OutfitHandle { index: 0 }, .unwrap()
OutfitHandle { index: 1 }, .clone(),
OutfitHandle { index: 2 }, ct.outfits
], .get(&ContentIndex::new("shield generator"))
); .unwrap()
.clone(),
ct.outfits
.get(&ContentIndex::new("blaster"))
.unwrap()
.clone(),
]);
Game { Game {
ct, ct,
@ -112,7 +131,7 @@ impl<'a> Game {
} }
pub fn update_player_controls(&mut self, player: &mut PlayerAgent) { 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) { pub fn step(&mut self, phys_img: &PhysImage) {

View File

@ -2,7 +2,7 @@ mod game;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use clap::Parser; use clap::Parser;
use galactica_content::{Content, SystemHandle}; use galactica_content::Content;
use galactica_playeragent::{PlayerAgent, PlayerStatus}; use galactica_playeragent::{PlayerAgent, PlayerStatus};
use galactica_render::RenderInput; use galactica_render::RenderInput;
use galactica_system::{ use galactica_system::{
@ -128,7 +128,7 @@ fn try_main() -> Result<()> {
let mut game = game::Game::new(content.clone()); let mut game = game::Game::new(content.clone());
let p = game.make_player(); 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( Arc::get_mut(&mut player).unwrap().set_camera_aspect(
gpu.window().inner_size().width as f32 / gpu.window().inner_size().height as f32, 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(), phys_img: phys_img.clone(),
player: player.clone(), player: player.clone(),
time_since_last_run: last_run.elapsed().as_secs_f32(), 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(), timing: game.get_timing().clone(),
}; };
last_run = Instant::now(); last_run = Instant::now();
@ -178,8 +179,7 @@ fn try_main() -> Result<()> {
| ShipState::Flying { .. } => Some(*o.rigidbody.translation()), | ShipState::Flying { .. } => Some(*o.rigidbody.translation()),
ShipState::Landed { target } => { ShipState::Landed { target } => {
let b = content.get_system_object(*target); Some(Vector2::new(target.pos.x, target.pos.y))
Some(Vector2::new(b.pos.x, b.pos.y))
} }
ShipState::Dead => None, ShipState::Dead => None,

View File

@ -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 rapier2d::geometry::ColliderHandle;
use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus}; use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus};
/// What the player has selected /// What the player has selected
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone)]
pub enum PlayerSelection { pub enum PlayerSelection {
/// We have nothing selected /// We have nothing selected
None, None,
/// We have a system body selected /// We have a system body selected
OrbitingBody(SystemObjectHandle), OrbitingBody(Arc<SystemObject>),
/// We have a ship selected /// We have a ship selected
Ship(ColliderHandle), Ship(ColliderHandle),
} }
impl PlayerSelection { impl PlayerSelection {
pub fn get_planet(&self) -> Option<SystemObjectHandle> { pub fn get_planet(&self) -> Option<&Arc<SystemObject>> {
match self { match self {
Self::OrbitingBody(h) => Some(*h), Self::OrbitingBody(h) => Some(h),
_ => None, _ => None,
} }
} }
@ -41,15 +43,21 @@ pub struct PlayerAgent {
} }
impl PlayerAgent { impl PlayerAgent {
pub fn new(ship: ColliderHandle) -> Self { pub fn new(ct: &Content, ship: ColliderHandle) -> Self {
Self { Self {
input: InputStatus::new(), input: InputStatus::new(),
camera: Camera::new(), camera: Camera::new(),
ship: Some(ship), ship: Some(ship),
selection: PlayerSelection::OrbitingBody(SystemObjectHandle { selection: PlayerSelection::OrbitingBody(
system_handle: SystemHandle { index: 0 }, ct.systems
body_index: 1, .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) { pub fn step(&mut self, ct: &Content, status: PlayerStatus) {
if self.input.get_v_scroll() != 0.0 { if self.input.get_v_scroll() != 0.0 {
self.camera.zoom = (self.camera.zoom + self.input.get_v_scroll()) 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() { if status.pos.is_some() {

View File

@ -120,21 +120,20 @@ impl GPUState {
glyphon::fontdb::Database::new(), glyphon::fontdb::Database::new(),
); );
let conf = ct.get_config(); for font in &ct.config.font_files {
for font in &conf.font_files {
text_font_system.db_mut().load_font_file(font)?; text_font_system.db_mut().load_font_file(font)?;
} }
// TODO: nice error if no family with this name is found // TODO: nice error if no family with this name is found
text_font_system text_font_system
.db_mut() .db_mut()
.set_sans_serif_family(conf.font_sans.clone()); .set_sans_serif_family(ct.config.font_sans.clone());
text_font_system text_font_system
.db_mut() .db_mut()
.set_serif_family(conf.font_serif.clone()); .set_serif_family(ct.config.font_serif.clone());
text_font_system text_font_system
.db_mut() .db_mut()
.set_monospace_family(conf.font_mono.clone()); .set_monospace_family(ct.config.font_mono.clone());
//text_font_system //text_font_system
// .db_mut() // .db_mut()
// .set_cursive_family(conf.font_cursive.clone()); // .set_cursive_family(conf.font_cursive.clone());
@ -287,16 +286,16 @@ impl GPUState {
camera_position_x: input.camera_pos.x, camera_position_x: input.camera_pos.x,
camera_position_y: input.camera_pos.y, camera_position_y: input.camera_pos.y,
camera_zoom: input.camera_zoom, camera_zoom: input.camera_zoom,
camera_zoom_min: input.ct.get_config().zoom_min, camera_zoom_min: input.ct.config.zoom_min,
camera_zoom_max: input.ct.get_config().zoom_max, camera_zoom_max: input.ct.config.zoom_max,
window_size_w: self.state.window_size.width as f32, window_size_w: self.state.window_size.width as f32,
window_size_h: self.state.window_size.height as f32, window_size_h: self.state.window_size.height as f32,
window_scale: self.state.window.scale_factor() as f32, window_scale: self.state.window.scale_factor() as f32,
window_aspect: self.state.window_aspect, window_aspect: self.state.window_aspect,
starfield_sprite: input.ct.get_config().starfield_texture.into(), starfield_sprite: input.ct.config.starfield_texture.into(),
starfield_tile_size: input.ct.get_config().starfield_size, starfield_tile_size: input.ct.config.starfield_size,
starfield_size_min: input.ct.get_config().starfield_min_size, starfield_size_min: input.ct.config.starfield_min_size,
starfield_size_max: input.ct.get_config().starfield_max_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); ship_pos = Point3::new(pos.x, pos.y, 1.0);
let ship_rot = r.rotation(); let ship_rot = r.rotation();
ship_ang = ship_rot.angle(); 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, .. } => { ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => {
@ -477,7 +476,7 @@ impl GPUState {
ship_pos = Point3::new(pos.x, pos.y, *current_z); ship_pos = Point3::new(pos.x, pos.y, *current_z);
let ship_rot = r.rotation(); let ship_rot = r.rotation();
ship_ang = ship_rot.angle(); 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. // This is in game units.
// //
// We take the maximum to account for rotated sprites. // We take the maximum to account for rotated sprites.
let m = let m = (ship_cnt.size / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0);
(ship_cnt.size / ship_pos.z) * input.ct.get_sprite(ship_cnt.sprite).aspect.max(1.0);
// Don't draw sprites that are off the screen // Don't draw sprites that are off the screen
if pos.x < screen_clip.0.x - m if pos.x < screen_clip.0.x - m
@ -597,7 +595,7 @@ impl GPUState {
// This is in game units. // This is in game units.
// //
// We take the maximum to account for rotated sprites. // 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 // Don't draw sprites that are off the screen
if pos.x < screen_clip.0.x - m if pos.x < screen_clip.0.x - m
@ -641,9 +639,15 @@ impl GPUState {
// NE and SW corners of screen // NE and SW corners of screen
screen_clip: (Point2<f32>, Point2<f32>), screen_clip: (Point2<f32>, Point2<f32>),
) { ) {
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 // Position adjusted for parallax
let pos: Point2<f32> = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.pos.z; let pos: Point2<f32> = (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. // This is in game units.
// //
// We take the maximum to account for rotated sprites. // 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 // Don't draw sprites that are off the screen
if pos.x < screen_clip.0.x - m 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 = o.sprite.get_first_frame(); // ANIMATE
let texture_a = sprite.get_first_frame(); // ANIMATE
// Push this object's instance // Push this object's instance
self.state.push_object_buffer(ObjectInstance { self.state.push_object_buffer(ObjectInstance {
@ -715,12 +718,7 @@ impl GPUState {
// This is in game units. // This is in game units.
// //
// We take the maximum to account for rotated sprites. // We take the maximum to account for rotated sprites.
let m = (p.effect.size / 1.0) let m = (p.effect.size / 1.0) * p.effect.anim.get_sprite().aspect.max(1.0);
* input
.ct
.get_sprite(p.effect.anim.get_sprite())
.aspect
.max(1.0);
// Don't draw sprites that are off the screen // Don't draw sprites that are off the screen
if adjusted_pos.x < screen_clip.0.x - m if adjusted_pos.x < screen_clip.0.x - m

View File

@ -1,6 +1,6 @@
use std::sync::Arc; use std::sync::Arc;
use galactica_content::{Content, SystemHandle}; use galactica_content::{Content, System};
use galactica_playeragent::PlayerAgent; use galactica_playeragent::PlayerAgent;
use galactica_system::phys::PhysImage; use galactica_system::phys::PhysImage;
use galactica_util::timing::Timing; use galactica_util::timing::Timing;
@ -16,7 +16,7 @@ pub struct RenderInput {
pub player: Arc<PlayerAgent>, pub player: Arc<PlayerAgent>,
/// The system we're currently in /// The system we're currently in
pub current_system: SystemHandle, pub current_system: Arc<System>,
/// Height of screen, in world units /// Height of screen, in world units
pub camera_zoom: f32, pub camera_zoom: f32,

View File

@ -39,7 +39,7 @@ impl<'a> VertexBuffers {
ui_counter: 0, ui_counter: 0,
radialbar_counter: 0, radialbar_counter: 0,
starfield_counter: 0, starfield_counter: 0,
starfield_limit: ct.get_config().starfield_instance_limit, starfield_limit: ct.config.starfield_instance_limit,
object: VertexBuffer::new::<TexturedVertex, ObjectInstance>( object: VertexBuffer::new::<TexturedVertex, ObjectInstance>(
"object", "object",
@ -54,7 +54,7 @@ impl<'a> VertexBuffers {
&device, &device,
Some(SPRITE_VERTICES), Some(SPRITE_VERTICES),
Some(SPRITE_INDICES), Some(SPRITE_INDICES),
ct.get_config().starfield_instance_limit, ct.config.starfield_instance_limit,
), ),
ui: VertexBuffer::new::<TexturedVertex, UiInstance>( ui: VertexBuffer::new::<TexturedVertex, UiInstance>(

View File

@ -30,36 +30,32 @@ impl Starfield {
pub fn regenerate(&mut self, ct: &Content) { pub fn regenerate(&mut self, ct: &Content) {
// TODO: save seed in system, regenerate on jump // TODO: save seed in system, regenerate on jump
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let sz = ct.get_config().starfield_size as f32 / 2.0; let sz = ct.config.starfield_size as f32 / 2.0;
self.stars = (0..ct.get_config().starfield_count) self.stars = (0..ct.config.starfield_count)
.map(|_| StarfieldStar { .map(|_| StarfieldStar {
pos: Point3::new( pos: Point3::new(
rng.gen_range(-sz..=sz), rng.gen_range(-sz..=sz),
rng.gen_range(-sz..=sz), rng.gen_range(-sz..=sz),
rng.gen_range( rng.gen_range(ct.config.starfield_min_dist..=ct.config.starfield_max_dist),
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,
), ),
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)), tint: Vector2::new(rng.gen_range(0.0..=1.0), rng.gen_range(0.0..=1.0)),
}) })
.collect(); .collect();
} }
pub fn update_buffer(&mut self, ct: &Content, state: &mut RenderState) { 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 // Compute window size in starfield tiles
let mut nw_tile = { let mut nw_tile = {
// Game coordinates (relative to camera) of nw corner of screen. // 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. // Parallax correction.
// Also, adjust v for mod to work properly // Also, adjust v for mod to work properly
// (v is centered at 0) // (v is centered at 0)
let v: Point2<f32> = clip_nw * ct.get_config().starfield_min_dist; let v: Point2<f32> = clip_nw * ct.config.starfield_min_dist;
let v_adj = Point2::new(v.x + (sz / 2.0), v.y + (sz / 2.0)); let v_adj = Point2::new(v.x + (sz / 2.0), v.y + (sz / 2.0));
#[rustfmt::skip] #[rustfmt::skip]
@ -83,8 +79,8 @@ impl Starfield {
// Truncate tile grid to buffer size // Truncate tile grid to buffer size
// (The window won't be full of stars if our instance limit is too small) // (The window won't be full of stars if our instance limit is too small)
while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * ct.get_config().starfield_count as i32) while ((nw_tile.x * 2 + 1) * (nw_tile.y * 2 + 1) * ct.config.starfield_count as i32)
> ct.get_config().starfield_instance_limit as i32 > ct.config.starfield_instance_limit as i32
{ {
nw_tile -= Vector2::new(1, 1); nw_tile -= Vector2::new(1, 1);
} }

View File

@ -3,7 +3,7 @@ use log::error;
use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module}; use rhai::{FnNamespace, FuncRegistration, ImmutableString, Module};
use std::{cell::RefCell, rc::Rc, sync::Arc}; 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}; use super::super::{Color, Rect};
@ -20,7 +20,7 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
move |name: ImmutableString, sprite: ImmutableString, rect: Rect| { move |name: ImmutableString, sprite: ImmutableString, rect: Rect| {
let mut ui_state = state.borrow_mut(); 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() { if sprite_handle.is_none() {
error!("made a sprite using an invalid source `{sprite}`"); error!("made a sprite using an invalid source `{sprite}`");
return; return;
@ -34,7 +34,11 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
ui_state.names.push(name.clone()); ui_state.names.push(name.clone());
ui_state.elements.insert( ui_state.elements.insert(
name.clone(), 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<Content>, state_src: Rc<RefCell<UiState>>
match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::Sprite(x)) => { 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() { if m.is_none() {
error!("called `set_sprite_mask` with an invalid mask `{mask}`"); error!("called `set_sprite_mask` with an invalid mask `{mask}`");
return; return;
} }
x.set_mask(m) x.set_mask(m.cloned())
} }
_ => { _ => {
@ -90,7 +94,6 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
); );
let state = state_src.clone(); let state = state_src.clone();
let ct = ct_src.clone();
let _ = FuncRegistration::new("take_edge") let _ = FuncRegistration::new("take_edge")
.with_namespace(FnNamespace::Internal) .with_namespace(FnNamespace::Internal)
.set_into_module( .set_into_module(
@ -100,24 +103,22 @@ pub fn build_sprite_module(ct_src: Arc<Content>, state_src: Rc<RefCell<UiState>>
match ui_state.get_mut_by_name(&name) { match ui_state.get_mut_by_name(&name) {
Some(UiElement::Sprite(x)) => { Some(UiElement::Sprite(x)) => {
let sprite_handle = x.anim.get_sprite(); let sprite = x.anim.get_sprite();
let sprite = &ct.get_sprite(sprite_handle);
let edge = resolve_edge_as_edge(edge_name.as_str(), duration, |x| { let edge =
sprite.get_section_handle_by_name(x) resolve_edge_as_edge(&sprite.sections, edge_name.as_str(), duration);
});
let edge = match edge { let edge = match edge {
Err(_) => { Err(_) => {
error!( error!(
"called `sprite::take_edge` on an invalid edge `{}` on sprite `{}`", "called `sprite::take_edge` on an invalid edge `{}` on sprite `{}`",
edge_name, sprite.name edge_name, sprite.index
); );
return; return;
} }
Ok(s) => s, Ok(s) => s,
}; };
x.anim.jump_to(&ct, edge); x.anim.jump_to(&edge);
} }
_ => { _ => {
error!("called `sprite::take_edge` on an invalid name `{name}`") error!("called `sprite::take_edge` on an invalid name `{name}`")

View File

@ -1,4 +1,4 @@
use galactica_content::{Ship, SystemObject, SystemObjectHandle}; use galactica_content::{Ship, SystemObject};
use galactica_system::{ use galactica_system::{
data::{self}, data::{self},
phys::{objects::PhysShip, PhysSimShipHandle}, phys::{objects::PhysShip, PhysSimShipHandle},
@ -26,7 +26,7 @@ impl ShipState {
.get_ship(self.ship.as_ref().unwrap()) .get_ship(self.ship.as_ref().unwrap())
.unwrap(); .unwrap();
let handle = ship.ship.get_data().get_content(); let handle = ship.ship.get_data().get_content();
self.input.ct.get_ship(handle) handle
} }
fn get_ship(&mut self) -> &PhysShip { fn get_ship(&mut self) -> &PhysShip {
@ -48,20 +48,13 @@ impl ShipState {
} }
fn landed_on(&mut self) -> SystemObjectState { fn landed_on(&mut self) -> SystemObjectState {
let input = self.input.clone();
match self.get_ship().get_data().get_state() { match self.get_ship().get_data().get_state() {
data::ShipState::Landed { target } => { data::ShipState::Landed { target } => {
return SystemObjectState { return SystemObjectState {
input, object: Some(target.clone()),
object: Some(*target),
}
}
_ => {
return SystemObjectState {
input,
object: None,
} }
} }
_ => return SystemObjectState { object: None },
}; };
} }
} }
@ -89,8 +82,15 @@ impl CustomType for ShipState {
.with_fn("is_collapsing", |s: &mut Self| { .with_fn("is_collapsing", |s: &mut Self| {
s.get_ship().get_data().get_state().is_collapsing() s.get_ship().get_data().get_state().is_collapsing()
}) })
.with_fn("name", |s: &mut Self| s.get_content().name.clone()) .with_fn("display_name", |s: &mut Self| {
.with_fn("thumbnail", |s: &mut Self| s.get_content().thumbnail) 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("landed_on", |s: &mut Self| s.landed_on())
.with_fn("get_shields", |s: &mut Self| { .with_fn("get_shields", |s: &mut Self| {
s.get_ship().get_data().get_shields() s.get_ship().get_data().get_shields()
@ -103,14 +103,14 @@ impl CustomType for ShipState {
s.get_ship().get_data().get_hull() s.get_ship().get_data().get_hull()
}) })
.with_fn("get_size", |s: &mut Self| s.get_content().size) .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| { .with_fn("get_pos", |s: &mut Self| {
let t = s.get_body().translation(); let t = s.get_body().translation();
UiVector::new(t.x, t.y) UiVector::new(t.x, t.y)
}) })
.with_fn("get_faction_color", |s: &mut Self| { .with_fn("get_faction_color", |s: &mut Self| {
let h = s.get_ship().get_data().get_faction(); 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) Color::new(c[0], c[1], c[2], 1.0)
}); });
} }
@ -118,15 +118,10 @@ impl CustomType for ShipState {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct SystemObjectState { pub struct SystemObjectState {
object: Option<SystemObjectHandle>, object: Option<Arc<SystemObject>>,
input: Arc<RenderInput>,
} }
impl SystemObjectState { impl SystemObjectState {}
fn get_content(&mut self) -> &SystemObject {
self.input.ct.get_system_object(self.object.unwrap())
}
}
impl CustomType for SystemObjectState { impl CustomType for SystemObjectState {
fn build(mut builder: TypeBuilder<Self>) { fn build(mut builder: TypeBuilder<Self>) {
@ -134,9 +129,11 @@ impl CustomType for SystemObjectState {
.with_name("SystemObjectState") .with_name("SystemObjectState")
// //
// Get landable name // Get landable name
.with_fn("name", |s: &mut Self| { .with_fn("display_name", |s: &mut Self| {
s.get_content() s.object
.name .as_ref()
.unwrap()
.display_name
.as_ref() .as_ref()
.map(|x| x.to_string()) .map(|x| x.to_string())
.unwrap_or_else(|| { .unwrap_or_else(|| {
@ -147,7 +144,9 @@ impl CustomType for SystemObjectState {
// //
// Get landable description // Get landable description
.with_fn("desc", |s: &mut Self| { .with_fn("desc", |s: &mut Self| {
s.get_content() s.object
.as_ref()
.unwrap()
.desc .desc
.as_ref() .as_ref()
.map(|x| x.to_string()) .map(|x| x.to_string())
@ -159,31 +158,31 @@ impl CustomType for SystemObjectState {
// //
// Get landable landscape image // Get landable landscape image
.with_fn("image", |s: &mut Self| { .with_fn("image", |s: &mut Self| {
let handle = s.get_content().image; if let Some(sprite) = &s.object.as_ref().unwrap().image {
if let Some(handle) = handle { sprite.index.to_string()
s.input.ct.get_sprite(handle).name.clone()
} else { } else {
error!("UI called `image()` on a system object which doesn't provide one"); error!("UI called `image()` on a system object which doesn't provide one");
"".to_string() "".to_string()
} }
}) })
.with_fn("is_some", |s: &mut Self| s.object.is_some()) .with_fn("is_some", |s: &mut Self| s.object.is_some())
.with_fn("==", |a: &mut Self, b: Self| a.object == b.object) .with_fn("==", |a: &mut Self, b: Self| match (&a.object, &b.object) {
.with_fn("get_size", |s: &mut Self| s.get_content().size) (None, _) => false,
.with_fn("get_label", |s: &mut Self| { (_, None) => false,
ImmutableString::from(&s.get_content().label) (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| { .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| { .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) UiVector::new(t.x, t.y)
}) })
.with_fn("get_pos_z", |s: &mut Self| { .with_fn("get_pos_z", |s: &mut Self| s.object.as_ref().unwrap().pos.z);
let t = s.get_content().pos;
t.z
});
} }
} }
@ -225,11 +224,9 @@ impl State {
pub fn objects(&mut self) -> Array { pub fn objects(&mut self) -> Array {
let mut a = Array::new(); let mut a = Array::new();
let s = self.input.current_system; for (_, o) in &self.input.current_system.objects {
for o in &self.input.ct.get_system(s).objects {
a.push(Dynamic::from(SystemObjectState { a.push(Dynamic::from(SystemObjectState {
input: self.input.clone(), object: Some(o.clone()),
object: Some(o.handle),
})); }));
} }
return a; return a;

View File

@ -51,7 +51,7 @@ impl<'a, 'b: 'a> FpsIndicator {
buffer: &self.buffer, buffer: &self.buffer,
left: 10.0, left: 10.0,
top: 400.0, top: 400.0,
scale: input.ct.get_config().ui_scale, scale: input.ct.config.ui_scale,
bounds: TextBounds { bounds: TextBounds {
left: 10, left: 10,
top: 400, top: 400,

View File

@ -38,12 +38,12 @@ impl RadialBar {
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
let rect = self let rect = self
.rect .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 { state.push_radialbar_buffer(RadialBarInstance {
position: [rect.pos.x, rect.pos.y], position: [rect.pos.x, rect.pos.y],
diameter: rect.dim.x.min(rect.dim.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(), color: self.color.as_array(),
angle: self.progress * TAU, angle: self.progress * TAU,
}); });

View File

@ -1,15 +1,17 @@
use std::sync::Arc;
use super::super::api::Rect; use super::super::api::Rect;
use crate::{ use crate::{
ui::{api::Color, event::Event}, ui::{api::Color, event::Event},
vertexbuffer::types::UiInstance, vertexbuffer::types::UiInstance,
RenderInput, RenderState, RenderInput, RenderState,
}; };
use galactica_content::{Content, SpriteAutomaton, SpriteHandle}; use galactica_content::{Sprite, SpriteAutomaton};
use galactica_util::to_radians; use galactica_util::to_radians;
use rhai::ImmutableString; use rhai::ImmutableString;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Sprite { pub struct UiSprite {
pub anim: SpriteAutomaton, pub anim: SpriteAutomaton,
pub name: ImmutableString, pub name: ImmutableString,
@ -21,7 +23,7 @@ pub struct Sprite {
preserve_aspect: bool, preserve_aspect: bool,
rect: Rect, rect: Rect,
mask: Option<SpriteHandle>, mask: Option<Arc<Sprite>>,
color: Color, color: Color,
/// If true, ignore mouse events until click is released /// If true, ignore mouse events until click is released
@ -30,11 +32,11 @@ pub struct Sprite {
has_click: bool, has_click: bool,
} }
impl Sprite { impl UiSprite {
pub fn new(ct: &Content, name: ImmutableString, sprite: SpriteHandle, rect: Rect) -> Self { pub fn new(name: ImmutableString, sprite: Arc<Sprite>, rect: Rect) -> Self {
Self { Self {
name, name,
anim: SpriteAutomaton::new(&ct, sprite), anim: SpriteAutomaton::new(sprite),
rect, rect,
angle: 0.0, angle: 0.0,
color: Color::new(1.0, 1.0, 1.0, 1.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<SpriteHandle>) { pub fn set_mask(&mut self, mask: Option<Arc<Sprite>>) {
self.mask = mask; self.mask = mask;
} }
@ -69,11 +71,11 @@ impl Sprite {
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) {
let mut rect = self let mut rect = self
.rect .rect
.to_centered(&state.window, input.ct.get_config().ui_scale); .to_centered(&state.window, input.ct.config.ui_scale);
if self.preserve_aspect { if self.preserve_aspect {
let rect_aspect = rect.dim.x / rect.dim.y; 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 // "wide rect" case => match height, reduce width
if rect_aspect > sprite_aspect { if rect_aspect > sprite_aspect {
@ -97,9 +99,9 @@ impl Sprite {
texture_fade: anim_state.fade, texture_fade: anim_state.fade,
mask_index: self mask_index: self
.mask .mask
.as_ref()
.map(|x| { .map(|x| {
let sprite = input.ct.get_sprite(x); let texture_b = x.get_first_frame(); // TODO: animate?
let texture_b = sprite.get_first_frame(); // TODO: animate?
[1, texture_b] [1, texture_b]
}) })
.unwrap_or([0, 0]), .unwrap_or([0, 0]),
@ -109,7 +111,7 @@ impl Sprite {
pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event { pub fn check_events(&mut self, input: &RenderInput, state: &mut RenderState) -> Event {
let r = self let r = self
.rect .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() { if self.waiting_for_release && self.has_mouse && !input.player.input.pressed_leftclick() {
self.waiting_for_release = false; self.waiting_for_release = false;
@ -155,6 +157,6 @@ impl Sprite {
} }
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) { 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);
} }
} }

View File

@ -86,9 +86,7 @@ impl TextBox {
impl<'a, 'b: 'a> TextBox { impl<'a, 'b: 'a> TextBox {
pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> { pub fn get_textarea(&'b self, input: &RenderInput, window: &Window) -> TextArea<'a> {
let rect = self let rect = self.rect.to_centered(window, input.ct.config.ui_scale);
.rect
.to_centered(window, input.ct.get_config().ui_scale);
// Glypon works with physical pixels, so we must do some conversion // Glypon works with physical pixels, so we must do some conversion
let fac = window.scale_factor() as f32; let fac = window.scale_factor() as f32;
@ -103,7 +101,7 @@ impl<'a, 'b: 'a> TextBox {
buffer: &self.buffer, buffer: &self.buffer,
top: corner_ne.y, top: corner_ne.y,
left: corner_ne.x, left: corner_ne.x,
scale: input.ct.get_config().ui_scale, scale: input.ct.config.ui_scale,
bounds: TextBounds { bounds: TextBounds {
top: (corner_ne.y) as i32, top: (corner_ne.y) as i32,
bottom: (corner_sw.y) as i32, bottom: (corner_sw.y) as i32,

View File

@ -92,7 +92,7 @@ impl UiScriptExecutor {
rhai_error_to_anyhow( rhai_error_to_anyhow(
self.engine.call_fn( self.engine.call_fn(
&mut self.scope, &mut self.scope,
ct.get_config() ct.config
.ui_scenes .ui_scenes
.get(current_scene.as_ref().unwrap().as_str()) .get(current_scene.as_ref().unwrap().as_str())
.unwrap(), .unwrap(),
@ -114,7 +114,7 @@ impl UiScriptExecutor {
if (*self.state).borrow().get_scene().is_none() { if (*self.state).borrow().get_scene().is_none() {
(*self.state) (*self.state)
.borrow_mut() .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())?; self.set_scene(state, input.clone())?;
let current_scene = (*self.state).borrow().get_scene().clone(); let current_scene = (*self.state).borrow().get_scene().clone();
@ -124,7 +124,7 @@ impl UiScriptExecutor {
// Run step() (if it is defined) // Run step() (if it is defined)
let ast = ct let ast = ct
.get_config() .config
.ui_scenes .ui_scenes
.get(current_scene.as_ref().unwrap().as_str()) .get(current_scene.as_ref().unwrap().as_str())
.unwrap(); .unwrap();
@ -161,7 +161,7 @@ impl UiScriptExecutor {
rhai_error_to_anyhow( rhai_error_to_anyhow(
self.engine.call_fn( self.engine.call_fn(
&mut self.scope, &mut self.scope,
ct.get_config() ct.config
.ui_scenes .ui_scenes
.get(current_scene.as_ref().unwrap().as_str()) .get(current_scene.as_ref().unwrap().as_str())
.unwrap(), .unwrap(),
@ -221,7 +221,7 @@ impl UiScriptExecutor {
rhai_error_to_anyhow( rhai_error_to_anyhow(
self.engine.call_fn( self.engine.call_fn(
&mut self.scope, &mut self.scope,
ct.get_config() ct.config
.ui_scenes .ui_scenes
.get(current_scene.as_ref().unwrap().as_str()) .get(current_scene.as_ref().unwrap().as_str())
.unwrap(), .unwrap(),

View File

@ -6,12 +6,12 @@ use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use winit::window::Window; use winit::window::Window;
use super::elements::{FpsIndicator, RadialBar, Sprite, TextBox}; use super::elements::{FpsIndicator, RadialBar, TextBox, UiSprite};
use crate::{RenderInput, RenderState}; use crate::{RenderInput, RenderState};
#[derive(Debug)] #[derive(Debug)]
pub enum UiElement { pub enum UiElement {
Sprite(Sprite), Sprite(UiSprite),
RadialBar(RadialBar), RadialBar(RadialBar),
Text(TextBox), Text(TextBox),
} }
@ -95,7 +95,7 @@ impl UiState {
} }
pub fn set_scene(&mut self, scene: ImmutableString) { 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"); error!("tried to switch to ui scene `{scene}`, which doesn't exist");
return; return;
} }

View File

@ -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 /// Possible outcomes when adding an outfit
pub enum OutfitAddResult { pub enum OutfitAddResult {
@ -34,7 +34,7 @@ pub enum OutfitRemoveResult {
/// A simple data class, used to keep track of delayed shield generators /// A simple data class, used to keep track of delayed shield generators
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct ShieldGenerator { pub(crate) struct ShieldGenerator {
pub outfit: OutfitHandle, pub outfit: Arc<Outfit>,
pub delay: f32, pub delay: f32,
pub generation: f32, pub generation: f32,
} }
@ -46,7 +46,7 @@ pub(crate) struct ShieldGenerator {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OutfitSet { pub struct OutfitSet {
/// What outfits does this statsum contain? /// What outfits does this statsum contain?
outfits: HashMap<OutfitHandle, u32>, outfits: HashMap<ContentIndex, (Arc<Outfit>, u32)>,
/// Space available in this outfitset. /// Space available in this outfitset.
/// set at creation and never changes. /// set at creation and never changes.
@ -59,7 +59,7 @@ pub struct OutfitSet {
/// The gun points available in this ship. /// The gun points available in this ship.
/// If value is None, this point is free. /// If value is None, this point is free.
/// if value is Some, this point is taken. /// if value is Some, this point is taken.
gun_points: HashMap<GunPoint, Option<OutfitHandle>>, gun_points: HashMap<GunPoint, Option<Arc<Outfit>>>,
/// Outfit values /// Outfit values
/// This isn't strictly necessary, but we don't want to /// 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<Outfit>) -> OutfitAddResult {
if !(self.total_space - self.used_space).can_contain(&o.space) { if !(self.total_space - self.used_space).can_contain(&o.space) {
return OutfitAddResult::NotEnoughSpace("TODO".to_string()); return OutfitAddResult::NotEnoughSpace("TODO".to_string());
} }
@ -100,7 +100,7 @@ impl OutfitSet {
let mut added = false; let mut added = false;
for (_, outfit) in &mut self.gun_points { for (_, outfit) in &mut self.gun_points {
if outfit.is_none() { if outfit.is_none() {
*outfit = Some(o.handle); *outfit = Some(o.clone());
added = true; added = true;
} }
} }
@ -115,29 +115,29 @@ impl OutfitSet {
self.steer_power += o.steer_power; self.steer_power += o.steer_power;
self.shield_strength += o.shield_strength; self.shield_strength += o.shield_strength;
self.shield_generators.push(ShieldGenerator { self.shield_generators.push(ShieldGenerator {
outfit: o.handle, outfit: o.clone(),
delay: o.shield_delay, delay: o.shield_delay,
generation: o.shield_generation, generation: o.shield_generation,
}); });
if self.outfits.contains_key(&o.handle) { if self.outfits.contains_key(&o.index) {
*self.outfits.get_mut(&o.handle).unwrap() += 1; self.outfits.get_mut(&o.index).unwrap().1 += 1;
} else { } else {
self.outfits.insert(o.handle, 1); self.outfits.insert(o.index.clone(), (o.clone(), 1));
} }
return OutfitAddResult::Ok; return OutfitAddResult::Ok;
} }
pub(super) fn remove(&mut self, o: &Outfit) -> OutfitRemoveResult { pub(super) fn remove(&mut self, o: &Arc<Outfit>) -> OutfitRemoveResult {
if !self.outfits.contains_key(&o.handle) { if !self.outfits.contains_key(&o.index) {
return OutfitRemoveResult::NotExist; return OutfitRemoveResult::NotExist;
} else { } else {
let n = *self.outfits.get(&o.handle).unwrap(); let n = self.outfits.get_mut(&o.index).unwrap();
if n == 1u32 { if n.1 == 1u32 {
self.outfits.remove(&o.handle); self.outfits.remove(&o.index);
} else { } 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 let index = self
.shield_generators .shield_generators
.iter() .iter()
.position(|g| g.outfit == o.handle) .position(|g| g.outfit.index == o.index)
.unwrap(); .unwrap();
self.shield_generators.remove(index); self.shield_generators.remove(index);
} }
@ -165,16 +165,16 @@ impl OutfitSet {
impl OutfitSet { impl OutfitSet {
/// The number of outfits in this set /// The number of outfits in this set
pub fn len(&self) -> u32 { pub fn len(&self) -> u32 {
self.outfits.iter().map(|(_, x)| x).sum() self.outfits.iter().map(|(_, (_, x))| x).sum()
} }
/// Iterate over all outfits /// Iterate over all outfits
pub fn iter_outfits(&self) -> impl Iterator<Item = (&OutfitHandle, &u32)> { pub fn iter_outfits(&self) -> impl Iterator<Item = &(Arc<Outfit>, u32)> {
self.outfits.iter() self.outfits.values()
} }
/// Iterate over all gun points /// Iterate over all gun points
pub fn iter_gun_points(&self) -> impl Iterator<Item = (&GunPoint, &Option<OutfitHandle>)> { pub fn iter_gun_points(&self) -> impl Iterator<Item = (&GunPoint, &Option<Arc<Outfit>>)> {
self.gun_points.iter() self.gun_points.iter()
} }
@ -190,7 +190,7 @@ impl OutfitSet {
/// Get the outfit attached to the given gun point /// Get the outfit attached to the given gun point
/// Will panic if this gunpoint is not in this outfit set. /// Will panic if this gunpoint is not in this outfit set.
pub fn get_gun(&self, point: &GunPoint) -> Option<OutfitHandle> { pub fn get_gun(&self, point: &GunPoint) -> Option<Arc<Outfit>> {
self.gun_points.get(point).unwrap().clone() self.gun_points.get(point).unwrap().clone()
} }

View File

@ -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 nalgebra::Isometry2;
use rand::{rngs::ThreadRng, Rng}; 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}; use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState};
@ -9,8 +9,8 @@ use super::{OutfitSet, ShipAutoPilot, ShipPersonality, ShipState};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ShipData { pub struct ShipData {
// Metadata values // Metadata values
ct_handle: ShipHandle, ship: Arc<Ship>,
faction: FactionHandle, faction: Arc<Faction>,
outfits: OutfitSet, outfits: OutfitSet,
personality: ShipPersonality, personality: ShipPersonality,
@ -34,16 +34,14 @@ pub struct ShipData {
impl ShipData { impl ShipData {
/// Create a new ShipData /// Create a new ShipData
pub(crate) fn new( pub(crate) fn new(
ct: &Content, ship: Arc<Ship>,
ct_handle: ShipHandle, faction: Arc<Faction>,
faction: FactionHandle,
personality: ShipPersonality, personality: ShipPersonality,
) -> Self { ) -> Self {
let s = ct.get_ship(ct_handle);
ShipData { ShipData {
ct_handle, ship: ship.clone(),
faction, faction,
outfits: OutfitSet::new(s.space, &s.guns), outfits: OutfitSet::new(ship.space, &ship.guns),
personality, personality,
last_hit: Instant::now(), last_hit: Instant::now(),
rng: rand::thread_rng(), rng: rand::thread_rng(),
@ -54,9 +52,9 @@ impl ShipData {
}, },
// Initial stats // Initial stats
hull: s.hull, hull: ship.hull,
shields: 0.0, 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. /// That is the simulation's responsiblity.
/// ///
/// Will panic if we're not flying. /// 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<SystemObject>) {
match self.state { match self.state {
ShipState::Flying { .. } => { ShipState::Flying { .. } => {
self.state = ShipState::Landing { self.state = ShipState::Landing {
@ -108,9 +106,11 @@ impl ShipData {
/// Finish landing sequence /// Finish landing sequence
/// Will panic if we're not landing /// Will panic if we're not landing
pub fn finish_land_on(&mut self) { pub fn finish_land_on(&mut self) {
match self.state { match &self.state {
ShipState::Landing { target, .. } => { 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!") unreachable!("Called `finish_land_on` on a ship that isn't landing!")
@ -123,14 +123,13 @@ impl ShipData {
/// That is the simulation's responsiblity. /// That is the simulation's responsiblity.
/// ///
/// Will panic if we're not flying. /// Will panic if we're not flying.
pub fn start_unland_to(&mut self, ct: &Content, to_position: Isometry2<f32>) { pub fn start_unland_to(&mut self, to_position: Isometry2<f32>) {
match self.state { match &self.state {
ShipState::Landed { target } => { ShipState::Landed { target } => {
let obj = ct.get_system_object(target);
self.state = ShipState::UnLanding { self.state = ShipState::UnLanding {
to_position, to_position,
from: target, from: target.clone(),
current_z: obj.pos.z, current_z: target.pos.z,
}; };
} }
_ => { _ => {
@ -177,14 +176,14 @@ impl ShipData {
} }
/// Add an outfit to this ship /// Add an outfit to this ship
pub fn add_outfit(&mut self, o: &Outfit) -> super::OutfitAddResult { pub fn add_outfit(&mut self, o: &Arc<Outfit>) -> super::OutfitAddResult {
let r = self.outfits.add(o); let r = self.outfits.add(o);
self.shields = self.outfits.get_total_shields(); self.shields = self.outfits.get_total_shields();
return r; return r;
} }
/// Remove an outfit from this ship /// Remove an outfit from this ship
pub fn remove_outfit(&mut self, o: &Outfit) -> super::OutfitRemoveResult { pub fn remove_outfit(&mut self, o: &Arc<Outfit>) -> super::OutfitRemoveResult {
self.outfits.remove(o) self.outfits.remove(o)
} }
@ -192,16 +191,14 @@ impl ShipData {
/// Will panic if `which` isn't a point on this ship. /// Will panic if `which` isn't a point on this ship.
/// Returns `true` if this gun was fired, /// Returns `true` if this gun was fired,
/// and `false` if it is on cooldown or empty. /// and `false` if it is on cooldown or empty.
pub(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(); let c = self.gun_cooldowns.get_mut(which).unwrap();
if *c > 0.0 { if *c > 0.0 {
return false; return false;
} }
let g = self.outfits.get_gun(which); if let Some(g) = self.outfits.get_gun(which) {
if g.is_some() {
let g = ct.get_outfit(g.unwrap());
let gun = g.gun.as_ref().unwrap(); let gun = g.gun.as_ref().unwrap();
*c = 0f32.max(gun.rate + self.rng.gen_range(-gun.rate_rng..=gun.rate_rng)); *c = 0f32.max(gun.rate + self.rng.gen_range(-gun.rate_rng..=gun.rate_rng));
return true; return true;
@ -289,8 +286,8 @@ impl ShipData {
} }
/// Get a handle to this ship's content /// Get a handle to this ship's content
pub fn get_content(&self) -> ShipHandle { pub fn get_content(&self) -> &Arc<Ship> {
self.ct_handle &self.ship
} }
/// Get this ship's current hull. /// Get this ship's current hull.
@ -316,12 +313,12 @@ impl ShipData {
} }
/// Get this ship's faction /// Get this ship's faction
pub fn get_faction(&self) -> FactionHandle { pub fn get_faction(&self) -> &Arc<Faction> {
self.faction &self.faction
} }
/// Get this ship's content handle /// Get this ship's content handle
pub fn get_ship(&self) -> ShipHandle { pub fn get_ship(&self) -> &Arc<Ship> {
self.ct_handle &self.ship
} }
} }

View File

@ -1,7 +1,6 @@
use std::num::NonZeroU32; use galactica_content::SystemObject;
use galactica_content::SystemObjectHandle;
use rapier2d::math::Isometry; use rapier2d::math::Isometry;
use std::{num::NonZeroU32, sync::Arc};
/// A ship autopilot. /// A ship autopilot.
/// An autopilot is a lightweight ShipController that /// An autopilot is a lightweight ShipController that
@ -14,7 +13,7 @@ pub enum ShipAutoPilot {
/// Automatically arrange for landing on the given object /// Automatically arrange for landing on the given object
Landing { Landing {
/// The body we want to land on /// The body we want to land on
target: SystemObjectHandle, target: Arc<SystemObject>,
}, },
} }
@ -40,14 +39,14 @@ pub enum ShipState {
/// This ship is landed on a planet /// This ship is landed on a planet
Landed { Landed {
/// The planet this ship is landed on /// The planet this ship is landed on
target: SystemObjectHandle, target: Arc<SystemObject>,
}, },
/// This ship is landing on a planet /// This ship is landing on a planet
/// (playing the animation) /// (playing the animation)
Landing { Landing {
/// The planet we're landing on /// The planet we're landing on
target: SystemObjectHandle, target: Arc<SystemObject>,
/// Our current z-coordinate /// Our current z-coordinate
current_z: f32, current_z: f32,
@ -60,7 +59,7 @@ pub enum ShipState {
to_position: Isometry<f32>, to_position: Isometry<f32>,
/// The planet we're taking off from /// The planet we're taking off from
from: SystemObjectHandle, from: Arc<SystemObject>,
/// Our current z-coordinate /// Our current z-coordinate
current_z: f32, current_z: f32,
@ -69,9 +68,9 @@ pub enum ShipState {
impl ShipState { impl ShipState {
/// What planet is this ship landed on? /// What planet is this ship landed on?
pub fn landed_on(&self) -> Option<SystemObjectHandle> { pub fn landed_on(&self) -> Option<Arc<SystemObject>> {
match self { match self {
Self::Landed { target } => Some(*target), Self::Landed { target } => Some(target.clone()),
_ => None, _ => None,
} }
} }

View File

@ -1,7 +1,8 @@
use galactica_content::{Content, EffectHandle, EffectVelocity, SpriteAutomaton}; use galactica_content::{Effect, EffectVelocity, SpriteAutomaton};
use nalgebra::{Point2, Rotation2, Vector2}; use nalgebra::{Point2, Rotation2, Vector2};
use rand::Rng; use rand::Rng;
use rapier2d::dynamics::{RevoluteJointBuilder, RigidBodyBuilder, RigidBodyHandle, RigidBodyType}; use rapier2d::dynamics::{RevoluteJointBuilder, RigidBodyBuilder, RigidBodyHandle, RigidBodyType};
use std::sync::Arc;
use crate::phys::{PhysStepResources, PhysWrapper}; use crate::phys::{PhysStepResources, PhysWrapper};
@ -32,16 +33,13 @@ pub struct PhysEffect {
impl PhysEffect { impl PhysEffect {
/// Create a new effect inside `Wrapper` /// Create a new effect inside `Wrapper`
pub fn new( pub fn new(
ct: &Content,
wrapper: &mut PhysWrapper, wrapper: &mut PhysWrapper,
effect: EffectHandle, effect: Arc<Effect>,
// Where to spawn the particle, in world space. // Where to spawn the particle, in world space.
pos: Vector2<f32>, pos: Vector2<f32>,
parent: RigidBodyHandle, parent: RigidBodyHandle,
target: Option<RigidBodyHandle>, target: Option<RigidBodyHandle>,
) -> Self { ) -> Self {
let effect = ct.get_effect(effect);
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let parent_body = wrapper.get_rigid_body(parent).unwrap(); let parent_body = wrapper.get_rigid_body(parent).unwrap();
let parent_angle = parent_body.rotation().angle(); let parent_angle = parent_body.rotation().angle();
@ -113,7 +111,7 @@ impl PhysEffect {
}; };
PhysEffect { PhysEffect {
anim: SpriteAutomaton::new(ct, effect.sprite), anim: SpriteAutomaton::new(effect.sprite.clone()),
rigid_body, rigid_body,
lifetime: 0f32.max( lifetime: 0f32.max(
effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng), effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
@ -153,7 +151,7 @@ impl PhysEffect {
); );
PhysEffect { PhysEffect {
anim: SpriteAutomaton::new(ct, effect.sprite), anim: SpriteAutomaton::new(effect.sprite.clone()),
rigid_body, rigid_body,
lifetime: 0f32.max( lifetime: 0f32.max(
effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng), effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng),
@ -174,7 +172,7 @@ impl PhysEffect {
return; return;
} }
self.anim.step(&res.ct, res.t); self.anim.step(res.t);
self.lifetime -= res.t; self.lifetime -= res.t;
if self.lifetime <= 0.0 { if self.lifetime <= 0.0 {

View File

@ -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 rand::Rng;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle}; use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
@ -10,7 +12,7 @@ use super::PhysEffect;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PhysProjectile { pub struct PhysProjectile {
/// This projectile's game data /// This projectile's game data
pub content: Projectile, pub content: Arc<Projectile>,
/// This projectile's sprite animation state /// This projectile's sprite animation state
anim: SpriteAutomaton, anim: SpriteAutomaton,
@ -19,7 +21,7 @@ pub struct PhysProjectile {
lifetime: f32, lifetime: f32,
/// The faction this projectile belongs to /// The faction this projectile belongs to
pub faction: FactionHandle, pub faction: Arc<Faction>,
/// This projectile's rigidbody /// This projectile's rigidbody
pub rigid_body: RigidBodyHandle, pub rigid_body: RigidBodyHandle,
@ -38,17 +40,16 @@ pub struct PhysProjectile {
impl PhysProjectile { impl PhysProjectile {
/// Create a new projectile /// Create a new projectile
pub fn new( pub fn new(
ct: &Content, content: Arc<Projectile>,
content: Projectile, // TODO: use a handle?
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
faction: FactionHandle, faction: Arc<Faction>,
collider: ColliderHandle, collider: ColliderHandle,
) -> Self { ) -> Self {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let size_rng = content.size_rng; let size_rng = content.size_rng;
let lifetime = content.lifetime; let lifetime = content.lifetime;
PhysProjectile { PhysProjectile {
anim: SpriteAutomaton::new(ct, content.sprite), anim: SpriteAutomaton::new(content.sprite.clone()),
rigid_body, rigid_body,
collider, collider,
content, content,
@ -67,31 +68,24 @@ impl PhysProjectile {
wrapper: &mut PhysWrapper, wrapper: &mut PhysWrapper,
) { ) {
self.lifetime -= res.t; self.lifetime -= res.t;
self.anim.step(&res.ct, res.t); self.anim.step(res.t);
if self.lifetime <= 0.0 { if self.lifetime <= 0.0 {
self.destroy(res, new, wrapper, true); self.destroy(new, wrapper, true);
} }
} }
/// Destroy this projectile without creating an expire effect /// Destroy this projectile without creating an expire effect
pub(in crate::phys) fn destroy_silent( pub(in crate::phys) fn destroy_silent(
&mut self, &mut self,
res: &PhysStepResources,
new: &mut NewObjects, new: &mut NewObjects,
wrapper: &mut PhysWrapper, wrapper: &mut PhysWrapper,
) { ) {
self.destroy(res, new, wrapper, false); self.destroy(new, wrapper, false);
} }
/// Destroy this projectile /// Destroy this projectile
fn destroy( fn destroy(&mut self, new: &mut NewObjects, wrapper: &mut PhysWrapper, expire: bool) {
&mut self,
res: &PhysStepResources,
new: &mut NewObjects,
wrapper: &mut PhysWrapper,
expire: bool,
) {
if self.is_destroyed { if self.is_destroyed {
return; return;
} }
@ -100,11 +94,10 @@ impl PhysProjectile {
if expire { if expire {
match &self.content.expire_effect { match &self.content.expire_effect {
None => {} None => {}
Some(handle) => { Some(effect) => {
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
&res.ct,
wrapper, wrapper,
*handle, effect.clone(),
*rb.translation(), *rb.translation(),
self.rigid_body, self.rigid_body,
None, None,

View File

@ -1,4 +1,4 @@
use galactica_content::{CollapseEvent, Content, Ship}; use galactica_content::{CollapseEvent, Ship};
use nalgebra::{Point2, Vector2}; use nalgebra::{Point2, Vector2};
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
use rapier2d::{ use rapier2d::{
@ -37,14 +37,13 @@ impl ShipCollapseSequence {
} }
/// Pick a random points inside a ship's collider /// Pick a random points inside a ship's collider
fn random_in_ship(&mut self, ct: &Content, ship: &Ship, collider: &Collider) -> Vector2<f32> { fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2<f32> {
let mut y = 0.0; let mut y = 0.0;
let mut x = 0.0; let mut x = 0.0;
let mut a = false; let mut a = false;
while !a { while !a {
x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0; 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 y = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0;
/ 2.0;
a = collider.shape().contains_local_point(&Point2::new(x, y)); a = collider.shape().contains_local_point(&Point2::new(x, y));
} }
Vector2::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 rigid_body = wrapper.get_rigid_body(rigid_body_handle).unwrap().clone();
let collider = wrapper.get_collider(collider_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_pos = rigid_body.translation();
let ship_rot = rigid_body.rotation(); let ship_rot = rigid_body.rotation();
@ -84,14 +83,13 @@ impl ShipCollapseSequence {
let pos: Vector2<f32> = if let Some(pos) = spawner.pos { let pos: Vector2<f32> = if let Some(pos) = spawner.pos {
Vector2::new(pos.x, pos.y) Vector2::new(pos.x, pos.y)
} else { } else {
self.random_in_ship(&res.ct, ship_content, &collider) self.random_in_ship(&ship_content, &collider)
}; };
let pos = ship_pos + (ship_rot * pos); let pos = ship_pos + (ship_rot * pos);
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
&res.ct,
wrapper, wrapper,
spawner.effect, spawner.effect.clone(),
pos, pos,
rigid_body_handle, rigid_body_handle,
None, None,
@ -124,15 +122,14 @@ impl ShipCollapseSequence {
let pos = if let Some(pos) = spawner.pos { let pos = if let Some(pos) = spawner.pos {
Vector2::new(pos.x, pos.y) Vector2::new(pos.x, pos.y)
} else { } else {
self.random_in_ship(&res.ct, ship_content, &collider) self.random_in_ship(&ship_content, &collider)
}; };
// Position, adjusted for ship rotation // Position, adjusted for ship rotation
let pos = ship_pos + (ship_rot * pos); let pos = ship_pos + (ship_rot * pos);
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
&res.ct,
wrapper, wrapper,
spawner.effect, spawner.effect.clone(),
pos, pos,
rigid_body_handle, rigid_body_handle,
None, None,

View File

@ -25,7 +25,7 @@ impl PointShipController {
impl ShipControllerStruct for PointShipController { impl ShipControllerStruct for PointShipController {
fn update_controls( fn update_controls(
&mut self, &mut self,
res: &PhysStepResources, _res: &PhysStepResources,
img: &PhysImage, img: &PhysImage,
this_ship: PhysSimShipHandle, this_ship: PhysSimShipHandle,
) -> Option<ShipControls> { ) -> Option<ShipControls> {
@ -40,14 +40,14 @@ impl ShipControllerStruct for PointShipController {
let my_position = this_rigidbody.translation(); let my_position = this_rigidbody.translation();
let my_rotation = this_rigidbody.rotation(); let my_rotation = this_rigidbody.rotation();
let my_angvel = this_rigidbody.angvel(); 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 // Iterate all possible targets
let mut hostile_ships = img.iter_ships().filter( let mut hostile_ships = img.iter_ships().filter(
// Target only flying ships we're hostile towards // Target only flying ships we're hostile towards
|s| match my_faction |s| match my_faction
.relationships .relationships
.get(&s.ship.data.get_faction()) .get(&s.ship.data.get_faction().index)
.unwrap() .unwrap()
{ {
Relationship::Hostile => match s.ship.data.get_state() { Relationship::Hostile => match s.ship.data.get_state() {

View File

@ -1,6 +1,8 @@
use std::sync::Arc;
use galactica_content::{ use galactica_content::{
AnimationState, Content, EnginePoint, FactionHandle, GunPoint, OutfitHandle, AnimationState, EnginePoint, Faction, GunPoint, Outfit, ProjectileCollider, Ship,
ProjectileCollider, ShipHandle, SpriteAutomaton, SpriteAutomaton,
}; };
use nalgebra::{vector, Point2, Rotation2, Vector2}; use nalgebra::{vector, Point2, Rotation2, Vector2};
use rand::Rng; use rand::Rng;
@ -66,24 +68,22 @@ pub struct PhysShip {
impl PhysShip { impl PhysShip {
/// Make a new ship /// Make a new ship
pub(crate) fn new( pub(crate) fn new(
ct: &Content, ship: Arc<Ship>,
handle: ShipHandle,
personality: ShipPersonality, personality: ShipPersonality,
faction: FactionHandle, faction: Arc<Faction>,
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
collider: ColliderHandle, collider: ColliderHandle,
) -> Self { ) -> Self {
let ship_ct = ct.get_ship(handle);
PhysShip { PhysShip {
uid: get_phys_id(), uid: get_phys_id(),
anim: SpriteAutomaton::new(ct, ship_ct.sprite), anim: SpriteAutomaton::new(ship.sprite.clone()),
rigid_body, rigid_body,
collider, collider,
data: ShipData::new(ct, handle, faction, personality), data: ShipData::new(ship.clone(), faction, personality),
engine_anim: Vec::new(), engine_anim: Vec::new(),
controls: ShipControls::new(), controls: ShipControls::new(),
last_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, is_destroyed: false,
controller: match personality { controller: match personality {
ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(), ShipPersonality::Dummy | ShipPersonality::Player => ShipController::new_null(),
@ -105,32 +105,28 @@ impl PhysShip {
} }
self.data.step(res.t); self.data.step(res.t);
self.anim.step(&res.ct, res.t); self.anim.step(res.t);
for (_, e) in &mut self.engine_anim { for (_, e) in &mut self.engine_anim {
e.step(&res.ct, res.t); e.step(res.t);
} }
// Flare animations // Flare animations
if !self.controls.thrust && self.last_controls.thrust { if !self.controls.thrust && self.last_controls.thrust {
let flare = self.get_flare(&res.ct); let flare = self.get_flare();
if flare.is_some() { if let Some(flare) = flare {
let flare_outfit = flare.unwrap();
let flare = res.ct.get_outfit(flare_outfit);
if flare.engine_flare_on_stop.is_some() { if flare.engine_flare_on_stop.is_some() {
for (_, e) in &mut self.engine_anim { 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 { } else if self.controls.thrust && !self.last_controls.thrust {
let flare = self.get_flare(&res.ct); let flare = self.get_flare();
if flare.is_some() { if let Some(flare) = flare {
let flare_outfit = flare.unwrap();
let flare = res.ct.get_outfit(flare_outfit);
if flare.engine_flare_on_start.is_some() { if flare.engine_flare_on_start.is_some() {
for (_, e) in &mut self.engine_anim { 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 // Compute new controls
let controls = match autopilot { let controls = match autopilot {
ShipAutoPilot::Landing { target } => { ShipAutoPilot::Landing { target } => {
let target_obj = res.ct.get_system_object(*target);
let controls = autopilot::auto_landing( let controls = autopilot::auto_landing(
res, res,
img, img,
PhysSimShipHandle(self.collider), 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. // Try to land the ship.
@ -176,7 +171,7 @@ impl PhysShip {
let landed = 'landed: { let landed = 'landed: {
let r = wrapper.get_rigid_body(self.rigid_body).unwrap(); 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( let s_pos = Vector2::new(
r.position().translation.x, r.position().translation.x,
r.position().translation.y, r.position().translation.y,
@ -186,13 +181,13 @@ impl PhysShip {
// Can't just set_active(false), since we still need that collider's mass. // Can't just set_active(false), since we still need that collider's mass.
// We're in land range... // 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; break 'landed false;
} }
// And we'll stay in land range long enough. // And we'll stay in land range long enough.
if (t_pos - (s_pos + r.velocity_at_point(r.center_of_mass()) * 2.0)) 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; break 'landed false;
} }
@ -202,7 +197,7 @@ impl PhysShip {
Group::GROUP_1, Group::GROUP_1,
Group::empty(), Group::empty(),
)); ));
self.data.start_land_on(*target); self.data.start_land_on(target.clone());
break 'landed true; break 'landed true;
}; };
@ -229,7 +224,7 @@ impl PhysShip {
if self.controls.guns { if self.controls.guns {
// TODO: don't allocate here. This is a hack to satisfy the borrow checker, // TODO: don't allocate here. This is a hack to satisfy the borrow checker,
// convert this to a refcell or do the replace dance. // convert this to a refcell or do the replace dance.
let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = self let pairs: Vec<(GunPoint, Option<Arc<Outfit>>)> = self
.data .data
.get_outfits() .get_outfits()
.iter_gun_points() .iter_gun_points()
@ -237,7 +232,7 @@ impl PhysShip {
.collect(); .collect();
for (gun_point, outfit) in pairs { 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 outfit = outfit.unwrap();
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
@ -250,20 +245,17 @@ impl PhysShip {
rigid_body.velocity_at_point(rigid_body.center_of_mass()); rigid_body.velocity_at_point(rigid_body.center_of_mass());
let pos = ship_pos + (ship_rot * gun_point.pos); let pos = ship_pos + (ship_rot * gun_point.pos);
let gun = outfit.gun.as_ref().unwrap();
let outfit = res.ct.get_outfit(outfit); let spread =
let outfit = outfit.gun.as_ref().unwrap(); rng.gen_range(-gun.projectile.angle_rng..=gun.projectile.angle_rng);
let spread = rng.gen_range(
-outfit.projectile.angle_rng..=outfit.projectile.angle_rng,
);
let vel = ship_vel let vel = ship_vel
+ (Rotation2::new(ship_ang + spread) + (Rotation2::new(ship_ang + spread)
* Vector2::new( * Vector2::new(
outfit.projectile.speed gun.projectile.speed
+ rng.gen_range( + rng.gen_range(
-outfit.projectile.speed_rng -gun.projectile.speed_rng
..=outfit.projectile.speed_rng, ..=gun.projectile.speed_rng,
), ),
0.0, 0.0,
)); ));
@ -274,7 +266,7 @@ impl PhysShip {
.linvel(vel) .linvel(vel)
.build(); .build();
let mut collider = match &outfit.projectile.collider { let mut collider = match &gun.projectile.collider {
ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius) ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
.sensor(true) .sensor(true)
.active_events(ActiveEvents::COLLISION_EVENTS) .active_events(ActiveEvents::COLLISION_EVENTS)
@ -290,10 +282,9 @@ impl PhysShip {
let collider = wrapper.insert_collider(collider, rigid_body); let collider = wrapper.insert_collider(collider, rigid_body);
new.projectiles.push(PhysProjectile::new( new.projectiles.push(PhysProjectile::new(
&res.ct, gun.projectile.clone(),
outfit.projectile.clone(),
rigid_body, rigid_body,
self.data.get_faction(), self.data.get_faction().clone(),
collider, collider,
)); ));
} }
@ -306,8 +297,6 @@ impl PhysShip {
current_z, current_z,
from, from,
} => { } => {
let from_obj = res.ct.get_system_object(*from);
let controls = autopilot::auto_landing( let controls = autopilot::auto_landing(
&res, &res,
img, img,
@ -315,7 +304,7 @@ impl PhysShip {
Vector2::new(to_position.translation.x, to_position.translation.y), Vector2::new(to_position.translation.x, to_position.translation.y),
); );
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap(); 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)) - Vector2::new(to_position.translation.x, to_position.translation.y))
.magnitude(); .magnitude();
let now_d = (r.translation() let now_d = (r.translation()
@ -324,7 +313,7 @@ impl PhysShip {
let f = now_d / max_d; let f = now_d / max_d;
let current_z = *current_z; 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 { if current_z <= 1.0 {
// Finish unlanding ship // Finish unlanding ship
@ -351,18 +340,17 @@ impl PhysShip {
} }
ShipState::Landing { target, current_z } => { ShipState::Landing { target, current_z } => {
let target_obj = res.ct.get_system_object(*target);
let controls = autopilot::auto_landing( let controls = autopilot::auto_landing(
&res, &res,
img, img,
PhysSimShipHandle(self.collider), 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 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 // Finish landing ship
self.data.finish_land_on(); self.data.finish_land_on();
let r = wrapper.get_rigid_body_mut(self.rigid_body).unwrap(); 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 rigid_body = wrapper.get_rigid_body(self.rigid_body).unwrap().clone();
let collider = wrapper.get_collider(self.collider).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_pos = rigid_body.translation();
let ship_rot = rigid_body.rotation(); let ship_rot = rigid_body.rotation();
let ship_ang = ship_rot.angle(); let ship_ang = ship_rot.angle();
@ -451,7 +439,7 @@ impl PhysShip {
while !a { while !a {
x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0; x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
y = rng.gen_range(-1.0..=1.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; / 2.0;
a = collider.shape().contains_local_point(&Point2::new(x, y)); 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); let pos = ship_pos + (Rotation2::new(ship_ang) * pos);
new.effects.push(PhysEffect::new( new.effects.push(PhysEffect::new(
&res.ct,
wrapper, wrapper,
e.effect, e.effect.clone(),
pos, pos,
self.rigid_body, self.rigid_body,
None, None,
@ -476,12 +463,11 @@ impl PhysShip {
/// Public mutable /// Public mutable
impl PhysShip { impl PhysShip {
fn get_flare(&mut self, ct: &Content) -> Option<OutfitHandle> { fn get_flare(&mut self) -> Option<Arc<Outfit>> {
// TODO: better way to pick flare sprite // TODO: better way to pick flare sprite
for (h, _) in self.data.get_outfits().iter_outfits() { for (h, _) in self.data.get_outfits().iter_outfits() {
let c = ct.get_outfit(*h); if h.engine_flare_sprite.is_some() {
if c.engine_flare_sprite.is_some() { return Some(h.clone());
return Some(*h);
} }
} }
return None; return None;
@ -489,37 +475,47 @@ impl PhysShip {
/// Re-create this ship's engine flare animations /// Re-create this ship's engine flare animations
/// Should be called whenever we change outfits /// Should be called whenever we change outfits
fn update_flares(&mut self, ct: &Content) { fn update_flares(&mut self) {
let flare_outfit = self.get_flare(ct); let flare = self.get_flare();
if flare_outfit.is_none() { if flare.is_none() {
self.engine_anim = Vec::new(); self.engine_anim = Vec::new();
return; return;
} }
let flare = ct
.get_outfit(flare_outfit.unwrap())
.engine_flare_sprite
.unwrap();
self.engine_anim = ct self.engine_anim = self
.get_ship(self.data.get_content()) .data
.get_content()
.engines .engines
.iter() .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(); .collect();
} }
/// Add one outfit to this ship /// Add one outfit to this ship
pub fn add_outfit(&mut self, ct: &Content, o: OutfitHandle) { pub fn add_outfit(&mut self, o: Arc<Outfit>) {
self.data.add_outfit(ct.get_outfit(o)); self.data.add_outfit(&o);
self.update_flares(ct); self.update_flares();
} }
/// Add many outfits to this ship /// Add many outfits to this ship
pub fn add_outfits(&mut self, ct: &Content, outfits: impl IntoIterator<Item = OutfitHandle>) { pub fn add_outfits(&mut self, outfits: impl IntoIterator<Item = Arc<Outfit>>) {
for o in outfits { for o in outfits {
self.data.add_outfit(ct.get_outfit(o)); self.data.add_outfit(&o);
} }
self.update_flares(ct); self.update_flares();
} }
} }

View File

@ -1,5 +1,6 @@
use galactica_content::Faction;
use galactica_content::Relationship; use galactica_content::Relationship;
use galactica_content::{Content, FactionHandle, ShipHandle, SystemHandle}; use galactica_content::Ship;
use galactica_playeragent::PlayerAgent; use galactica_playeragent::PlayerAgent;
use nalgebra::{Isometry2, Point2, Rotation2, Vector2}; use nalgebra::{Isometry2, Point2, Rotation2, Vector2};
use rand::Rng; use rand::Rng;
@ -8,6 +9,7 @@ use rapier2d::{
geometry::{ColliderHandle, Group, InteractionGroups}, geometry::{ColliderHandle, Group, InteractionGroups},
}; };
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc;
use super::PhysEffectImage; use super::PhysEffectImage;
use super::{ use super::{
@ -49,13 +51,8 @@ impl NewObjects {
/// Manages the physics state of one system /// Manages the physics state of one system
pub struct PhysSim { pub struct PhysSim {
/// The system this sim is attached to
_system: SystemHandle,
wrapper: PhysWrapper, wrapper: PhysWrapper,
new: NewObjects, new: NewObjects,
effects: Vec<PhysEffect>, effects: Vec<PhysEffect>,
projectiles: HashMap<ColliderHandle, PhysProjectile>, projectiles: HashMap<ColliderHandle, PhysProjectile>,
ships: HashMap<ColliderHandle, PhysShip>, ships: HashMap<ColliderHandle, PhysShip>,
@ -63,10 +60,9 @@ pub struct PhysSim {
// Private methods // Private methods
impl PhysSim { 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 ship = self.ships.get_mut(&collider).unwrap();
let obj = ship.data.get_state().landed_on().unwrap(); let obj = ship.data.get_state().landed_on().unwrap();
let obj = ct.get_system_object(obj);
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let radius = rng.gen_range(500.0..=1500.0); 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_trans = Vector2::new(obj.pos.x, obj.pos.y) + target_offset;
let target_pos = Isometry2::new(target_trans, angle); 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(); let r = self.wrapper.get_rigid_body_mut(ship.rigid_body).unwrap();
r.set_enabled(true); r.set_enabled(true);
@ -87,7 +83,6 @@ impl PhysSim {
pub(super) fn collide_projectile_ship( pub(super) fn collide_projectile_ship(
&mut self, &mut self,
res: &mut PhysStepResources,
projectile_h: ColliderHandle, projectile_h: ColliderHandle,
ship_h: ColliderHandle, ship_h: ColliderHandle,
) { ) {
@ -99,8 +94,11 @@ impl PhysSim {
let projectile = projectile.unwrap(); let projectile = projectile.unwrap();
let ship = ship.unwrap(); let ship = ship.unwrap();
let f = res.ct.get_faction(projectile.faction); let r = projectile
let r = f.relationships.get(&ship.data.get_faction()).unwrap(); .faction
.relationships
.get(&ship.data.get_faction().index)
.unwrap();
let destory_projectile = match r { let destory_projectile = match r {
Relationship::Hostile => match ship.data.get_state() { Relationship::Hostile => match ship.data.get_state() {
ShipState::Flying { .. } => { ShipState::Flying { .. } => {
@ -131,9 +129,8 @@ impl PhysSim {
None => {} None => {}
Some(x) => { Some(x) => {
self.effects.push(PhysEffect::new( self.effects.push(PhysEffect::new(
&res.ct,
&mut self.wrapper, &mut self.wrapper,
*x, x.clone(),
pos, pos,
projectile.rigid_body, projectile.rigid_body,
Some(ship.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 // Public methods
impl PhysSim { impl PhysSim {
/// Create a new physics system /// Create a new physics system
pub fn new(_ct: &Content, system: SystemHandle) -> Self { pub fn new() -> Self {
Self { Self {
_system: system,
wrapper: PhysWrapper::new(), wrapper: PhysWrapper::new(),
new: NewObjects::new(), new: NewObjects::new(),
effects: Vec::new(), effects: Vec::new(),
projectiles: HashMap::new(), projectiles: HashMap::new(),
@ -164,19 +159,17 @@ impl PhysSim {
/// Add a ship to this physics system /// Add a ship to this physics system
pub fn add_ship( pub fn add_ship(
&mut self, &mut self,
ct: &Content, ship: Arc<Ship>,
handle: ShipHandle, faction: Arc<Faction>,
faction: FactionHandle,
personality: ShipPersonality, personality: ShipPersonality,
position: Point2<f32>, position: Point2<f32>,
) -> PhysSimShipHandle { ) -> PhysSimShipHandle {
let ship_content = ct.get_ship(handle); let mut cl = ship.collider.0.clone();
let mut cl = ship_content.collider.0.clone();
// TODO: additonal ship mass from outfits and cargo // TODO: additonal ship mass from outfits and cargo
let rb = RigidBodyBuilder::dynamic() let rb = RigidBodyBuilder::dynamic()
.angular_damping(ship_content.angular_drag) .angular_damping(ship.angular_drag)
.linear_damping(ship_content.linear_drag) .linear_damping(ship.linear_drag)
.translation(Vector2::new(position.x, position.y)) .translation(Vector2::new(position.x, position.y))
.can_sleep(false); .can_sleep(false);
@ -190,14 +183,14 @@ impl PhysSim {
self.ships.insert( self.ships.insert(
collider, collider,
PhysShip::new(ct, handle, personality, faction, ridid_body, collider), PhysShip::new(ship, personality, faction, ridid_body, collider),
); );
return PhysSimShipHandle(collider); return PhysSimShipHandle(collider);
} }
/// Update a player ship's controls /// 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() { if player.ship.is_none() {
return; return;
} }
@ -218,9 +211,11 @@ impl PhysSim {
ship_object.controls.thrust = player.input.pressed_thrust(); ship_object.controls.thrust = player.input.pressed_thrust();
if player.input.pressed_land() { if player.input.pressed_land() {
ship_object.data.set_autopilot(ShipAutoPilot::Landing { if let Some(target) = player.selection.get_planet() {
target: player.selection.get_planet().unwrap(), ship_object.data.set_autopilot(ShipAutoPilot::Landing {
}) target: target.clone(),
})
}
} }
} }
@ -237,7 +232,7 @@ impl PhysSim {
ShipState::Landed { .. } => { ShipState::Landed { .. } => {
if player.input.pressed_land() { 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() { if p.is_none() || s.is_none() {
continue; continue;
} }
self.collide_projectile_ship(&mut res, a, b); self.collide_projectile_ship(a, b);
} }
} }
res.timing.mark_physics_step(); res.timing.mark_physics_step();