Compare commits

..

No commits in common. "35c6676e9545be25e8956b2b369d0d47fefee2a8" and "966ad4e5a4e885ef657707b2bc47c056fe64fc54" have entirely different histories.

29 changed files with 485 additions and 561 deletions

11
Cargo.lock generated
View File

@ -579,6 +579,7 @@ version = "0.0.0"
dependencies = [
"anyhow",
"cgmath",
"galactica-behavior",
"galactica-constants",
"galactica-content",
"galactica-gameobject",
@ -589,6 +590,15 @@ dependencies = [
"winit",
]
[[package]]
name = "galactica-behavior"
version = "0.0.0"
dependencies = [
"cgmath",
"galactica-content",
"galactica-world",
]
[[package]]
name = "galactica-constants"
version = "0.0.0"
@ -637,7 +647,6 @@ dependencies = [
"cgmath",
"galactica-constants",
"galactica-content",
"galactica-gameobject",
"galactica-packer",
"galactica-world",
"image",

View File

@ -46,6 +46,7 @@ galactica-constants = { path = "crates/constants" }
galactica-content = { path = "crates/content" }
galactica-render = { path = "crates/render" }
galactica-world = { path = "crates/world" }
galactica-behavior = { path = "crates/behavior" }
galactica-gameobject = { path = "crates/gameobject" }
galactica-packer = { path = "crates/packer" }
galactica = { path = "crates/galactica" }

View File

@ -0,0 +1,22 @@
[package]
name = "galactica-behavior"
description = "AI behaviors for Galactica"
categories = { workspace = true }
keywords = { workspace = true }
version = { workspace = true }
rust-version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
documentation = { workspace = true }
readme = { workspace = true }
[lints]
workspace = true
[dependencies]
galactica-content = { workspace = true }
galactica-world = { workspace = true }
cgmath = { workspace = true }

View File

@ -0,0 +1,23 @@
use crate::ShipBehavior;
use galactica_content as content;
use galactica_world::{ShipPhysicsHandle, World};
/// Dummy ship behavior.
/// Does nothing.
pub struct Dummy {
_handle: ShipPhysicsHandle,
}
impl Dummy {
/// Create a new ship controller
pub fn new(handle: ShipPhysicsHandle) -> Self {
Self { _handle: handle }
}
}
impl ShipBehavior for Dummy {
fn update_controls(&mut self, _physics: &mut World, _content: &content::Content) {}
fn get_handle(&self) -> ShipPhysicsHandle {
return self._handle;
}
}

View File

@ -0,0 +1,9 @@
//! Various implementations of [`crate::ShipBehavior`]
mod dummy;
mod player;
mod point;
pub use dummy::Dummy;
pub use player::Player;
pub use point::Point;

View File

@ -0,0 +1,48 @@
use crate::ShipBehavior;
use galactica_content as content;
use galactica_world::{ShipPhysicsHandle, World};
/// Player ship behavior.
/// Controls a ship using controller input
pub struct Player {
handle: ShipPhysicsHandle,
/// Turn left
pub key_left: bool,
/// Turn right
pub key_right: bool,
/// Fire guns
pub key_guns: bool,
/// Foward thrust
pub key_thrust: bool,
}
impl Player {
/// Make a new ship controller
pub fn new(handle: ShipPhysicsHandle) -> Self {
Self {
handle,
key_left: false,
key_right: false,
key_guns: false,
key_thrust: false,
}
}
}
impl ShipBehavior for Player {
fn update_controls(&mut self, physics: &mut World, _content: &content::Content) {
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = self.key_left;
s.controls.right = self.key_right;
s.controls.guns = self.key_guns;
s.controls.thrust = self.key_thrust;
}
fn get_handle(&self) -> ShipPhysicsHandle {
return self.handle;
}
}

View File

@ -1,30 +1,24 @@
use cgmath::{Deg, InnerSpace};
use galactica_gameobject::GameData;
use crate::ShipBehavior;
use galactica_content as content;
use crate::World;
use super::ShipBehavior;
use galactica_world::{util, ShipPhysicsHandle, World};
/// "Point" ship behavior.
/// Point and shoot towards the nearest enemy.
pub struct Point {}
pub struct Point {
handle: ShipPhysicsHandle,
}
impl Point {
/// Create a new ship controller
pub fn new() -> Self {
Self {}
pub fn new(handle: ShipPhysicsHandle) -> Self {
Self { handle }
}
}
impl ShipBehavior for Point {
fn update_controls(
&mut self,
physics: &mut World,
content: &content::Content,
data: &GameData,
) {
fn update_controls(&mut self, physics: &mut World, content: &content::Content) {
// Turn off all controls
let s = physics.get_ship_mut(&self.handle).unwrap();
s.controls.left = false;
@ -33,22 +27,20 @@ impl ShipBehavior for Point {
s.controls.thrust = false;
let (my_s, my_r) = physics.get_ship_body(self.handle).unwrap();
let my_data = data.get_ship(my_s.data_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_data.get_faction());
let my_faction = content.get_faction(my_s.ship.faction);
// Iterate all possible targets
let mut it = physics
.iter_ship_body()
.filter(|(s, _)| {
let data = data.get_ship(s.data_handle).unwrap();
match my_faction.relationships.get(&data.get_faction()).unwrap() {
.filter(
|(s, _)| match my_faction.relationships.get(&s.ship.faction).unwrap() {
content::Relationship::Hostile => true,
_ => false,
}
})
},
)
.map(|(_, r)| r);
// Find the closest target
@ -83,4 +75,8 @@ impl ShipBehavior for Point {
s.controls.guns = true;
s.controls.thrust = false;
}
fn get_handle(&self) -> ShipPhysicsHandle {
return self.handle;
}
}

View File

@ -0,0 +1,21 @@
#![warn(missing_docs)]
//! Computer-controlled ship behaviors
pub mod behavior;
use galactica_content as content;
use galactica_world::{ShipPhysicsHandle, World};
/// Main behavior trait. Any struct that implements this
/// may be used to control a ship.
pub trait ShipBehavior
where
Self: Send,
{
/// Update a ship's controls based on world state
fn update_controls(&mut self, physics: &mut World, content: &content::Content);
/// Get the ship this behavior is attached to
fn get_handle(&self) -> ShipPhysicsHandle;
}

View File

@ -272,11 +272,6 @@ impl Content {
// Access methods
impl Content {
/// Iterate over all valid system handles
pub fn iter_systems(&self) -> impl Iterator<Item = SystemHandle> {
(0..self.systems.len()).map(|x| SystemHandle { index: x })
}
/// Get the handle for the starfield sprite
pub fn get_starfield_handle(&self) -> SpriteHandle {
match self.starfield_handle {

View File

@ -25,6 +25,7 @@ galactica-content = { workspace = true }
galactica-render = { workspace = true }
galactica-constants = { workspace = true }
galactica-world = { workspace = true }
galactica-behavior = { workspace = true }
galactica-gameobject = { workspace = true }
winit = { workspace = true }

View File

@ -1,23 +1,23 @@
use object::{
ship::{OutfitSet, ShipPersonality},
GameData, GameShipHandle,
};
use cgmath::Point2;
use content::Ship;
use object::{ship::ShipPersonality, GameData};
use std::time::Instant;
use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode};
use crate::camera::Camera;
use crate::inputstatus::InputStatus;
use galactica_behavior::{behavior, ShipBehavior};
use galactica_constants;
use galactica_content as content;
use galactica_gameobject as object;
use galactica_render::RenderState;
use galactica_world::{objects::ShipControls, ParticleBuilder, StepResources, World};
use galactica_world::{util, ParticleBuilder, ShipPhysicsHandle, World};
pub struct Game {
input: InputStatus,
last_update: Instant,
player: GameShipHandle,
player: ShipPhysicsHandle,
paused: bool,
time_scale: f32,
start_instant: Instant,
@ -25,6 +25,9 @@ pub struct Game {
// TODO: include system in world
//system: object::System,
shipbehaviors: Vec<Box<dyn ShipBehavior>>,
playerbehavior: behavior::Player,
gamedata: GameData,
content: content::Content,
@ -34,28 +37,28 @@ pub struct Game {
impl Game {
pub fn new(ct: content::Content) -> Self {
let mut gamedata = GameData::new(&ct);
let mut physics = World::new();
let mut gamedata = GameData::new();
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 mut o1 = object::OutfitSet::new(ss);
o1.add(&ct, content::OutfitHandle { index: 0 });
o1.add(&ct, 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 mut o1 = OutfitSet::new(ss.space, &[]);
o1.add(&ct.get_outfit(content::OutfitHandle { index: 0 }));
//o1.add_gun(&ct, content::GunHandle { index: 0 });
let mut o1 = object::OutfitSet::new(ss);
o1.add(&ct, content::OutfitHandle { index: 0 });
o1.add_gun(&ct, content::GunHandle { index: 0 });
let player = gamedata.create_ship(
gamedata.create_ship(
&ct,
content::ShipHandle { index: 0 },
content::FactionHandle { index: 0 },
ShipPersonality::Player,
o1.clone(),
&content::SystemHandle { index: 0 },
o1,
);
gamedata.create_ship(
@ -63,8 +66,7 @@ impl Game {
content::ShipHandle { index: 0 },
content::FactionHandle { index: 1 },
ShipPersonality::Dummy,
OutfitSet::new(ss.space, &[]),
&content::SystemHandle { index: 0 },
object::OutfitSet::new(ss),
);
gamedata.create_ship(
@ -73,15 +75,16 @@ impl Game {
content::FactionHandle { index: 1 },
ShipPersonality::Point,
o1,
&content::SystemHandle { index: 0 },
);
let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 });
//let mut shipbehaviors: Vec<Box<dyn ShipBehavior>> = Vec::new();
//shipbehaviors.push(Box::new(behavior::Dummy::new(h2)));
//shipbehaviors.push(Box::new(behavior::Point::new(h3)));
Game {
last_update: Instant::now(),
input: InputStatus::new(),
player,
player: h1,
start_instant: Instant::now(),
camera: Camera {
@ -93,8 +96,10 @@ impl Game {
paused: false,
time_scale: 1.0,
world: physics,
shipbehaviors,
gamedata,
content: ct,
playerbehavior: behavior::Player::new(h1),
new_particles: Vec::new(),
}
}
@ -127,27 +132,39 @@ impl Game {
pub fn update(&mut self) {
let t: f32 = self.last_update.elapsed().as_secs_f32() * self.time_scale;
let world_output = self.world.step(StepResources {
player: self.player,
player_controls: ShipControls {
left: self.input.key_left,
right: self.input.key_right,
thrust: self.input.key_thrust,
guns: self.input.key_guns,
},
ct: &self.content,
dt: &mut self.gamedata,
particles: &mut self.new_particles,
t,
self.playerbehavior.key_guns = self.input.key_guns;
self.playerbehavior.key_thrust = self.input.key_thrust;
self.playerbehavior.key_right = self.input.key_right;
self.playerbehavior.key_left = self.input.key_left;
self.playerbehavior
.update_controls(&mut self.world, &self.content);
self.shipbehaviors.retain_mut(|b| {
// Remove shipbehaviors of destroyed ships
if self.world.get_ship_mut(&b.get_handle()).is_none() {
false
} else {
b.update_controls(&mut self.world, &self.content);
true
}
});
self.world.step(t, &self.content, &mut self.new_particles);
if self.input.v_scroll != 0.0 {
self.camera.zoom = (self.camera.zoom + self.input.v_scroll)
.clamp(galactica_constants::ZOOM_MIN, galactica_constants::ZOOM_MAX);
self.input.v_scroll = 0.0;
}
self.camera.pos = world_output.player_position;
let r = self
.world
.get_ship_mut(&self.player)
.unwrap()
.physics_handle;
let r = self.world.get_rigid_body(r.0).unwrap(); // TODO: r.0 shouldn't be public
let ship_pos = util::rigidbody_position(r);
self.camera.pos = ship_pos;
self.last_update = Instant::now();
}
@ -159,8 +176,7 @@ impl Game {
content: &self.content,
world: &self.world,
particles: &mut self.new_particles,
player_data: self.player,
data: &self.gamedata,
player: &self.player,
}
}
}

View File

@ -5,33 +5,21 @@ use crate::{
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.
#[derive(Debug)]
pub struct GameData {
/// Universal counter.
/// Used to create unique handles for game objects.
// Universal counter.
// Used to create unique handles for game objects.
index: u64,
/// All ships in the galaxy
ships: HashMap<GameShipHandle, Ship>,
/// Ships indexed by the system they're in.
/// A ship must always be in exactly one system.
system_ship_table: HashMap<content::SystemHandle, Vec<GameShipHandle>>,
/// Systems indexed by which ships they contain.
/// A ship must always be in exactly one system.
ship_system_table: HashMap<GameShipHandle, content::SystemHandle>,
}
impl GameData {
pub fn new(ct: &content::Content) -> Self {
pub fn new() -> Self {
Self {
system_ship_table: ct.iter_systems().map(|s| (s, Vec::new())).collect(),
ship_system_table: HashMap::new(),
index: 0,
ships: HashMap::new(),
}
@ -41,40 +29,19 @@ impl GameData {
pub fn create_ship(
&mut self,
ct: &content::Content,
ship: content::ShipHandle,
ship: ShipHandle,
faction: content::FactionHandle,
personality: ShipPersonality,
outfits: OutfitSet,
system: &content::SystemHandle,
) -> GameShipHandle {
let handle = GameShipHandle {
index: self.index,
content: ship,
};
let handle = GameShipHandle { index: self.index };
self.index += 1;
self.ships.insert(
handle,
Ship::new(ct, handle, ship, faction, personality, outfits),
);
self.system_ship_table.get_mut(system).unwrap().push(handle);
self.ship_system_table.insert(handle, *system);
self.ships
.insert(handle, Ship::new(ct, ship, faction, personality, outfits));
return handle;
}
}
// Public getters
impl GameData {
pub fn get_ship(&self, handle: GameShipHandle) -> Option<&Ship> {
self.ships.get(&handle)
}
pub fn get_ship_mut(&mut self, handle: GameShipHandle) -> Option<&mut Ship> {
self.ships.get_mut(&handle)
}
pub fn iter_ships(&self) -> impl Iterator<Item = &Ship> {
self.ships.values()
}
}

View File

@ -1,33 +1,5 @@
use std::hash::Hash;
use galactica_content::ShipHandle;
/// A lightweight representation of a ship in the galaxy
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GameShipHandle {
/// This ship's unique index
pub(crate) index: u64,
/// This ship's content handle
/// (technically redundant, but this greatly simplifies code)
pub(crate) content: ShipHandle,
}
impl GameShipHandle {
pub fn content_handle(&self) -> ShipHandle {
self.content
}
}
impl Hash for GameShipHandle {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.index.hash(state)
}
}
impl Eq for GameShipHandle {}
impl PartialEq for GameShipHandle {
fn eq(&self, other: &Self) -> bool {
self.index.eq(&other.index)
}
}

View File

@ -0,0 +1,20 @@
use galactica_content as content;
// TODO: remove. projectiles only exist in physics
#[derive(Debug)]
pub struct Projectile {
pub content: content::Projectile,
pub lifetime: f32,
pub faction: content::FactionHandle,
}
impl Projectile {
pub fn tick(&mut self, t: f32) {
self.lifetime -= t;
}
pub fn is_expired(&self) -> bool {
return self.lifetime < 0.0;
}
}

View File

@ -29,7 +29,7 @@ pub enum OutfitRemoveResult {
}
/// A simple data class, used to keep track of delayed shield generators
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(crate) struct ShieldGenerator {
pub outfit: OutfitHandle,
pub delay: f32,
@ -37,7 +37,7 @@ pub(crate) struct ShieldGenerator {
}
/// This struct keeps track of a ship's outfit loadout.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct OutfitSet {
/// What outfits does this statsum contain?
outfits: HashMap<OutfitHandle, u32>,

View File

@ -1,14 +1,11 @@
use std::time::Instant;
use crate::GameShipHandle;
use super::{OutfitSet, ShipPersonality};
use galactica_content as content;
#[derive(Debug)]
pub struct Ship {
// Metadata values
handle: GameShipHandle,
ct_handle: content::ShipHandle,
faction: content::FactionHandle,
outfits: OutfitSet,
@ -28,7 +25,6 @@ pub struct Ship {
impl Ship {
pub(crate) fn new(
ct: &content::Content,
handle: GameShipHandle,
ct_handle: content::ShipHandle,
faction: content::FactionHandle,
personality: ShipPersonality,
@ -37,7 +33,6 @@ impl Ship {
let s = ct.get_ship(ct_handle);
let shields = outfits.get_shield_strength();
Ship {
handle,
ct_handle,
faction,
outfits,
@ -89,20 +84,10 @@ impl Ship {
// Misc getters, so internal state is untouchable
impl Ship {
/// Get a handle to this ship game object
pub fn get_handle(&self) -> GameShipHandle {
self.handle
}
/// Get a handle to this ship's content
pub fn get_content(&self) -> content::ShipHandle {
self.ct_handle
}
/// Get this ship's current hull.
/// Use content handle to get maximum hull
pub fn get_hull(&self) -> f32 {
self.hull
return self.hull;
}
/// Get this ship's current shields.

View File

@ -21,7 +21,6 @@ galactica-content = { workspace = true }
galactica-constants = { workspace = true }
galactica-packer = { workspace = true }
galactica-world = { workspace = true }
galactica-gameobject = { workspace = true }
anyhow = { workspace = true }
cgmath = { workspace = true }

View File

@ -22,12 +22,7 @@ impl GPUState {
//let system_object_scale = 1.0 / 600.0;
let ship_scale = 1.0 / 10.0;
let player_world_object = state.world.get_ship(state.player_data).unwrap();
let player_body = state
.world
.get_rigid_body(player_world_object.rigid_body)
.unwrap();
let (_, player_body) = state.world.get_ship_body(*state.player).unwrap();
let player_position = util::rigidbody_position(player_body);
//let planet_sprite = state.content.get_sprite_handle("ui::planetblip");
let ship_sprite = state.content.get_sprite_handle("ui::shipblip");
@ -93,8 +88,7 @@ impl GPUState {
// Draw ships
for (s, r) in state.world.iter_ship_body() {
let data = state.data.get_ship(s.data_handle).unwrap();
let ship = state.content.get_ship(s.data_handle.content_handle());
let ship = state.content.get_ship(s.ship.handle);
let size = (ship.size * ship.sprite.aspect) * ship_scale;
let p = util::rigidbody_position(r);
let d = (p - player_position) / radar_range;
@ -105,7 +99,7 @@ impl GPUState {
continue;
}
let angle = util::rigidbody_rotation(r).angle(Vector2 { x: 0.0, y: 1.0 });
let f = state.content.get_faction(data.get_faction()).color;
let f = state.content.get_faction(s.ship.faction).color;
let f = [f[0], f[1], f[2], 1.0];
let position = Point2 {
@ -270,16 +264,11 @@ impl GPUState {
panic!("UI limit exceeded!")
}
let player_world_object = state.world.get_ship(state.player_data).unwrap();
let data = state
.data
.get_ship(player_world_object.data_handle)
.unwrap();
let max_shields = data.get_outfits().get_shield_strength();
let current_shields = data.get_shields();
let current_hull = data.get_hull();
let max_hull = state.content.get_ship(data.get_content()).hull;
let (s, _) = state.world.get_ship_body(*state.player).unwrap();
let max_shields = s.ship.outfits.stat_sum().shield_strength;
let current_shields = s.ship.shields;
let current_hull = s.ship.hull;
let max_hull = state.content.get_ship(s.ship.handle).hull;
self.queue.write_buffer(
&self.vertex_buffers.ui.instances,

View File

@ -21,12 +21,11 @@ impl GPUState {
screen_clip: (Point2<f32>, Point2<f32>),
s: &ShipWorldObject,
) {
let r = state.world.get_rigid_body(s.rigid_body).unwrap();
let ship = state.data.get_ship(s.data_handle);
let (_, r) = state.world.get_ship_body(s.physics_handle).unwrap();
let ship_pos = util::rigidbody_position(&r);
let ship_rot = util::rigidbody_rotation(r);
let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix!
let ship_cnt = state.content.get_ship(s.data_handle.content_handle());
let ship_cnt = state.content.get_ship(s.ship.handle);
// Position adjusted for parallax
// TODO: adjust parallax for zoom?
@ -85,10 +84,8 @@ impl GPUState {
);
self.vertex_buffers.object_counter += 1;
/*
// Draw engine flares if necessary
//if s.controls.thrust && !s.ship.is_dead() {
if s.controls.thrust {
if s.controls.thrust && !s.ship.is_dead() {
for f in s.ship.outfits.iter_enginepoints() {
let flare = match s.ship.outfits.get_flare_sprite() {
None => continue,
@ -129,7 +126,6 @@ impl GPUState {
self.vertex_buffers.object_counter += 1;
}
}
*/
}
pub(super) fn world_push_projectile(
@ -143,7 +139,7 @@ impl GPUState {
let proj_pos = util::rigidbody_position(&r);
let proj_rot = util::rigidbody_rotation(r);
let proj_ang = -proj_rot.angle(Vector2 { x: 1.0, y: 0.0 });
let proj_cnt = &p.content; // TODO: don't clone this?
let proj_cnt = &p.projectile.content; // TODO: don't clone this?
// Position adjusted for parallax
// TODO: adjust parallax for zoom?

View File

@ -1,15 +1,14 @@
use cgmath::Point2;
use galactica_content::Content;
use galactica_gameobject::{GameData, GameShipHandle};
use galactica_world::{ParticleBuilder, World};
use galactica_world::{ParticleBuilder, ShipPhysicsHandle, World};
/// Bundles parameters passed to a single call to GPUState::render
pub struct RenderState<'a> {
/// Camera position, in world units
pub camera_pos: Point2<f32>,
/// Player ship data
pub player_data: GameShipHandle,
/// Player ship
pub player: &'a ShipPhysicsHandle,
/// Height of screen, in world units
pub camera_zoom: f32,
@ -24,9 +23,6 @@ pub struct RenderState<'a> {
/// Game content
pub content: &'a Content,
/// Game data
pub data: &'a GameData,
/// Particles to spawn during this frame
pub particles: &'a mut Vec<ParticleBuilder>,
}

View File

@ -1,18 +0,0 @@
//! Various implementations of [`crate::ShipBehavior`]
mod null;
//mod point;
pub use null::*;
//pub use point::Point;
use crate::{objects::ShipControls, StepResources};
/// Main behavior trait. Any struct that implements this
/// may be used to control a ship.
pub trait ShipBehavior {
/// Update a ship's controls based on world state.
/// This method does not return anything, it modifies
/// the ship's controls in-place.
fn update_controls(&mut self, res: &StepResources) -> ShipControls;
}

View File

@ -1,19 +0,0 @@
use super::ShipBehavior;
use crate::{objects::ShipControls, StepResources};
/// The Null behaviors is assigned to objects that are not controlled by the computer.
/// Most notably, the player's ship has a Null behavior.
pub struct Null {}
impl Null {
/// Create a new ship controller
pub fn new() -> Self {
Self {}
}
}
impl ShipBehavior for Null {
fn update_controls(&mut self, _res: &StepResources) -> ShipControls {
ShipControls::new()
}
}

View File

@ -3,14 +3,102 @@
//! This module keeps track of the visible world.
//! Ships, projectiles, collisions, etc.
pub mod behavior;
pub mod objects;
mod particlebuilder;
mod stepresources;
pub mod util;
mod world;
mod wrapper;
pub use particlebuilder::*;
pub use stepresources::*;
use cgmath::{Matrix2, Point2, Rad, Vector2};
use galactica_content as content;
use rand::Rng;
pub use world::World;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
/// A lightweight handle for a specific ship in our physics system
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ShipPhysicsHandle(pub RigidBodyHandle, ColliderHandle);
/// Instructions to create a new particle
pub struct ParticleBuilder {
/// The sprite to use for this particle
pub sprite: content::SpriteHandle,
/// This object's center, in world coordinates.
pub pos: Point2<f32>,
/// This particle's velocity, in world coordinates
pub velocity: Vector2<f32>,
/// This particle's angle
pub angle: Rad<f32>,
/// This particle's angular velocity (rad/sec)
pub angvel: Rad<f32>,
/// This particle's lifetime, in seconds
pub lifetime: f32,
/// The size of this particle,
/// given as height in world units.
pub size: f32,
/// Fade this particle out over this many seconds as it expires
pub fade: f32,
}
impl ParticleBuilder {
/// Create a ParticleBuilder from an Effect
pub fn from_content(
effect: &content::Effect,
pos: Point2<f32>,
parent_angle: Rad<f32>,
parent_velocity: Vector2<f32>,
target_velocity: Vector2<f32>,
) -> Self {
let mut rng = rand::thread_rng();
let velocity = {
let a =
rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng);
let b =
rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng);
let velocity = ((effect.velocity_scale_parent + a) * parent_velocity)
+ ((effect.velocity_scale_target + b) * target_velocity);
Matrix2::from_angle(Rad(
rng.gen_range(-effect.direction_rng..=effect.direction_rng)
)) * velocity
};
// Rad has odd behavior when its angle is zero, so we need extra checks here
let angvel = if effect.angvel_rng == 0.0 {
effect.angvel
} else {
Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng))
};
let angle = if effect.angle_rng == 0.0 {
parent_angle + effect.angle
} else {
parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng))
};
ParticleBuilder {
sprite: effect.sprite,
pos,
velocity,
angle,
angvel,
lifetime: 0f32
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
// Make sure size isn't negative. This check should be on EVERY rng!
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
}
}
}

View File

@ -3,4 +3,4 @@ mod projectile;
mod ship;
pub use projectile::ProjectileWorldObject;
pub use ship::{ShipControls, ShipWorldObject};
pub use ship::ShipWorldObject;

View File

@ -1,19 +1,13 @@
use rand::Rng;
use rapier2d::{dynamics::RigidBodyHandle, geometry::ColliderHandle};
use galactica_content as content;
use galactica_gameobject as object;
/// A single projectile in the world
#[derive(Debug)]
pub struct ProjectileWorldObject {
/// This projectile's game data
pub content: content::Projectile,
/// The remaining lifetime of this projectile, in seconds
pub lifetime: f32,
/// The faction this projectile belongs to
pub faction: content::FactionHandle,
pub projectile: object::Projectile,
/// This projectile's rigidbody
pub rigid_body: RigidBodyHandle,
@ -26,33 +20,19 @@ pub struct ProjectileWorldObject {
}
impl ProjectileWorldObject {
/// Create a new projectile
/// Make a new projectile
pub fn new(
content: content::Projectile, // TODO: use a handle
projectile: object::Projectile,
rigid_body: RigidBodyHandle,
faction: content::FactionHandle,
collider: ColliderHandle,
) -> Self {
let mut rng = rand::thread_rng();
let size_rng = content.size_rng;
let lifetime = content.lifetime;
let size_rng = projectile.content.size_rng;
ProjectileWorldObject {
rigid_body,
collider,
content,
lifetime,
faction,
projectile,
size_rng: rng.gen_range(-size_rng..=size_rng),
}
}
/// Process this projectile's state after `t` seconds
pub fn tick(&mut self, t: f32) {
self.lifetime -= t;
}
/// Has this projectile expired?
pub fn is_expired(&self) -> bool {
return self.lifetime < 0.0;
}
}

View File

@ -1,30 +1,17 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
use nalgebra::{point, vector};
use object::GameShipHandle;
use rand::{rngs::ThreadRng, Rng};
use rapier2d::{
dynamics::{RigidBody, RigidBodyHandle},
geometry::{Collider, ColliderHandle},
};
use rapier2d::{dynamics::RigidBody, geometry::Collider};
use crate::{behavior::ShipBehavior, util, ParticleBuilder, StepResources};
use crate::{util, ParticleBuilder, ShipPhysicsHandle};
use galactica_content as content;
use galactica_gameobject as object;
/// A ship's controls
#[derive(Debug, Clone)]
pub struct ShipControls {
/// True if turning left
pub left: bool,
/// True if turning right
pub right: bool,
/// True if foward thrust
pub thrust: bool,
/// True if firing guns
pub guns: bool,
}
@ -48,23 +35,22 @@ struct ShipCollapseSequence {
impl ShipCollapseSequence {
fn new(total_length: f32) -> Self {
Self {
total_length,
total_length: total_length,
time_elapsed: 0.0,
rng: rand::thread_rng(),
}
}
/// Has this sequence been fully played out?
fn is_done(&self) -> bool {
self.time_elapsed >= self.total_length
}
/// Pick a random points inside a ship's collider
fn random_in_ship(
&mut self,
ship_content: &content::Ship,
collider: &Collider,
) -> Vector2<f32> {
// Pick a random point inside this ship's collider
let mut y = 0.0;
let mut x = 0.0;
let mut a = false;
@ -77,16 +63,17 @@ impl ShipCollapseSequence {
Vector2 { x, y }
}
/// Step this sequence `t` seconds
fn step(
&mut self,
res: &mut StepResources,
ship: GameShipHandle,
ship: &object::Ship,
ct: &content::Content,
particles: &mut Vec<ParticleBuilder>,
rigid_body: &mut RigidBody,
collider: &mut Collider,
t: f32,
) {
let h = ship.content_handle();
let ship_content = res.ct.get_ship(h);
let h = ship.handle;
let ship_content = ct.get_ship(h);
let ship_pos = util::rigidbody_position(rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
@ -98,11 +85,11 @@ impl ShipCollapseSequence {
for event in &ship_content.collapse.events {
match event {
content::CollapseEvent::Effect(event) => {
if (event.time > self.time_elapsed && event.time <= self.time_elapsed + res.t)
if (event.time > self.time_elapsed && event.time <= self.time_elapsed + t)
|| (event.time == 0.0 && self.time_elapsed == 0.0)
{
for spawner in &event.effects {
let effect = res.ct.get_effect(spawner.effect);
let effect = ct.get_effect(spawner.effect);
for _ in 0..spawner.count as usize {
let pos = if let Some(pos) = spawner.pos {
@ -115,7 +102,7 @@ impl ShipCollapseSequence {
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
res.particles.push(ParticleBuilder::from_content(
particles.push(ParticleBuilder::from_content(
effect,
pos,
Rad::zero(),
@ -134,7 +121,7 @@ impl ShipCollapseSequence {
// Create collapse effects
for spawner in &ship_content.collapse.effects {
let effect = res.ct.get_effect(spawner.effect);
let effect = ct.get_effect(spawner.effect);
// Probability of adding a particle this frame.
// The area of this function over [0, 1] should be 1.
@ -149,7 +136,7 @@ impl ShipCollapseSequence {
return y;
};
let p_add = (res.t / self.total_length) * pdf(frac_done) * spawner.count;
let p_add = (t / self.total_length) * pdf(frac_done) * spawner.count;
if self.rng.gen_range(0.0..=1.0) <= p_add {
let pos = if let Some(pos) = spawner.pos {
@ -162,7 +149,7 @@ impl ShipCollapseSequence {
let pos = ship_pos + Matrix2::from_angle(-ship_ang - Rad::from(Deg(90.0))) * pos;
let vel = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
res.particles.push(ParticleBuilder {
particles.push(ParticleBuilder {
sprite: effect.sprite,
pos,
velocity: Vector2 { x: vel.x, y: vel.y },
@ -175,28 +162,21 @@ impl ShipCollapseSequence {
}
}
self.time_elapsed += res.t;
self.time_elapsed += t;
}
}
/// A ship instance in the physics system
pub struct ShipWorldObject {
/// This ship's physics handle
pub rigid_body: RigidBodyHandle,
/// This ship's collider
pub collider: ColliderHandle,
pub physics_handle: ShipPhysicsHandle,
/// This ship's game data
pub data_handle: GameShipHandle,
pub ship: object::Ship,
/// This ship's controls
pub(crate) controls: ShipControls,
pub controls: ShipControls,
/// This ship's behavior
behavior: Box<dyn ShipBehavior>,
/// This ship's collapse sequence
collapse_sequence: ShipCollapseSequence,
}
@ -204,45 +184,51 @@ impl ShipWorldObject {
/// Make a new ship
pub fn new(
ct: &content::Content,
data_handle: GameShipHandle,
behavior: Box<dyn ShipBehavior>,
rigid_body: RigidBodyHandle,
collider: ColliderHandle,
ship: object::Ship,
physics_handle: ShipPhysicsHandle,
) -> Self {
let ship_content = ct.get_ship(data_handle.content_handle());
let ship_content = ct.get_ship(ship.handle);
ShipWorldObject {
rigid_body,
collider,
data_handle,
behavior,
physics_handle,
ship,
controls: ShipControls::new(),
collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length),
}
}
/// Compute this ship's controls using its behavior
pub fn update_controls(&mut self, res: &StepResources) {
self.controls = self.behavior.update_controls(res);
/// Should this ship should be removed from the world?
pub fn remove_from_world(&self) -> bool {
return self.ship.is_dead() && self.collapse_sequence.is_done();
}
/// Step this ship's state by t seconds
pub fn step(
&mut self,
res: &mut StepResources,
ct: &content::Content,
particles: &mut Vec<ParticleBuilder>,
rigid_body: &mut RigidBody,
collider: &mut Collider,
t: f32,
) {
let ship = res.dt.get_ship(self.data_handle).unwrap();
let ship_content = res.ct.get_ship(self.data_handle.content_handle());
if self.ship.is_dead() {
return self
.collapse_sequence
.step(&self.ship, ct, particles, rigid_body, collider, t);
}
self.ship.step(t);
let ship_content = ct.get_ship(self.ship.handle);
let ship_pos = util::rigidbody_position(&rigid_body);
let ship_rot = util::rigidbody_rotation(rigid_body);
let ship_ang = ship_rot.angle(Vector2 { x: 1.0, y: 0.0 });
let mut rng = rand::thread_rng();
if ship.get_hull() <= ship_content.damage.hull {
if self.ship.hull <= ship_content.damage.hull {
for e in &ship_content.damage.effects {
if rng.gen_range(0.0..=1.0) <= res.t / e.frequency {
let effect = res.ct.get_effect(e.effect);
if rng.gen_range(0.0..=1.0) <= t / e.frequency {
let effect = ct.get_effect(e.effect);
let ship_content = ct.get_ship(self.ship.handle);
let pos = if let Some(pos) = e.pos {
pos.to_vec()
@ -266,7 +252,7 @@ impl ShipWorldObject {
let velocity = rigid_body.velocity_at_point(&point![pos.x, pos.y]);
res.particles.push(ParticleBuilder::from_content(
particles.push(ParticleBuilder::from_content(
effect,
pos,
Rad::zero(),
@ -280,27 +266,28 @@ impl ShipWorldObject {
}
}
let engine_force = ship_rot * res.t;
let engine_force = ship_rot * t;
if self.controls.thrust {
rigid_body.apply_impulse(
vector![engine_force.x, engine_force.y] * ship.get_outfits().get_engine_thrust(),
vector![engine_force.x, engine_force.y]
* self.ship.outfits.stat_sum().engine_thrust,
true,
);
}
if self.controls.right {
rigid_body
.apply_torque_impulse(ship.get_outfits().get_steer_power() * -100.0 * res.t, true);
.apply_torque_impulse(self.ship.outfits.stat_sum().steer_power * -100.0 * t, true);
}
if self.controls.left {
rigid_body
.apply_torque_impulse(ship.get_outfits().get_steer_power() * 100.0 * res.t, true);
.apply_torque_impulse(self.ship.outfits.stat_sum().steer_power * 100.0 * t, true);
}
//for i in self.ship.outfits.iter_guns() {
// i.cooldown -= t;
//}
for i in self.ship.outfits.iter_guns() {
i.cooldown -= t;
}
}
}

View File

@ -1,88 +0,0 @@
use cgmath::{Matrix2, Point2, Rad, Vector2};
use galactica_content as content;
use rand::Rng;
/// Instructions to create a new particle
#[derive(Debug)]
pub struct ParticleBuilder {
/// The sprite to use for this particle
pub sprite: content::SpriteHandle,
/// This object's center, in world coordinates.
pub pos: Point2<f32>,
/// This particle's velocity, in world coordinates
pub velocity: Vector2<f32>,
/// This particle's angle
pub angle: Rad<f32>,
/// This particle's angular velocity (rad/sec)
pub angvel: Rad<f32>,
/// This particle's lifetime, in seconds
pub lifetime: f32,
/// The size of this particle,
/// given as height in world units.
pub size: f32,
/// Fade this particle out over this many seconds as it expires
pub fade: f32,
}
impl ParticleBuilder {
/// Create a ParticleBuilder from an Effect
pub fn from_content(
effect: &content::Effect,
pos: Point2<f32>,
parent_angle: Rad<f32>,
parent_velocity: Vector2<f32>,
target_velocity: Vector2<f32>,
) -> Self {
let mut rng = rand::thread_rng();
let velocity = {
let a =
rng.gen_range(-effect.velocity_scale_parent_rng..=effect.velocity_scale_parent_rng);
let b =
rng.gen_range(-effect.velocity_scale_target_rng..=effect.velocity_scale_target_rng);
let velocity = ((effect.velocity_scale_parent + a) * parent_velocity)
+ ((effect.velocity_scale_target + b) * target_velocity);
Matrix2::from_angle(Rad(
rng.gen_range(-effect.direction_rng..=effect.direction_rng)
)) * velocity
};
// Rad has odd behavior when its angle is zero, so we need extra checks here
let angvel = if effect.angvel_rng == 0.0 {
effect.angvel
} else {
Rad(effect.angvel.0 + rng.gen_range(-effect.angvel_rng..=effect.angvel_rng))
};
let angle = if effect.angle_rng == 0.0 {
parent_angle + effect.angle
} else {
parent_angle + effect.angle + Rad(rng.gen_range(-effect.angle_rng..=effect.angle_rng))
};
ParticleBuilder {
sprite: effect.sprite,
pos,
velocity,
angle,
angvel,
lifetime: 0f32
.max(effect.lifetime + rng.gen_range(-effect.lifetime_rng..=effect.lifetime_rng)),
// Make sure size isn't negative. This check should be on EVERY rng!
size: 0f32.max(effect.size + rng.gen_range(-effect.size_rng..=effect.size_rng)),
fade: 0f32.max(effect.fade + rng.gen_range(-effect.fade_rng..=effect.fade_rng)),
}
}
}

View File

@ -1,33 +0,0 @@
use crate::{objects::ShipControls, ParticleBuilder};
use cgmath::Point2;
use galactica_content::Content;
use galactica_gameobject::{GameData, GameShipHandle};
/// External resources we need to compute time steps
#[derive(Debug)]
pub struct StepResources<'a> {
/// Game content
pub ct: &'a Content,
/// Game data
pub dt: &'a mut GameData,
/// Length of time step
pub t: f32,
/// Particles to create
pub particles: &'a mut Vec<ParticleBuilder>,
/// Player inputs
pub player_controls: ShipControls,
/// The ship that the player controls
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>,
}

View File

@ -1,7 +1,6 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero};
use crossbeam::channel::Receiver;
use nalgebra::{point, vector};
use object::{ship::Ship, GameData, GameShipHandle};
use rand::Rng;
use rapier2d::{
dynamics::{RigidBody, RigidBodyBuilder, RigidBodyHandle},
@ -11,24 +10,21 @@ use rapier2d::{
use std::{collections::HashMap, f32::consts::PI};
use crate::{
behavior, objects,
objects,
objects::{ProjectileWorldObject, ShipWorldObject},
util,
wrapper::Wrapper,
ParticleBuilder, StepOutput, StepResources,
ParticleBuilder, ShipPhysicsHandle,
};
use galactica_content as content;
use galactica_gameobject as object;
/// Manages the physics state of one system
/// Keeps track of all objects in the world that we can interact with.
/// Also wraps our physics engine
pub struct World {
/// The system this world is attached to
system: content::SystemHandle,
wrapper: Wrapper,
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
ships: HashMap<GameShipHandle, objects::ShipWorldObject>,
collider_ship_table: HashMap<ColliderHandle, GameShipHandle>,
ships: HashMap<ColliderHandle, objects::ShipWorldObject>,
collision_handler: ChannelEventCollector,
collision_queue: Receiver<CollisionEvent>,
@ -60,25 +56,23 @@ impl<'a> World {
return Some((r, p));
}
fn remove_ship(&mut self, s: &ShipWorldObject) {
fn remove_ship(&mut self, h: ShipPhysicsHandle) {
self.wrapper.rigid_body_set.remove(
s.rigid_body,
h.0,
&mut self.wrapper.im,
&mut self.wrapper.collider_set,
&mut self.wrapper.ij,
&mut self.wrapper.mj,
true,
);
let h = self.collider_ship_table.remove(&s.collider).unwrap();
self.ships.remove(&h);
self.ships.remove(&h.1);
}
/*
/// Add a projectile fired from a ship
fn add_projectiles(
&mut self,
s: ShipPhysicsHandle,
p: Vec<(ProjectileWorldObject, content::GunPoint)>,
p: Vec<(object::Projectile, content::GunPoint)>,
) {
let mut rng = rand::thread_rng();
for (projectile, point) in p {
@ -132,44 +126,37 @@ impl<'a> World {
);
}
}
*/
fn collide_projectile_ship(
&mut self,
res: &mut StepResources,
ct: &content::Content,
particles: &mut Vec<ParticleBuilder>,
projectile_h: ColliderHandle,
ship_h: ColliderHandle,
) {
let projectile = self.projectiles.get(&projectile_h);
let ship = self
.ships
.get_mut(self.collider_ship_table.get(&ship_h).unwrap());
let ship = self.ships.get_mut(&ship_h);
if projectile.is_none() || ship.is_none() {
return;
}
let projectile = projectile.unwrap();
let ship = ship.unwrap();
let ship_d = res.dt.get_ship_mut(ship.data_handle).unwrap();
// TODO: check faction
ship_d.apply_damage(projectile.content.damage);
if true {
let hit = ship
.ship
.handle_projectile_collision(ct, &projectile.projectile);
let s = ship.physics_handle;
if hit {
let pr = self
.wrapper
.rigid_body_set
.get(projectile.rigid_body)
.unwrap();
let v = util::rigidbody_velocity(pr).normalize() * projectile.content.force;
let v = util::rigidbody_velocity(pr).normalize() * projectile.projectile.content.force;
let pos = util::rigidbody_position(pr);
let _ = pr;
let r = self
.wrapper
.rigid_body_set
.get_mut(ship.rigid_body)
.unwrap();
let r = self.wrapper.rigid_body_set.get_mut(s.0).unwrap();
r.apply_impulse_at_point(vector![v.x, v.y], point![pos.x, pos.y], true);
// Borrow again, we can only have one at a time
@ -181,17 +168,16 @@ impl<'a> World {
let pos = util::rigidbody_position(pr);
let angle = util::rigidbody_rotation(pr).angle(Vector2 { x: 1.0, y: 0.0 });
match &projectile.content.impact_effect {
match &projectile.projectile.content.impact_effect {
None => {}
Some(x) => {
let effect = res.ct.get_effect(*x);
let r = ship.rigid_body;
let sr = self.get_rigid_body(r).unwrap();
let effect = ct.get_effect(*x);
let (_, sr) = self.get_ship_body(s).unwrap();
let parent_velocity = util::rigidbody_velocity(pr);
let target_velocity =
sr.velocity_at_point(&nalgebra::Point2::new(pos.x, pos.y));
res.particles.push(ParticleBuilder::from_content(
particles.push(ParticleBuilder::from_content(
effect,
pos,
-angle,
@ -212,41 +198,27 @@ impl<'a> World {
// Public methods
impl<'a> World {
/// Create a new physics system
pub fn new(ct: &content::Content, dt: &GameData, system: content::SystemHandle) -> Self {
pub fn new() -> Self {
let (collision_send, collision_queue) = crossbeam::channel::unbounded();
let (contact_force_send, _) = crossbeam::channel::unbounded();
let mut w = Self {
system,
Self {
wrapper: Wrapper::new(),
projectiles: HashMap::new(),
ships: HashMap::new(),
collider_ship_table: HashMap::new(),
collision_handler: ChannelEventCollector::new(collision_send, contact_force_send),
collision_queue,
};
// TODO: guarantee not touching
// TODO: add, remove ships each tick
// Maybe store position in gamedata?
let mut rng = rand::thread_rng();
for s in dt.iter_ships() {
w.add_ship(
ct,
s,
Point2 {
x: rng.gen_range(-500.0..=500.0),
y: rng.gen_range(-500.0..=500.0),
},
);
}
return w;
}
/// Add a ship to this physics system
pub fn add_ship(&mut self, ct: &content::Content, ship: &Ship, position: Point2<f32>) {
let ship_content = ct.get_ship(ship.get_content());
pub fn add_ship(
&mut self,
ct: &content::Content,
ship: object::Ship,
position: Point2<f32>,
) -> ShipPhysicsHandle {
let ship_content = ct.get_ship(ship.handle);
let cl = ColliderBuilder::convex_decomposition(
&ship_content.collision.points[..],
&ship_content.collision.indices[..],
@ -270,60 +242,42 @@ impl<'a> World {
&mut self.wrapper.rigid_body_set,
);
self.collider_ship_table.insert(c, ship.get_handle());
self.ships.insert(
ship.get_handle(),
objects::ShipWorldObject::new(
ct,
ship.get_handle(),
Box::new(behavior::Null::new()),
r,
c,
),
);
let h = ShipPhysicsHandle(r, c);
self.ships
.insert(c, objects::ShipWorldObject::new(ct, ship, h));
return h;
}
/// Step this physics system by `t` seconds
pub fn step(&mut self, mut res: StepResources) -> StepOutput {
let mut output = StepOutput {
player_position: Point2 { x: 0.0, y: 0.0 },
};
pub fn step(&mut self, t: f32, ct: &content::Content, particles: &mut Vec<ParticleBuilder>) {
// Run ship updates
// TODO: maybe reorganize projectile creation?
//let mut projectiles = Vec::new();
let mut projectiles = Vec::new();
let mut to_remove = Vec::new();
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);
}
let r = &mut self.wrapper.rigid_body_set[s.physics_handle.0];
let c = &mut self.wrapper.collider_set[s.physics_handle.1];
// 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() {
// to_remove.push(s.physics_handle);
// continue;
//}
s.step(ct, particles, r, c, t);
if s.controls.guns {
projectiles.push((s.physics_handle, s.ship.fire_guns()));
}
if s.remove_from_world() {
to_remove.push(s.physics_handle);
continue;
}
}
for (s, p) in projectiles {
self.add_projectiles(s, p);
}
//for (s, p) in projectiles {
// self.add_projectiles(s, p);
//}
for s in to_remove {
self.remove_ship(s);
}
// Update physics
self.wrapper.step(res.t, &self.collision_handler);
self.wrapper.step(t, &self.collision_handler);
// Handle collision events
while let Ok(event) = &self.collision_queue.try_recv() {
@ -340,21 +294,19 @@ impl<'a> World {
};
let p = self.projectiles.get(&a);
let s = self
.ships
.get_mut(self.collider_ship_table.get(&b).unwrap());
let s = self.ships.get_mut(&b);
if p.is_none() || s.is_none() {
continue;
}
self.collide_projectile_ship(&mut res, a, b);
self.collide_projectile_ship(ct, particles, a, b);
}
}
// Delete projectiles
let mut to_remove = Vec::new();
for (c, p) in &mut self.projectiles {
p.tick(res.t);
if p.is_expired() {
p.projectile.tick(t);
if p.projectile.is_expired() {
to_remove.push(*c);
}
}
@ -363,10 +315,10 @@ impl<'a> World {
for c in to_remove {
let (pr, p) = self.remove_projectile(c).unwrap();
match &p.content.expire_effect {
match &p.projectile.content.expire_effect {
None => {}
Some(x) => {
let x = res.ct.get_effect(*x);
let x = ct.get_effect(*x);
let pos = util::rigidbody_position(&pr);
let vel = util::rigidbody_velocity(&pr);
let angle = util::rigidbody_rotation(&pr).angle(Vector2 { x: 1.0, y: 0.0 });
@ -380,7 +332,7 @@ impl<'a> World {
velocity
};
res.particles.push(ParticleBuilder::from_content(
particles.push(ParticleBuilder::from_content(
x,
pos,
-angle,
@ -390,12 +342,6 @@ impl<'a> World {
}
};
}
return output;
}
pub fn get_ship(&self, ship: GameShipHandle) -> Option<&ShipWorldObject> {
self.ships.get(&ship)
}
/// Get a rigid body from a handle
@ -408,13 +354,29 @@ impl<'a> World {
self.wrapper.rigid_body_set.get_mut(r)
}
/// Get a ship from a handle
pub fn get_ship_mut(&mut self, s: &ShipPhysicsHandle) -> Option<&mut objects::ShipWorldObject> {
self.ships.get_mut(&s.1)
}
/// Get a ship and its rigidbody from a handle
pub fn get_ship_body(
&self,
s: ShipPhysicsHandle,
) -> Option<(&objects::ShipWorldObject, &RigidBody)> {
Some((self.ships.get(&s.1)?, self.wrapper.rigid_body_set.get(s.0)?))
}
/// Iterate over all ships in this physics system
pub fn iter_ship_body(
&self,
) -> impl Iterator<Item = (&objects::ShipWorldObject, &RigidBody)> + '_ {
self.ships
.values()
.map(|x| (x, self.wrapper.rigid_body_set.get(x.rigid_body).unwrap()))
self.ships.values().map(|x| {
(
x,
self.wrapper.rigid_body_set.get(x.physics_handle.0).unwrap(),
)
})
}
/// Iterate over all ships in this physics system