Compare commits

...

2 Commits

Author SHA1 Message Date
Mark 2c4db4ebc5
Added faction relationships 2023-12-30 17:39:19 -08:00
Mark cbe8054f45
Fixed physics angles
Added factions
2023-12-30 16:57:03 -08:00
19 changed files with 428 additions and 163 deletions

7
content/factions.toml Normal file
View File

@ -0,0 +1,7 @@
[faction."player"]
display_name = "Player"
relationship.enemy = "hostile"
[faction."enemy"]
display_name = "Enemy"
relationship.player = "hostile"

View File

@ -1,13 +1,12 @@
[ship."Gypsum"] [ship."Gypsum"]
sprite_texture = "ship::gypsum" sprite_texture = "ship::gypsum"
size = 100 size = 100
mass = 10 mass = 1
hull = 200 hull = 200
engines = [{ x = 0.0, y = -1.05, size = 50.0 }] engines = [{ x = 0.0, y = -1.05, size = 50.0 }]
guns = [{ x = 0.0, y = 1 }, { x = 0.1, y = 0.80 }, { x = -0.1, y = 0.80 }] guns = [{ x = 0.0, y = 1 }, { x = 0.1, y = 0.80 }, { x = -0.1, y = 0.80 }]
collision.points = [ collision.points = [
#[rustfmt:skip], #[rustfmt:skip],
[0.53921, 1.0000], [0.53921, 1.0000],

View File

@ -0,0 +1,38 @@
use std::{cmp::Eq, hash::Hash};
/// Represents a specific texture defined in the content dir.
#[derive(Debug, Clone, Copy)]
pub struct TextureHandle {
/// The index of this texture in content.textures
pub(crate) index: usize,
/// The aspect ratio of this texture (width / height)
pub aspect: f32,
}
impl Hash for TextureHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
impl Eq for TextureHandle {}
impl PartialEq for TextureHandle {
fn eq(&self, other: &Self) -> bool {
self.index.eq(&other.index)
}
}
/// A lightweight representation of a faction
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FactionHandle {
/// The index of this faction in content.factions
/// TODO: pub in crate, currently for debug.
pub index: usize,
}
impl Hash for FactionHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}

View File

@ -3,35 +3,29 @@
//! This subcrate is responsible for loading, parsing, validating game content, //! This subcrate is responsible for loading, parsing, validating game content,
//! which is usually stored in `./content`. //! which is usually stored in `./content`.
mod engine; mod handle;
mod gun; mod part;
mod ship;
mod system;
mod texture;
mod util; mod util;
pub use engine::Engine; use anyhow::{Context, Result};
pub use gun::{Gun, Projectile};
pub use ship::{EnginePoint, GunPoint, Ship};
pub use system::{Object, System};
pub use texture::Texture;
use anyhow::{bail, Context, Result};
use std::{ use std::{
cmp::Eq,
collections::HashMap, collections::HashMap,
fs::File, fs::File,
hash::Hash,
io::Read, io::Read,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use toml; use toml;
use walkdir::WalkDir; use walkdir::WalkDir;
pub use handle::{FactionHandle, TextureHandle};
pub use part::{Engine, EnginePoint, Faction, Gun, GunPoint, Relationship, Ship, System, Texture};
mod syntax { mod syntax {
use super::{bail, HashMap, Result}; use anyhow::{bail, Result};
use super::{engine, gun, ship, system, texture};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap;
use crate::part::{engine, faction, gun, ship, system, texture};
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Root { pub struct Root {
@ -40,6 +34,7 @@ mod syntax {
pub system: Option<HashMap<String, system::syntax::System>>, pub system: Option<HashMap<String, system::syntax::System>>,
pub engine: Option<HashMap<String, engine::syntax::Engine>>, pub engine: Option<HashMap<String, engine::syntax::Engine>>,
pub texture: Option<HashMap<String, texture::syntax::Texture>>, pub texture: Option<HashMap<String, texture::syntax::Texture>>,
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
} }
impl Root { impl Root {
@ -50,6 +45,7 @@ mod syntax {
system: None, system: None,
engine: None, engine: None,
texture: None, texture: None,
faction: None,
} }
} }
@ -131,6 +127,21 @@ mod syntax {
} }
} }
if let Some(a) = other.faction {
if self.faction.is_none() {
self.faction = Some(a);
} else {
let sg = self.faction.as_mut().unwrap();
for (k, v) in a {
if sg.contains_key(&k) {
bail!("Repeated faction name {k}");
} else {
sg.insert(k, v);
}
}
}
}
return Ok(()); return Ok(());
} }
} }
@ -145,53 +156,33 @@ trait Build {
Self: Sized; Self: Sized;
} }
/// Represents a specific texture defined in the content dir. /// Represents static game content
#[derive(Debug, Clone, Copy)]
pub struct TextureHandle {
/// The index of this texture in content.textures
pub index: usize,
/// The aspect ratio of this texture (width / height)
pub aspect: f32,
}
impl Hash for TextureHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
impl Eq for TextureHandle {}
impl PartialEq for TextureHandle {
fn eq(&self, other: &Self) -> bool {
self.index.eq(&other.index)
}
}
/// Represents generic game content, not connected to any game objects.
#[derive(Debug)] #[derive(Debug)]
pub struct Content { pub struct Content {
/// Star systems /// Star systems
pub systems: Vec<system::System>, pub systems: Vec<part::system::System>,
/// Ship bodies /// Ship bodies
pub ships: Vec<ship::Ship>, pub ships: Vec<part::ship::Ship>,
/// Gun outfits /// Gun outfits
pub guns: Vec<gun::Gun>, pub guns: Vec<part::gun::Gun>,
/// Engine outfits /// Engine outfits
pub engines: Vec<engine::Engine>, pub engines: Vec<part::engine::Engine>,
/// Textures /// Textures
pub textures: Vec<texture::Texture>, pub textures: Vec<part::texture::Texture>,
/// Factions
pub factions: Vec<part::faction::Faction>,
/// Map strings to texture handles /// Map strings to texture handles
/// This is never used outside this crate. /// This is never used outside this crate.
texture_index: HashMap<String, TextureHandle>, texture_index: HashMap<String, handle::TextureHandle>,
/// The texture to use for starfield stars /// The texture to use for starfield stars
starfield_handle: Option<TextureHandle>, starfield_handle: Option<handle::TextureHandle>,
/// Root directory for textures /// Root directory for textures
texture_root: PathBuf, texture_root: PathBuf,
@ -217,12 +208,17 @@ impl Content {
} }
/// Get a texture from a handle /// Get a texture from a handle
pub fn get_texture(&self, h: TextureHandle) -> &texture::Texture { pub fn get_texture(&self, h: TextureHandle) -> &Texture {
// In theory, this could fail if h has a bad index, but that shouldn't ever happen. // In theory, this could fail if h has a bad index, but that shouldn't ever happen.
// The only TextureHandles that exist should be created by this crate. // The only TextureHandles that exist should be created by this crate.
return &self.textures[h.index]; return &self.textures[h.index];
} }
/// Get a faction from a handle
pub fn get_faction(&self, h: FactionHandle) -> &Faction {
return &self.factions[h.index];
}
/// Load content from a directory. /// Load content from a directory.
pub fn load_dir( pub fn load_dir(
path: PathBuf, path: PathBuf,
@ -262,6 +258,7 @@ impl Content {
guns: Vec::new(), guns: Vec::new(),
engines: Vec::new(), engines: Vec::new(),
textures: Vec::new(), textures: Vec::new(),
factions: Vec::new(),
texture_index: HashMap::new(), texture_index: HashMap::new(),
starfield_handle: None, starfield_handle: None,
texture_root, texture_root,
@ -270,19 +267,22 @@ impl Content {
// Order here matters, usually // Order here matters, usually
if root.texture.is_some() { if root.texture.is_some() {
texture::Texture::build(root.texture.take().unwrap(), &mut content)?; part::texture::Texture::build(root.texture.take().unwrap(), &mut content)?;
} }
if root.ship.is_some() { if root.ship.is_some() {
ship::Ship::build(root.ship.take().unwrap(), &mut content)?; part::ship::Ship::build(root.ship.take().unwrap(), &mut content)?;
} }
if root.gun.is_some() { if root.gun.is_some() {
gun::Gun::build(root.gun.take().unwrap(), &mut content)?; part::gun::Gun::build(root.gun.take().unwrap(), &mut content)?;
} }
if root.engine.is_some() { if root.engine.is_some() {
engine::Engine::build(root.engine.take().unwrap(), &mut content)?; part::engine::Engine::build(root.engine.take().unwrap(), &mut content)?;
} }
if root.system.is_some() { if root.system.is_some() {
system::System::build(root.system.take().unwrap(), &mut content)?; part::system::System::build(root.system.take().unwrap(), &mut content)?;
}
if root.faction.is_some() {
part::faction::Faction::build(root.faction.take().unwrap(), &mut content)?;
} }
return Ok(content); return Ok(content);

View File

@ -2,9 +2,9 @@ use std::collections::HashMap;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use crate::{Content, TextureHandle}; use crate::{handle::TextureHandle, Content};
pub(super) mod syntax { pub(crate) mod syntax {
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.
@ -36,7 +36,7 @@ pub struct Engine {
pub flare_sprite_texture: TextureHandle, pub flare_sprite_texture: TextureHandle,
} }
impl super::Build for Engine { impl crate::Build for Engine {
type InputSyntax = HashMap<String, syntax::Engine>; type InputSyntax = HashMap<String, syntax::Engine>;
fn build(engine: Self::InputSyntax, ct: &mut Content) -> Result<()> { fn build(engine: Self::InputSyntax, ct: &mut Content) -> Result<()> {

View File

@ -0,0 +1,106 @@
use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
use crate::{handle::FactionHandle, Content};
pub(crate) mod syntax {
use std::collections::HashMap;
use serde::Deserialize;
// Raw serde syntax structs.
// These are never seen by code outside this crate.
#[derive(Debug, Deserialize)]
pub struct Faction {
pub display_name: String,
pub relationship: HashMap<String, super::Relationship>,
}
}
/// How two factions should interact with each other.
/// Relationships are directional: the relationship of
/// `a` to `b` may not equal the relationship of `b` to `a`.
///
/// Relationships dictate how a ship of THIS faction
/// will interact with a ship of the OTHER faction.
#[derive(Debug, Deserialize, Clone, Copy)]
pub enum Relationship {
/// Attack this faction
#[serde(rename = "hostile")]
Hostile,
/// Ignore this faction
#[serde(rename = "neutral")]
Neutral,
/// Protect this faction
#[serde(rename = "friend")]
Friend,
}
/// Represents a game faction
#[derive(Debug, Clone)]
pub struct Faction {
/// The name of this faction
pub name: String,
/// This faction's handle
pub handle: FactionHandle,
/// Relationships between this faction and other factions
/// This is guaranteed to contain an entry for ALL factions.
pub relationships: HashMap<FactionHandle, Relationship>,
}
impl crate::Build for Faction {
type InputSyntax = HashMap<String, syntax::Faction>;
fn build(factions: Self::InputSyntax, ct: &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);
for f_idx in 0..faction_names.len() {
let faction_name = &faction_names[f_idx];
let faction = &factions[faction_name];
// Handle for this faction
let h = FactionHandle { index: f_idx };
// Compute relationships
let mut relationships = HashMap::new();
for i in 0..faction_names.len() {
let f_other = &faction_names[i];
let h_other = FactionHandle { index: i };
if let Some(r) = faction.relationship.get(f_other) {
relationships.insert(h_other, *r);
} else {
// Default relationship, if not specified
// Look at reverse direction...
let other = factions[f_other].relationship.get(faction_name);
relationships.insert(
h_other,
// ... and pick a relationship based on that.
match other {
Some(Relationship::Hostile) => Relationship::Hostile {},
_ => Relationship::Neutral {},
},
);
}
}
ct.factions.push(Self {
name: faction_name.to_owned(),
handle: h,
relationships,
});
}
return Ok(());
}
}

View File

@ -3,9 +3,9 @@ use std::collections::HashMap;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use cgmath::Deg; use cgmath::Deg;
use crate::{Content, TextureHandle}; use crate::{handle::TextureHandle, Content};
pub(super) mod syntax { pub(crate) mod syntax {
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.
@ -82,7 +82,7 @@ pub struct Projectile {
pub damage: f32, pub damage: f32,
} }
impl super::Build for Gun { impl crate::Build for Gun {
type InputSyntax = HashMap<String, syntax::Gun>; type InputSyntax = HashMap<String, syntax::Gun>;
fn build(gun: Self::InputSyntax, ct: &mut Content) -> Result<()> { fn build(gun: Self::InputSyntax, ct: &mut Content) -> Result<()> {

View File

@ -0,0 +1,15 @@
//! Content parts
pub mod engine;
pub mod faction;
pub mod gun;
pub mod ship;
pub mod system;
pub mod texture;
pub use engine::Engine;
pub use faction::{Faction, Relationship};
pub use gun::{Gun, Projectile};
pub use ship::{EnginePoint, GunPoint, Ship};
pub use system::{Object, System};
pub use texture::Texture;

View File

@ -4,9 +4,9 @@ use anyhow::{bail, Result};
use cgmath::Point2; use cgmath::Point2;
use nalgebra::{point, Point}; use nalgebra::{point, Point};
use crate::{Content, TextureHandle}; use crate::{handle::TextureHandle, Content};
pub(super) mod syntax { pub(crate) mod syntax {
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.
@ -108,7 +108,7 @@ pub struct GunPoint {
pub pos: Point2<f32>, pub pos: Point2<f32>,
} }
impl super::Build for Ship { impl crate::Build for Ship {
type InputSyntax = HashMap<String, syntax::Ship>; type InputSyntax = HashMap<String, syntax::Ship>;
fn build(ship: Self::InputSyntax, ct: &mut Content) -> Result<()> { fn build(ship: Self::InputSyntax, ct: &mut Content) -> Result<()> {
@ -154,7 +154,7 @@ impl super::Build for Ship {
}) })
.collect(), .collect(),
collision: Collision { collision: Collision {
indices: ship.collision.indices.clone(), indices: ship.collision.indices,
points: ship points: ship
.collision .collision
.points .points

View File

@ -2,9 +2,9 @@ use anyhow::{bail, Context, Result};
use cgmath::{Deg, Point3}; use cgmath::{Deg, Point3};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use crate::{util::Polar, Content, TextureHandle}; use crate::{handle::TextureHandle, util::Polar, Content};
pub(super) mod syntax { pub(crate) mod syntax {
use super::HashMap; use super::HashMap;
use serde::Deserialize; use serde::Deserialize;
// Raw serde syntax structs. // Raw serde syntax structs.
@ -174,7 +174,7 @@ fn resolve_position(
} }
} }
impl super::Build for System { impl crate::Build for System {
type InputSyntax = HashMap<String, syntax::System>; type InputSyntax = HashMap<String, syntax::System>;
fn build(system: Self::InputSyntax, ct: &mut Content) -> Result<()> { fn build(system: Self::InputSyntax, ct: &mut Content) -> Result<()> {

View File

@ -3,9 +3,9 @@ use std::{collections::HashMap, path::PathBuf};
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use image::io::Reader; use image::io::Reader;
use crate::{Content, TextureHandle}; use crate::{handle::TextureHandle, Content};
pub(super) mod syntax { pub(crate) mod syntax {
use std::path::PathBuf; use std::path::PathBuf;
use serde::Deserialize; use serde::Deserialize;
@ -31,7 +31,7 @@ pub struct Texture {
pub path: PathBuf, pub path: PathBuf,
} }
impl super::Build for Texture { impl crate::Build for Texture {
type InputSyntax = HashMap<String, syntax::Texture>; type InputSyntax = HashMap<String, syntax::Texture>;
fn build(texture: Self::InputSyntax, ct: &mut Content) -> Result<()> { fn build(texture: Self::InputSyntax, ct: &mut Content) -> Result<()> {

View File

@ -1,4 +1,5 @@
use cgmath::Point2; use cgmath::Point2;
use galactica_content::FactionHandle;
use std::time::Instant; use std::time::Instant;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
@ -23,6 +24,7 @@ pub struct Game {
physics: Physics, physics: Physics,
shipbehaviors: Vec<Box<dyn ShipBehavior>>, shipbehaviors: Vec<Box<dyn ShipBehavior>>,
content: Content,
} }
impl Game { impl Game {
@ -32,30 +34,37 @@ impl Game {
let h1 = physics.add_ship( let h1 = physics.add_ship(
&ct.ships[0], &ct.ships[0],
vec![ vec![
outfits::ShipOutfit::Gun(outfits::ShipGun::new(ct.guns[0].clone(), 1)), outfits::ShipOutfit::Gun(outfits::ShipGun::new(&ct.guns[0], 1)),
outfits::ShipOutfit::Gun(outfits::ShipGun::new(ct.guns[0].clone(), 2)), outfits::ShipOutfit::Gun(outfits::ShipGun::new(&ct.guns[0], 2)),
outfits::ShipOutfit::Engine(ct.engines[0].clone()), outfits::ShipOutfit::Engine(ct.engines[0].clone()),
], ],
Point2 { x: 0.0, y: 0.0 }, Point2 { x: 0.0, y: 0.0 },
FactionHandle { index: 0 },
); );
let h2 = physics.add_ship(&ct.ships[0], vec![], Point2 { x: 300.0, y: 300.0 }); let h2 = physics.add_ship(
let _h3 = physics.add_ship( &ct.ships[0],
vec![],
Point2 { x: 300.0, y: 300.0 },
FactionHandle { index: 0 },
);
let h3 = physics.add_ship(
&ct.ships[0], &ct.ships[0],
vec![outfits::ShipOutfit::Gun(outfits::ShipGun::new( vec![outfits::ShipOutfit::Gun(outfits::ShipGun::new(
ct.guns[0].clone(), &ct.guns[0],
0, 0,
))], ))],
Point2 { Point2 {
x: -300.0, x: -300.0,
y: 300.0, y: 300.0,
}, },
FactionHandle { index: 1 },
); );
let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new(); let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new();
shipbehaviors.push(shipbehavior::Player::new(h1)); shipbehaviors.push(shipbehavior::Player::new(h1));
//shipbehaviors.push(shipbehavior::Point::new(h3));
shipbehaviors.push(shipbehavior::Dummy::new(h2)); shipbehaviors.push(shipbehavior::Dummy::new(h2));
shipbehaviors.push(shipbehavior::Point::new(h3));
Game { Game {
last_update: Instant::now(), last_update: Instant::now(),
@ -72,6 +81,7 @@ impl Game {
time_scale: 1.0, time_scale: 1.0,
physics, physics,
shipbehaviors, shipbehaviors,
content: ct,
} }
} }
@ -99,11 +109,16 @@ 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;
for b in &mut self.shipbehaviors { self.shipbehaviors.retain_mut(|b| {
b.update_controls(&mut self.physics, &self.input, self.player); if self.physics.get_ship_mut(&b.get_handle()).is_none() {
false
} else {
b.update_controls(&mut self.physics, &self.input, &self.content);
true
} }
});
self.physics.step(t); self.physics.step(t, &self.content);
if self.input.v_scroll != 0.0 { if self.input.v_scroll != 0.0 {
self.camera.zoom = self.camera.zoom =
@ -112,7 +127,11 @@ impl Game {
} }
// TODO: Camera physics // TODO: Camera physics
let r = self.physics.get_ship_mut(&self.player).physics_handle; let r = self
.physics
.get_ship_mut(&self.player)
.unwrap()
.physics_handle;
let r = self.physics.get_rigid_body(r.0); // TODO: r.0 shouldn't be public let r = self.physics.get_rigid_body(r.0); // TODO: r.0 shouldn't be public
let ship_pos = util::rigidbody_position(r); let ship_pos = util::rigidbody_position(r);
self.camera.pos = ship_pos; self.camera.pos = ship_pos;

View File

@ -1,9 +1,6 @@
use cgmath::{Deg, Point3}; use cgmath::{Deg, Point3};
use crate::{ use crate::{content, render::SubSprite};
content::{self, EnginePoint, GunPoint},
render::SubSprite,
};
/// Represents a gun attached to a specific ship at a certain gunpoint. /// Represents a gun attached to a specific ship at a certain gunpoint.
pub struct ShipGun { pub struct ShipGun {
@ -13,9 +10,9 @@ pub struct ShipGun {
} }
impl ShipGun { impl ShipGun {
pub fn new(kind: content::Gun, point: usize) -> Self { pub fn new(kind: &content::Gun, point: usize) -> Self {
Self { Self {
kind, kind: kind.clone(),
point, point,
cooldown: 0.0, cooldown: 0.0,
} }
@ -77,7 +74,7 @@ impl<'a> ShipOutfits {
.map(|x| x.unwrap()) .map(|x| x.unwrap())
} }
pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &GunPoint)> { pub fn iter_guns_points(&mut self) -> impl Iterator<Item = (&mut ShipGun, &content::GunPoint)> {
self.outfits self.outfits
.iter_mut() .iter_mut()
.map(|x| x.gun()) .map(|x| x.gun())
@ -95,7 +92,7 @@ impl<'a> ShipOutfits {
.map(|x| x.unwrap()) .map(|x| x.unwrap())
} }
pub fn iter_enginepoints(&self) -> impl Iterator<Item = &EnginePoint> { pub fn iter_enginepoints(&self) -> impl Iterator<Item = &content::EnginePoint> {
self.enginepoints.iter() self.enginepoints.iter()
} }

View File

@ -24,6 +24,8 @@ fn main() -> Result<()> {
consts::STARFIELD_TEXTURE_NAME.to_owned(), consts::STARFIELD_TEXTURE_NAME.to_owned(),
)?; )?;
println!("{:?}", content.factions);
pollster::block_on(run(content))?; pollster::block_on(run(content))?;
return Ok(()); return Ok(());
} }

View File

@ -1,5 +1,5 @@
use cgmath::Point3; use cgmath::{Deg, InnerSpace, Point3, Vector2};
use galactica_content::TextureHandle; use galactica_content::{FactionHandle, TextureHandle};
use rapier2d::{ use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle}, dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
geometry::{ColliderBuilder, ColliderHandle}, geometry::{ColliderBuilder, ColliderHandle},
@ -14,6 +14,7 @@ pub struct ProjectileBuilder {
pub lifetime: f32, pub lifetime: f32,
pub size: f32, pub size: f32,
pub damage: f32, pub damage: f32,
pub faction: FactionHandle,
} }
impl ProjectileBuilder { impl ProjectileBuilder {
@ -25,6 +26,7 @@ impl ProjectileBuilder {
lifetime: self.lifetime, lifetime: self.lifetime,
size: self.size, size: self.size,
damage: self.damage, damage: self.damage,
faction: self.faction,
} }
} }
} }
@ -37,6 +39,7 @@ pub struct Projectile {
pub lifetime: f32, pub lifetime: f32,
pub size: f32, pub size: f32,
pub damage: f32, pub damage: f32,
pub faction: FactionHandle,
} }
impl Projectile { impl Projectile {
@ -50,7 +53,11 @@ impl Projectile {
pub fn get_sprite(&self, r: &RigidBody) -> Sprite { pub fn get_sprite(&self, r: &RigidBody) -> Sprite {
let pos = util::rigidbody_position(r); let pos = util::rigidbody_position(r);
let ang = util::rigidbody_angle(r); let rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees
let ang: Deg<f32> = rot.angle(Vector2 { x: 1.0, y: 0.0 }).into();
Sprite { Sprite {
texture: self.sprite_texture, texture: self.sprite_texture,
pos: Point3 { pos: Point3 {
@ -59,7 +66,7 @@ impl Projectile {
z: 1.0, z: 1.0,
}, },
size: self.size, size: self.size,
angle: ang, angle: -ang,
children: None, children: None,
} }
} }

View File

@ -1,5 +1,5 @@
use cgmath::{Deg, EuclideanSpace, Matrix2, Rad, Vector2}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2};
use content::TextureHandle; use content::{FactionHandle, TextureHandle};
use nalgebra::vector; use nalgebra::vector;
use rand::Rng; use rand::Rng;
use rapier2d::{ use rapier2d::{
@ -40,12 +40,13 @@ impl ShipControls {
pub struct Ship { pub struct Ship {
pub physics_handle: ShipHandle, pub physics_handle: ShipHandle,
outfits: outfits::ShipOutfits, pub faction: FactionHandle,
sprite_texture: TextureHandle,
size: f32,
pub hull: f32, pub hull: f32,
pub controls: ShipControls, pub controls: ShipControls,
outfits: outfits::ShipOutfits,
sprite_texture: TextureHandle,
size: f32,
} }
impl Ship { impl Ship {
@ -53,6 +54,7 @@ impl Ship {
c: &content::Ship, c: &content::Ship,
outfits: Vec<outfits::ShipOutfit>, outfits: Vec<outfits::ShipOutfit>,
physics_handle: ShipHandle, physics_handle: ShipHandle,
faction: FactionHandle,
) -> Self { ) -> Self {
let mut o = outfits::ShipOutfits::new(c); let mut o = outfits::ShipOutfits::new(c);
for x in outfits.into_iter() { for x in outfits.into_iter() {
@ -66,6 +68,7 @@ impl Ship {
size: c.size, size: c.size,
hull: c.hull, hull: c.hull,
controls: ShipControls::new(), controls: ShipControls::new(),
faction,
} }
} }
@ -81,24 +84,28 @@ impl Ship {
g.cooldown = g.kind.rate + rng.gen_range(-g.kind.rate_rng..=g.kind.rate_rng); g.cooldown = g.kind.rate + rng.gen_range(-g.kind.rate_rng..=g.kind.rate_rng);
let ship_pos = util::rigidbody_position(r); let ship_pos = util::rigidbody_position(r);
let ship_ang: Deg<f32> = util::rigidbody_angle(r); let ship_rot = util::rigidbody_rotation(r);
let ship_ang_rad: Rad<f32> = ship_ang.into();
let ship_vel = util::rigidbody_velocity(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) * p.pos.to_vec()); let pos = ship_pos + (Matrix2::from_angle(-ship_ang) * p.pos.to_vec());
let spread: Rad<f32> =
Deg(rng.gen_range(-(g.kind.spread.0 / 2.0)..=g.kind.spread.0 / 2.0)).into();
let vel = ship_vel let vel = ship_vel
+ (Matrix2::from_angle( + (Matrix2::from_angle(-ship_ang + spread)
ship_ang + Deg(rng.gen_range(-(g.kind.spread.0 / 2.0)..=g.kind.spread.0 / 2.0)), * Vector2 {
) * Vector2 {
x: 0.0, x: 0.0,
y: g.kind.projectile.speed y: g.kind.projectile.speed
+ rng.gen_range(-g.kind.projectile.speed_rng..=g.kind.projectile.speed_rng), + rng.gen_range(
-g.kind.projectile.speed_rng..=g.kind.projectile.speed_rng,
),
}); });
let p_r = RigidBodyBuilder::kinematic_velocity_based() let p_r = RigidBodyBuilder::kinematic_velocity_based()
.translation(vector![pos.x, pos.y]) .translation(vector![pos.x, pos.y])
.rotation(-ship_ang_rad.0) .rotation(-ship_ang.0)
.linvel(vector![vel.x, vel.y]); .linvel(vector![vel.x, vel.y]);
let p_c = ColliderBuilder::ball(5.0) let p_c = ColliderBuilder::ball(5.0)
@ -116,6 +123,7 @@ impl Ship {
size: g.kind.projectile.size size: g.kind.projectile.size
+ rng.gen_range(-g.kind.projectile.size_rng..=g.kind.projectile.size_rng), + rng.gen_range(-g.kind.projectile.size_rng..=g.kind.projectile.size_rng),
damage: g.kind.projectile.damage, // TODO: kind as param to builder damage: g.kind.projectile.damage, // TODO: kind as param to builder
faction: self.faction,
}) })
} }
return out; return out;
@ -123,8 +131,8 @@ impl Ship {
/// Apply the effects of all active controls /// Apply the effects of all active controls
pub fn apply_controls(&mut self, r: &mut RigidBody, t: f32) -> ShipTickResult { pub fn apply_controls(&mut self, r: &mut RigidBody, t: f32) -> ShipTickResult {
let ship_ang = util::rigidbody_angle(r); let ship_rot = util::rigidbody_rotation(r);
let engine_force = Matrix2::from_angle(ship_ang) * Vector2 { x: 0.0, y: 1.0 } * t; let engine_force = ship_rot * t;
if self.controls.thrust { if self.controls.thrust {
for e in self.outfits.iter_engines() { for e in self.outfits.iter_engines() {
@ -155,12 +163,15 @@ impl Ship {
pub fn get_sprite(&self, r: &RigidBody) -> Sprite { pub fn get_sprite(&self, r: &RigidBody) -> Sprite {
let ship_pos = util::rigidbody_position(r); let ship_pos = util::rigidbody_position(r);
let ship_ang = util::rigidbody_angle(r); let ship_rot = util::rigidbody_rotation(r);
// Sprites point north at 0 degrees
let ship_ang: Deg<f32> = ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }).into();
Sprite { Sprite {
pos: (ship_pos.x, ship_pos.y, 1.0).into(), pos: (ship_pos.x, ship_pos.y, 1.0).into(),
texture: self.sprite_texture.clone(), // TODO: sprite texture should be easy to clone texture: self.sprite_texture,
angle: ship_ang, angle: -ship_ang,
size: self.size, size: self.size,
children: if self.controls.thrust { children: if self.controls.thrust {

View File

@ -1,4 +1,5 @@
use cgmath::Point2; use cgmath::Point2;
use content::{Content, FactionHandle};
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use nalgebra::vector; use nalgebra::vector;
use rapier2d::{ use rapier2d::{
@ -6,7 +7,7 @@ use rapier2d::{
geometry::{ColliderBuilder, ColliderHandle, CollisionEvent}, geometry::{ColliderBuilder, ColliderHandle, CollisionEvent},
pipeline::ChannelEventCollector, pipeline::ChannelEventCollector,
}; };
use std::collections::HashMap; use std::{collections::HashMap, f32::consts::PI};
use super::{wrapper::Wrapper, ShipHandle}; use super::{wrapper::Wrapper, ShipHandle};
use crate::{content, game::outfits, objects, render::Sprite}; use crate::{content, game::outfits, objects, render::Sprite};
@ -83,11 +84,15 @@ impl Physics {
ct: &content::Ship, ct: &content::Ship,
outfits: Vec<outfits::ShipOutfit>, outfits: Vec<outfits::ShipOutfit>,
position: Point2<f32>, position: Point2<f32>,
faction: FactionHandle,
) -> ShipHandle { ) -> ShipHandle {
let cl = ColliderBuilder::convex_decomposition( let cl = ColliderBuilder::convex_decomposition(
&ct.collision.points[..], &ct.collision.points[..],
&ct.collision.indices[..], &ct.collision.indices[..],
) )
// Rotate collider to match sprite
// (Collider starts pointing east, sprite starts pointing north.)
.rotation(PI / -2.0)
.mass(ct.mass); .mass(ct.mass);
let rb = RigidBodyBuilder::dynamic() let rb = RigidBodyBuilder::dynamic()
@ -102,11 +107,12 @@ impl Physics {
); );
let h = ShipHandle(r, c); let h = ShipHandle(r, c);
self.ships.insert(c, objects::Ship::new(ct, outfits, h)); self.ships
.insert(c, objects::Ship::new(ct, outfits, h, faction));
return h; return h;
} }
pub fn step(&mut self, t: f32) { pub fn step(&mut self, t: f32, ct: &Content) {
// Run ship updates // Run ship updates
let mut res = Vec::new(); let mut res = Vec::new();
let mut to_remove = Vec::new(); let mut to_remove = Vec::new();
@ -136,8 +142,8 @@ impl Physics {
let a = &event.collider1(); let a = &event.collider1();
let b = &event.collider2(); let b = &event.collider2();
// If a projectile is part of the collision, make sure // If projectiles are a part of this collision, make sure
// a is a projectile. // `a` is one of them.
let (a, b) = if self.projectiles.contains_key(b) { let (a, b) = if self.projectiles.contains_key(b) {
(b, a) (b, a)
} else { } else {
@ -146,11 +152,19 @@ impl Physics {
if let Some(p) = self.projectiles.get(a) { if let Some(p) = self.projectiles.get(a) {
if let Some(s) = self.ships.get_mut(b) { if let Some(s) = self.ships.get_mut(b) {
s.hull -= p.damage; let p_faction = ct.get_faction(p.faction);
} let r = p_faction.relationships[&s.faction];
match r {
content::Relationship::Hostile => {
// TODO: implement death and spawning, and enable damage
//s.hull -= p.damage;
self.remove_projectile(*a); self.remove_projectile(*a);
self.remove_projectile(*b); self.remove_projectile(*b);
} }
_ => {}
}
}
}
} }
} }
@ -171,16 +185,22 @@ impl Physics {
&self.wrapper.rigid_body_set[r] &self.wrapper.rigid_body_set[r]
} }
pub fn get_ship_mut(&mut self, s: &ShipHandle) -> &mut objects::Ship { pub fn get_ship_mut(&mut self, s: &ShipHandle) -> Option<&mut objects::Ship> {
self.ships.get_mut(&s.1).unwrap() self.ships.get_mut(&s.1)
} }
pub fn get_ship_body(&self, s: &ShipHandle) -> (&objects::Ship, &RigidBody) { pub fn get_ship_body(&self, s: &ShipHandle) -> Option<(&objects::Ship, &RigidBody)> {
// TODO: handle dead handles // TODO: handle dead handles
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
}
pub fn iter_ship_body(&self) -> impl Iterator<Item = (&objects::Ship, &RigidBody)> + '_ {
self.ships.values().map(|x| {
( (
self.ships.get(&s.1).unwrap(), x,
self.wrapper.rigid_body_set.get(s.0).unwrap(), self.wrapper.rigid_body_set.get(x.physics_handle.0).unwrap(),
) )
})
} }
pub fn get_ship_sprites(&self) -> impl Iterator<Item = Sprite> + '_ { pub fn get_ship_sprites(&self) -> impl Iterator<Item = Sprite> + '_ {

View File

@ -1,4 +1,4 @@
use cgmath::{Deg, InnerSpace, Point2, Vector2}; use cgmath::{Point2, Vector2};
use nalgebra; use nalgebra;
use rapier2d::dynamics::RigidBody; use rapier2d::dynamics::RigidBody;
@ -9,19 +9,10 @@ pub fn rigidbody_position(r: &RigidBody) -> cgmath::Point2<f32> {
} }
} }
pub fn rigidbody_angle(r: &RigidBody) -> Deg<f32> {
Vector2 {
x: r.rotation().im,
y: r.rotation().re,
}
.angle(Vector2 { x: 0.0, y: 1.0 })
.into()
}
pub fn rigidbody_rotation(r: &RigidBody) -> Vector2<f32> { pub fn rigidbody_rotation(r: &RigidBody) -> Vector2<f32> {
Vector2 { Vector2 {
x: r.rotation().im, x: r.rotation().re,
y: r.rotation().re, y: r.rotation().im,
} }
} }

View File

@ -1,4 +1,5 @@
use cgmath::{Deg, InnerSpace}; use cgmath::{Deg, InnerSpace};
use galactica_content as content;
use crate::{ use crate::{
inputstatus::InputStatus, inputstatus::InputStatus,
@ -9,7 +10,13 @@ pub trait ShipBehavior
where where
Self: Send, Self: Send,
{ {
fn update_controls(&mut self, physics: &mut Physics, input: &InputStatus, player: ShipHandle); fn update_controls(
&mut self,
physics: &mut Physics,
input: &InputStatus,
content: &content::Content,
);
fn get_handle(&self) -> ShipHandle;
} }
pub struct Dummy { pub struct Dummy {
@ -27,9 +34,12 @@ impl ShipBehavior for Dummy {
&mut self, &mut self,
_physics: &mut Physics, _physics: &mut Physics,
_input: &InputStatus, _input: &InputStatus,
_player: ShipHandle, _content: &content::Content,
) { ) {
} }
fn get_handle(&self) -> ShipHandle {
return self._handle;
}
} }
pub struct Player { pub struct Player {
@ -43,13 +53,22 @@ impl Player {
} }
impl ShipBehavior for Player { impl ShipBehavior for Player {
fn update_controls(&mut self, physics: &mut Physics, input: &InputStatus, _player: ShipHandle) { fn update_controls(
let s = physics.get_ship_mut(&self.handle); &mut self,
physics: &mut Physics,
input: &InputStatus,
_content: &content::Content,
) {
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = input.key_left; s.controls.left = input.key_left;
s.controls.right = input.key_right; s.controls.right = input.key_right;
s.controls.guns = input.key_guns; s.controls.guns = input.key_guns;
s.controls.thrust = input.key_thrust; s.controls.thrust = input.key_thrust;
} }
fn get_handle(&self) -> ShipHandle {
return self.handle;
}
} }
pub struct Point { pub struct Point {
@ -63,34 +82,68 @@ impl Point {
} }
impl ShipBehavior for Point { impl ShipBehavior for Point {
fn update_controls(&mut self, physics: &mut Physics, _input: &InputStatus, player: ShipHandle) { fn update_controls(
let (_, r) = physics.get_ship_body(&player); &mut self,
physics: &mut Physics,
_input: &InputStatus,
content: &content::Content,
) {
// Turn off all controls
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = false;
s.controls.right = false;
s.controls.guns = false;
s.controls.thrust = false;
let (my_s, my_r) = physics.get_ship_body(&self.handle).unwrap();
let my_position = util::rigidbody_position(my_r);
let my_rotation = util::rigidbody_rotation(my_r);
let my_angvel = my_r.angvel();
let my_faction = content.get_faction(my_s.faction);
// Iterate all possible targets
let mut it = physics
.iter_ship_body()
.filter(|(s, _)| match my_faction.relationships[&s.faction] {
content::Relationship::Hostile => true,
_ => false,
})
.map(|(_, r)| r);
// Find the closest target
let mut closest_enemy_position = match it.next() {
Some(c) => util::rigidbody_position(c),
None => return, // Do nothing if no targets are available
};
let mut d = (my_position - closest_enemy_position).magnitude();
for r in it {
let p = util::rigidbody_position(r); let p = util::rigidbody_position(r);
let new_d = (my_position - p).magnitude();
if new_d < d {
d = new_d;
closest_enemy_position = p;
}
}
let (_, r) = physics.get_ship_body(&self.handle); let angle_delta: Deg<f32> = (my_rotation)
let t = util::rigidbody_position(r); .angle(closest_enemy_position - my_position)
let pa = util::rigidbody_rotation(r); .into();
let v = r.angvel();
let d: Deg<f32> = (t - p).angle(pa).into();
println!("{:?}", d);
let s = physics.get_ship_mut(&self.handle);
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = false; s.controls.left = false;
s.controls.right = false; s.controls.right = false;
if d < Deg(0.0) && v < 0.1 { if angle_delta < Deg(0.0) && my_angvel > -0.3 {
s.controls.left = false;
s.controls.right = true; s.controls.right = true;
println!("r") } else if angle_delta > Deg(0.0) && my_angvel < 0.3 {
} else if d > Deg(0.0) && v > -0.1 {
println!("l");
s.controls.left = true; s.controls.left = true;
s.controls.right = false;
} }
s.controls.guns = true; s.controls.guns = true;
s.controls.thrust = false; s.controls.thrust = false;
} }
fn get_handle(&self) -> ShipHandle {
return self.handle;
}
} }