Compare commits
3 Commits
1c3f64a132
...
1b9e1f2877
Author | SHA1 | Date |
---|---|---|
Mark | 1b9e1f2877 | |
Mark | 9e0551ae12 | |
Mark | acb3315392 |
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -9,6 +11,8 @@ pub(crate) mod syntax {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: user-defined space values
|
||||||
|
|
||||||
/// Represents outfit space, either that available in a ship
|
/// Represents outfit space, either that available in a ship
|
||||||
/// or that used by an outfit.
|
/// or that used by an outfit.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -25,7 +29,7 @@ pub struct OutfitSpace {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OutfitSpace {
|
impl OutfitSpace {
|
||||||
/// Make a new, zero OutfitSpace
|
/// Make a new, zeroed OutfitSpace
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
outfit: 0,
|
outfit: 0,
|
||||||
|
@ -34,28 +38,54 @@ impl OutfitSpace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Does this outfit contain `smaller`?
|
/// Can this outfit contain `smaller`?
|
||||||
pub fn can_contain(&self, smaller: &Self) -> bool {
|
pub fn can_contain(&self, smaller: &Self) -> bool {
|
||||||
self.outfit >= (smaller.outfit + smaller.weapon + smaller.engine)
|
self.outfit >= smaller.outfit
|
||||||
&& self.weapon >= smaller.weapon
|
&& self.weapon >= smaller.weapon
|
||||||
&& self.engine >= smaller.engine
|
&& self.engine >= smaller.engine
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Free outfit space
|
impl Sub for OutfitSpace {
|
||||||
pub fn free(&mut self, rhs: &Self) {
|
type Output = Self;
|
||||||
self.outfit += rhs.outfit + rhs.weapon + rhs.engine;
|
|
||||||
self.weapon += rhs.weapon;
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
self.engine += rhs.engine;
|
OutfitSpace {
|
||||||
|
outfit: self.outfit - rhs.outfit,
|
||||||
|
weapon: self.weapon - rhs.weapon,
|
||||||
|
engine: self.engine - rhs.engine,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Occupy outfit space
|
impl SubAssign for OutfitSpace {
|
||||||
pub fn occupy(&mut self, rhs: &Self) {
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
self.outfit -= rhs.outfit + rhs.weapon + rhs.engine;
|
self.outfit -= rhs.outfit;
|
||||||
self.weapon -= rhs.weapon;
|
self.weapon -= rhs.weapon;
|
||||||
self.engine -= rhs.engine;
|
self.engine -= rhs.engine;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Add for OutfitSpace {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
OutfitSpace {
|
||||||
|
outfit: self.outfit + rhs.outfit,
|
||||||
|
weapon: self.weapon + rhs.weapon,
|
||||||
|
engine: self.engine + rhs.engine,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign for OutfitSpace {
|
||||||
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.outfit += rhs.outfit;
|
||||||
|
self.weapon += rhs.weapon;
|
||||||
|
self.engine += rhs.engine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<syntax::OutfitSpace> for OutfitSpace {
|
impl From<syntax::OutfitSpace> for OutfitSpace {
|
||||||
fn from(value: syntax::OutfitSpace) -> Self {
|
fn from(value: syntax::OutfitSpace) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, hash::Hash};
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use cgmath::Point2;
|
use cgmath::Point2;
|
||||||
|
@ -160,11 +160,28 @@ pub struct EnginePoint {
|
||||||
/// A gun point on a ship.
|
/// A gun point on a ship.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GunPoint {
|
pub struct GunPoint {
|
||||||
|
/// This gun point's index in this ship
|
||||||
|
pub idx: u32,
|
||||||
|
|
||||||
/// This gun point's position, in game units,
|
/// This gun point's position, in game units,
|
||||||
/// relative to the ship's center.
|
/// relative to the ship's center.
|
||||||
pub pos: Point2<f32>,
|
pub pos: Point2<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eq for GunPoint {}
|
||||||
|
impl PartialEq for GunPoint {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.idx == other.idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a hashmap of these in OutfitSet
|
||||||
|
impl Hash for GunPoint {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.idx.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parameters for a ship's collapse sequence
|
/// Parameters for a ship's collapse sequence
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ShipCollapse {
|
pub struct ShipCollapse {
|
||||||
|
@ -345,6 +362,18 @@ impl crate::Build for Ship {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: document this
|
||||||
|
let mut guns = Vec::new();
|
||||||
|
for g in ship.guns {
|
||||||
|
guns.push(GunPoint {
|
||||||
|
idx: guns.len() as u32,
|
||||||
|
pos: Point2 {
|
||||||
|
x: g.x * size * aspect / 2.0,
|
||||||
|
y: g.y * size / 2.0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ct.ships.push(Self {
|
ct.ships.push(Self {
|
||||||
aspect,
|
aspect,
|
||||||
collapse,
|
collapse,
|
||||||
|
@ -370,16 +399,7 @@ impl crate::Build for Ship {
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
|
|
||||||
guns: ship
|
guns,
|
||||||
.guns
|
|
||||||
.iter()
|
|
||||||
.map(|e| GunPoint {
|
|
||||||
pos: Point2 {
|
|
||||||
x: e.x * size * aspect / 2.0,
|
|
||||||
y: e.y * size / 2.0,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
|
|
||||||
collision: Collision {
|
collision: Collision {
|
||||||
indices: (0..ship.collision.len())
|
indices: (0..ship.collision.len())
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handles::GameShipHandle,
|
||||||
|
ship::Ship,
|
||||||
|
ship::{OutfitSet, ShipPersonality},
|
||||||
|
};
|
||||||
|
use content::ShipHandle;
|
||||||
|
use galactica_content as content;
|
||||||
|
|
||||||
|
/// Keeps track of all objects in the galaxy.
|
||||||
|
/// This struct does NO physics, it keeps track of data exclusively.
|
||||||
|
pub struct GameData {
|
||||||
|
// Universal counter.
|
||||||
|
// Used to create unique handles for game objects.
|
||||||
|
index: u64,
|
||||||
|
ships: HashMap<GameShipHandle, Ship>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GameData {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
index: 0,
|
||||||
|
ships: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawn a ship
|
||||||
|
pub fn create_ship(
|
||||||
|
&mut self,
|
||||||
|
ct: &content::Content,
|
||||||
|
ship: ShipHandle,
|
||||||
|
faction: content::FactionHandle,
|
||||||
|
personality: ShipPersonality,
|
||||||
|
outfits: OutfitSet,
|
||||||
|
) -> GameShipHandle {
|
||||||
|
let handle = GameShipHandle { index: self.index };
|
||||||
|
self.index += 1;
|
||||||
|
self.ships
|
||||||
|
.insert(handle, Ship::new(ct, ship, faction, personality, outfits));
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ship(&self, handle: GameShipHandle) -> Option<&Ship> {
|
||||||
|
self.ships.get(&handle)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
/// A lightweight representation of a ship in the galaxy
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub struct GameShipHandle {
|
||||||
|
pub(crate) index: u64,
|
||||||
|
}
|
|
@ -4,14 +4,11 @@
|
||||||
//! of every ship in the game, but it has no understanding of physics.
|
//! of every ship in the game, but it has no understanding of physics.
|
||||||
//! That is done in `galactica_world`.
|
//! That is done in `galactica_world`.
|
||||||
|
|
||||||
mod outfits;
|
mod gamedata;
|
||||||
mod projectile;
|
mod handles;
|
||||||
mod ship;
|
pub mod ship;
|
||||||
mod system;
|
mod system;
|
||||||
mod systemobject;
|
|
||||||
|
|
||||||
pub use outfits::{OutfitSet, OutfitStatSum, ShipGun};
|
pub use gamedata::*;
|
||||||
pub use projectile::Projectile;
|
pub use handles::*;
|
||||||
pub use ship::Ship;
|
pub use system::{System, SystemObject};
|
||||||
pub use system::System;
|
|
||||||
pub use systemobject::SystemObject;
|
|
||||||
|
|
|
@ -1,203 +0,0 @@
|
||||||
use content::{EnginePoint, OutfitHandle, SpriteHandle};
|
|
||||||
use galactica_content as content;
|
|
||||||
|
|
||||||
/// Represents a gun attached to a specific ship at a certain gunpoint.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ShipGun {
|
|
||||||
/// The kind of gun this is
|
|
||||||
pub kind: content::Gun,
|
|
||||||
|
|
||||||
/// How many seconds we must wait before this gun can fire
|
|
||||||
pub cooldown: f32,
|
|
||||||
|
|
||||||
/// Index of the gunpoint this gun is attached to
|
|
||||||
pub point: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShipGun {
|
|
||||||
/// Make a new shipgun
|
|
||||||
pub fn new(kind: &content::Gun, point: usize) -> Self {
|
|
||||||
Self {
|
|
||||||
kind: kind.clone(),
|
|
||||||
point,
|
|
||||||
cooldown: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This struct keeps track of the combined stats of a set of outfits.
|
|
||||||
/// It does NOT check for sanity (e.g, removing an outfit that was never added)
|
|
||||||
/// That is handled by ShipOutfits.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct OutfitStatSum {
|
|
||||||
pub engine_thrust: f32,
|
|
||||||
pub steer_power: f32,
|
|
||||||
pub engine_flare_sprites: Vec<content::SpriteHandle>,
|
|
||||||
pub shield_strength: f32,
|
|
||||||
|
|
||||||
// Delay, generation
|
|
||||||
pub shield_generators: Vec<(OutfitHandle, f32, f32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutfitStatSum {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
engine_thrust: 0.0,
|
|
||||||
steer_power: 0.0,
|
|
||||||
engine_flare_sprites: Vec::new(),
|
|
||||||
shield_strength: 0.0,
|
|
||||||
shield_generators: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&mut self, o: &content::Outfit) {
|
|
||||||
self.engine_thrust += o.engine_thrust;
|
|
||||||
if let Some(t) = o.engine_flare_sprite {
|
|
||||||
self.engine_flare_sprites.push(t);
|
|
||||||
};
|
|
||||||
self.steer_power += o.steer_power;
|
|
||||||
self.shield_strength += o.shield_strength;
|
|
||||||
self.shield_generators
|
|
||||||
.push((o.handle, o.shield_delay, o.shield_generation));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, o: &content::Outfit) {
|
|
||||||
self.engine_thrust -= o.engine_thrust;
|
|
||||||
if let Some(t) = o.engine_flare_sprite {
|
|
||||||
self.engine_flare_sprites.remove(
|
|
||||||
self.engine_flare_sprites
|
|
||||||
.iter()
|
|
||||||
.position(|x| *x == t)
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.steer_power -= o.steer_power;
|
|
||||||
self.shield_strength -= o.shield_strength;
|
|
||||||
{
|
|
||||||
// Assume such an index exists
|
|
||||||
let index = self
|
|
||||||
.shield_generators
|
|
||||||
.iter()
|
|
||||||
.position(|(h, _, _)| *h == o.handle)
|
|
||||||
.unwrap();
|
|
||||||
self.shield_generators.remove(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a set of outfits attached to a ship.
|
|
||||||
/// Keeps track of the sum of their stats, so it mustn't be re-computed every frame.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct OutfitSet {
|
|
||||||
/// The total sum of the stats this set of outfits provides
|
|
||||||
stats: OutfitStatSum,
|
|
||||||
|
|
||||||
//pub total_space: content::OutfitSpace,
|
|
||||||
available_space: content::OutfitSpace,
|
|
||||||
outfits: Vec<content::OutfitHandle>,
|
|
||||||
guns: Vec<ShipGun>,
|
|
||||||
enginepoints: Vec<content::EnginePoint>,
|
|
||||||
gunpoints: Vec<content::GunPoint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> OutfitSet {
|
|
||||||
/// Make a new outfit array
|
|
||||||
pub fn new(content: &content::Ship) -> Self {
|
|
||||||
Self {
|
|
||||||
stats: OutfitStatSum::new(),
|
|
||||||
outfits: Vec::new(),
|
|
||||||
guns: Vec::new(),
|
|
||||||
available_space: content.space.clone(),
|
|
||||||
//total_space: content.space.clone(),
|
|
||||||
enginepoints: content.engines.clone(),
|
|
||||||
gunpoints: content.guns.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Does this outfit set contain the specified outfit?
|
|
||||||
pub fn contains_outfit(&self, o: content::OutfitHandle) -> bool {
|
|
||||||
match self.outfits.iter().position(|x| *x == o) {
|
|
||||||
Some(_) => true,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stat_sum(&self) -> &OutfitStatSum {
|
|
||||||
&self.stats
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add an outfit to this ship.
|
|
||||||
/// Returns true on success, and false on failure
|
|
||||||
/// TODO: failure reason enum
|
|
||||||
pub fn add(&mut self, ct: &content::Content, o: content::OutfitHandle) -> bool {
|
|
||||||
let outfit = ct.get_outfit(o);
|
|
||||||
if !self.available_space.can_contain(&outfit.space) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
self.available_space.occupy(&outfit.space);
|
|
||||||
self.stats.add(&outfit);
|
|
||||||
self.outfits.push(o);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove an outfit from this set
|
|
||||||
pub fn remove(&mut self, ct: &content::Content, o: content::OutfitHandle) {
|
|
||||||
let outfit = ct.get_outfit(o);
|
|
||||||
let i = match self.outfits.iter().position(|x| *x == o) {
|
|
||||||
Some(i) => i,
|
|
||||||
None => panic!("removed non-existing outfit"),
|
|
||||||
};
|
|
||||||
self.available_space.free(&outfit.space);
|
|
||||||
self.outfits.remove(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a gun to this outfit set.
|
|
||||||
/// This automatically attaches the gun to the first available gunpoint,
|
|
||||||
/// and returns false (applying no changes) if no points are available.
|
|
||||||
pub fn add_gun(&mut self, ct: &content::Content, g: content::GunHandle) -> bool {
|
|
||||||
// Find first unused point
|
|
||||||
let mut p = None;
|
|
||||||
'outer: for i in 0..self.gunpoints.len() {
|
|
||||||
for g in &self.guns {
|
|
||||||
if g.point == i {
|
|
||||||
continue 'outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p = Some(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let gun = ct.get_gun(g);
|
|
||||||
|
|
||||||
// All points are taken
|
|
||||||
if p.is_none() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sg = ShipGun::new(gun, p.unwrap());
|
|
||||||
self.guns.push(sg);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all guns in this outfitset
|
|
||||||
pub fn iter_guns(&mut self) -> impl Iterator<Item = &mut ShipGun> {
|
|
||||||
self.guns.iter_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterate over all guns and the gunpoints they're attached to
|
|
||||||
pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &content::GunPoint)> {
|
|
||||||
self.guns
|
|
||||||
.iter_mut()
|
|
||||||
.map(|x| (&self.gunpoints[x.point], x))
|
|
||||||
.map(|(a, b)| (b, a))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: move to ship
|
|
||||||
/// Iterate over all ships in this physics system
|
|
||||||
pub fn iter_enginepoints(&self) -> impl Iterator<Item = &EnginePoint> + '_ {
|
|
||||||
self.enginepoints.iter()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_flare_sprite(&self) -> Option<SpriteHandle> {
|
|
||||||
self.stats.engine_flare_sprites.iter().next().map(|x| *x)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,112 +0,0 @@
|
||||||
use rand::Rng;
|
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use crate::{OutfitSet, Projectile};
|
|
||||||
use galactica_content as content;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Ship {
|
|
||||||
pub handle: content::ShipHandle,
|
|
||||||
pub faction: content::FactionHandle,
|
|
||||||
pub outfits: OutfitSet,
|
|
||||||
pub last_hit: Instant,
|
|
||||||
|
|
||||||
// TODO: unified ship stats struct, like space
|
|
||||||
// TODO: unified outfit stats struct: check
|
|
||||||
pub hull: f32,
|
|
||||||
pub shields: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ship {
|
|
||||||
pub fn new(
|
|
||||||
ct: &content::Content,
|
|
||||||
handle: content::ShipHandle,
|
|
||||||
faction: content::FactionHandle,
|
|
||||||
outfits: OutfitSet,
|
|
||||||
) -> Self {
|
|
||||||
let s = ct.get_ship(handle);
|
|
||||||
let shields = outfits.stat_sum().shield_strength;
|
|
||||||
Ship {
|
|
||||||
handle: handle,
|
|
||||||
faction,
|
|
||||||
outfits,
|
|
||||||
hull: s.hull,
|
|
||||||
shields,
|
|
||||||
last_hit: Instant::now(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_dead(&self) -> bool {
|
|
||||||
self.hull <= 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Called whenever a projectile hits this ship.
|
|
||||||
/// Returns true if that projectile should be destroyed and false otherwise.
|
|
||||||
pub fn handle_projectile_collision(&mut self, ct: &content::Content, p: &Projectile) -> bool {
|
|
||||||
let f = ct.get_faction(self.faction);
|
|
||||||
let r = f.relationships.get(&p.faction).unwrap();
|
|
||||||
match r {
|
|
||||||
content::Relationship::Hostile => {
|
|
||||||
let mut d = p.content.damage;
|
|
||||||
if self.is_dead() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if self.shields >= d {
|
|
||||||
self.shields -= d
|
|
||||||
} else {
|
|
||||||
d -= self.shields;
|
|
||||||
self.shields = 0.0;
|
|
||||||
self.hull -= d;
|
|
||||||
}
|
|
||||||
self.last_hit = Instant::now();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fire_guns(&mut self) -> Vec<(Projectile, content::GunPoint)> {
|
|
||||||
if self.is_dead() {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
self.outfits
|
|
||||||
.iter_guns_points()
|
|
||||||
.filter(|(g, _)| g.cooldown <= 0.0)
|
|
||||||
.map(|(g, p)| {
|
|
||||||
let mut rng = rand::thread_rng();
|
|
||||||
g.cooldown = g.kind.rate + &rng.gen_range(-g.kind.rate_rng..=g.kind.rate_rng);
|
|
||||||
|
|
||||||
let lifetime = g.kind.projectile.lifetime
|
|
||||||
+ rng.gen_range(
|
|
||||||
-g.kind.projectile.lifetime_rng..=g.kind.projectile.lifetime_rng,
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
|
||||||
Projectile {
|
|
||||||
content: g.kind.projectile.clone(),
|
|
||||||
lifetime: 0f32.max(lifetime),
|
|
||||||
faction: self.faction,
|
|
||||||
},
|
|
||||||
p.clone(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn step(&mut self, t: f32) {
|
|
||||||
let time_since = self.last_hit.elapsed().as_secs_f32();
|
|
||||||
if self.shields != self.outfits.stat_sum().shield_strength {
|
|
||||||
for (_, d, g) in &self.outfits.stat_sum().shield_generators {
|
|
||||||
if time_since >= *d {
|
|
||||||
self.shields += g * t;
|
|
||||||
if self.shields > self.outfits.stat_sum().shield_strength {
|
|
||||||
self.shields = self.outfits.stat_sum().shield_strength;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
mod outfitset;
|
||||||
|
mod personality;
|
||||||
|
mod ship;
|
||||||
|
|
||||||
|
pub use outfitset::*;
|
||||||
|
pub use personality::*;
|
||||||
|
pub use ship::*;
|
|
@ -0,0 +1,187 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use content::{GunPoint, OutfitHandle, OutfitSpace};
|
||||||
|
use galactica_content as content;
|
||||||
|
|
||||||
|
/// Possible outcomes when adding an outfit
|
||||||
|
pub enum OutfitAddResult {
|
||||||
|
/// An outfit was successfully added
|
||||||
|
Ok,
|
||||||
|
|
||||||
|
/// An outfit could not be added because we don't have enough free space.
|
||||||
|
/// The string tells us what kind of space we need:
|
||||||
|
/// `outfit,` `weapon,` `engine,` etc. Note that these sometimes overlap:
|
||||||
|
/// outfits may need outfit AND weapon space. In these cases, this result
|
||||||
|
/// should name the "most specific" kind of space we lack.
|
||||||
|
NotEnoughSpace(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possible outcomes when removing an outfit
|
||||||
|
pub enum OutfitRemoveResult {
|
||||||
|
/// This outfit was successfully removed
|
||||||
|
Ok,
|
||||||
|
|
||||||
|
/// This outfit isn't in this set
|
||||||
|
NotExist,
|
||||||
|
// TODO:
|
||||||
|
// This is where we'll add non-removable outfits,
|
||||||
|
// outfits that provide space, etc
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple data class, used to keep track of delayed shield generators
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ShieldGenerator {
|
||||||
|
pub outfit: OutfitHandle,
|
||||||
|
pub delay: f32,
|
||||||
|
pub generation: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct keeps track of a ship's outfit loadout.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct OutfitSet {
|
||||||
|
/// What outfits does this statsum contain?
|
||||||
|
outfits: HashMap<OutfitHandle, u32>,
|
||||||
|
|
||||||
|
/// Space available in this outfitset.
|
||||||
|
/// set at creation and never changes.
|
||||||
|
total_space: OutfitSpace,
|
||||||
|
|
||||||
|
/// Space used by the outfits in this set.
|
||||||
|
/// This may be negative if certain outfits provide space!
|
||||||
|
used_space: OutfitSpace,
|
||||||
|
|
||||||
|
/// The gun points available in this ship.
|
||||||
|
/// If value is None, this point is free.
|
||||||
|
/// if value is Some, this point is taken.
|
||||||
|
gun_points: HashMap<GunPoint, Option<OutfitHandle>>,
|
||||||
|
|
||||||
|
// Outfit values
|
||||||
|
// This isn't strictly necessary, but we don't want to
|
||||||
|
// re-compute this on each frame.
|
||||||
|
engine_thrust: f32,
|
||||||
|
steer_power: f32,
|
||||||
|
shield_strength: f32,
|
||||||
|
|
||||||
|
// Delay, generation
|
||||||
|
// TODO: struct
|
||||||
|
shield_generators: Vec<ShieldGenerator>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutfitSet {
|
||||||
|
pub fn new(available_space: OutfitSpace, gun_points: &[GunPoint]) -> Self {
|
||||||
|
Self {
|
||||||
|
outfits: HashMap::new(),
|
||||||
|
total_space: available_space,
|
||||||
|
used_space: OutfitSpace::new(),
|
||||||
|
gun_points: gun_points.iter().map(|x| (x.clone(), None)).collect(),
|
||||||
|
|
||||||
|
engine_thrust: 0.0,
|
||||||
|
steer_power: 0.0,
|
||||||
|
shield_strength: 0.0,
|
||||||
|
shield_generators: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, o: &content::Outfit) -> OutfitAddResult {
|
||||||
|
if !(self.total_space - self.used_space).can_contain(&o.space) {
|
||||||
|
return OutfitAddResult::NotEnoughSpace("TODO".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.used_space += o.space;
|
||||||
|
|
||||||
|
self.engine_thrust += o.engine_thrust;
|
||||||
|
self.steer_power += o.steer_power;
|
||||||
|
self.shield_strength += o.shield_strength;
|
||||||
|
self.shield_generators.push(ShieldGenerator {
|
||||||
|
outfit: o.handle,
|
||||||
|
delay: o.shield_delay,
|
||||||
|
generation: o.shield_generation,
|
||||||
|
});
|
||||||
|
|
||||||
|
return OutfitAddResult::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, o: &content::Outfit) -> OutfitRemoveResult {
|
||||||
|
if !self.outfits.contains_key(&o.handle) {
|
||||||
|
return OutfitRemoveResult::NotExist;
|
||||||
|
} else {
|
||||||
|
let n = *self.outfits.get(&o.handle).unwrap();
|
||||||
|
if n == 1u32 {
|
||||||
|
self.outfits.remove(&o.handle);
|
||||||
|
} else {
|
||||||
|
*self.outfits.get_mut(&o.handle).unwrap() -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.used_space -= o.space;
|
||||||
|
|
||||||
|
self.engine_thrust -= o.engine_thrust;
|
||||||
|
self.steer_power -= o.steer_power;
|
||||||
|
self.shield_strength -= o.shield_strength;
|
||||||
|
|
||||||
|
{
|
||||||
|
// This index will exist, since we checked the hashmap
|
||||||
|
let index = self
|
||||||
|
.shield_generators
|
||||||
|
.iter()
|
||||||
|
.position(|g| g.outfit == o.handle)
|
||||||
|
.unwrap();
|
||||||
|
self.shield_generators.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OutfitRemoveResult::Ok;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple getters to make sure nobody meddles with our internal state
|
||||||
|
impl OutfitSet {
|
||||||
|
/// The number of outfits in this set
|
||||||
|
pub fn len(&self) -> u32 {
|
||||||
|
self.outfits.iter().map(|(_, x)| x).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all outfits
|
||||||
|
pub fn iter_outfits(&self) -> impl Iterator<Item = (&OutfitHandle, &u32)> {
|
||||||
|
self.outfits.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all gun points
|
||||||
|
pub fn iter_gun_points(&self) -> impl Iterator<Item = (&GunPoint, &Option<OutfitHandle>)> {
|
||||||
|
self.gun_points.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over all shield generators
|
||||||
|
pub(crate) fn iter_shield_generators(&self) -> impl Iterator<Item = &ShieldGenerator> {
|
||||||
|
self.shield_generators.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get maximum possible shield regen
|
||||||
|
pub fn get_max_shield_regen(&self) -> f32 {
|
||||||
|
self.shield_generators.iter().map(|x| x.generation).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Total available outfit space
|
||||||
|
pub fn get_total_space(&self) -> &OutfitSpace {
|
||||||
|
&self.total_space
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used outfit space
|
||||||
|
pub fn get_used_space(&self) -> &OutfitSpace {
|
||||||
|
&self.used_space
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Total foward thrust
|
||||||
|
pub fn get_engine_thrust(&self) -> f32 {
|
||||||
|
self.engine_thrust
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Total steer power
|
||||||
|
pub fn get_steer_power(&self) -> f32 {
|
||||||
|
self.steer_power
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Total shield strength
|
||||||
|
pub fn get_shield_strength(&self) -> f32 {
|
||||||
|
self.shield_strength
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
/// Computer-controlled ship behavior variants.
|
||||||
|
/// This is just a list, actual physics-aware
|
||||||
|
/// behaviors are implemented in [`galactica-behavior`]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ShipPersonality {
|
||||||
|
/// This ship is controlled by a player
|
||||||
|
Player,
|
||||||
|
|
||||||
|
/// Does nothing
|
||||||
|
Dummy,
|
||||||
|
|
||||||
|
/// Points and shoots towards the nearest enemy
|
||||||
|
Point,
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use super::{OutfitSet, ShipPersonality};
|
||||||
|
use galactica_content as content;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Ship {
|
||||||
|
// Metadata values
|
||||||
|
ct_handle: content::ShipHandle,
|
||||||
|
faction: content::FactionHandle,
|
||||||
|
outfits: OutfitSet,
|
||||||
|
|
||||||
|
personality: ShipPersonality,
|
||||||
|
|
||||||
|
// State values
|
||||||
|
// TODO: unified ship stats struct, like outfit space
|
||||||
|
hull: f32,
|
||||||
|
shields: f32,
|
||||||
|
|
||||||
|
// Utility values
|
||||||
|
/// The last time this ship was damaged
|
||||||
|
last_hit: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ship {
|
||||||
|
pub(crate) fn new(
|
||||||
|
ct: &content::Content,
|
||||||
|
ct_handle: content::ShipHandle,
|
||||||
|
faction: content::FactionHandle,
|
||||||
|
personality: ShipPersonality,
|
||||||
|
outfits: OutfitSet,
|
||||||
|
) -> Self {
|
||||||
|
let s = ct.get_ship(ct_handle);
|
||||||
|
let shields = outfits.get_shield_strength();
|
||||||
|
Ship {
|
||||||
|
ct_handle,
|
||||||
|
faction,
|
||||||
|
outfits,
|
||||||
|
personality,
|
||||||
|
last_hit: Instant::now(),
|
||||||
|
|
||||||
|
// Initial stats
|
||||||
|
hull: s.hull,
|
||||||
|
shields,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this ship is dead, it will be removed from the game.
|
||||||
|
pub fn is_dead(&self) -> bool {
|
||||||
|
self.hull <= 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hit this ship with the given amount of damage
|
||||||
|
pub fn apply_damage(&mut self, mut d: f32) {
|
||||||
|
if self.is_dead() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.shields >= d {
|
||||||
|
self.shields -= d
|
||||||
|
} else {
|
||||||
|
d -= self.shields;
|
||||||
|
self.shields = 0.0;
|
||||||
|
self.hull = 0f32.max(self.hull - d);
|
||||||
|
}
|
||||||
|
self.last_hit = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update this ship's state by `t` seconds
|
||||||
|
pub fn step(&mut self, t: f32) {
|
||||||
|
let time_since = self.last_hit.elapsed().as_secs_f32();
|
||||||
|
if self.shields != self.outfits.get_shield_strength() {
|
||||||
|
for g in self.outfits.iter_shield_generators() {
|
||||||
|
if time_since >= g.delay {
|
||||||
|
self.shields += g.generation * t;
|
||||||
|
if self.shields > self.outfits.get_shield_strength() {
|
||||||
|
self.shields = self.outfits.get_shield_strength();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Misc getters, so internal state is untouchable
|
||||||
|
impl Ship {
|
||||||
|
/// Get this ship's current hull.
|
||||||
|
/// Use content handle to get maximum hull
|
||||||
|
pub fn get_hull(&self) -> f32 {
|
||||||
|
return self.hull;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's current shields.
|
||||||
|
/// Use get_outfits() for maximum shields
|
||||||
|
pub fn get_shields(&self) -> f32 {
|
||||||
|
self.shields
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all outfits on this ship
|
||||||
|
pub fn get_outfits(&self) -> &OutfitSet {
|
||||||
|
&self.outfits
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's personality
|
||||||
|
pub fn get_personality(&self) -> ShipPersonality {
|
||||||
|
self.personality
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's faction
|
||||||
|
pub fn get_faction(&self) -> content::FactionHandle {
|
||||||
|
self.faction
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this ship's content handle
|
||||||
|
pub fn get_ship(&self) -> content::ShipHandle {
|
||||||
|
self.ct_handle
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::SystemObject;
|
use cgmath::{Point3, Rad};
|
||||||
use galactica_content as content;
|
use galactica_content as content;
|
||||||
|
|
||||||
|
// TODO: rework
|
||||||
|
|
||||||
pub struct System {
|
pub struct System {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub bodies: Vec<SystemObject>,
|
pub bodies: Vec<SystemObject>,
|
||||||
|
@ -25,8 +27,11 @@ impl System {
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//pub fn get_sprites(&self) -> Vec<ObjectSprite> {
|
|
||||||
// return self.bodies.iter().map(|x| x.get_sprite()).collect();
|
pub struct SystemObject {
|
||||||
//}
|
pub sprite: content::SpriteHandle,
|
||||||
|
pub pos: Point3<f32>,
|
||||||
|
pub size: f32,
|
||||||
|
pub angle: Rad<f32>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
use cgmath::{Point3, Rad};
|
|
||||||
|
|
||||||
use galactica_content as content;
|
|
||||||
|
|
||||||
pub struct SystemObject {
|
|
||||||
pub sprite: content::SpriteHandle,
|
|
||||||
pub pos: Point3<f32>,
|
|
||||||
pub size: f32,
|
|
||||||
pub angle: Rad<f32>,
|
|
||||||
}
|
|
|
@ -388,13 +388,4 @@ impl<'a> World {
|
||||||
pub fn iter_projectiles(&'a self) -> impl Iterator<Item = &ProjectileWorldObject> + '_ {
|
pub fn iter_projectiles(&'a self) -> impl Iterator<Item = &ProjectileWorldObject> + '_ {
|
||||||
self.projectiles.values()
|
self.projectiles.values()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Iterate over all projectile sprites in this physics system
|
|
||||||
pub fn get_projectile_sprites(&self) -> impl Iterator<Item = ObjectSprite> + '_ {
|
|
||||||
self.projectiles
|
|
||||||
.values()
|
|
||||||
.map(|x| x.get_sprite(&self.wrapper.rigid_body_set[x.rigid_body]))
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue