Compare commits
No commits in common. "7565f09e3b1a9d4be4d3cdde6fa0182dbfcda83f" and "aa4c6c18b3ea5458328c3040b4e73e0e933a291f" have entirely different histories.
7565f09e3b
...
aa4c6c18b3
4
TODO.md
4
TODO.md
|
@ -1,11 +1,9 @@
|
||||||
## Specific Jobs
|
## Specific Jobs
|
||||||
- Particle variation
|
- Particle variation
|
||||||
- Particle physics
|
|
||||||
- UI: health, shield, fuel, heat, energy bars
|
- UI: health, shield, fuel, heat, energy bars
|
||||||
- UI: text arranger
|
- UI: text arranger
|
||||||
- Sound system
|
- Sound system
|
||||||
- Ship death debris
|
- Ship death animation & debris
|
||||||
- Sprite reels
|
|
||||||
|
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
|
2
assets
2
assets
|
@ -1 +1 @@
|
||||||
Subproject commit 74ddbde9e1cec1418c17844bc7336324ece88d15
|
Subproject commit 38fd6766762ce90bb699f98e30e46b17c4eda50c
|
|
@ -1,34 +0,0 @@
|
||||||
[effect."small explosion"]
|
|
||||||
sprite = "particle::explosion::small"
|
|
||||||
lifetime = "inherit"
|
|
||||||
inherit_velocity = "target"
|
|
||||||
size = 8.0
|
|
||||||
|
|
||||||
[effect."large explosion"]
|
|
||||||
sprite = "particle::explosion::large"
|
|
||||||
lifetime = "inherit"
|
|
||||||
inherit_velocity = "target"
|
|
||||||
size = 25.0
|
|
||||||
|
|
||||||
[effect."huge explosion"]
|
|
||||||
sprite = "particle::explosion::huge"
|
|
||||||
lifetime = "inherit"
|
|
||||||
inherit_velocity = "target"
|
|
||||||
size = 50.0
|
|
||||||
|
|
||||||
[effect."blaster expire"]
|
|
||||||
sprite = "particle::blaster"
|
|
||||||
lifetime = "inherit"
|
|
||||||
inherit_velocity = "projectile"
|
|
||||||
size = 3.0
|
|
||||||
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
# inherit velocity scale
|
|
||||||
# absolute velocity/angle (no inherit)
|
|
||||||
# random lifetime, velocity, angle, spin
|
|
||||||
# bullet bounce effect: inherit and change velocity
|
|
||||||
# effect probabilities & variants
|
|
||||||
# multiple particles in one effect
|
|
||||||
# fade
|
|
||||||
# document: effect vs particle
|
|
|
@ -29,9 +29,13 @@ projectile.force = 0.0
|
||||||
|
|
||||||
projectile.collider.ball.radius = 2.0
|
projectile.collider.ball.radius = 2.0
|
||||||
|
|
||||||
projectile.impact_effect = "small explosion"
|
projectile.impact.sprite = "particle::explosion"
|
||||||
|
projectile.impact.lifetime = "inherit"
|
||||||
|
projectile.impact.inherit_velocity = "target"
|
||||||
|
projectile.impact.size = 3.0
|
||||||
|
|
||||||
projectile.expire_effect.sprite = "particle::blaster"
|
|
||||||
projectile.expire_effect.lifetime = "inherit"
|
projectile.expire.sprite = "particle::blaster"
|
||||||
projectile.expire_effect.inherit_velocity = "projectile"
|
projectile.expire.lifetime = "inherit"
|
||||||
projectile.expire_effect.size = 3.0
|
projectile.expire.inherit_velocity = "projectile"
|
||||||
|
projectile.expire.size = 3.0
|
||||||
|
|
|
@ -6,8 +6,6 @@ hull = 200
|
||||||
linear_drag = 0.2
|
linear_drag = 0.2
|
||||||
angular_drag = 0.2
|
angular_drag = 0.2
|
||||||
|
|
||||||
# TODO: disable
|
|
||||||
# TODO: damage effects
|
|
||||||
|
|
||||||
space.outfit = 200
|
space.outfit = 200
|
||||||
space.engine = 50
|
space.engine = 50
|
||||||
|
@ -16,20 +14,7 @@ space.weapon = 50
|
||||||
engines = [{ x = 0.0, y = -1.05, size = 50.0 }]
|
engines = [{ x = 0.0, y = -1.05, size = 50.0 }]
|
||||||
guns = [{ x = 0.0, y = 1 }, { x = 0.1, y = 0.80 }, { x = -0.1, y = 0.80 }]
|
guns = [{ x = 0.0, y = 1 }, { x = 0.1, y = 0.80 }, { x = -0.1, y = 0.80 }]
|
||||||
|
|
||||||
|
collision.points = [
|
||||||
# Length of death sequence, in seconds
|
|
||||||
collapse.length = 5.0
|
|
||||||
|
|
||||||
# Effects to create during the collapse sequence.
|
|
||||||
# On average, `count` will be spawned over the sequence,
|
|
||||||
# with a distribution of (x^2 + 0.1)
|
|
||||||
collapse.effects = [
|
|
||||||
{ effect = "small explosion", count = 30 },
|
|
||||||
{ effect = "large explosion", count = 5 },
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
collision = [
|
|
||||||
#[rustfmt:skip],
|
#[rustfmt:skip],
|
||||||
[0.53921, 1.0000],
|
[0.53921, 1.0000],
|
||||||
[0.53921, 0.29343],
|
[0.53921, 0.29343],
|
||||||
|
@ -54,43 +39,27 @@ collision = [
|
||||||
[-0.53921, 1.0000],
|
[-0.53921, 1.0000],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# TODO: generate this automatically
|
||||||
# Scripted explosion
|
collision.indices = [
|
||||||
[[ship."Gypsum".collapse.event]]
|
|
||||||
time = 5.0
|
|
||||||
effects = [
|
|
||||||
#[rustfmt:skip],
|
#[rustfmt:skip],
|
||||||
{ effect = "small explosion", count = 8 },
|
[0, 1],
|
||||||
{ effect = "large explosion", count = 5 },
|
[1, 2],
|
||||||
{ effect = "huge explosion", count = 1, pos = [0, 0] },
|
[2, 3],
|
||||||
{ effect = "huge explosion", count = 4 },
|
[3, 4],
|
||||||
|
[4, 5],
|
||||||
|
[5, 6],
|
||||||
|
[6, 7],
|
||||||
|
[7, 8],
|
||||||
|
[8, 9],
|
||||||
|
[9, 10],
|
||||||
|
[10, 11],
|
||||||
|
[11, 12],
|
||||||
|
[12, 13],
|
||||||
|
[13, 14],
|
||||||
|
[14, 15],
|
||||||
|
[15, 16],
|
||||||
|
[16, 17],
|
||||||
|
[17, 18],
|
||||||
|
[18, 19],
|
||||||
|
[19, 0],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Scripted explosion
|
|
||||||
[[ship."Gypsum".collapse.event]]
|
|
||||||
time = 0.0
|
|
||||||
effects = [
|
|
||||||
#[rustfmt:skip],
|
|
||||||
{ effect = "small explosion", count = 3 },
|
|
||||||
{ effect = "large explosion", count = 1 },
|
|
||||||
]
|
|
||||||
|
|
||||||
# Play a sprite reel (or change sprite)
|
|
||||||
#[[ship."Gypsum".death.collapse.event]]
|
|
||||||
#time = 10.0
|
|
||||||
#reel = "gypsum post-death"
|
|
||||||
|
|
||||||
# Create debris
|
|
||||||
#[[ship."Gypsum".death.collapse.event]]
|
|
||||||
#time = "end"
|
|
||||||
#physics = "inherit"
|
|
||||||
# OR (relative to original rigidbody)
|
|
||||||
#physics.position = [0, 0]
|
|
||||||
#physics.velocity = [0, 0]
|
|
||||||
#physics.angle = 0
|
|
||||||
#physics.angvel = 0
|
|
||||||
#debris = "debris"
|
|
||||||
|
|
||||||
# Burning, interactable, destructible debris
|
|
||||||
#[debris]
|
|
||||||
#effects = [{ type = "small explosion", count = 10 }]
|
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
# TODO:
|
|
||||||
# random start frame
|
|
||||||
# repeat once: stay on last frame
|
|
||||||
# blending mode: alpha / half-alpha / additive
|
|
||||||
|
|
||||||
|
|
||||||
[sprite."starfield"]
|
[sprite."starfield"]
|
||||||
file = "starfield.png"
|
file = "starfield.png"
|
||||||
|
|
||||||
|
@ -26,9 +20,8 @@ file = "projectile/blaster.png"
|
||||||
file = "ship/gypsum.png"
|
file = "ship/gypsum.png"
|
||||||
|
|
||||||
[sprite."ship::peregrine"]
|
[sprite."ship::peregrine"]
|
||||||
timing.duration = 1.3
|
duration = 1.3
|
||||||
repeat = "reverse"
|
repeat = "reverse"
|
||||||
random_start_frame = true
|
|
||||||
frames = [
|
frames = [
|
||||||
"ship/peregrine/01.png",
|
"ship/peregrine/01.png",
|
||||||
"ship/peregrine/02.png",
|
"ship/peregrine/02.png",
|
||||||
|
@ -59,7 +52,7 @@ file = "ui/radarframe.png"
|
||||||
file = "ui/center-arrow.png"
|
file = "ui/center-arrow.png"
|
||||||
|
|
||||||
[sprite."particle::blaster"]
|
[sprite."particle::blaster"]
|
||||||
timing.duration = 0.15
|
duration = 0.15
|
||||||
repeat = "once"
|
repeat = "once"
|
||||||
frames = [
|
frames = [
|
||||||
"particle/blaster/01.png",
|
"particle/blaster/01.png",
|
||||||
|
@ -69,48 +62,8 @@ frames = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[sprite."particle::explosion::tiny"]
|
[sprite."particle::explosion"]
|
||||||
timing.fps = 15
|
duration = 0.4
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
|
||||||
"particle/explosion-tiny/01.png",
|
|
||||||
"particle/explosion-tiny/02.png",
|
|
||||||
"particle/explosion-tiny/03.png",
|
|
||||||
"particle/explosion-tiny/04.png",
|
|
||||||
"particle/explosion-tiny/05.png",
|
|
||||||
"particle/explosion-tiny/06.png",
|
|
||||||
]
|
|
||||||
|
|
||||||
[sprite."particle::explosion::small"]
|
|
||||||
timing.fps = 15
|
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
|
||||||
"particle/explosion-small/01.png",
|
|
||||||
"particle/explosion-small/02.png",
|
|
||||||
"particle/explosion-small/03.png",
|
|
||||||
"particle/explosion-small/04.png",
|
|
||||||
"particle/explosion-small/05.png",
|
|
||||||
"particle/explosion-small/06.png",
|
|
||||||
"particle/explosion-small/07.png",
|
|
||||||
]
|
|
||||||
|
|
||||||
[sprite."particle::explosion::medium"]
|
|
||||||
timing.fps = 15
|
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
|
||||||
"particle/explosion-medium/01.png",
|
|
||||||
"particle/explosion-medium/02.png",
|
|
||||||
"particle/explosion-medium/03.png",
|
|
||||||
"particle/explosion-medium/04.png",
|
|
||||||
"particle/explosion-medium/05.png",
|
|
||||||
"particle/explosion-medium/06.png",
|
|
||||||
"particle/explosion-medium/07.png",
|
|
||||||
"particle/explosion-medium/08.png",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[sprite."particle::explosion::large"]
|
|
||||||
timing.fps = 15
|
|
||||||
repeat = "once"
|
repeat = "once"
|
||||||
frames = [
|
frames = [
|
||||||
"particle/explosion-large/01.png",
|
"particle/explosion-large/01.png",
|
||||||
|
@ -123,19 +76,3 @@ frames = [
|
||||||
"particle/explosion-large/08.png",
|
"particle/explosion-large/08.png",
|
||||||
"particle/explosion-large/09.png",
|
"particle/explosion-large/09.png",
|
||||||
]
|
]
|
||||||
|
|
||||||
[sprite."particle::explosion::huge"]
|
|
||||||
timing.fps = 15
|
|
||||||
repeat = "once"
|
|
||||||
frames = [
|
|
||||||
"particle/explosion-huge/01.png",
|
|
||||||
"particle/explosion-huge/02.png",
|
|
||||||
"particle/explosion-huge/03.png",
|
|
||||||
"particle/explosion-huge/04.png",
|
|
||||||
"particle/explosion-huge/05.png",
|
|
||||||
"particle/explosion-huge/06.png",
|
|
||||||
"particle/explosion-huge/07.png",
|
|
||||||
"particle/explosion-huge/08.png",
|
|
||||||
"particle/explosion-huge/09.png",
|
|
||||||
"particle/explosion-huge/10.png",
|
|
||||||
]
|
|
||||||
|
|
|
@ -10,20 +10,17 @@ use std::{cmp::Eq, hash::Hash};
|
||||||
/// A lightweight representation of a sprite
|
/// A lightweight representation of a sprite
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct SpriteHandle {
|
pub struct SpriteHandle {
|
||||||
pub(crate) index: usize,
|
/// The index of this sprite in content.sprites
|
||||||
|
/// This must be public, since render uses this to
|
||||||
|
/// select sprites.
|
||||||
|
///
|
||||||
|
/// This is a u32 for that same reason, too.
|
||||||
|
pub index: u32,
|
||||||
|
|
||||||
/// The aspect ratio of this sprite (width / height)
|
/// The aspect ratio of this sprite (width / height)
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteHandle {
|
|
||||||
/// The index of this sprite in content's sprite array.
|
|
||||||
/// Render uses this to build its buffers.
|
|
||||||
pub fn get_index(&self) -> u32 {
|
|
||||||
self.index as u32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hash for SpriteHandle {
|
impl Hash for SpriteHandle {
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
self.index.hash(state)
|
self.index.hash(state)
|
||||||
|
@ -40,7 +37,7 @@ impl PartialEq for SpriteHandle {
|
||||||
/// A lightweight representation of an outfit
|
/// A lightweight representation of an outfit
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct OutfitHandle {
|
pub struct OutfitHandle {
|
||||||
/// TODO: pub in crate, currently for debug (same with all other handles)
|
/// TODO
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,12 +65,7 @@ pub struct SystemHandle {
|
||||||
/// A lightweight representation of a faction
|
/// A lightweight representation of a faction
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct FactionHandle {
|
pub struct FactionHandle {
|
||||||
/// TODO
|
/// The index of this faction in content.factions
|
||||||
|
/// TODO: pub in crate, currently for debug (same with all other handles)
|
||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A lightweight representation of an effect
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub struct EffectHandle {
|
|
||||||
pub(crate) index: usize,
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,17 +18,18 @@ use std::{
|
||||||
use toml;
|
use toml;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
pub use handle::{
|
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SpriteHandle, SystemHandle};
|
||||||
EffectHandle, FactionHandle, GunHandle, OutfitHandle, ShipHandle, SpriteHandle, SystemHandle,
|
pub use part::{
|
||||||
|
EnginePoint, Faction, Gun, GunPoint, ImpactInheritVelocity, Outfit, OutfitSpace, Projectile,
|
||||||
|
ProjectileCollider, ProjectileParticle, Relationship, RepeatMode, Ship, Sprite, System,
|
||||||
};
|
};
|
||||||
pub use part::*;
|
|
||||||
|
|
||||||
mod syntax {
|
mod syntax {
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, fmt::Display, hash::Hash};
|
use std::{collections::HashMap, fmt::Display, hash::Hash};
|
||||||
|
|
||||||
use crate::part::{effect, faction, gun, outfit, ship, sprite, system};
|
use crate::part::{faction, gun, outfit, ship, sprite, system};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
|
@ -38,7 +39,6 @@ mod syntax {
|
||||||
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
|
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
|
||||||
pub sprite: Option<HashMap<String, sprite::syntax::Sprite>>,
|
pub sprite: Option<HashMap<String, sprite::syntax::Sprite>>,
|
||||||
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
||||||
pub effect: Option<HashMap<String, effect::syntax::Effect>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_hashmap<K, V>(
|
fn merge_hashmap<K, V>(
|
||||||
|
@ -75,7 +75,6 @@ mod syntax {
|
||||||
outfit: None,
|
outfit: None,
|
||||||
sprite: None,
|
sprite: None,
|
||||||
faction: None,
|
faction: None,
|
||||||
effect: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,8 +89,6 @@ mod syntax {
|
||||||
.with_context(|| "while merging sprites")?;
|
.with_context(|| "while merging sprites")?;
|
||||||
merge_hashmap(&mut self.faction, other.faction)
|
merge_hashmap(&mut self.faction, other.faction)
|
||||||
.with_context(|| "while merging factions")?;
|
.with_context(|| "while merging factions")?;
|
||||||
merge_hashmap(&mut self.effect, other.effect)
|
|
||||||
.with_context(|| "while merging effects")?;
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,29 +98,11 @@ trait Build {
|
||||||
type InputSyntaxType;
|
type InputSyntaxType;
|
||||||
|
|
||||||
/// Build a processed System struct from raw serde data
|
/// Build a processed System struct from raw serde data
|
||||||
fn build(
|
fn build(root: Self::InputSyntaxType, ct: &mut Content) -> Result<()>
|
||||||
root: Self::InputSyntaxType,
|
|
||||||
build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<()>
|
|
||||||
where
|
where
|
||||||
Self: Sized;
|
Self: Sized;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stores temporary data while building context objects
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct ContentBuildContext {
|
|
||||||
pub effect_index: HashMap<String, EffectHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ContentBuildContext {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
effect_index: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents static game content
|
/// Represents static game content
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
|
@ -134,22 +113,30 @@ pub struct Content {
|
||||||
starfield_sprite_name: String,
|
starfield_sprite_name: String,
|
||||||
|
|
||||||
/// Sprites
|
/// Sprites
|
||||||
pub sprites: Vec<Sprite>,
|
pub sprites: Vec<part::sprite::Sprite>,
|
||||||
/// Map strings to texture names.
|
/// Map strings to texture names.
|
||||||
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
/// This is only necessary because we need to hard-code a few texture names for UI elements.
|
||||||
sprite_index: HashMap<String, SpriteHandle>,
|
sprite_index: HashMap<String, handle::SpriteHandle>,
|
||||||
/// The texture to use for starfield stars
|
/// The texture to use for starfield stars
|
||||||
starfield_handle: Option<SpriteHandle>,
|
starfield_handle: Option<handle::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>,
|
/// Outfits
|
||||||
guns: Vec<Gun>, // TODO: merge with outfit
|
outfits: Vec<part::outfit::Outfit>,
|
||||||
ships: Vec<Ship>,
|
|
||||||
systems: Vec<System>,
|
/// Ship guns
|
||||||
factions: Vec<Faction>,
|
guns: Vec<part::gun::Gun>,
|
||||||
effects: Vec<Effect>,
|
|
||||||
|
/// Ship bodies
|
||||||
|
ships: Vec<part::ship::Ship>,
|
||||||
|
|
||||||
|
/// Star systems
|
||||||
|
systems: Vec<part::system::System>,
|
||||||
|
|
||||||
|
/// Factions
|
||||||
|
factions: Vec<part::faction::Faction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading methods
|
// Loading methods
|
||||||
|
@ -202,8 +189,6 @@ impl Content {
|
||||||
toml::from_str(&file_string)?
|
toml::from_str(&file_string)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut build_context = ContentBuildContext::new();
|
|
||||||
|
|
||||||
let mut content = Self {
|
let mut content = Self {
|
||||||
sprite_atlas: atlas,
|
sprite_atlas: atlas,
|
||||||
systems: Vec::new(),
|
systems: Vec::new(),
|
||||||
|
@ -212,58 +197,33 @@ impl Content {
|
||||||
outfits: Vec::new(),
|
outfits: Vec::new(),
|
||||||
sprites: Vec::new(),
|
sprites: Vec::new(),
|
||||||
factions: Vec::new(),
|
factions: Vec::new(),
|
||||||
effects: Vec::new(),
|
|
||||||
sprite_index: HashMap::new(),
|
sprite_index: HashMap::new(),
|
||||||
starfield_handle: None,
|
starfield_handle: None,
|
||||||
image_root: texture_root,
|
image_root: texture_root,
|
||||||
starfield_sprite_name: starfield_texture_name,
|
starfield_sprite_name: starfield_texture_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Order here matters, usually
|
||||||
|
if root.sprite.is_some() {
|
||||||
|
part::sprite::Sprite::build(root.sprite.take().unwrap(), &mut content)?;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: enforce sprite and image limits
|
// TODO: enforce sprite and image limits
|
||||||
|
|
||||||
// Order matters. Some content types require another to be fully initialized
|
|
||||||
if root.sprite.is_some() {
|
|
||||||
part::sprite::Sprite::build(
|
|
||||||
root.sprite.take().unwrap(),
|
|
||||||
&mut build_context,
|
|
||||||
&mut content,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
if root.effect.is_some() {
|
|
||||||
part::effect::Effect::build(
|
|
||||||
root.effect.take().unwrap(),
|
|
||||||
&mut build_context,
|
|
||||||
&mut content,
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order below this line does not matter
|
|
||||||
if root.ship.is_some() {
|
if root.ship.is_some() {
|
||||||
part::ship::Ship::build(root.ship.take().unwrap(), &mut build_context, &mut content)?;
|
part::ship::Ship::build(root.ship.take().unwrap(), &mut content)?;
|
||||||
}
|
}
|
||||||
if root.gun.is_some() {
|
if root.gun.is_some() {
|
||||||
part::gun::Gun::build(root.gun.take().unwrap(), &mut build_context, &mut content)?;
|
part::gun::Gun::build(root.gun.take().unwrap(), &mut content)?;
|
||||||
}
|
}
|
||||||
if root.outfit.is_some() {
|
if root.outfit.is_some() {
|
||||||
part::outfit::Outfit::build(
|
part::outfit::Outfit::build(root.outfit.take().unwrap(), &mut content)?;
|
||||||
root.outfit.take().unwrap(),
|
|
||||||
&mut build_context,
|
|
||||||
&mut content,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
if root.system.is_some() {
|
if root.system.is_some() {
|
||||||
part::system::System::build(
|
part::system::System::build(root.system.take().unwrap(), &mut content)?;
|
||||||
root.system.take().unwrap(),
|
|
||||||
&mut build_context,
|
|
||||||
&mut content,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
if root.faction.is_some() {
|
if root.faction.is_some() {
|
||||||
part::faction::Faction::build(
|
part::faction::Faction::build(root.faction.take().unwrap(), &mut content)?;
|
||||||
root.faction.take().unwrap(),
|
|
||||||
&mut build_context,
|
|
||||||
&mut content,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(content);
|
return Ok(content);
|
||||||
|
@ -329,9 +289,4 @@ impl Content {
|
||||||
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
|
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
|
||||||
return &self.factions[h.index];
|
return &self.factions[h.index];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get an effect from a handle
|
|
||||||
pub fn get_effect(&self, h: EffectHandle) -> &Effect {
|
|
||||||
return &self.effects[h.index];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,154 +0,0 @@
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle};
|
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{Content, ContentBuildContext, EffectHandle};
|
|
||||||
// Raw serde syntax structs.
|
|
||||||
// These are never seen by code outside this crate.
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Effect {
|
|
||||||
pub sprite: String,
|
|
||||||
pub lifetime: EffectLifetime,
|
|
||||||
pub inherit_velocity: super::ImpactInheritVelocity,
|
|
||||||
pub size: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
// We implement building here instead of in super::Effect because
|
|
||||||
// effects may be defined inline (see EffectReference).
|
|
||||||
impl Effect {
|
|
||||||
pub fn add_to(
|
|
||||||
self,
|
|
||||||
_build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<EffectHandle> {
|
|
||||||
let sprite = match content.sprite_index.get(&self.sprite) {
|
|
||||||
None => bail!("sprite `{}` doesn't exist", self.sprite),
|
|
||||||
Some(t) => *t,
|
|
||||||
};
|
|
||||||
|
|
||||||
let lifetime = match self.lifetime {
|
|
||||||
EffectLifetime::Seconds(s) => s,
|
|
||||||
EffectLifetime::Inherit(s) => {
|
|
||||||
if s == "inherit" {
|
|
||||||
let sprite = content.get_sprite(sprite);
|
|
||||||
sprite.fps * sprite.frames.len() as f32
|
|
||||||
} else {
|
|
||||||
bail!("bad effect lifetime, must be float or \"inherit\"",)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let handle = EffectHandle {
|
|
||||||
index: content.effects.len(),
|
|
||||||
};
|
|
||||||
content.effects.push(super::Effect {
|
|
||||||
sprite,
|
|
||||||
lifetime,
|
|
||||||
handle,
|
|
||||||
inherit_velocity: self.inherit_velocity,
|
|
||||||
size: self.size,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum EffectLifetime {
|
|
||||||
Inherit(String),
|
|
||||||
Seconds(f32),
|
|
||||||
}
|
|
||||||
|
|
||||||
// This isn't used here, but is pulled in by other content items.
|
|
||||||
/// A reference to an effect by name, or an inline definition.
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum EffectReference {
|
|
||||||
Label(String),
|
|
||||||
Effect(Effect),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EffectReference {
|
|
||||||
pub fn to_handle(
|
|
||||||
self,
|
|
||||||
build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<EffectHandle> {
|
|
||||||
// We do not insert anything into build_context here,
|
|
||||||
// since inline effects cannot be referenced by name.
|
|
||||||
Ok(match self {
|
|
||||||
Self::Effect(e) => e.add_to(build_context, content)?,
|
|
||||||
Self::Label(l) => match build_context.effect_index.get(&l) {
|
|
||||||
Some(h) => *h,
|
|
||||||
None => bail!("no effect named `{}`", l),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How we should set an effect's velocity
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
|
||||||
pub enum ImpactInheritVelocity {
|
|
||||||
/// Don't inherit any velocity.
|
|
||||||
/// This impact particle will be still.
|
|
||||||
#[serde(rename = "don't")]
|
|
||||||
Dont,
|
|
||||||
|
|
||||||
/// Inherit target velocity.
|
|
||||||
/// This impact particle will stick to the object it hits.
|
|
||||||
#[serde(rename = "target")]
|
|
||||||
Target,
|
|
||||||
|
|
||||||
/// Inherit projectile velocity.
|
|
||||||
/// This impact particle will continue on its projectile's path.
|
|
||||||
#[serde(rename = "projectile")]
|
|
||||||
Projectile,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The particle a projectile will spawn when it hits something
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Effect {
|
|
||||||
/// The sprite to use for this particle.
|
|
||||||
/// This is most likely animated.
|
|
||||||
pub sprite: SpriteHandle,
|
|
||||||
|
|
||||||
/// This effect's handle
|
|
||||||
pub handle: EffectHandle,
|
|
||||||
|
|
||||||
/// How many seconds this particle should live
|
|
||||||
pub lifetime: f32,
|
|
||||||
|
|
||||||
/// How we should set this particle's velocity
|
|
||||||
pub inherit_velocity: ImpactInheritVelocity,
|
|
||||||
|
|
||||||
/// The height of this particle, in game units.
|
|
||||||
pub size: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::Build for Effect {
|
|
||||||
type InputSyntaxType = HashMap<String, syntax::Effect>;
|
|
||||||
|
|
||||||
fn build(
|
|
||||||
effects: Self::InputSyntaxType,
|
|
||||||
build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
for (effect_name, effect) in effects {
|
|
||||||
let h = effect
|
|
||||||
.add_to(build_context, content)
|
|
||||||
.with_context(|| format!("while evaluating effect `{}`", effect_name))?;
|
|
||||||
build_context.effect_index.insert(effect_name, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ use anyhow::{bail, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{handle::FactionHandle, Content, ContentBuildContext};
|
use crate::{handle::FactionHandle, Content};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -61,17 +61,13 @@ pub struct Faction {
|
||||||
impl crate::Build for Faction {
|
impl crate::Build for Faction {
|
||||||
type InputSyntaxType = HashMap<String, syntax::Faction>;
|
type InputSyntaxType = HashMap<String, syntax::Faction>;
|
||||||
|
|
||||||
fn build(
|
fn build(factions: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
factions: Self::InputSyntaxType,
|
|
||||||
_build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
// Keeps track of position in faction array.
|
// Keeps track of position in faction array.
|
||||||
// This lets us build FactionHandles before finishing all factions.
|
// This lets us build FactionHandles before finishing all factions.
|
||||||
let faction_names: Vec<String> = factions.keys().map(|x| x.to_owned()).collect();
|
let faction_names: Vec<String> = factions.keys().map(|x| x.to_owned()).collect();
|
||||||
|
|
||||||
// Indexing will break if this is false.
|
// Indexing will break if this is false.
|
||||||
assert!(content.factions.len() == 0);
|
assert!(ct.factions.len() == 0);
|
||||||
|
|
||||||
for f_idx in 0..faction_names.len() {
|
for f_idx in 0..faction_names.len() {
|
||||||
let faction_name = &faction_names[f_idx];
|
let faction_name = &faction_names[f_idx];
|
||||||
|
@ -116,7 +112,7 @@ impl crate::Build for Faction {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
content.factions.push(Self {
|
ct.factions.push(Self {
|
||||||
name: faction_name.to_owned(),
|
name: faction_name.to_owned(),
|
||||||
handle: h,
|
handle: h,
|
||||||
relationships,
|
relationships,
|
||||||
|
|
|
@ -5,11 +5,10 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content};
|
use crate::{handle::SpriteHandle, Content};
|
||||||
|
|
||||||
use crate::{ContentBuildContext, EffectHandle, OutfitSpace};
|
use crate::OutfitSpace;
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::part::effect;
|
use crate::part::shared;
|
||||||
use crate::part::outfitspace;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
// 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.
|
||||||
|
@ -19,7 +18,7 @@ pub(crate) mod syntax {
|
||||||
pub projectile: Projectile,
|
pub projectile: Projectile,
|
||||||
pub rate: f32,
|
pub rate: f32,
|
||||||
pub rate_rng: f32,
|
pub rate_rng: f32,
|
||||||
pub space: outfitspace::syntax::OutfitSpace,
|
pub space: shared::syntax::OutfitSpace,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -33,11 +32,26 @@ pub(crate) mod syntax {
|
||||||
pub lifetime_rng: f32,
|
pub lifetime_rng: f32,
|
||||||
pub damage: f32,
|
pub damage: f32,
|
||||||
pub angle_rng: f32,
|
pub angle_rng: f32,
|
||||||
pub impact_effect: Option<effect::syntax::EffectReference>,
|
pub impact: Option<ProjectileParticle>,
|
||||||
pub expire_effect: Option<effect::syntax::EffectReference>,
|
pub expire: Option<ProjectileParticle>,
|
||||||
pub collider: super::ProjectileCollider,
|
pub collider: super::ProjectileCollider,
|
||||||
pub force: f32,
|
pub force: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct ProjectileParticle {
|
||||||
|
pub sprite: String,
|
||||||
|
pub lifetime: ParticleLifetime,
|
||||||
|
pub inherit_velocity: super::ImpactInheritVelocity,
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ParticleLifetime {
|
||||||
|
Inherit(String),
|
||||||
|
Seconds(f32),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines a projectile's collider
|
/// Defines a projectile's collider
|
||||||
|
@ -48,13 +62,30 @@ pub enum ProjectileCollider {
|
||||||
Ball(BallCollider),
|
Ball(BallCollider),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple ball-shaped collider, centered at the object's position
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct BallCollider {
|
pub struct BallCollider {
|
||||||
/// The radius of this ball
|
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How we should set an impact particle's velocity
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub enum ImpactInheritVelocity {
|
||||||
|
/// Don't inherit any velocity.
|
||||||
|
/// This impact particle will be still.
|
||||||
|
#[serde(rename = "don't")]
|
||||||
|
Dont,
|
||||||
|
|
||||||
|
/// Inherit target velocity.
|
||||||
|
/// This impact particle will stick to the object it hits.
|
||||||
|
#[serde(rename = "target")]
|
||||||
|
Target,
|
||||||
|
|
||||||
|
/// Inherit projectile velocity.
|
||||||
|
/// This impact particle will continue on its projectile's path.
|
||||||
|
#[serde(rename = "projectile")]
|
||||||
|
Projectile,
|
||||||
|
}
|
||||||
|
|
||||||
/// Represents a gun outfit.
|
/// Represents a gun outfit.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Gun {
|
pub struct Gun {
|
||||||
|
@ -112,50 +143,86 @@ pub struct Projectile {
|
||||||
pub angle_rng: Deg<f32>,
|
pub angle_rng: Deg<f32>,
|
||||||
|
|
||||||
/// The particle this projectile will spawn when it hits something
|
/// The particle this projectile will spawn when it hits something
|
||||||
pub impact_effect: Option<EffectHandle>,
|
pub impact_particle: Option<ProjectileParticle>,
|
||||||
|
|
||||||
/// The particle this projectile will spawn when it expires
|
/// The particle this projectile will spawn when it expires
|
||||||
pub expire_effect: Option<EffectHandle>,
|
pub expire_particle: Option<ProjectileParticle>,
|
||||||
|
|
||||||
/// Collider parameters for this projectile
|
/// Collider parameters for this projectile
|
||||||
pub collider: ProjectileCollider,
|
pub collider: ProjectileCollider,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The particle a projectile will spawn when it hits something
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProjectileParticle {
|
||||||
|
/// The sprite to use for this particle.
|
||||||
|
/// This is most likely animated.
|
||||||
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
|
/// How many seconds this particle should live
|
||||||
|
pub lifetime: f32,
|
||||||
|
|
||||||
|
/// How we should set this particle's velocity
|
||||||
|
pub inherit_velocity: ImpactInheritVelocity,
|
||||||
|
|
||||||
|
/// The height of this particle, in game units.
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_projectile_particle(
|
||||||
|
ct: &Content,
|
||||||
|
p: Option<syntax::ProjectileParticle>,
|
||||||
|
) -> Result<Option<ProjectileParticle>> {
|
||||||
|
if let Some(impact) = p {
|
||||||
|
let impact_sprite_handle = match ct.sprite_index.get(&impact.sprite) {
|
||||||
|
None => bail!("impact sprite `{}` doesn't exist", impact.sprite),
|
||||||
|
Some(t) => *t,
|
||||||
|
};
|
||||||
|
|
||||||
|
let impact_lifetime = match impact.lifetime {
|
||||||
|
syntax::ParticleLifetime::Seconds(s) => s,
|
||||||
|
syntax::ParticleLifetime::Inherit(s) => {
|
||||||
|
if s == "inherit" {
|
||||||
|
let sprite = ct.get_sprite(impact_sprite_handle);
|
||||||
|
sprite.fps * sprite.frames.len() as f32
|
||||||
|
} else {
|
||||||
|
bail!("bad impact lifetime, must be float or \"inherit\"",)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(ProjectileParticle {
|
||||||
|
sprite: impact_sprite_handle,
|
||||||
|
lifetime: impact_lifetime,
|
||||||
|
inherit_velocity: impact.inherit_velocity,
|
||||||
|
size: impact.size,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl crate::Build for Gun {
|
impl crate::Build for Gun {
|
||||||
type InputSyntaxType = HashMap<String, syntax::Gun>;
|
type InputSyntaxType = HashMap<String, syntax::Gun>;
|
||||||
|
|
||||||
fn build(
|
fn build(gun: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
gun: Self::InputSyntaxType,
|
|
||||||
build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
for (gun_name, gun) in gun {
|
for (gun_name, gun) in gun {
|
||||||
let projectile_sprite_handle = match content.sprite_index.get(&gun.projectile.sprite) {
|
let projectile_sprite_handle = match ct.sprite_index.get(&gun.projectile.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"projectile sprite `{}` doesn't exist in gun `{}`",
|
"In gun `{}`: projectile sprite `{}` doesn't exist",
|
||||||
gun.projectile.sprite,
|
|
||||||
gun_name,
|
gun_name,
|
||||||
|
gun.projectile.sprite
|
||||||
),
|
),
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
|
|
||||||
let impact_effect = match gun.projectile.impact_effect {
|
let impact_particle = parse_projectile_particle(ct, gun.projectile.impact)
|
||||||
Some(e) => Some(
|
.with_context(|| format!("In gun `{}`", gun_name))?;
|
||||||
e.to_handle(build_context, content)
|
|
||||||
.with_context(|| format!("while loading gun `{}`", gun_name))?,
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let expire_effect = match gun.projectile.expire_effect {
|
let expire_particle = parse_projectile_particle(ct, gun.projectile.expire)
|
||||||
Some(e) => Some(
|
.with_context(|| format!("In gun `{}`", gun_name))?;
|
||||||
e.to_handle(build_context, content)
|
|
||||||
.with_context(|| format!("while loading gun `{}`", gun_name))?,
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
content.guns.push(Self {
|
ct.guns.push(Self {
|
||||||
name: gun_name,
|
name: gun_name,
|
||||||
space: gun.space.into(),
|
space: gun.space.into(),
|
||||||
rate: gun.rate,
|
rate: gun.rate,
|
||||||
|
@ -171,8 +238,8 @@ impl crate::Build for Gun {
|
||||||
lifetime_rng: gun.projectile.lifetime_rng,
|
lifetime_rng: gun.projectile.lifetime_rng,
|
||||||
damage: gun.projectile.damage,
|
damage: gun.projectile.damage,
|
||||||
angle_rng: Deg(gun.projectile.angle_rng),
|
angle_rng: Deg(gun.projectile.angle_rng),
|
||||||
impact_effect,
|
impact_particle,
|
||||||
expire_effect,
|
expire_particle,
|
||||||
collider: gun.projectile.collider,
|
collider: gun.projectile.collider,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
//! Content parts
|
//! Content parts
|
||||||
|
|
||||||
pub(crate) mod effect;
|
pub mod faction;
|
||||||
pub(crate) mod faction;
|
pub mod gun;
|
||||||
pub(crate) mod gun;
|
pub mod outfit;
|
||||||
pub(crate) mod outfit;
|
mod shared;
|
||||||
pub(crate) mod outfitspace;
|
pub mod ship;
|
||||||
pub(crate) mod ship;
|
pub mod sprite;
|
||||||
pub(crate) mod sprite;
|
pub mod system;
|
||||||
pub(crate) mod system;
|
|
||||||
|
|
||||||
pub use effect::{Effect, ImpactInheritVelocity};
|
|
||||||
pub use faction::{Faction, Relationship};
|
pub use faction::{Faction, Relationship};
|
||||||
pub use gun::{Gun, Projectile, ProjectileCollider};
|
pub use gun::{Gun, ImpactInheritVelocity, Projectile, ProjectileCollider, ProjectileParticle};
|
||||||
pub use outfit::Outfit;
|
pub use outfit::Outfit;
|
||||||
pub use outfitspace::OutfitSpace;
|
pub use shared::OutfitSpace;
|
||||||
pub use ship::{
|
pub use ship::{EnginePoint, GunPoint, Ship};
|
||||||
CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship,
|
|
||||||
ShipCollapse,
|
|
||||||
};
|
|
||||||
pub use sprite::{RepeatMode, Sprite};
|
pub use sprite::{RepeatMode, Sprite};
|
||||||
pub use system::{Object, System};
|
pub use system::{Object, System};
|
||||||
|
|
|
@ -2,10 +2,10 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, OutfitSpace};
|
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::part::outfitspace;
|
use crate::part::shared;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
// 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.
|
||||||
|
@ -14,7 +14,7 @@ pub(crate) mod syntax {
|
||||||
pub struct Outfit {
|
pub struct Outfit {
|
||||||
pub engine: Option<Engine>,
|
pub engine: Option<Engine>,
|
||||||
pub steering: Option<Steering>,
|
pub steering: Option<Steering>,
|
||||||
pub space: outfitspace::syntax::OutfitSpace,
|
pub space: shared::syntax::OutfitSpace,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -53,11 +53,7 @@ pub struct Outfit {
|
||||||
impl crate::Build for Outfit {
|
impl crate::Build for Outfit {
|
||||||
type InputSyntaxType = HashMap<String, syntax::Outfit>;
|
type InputSyntaxType = HashMap<String, syntax::Outfit>;
|
||||||
|
|
||||||
fn build(
|
fn build(outfits: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
outfits: Self::InputSyntaxType,
|
|
||||||
_build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
for (outfit_name, outfit) in outfits {
|
for (outfit_name, outfit) in outfits {
|
||||||
let mut o = Self {
|
let mut o = Self {
|
||||||
name: outfit_name.clone(),
|
name: outfit_name.clone(),
|
||||||
|
@ -69,7 +65,7 @@ impl crate::Build for Outfit {
|
||||||
|
|
||||||
// Engine stats
|
// Engine stats
|
||||||
if let Some(engine) = outfit.engine {
|
if let Some(engine) = outfit.engine {
|
||||||
let th = match content.sprite_index.get(&engine.flare_sprite) {
|
let th = match ct.sprite_index.get(&engine.flare_sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In outfit `{}`: flare sprite `{}` doesn't exist",
|
"In outfit `{}`: flare sprite `{}` doesn't exist",
|
||||||
outfit_name,
|
outfit_name,
|
||||||
|
@ -86,7 +82,7 @@ impl crate::Build for Outfit {
|
||||||
o.steer_power = steer.power;
|
o.steer_power = steer.power;
|
||||||
}
|
}
|
||||||
|
|
||||||
content.outfits.push(o);
|
ct.outfits.push(o);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Result};
|
||||||
use cgmath::Point2;
|
use cgmath::Point2;
|
||||||
use nalgebra::{point, Point};
|
use nalgebra::{point, Point};
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitSpace};
|
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::part::{effect::syntax::EffectReference, outfitspace};
|
use crate::part::shared;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
// Raw serde syntax structs.
|
// Raw serde syntax structs.
|
||||||
|
@ -21,11 +21,16 @@ pub(crate) mod syntax {
|
||||||
pub guns: Vec<Gun>,
|
pub guns: Vec<Gun>,
|
||||||
pub hull: f32,
|
pub hull: f32,
|
||||||
pub mass: f32,
|
pub mass: f32,
|
||||||
pub collision: Vec<[f32; 2]>,
|
pub collision: Collision,
|
||||||
pub angular_drag: f32,
|
pub angular_drag: f32,
|
||||||
pub linear_drag: f32,
|
pub linear_drag: f32,
|
||||||
pub space: outfitspace::syntax::OutfitSpace,
|
pub space: shared::syntax::OutfitSpace,
|
||||||
pub collapse: Option<Collapse>,
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Collision {
|
||||||
|
pub points: Vec<[f32; 2]>,
|
||||||
|
pub indices: Vec<[u32; 2]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -40,34 +45,6 @@ pub(crate) mod syntax {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// plural or not? document!
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Collapse {
|
|
||||||
pub length: f32,
|
|
||||||
pub effects: Vec<CollapseEffectSpawner>,
|
|
||||||
pub event: Vec<CollapseEvent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct CollapseEffectSpawner {
|
|
||||||
pub effect: EffectReference,
|
|
||||||
pub count: f32,
|
|
||||||
pub pos: Option<[f32; 2]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum CollapseEvent {
|
|
||||||
Effect(EffectCollapseEvent),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct EffectCollapseEvent {
|
|
||||||
pub time: f32,
|
|
||||||
pub effects: Vec<CollapseEffectSpawner>,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Processed data structs.
|
// Processed data structs.
|
||||||
|
@ -115,9 +92,6 @@ pub struct Ship {
|
||||||
|
|
||||||
/// Outfit space in this ship
|
/// Outfit space in this ship
|
||||||
pub space: OutfitSpace,
|
pub space: OutfitSpace,
|
||||||
|
|
||||||
/// Ship collapse sequence
|
|
||||||
pub collapse: ShipCollapse,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collision shape for this ship
|
/// Collision shape for this ship
|
||||||
|
@ -148,58 +122,10 @@ pub struct GunPoint {
|
||||||
pub pos: Point2<f32>,
|
pub pos: Point2<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parameters for a ship's collapse sequence
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ShipCollapse {
|
|
||||||
/// Collapse sequence length, in seconds
|
|
||||||
pub length: f32,
|
|
||||||
|
|
||||||
/// Effects to create during collapse
|
|
||||||
pub effects: Vec<CollapseEffectSpawner>,
|
|
||||||
|
|
||||||
/// Scripted events during ship collapse
|
|
||||||
pub events: Vec<CollapseEvent>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scripted event during a ship collapse sequence
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct CollapseEffectSpawner {
|
|
||||||
/// The effect to create
|
|
||||||
pub effect: EffectHandle,
|
|
||||||
|
|
||||||
/// How many effects to create
|
|
||||||
pub count: f32,
|
|
||||||
|
|
||||||
/// Where to create these effects.
|
|
||||||
/// Position is random if None.
|
|
||||||
pub pos: Option<Point2<f32>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scripted event during a ship collapse sequence
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum CollapseEvent {
|
|
||||||
/// A scripted effect during a ship collapse sequence
|
|
||||||
Effect(EffectCollapseEvent),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scripted effect during a ship collapse sequence
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct EffectCollapseEvent {
|
|
||||||
/// When to trigger this event
|
|
||||||
pub time: f32,
|
|
||||||
|
|
||||||
/// The effect to create
|
|
||||||
pub effects: Vec<CollapseEffectSpawner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::Build for Ship {
|
impl crate::Build for Ship {
|
||||||
type InputSyntaxType = HashMap<String, syntax::Ship>;
|
type InputSyntaxType = HashMap<String, syntax::Ship>;
|
||||||
|
|
||||||
fn build(
|
fn build(ship: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
ship: Self::InputSyntaxType,
|
|
||||||
build_context: &mut ContentBuildContext,
|
|
||||||
ct: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
for (ship_name, ship) in ship {
|
for (ship_name, ship) in ship {
|
||||||
let handle = match ct.sprite_index.get(&ship.sprite) {
|
let handle = match ct.sprite_index.get(&ship.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
|
@ -213,65 +139,8 @@ impl crate::Build for Ship {
|
||||||
let size = ship.size;
|
let size = ship.size;
|
||||||
let aspect = ct.get_sprite(handle).aspect;
|
let aspect = ct.get_sprite(handle).aspect;
|
||||||
|
|
||||||
let collapse = if let Some(c) = ship.collapse {
|
|
||||||
let mut effects = Vec::new();
|
|
||||||
for e in c.effects {
|
|
||||||
effects.push(CollapseEffectSpawner {
|
|
||||||
effect: e
|
|
||||||
.effect
|
|
||||||
.to_handle(build_context, ct)
|
|
||||||
.with_context(|| format!("while loading ship `{}`", ship_name))?,
|
|
||||||
count: e.count,
|
|
||||||
pos: e.pos.map(|p| Point2 {
|
|
||||||
x: p[0] * (size / 2.0) * aspect,
|
|
||||||
y: p[1] * size / 2.0,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut events = Vec::new();
|
|
||||||
for e in c.event {
|
|
||||||
match e {
|
|
||||||
syntax::CollapseEvent::Effect(e) => {
|
|
||||||
let mut effects = Vec::new();
|
|
||||||
for g in e.effects {
|
|
||||||
effects.push(CollapseEffectSpawner {
|
|
||||||
effect: g.effect.to_handle(build_context, ct).with_context(
|
|
||||||
|| format!("while loading ship `{}`", ship_name),
|
|
||||||
)?,
|
|
||||||
count: g.count,
|
|
||||||
pos: g.pos.map(|p| Point2 {
|
|
||||||
x: p[0] * (size / 2.0) * aspect,
|
|
||||||
y: p[1] * size / 2.0,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
events.push(CollapseEvent::Effect(EffectCollapseEvent {
|
|
||||||
time: e.time,
|
|
||||||
effects,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipCollapse {
|
|
||||||
length: c.length,
|
|
||||||
effects,
|
|
||||||
events,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Default collapse sequence
|
|
||||||
ShipCollapse {
|
|
||||||
length: 0.0,
|
|
||||||
effects: vec![],
|
|
||||||
events: vec![],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ct.ships.push(Self {
|
ct.ships.push(Self {
|
||||||
aspect,
|
aspect,
|
||||||
collapse,
|
|
||||||
name: ship_name,
|
name: ship_name,
|
||||||
sprite: handle,
|
sprite: handle,
|
||||||
mass: ship.mass,
|
mass: ship.mass,
|
||||||
|
@ -280,7 +149,6 @@ impl crate::Build for Ship {
|
||||||
linear_drag: ship.linear_drag,
|
linear_drag: ship.linear_drag,
|
||||||
size,
|
size,
|
||||||
hull: ship.hull,
|
hull: ship.hull,
|
||||||
|
|
||||||
engines: ship
|
engines: ship
|
||||||
.engines
|
.engines
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -292,7 +160,6 @@ impl crate::Build for Ship {
|
||||||
size: e.size,
|
size: e.size,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
||||||
guns: ship
|
guns: ship
|
||||||
.guns
|
.guns
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -303,22 +170,11 @@ impl crate::Build for Ship {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
||||||
collision: Collision {
|
collision: Collision {
|
||||||
indices: (0..ship.collision.len())
|
indices: ship.collision.indices,
|
||||||
.map(|x| {
|
|
||||||
// Auto-generate mesh lines:
|
|
||||||
// [ [0, 1], [1, 2], ..., [n, 0] ]
|
|
||||||
let next = if x == ship.collision.len() - 1 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
x + 1
|
|
||||||
};
|
|
||||||
[x as u32, next as u32]
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
points: ship
|
points: ship
|
||||||
.collision
|
.collision
|
||||||
|
.points
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| point![x[0] * (size / 2.0) * aspect, x[1] * size / 2.0])
|
.map(|x| point![x[0] * (size / 2.0) * aspect, x[1] * size / 2.0])
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
|
@ -3,7 +3,7 @@ use image::io::Reader;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
use crate::{handle::SpriteHandle, Content};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -29,18 +29,8 @@ pub(crate) mod syntax {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct FrameSprite {
|
pub struct FrameSprite {
|
||||||
pub frames: Vec<PathBuf>,
|
pub frames: Vec<PathBuf>,
|
||||||
pub timing: Timing,
|
pub duration: f32,
|
||||||
pub repeat: RepeatMode,
|
pub repeat: RepeatMode,
|
||||||
pub random_start_frame: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub enum Timing {
|
|
||||||
#[serde(rename = "duration")]
|
|
||||||
Duration(f32),
|
|
||||||
|
|
||||||
#[serde(rename = "fps")]
|
|
||||||
Fps(f32),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,23 +84,16 @@ pub struct Sprite {
|
||||||
|
|
||||||
/// Aspect ratio of this sprite (width / height)
|
/// Aspect ratio of this sprite (width / height)
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
|
|
||||||
/// If true, start on a random frame of this sprite.
|
|
||||||
pub random_start_frame: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Sprite {
|
impl crate::Build for Sprite {
|
||||||
type InputSyntaxType = HashMap<String, syntax::Sprite>;
|
type InputSyntaxType = HashMap<String, syntax::Sprite>;
|
||||||
|
|
||||||
fn build(
|
fn build(sprites: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
sprites: Self::InputSyntaxType,
|
|
||||||
_build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
for (sprite_name, t) in sprites {
|
for (sprite_name, t) in sprites {
|
||||||
match t {
|
match t {
|
||||||
syntax::Sprite::Static(t) => {
|
syntax::Sprite::Static(t) => {
|
||||||
let file = content.image_root.join(&t.file);
|
let file = ct.image_root.join(&t.file);
|
||||||
let reader = Reader::open(&file).with_context(|| {
|
let reader = Reader::open(&file).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to read file `{}` in sprite `{}`",
|
"Failed to read file `{}` in sprite `{}`",
|
||||||
|
@ -127,35 +110,34 @@ impl crate::Build for Sprite {
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let h = SpriteHandle {
|
let h = SpriteHandle {
|
||||||
index: content.sprites.len(),
|
index: ct.sprites.len() as u32,
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
if sprite_name == content.starfield_sprite_name {
|
if sprite_name == ct.starfield_sprite_name {
|
||||||
if content.starfield_handle.is_none() {
|
if ct.starfield_handle.is_none() {
|
||||||
content.starfield_handle = Some(h)
|
ct.starfield_handle = Some(h)
|
||||||
} else {
|
} else {
|
||||||
// This can't happen, since this is a hashmap.
|
// This can't happen, since this is a hashmap.
|
||||||
unreachable!("Found two starfield sprites! Something is very wrong.")
|
unreachable!("Found two starfield sprites! Something is very wrong.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
content.sprite_index.insert(sprite_name.clone(), h);
|
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||||
|
|
||||||
content.sprites.push(Self {
|
ct.sprites.push(Self {
|
||||||
name: sprite_name,
|
name: sprite_name,
|
||||||
frames: vec![t.file],
|
frames: vec![t.file],
|
||||||
fps: 0.0,
|
fps: 0.0,
|
||||||
handle: h,
|
handle: h,
|
||||||
repeat: RepeatMode::Once,
|
repeat: RepeatMode::Once,
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
random_start_frame: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
syntax::Sprite::Frames(t) => {
|
syntax::Sprite::Frames(t) => {
|
||||||
let mut dim = None;
|
let mut dim = None;
|
||||||
for f in &t.frames {
|
for f in &t.frames {
|
||||||
let file = content.image_root.join(f);
|
let file = ct.image_root.join(f);
|
||||||
let reader = Reader::open(&file).with_context(|| {
|
let reader = Reader::open(&file).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to read file `{}` in sprite `{}`",
|
"Failed to read file `{}` in sprite `{}`",
|
||||||
|
@ -185,37 +167,33 @@ impl crate::Build for Sprite {
|
||||||
let dim = dim.unwrap();
|
let dim = dim.unwrap();
|
||||||
|
|
||||||
let h = SpriteHandle {
|
let h = SpriteHandle {
|
||||||
index: content.sprites.len(),
|
index: ct.sprites.len() as u32,
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
if sprite_name == content.starfield_sprite_name {
|
if sprite_name == ct.starfield_sprite_name {
|
||||||
unreachable!("Starfield texture may not be animated")
|
unreachable!("Starfield texture may not be animated")
|
||||||
}
|
}
|
||||||
|
|
||||||
let fps = match t.timing {
|
let fps = t.duration / t.frames.len() as f32;
|
||||||
syntax::Timing::Duration(d) => d / t.frames.len() as f32,
|
|
||||||
syntax::Timing::Fps(f) => 1.0 / f,
|
|
||||||
};
|
|
||||||
|
|
||||||
content.sprite_index.insert(sprite_name.clone(), h);
|
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||||
content.sprites.push(Self {
|
ct.sprites.push(Self {
|
||||||
name: sprite_name,
|
name: sprite_name,
|
||||||
frames: t.frames,
|
frames: t.frames,
|
||||||
fps,
|
fps,
|
||||||
handle: h,
|
handle: h,
|
||||||
repeat: t.repeat,
|
repeat: t.repeat,
|
||||||
aspect: dim.0 as f32 / dim.1 as f32,
|
aspect: dim.0 as f32 / dim.1 as f32,
|
||||||
random_start_frame: t.random_start_frame.unwrap_or(false),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if content.starfield_handle.is_none() {
|
if ct.starfield_handle.is_none() {
|
||||||
bail!(
|
bail!(
|
||||||
"Could not find a starfield texture (name: `{}`)",
|
"Could not find a starfield texture (name: `{}`)",
|
||||||
content.starfield_sprite_name
|
ct.starfield_sprite_name
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{bail, Context, Result};
|
||||||
use cgmath::{Deg, Point3};
|
use cgmath::{Deg, Point3};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, util::Polar, Content, ContentBuildContext};
|
use crate::{handle::SpriteHandle, util::Polar, Content};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -177,11 +177,7 @@ fn resolve_position(
|
||||||
impl crate::Build for System {
|
impl crate::Build for System {
|
||||||
type InputSyntaxType = HashMap<String, syntax::System>;
|
type InputSyntaxType = HashMap<String, syntax::System>;
|
||||||
|
|
||||||
fn build(
|
fn build(system: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||||
system: Self::InputSyntaxType,
|
|
||||||
_build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
for (system_name, system) in system {
|
for (system_name, system) in system {
|
||||||
let mut objects = Vec::new();
|
let mut objects = Vec::new();
|
||||||
|
|
||||||
|
@ -189,7 +185,7 @@ impl crate::Build for System {
|
||||||
let mut cycle_detector = HashSet::new();
|
let mut cycle_detector = HashSet::new();
|
||||||
cycle_detector.insert(label.clone());
|
cycle_detector.insert(label.clone());
|
||||||
|
|
||||||
let handle = match content.sprite_index.get(&obj.sprite) {
|
let handle = match ct.sprite_index.get(&obj.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In system `{}`: sprite `{}` doesn't exist",
|
"In system `{}`: sprite `{}` doesn't exist",
|
||||||
system_name,
|
system_name,
|
||||||
|
@ -207,7 +203,7 @@ impl crate::Build for System {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
content.systems.push(Self {
|
ct.systems.push(Self {
|
||||||
name: system_name,
|
name: system_name,
|
||||||
objects,
|
objects,
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,7 +41,6 @@ impl Game {
|
||||||
o1.add(&ct, content::OutfitHandle { index: 0 });
|
o1.add(&ct, content::OutfitHandle { index: 0 });
|
||||||
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
|
||||||
|
|
||||||
let s = object::Ship::new(
|
let s = object::Ship::new(
|
||||||
&ct,
|
&ct,
|
||||||
|
@ -58,14 +57,14 @@ impl Game {
|
||||||
// This method of specifying factions is non-deterministic,
|
// This method of specifying factions is non-deterministic,
|
||||||
// but that's ok since this is for debug.
|
// but that's ok since this is for debug.
|
||||||
// TODO: fix
|
// TODO: fix
|
||||||
content::FactionHandle { index: 1 },
|
content::FactionHandle { index: 0 },
|
||||||
object::OutfitSet::new(ss),
|
object::OutfitSet::new(ss),
|
||||||
);
|
);
|
||||||
let h2 = physics.add_ship(&ct, s, Point2 { x: 300.0, y: 300.0 });
|
let h2 = physics.add_ship(&ct, s, Point2 { x: 300.0, y: 300.0 });
|
||||||
|
|
||||||
let mut o1 = object::OutfitSet::new(ss);
|
let mut o1 = object::OutfitSet::new(ss);
|
||||||
o1.add(&ct, content::OutfitHandle { index: 0 });
|
o1.add(&ct, content::OutfitHandle { index: 0 });
|
||||||
//o1.add_gun(&ct, content::GunHandle { index: 0 });
|
o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
|
|
||||||
let s = object::Ship::new(
|
let s = object::Ship::new(
|
||||||
&ct,
|
&ct,
|
||||||
|
|
|
@ -30,7 +30,8 @@ impl Ship {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_dead(&self) -> bool {
|
/// Has this ship been destroyed?
|
||||||
|
pub fn is_destroyed(&self) -> bool {
|
||||||
self.hull <= 0.0
|
self.hull <= 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +42,8 @@ impl Ship {
|
||||||
let r = f.relationships.get(&p.faction).unwrap();
|
let r = f.relationships.get(&p.faction).unwrap();
|
||||||
match r {
|
match r {
|
||||||
content::Relationship::Hostile => {
|
content::Relationship::Hostile => {
|
||||||
if self.is_dead() {
|
// TODO: implement death and spawning, and enable damage
|
||||||
return true;
|
//s.hull -= p.damage;
|
||||||
}
|
|
||||||
self.hull -= p.content.damage;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
_ => return false,
|
_ => return false,
|
||||||
|
@ -52,10 +51,6 @@ impl Ship {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fire_guns(&mut self) -> Vec<(Projectile, content::GunPoint)> {
|
pub fn fire_guns(&mut self) -> Vec<(Projectile, content::GunPoint)> {
|
||||||
if self.is_dead() {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
self.outfits
|
self.outfits
|
||||||
.iter_guns_points()
|
.iter_guns_points()
|
||||||
.filter(|(g, _)| g.cooldown <= 0.0)
|
.filter(|(g, _)| g.cooldown <= 0.0)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||||
use image::{imageops, GenericImageView, ImageBuffer, Rgba, RgbaImage};
|
use image::{imageops, ImageBuffer, Rgba, RgbaImage};
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{Read, Write},
|
io::{Read, Write},
|
||||||
|
@ -51,9 +51,6 @@ pub struct AtlasSet {
|
||||||
/// The root directory that contains all image files.
|
/// The root directory that contains all image files.
|
||||||
/// Files outside this directory will not be packed.
|
/// Files outside this directory will not be packed.
|
||||||
asset_root: PathBuf,
|
asset_root: PathBuf,
|
||||||
|
|
||||||
/// Leave an empty border this many pixels wide around each image
|
|
||||||
image_margin: u32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AtlasSet {
|
impl AtlasSet {
|
||||||
|
@ -62,7 +59,6 @@ impl AtlasSet {
|
||||||
texture_height: u32,
|
texture_height: u32,
|
||||||
texture_limit: usize,
|
texture_limit: usize,
|
||||||
asset_root: &Path,
|
asset_root: &Path,
|
||||||
image_margin: u32,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
asset_root: asset_root.to_path_buf(),
|
asset_root: asset_root.to_path_buf(),
|
||||||
|
@ -75,12 +71,11 @@ impl AtlasSet {
|
||||||
index: SpriteAtlas::new(),
|
index: SpriteAtlas::new(),
|
||||||
used_area: 0f64,
|
used_area: 0f64,
|
||||||
image_y_start: Vec::new(),
|
image_y_start: Vec::new(),
|
||||||
image_margin,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a sprite to this atlas set
|
/// Add a sprite to this atlas set
|
||||||
pub fn write_image(&mut self, path: &Path) -> Result<usize> {
|
pub fn write_image(&mut self, path: &Path, dim: [u32; 2]) -> Result<usize> {
|
||||||
let mut f = File::open(&path)?;
|
let mut f = File::open(&path)?;
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
f.read_to_end(&mut bytes)
|
f.read_to_end(&mut bytes)
|
||||||
|
@ -88,13 +83,6 @@ impl AtlasSet {
|
||||||
let img = image::load_from_memory(&bytes)
|
let img = image::load_from_memory(&bytes)
|
||||||
.with_context(|| format!("While loading file `{}`", path.display()))?;
|
.with_context(|| format!("While loading file `{}`", path.display()))?;
|
||||||
|
|
||||||
let image_dim = img.dimensions();
|
|
||||||
|
|
||||||
let dim = [
|
|
||||||
image_dim.0 + 2 * self.image_margin,
|
|
||||||
image_dim.1 + 2 * self.image_margin,
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut x = 0;
|
let mut x = 0;
|
||||||
let mut y = 0;
|
let mut y = 0;
|
||||||
let mut final_atlas_idx = None;
|
let mut final_atlas_idx = None;
|
||||||
|
@ -125,6 +113,8 @@ impl AtlasSet {
|
||||||
if dim[0] >= sd[0] || dim[1] >= sd[1] {
|
if dim[0] >= sd[0] || dim[1] >= sd[1] {
|
||||||
y = sy;
|
y = sy;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.image_y_start.push((0, [u32::MAX, u32::MAX]));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut free = false;
|
let mut free = false;
|
||||||
|
@ -166,12 +156,8 @@ impl AtlasSet {
|
||||||
};
|
};
|
||||||
|
|
||||||
// We found a spot for this image, write it.
|
// We found a spot for this image, write it.
|
||||||
imageops::overlay(
|
//let img = RgbaImage::from_pixel(dim[0], dim[1], Rgba([0, 0, 0, 255]));
|
||||||
&mut self.texture_list[atlas_idx],
|
imageops::overlay(&mut self.texture_list[atlas_idx], &img, x.into(), y.into());
|
||||||
&img,
|
|
||||||
(x + self.image_margin).into(),
|
|
||||||
(y + self.image_margin).into(),
|
|
||||||
);
|
|
||||||
self.used_regions[atlas_idx].push(([x, y], dim));
|
self.used_regions[atlas_idx].push(([x, y], dim));
|
||||||
self.used_area += dim[0] as f64 * dim[1] as f64;
|
self.used_area += dim[0] as f64 * dim[1] as f64;
|
||||||
|
|
||||||
|
@ -196,10 +182,10 @@ impl AtlasSet {
|
||||||
p.to_path_buf(),
|
p.to_path_buf(),
|
||||||
SpriteAtlasImage {
|
SpriteAtlasImage {
|
||||||
atlas: atlas_idx as u32,
|
atlas: atlas_idx as u32,
|
||||||
x: (x + self.image_margin) as f32 / self.texture_width as f32,
|
x: x as f32 / self.texture_width as f32,
|
||||||
y: (y + self.image_margin) as f32 / self.texture_height as f32,
|
y: y as f32 / self.texture_height as f32,
|
||||||
w: image_dim.0 as f32 / self.texture_width as f32,
|
w: dim[0] as f32 / self.texture_width as f32,
|
||||||
h: image_dim.1 as f32 / self.texture_height as f32,
|
h: dim[1] as f32 / self.texture_height as f32,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -64,13 +64,13 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create atlas set
|
// Create atlas set
|
||||||
let mut atlas_set = AtlasSet::new(8192, 8192, 16, &asset_root, 2);
|
let mut atlas_set = AtlasSet::new(8192, 8192, 16, &asset_root);
|
||||||
let total = files.len();
|
let total = files.len();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut peak_efficiency = 0f64;
|
let mut peak_efficiency = 0f64;
|
||||||
for (path, _) in files {
|
for (path, dim) in files {
|
||||||
i += 1;
|
i += 1;
|
||||||
let atlas_idx = atlas_set.write_image(&path)?;
|
let atlas_idx = atlas_set.write_image(&path, dim)?;
|
||||||
println!(
|
println!(
|
||||||
"({i} of {total}, atlas {atlas_idx}, efficiency {:.02}%) Added {}",
|
"({i} of {total}, atlas {atlas_idx}, efficiency {:.02}%) Added {}",
|
||||||
100.0 * atlas_set.get_efficiency(),
|
100.0 * atlas_set.get_efficiency(),
|
||||||
|
|
|
@ -11,11 +11,11 @@ fn animate(instance: InstanceInput, age: f32) -> u32 {
|
||||||
var frame: u32 = u32(0);
|
var frame: u32 = u32(0);
|
||||||
|
|
||||||
|
|
||||||
// Once
|
// Repeat
|
||||||
if rep == u32(1) {
|
if rep == u32(1) {
|
||||||
|
|
||||||
frame = u32(min(
|
frame = u32(min(
|
||||||
age / fps,
|
(age / fps),
|
||||||
f32(len) - 1.0
|
f32(len) - 1.0
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ use anyhow::Result;
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use cgmath::{Deg, EuclideanSpace, Matrix2, Matrix4, Point2, Vector3};
|
use cgmath::{Deg, EuclideanSpace, Matrix2, Matrix4, Point2, Vector3};
|
||||||
use galactica_constants;
|
use galactica_constants;
|
||||||
use rand::seq::SliceRandom;
|
|
||||||
use std::{iter, rc::Rc};
|
use std::{iter, rc::Rc};
|
||||||
use wgpu;
|
use wgpu;
|
||||||
use winit::{self, dpi::LogicalSize, window::Window};
|
use winit::{self, dpi::LogicalSize, window::Window};
|
||||||
|
@ -381,7 +380,7 @@ impl GPUState {
|
||||||
|
|
||||||
instances.push(ObjectInstance {
|
instances.push(ObjectInstance {
|
||||||
transform: t.into(),
|
transform: t.into(),
|
||||||
sprite_index: s.sprite.get_index(),
|
sprite_index: s.sprite.index,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add children
|
// Add children
|
||||||
|
@ -430,7 +429,7 @@ impl GPUState {
|
||||||
|
|
||||||
instances.push(ObjectInstance {
|
instances.push(ObjectInstance {
|
||||||
transform: t.into(),
|
transform: t.into(),
|
||||||
sprite_index: s.sprite.get_index(),
|
sprite_index: s.sprite.index,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -482,7 +481,7 @@ impl GPUState {
|
||||||
|
|
||||||
instances.push(UiInstance {
|
instances.push(UiInstance {
|
||||||
transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(),
|
transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(),
|
||||||
sprite_index: s.sprite.get_index(),
|
sprite_index: s.sprite.index,
|
||||||
color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]),
|
color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -614,7 +613,7 @@ impl GPUState {
|
||||||
self.window_size.height as f32,
|
self.window_size.height as f32,
|
||||||
],
|
],
|
||||||
window_aspect: [self.window_aspect, 0.0],
|
window_aspect: [self.window_aspect, 0.0],
|
||||||
starfield_sprite: [s.get_index(), 0],
|
starfield_sprite: [s.index, 0],
|
||||||
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
||||||
starfield_size_limits: [
|
starfield_size_limits: [
|
||||||
galactica_constants::STARFIELD_SIZE_MIN,
|
galactica_constants::STARFIELD_SIZE_MIN,
|
||||||
|
@ -625,7 +624,6 @@ impl GPUState {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write all new particles to GPU buffer
|
// Write all new particles to GPU buffer
|
||||||
state.new_particles.shuffle(&mut rand::thread_rng());
|
|
||||||
for i in state.new_particles.iter() {
|
for i in state.new_particles.iter() {
|
||||||
self.queue.write_buffer(
|
self.queue.write_buffer(
|
||||||
&self.vertex_buffers.particle.instances,
|
&self.vertex_buffers.particle.instances,
|
||||||
|
@ -635,7 +633,7 @@ impl GPUState {
|
||||||
velocity: i.velocity.into(),
|
velocity: i.velocity.into(),
|
||||||
rotation: Matrix2::from_angle(i.angle).into(),
|
rotation: Matrix2::from_angle(i.angle).into(),
|
||||||
size: i.size,
|
size: i.size,
|
||||||
sprite_index: i.sprite.get_index(),
|
sprite_index: i.sprite.index,
|
||||||
created: state.current_time,
|
created: state.current_time,
|
||||||
expires: state.current_time + i.lifetime,
|
expires: state.current_time + i.lifetime,
|
||||||
}]),
|
}]),
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
|
use cgmath::{Deg, InnerSpace, Vector2};
|
||||||
use nalgebra::{point, vector};
|
use nalgebra::vector;
|
||||||
|
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
use rapier2d::dynamics::RigidBody;
|
||||||
use rapier2d::{dynamics::RigidBody, geometry::Collider};
|
|
||||||
|
|
||||||
use crate::{util, ShipPhysicsHandle};
|
use crate::{util, ShipPhysicsHandle};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
use galactica_render::{ObjectSprite, ParticleBuilder};
|
use galactica_render::ObjectSprite;
|
||||||
|
|
||||||
pub struct ShipControls {
|
pub struct ShipControls {
|
||||||
pub left: bool,
|
pub left: bool,
|
||||||
|
@ -27,141 +26,6 @@ impl ShipControls {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ShipCollapseSequence {
|
|
||||||
total_length: f32,
|
|
||||||
time_elapsed: f32,
|
|
||||||
rng: ThreadRng,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipCollapseSequence {
|
|
||||||
fn new(total_length: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
total_length: total_length,
|
|
||||||
time_elapsed: 0.0,
|
|
||||||
rng: rand::thread_rng(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_done(&self) -> bool {
|
|
||||||
self.time_elapsed >= self.total_length
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_in_ship(
|
|
||||||
&mut self,
|
|
||||||
ship_content: &content::Ship,
|
|
||||||
collider: &Collider,
|
|
||||||
) -> Vector2<f32> {
|
|
||||||
// Pick a random point inside this ship's collider
|
|
||||||
let mut y = 0.0;
|
|
||||||
let mut x = 0.0;
|
|
||||||
let mut a = false;
|
|
||||||
while !a {
|
|
||||||
y = self.rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0;
|
|
||||||
x = self.rng.gen_range(-1.0..=1.0) * ship_content.size * ship_content.sprite.aspect
|
|
||||||
/ 2.0;
|
|
||||||
a = collider.shape().contains_local_point(&point![x, y]);
|
|
||||||
}
|
|
||||||
Vector2 { x, y }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step(
|
|
||||||
&mut self,
|
|
||||||
ship: &object::Ship,
|
|
||||||
ct: &content::Content,
|
|
||||||
particles: &mut Vec<ParticleBuilder>,
|
|
||||||
rigid_body: &mut RigidBody,
|
|
||||||
collider: &mut Collider,
|
|
||||||
t: f32,
|
|
||||||
) {
|
|
||||||
let h = ship.handle;
|
|
||||||
let ship_content = ct.get_ship(h);
|
|
||||||
let ship_pos = util::rigidbody_position(rigid_body);
|
|
||||||
let ship_rot = util::rigidbody_rotation(rigid_body);
|
|
||||||
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
|
|
||||||
|
|
||||||
// The fraction of this collapse sequence that has been played
|
|
||||||
let frac_done = self.time_elapsed / self.total_length;
|
|
||||||
|
|
||||||
// Trigger collapse events
|
|
||||||
for event in &ship_content.collapse.events {
|
|
||||||
match event {
|
|
||||||
content::CollapseEvent::Effect(event) => {
|
|
||||||
if (event.time > self.time_elapsed && event.time <= self.time_elapsed + t)
|
|
||||||
|| (event.time == 0.0 && self.time_elapsed == 0.0)
|
|
||||||
{
|
|
||||||
for spawner in &event.effects {
|
|
||||||
let effect = ct.get_effect(spawner.effect);
|
|
||||||
|
|
||||||
for _ in 0..spawner.count as usize {
|
|
||||||
let pos = if let Some(pos) = spawner.pos {
|
|
||||||
pos.to_vec()
|
|
||||||
} else {
|
|
||||||
self.random_in_ship(ship_content, collider)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Position, adjusted for ship rotation
|
|
||||||
let pos =
|
|
||||||
Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
|
|
||||||
|
|
||||||
particles.push(ParticleBuilder {
|
|
||||||
sprite: effect.sprite,
|
|
||||||
pos: ship_pos + pos,
|
|
||||||
velocity: Vector2::zero(),
|
|
||||||
angle: Deg::zero(),
|
|
||||||
lifetime: effect.lifetime,
|
|
||||||
size: effect.size,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create collapse effects
|
|
||||||
for spawner in &ship_content.collapse.effects {
|
|
||||||
let effect = ct.get_effect(spawner.effect);
|
|
||||||
|
|
||||||
// Probability of adding a particle this frame.
|
|
||||||
// The area of this function over [0, 1] should be 1.
|
|
||||||
let pdf = |x: f32| {
|
|
||||||
let f = 0.2;
|
|
||||||
let y = if x < (1.0 - f) {
|
|
||||||
let x = x / (1.0 - f);
|
|
||||||
(x * x + 0.2) * 1.8 - f
|
|
||||||
} else {
|
|
||||||
1.0
|
|
||||||
};
|
|
||||||
return y;
|
|
||||||
};
|
|
||||||
|
|
||||||
let p_add = (t / self.total_length) * pdf(frac_done) * spawner.count;
|
|
||||||
|
|
||||||
if self.rng.gen_range(0.0..=1.0) <= p_add {
|
|
||||||
let pos = if let Some(pos) = spawner.pos {
|
|
||||||
pos.to_vec()
|
|
||||||
} else {
|
|
||||||
self.random_in_ship(ship_content, collider)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Position, adjusted for ship rotation
|
|
||||||
let pos = Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
|
|
||||||
|
|
||||||
particles.push(ParticleBuilder {
|
|
||||||
sprite: effect.sprite,
|
|
||||||
pos: ship_pos + pos,
|
|
||||||
velocity: Vector2::zero(),
|
|
||||||
angle: Deg::zero(),
|
|
||||||
lifetime: effect.lifetime,
|
|
||||||
size: effect.size,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.time_elapsed += t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A ship instance in the physics system
|
/// A ship instance in the physics system
|
||||||
pub struct ShipWorldObject {
|
pub struct ShipWorldObject {
|
||||||
/// This ship's physics handle
|
/// This ship's physics handle
|
||||||
|
@ -172,46 +36,20 @@ pub struct ShipWorldObject {
|
||||||
|
|
||||||
/// This ship's controls
|
/// This ship's controls
|
||||||
pub controls: ShipControls,
|
pub controls: ShipControls,
|
||||||
|
|
||||||
collapse_sequence: ShipCollapseSequence,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ShipWorldObject {
|
impl ShipWorldObject {
|
||||||
/// Make a new ship
|
/// Make a new ship
|
||||||
pub fn new(
|
pub fn new(ship: object::Ship, physics_handle: ShipPhysicsHandle) -> Self {
|
||||||
ct: &content::Content,
|
|
||||||
ship: object::Ship,
|
|
||||||
physics_handle: ShipPhysicsHandle,
|
|
||||||
) -> Self {
|
|
||||||
let ship_content = ct.get_ship(ship.handle);
|
|
||||||
ShipWorldObject {
|
ShipWorldObject {
|
||||||
physics_handle,
|
physics_handle,
|
||||||
ship,
|
ship,
|
||||||
controls: ShipControls::new(),
|
controls: ShipControls::new(),
|
||||||
collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Should this ship should be removed from the world?
|
|
||||||
pub fn remove_from_world(&self) -> bool {
|
|
||||||
return self.ship.is_dead() && self.collapse_sequence.is_done();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Step this ship's state by t seconds
|
/// Step this ship's state by t seconds
|
||||||
pub fn step(
|
pub fn step(&mut self, r: &mut RigidBody, t: f32) {
|
||||||
&mut self,
|
|
||||||
ct: &content::Content,
|
|
||||||
particles: &mut Vec<ParticleBuilder>,
|
|
||||||
r: &mut RigidBody,
|
|
||||||
c: &mut Collider,
|
|
||||||
t: f32,
|
|
||||||
) {
|
|
||||||
if self.ship.is_dead() {
|
|
||||||
return self
|
|
||||||
.collapse_sequence
|
|
||||||
.step(&self.ship, ct, particles, r, c, t);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ship_rot = util::rigidbody_rotation(r);
|
let ship_rot = util::rigidbody_rotation(r);
|
||||||
let engine_force = ship_rot * t;
|
let engine_force = ship_rot * t;
|
||||||
|
|
||||||
|
|
|
@ -169,10 +169,9 @@ impl<'a> World {
|
||||||
.angle(Vector2 { x: 1.0, y: 0.0 })
|
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
match &projectile.projectile.content.impact_effect {
|
match &projectile.projectile.content.impact_particle {
|
||||||
None => {}
|
None => {}
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let x = ct.get_effect(*x);
|
|
||||||
let velocity = match x.inherit_velocity {
|
let velocity = match x.inherit_velocity {
|
||||||
content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 },
|
content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 },
|
||||||
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(pr),
|
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(pr),
|
||||||
|
@ -253,8 +252,7 @@ impl<'a> World {
|
||||||
);
|
);
|
||||||
|
|
||||||
let h = ShipPhysicsHandle(r, c);
|
let h = ShipPhysicsHandle(r, c);
|
||||||
self.ships
|
self.ships.insert(c, objects::ShipWorldObject::new(ship, h));
|
||||||
.insert(c, objects::ShipWorldObject::new(ct, ship, h));
|
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,19 +263,16 @@ impl<'a> World {
|
||||||
let mut projectiles = Vec::new();
|
let mut projectiles = Vec::new();
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
for (_, s) in &mut self.ships {
|
for (_, s) in &mut self.ships {
|
||||||
let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0];
|
if s.ship.is_destroyed() {
|
||||||
let c = &mut self.wrapper.collider_set[s.physics_handle.1];
|
|
||||||
|
|
||||||
// TODO: unified step info struct
|
|
||||||
s.step(ct, particles, r, c, t);
|
|
||||||
if s.controls.guns {
|
|
||||||
projectiles.push((s.physics_handle, s.ship.fire_guns()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.remove_from_world() {
|
|
||||||
to_remove.push(s.physics_handle);
|
to_remove.push(s.physics_handle);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0];
|
||||||
|
s.step(r, t);
|
||||||
|
if s.controls.guns {
|
||||||
|
projectiles.push((s.physics_handle, s.ship.fire_guns()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (s, p) in projectiles {
|
for (s, p) in projectiles {
|
||||||
self.add_projectiles(s, p);
|
self.add_projectiles(s, p);
|
||||||
|
@ -323,10 +318,9 @@ impl<'a> World {
|
||||||
for c in to_remove {
|
for c in to_remove {
|
||||||
let (pr, p) = self.remove_projectile(c).unwrap();
|
let (pr, p) = self.remove_projectile(c).unwrap();
|
||||||
|
|
||||||
match &p.projectile.content.expire_effect {
|
match &p.projectile.content.expire_particle {
|
||||||
None => {}
|
None => {}
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
let x = ct.get_effect(*x);
|
|
||||||
let pos = util::rigidbody_position(&pr);
|
let pos = util::rigidbody_position(&pr);
|
||||||
let angle: Deg<f32> = util::rigidbody_rotation(&pr)
|
let angle: Deg<f32> = util::rigidbody_rotation(&pr)
|
||||||
.angle(Vector2 { x: 1.0, y: 0.0 })
|
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||||
|
|
Loading…
Reference in New Issue