Improved effect definitions
parent
aa4c6c18b3
commit
eabc1ebd37
|
@ -0,0 +1,29 @@
|
|||
[effect."small explosion"]
|
||||
sprite = "particle::explosion::small"
|
||||
lifetime = "inherit"
|
||||
inherit_velocity = "target"
|
||||
size = 3.0
|
||||
|
||||
[effect."huge explosion"]
|
||||
sprite = "particle::explosion::huge"
|
||||
lifetime = "inherit"
|
||||
inherit_velocity = "target"
|
||||
size = 3.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
|
||||
# better physics
|
||||
# document:effect vs particle
|
|
@ -29,13 +29,9 @@ projectile.force = 0.0
|
|||
|
||||
projectile.collider.ball.radius = 2.0
|
||||
|
||||
projectile.impact.sprite = "particle::explosion"
|
||||
projectile.impact.lifetime = "inherit"
|
||||
projectile.impact.inherit_velocity = "target"
|
||||
projectile.impact.size = 3.0
|
||||
projectile.impact_effect = "small explosion"
|
||||
|
||||
|
||||
projectile.expire.sprite = "particle::blaster"
|
||||
projectile.expire.lifetime = "inherit"
|
||||
projectile.expire.inherit_velocity = "projectile"
|
||||
projectile.expire.size = 3.0
|
||||
projectile.expire_effect.sprite = "particle::blaster"
|
||||
projectile.expire_effect.lifetime = "inherit"
|
||||
projectile.expire_effect.inherit_velocity = "projectile"
|
||||
projectile.expire_effect.size = 3.0
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
# TODO:
|
||||
# random start frame
|
||||
# repeat once: stay on last frame
|
||||
# blending mode: alpha / half-alpha / additive
|
||||
|
||||
|
||||
[sprite."starfield"]
|
||||
file = "starfield.png"
|
||||
|
||||
|
@ -20,8 +26,9 @@ file = "projectile/blaster.png"
|
|||
file = "ship/gypsum.png"
|
||||
|
||||
[sprite."ship::peregrine"]
|
||||
duration = 1.3
|
||||
timing.duration = 1.3
|
||||
repeat = "reverse"
|
||||
random_start_frame = true
|
||||
frames = [
|
||||
"ship/peregrine/01.png",
|
||||
"ship/peregrine/02.png",
|
||||
|
@ -52,7 +59,7 @@ file = "ui/radarframe.png"
|
|||
file = "ui/center-arrow.png"
|
||||
|
||||
[sprite."particle::blaster"]
|
||||
duration = 0.15
|
||||
timing.duration = 0.15
|
||||
repeat = "once"
|
||||
frames = [
|
||||
"particle/blaster/01.png",
|
||||
|
@ -62,8 +69,48 @@ frames = [
|
|||
]
|
||||
|
||||
|
||||
[sprite."particle::explosion"]
|
||||
duration = 0.4
|
||||
[sprite."particle::explosion::tiny"]
|
||||
timing.fps = 15
|
||||
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"
|
||||
frames = [
|
||||
"particle/explosion-large/01.png",
|
||||
|
@ -76,3 +123,19 @@ frames = [
|
|||
"particle/explosion-large/08.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,17 +10,20 @@ use std::{cmp::Eq, hash::Hash};
|
|||
/// A lightweight representation of a sprite
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SpriteHandle {
|
||||
/// 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,
|
||||
pub(crate) index: usize,
|
||||
|
||||
/// The aspect ratio of this sprite (width / height)
|
||||
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 {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.index.hash(state)
|
||||
|
@ -37,7 +40,7 @@ impl PartialEq for SpriteHandle {
|
|||
/// A lightweight representation of an outfit
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct OutfitHandle {
|
||||
/// TODO
|
||||
/// TODO: pub in crate, currently for debug (same with all other handles)
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
|
@ -65,7 +68,12 @@ pub struct SystemHandle {
|
|||
/// A lightweight representation of a faction
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct FactionHandle {
|
||||
/// The index of this faction in content.factions
|
||||
/// TODO: pub in crate, currently for debug (same with all other handles)
|
||||
/// TODO
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
/// A lightweight representation of an effect
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct EffectHandle {
|
||||
pub(crate) index: usize,
|
||||
}
|
||||
|
|
|
@ -18,10 +18,12 @@ use std::{
|
|||
use toml;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
pub use handle::{FactionHandle, GunHandle, OutfitHandle, ShipHandle, SpriteHandle, SystemHandle};
|
||||
pub use handle::{
|
||||
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,
|
||||
Effect, EnginePoint, Faction, Gun, GunPoint, ImpactInheritVelocity, Outfit, OutfitSpace,
|
||||
Projectile, ProjectileCollider, Relationship, RepeatMode, Ship, Sprite, System,
|
||||
};
|
||||
|
||||
mod syntax {
|
||||
|
@ -29,7 +31,7 @@ mod syntax {
|
|||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, fmt::Display, hash::Hash};
|
||||
|
||||
use crate::part::{faction, gun, outfit, ship, sprite, system};
|
||||
use crate::part::{effect, faction, gun, outfit, ship, sprite, system};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Root {
|
||||
|
@ -39,6 +41,7 @@ mod syntax {
|
|||
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
|
||||
pub sprite: Option<HashMap<String, sprite::syntax::Sprite>>,
|
||||
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
||||
pub effect: Option<HashMap<String, effect::syntax::Effect>>,
|
||||
}
|
||||
|
||||
fn merge_hashmap<K, V>(
|
||||
|
@ -75,6 +78,7 @@ mod syntax {
|
|||
outfit: None,
|
||||
sprite: None,
|
||||
faction: None,
|
||||
effect: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,6 +93,8 @@ mod syntax {
|
|||
.with_context(|| "while merging sprites")?;
|
||||
merge_hashmap(&mut self.faction, other.faction)
|
||||
.with_context(|| "while merging factions")?;
|
||||
merge_hashmap(&mut self.effect, other.effect)
|
||||
.with_context(|| "while merging effects")?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
@ -98,11 +104,29 @@ trait Build {
|
|||
type InputSyntaxType;
|
||||
|
||||
/// Build a processed System struct from raw serde data
|
||||
fn build(root: Self::InputSyntaxType, ct: &mut Content) -> Result<()>
|
||||
fn build(
|
||||
root: Self::InputSyntaxType,
|
||||
build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
) -> Result<()>
|
||||
where
|
||||
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
|
||||
#[derive(Debug)]
|
||||
pub struct Content {
|
||||
|
@ -113,30 +137,22 @@ pub struct Content {
|
|||
starfield_sprite_name: String,
|
||||
|
||||
/// Sprites
|
||||
pub sprites: Vec<part::sprite::Sprite>,
|
||||
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, handle::SpriteHandle>,
|
||||
sprite_index: HashMap<String, SpriteHandle>,
|
||||
/// The texture to use for starfield stars
|
||||
starfield_handle: Option<handle::SpriteHandle>,
|
||||
starfield_handle: Option<SpriteHandle>,
|
||||
|
||||
/// Keeps track of which images are in which texture
|
||||
sprite_atlas: SpriteAtlas,
|
||||
|
||||
/// Outfits
|
||||
outfits: Vec<part::outfit::Outfit>,
|
||||
|
||||
/// Ship guns
|
||||
guns: Vec<part::gun::Gun>,
|
||||
|
||||
/// Ship bodies
|
||||
ships: Vec<part::ship::Ship>,
|
||||
|
||||
/// Star systems
|
||||
systems: Vec<part::system::System>,
|
||||
|
||||
/// Factions
|
||||
factions: Vec<part::faction::Faction>,
|
||||
outfits: Vec<Outfit>,
|
||||
guns: Vec<Gun>, // TODO: merge with outfit
|
||||
ships: Vec<Ship>,
|
||||
systems: Vec<System>,
|
||||
factions: Vec<Faction>,
|
||||
effects: Vec<Effect>,
|
||||
}
|
||||
|
||||
// Loading methods
|
||||
|
@ -189,6 +205,8 @@ impl Content {
|
|||
toml::from_str(&file_string)?
|
||||
};
|
||||
|
||||
let mut build_context = ContentBuildContext::new();
|
||||
|
||||
let mut content = Self {
|
||||
sprite_atlas: atlas,
|
||||
systems: Vec::new(),
|
||||
|
@ -197,33 +215,58 @@ impl Content {
|
|||
outfits: Vec::new(),
|
||||
sprites: Vec::new(),
|
||||
factions: Vec::new(),
|
||||
effects: Vec::new(),
|
||||
sprite_index: HashMap::new(),
|
||||
starfield_handle: None,
|
||||
image_root: texture_root,
|
||||
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
|
||||
|
||||
// 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() {
|
||||
part::ship::Ship::build(root.ship.take().unwrap(), &mut content)?;
|
||||
part::ship::Ship::build(root.ship.take().unwrap(), &mut build_context, &mut content)?;
|
||||
}
|
||||
if root.gun.is_some() {
|
||||
part::gun::Gun::build(root.gun.take().unwrap(), &mut content)?;
|
||||
part::gun::Gun::build(root.gun.take().unwrap(), &mut build_context, &mut content)?;
|
||||
}
|
||||
if root.outfit.is_some() {
|
||||
part::outfit::Outfit::build(root.outfit.take().unwrap(), &mut content)?;
|
||||
part::outfit::Outfit::build(
|
||||
root.outfit.take().unwrap(),
|
||||
&mut build_context,
|
||||
&mut content,
|
||||
)?;
|
||||
}
|
||||
if root.system.is_some() {
|
||||
part::system::System::build(root.system.take().unwrap(), &mut content)?;
|
||||
part::system::System::build(
|
||||
root.system.take().unwrap(),
|
||||
&mut build_context,
|
||||
&mut content,
|
||||
)?;
|
||||
}
|
||||
if root.faction.is_some() {
|
||||
part::faction::Faction::build(root.faction.take().unwrap(), &mut content)?;
|
||||
part::faction::Faction::build(
|
||||
root.faction.take().unwrap(),
|
||||
&mut build_context,
|
||||
&mut content,
|
||||
)?;
|
||||
}
|
||||
|
||||
return Ok(content);
|
||||
|
@ -289,4 +332,9 @@ impl Content {
|
|||
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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
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 std::collections::HashMap;
|
||||
|
||||
use crate::{handle::FactionHandle, Content};
|
||||
use crate::{handle::FactionHandle, Content, ContentBuildContext};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use std::collections::HashMap;
|
||||
|
@ -61,13 +61,17 @@ pub struct Faction {
|
|||
impl crate::Build for Faction {
|
||||
type InputSyntaxType = HashMap<String, syntax::Faction>;
|
||||
|
||||
fn build(factions: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||
fn build(
|
||||
factions: Self::InputSyntaxType,
|
||||
_build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
) -> Result<()> {
|
||||
// Keeps track of position in faction array.
|
||||
// This lets us build FactionHandles before finishing all factions.
|
||||
let faction_names: Vec<String> = factions.keys().map(|x| x.to_owned()).collect();
|
||||
|
||||
// Indexing will break if this is false.
|
||||
assert!(ct.factions.len() == 0);
|
||||
assert!(content.factions.len() == 0);
|
||||
|
||||
for f_idx in 0..faction_names.len() {
|
||||
let faction_name = &faction_names[f_idx];
|
||||
|
@ -112,7 +116,7 @@ impl crate::Build for Faction {
|
|||
);
|
||||
}
|
||||
|
||||
ct.factions.push(Self {
|
||||
content.factions.push(Self {
|
||||
name: faction_name.to_owned(),
|
||||
handle: h,
|
||||
relationships,
|
||||
|
|
|
@ -5,10 +5,11 @@ use std::collections::HashMap;
|
|||
|
||||
use crate::{handle::SpriteHandle, Content};
|
||||
|
||||
use crate::OutfitSpace;
|
||||
use crate::{ContentBuildContext, EffectHandle, OutfitSpace};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use crate::part::shared;
|
||||
use crate::part::effect;
|
||||
use crate::part::outfitspace;
|
||||
use serde::Deserialize;
|
||||
// Raw serde syntax structs.
|
||||
// These are never seen by code outside this crate.
|
||||
|
@ -18,7 +19,7 @@ pub(crate) mod syntax {
|
|||
pub projectile: Projectile,
|
||||
pub rate: f32,
|
||||
pub rate_rng: f32,
|
||||
pub space: shared::syntax::OutfitSpace,
|
||||
pub space: outfitspace::syntax::OutfitSpace,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -32,26 +33,11 @@ pub(crate) mod syntax {
|
|||
pub lifetime_rng: f32,
|
||||
pub damage: f32,
|
||||
pub angle_rng: f32,
|
||||
pub impact: Option<ProjectileParticle>,
|
||||
pub expire: Option<ProjectileParticle>,
|
||||
pub impact_effect: Option<effect::syntax::EffectReference>,
|
||||
pub expire_effect: Option<effect::syntax::EffectReference>,
|
||||
pub collider: super::ProjectileCollider,
|
||||
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
|
||||
|
@ -67,25 +53,6 @@ pub struct BallCollider {
|
|||
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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Gun {
|
||||
|
@ -143,86 +110,50 @@ pub struct Projectile {
|
|||
pub angle_rng: Deg<f32>,
|
||||
|
||||
/// The particle this projectile will spawn when it hits something
|
||||
pub impact_particle: Option<ProjectileParticle>,
|
||||
pub impact_effect: Option<EffectHandle>,
|
||||
|
||||
/// The particle this projectile will spawn when it expires
|
||||
pub expire_particle: Option<ProjectileParticle>,
|
||||
pub expire_effect: Option<EffectHandle>,
|
||||
|
||||
/// Collider parameters for this projectile
|
||||
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 {
|
||||
type InputSyntaxType = HashMap<String, syntax::Gun>;
|
||||
|
||||
fn build(gun: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||
fn build(
|
||||
gun: Self::InputSyntaxType,
|
||||
build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
) -> Result<()> {
|
||||
for (gun_name, gun) in gun {
|
||||
let projectile_sprite_handle = match ct.sprite_index.get(&gun.projectile.sprite) {
|
||||
let projectile_sprite_handle = match content.sprite_index.get(&gun.projectile.sprite) {
|
||||
None => bail!(
|
||||
"In gun `{}`: projectile sprite `{}` doesn't exist",
|
||||
"projectile sprite `{}` doesn't exist in gun `{}`",
|
||||
gun.projectile.sprite,
|
||||
gun_name,
|
||||
gun.projectile.sprite
|
||||
),
|
||||
Some(t) => *t,
|
||||
};
|
||||
|
||||
let impact_particle = parse_projectile_particle(ct, gun.projectile.impact)
|
||||
.with_context(|| format!("In gun `{}`", gun_name))?;
|
||||
let impact_effect = match gun.projectile.impact_effect {
|
||||
Some(e) => Some(
|
||||
e.to_handle(build_context, content)
|
||||
.with_context(|| format!("while loading gun `{}`", gun_name))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let expire_particle = parse_projectile_particle(ct, gun.projectile.expire)
|
||||
.with_context(|| format!("In gun `{}`", gun_name))?;
|
||||
let expire_effect = match gun.projectile.expire_effect {
|
||||
Some(e) => Some(
|
||||
e.to_handle(build_context, content)
|
||||
.with_context(|| format!("while loading gun `{}`", gun_name))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
ct.guns.push(Self {
|
||||
content.guns.push(Self {
|
||||
name: gun_name,
|
||||
space: gun.space.into(),
|
||||
rate: gun.rate,
|
||||
|
@ -238,8 +169,8 @@ impl crate::Build for Gun {
|
|||
lifetime_rng: gun.projectile.lifetime_rng,
|
||||
damage: gun.projectile.damage,
|
||||
angle_rng: Deg(gun.projectile.angle_rng),
|
||||
impact_particle,
|
||||
expire_particle,
|
||||
impact_effect,
|
||||
expire_effect,
|
||||
collider: gun.projectile.collider,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
//! Content parts
|
||||
|
||||
pub mod effect;
|
||||
pub mod faction;
|
||||
pub mod gun;
|
||||
pub mod outfit;
|
||||
mod shared;
|
||||
pub mod outfitspace;
|
||||
pub mod ship;
|
||||
pub mod sprite;
|
||||
pub mod system;
|
||||
|
||||
pub use effect::{Effect, ImpactInheritVelocity};
|
||||
pub use faction::{Faction, Relationship};
|
||||
pub use gun::{Gun, ImpactInheritVelocity, Projectile, ProjectileCollider, ProjectileParticle};
|
||||
pub use gun::{Gun, Projectile, ProjectileCollider};
|
||||
pub use outfit::Outfit;
|
||||
pub use shared::OutfitSpace;
|
||||
pub use outfitspace::OutfitSpace;
|
||||
pub use ship::{EnginePoint, GunPoint, Ship};
|
||||
pub use sprite::{RepeatMode, Sprite};
|
||||
pub use system::{Object, System};
|
||||
|
|
|
@ -2,10 +2,10 @@ use std::collections::HashMap;
|
|||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, OutfitSpace};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use crate::part::shared;
|
||||
use crate::part::outfitspace;
|
||||
use serde::Deserialize;
|
||||
// Raw serde syntax structs.
|
||||
// These are never seen by code outside this crate.
|
||||
|
@ -14,7 +14,7 @@ pub(crate) mod syntax {
|
|||
pub struct Outfit {
|
||||
pub engine: Option<Engine>,
|
||||
pub steering: Option<Steering>,
|
||||
pub space: shared::syntax::OutfitSpace,
|
||||
pub space: outfitspace::syntax::OutfitSpace,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -53,7 +53,11 @@ pub struct Outfit {
|
|||
impl crate::Build for Outfit {
|
||||
type InputSyntaxType = HashMap<String, syntax::Outfit>;
|
||||
|
||||
fn build(outfits: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||
fn build(
|
||||
outfits: Self::InputSyntaxType,
|
||||
_build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
) -> Result<()> {
|
||||
for (outfit_name, outfit) in outfits {
|
||||
let mut o = Self {
|
||||
name: outfit_name.clone(),
|
||||
|
@ -65,7 +69,7 @@ impl crate::Build for Outfit {
|
|||
|
||||
// Engine stats
|
||||
if let Some(engine) = outfit.engine {
|
||||
let th = match ct.sprite_index.get(&engine.flare_sprite) {
|
||||
let th = match content.sprite_index.get(&engine.flare_sprite) {
|
||||
None => bail!(
|
||||
"In outfit `{}`: flare sprite `{}` doesn't exist",
|
||||
outfit_name,
|
||||
|
@ -82,7 +86,7 @@ impl crate::Build for Outfit {
|
|||
o.steer_power = steer.power;
|
||||
}
|
||||
|
||||
ct.outfits.push(o);
|
||||
content.outfits.push(o);
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
|
|
|
@ -4,10 +4,10 @@ use anyhow::{bail, Result};
|
|||
use cgmath::Point2;
|
||||
use nalgebra::{point, Point};
|
||||
|
||||
use crate::{handle::SpriteHandle, Content, OutfitSpace};
|
||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, OutfitSpace};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use crate::part::shared;
|
||||
use crate::part::outfitspace;
|
||||
use serde::Deserialize;
|
||||
|
||||
// Raw serde syntax structs.
|
||||
|
@ -24,7 +24,7 @@ pub(crate) mod syntax {
|
|||
pub collision: Collision,
|
||||
pub angular_drag: f32,
|
||||
pub linear_drag: f32,
|
||||
pub space: shared::syntax::OutfitSpace,
|
||||
pub space: outfitspace::syntax::OutfitSpace,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
@ -125,9 +125,13 @@ pub struct GunPoint {
|
|||
impl crate::Build for Ship {
|
||||
type InputSyntaxType = HashMap<String, syntax::Ship>;
|
||||
|
||||
fn build(ship: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||
fn build(
|
||||
ship: Self::InputSyntaxType,
|
||||
_build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
) -> Result<()> {
|
||||
for (ship_name, ship) in ship {
|
||||
let handle = match ct.sprite_index.get(&ship.sprite) {
|
||||
let handle = match content.sprite_index.get(&ship.sprite) {
|
||||
None => bail!(
|
||||
"In ship `{}`: sprite `{}` doesn't exist",
|
||||
ship_name,
|
||||
|
@ -137,9 +141,9 @@ impl crate::Build for Ship {
|
|||
};
|
||||
|
||||
let size = ship.size;
|
||||
let aspect = ct.get_sprite(handle).aspect;
|
||||
let aspect = content.get_sprite(handle).aspect;
|
||||
|
||||
ct.ships.push(Self {
|
||||
content.ships.push(Self {
|
||||
aspect,
|
||||
name: ship_name,
|
||||
sprite: handle,
|
||||
|
|
|
@ -3,7 +3,7 @@ use image::io::Reader;
|
|||
use serde::Deserialize;
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use crate::{handle::SpriteHandle, Content};
|
||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use serde::Deserialize;
|
||||
|
@ -29,8 +29,18 @@ pub(crate) mod syntax {
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub struct FrameSprite {
|
||||
pub frames: Vec<PathBuf>,
|
||||
pub duration: f32,
|
||||
pub timing: Timing,
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,16 +94,23 @@ pub struct Sprite {
|
|||
|
||||
/// Aspect ratio of this sprite (width / height)
|
||||
pub aspect: f32,
|
||||
|
||||
/// If true, start on a random frame of this sprite.
|
||||
pub random_start_frame: bool,
|
||||
}
|
||||
|
||||
impl crate::Build for Sprite {
|
||||
type InputSyntaxType = HashMap<String, syntax::Sprite>;
|
||||
|
||||
fn build(sprites: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||
fn build(
|
||||
sprites: Self::InputSyntaxType,
|
||||
_build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
) -> Result<()> {
|
||||
for (sprite_name, t) in sprites {
|
||||
match t {
|
||||
syntax::Sprite::Static(t) => {
|
||||
let file = ct.image_root.join(&t.file);
|
||||
let file = content.image_root.join(&t.file);
|
||||
let reader = Reader::open(&file).with_context(|| {
|
||||
format!(
|
||||
"Failed to read file `{}` in sprite `{}`",
|
||||
|
@ -110,34 +127,35 @@ impl crate::Build for Sprite {
|
|||
})?;
|
||||
|
||||
let h = SpriteHandle {
|
||||
index: ct.sprites.len() as u32,
|
||||
index: content.sprites.len(),
|
||||
aspect: dim.0 as f32 / dim.1 as f32,
|
||||
};
|
||||
|
||||
if sprite_name == ct.starfield_sprite_name {
|
||||
if ct.starfield_handle.is_none() {
|
||||
ct.starfield_handle = Some(h)
|
||||
if sprite_name == content.starfield_sprite_name {
|
||||
if content.starfield_handle.is_none() {
|
||||
content.starfield_handle = Some(h)
|
||||
} else {
|
||||
// This can't happen, since this is a hashmap.
|
||||
unreachable!("Found two starfield sprites! Something is very wrong.")
|
||||
}
|
||||
}
|
||||
|
||||
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||
content.sprite_index.insert(sprite_name.clone(), h);
|
||||
|
||||
ct.sprites.push(Self {
|
||||
content.sprites.push(Self {
|
||||
name: sprite_name,
|
||||
frames: vec![t.file],
|
||||
fps: 0.0,
|
||||
handle: h,
|
||||
repeat: RepeatMode::Once,
|
||||
aspect: dim.0 as f32 / dim.1 as f32,
|
||||
random_start_frame: false,
|
||||
});
|
||||
}
|
||||
syntax::Sprite::Frames(t) => {
|
||||
let mut dim = None;
|
||||
for f in &t.frames {
|
||||
let file = ct.image_root.join(f);
|
||||
let file = content.image_root.join(f);
|
||||
let reader = Reader::open(&file).with_context(|| {
|
||||
format!(
|
||||
"Failed to read file `{}` in sprite `{}`",
|
||||
|
@ -167,33 +185,37 @@ impl crate::Build for Sprite {
|
|||
let dim = dim.unwrap();
|
||||
|
||||
let h = SpriteHandle {
|
||||
index: ct.sprites.len() as u32,
|
||||
index: content.sprites.len(),
|
||||
aspect: dim.0 as f32 / dim.1 as f32,
|
||||
};
|
||||
|
||||
if sprite_name == ct.starfield_sprite_name {
|
||||
if sprite_name == content.starfield_sprite_name {
|
||||
unreachable!("Starfield texture may not be animated")
|
||||
}
|
||||
|
||||
let fps = t.duration / t.frames.len() as f32;
|
||||
let fps = match t.timing {
|
||||
syntax::Timing::Duration(d) => d / t.frames.len() as f32,
|
||||
syntax::Timing::Fps(f) => 1.0 / f,
|
||||
};
|
||||
|
||||
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||
ct.sprites.push(Self {
|
||||
content.sprite_index.insert(sprite_name.clone(), h);
|
||||
content.sprites.push(Self {
|
||||
name: sprite_name,
|
||||
frames: t.frames,
|
||||
fps,
|
||||
handle: h,
|
||||
repeat: t.repeat,
|
||||
aspect: dim.0 as f32 / dim.1 as f32,
|
||||
random_start_frame: t.random_start_frame.unwrap_or(false),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ct.starfield_handle.is_none() {
|
||||
if content.starfield_handle.is_none() {
|
||||
bail!(
|
||||
"Could not find a starfield texture (name: `{}`)",
|
||||
ct.starfield_sprite_name
|
||||
content.starfield_sprite_name
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ use anyhow::{bail, Context, Result};
|
|||
use cgmath::{Deg, Point3};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::{handle::SpriteHandle, util::Polar, Content};
|
||||
use crate::{handle::SpriteHandle, util::Polar, Content, ContentBuildContext};
|
||||
|
||||
pub(crate) mod syntax {
|
||||
use serde::Deserialize;
|
||||
|
@ -177,7 +177,11 @@ fn resolve_position(
|
|||
impl crate::Build for System {
|
||||
type InputSyntaxType = HashMap<String, syntax::System>;
|
||||
|
||||
fn build(system: Self::InputSyntaxType, ct: &mut Content) -> Result<()> {
|
||||
fn build(
|
||||
system: Self::InputSyntaxType,
|
||||
_build_context: &mut ContentBuildContext,
|
||||
content: &mut Content,
|
||||
) -> Result<()> {
|
||||
for (system_name, system) in system {
|
||||
let mut objects = Vec::new();
|
||||
|
||||
|
@ -185,7 +189,7 @@ impl crate::Build for System {
|
|||
let mut cycle_detector = HashSet::new();
|
||||
cycle_detector.insert(label.clone());
|
||||
|
||||
let handle = match ct.sprite_index.get(&obj.sprite) {
|
||||
let handle = match content.sprite_index.get(&obj.sprite) {
|
||||
None => bail!(
|
||||
"In system `{}`: sprite `{}` doesn't exist",
|
||||
system_name,
|
||||
|
@ -203,7 +207,7 @@ impl crate::Build for System {
|
|||
});
|
||||
}
|
||||
|
||||
ct.systems.push(Self {
|
||||
content.systems.push(Self {
|
||||
name: system_name,
|
||||
objects,
|
||||
});
|
||||
|
|
|
@ -11,11 +11,11 @@ fn animate(instance: InstanceInput, age: f32) -> u32 {
|
|||
var frame: u32 = u32(0);
|
||||
|
||||
|
||||
// Repeat
|
||||
// Once
|
||||
if rep == u32(1) {
|
||||
|
||||
frame = u32(min(
|
||||
(age / fps),
|
||||
age / fps,
|
||||
f32(len) - 1.0
|
||||
));
|
||||
|
||||
|
|
|
@ -380,7 +380,7 @@ impl GPUState {
|
|||
|
||||
instances.push(ObjectInstance {
|
||||
transform: t.into(),
|
||||
sprite_index: s.sprite.index,
|
||||
sprite_index: s.sprite.get_index(),
|
||||
});
|
||||
|
||||
// Add children
|
||||
|
@ -429,7 +429,7 @@ impl GPUState {
|
|||
|
||||
instances.push(ObjectInstance {
|
||||
transform: t.into(),
|
||||
sprite_index: s.sprite.index,
|
||||
sprite_index: s.sprite.get_index(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -481,7 +481,7 @@ impl GPUState {
|
|||
|
||||
instances.push(UiInstance {
|
||||
transform: (OPENGL_TO_WGPU_MATRIX * translate * screen_aspect * rotate * scale).into(),
|
||||
sprite_index: s.sprite.index,
|
||||
sprite_index: s.sprite.get_index(),
|
||||
color: s.color.unwrap_or([1.0, 1.0, 1.0, 1.0]),
|
||||
});
|
||||
}
|
||||
|
@ -613,7 +613,7 @@ impl GPUState {
|
|||
self.window_size.height as f32,
|
||||
],
|
||||
window_aspect: [self.window_aspect, 0.0],
|
||||
starfield_sprite: [s.index, 0],
|
||||
starfield_sprite: [s.get_index(), 0],
|
||||
starfield_tile_size: [galactica_constants::STARFIELD_SIZE as f32, 0.0],
|
||||
starfield_size_limits: [
|
||||
galactica_constants::STARFIELD_SIZE_MIN,
|
||||
|
@ -633,7 +633,7 @@ impl GPUState {
|
|||
velocity: i.velocity.into(),
|
||||
rotation: Matrix2::from_angle(i.angle).into(),
|
||||
size: i.size,
|
||||
sprite_index: i.sprite.index,
|
||||
sprite_index: i.sprite.get_index(),
|
||||
created: state.current_time,
|
||||
expires: state.current_time + i.lifetime,
|
||||
}]),
|
||||
|
|
|
@ -169,9 +169,10 @@ impl<'a> World {
|
|||
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||
.into();
|
||||
|
||||
match &projectile.projectile.content.impact_particle {
|
||||
match &projectile.projectile.content.impact_effect {
|
||||
None => {}
|
||||
Some(x) => {
|
||||
let x = ct.get_effect(*x);
|
||||
let velocity = match x.inherit_velocity {
|
||||
content::ImpactInheritVelocity::Dont => Vector2 { x: 0.0, y: 0.0 },
|
||||
content::ImpactInheritVelocity::Projectile => util::rigidbody_velocity(pr),
|
||||
|
@ -318,9 +319,10 @@ impl<'a> World {
|
|||
for c in to_remove {
|
||||
let (pr, p) = self.remove_projectile(c).unwrap();
|
||||
|
||||
match &p.projectile.content.expire_particle {
|
||||
match &p.projectile.content.expire_effect {
|
||||
None => {}
|
||||
Some(x) => {
|
||||
let x = ct.get_effect(*x);
|
||||
let pos = util::rigidbody_position(&pr);
|
||||
let angle: Deg<f32> = util::rigidbody_rotation(&pr)
|
||||
.angle(Vector2 { x: 1.0, y: 0.0 })
|
||||
|
|
Loading…
Reference in New Issue