Compare commits
No commits in common. "07ec685c139eee96894d421710404b261cee0a44" and "7b264c7c3ec76d9c515a735f594cf013d0d3b29b" have entirely different histories.
07ec685c13
...
7b264c7c3e
37
content/guns.toml
Normal file
37
content/guns.toml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
[gun."blaster"]
|
||||||
|
|
||||||
|
space.weapon = 10
|
||||||
|
|
||||||
|
# Average delay between shots
|
||||||
|
rate = 0.2
|
||||||
|
# Random rate variation (each cooldown is +- this)
|
||||||
|
rate_rng = 0.1
|
||||||
|
|
||||||
|
# TODO: apply force to ship on fire
|
||||||
|
projectile.sprite = "projectile::blaster"
|
||||||
|
# Height of projectile in game units
|
||||||
|
projectile.size = 6
|
||||||
|
projectile.size_rng = 0.0
|
||||||
|
# Speed of projectile, in game units/second
|
||||||
|
projectile.speed = 300
|
||||||
|
projectile.speed_rng = 10.0
|
||||||
|
# Lifetime of projectile, in seconds
|
||||||
|
projectile.lifetime = 2.0
|
||||||
|
projectile.lifetime_rng = 0.2
|
||||||
|
projectile.damage = 10.0
|
||||||
|
# Random variation of firing angle.
|
||||||
|
# If this is 0, the projectile always goes straight.
|
||||||
|
# If this is 10, projectiles will form a cone of 10 degrees
|
||||||
|
# ( 5 degrees on each side )
|
||||||
|
projectile.angle_rng = 2
|
||||||
|
# How much force this projectile will apply to an object it hits
|
||||||
|
projectile.force = 0.0
|
||||||
|
|
||||||
|
projectile.collider.ball.radius = 2.0
|
||||||
|
|
||||||
|
projectile.impact_effect = "blaster impact"
|
||||||
|
|
||||||
|
projectile.expire_effect.sprite = "particle::blaster"
|
||||||
|
projectile.expire_effect.lifetime = "inherit"
|
||||||
|
projectile.expire_effect.size = 3.0
|
||||||
|
projectile.expire_effect.velocity_scale_parent = 1.0
|
@ -14,42 +14,3 @@ space.outfit = 5
|
|||||||
shield.generation = 10
|
shield.generation = 10
|
||||||
shield.strength = 500
|
shield.strength = 500
|
||||||
shield.delay = 2.0
|
shield.delay = 2.0
|
||||||
|
|
||||||
|
|
||||||
[outfit."blaster"]
|
|
||||||
|
|
||||||
space.weapon = 10
|
|
||||||
|
|
||||||
# Average delay between shots
|
|
||||||
gun.rate = 0.2
|
|
||||||
# Random rate variation (each cooldown is +- this)
|
|
||||||
gun.rate_rng = 0.1
|
|
||||||
|
|
||||||
# TODO: apply force to ship on fire
|
|
||||||
gun.projectile.sprite = "projectile::blaster"
|
|
||||||
# Height of projectile in game units
|
|
||||||
gun.projectile.size = 6
|
|
||||||
gun.projectile.size_rng = 0.0
|
|
||||||
# Speed of projectile, in game units/second
|
|
||||||
gun.projectile.speed = 300
|
|
||||||
gun.projectile.speed_rng = 10.0
|
|
||||||
# Lifetime of projectile, in seconds
|
|
||||||
gun.projectile.lifetime = 2.0
|
|
||||||
gun.projectile.lifetime_rng = 0.2
|
|
||||||
gun.projectile.damage = 10.0
|
|
||||||
# Random variation of firing angle.
|
|
||||||
# If this is 0, the projectile always goes straight.
|
|
||||||
# If this is 10, projectiles will form a cone of 10 degrees
|
|
||||||
# ( 5 degrees on each side )
|
|
||||||
gun.projectile.angle_rng = 2
|
|
||||||
# How much force this projectile will apply to an object it hits
|
|
||||||
gun.projectile.force = 0.0
|
|
||||||
|
|
||||||
gun.projectile.collider.ball.radius = 2.0
|
|
||||||
|
|
||||||
gun.projectile.impact_effect = "blaster impact"
|
|
||||||
|
|
||||||
gun.projectile.expire_effect.sprite = "particle::blaster"
|
|
||||||
gun.projectile.expire_effect.lifetime = "inherit"
|
|
||||||
gun.projectile.expire_effect.size = 3.0
|
|
||||||
gun.projectile.expire_effect.velocity_scale_parent = 1.0
|
|
||||||
|
@ -28,10 +28,11 @@ mod syntax {
|
|||||||
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, outfit, ship, sprite, system};
|
use crate::part::{effect, faction, gun, outfit, ship, sprite, system};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Root {
|
pub struct Root {
|
||||||
|
pub gun: Option<HashMap<String, gun::syntax::Gun>>,
|
||||||
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
|
pub ship: Option<HashMap<String, ship::syntax::Ship>>,
|
||||||
pub system: Option<HashMap<String, system::syntax::System>>,
|
pub system: Option<HashMap<String, system::syntax::System>>,
|
||||||
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
|
pub outfit: Option<HashMap<String, outfit::syntax::Outfit>>,
|
||||||
@ -68,6 +69,7 @@ mod syntax {
|
|||||||
impl Root {
|
impl Root {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
gun: None,
|
||||||
ship: None,
|
ship: None,
|
||||||
system: None,
|
system: None,
|
||||||
outfit: None,
|
outfit: None,
|
||||||
@ -78,6 +80,7 @@ mod syntax {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn merge(&mut self, other: Root) -> Result<()> {
|
pub fn merge(&mut self, other: Root) -> Result<()> {
|
||||||
|
merge_hashmap(&mut self.gun, other.gun).with_context(|| "while merging guns")?;
|
||||||
merge_hashmap(&mut self.ship, other.ship).with_context(|| "while merging ships")?;
|
merge_hashmap(&mut self.ship, other.ship).with_context(|| "while merging ships")?;
|
||||||
merge_hashmap(&mut self.system, other.system)
|
merge_hashmap(&mut self.system, other.system)
|
||||||
.with_context(|| "while merging systems")?;
|
.with_context(|| "while merging systems")?;
|
||||||
@ -238,6 +241,9 @@ impl Content {
|
|||||||
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 build_context, &mut content)?;
|
||||||
}
|
}
|
||||||
|
if root.gun.is_some() {
|
||||||
|
part::gun::Gun::build(root.gun.take().unwrap(), &mut build_context, &mut content)?;
|
||||||
|
}
|
||||||
if root.outfit.is_some() {
|
if root.outfit.is_some() {
|
||||||
part::outfit::Outfit::build(
|
part::outfit::Outfit::build(
|
||||||
root.outfit.take().unwrap(),
|
root.outfit.take().unwrap(),
|
||||||
|
186
crates/content/src/part/gun.rs
Normal file
186
crates/content/src/part/gun.rs
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
use anyhow::{bail, Context, Result};
|
||||||
|
use cgmath::{Deg, Rad};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{handle::SpriteHandle, Content};
|
||||||
|
|
||||||
|
use crate::{ContentBuildContext, EffectHandle, OutfitSpace};
|
||||||
|
|
||||||
|
pub(crate) mod syntax {
|
||||||
|
use crate::part::effect;
|
||||||
|
use crate::part::outfitspace;
|
||||||
|
use serde::Deserialize;
|
||||||
|
// Raw serde syntax structs.
|
||||||
|
// These are never seen by code outside this crate.
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Gun {
|
||||||
|
pub projectile: Projectile,
|
||||||
|
pub rate: f32,
|
||||||
|
pub rate_rng: f32,
|
||||||
|
pub space: outfitspace::syntax::OutfitSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Projectile {
|
||||||
|
pub sprite: String,
|
||||||
|
pub size: f32,
|
||||||
|
pub size_rng: f32,
|
||||||
|
pub speed: f32,
|
||||||
|
pub speed_rng: f32,
|
||||||
|
pub lifetime: f32,
|
||||||
|
pub lifetime_rng: f32,
|
||||||
|
pub damage: f32,
|
||||||
|
pub angle_rng: f32,
|
||||||
|
pub impact_effect: Option<effect::syntax::EffectReference>,
|
||||||
|
pub expire_effect: Option<effect::syntax::EffectReference>,
|
||||||
|
pub collider: super::ProjectileCollider,
|
||||||
|
pub force: f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Defines a projectile's collider
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub enum ProjectileCollider {
|
||||||
|
/// A ball collider
|
||||||
|
#[serde(rename = "ball")]
|
||||||
|
Ball(BallCollider),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple ball-shaped collider, centered at the object's position
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct BallCollider {
|
||||||
|
/// The radius of this ball
|
||||||
|
pub radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a gun outfit.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Gun {
|
||||||
|
/// The name of this gun
|
||||||
|
pub name: String,
|
||||||
|
|
||||||
|
/// The projectile this gun produces
|
||||||
|
pub projectile: Projectile,
|
||||||
|
|
||||||
|
/// Average delay between projectiles, in seconds.
|
||||||
|
pub rate: f32,
|
||||||
|
|
||||||
|
/// Random variation of projectile delay, in seconds.
|
||||||
|
/// Each shot waits (rate += rate_rng).
|
||||||
|
pub rate_rng: f32,
|
||||||
|
|
||||||
|
/// How much space this gun uses
|
||||||
|
pub space: OutfitSpace,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a projectile that a [`Gun`] produces.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Projectile {
|
||||||
|
/// The projectile sprite
|
||||||
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
|
/// The average size of this projectile
|
||||||
|
/// (height in game units)
|
||||||
|
pub size: f32,
|
||||||
|
/// Random size variation
|
||||||
|
pub size_rng: f32,
|
||||||
|
|
||||||
|
/// The speed of this projectile, in game units / second
|
||||||
|
pub speed: f32,
|
||||||
|
/// Random speed variation
|
||||||
|
pub speed_rng: f32,
|
||||||
|
|
||||||
|
/// The lifespan of this projectile.
|
||||||
|
/// It will vanish if it lives this long without hitting anything.
|
||||||
|
pub lifetime: f32,
|
||||||
|
/// Random lifetime variation
|
||||||
|
pub lifetime_rng: f32,
|
||||||
|
|
||||||
|
/// The damage this projectile does
|
||||||
|
pub damage: f32,
|
||||||
|
|
||||||
|
/// The force this projectile applies
|
||||||
|
pub force: f32,
|
||||||
|
|
||||||
|
/// The angle variation of this projectile.
|
||||||
|
/// Projectiles can be off center up to
|
||||||
|
/// `spread / 2` degrees in both directions.
|
||||||
|
///
|
||||||
|
/// (Forming a "fire cone" of `spread` degrees)
|
||||||
|
pub angle_rng: Rad<f32>,
|
||||||
|
|
||||||
|
/// The particle this projectile will spawn when it hits something
|
||||||
|
pub impact_effect: Option<EffectHandle>,
|
||||||
|
|
||||||
|
/// The particle this projectile will spawn when it expires
|
||||||
|
pub expire_effect: Option<EffectHandle>,
|
||||||
|
|
||||||
|
/// Collider parameters for this projectile
|
||||||
|
pub collider: ProjectileCollider,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::Build for Gun {
|
||||||
|
type InputSyntaxType = HashMap<String, syntax::Gun>;
|
||||||
|
|
||||||
|
fn build(
|
||||||
|
gun: Self::InputSyntaxType,
|
||||||
|
build_context: &mut ContentBuildContext,
|
||||||
|
content: &mut Content,
|
||||||
|
) -> Result<()> {
|
||||||
|
for (gun_name, gun) in gun {
|
||||||
|
let projectile_sprite_handle = match content.sprite_index.get(&gun.projectile.sprite) {
|
||||||
|
None => bail!(
|
||||||
|
"projectile sprite `{}` doesn't exist in gun `{}`",
|
||||||
|
gun.projectile.sprite,
|
||||||
|
gun_name,
|
||||||
|
),
|
||||||
|
Some(t) => *t,
|
||||||
|
};
|
||||||
|
|
||||||
|
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_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,
|
||||||
|
};
|
||||||
|
|
||||||
|
content.guns.push(Self {
|
||||||
|
name: gun_name,
|
||||||
|
space: gun.space.into(),
|
||||||
|
rate: gun.rate,
|
||||||
|
rate_rng: gun.rate_rng,
|
||||||
|
projectile: Projectile {
|
||||||
|
force: gun.projectile.force,
|
||||||
|
sprite: projectile_sprite_handle,
|
||||||
|
size: gun.projectile.size,
|
||||||
|
size_rng: gun.projectile.size_rng,
|
||||||
|
speed: gun.projectile.speed,
|
||||||
|
speed_rng: gun.projectile.speed_rng,
|
||||||
|
lifetime: gun.projectile.lifetime,
|
||||||
|
lifetime_rng: gun.projectile.lifetime_rng,
|
||||||
|
damage: gun.projectile.damage,
|
||||||
|
|
||||||
|
// Divide by 2, so the angle matches the angle of the fire cone.
|
||||||
|
// This should ALWAYS be done in the content parser.
|
||||||
|
angle_rng: Deg(gun.projectile.angle_rng / 2.0).into(),
|
||||||
|
impact_effect,
|
||||||
|
expire_effect,
|
||||||
|
collider: gun.projectile.collider,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
pub(crate) mod effect;
|
pub(crate) mod effect;
|
||||||
pub(crate) mod faction;
|
pub(crate) mod faction;
|
||||||
|
pub(crate) mod gun;
|
||||||
pub(crate) mod outfit;
|
pub(crate) mod outfit;
|
||||||
pub(crate) mod outfitspace;
|
pub(crate) mod outfitspace;
|
||||||
pub(crate) mod ship;
|
pub(crate) mod ship;
|
||||||
@ -10,7 +11,8 @@ pub(crate) mod system;
|
|||||||
|
|
||||||
pub use effect::Effect;
|
pub use effect::Effect;
|
||||||
pub use faction::{Faction, Relationship};
|
pub use faction::{Faction, Relationship};
|
||||||
pub use outfit::{Gun, Outfit, Projectile, ProjectileCollider};
|
pub use gun::{Gun, Projectile, ProjectileCollider};
|
||||||
|
pub use outfit::Outfit;
|
||||||
pub use outfitspace::OutfitSpace;
|
pub use outfitspace::OutfitSpace;
|
||||||
pub use ship::{
|
pub use ship::{
|
||||||
CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship,
|
CollapseEffectSpawner, CollapseEvent, EffectCollapseEvent, EnginePoint, GunPoint, Ship,
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Result};
|
||||||
use cgmath::Rad;
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{handle::SpriteHandle, Content, ContentBuildContext, OutfitHandle, OutfitSpace};
|
||||||
handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::{effect, part::outfitspace, ContentBuildContext};
|
use crate::part::outfitspace;
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use cgmath::Deg;
|
|
||||||
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.
|
||||||
@ -22,7 +16,6 @@ pub(crate) mod syntax {
|
|||||||
pub steering: Option<Steering>,
|
pub steering: Option<Steering>,
|
||||||
pub space: outfitspace::syntax::OutfitSpace,
|
pub space: outfitspace::syntax::OutfitSpace,
|
||||||
pub shield: Option<Shield>,
|
pub shield: Option<Shield>,
|
||||||
pub gun: Option<Gun>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -43,79 +36,6 @@ pub(crate) mod syntax {
|
|||||||
pub struct Steering {
|
pub struct Steering {
|
||||||
pub power: f32,
|
pub power: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Gun {
|
|
||||||
pub projectile: Projectile,
|
|
||||||
pub rate: f32,
|
|
||||||
pub rate_rng: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gun {
|
|
||||||
pub fn build(
|
|
||||||
self,
|
|
||||||
build_context: &mut ContentBuildContext,
|
|
||||||
content: &mut crate::Content,
|
|
||||||
) -> Result<super::Gun> {
|
|
||||||
let projectile_sprite_handle = match content.sprite_index.get(&self.projectile.sprite) {
|
|
||||||
None => bail!(
|
|
||||||
"projectile sprite `{}` doesn't exist",
|
|
||||||
self.projectile.sprite,
|
|
||||||
),
|
|
||||||
Some(t) => *t,
|
|
||||||
};
|
|
||||||
|
|
||||||
let impact_effect = match self.projectile.impact_effect {
|
|
||||||
Some(e) => Some(e.to_handle(build_context, content)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let expire_effect = match self.projectile.expire_effect {
|
|
||||||
Some(e) => Some(e.to_handle(build_context, content)?),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(super::Gun {
|
|
||||||
rate: self.rate,
|
|
||||||
rate_rng: self.rate_rng.unwrap_or(0.0),
|
|
||||||
projectile: super::Projectile {
|
|
||||||
force: self.projectile.force,
|
|
||||||
sprite: projectile_sprite_handle,
|
|
||||||
size: self.projectile.size,
|
|
||||||
size_rng: self.projectile.size_rng,
|
|
||||||
speed: self.projectile.speed,
|
|
||||||
speed_rng: self.projectile.speed_rng,
|
|
||||||
lifetime: self.projectile.lifetime,
|
|
||||||
lifetime_rng: self.projectile.lifetime_rng,
|
|
||||||
damage: self.projectile.damage,
|
|
||||||
|
|
||||||
// Divide by 2, so the angle matches the angle of the fire cone.
|
|
||||||
// This should ALWAYS be done in the content parser.
|
|
||||||
angle_rng: Deg(self.projectile.angle_rng / 2.0).into(),
|
|
||||||
impact_effect,
|
|
||||||
expire_effect,
|
|
||||||
collider: self.projectile.collider,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Projectile {
|
|
||||||
pub sprite: String,
|
|
||||||
pub size: f32,
|
|
||||||
pub size_rng: f32,
|
|
||||||
pub speed: f32,
|
|
||||||
pub speed_rng: f32,
|
|
||||||
pub lifetime: f32,
|
|
||||||
pub lifetime_rng: f32,
|
|
||||||
pub damage: f32,
|
|
||||||
pub angle_rng: f32,
|
|
||||||
pub impact_effect: Option<effect::syntax::EffectReference>,
|
|
||||||
pub expire_effect: Option<effect::syntax::EffectReference>,
|
|
||||||
pub collider: super::ProjectileCollider,
|
|
||||||
pub force: f32,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an outfit that may be attached to a ship.
|
/// Represents an outfit that may be attached to a ship.
|
||||||
@ -149,82 +69,6 @@ pub struct Outfit {
|
|||||||
|
|
||||||
/// Wait this many seconds after taking damage before regenerating shields
|
/// Wait this many seconds after taking damage before regenerating shields
|
||||||
pub shield_delay: f32,
|
pub shield_delay: f32,
|
||||||
|
|
||||||
/// This outfit's gun stats.
|
|
||||||
/// If this is some, this outfit requires a gun point.
|
|
||||||
pub gun: Option<Gun>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines a projectile's collider
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
|
||||||
pub enum ProjectileCollider {
|
|
||||||
/// A ball collider
|
|
||||||
#[serde(rename = "ball")]
|
|
||||||
Ball(BallCollider),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A simple ball-shaped collider, centered at the object's position
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
|
||||||
pub struct BallCollider {
|
|
||||||
/// The radius of this ball
|
|
||||||
pub radius: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents gun stats of an outfit.
|
|
||||||
/// If an outfit has this value, it requires a gun point.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Gun {
|
|
||||||
/// The projectile this gun produces
|
|
||||||
pub projectile: Projectile,
|
|
||||||
|
|
||||||
/// Average delay between projectiles, in seconds.
|
|
||||||
pub rate: f32,
|
|
||||||
|
|
||||||
/// Random variation of projectile delay, in seconds.
|
|
||||||
/// Each shot waits (rate += rate_rng).
|
|
||||||
pub rate_rng: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a projectile that a [`Gun`] produces.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Projectile {
|
|
||||||
/// The projectile sprite
|
|
||||||
pub sprite: SpriteHandle,
|
|
||||||
|
|
||||||
/// The average size of this projectile
|
|
||||||
/// (height in game units)
|
|
||||||
pub size: f32,
|
|
||||||
/// Random size variation
|
|
||||||
pub size_rng: f32,
|
|
||||||
|
|
||||||
/// The speed of this projectile, in game units / second
|
|
||||||
pub speed: f32,
|
|
||||||
/// Random speed variation
|
|
||||||
pub speed_rng: f32,
|
|
||||||
|
|
||||||
/// The lifespan of this projectile.
|
|
||||||
/// It will vanish if it lives this long without hitting anything.
|
|
||||||
pub lifetime: f32,
|
|
||||||
/// Random lifetime variation
|
|
||||||
pub lifetime_rng: f32,
|
|
||||||
|
|
||||||
/// The damage this projectile does
|
|
||||||
pub damage: f32,
|
|
||||||
|
|
||||||
/// The force this projectile applies
|
|
||||||
pub force: f32,
|
|
||||||
|
|
||||||
/// The angle variation of this projectile.
|
|
||||||
pub angle_rng: Rad<f32>,
|
|
||||||
|
|
||||||
/// The particle this projectile will spawn when it hits something
|
|
||||||
pub impact_effect: Option<EffectHandle>,
|
|
||||||
|
|
||||||
/// The particle this projectile will spawn when it expires
|
|
||||||
pub expire_effect: Option<EffectHandle>,
|
|
||||||
|
|
||||||
/// Collider parameters for this projectile
|
|
||||||
pub collider: ProjectileCollider,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::Build for Outfit {
|
impl crate::Build for Outfit {
|
||||||
@ -232,7 +76,7 @@ impl crate::Build for Outfit {
|
|||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
outfits: Self::InputSyntaxType,
|
outfits: Self::InputSyntaxType,
|
||||||
build_context: &mut ContentBuildContext,
|
_build_context: &mut ContentBuildContext,
|
||||||
content: &mut Content,
|
content: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (outfit_name, outfit) in outfits {
|
for (outfit_name, outfit) in outfits {
|
||||||
@ -240,16 +84,7 @@ impl crate::Build for Outfit {
|
|||||||
index: content.outfits.len(),
|
index: content.outfits.len(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let gun = match outfit.gun {
|
|
||||||
None => None,
|
|
||||||
Some(g) => Some(
|
|
||||||
g.build(build_context, content)
|
|
||||||
.with_context(|| format!("in outfit {}", outfit_name))?,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut o = Self {
|
let mut o = Self {
|
||||||
gun,
|
|
||||||
handle,
|
handle,
|
||||||
name: outfit_name.clone(),
|
name: outfit_name.clone(),
|
||||||
engine_thrust: 0.0,
|
engine_thrust: 0.0,
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use object::{ship::ShipPersonality, GameData, GameShipHandle};
|
use object::{
|
||||||
|
ship::{OutfitSet, ShipPersonality},
|
||||||
|
GameData, GameShipHandle,
|
||||||
|
};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
|
||||||
|
|
||||||
@ -9,7 +12,7 @@ use galactica_constants;
|
|||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
use galactica_render::RenderState;
|
use galactica_render::RenderState;
|
||||||
use galactica_world::{objects::ShipControls, util, ParticleBuilder, StepResources, World};
|
use galactica_world::{objects::ShipControls, ParticleBuilder, StepResources, World};
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
input: InputStatus,
|
input: InputStatus,
|
||||||
@ -33,35 +36,45 @@ impl Game {
|
|||||||
pub fn new(ct: content::Content) -> Self {
|
pub fn new(ct: content::Content) -> Self {
|
||||||
let mut gamedata = GameData::new(&ct);
|
let mut gamedata = GameData::new(&ct);
|
||||||
|
|
||||||
|
let ss = ct.get_ship(content::ShipHandle { index: 0 });
|
||||||
|
|
||||||
|
let mut o1 = OutfitSet::new(ss.space, &[]);
|
||||||
|
o1.add(&ct.get_outfit(content::OutfitHandle { index: 0 }));
|
||||||
|
o1.add(&ct.get_outfit(content::OutfitHandle { index: 1 }));
|
||||||
|
//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 player = gamedata.create_ship(
|
let player = gamedata.create_ship(
|
||||||
&ct,
|
&ct,
|
||||||
content::ShipHandle { index: 0 },
|
content::ShipHandle { index: 0 },
|
||||||
content::FactionHandle { index: 0 },
|
content::FactionHandle { index: 0 },
|
||||||
ShipPersonality::Player,
|
ShipPersonality::Player,
|
||||||
|
o1.clone(),
|
||||||
&content::SystemHandle { index: 0 },
|
&content::SystemHandle { index: 0 },
|
||||||
);
|
);
|
||||||
let s = gamedata.get_ship_mut(player).unwrap();
|
|
||||||
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 }));
|
|
||||||
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 }));
|
|
||||||
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 }));
|
|
||||||
|
|
||||||
gamedata.create_ship(
|
gamedata.create_ship(
|
||||||
&ct,
|
&ct,
|
||||||
content::ShipHandle { index: 0 },
|
content::ShipHandle { index: 0 },
|
||||||
content::FactionHandle { index: 1 },
|
content::FactionHandle { index: 1 },
|
||||||
ShipPersonality::Dummy,
|
ShipPersonality::Dummy,
|
||||||
|
OutfitSet::new(ss.space, &[]),
|
||||||
&content::SystemHandle { index: 0 },
|
&content::SystemHandle { index: 0 },
|
||||||
);
|
);
|
||||||
|
|
||||||
let a = gamedata.create_ship(
|
let mut o1 = OutfitSet::new(ss.space, &[]);
|
||||||
|
o1.add(&ct.get_outfit(content::OutfitHandle { index: 0 }));
|
||||||
|
//o1.add_gun(&ct, content::GunHandle { index: 0 });
|
||||||
|
|
||||||
|
gamedata.create_ship(
|
||||||
&ct,
|
&ct,
|
||||||
content::ShipHandle { index: 0 },
|
content::ShipHandle { index: 0 },
|
||||||
content::FactionHandle { index: 1 },
|
content::FactionHandle { index: 1 },
|
||||||
ShipPersonality::Point,
|
ShipPersonality::Point,
|
||||||
|
o1,
|
||||||
&content::SystemHandle { index: 0 },
|
&content::SystemHandle { index: 0 },
|
||||||
);
|
);
|
||||||
let s = gamedata.get_ship_mut(a).unwrap();
|
|
||||||
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 }));
|
|
||||||
|
|
||||||
let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 });
|
let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 });
|
||||||
|
|
||||||
@ -114,9 +127,7 @@ impl Game {
|
|||||||
pub fn update(&mut self) {
|
pub fn update(&mut self) {
|
||||||
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
|
||||||
|
|
||||||
self.gamedata.step(t);
|
let world_output = self.world.step(StepResources {
|
||||||
|
|
||||||
self.world.step(StepResources {
|
|
||||||
player: self.player,
|
player: self.player,
|
||||||
player_controls: ShipControls {
|
player_controls: ShipControls {
|
||||||
left: self.input.key_left,
|
left: self.input.key_left,
|
||||||
@ -136,12 +147,7 @@ impl Game {
|
|||||||
self.input.v_scroll = 0.0;
|
self.input.v_scroll = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.camera.pos = {
|
self.camera.pos = world_output.player_position;
|
||||||
let o = self.world.get_ship(self.player).unwrap();
|
|
||||||
let r = self.world.get_rigid_body(o.rigid_body).unwrap();
|
|
||||||
util::rigidbody_position(r)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.last_update = Instant::now();
|
self.last_update = Instant::now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{handles::GameShipHandle, ship::Ship, ship::ShipPersonality};
|
use crate::{
|
||||||
|
handles::GameShipHandle,
|
||||||
|
ship::Ship,
|
||||||
|
ship::{OutfitSet, ShipPersonality},
|
||||||
|
};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
|
|
||||||
/// Keeps track of all objects in the galaxy.
|
/// Keeps track of all objects in the galaxy.
|
||||||
@ -40,6 +44,7 @@ impl GameData {
|
|||||||
ship: content::ShipHandle,
|
ship: content::ShipHandle,
|
||||||
faction: content::FactionHandle,
|
faction: content::FactionHandle,
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
|
outfits: OutfitSet,
|
||||||
system: &content::SystemHandle,
|
system: &content::SystemHandle,
|
||||||
) -> GameShipHandle {
|
) -> GameShipHandle {
|
||||||
let handle = GameShipHandle {
|
let handle = GameShipHandle {
|
||||||
@ -48,19 +53,15 @@ impl GameData {
|
|||||||
};
|
};
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
|
|
||||||
self.ships
|
self.ships.insert(
|
||||||
.insert(handle, Ship::new(ct, handle, ship, faction, personality));
|
handle,
|
||||||
|
Ship::new(ct, handle, ship, faction, personality, outfits),
|
||||||
|
);
|
||||||
self.system_ship_table.get_mut(system).unwrap().push(handle);
|
self.system_ship_table.get_mut(system).unwrap().push(handle);
|
||||||
self.ship_system_table.insert(handle, *system);
|
self.ship_system_table.insert(handle, *system);
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn step(&mut self, t: f32) {
|
|
||||||
for (_, s) in &mut self.ships {
|
|
||||||
s.step(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public getters
|
// Public getters
|
||||||
|
@ -14,10 +14,6 @@ pub enum OutfitAddResult {
|
|||||||
/// outfits may need outfit AND weapon space. In these cases, this result
|
/// outfits may need outfit AND weapon space. In these cases, this result
|
||||||
/// should name the "most specific" kind of space we lack.
|
/// should name the "most specific" kind of space we lack.
|
||||||
NotEnoughSpace(String),
|
NotEnoughSpace(String),
|
||||||
|
|
||||||
/// An outfit couldn't be added because there weren't enough points for it
|
|
||||||
/// (e.g, gun points, turret points, etc)
|
|
||||||
NotEnoughPoints(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Possible outcomes when removing an outfit
|
/// Possible outcomes when removing an outfit
|
||||||
@ -41,9 +37,6 @@ pub(crate) struct ShieldGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// This struct keeps track of a ship's outfit loadout.
|
/// This struct keeps track of a ship's outfit loadout.
|
||||||
/// This is a fairly static data structure: it does not keep track of cooldowns,
|
|
||||||
/// shield damage, etc. It only provides an interface for static stats which are
|
|
||||||
/// then used elsewhere.
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct OutfitSet {
|
pub struct OutfitSet {
|
||||||
/// What outfits does this statsum contain?
|
/// What outfits does this statsum contain?
|
||||||
@ -75,7 +68,7 @@ pub struct OutfitSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OutfitSet {
|
impl OutfitSet {
|
||||||
pub(super) fn new(available_space: OutfitSpace, gun_points: &[GunPoint]) -> Self {
|
pub fn new(available_space: OutfitSpace, gun_points: &[GunPoint]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
outfits: HashMap::new(),
|
outfits: HashMap::new(),
|
||||||
total_space: available_space,
|
total_space: available_space,
|
||||||
@ -89,26 +82,11 @@ impl OutfitSet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn add(&mut self, o: &content::Outfit) -> OutfitAddResult {
|
pub fn add(&mut self, o: &content::Outfit) -> OutfitAddResult {
|
||||||
if !(self.total_space - self.used_space).can_contain(&o.space) {
|
if !(self.total_space - self.used_space).can_contain(&o.space) {
|
||||||
return OutfitAddResult::NotEnoughSpace("TODO".to_string());
|
return OutfitAddResult::NotEnoughSpace("TODO".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and return as fast as possible,
|
|
||||||
// BEFORE we make any changes.
|
|
||||||
if o.gun.is_some() {
|
|
||||||
let mut added = false;
|
|
||||||
for (_, outfit) in &mut self.gun_points {
|
|
||||||
if outfit.is_none() {
|
|
||||||
*outfit = Some(o.handle);
|
|
||||||
added = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !added {
|
|
||||||
return OutfitAddResult::NotEnoughPoints("gun".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.used_space += o.space;
|
self.used_space += o.space;
|
||||||
|
|
||||||
self.engine_thrust += o.engine_thrust;
|
self.engine_thrust += o.engine_thrust;
|
||||||
@ -129,7 +107,7 @@ impl OutfitSet {
|
|||||||
return OutfitAddResult::Ok;
|
return OutfitAddResult::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn remove(&mut self, o: &content::Outfit) -> OutfitRemoveResult {
|
pub fn remove(&mut self, o: &content::Outfit) -> OutfitRemoveResult {
|
||||||
if !self.outfits.contains_key(&o.handle) {
|
if !self.outfits.contains_key(&o.handle) {
|
||||||
return OutfitRemoveResult::NotExist;
|
return OutfitRemoveResult::NotExist;
|
||||||
} else {
|
} else {
|
||||||
@ -199,12 +177,6 @@ impl OutfitSet {
|
|||||||
self.shield_generators.iter().map(|x| x.generation).sum()
|
self.shield_generators.iter().map(|x| x.generation).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the outfit attached to the given gun point
|
|
||||||
/// Will panic if this gunpoint is not in this outfit set.
|
|
||||||
pub fn get_gun(&self, point: &GunPoint) -> Option<OutfitHandle> {
|
|
||||||
self.gun_points.get(point).unwrap().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Total available outfit space
|
/// Total available outfit space
|
||||||
pub fn get_total_space(&self) -> &OutfitSpace {
|
pub fn get_total_space(&self) -> &OutfitSpace {
|
||||||
&self.total_space
|
&self.total_space
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
use std::{collections::HashMap, time::Instant};
|
use std::time::Instant;
|
||||||
|
|
||||||
use crate::GameShipHandle;
|
use crate::GameShipHandle;
|
||||||
|
|
||||||
use super::{OutfitSet, ShipPersonality};
|
use super::{OutfitSet, ShipPersonality};
|
||||||
use content::GunPoint;
|
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use rand::{rngs::ThreadRng, Rng};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
@ -21,8 +19,6 @@ pub struct Ship {
|
|||||||
// TODO: unified ship stats struct, like outfit space
|
// TODO: unified ship stats struct, like outfit space
|
||||||
hull: f32,
|
hull: f32,
|
||||||
shields: f32,
|
shields: f32,
|
||||||
gun_cooldowns: HashMap<GunPoint, f32>,
|
|
||||||
rng: ThreadRng,
|
|
||||||
|
|
||||||
// Utility values
|
// Utility values
|
||||||
/// The last time this ship was damaged
|
/// The last time this ship was damaged
|
||||||
@ -36,61 +32,29 @@ impl Ship {
|
|||||||
ct_handle: content::ShipHandle,
|
ct_handle: content::ShipHandle,
|
||||||
faction: content::FactionHandle,
|
faction: content::FactionHandle,
|
||||||
personality: ShipPersonality,
|
personality: ShipPersonality,
|
||||||
|
outfits: OutfitSet,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let s = ct.get_ship(ct_handle);
|
let s = ct.get_ship(ct_handle);
|
||||||
|
let shields = outfits.get_shield_strength();
|
||||||
Ship {
|
Ship {
|
||||||
handle,
|
handle,
|
||||||
ct_handle,
|
ct_handle,
|
||||||
faction,
|
faction,
|
||||||
outfits: OutfitSet::new(s.space, &s.guns),
|
outfits,
|
||||||
personality,
|
personality,
|
||||||
last_hit: Instant::now(),
|
last_hit: Instant::now(),
|
||||||
rng: rand::thread_rng(),
|
|
||||||
|
|
||||||
// Initial stats
|
// Initial stats
|
||||||
hull: s.hull,
|
hull: s.hull,
|
||||||
shields: 0.0,
|
shields,
|
||||||
gun_cooldowns: s.guns.iter().map(|x| (x.clone(), 0.0)).collect(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an outfit to this ship
|
|
||||||
pub fn add_outfit(&mut self, o: &content::Outfit) -> super::OutfitAddResult {
|
|
||||||
self.outfits.add(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove an outfit from this ship
|
|
||||||
pub fn remove_outfit(&mut self, o: &content::Outfit) -> super::OutfitRemoveResult {
|
|
||||||
self.outfits.remove(o)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If this ship is dead, it will be removed from the game.
|
/// If this ship is dead, it will be removed from the game.
|
||||||
pub fn is_dead(&self) -> bool {
|
pub fn is_dead(&self) -> bool {
|
||||||
self.hull <= 0.0
|
self.hull <= 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to fire a gun.
|
|
||||||
/// Will panic if `which` isn't a point on this ship.
|
|
||||||
/// Returns `true` if this gun was fired,
|
|
||||||
/// and `false` if it is on cooldown or empty.
|
|
||||||
pub fn fire_gun(&mut self, ct: &content::Content, which: &GunPoint) -> bool {
|
|
||||||
let c = self.gun_cooldowns.get_mut(which).unwrap();
|
|
||||||
|
|
||||||
if *c > 0.0 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let g = self.outfits.get_gun(which);
|
|
||||||
if g.is_some() {
|
|
||||||
let g = ct.get_outfit(g.unwrap());
|
|
||||||
let gun = g.gun.as_ref().unwrap();
|
|
||||||
*c = 0f32.max(gun.rate + self.rng.gen_range(-gun.rate_rng..=gun.rate_rng));
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hit this ship with the given amount of damage
|
/// Hit this ship with the given amount of damage
|
||||||
pub fn apply_damage(&mut self, mut d: f32) {
|
pub fn apply_damage(&mut self, mut d: f32) {
|
||||||
if self.is_dead() {
|
if self.is_dead() {
|
||||||
@ -108,14 +72,6 @@ impl Ship {
|
|||||||
|
|
||||||
/// Update this ship's state by `t` seconds
|
/// Update this ship's state by `t` seconds
|
||||||
pub fn step(&mut self, t: f32) {
|
pub fn step(&mut self, t: f32) {
|
||||||
// Cooldown guns
|
|
||||||
for (_, c) in &mut self.gun_cooldowns {
|
|
||||||
if *c > 0.0 {
|
|
||||||
*c -= t;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regenerate shields
|
|
||||||
let time_since = self.last_hit.elapsed().as_secs_f32();
|
let time_since = self.last_hit.elapsed().as_secs_f32();
|
||||||
if self.shields != self.outfits.get_shield_strength() {
|
if self.shields != self.outfits.get_shield_strength() {
|
||||||
for g in self.outfits.iter_shield_generators() {
|
for g in self.outfits.iter_shield_generators() {
|
||||||
|
@ -298,6 +298,10 @@ impl ShipWorldObject {
|
|||||||
rigid_body
|
rigid_body
|
||||||
.apply_torque_impulse(ship.get_outfits().get_steer_power() * 100.0 * res.t, true);
|
.apply_torque_impulse(ship.get_outfits().get_steer_power() * 100.0 * res.t, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//for i in self.ship.outfits.iter_guns() {
|
||||||
|
// i.cooldown -= t;
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use crate::{objects::ShipControls, ParticleBuilder};
|
use crate::{objects::ShipControls, ParticleBuilder};
|
||||||
|
use cgmath::Point2;
|
||||||
use galactica_content::Content;
|
use galactica_content::Content;
|
||||||
use galactica_gameobject::{GameData, GameShipHandle};
|
use galactica_gameobject::{GameData, GameShipHandle};
|
||||||
|
|
||||||
@ -23,3 +24,10 @@ pub struct StepResources<'a> {
|
|||||||
/// The ship that the player controls
|
/// The ship that the player controls
|
||||||
pub player: GameShipHandle,
|
pub player: GameShipHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return values after computing time steps
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct StepOutput {
|
||||||
|
/// The player's position in world coordinates
|
||||||
|
pub player_position: Point2<f32>,
|
||||||
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero};
|
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero};
|
||||||
use content::{GunPoint, OutfitHandle};
|
|
||||||
use crossbeam::channel::Receiver;
|
use crossbeam::channel::Receiver;
|
||||||
use nalgebra::{point, vector};
|
use nalgebra::{point, vector};
|
||||||
use object::{ship::Ship, GameData, GameShipHandle};
|
use object::{ship::Ship, GameData, GameShipHandle};
|
||||||
@ -16,7 +15,7 @@ use crate::{
|
|||||||
objects::{ProjectileWorldObject, ShipWorldObject},
|
objects::{ProjectileWorldObject, ShipWorldObject},
|
||||||
util,
|
util,
|
||||||
wrapper::Wrapper,
|
wrapper::Wrapper,
|
||||||
ParticleBuilder, StepResources,
|
ParticleBuilder, StepOutput, StepResources,
|
||||||
};
|
};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
use galactica_gameobject as object;
|
use galactica_gameobject as object;
|
||||||
@ -24,7 +23,7 @@ use galactica_gameobject as object;
|
|||||||
/// Manages the physics state of one system
|
/// Manages the physics state of one system
|
||||||
pub struct World {
|
pub struct World {
|
||||||
/// The system this world is attached to
|
/// The system this world is attached to
|
||||||
_system: content::SystemHandle,
|
system: content::SystemHandle,
|
||||||
|
|
||||||
wrapper: Wrapper,
|
wrapper: Wrapper,
|
||||||
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
|
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
|
||||||
@ -74,6 +73,67 @@ impl<'a> World {
|
|||||||
self.ships.remove(&h);
|
self.ships.remove(&h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/// Add a projectile fired from a ship
|
||||||
|
fn add_projectiles(
|
||||||
|
&mut self,
|
||||||
|
s: ShipPhysicsHandle,
|
||||||
|
p: Vec<(ProjectileWorldObject, content::GunPoint)>,
|
||||||
|
) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
for (projectile, point) in p {
|
||||||
|
let r = self.get_ship_body(s).unwrap().1;
|
||||||
|
|
||||||
|
let ship_pos = util::rigidbody_position(r);
|
||||||
|
let ship_rot = util::rigidbody_rotation(r);
|
||||||
|
let ship_vel = util::rigidbody_velocity(r);
|
||||||
|
let ship_ang = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 });
|
||||||
|
|
||||||
|
let pos = ship_pos + (Matrix2::from_angle(-ship_ang) * point.pos.to_vec());
|
||||||
|
|
||||||
|
let spread: Rad<f32> = Deg(
|
||||||
|
rng.gen_range(-projectile.content.angle_rng.0..=projectile.content.angle_rng.0)
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let vel = ship_vel
|
||||||
|
+ (Matrix2::from_angle(-ship_ang + spread)
|
||||||
|
* Vector2 {
|
||||||
|
x: 0.0,
|
||||||
|
y: projectile.content.speed
|
||||||
|
+ rng.gen_range(
|
||||||
|
-projectile.content.speed_rng..=projectile.content.speed_rng,
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
let rigid_body = RigidBodyBuilder::kinematic_velocity_based()
|
||||||
|
.translation(vector![pos.x, pos.y])
|
||||||
|
.rotation(-ship_ang.0)
|
||||||
|
.linvel(vector![vel.x, vel.y])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let collider = match &projectile.content.collider {
|
||||||
|
content::ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
||||||
|
.sensor(true)
|
||||||
|
.active_events(ActiveEvents::COLLISION_EVENTS)
|
||||||
|
.build(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rigid_body = self.wrapper.rigid_body_set.insert(rigid_body);
|
||||||
|
let collider = self.wrapper.collider_set.insert_with_parent(
|
||||||
|
collider,
|
||||||
|
rigid_body,
|
||||||
|
&mut self.wrapper.rigid_body_set,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.projectiles.insert(
|
||||||
|
collider.clone(),
|
||||||
|
ProjectileWorldObject::new(projectile, rigid_body, collider),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
fn collide_projectile_ship(
|
fn collide_projectile_ship(
|
||||||
&mut self,
|
&mut self,
|
||||||
res: &mut StepResources,
|
res: &mut StepResources,
|
||||||
@ -92,17 +152,10 @@ impl<'a> World {
|
|||||||
|
|
||||||
let ship_d = res.dt.get_ship_mut(ship.data_handle).unwrap();
|
let ship_d = res.dt.get_ship_mut(ship.data_handle).unwrap();
|
||||||
|
|
||||||
let f = res.ct.get_faction(projectile.faction);
|
// TODO: check faction
|
||||||
let r = f.relationships.get(&ship_d.get_faction()).unwrap();
|
|
||||||
let destory_projectile = match r {
|
|
||||||
content::Relationship::Hostile => {
|
|
||||||
ship_d.apply_damage(projectile.content.damage);
|
ship_d.apply_damage(projectile.content.damage);
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if destory_projectile {
|
if true {
|
||||||
let pr = self
|
let pr = self
|
||||||
.wrapper
|
.wrapper
|
||||||
.rigid_body_set
|
.rigid_body_set
|
||||||
@ -157,14 +210,14 @@ impl<'a> World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Public methods
|
// Public methods
|
||||||
impl World {
|
impl<'a> World {
|
||||||
/// Create a new physics system
|
/// Create a new physics system
|
||||||
pub fn new(ct: &content::Content, dt: &GameData, system: content::SystemHandle) -> Self {
|
pub fn new(ct: &content::Content, dt: &GameData, system: content::SystemHandle) -> Self {
|
||||||
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
|
||||||
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
let (contact_force_send, _) = crossbeam::channel::unbounded();
|
||||||
|
|
||||||
let mut w = Self {
|
let mut w = Self {
|
||||||
_system: system,
|
system,
|
||||||
wrapper: Wrapper::new(),
|
wrapper: Wrapper::new(),
|
||||||
projectiles: HashMap::new(),
|
projectiles: HashMap::new(),
|
||||||
ships: HashMap::new(),
|
ships: HashMap::new(),
|
||||||
@ -230,125 +283,45 @@ impl World {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run ship updates:
|
/// Step this physics system by `t` seconds
|
||||||
/// - updates ship controls (runs behaviors)
|
pub fn step(&mut self, mut res: StepResources) -> StepOutput {
|
||||||
/// - applies ship controls
|
let mut output = StepOutput {
|
||||||
/// - creates projectiles
|
player_position: Point2 { x: 0.0, y: 0.0 },
|
||||||
fn step_ships(&mut self, res: &mut StepResources) {
|
};
|
||||||
let mut projectiles = Vec::new();
|
|
||||||
|
// Run ship updates
|
||||||
|
// TODO: maybe reorganize projectile creation?
|
||||||
|
//let mut projectiles = Vec::new();
|
||||||
let mut to_remove = Vec::new();
|
let mut to_remove = Vec::new();
|
||||||
for (_, ship_object) in &mut self.ships {
|
for (_, s) in &mut self.ships {
|
||||||
|
let r = &mut self.wrapper.rigid_body_set[s.rigid_body];
|
||||||
|
let c = &mut self.wrapper.collider_set[s.collider];
|
||||||
|
|
||||||
|
if s.data_handle == res.player {
|
||||||
|
s.controls = res.player_controls.clone();
|
||||||
|
output.player_position = util::rigidbody_position(r);
|
||||||
|
} else {
|
||||||
|
s.update_controls(&res);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: unified step info struct
|
||||||
|
s.step(&mut res, r, c);
|
||||||
|
//if s.controls.guns {
|
||||||
|
// projectiles.push((s.physics_handle, s.ship.fire_guns()));
|
||||||
|
//}
|
||||||
|
|
||||||
//if s.remove_from_world() {
|
//if s.remove_from_world() {
|
||||||
// to_remove.push(s.physics_handle);
|
// to_remove.push(s.physics_handle);
|
||||||
// continue;
|
// continue;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
let rigid_body = &mut self.wrapper.rigid_body_set[ship_object.rigid_body];
|
|
||||||
let collider = &mut self.wrapper.collider_set[ship_object.collider];
|
|
||||||
|
|
||||||
if ship_object.data_handle == res.player {
|
|
||||||
ship_object.controls = res.player_controls.clone();
|
|
||||||
} else {
|
|
||||||
ship_object.update_controls(&res);
|
|
||||||
}
|
}
|
||||||
|
//for (s, p) in projectiles {
|
||||||
// TODO: unified step info struct
|
// self.add_projectiles(s, p);
|
||||||
ship_object.step(res, rigid_body, collider);
|
//}
|
||||||
if ship_object.controls.guns {
|
|
||||||
let ship_data = res.dt.get_ship_mut(ship_object.data_handle).unwrap();
|
|
||||||
|
|
||||||
// TODO: don't allocate here. This is a hack to satisfy the borrow checker,
|
|
||||||
// convert this to a refcell or do the replace dance.
|
|
||||||
let pairs: Vec<(GunPoint, Option<OutfitHandle>)> = ship_data
|
|
||||||
.get_outfits()
|
|
||||||
.iter_gun_points()
|
|
||||||
.map(|(p, o)| (p.clone(), o.clone()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for (gun, outfit) in pairs {
|
|
||||||
if ship_data.fire_gun(res.ct, &gun) {
|
|
||||||
projectiles.push((
|
|
||||||
ship_object.data_handle,
|
|
||||||
ship_object.rigid_body,
|
|
||||||
gun.clone(),
|
|
||||||
outfit.unwrap(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove ships that don't exist
|
|
||||||
for s in to_remove {
|
for s in to_remove {
|
||||||
self.remove_ship(s);
|
self.remove_ship(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create projectiles
|
|
||||||
for (ship_dat, rigid_body, gun_point, outfit) in projectiles {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
let rigid_body = self.get_rigid_body(rigid_body).unwrap();
|
|
||||||
|
|
||||||
let ship_dat = res.dt.get_ship(ship_dat).unwrap();
|
|
||||||
let ship_pos = util::rigidbody_position(rigid_body);
|
|
||||||
let ship_rot = util::rigidbody_rotation(rigid_body);
|
|
||||||
let ship_vel = util::rigidbody_velocity(rigid_body);
|
|
||||||
let ship_ang = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 });
|
|
||||||
|
|
||||||
let pos = ship_pos + (Matrix2::from_angle(-ship_ang) * gun_point.pos.to_vec());
|
|
||||||
|
|
||||||
let outfit = res.ct.get_outfit(outfit);
|
|
||||||
let outfit = outfit.gun.as_ref().unwrap();
|
|
||||||
|
|
||||||
let spread: Rad<f32> =
|
|
||||||
Deg(rng.gen_range(-outfit.projectile.angle_rng.0..=outfit.projectile.angle_rng.0))
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let vel = ship_vel
|
|
||||||
+ (Matrix2::from_angle(-ship_ang + spread)
|
|
||||||
* Vector2 {
|
|
||||||
x: 0.0,
|
|
||||||
y: outfit.projectile.speed
|
|
||||||
+ rng.gen_range(
|
|
||||||
-outfit.projectile.speed_rng..=outfit.projectile.speed_rng,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
let rigid_body = RigidBodyBuilder::kinematic_velocity_based()
|
|
||||||
.translation(vector![pos.x, pos.y])
|
|
||||||
.rotation(-ship_ang.0)
|
|
||||||
.linvel(vector![vel.x, vel.y])
|
|
||||||
.build();
|
|
||||||
|
|
||||||
let collider = match &outfit.projectile.collider {
|
|
||||||
content::ProjectileCollider::Ball(b) => ColliderBuilder::ball(b.radius)
|
|
||||||
.sensor(true)
|
|
||||||
.active_events(ActiveEvents::COLLISION_EVENTS)
|
|
||||||
.build(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let rigid_body = self.wrapper.rigid_body_set.insert(rigid_body);
|
|
||||||
let collider = self.wrapper.collider_set.insert_with_parent(
|
|
||||||
collider,
|
|
||||||
rigid_body,
|
|
||||||
&mut self.wrapper.rigid_body_set,
|
|
||||||
);
|
|
||||||
|
|
||||||
self.projectiles.insert(
|
|
||||||
collider.clone(),
|
|
||||||
ProjectileWorldObject::new(
|
|
||||||
outfit.projectile.clone(),
|
|
||||||
rigid_body,
|
|
||||||
ship_dat.get_faction(),
|
|
||||||
collider,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Step this physics system by `t` seconds
|
|
||||||
pub fn step(&mut self, mut res: StepResources) {
|
|
||||||
self.step_ships(&mut res);
|
|
||||||
|
|
||||||
// Update physics
|
// Update physics
|
||||||
self.wrapper.step(res.t, &self.collision_handler);
|
self.wrapper.step(res.t, &self.collision_handler);
|
||||||
|
|
||||||
@ -417,12 +390,10 @@ impl World {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public getters
|
|
||||||
impl World {
|
|
||||||
/// Get a ship physics object
|
|
||||||
pub fn get_ship(&self, ship: GameShipHandle) -> Option<&ShipWorldObject> {
|
pub fn get_ship(&self, ship: GameShipHandle) -> Option<&ShipWorldObject> {
|
||||||
self.ships.get(&ship)
|
self.ships.get(&ship)
|
||||||
}
|
}
|
||||||
@ -447,12 +418,12 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all ships in this physics system
|
/// Iterate over all ships in this physics system
|
||||||
pub fn iter_ships(&self) -> impl Iterator<Item = &ShipWorldObject> + '_ {
|
pub fn iter_ships(&'a self) -> impl Iterator<Item = &ShipWorldObject> + '_ {
|
||||||
self.ships.values()
|
self.ships.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all ships in this physics system
|
/// Iterate over all ships in this physics system
|
||||||
pub fn iter_projectiles(&self) -> impl Iterator<Item = &ProjectileWorldObject> + '_ {
|
pub fn iter_projectiles(&'a self) -> impl Iterator<Item = &ProjectileWorldObject> + '_ {
|
||||||
self.projectiles.values()
|
self.projectiles.values()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user