Rework world crate

master
Mark 2024-01-09 11:38:47 -08:00
parent 901f407068
commit 35c6676e95
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
5 changed files with 248 additions and 193 deletions

View File

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

View File

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

View File

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

View File

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