diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index 3f06625..426a8e0 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -45,23 +45,29 @@ impl Game { s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 })); s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); - gamedata.create_ship( + let a = gamedata.create_ship( &ct, content::ShipHandle { index: 0 }, content::FactionHandle { index: 1 }, ShipPersonality::Dummy, &content::SystemHandle { index: 0 }, ); + let s = gamedata.get_ship_mut(a).unwrap(); + s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 })); + s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 })); + s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); let a = gamedata.create_ship( &ct, content::ShipHandle { index: 0 }, - content::FactionHandle { index: 1 }, + content::FactionHandle { index: 0 }, ShipPersonality::Point, &content::SystemHandle { index: 0 }, ); let s = gamedata.get_ship_mut(a).unwrap(); s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 0 })); + s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 1 })); + s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 }); diff --git a/crates/gameobject/src/ship/ship.rs b/crates/gameobject/src/ship/ship.rs index 8d532d7..db66924 100644 --- a/crates/gameobject/src/ship/ship.rs +++ b/crates/gameobject/src/ship/ship.rs @@ -56,7 +56,9 @@ impl Ship { /// Add an outfit to this ship pub fn add_outfit(&mut self, o: &content::Outfit) -> super::OutfitAddResult { - self.outfits.add(o) + let r = self.outfits.add(o); + self.shields = self.outfits.get_shield_strength(); + return r; } /// Remove an outfit from this ship diff --git a/crates/world/src/behavior/mod.rs b/crates/world/src/behavior/mod.rs index 4ded665..a632d71 100644 --- a/crates/world/src/behavior/mod.rs +++ b/crates/world/src/behavior/mod.rs @@ -1,12 +1,19 @@ //! Various implementations of [`crate::ShipBehavior`] mod null; -//mod point; +mod point; +use std::collections::HashMap; + +use galactica_gameobject::GameShipHandle; pub use null::*; -//pub use point::Point; +pub use point::Point; +use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet}; -use crate::{objects::ShipControls, StepResources}; +use crate::{ + objects::{ShipControls, ShipWorldObject}, + StepResources, +}; /// Main behavior trait. Any struct that implements this /// may be used to control a ship. @@ -14,5 +21,12 @@ 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; + fn update_controls( + &mut self, + res: &StepResources, + rigid_bodies: &RigidBodySet, + ships: &HashMap, + this_ship: RigidBodyHandle, + this_data: GameShipHandle, + ) -> ShipControls; } diff --git a/crates/world/src/behavior/null.rs b/crates/world/src/behavior/null.rs index 89d4115..ef64c96 100644 --- a/crates/world/src/behavior/null.rs +++ b/crates/world/src/behavior/null.rs @@ -1,5 +1,13 @@ +use std::collections::HashMap; + +use galactica_gameobject::GameShipHandle; +use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet}; + use super::ShipBehavior; -use crate::{objects::ShipControls, StepResources}; +use crate::{ + objects::{ShipControls, ShipWorldObject}, + 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. @@ -13,7 +21,14 @@ impl Null { } impl ShipBehavior for Null { - fn update_controls(&mut self, _res: &StepResources) -> ShipControls { + fn update_controls( + &mut self, + _res: &StepResources, + _rigid_bodies: &RigidBodySet, + _ships: &HashMap, + _this_ship: RigidBodyHandle, + _this_data: GameShipHandle, + ) -> ShipControls { ShipControls::new() } } diff --git a/crates/world/src/behavior/point.rs b/crates/world/src/behavior/point.rs index 2b1975d..f3ea853 100644 --- a/crates/world/src/behavior/point.rs +++ b/crates/world/src/behavior/point.rs @@ -1,9 +1,13 @@ use cgmath::{Deg, InnerSpace}; -use galactica_gameobject::GameData; +use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet}; +use std::collections::HashMap; +use crate::{ + objects::{ShipControls, ShipWorldObject}, + util, StepResources, +}; use galactica_content as content; - -use crate::World; +use galactica_gameobject::GameShipHandle; use super::ShipBehavior; @@ -21,43 +25,47 @@ impl Point { impl ShipBehavior for Point { fn update_controls( &mut self, - physics: &mut World, - content: &content::Content, - data: &GameData, - ) { - // 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; + res: &StepResources, + rigid_bodies: &RigidBodySet, + ships: &HashMap, + this_ship: RigidBodyHandle, + this_data: GameShipHandle, + ) -> ShipControls { + let mut controls = ShipControls::new(); - 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 this_rigidbody = rigid_bodies.get(this_ship).unwrap(); + let my_data = res.dt.get_ship(this_data).unwrap(); + let my_position = util::rigidbody_position(this_rigidbody); + let my_rotation = util::rigidbody_rotation(this_rigidbody); + let my_angvel = this_rigidbody.angvel(); + let my_faction = res.ct.get_faction(my_data.get_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() { - content::Relationship::Hostile => true, - _ => false, + let mut hostile_ships = ships + .values() + .filter(|s| { + let data = res.dt.get_ship(s.data_handle); + if let Some(data) = data { + match my_faction.relationships.get(&data.get_faction()).unwrap() { + content::Relationship::Hostile => true, + _ => false, + } + } else { + // This check is necessary---we don't want to target (or panic on) + // ships that don't have data (and are thus playing their collapse animation) + false } }) - .map(|(_, r)| r); + .map(|s| rigid_bodies.get(s.rigid_body).unwrap()); // Find the closest target - let mut closest_enemy_position = match it.next() { + let mut closest_enemy_position = match hostile_ships.next() { Some(c) => util::rigidbody_position(c), - None => return, // Do nothing if no targets are available + None => return controls, // Do nothing if no targets are available }; + let mut d = (my_position - closest_enemy_position).magnitude(); - for r in it { + for r in hostile_ships { let p = util::rigidbody_position(r); let new_d = (my_position - p).magnitude(); if new_d < d { @@ -70,17 +78,13 @@ impl ShipBehavior for Point { .angle(closest_enemy_position - my_position) .into(); - let s = physics.get_ship_mut(&self.handle).unwrap(); - s.controls.left = false; - s.controls.right = false; - if angle_delta < Deg(0.0) && my_angvel > -0.3 { - s.controls.right = true; + controls.right = true; } else if angle_delta > Deg(0.0) && my_angvel < 0.3 { - s.controls.left = true; + controls.left = true; } - s.controls.guns = true; - s.controls.thrust = false; + controls.guns = true; + return controls; } } diff --git a/crates/world/src/objects/ship.rs b/crates/world/src/objects/ship.rs index 044bbd2..67233bc 100644 --- a/crates/world/src/objects/ship.rs +++ b/crates/world/src/objects/ship.rs @@ -1,7 +1,6 @@ use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero}; use content::{FactionHandle, ShipHandle}; use nalgebra::{point, vector}; - use object::GameShipHandle; use rand::{rngs::ThreadRng, Rng}; use rapier2d::{ @@ -9,7 +8,7 @@ use rapier2d::{ geometry::{Collider, ColliderHandle}, }; -use crate::{behavior::ShipBehavior, util, ParticleBuilder, StepResources}; +use crate::{util, ParticleBuilder, StepResources}; use galactica_content as content; use galactica_gameobject as object; @@ -41,6 +40,7 @@ impl ShipControls { } } +#[derive(Debug)] struct ShipCollapseSequence { total_length: f32, time_elapsed: f32, @@ -181,6 +181,7 @@ impl ShipCollapseSequence { } /// A ship instance in the physics system +#[derive(Debug)] pub struct ShipWorldObject { /// This ship's physics handle pub rigid_body: RigidBodyHandle, @@ -194,9 +195,6 @@ pub struct ShipWorldObject { /// This ship's controls pub(crate) controls: ShipControls, - /// This ship's behavior - behavior: Box, - /// This ship's collapse sequence collapse_sequence: ShipCollapseSequence, @@ -209,10 +207,9 @@ pub struct ShipWorldObject { impl ShipWorldObject { /// Make a new ship - pub fn new( + pub(crate) fn new( ct: &content::Content, data_handle: GameShipHandle, - behavior: Box, faction: FactionHandle, rigid_body: RigidBodyHandle, collider: ColliderHandle, @@ -222,18 +219,12 @@ impl ShipWorldObject { rigid_body, collider, data_handle, - behavior, controls: ShipControls::new(), faction, 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); - } - /// If this is true, remove this ship from the physics system. pub fn should_be_removed(&self) -> bool { self.collapse_sequence.is_done() diff --git a/crates/world/src/world.rs b/crates/world/src/world.rs index 797d6d7..42781c1 100644 --- a/crates/world/src/world.rs +++ b/crates/world/src/world.rs @@ -12,7 +12,8 @@ use rapier2d::{ use std::{collections::HashMap, f32::consts::PI}; use crate::{ - behavior, objects, + behavior::{self, ShipBehavior}, + objects, objects::{ProjectileWorldObject, ShipWorldObject}, util, wrapper::Wrapper, @@ -29,6 +30,7 @@ pub struct World { wrapper: Wrapper, projectiles: HashMap, ships: HashMap, + ship_behaviors: HashMap>, collider_ship_table: HashMap, collision_handler: ChannelEventCollector, @@ -79,6 +81,7 @@ impl<'a> World { true, ); let h = self.collider_ship_table.remove(&s.collider).unwrap(); + self.ship_behaviors.remove(&h); self.ships.remove(&h); } @@ -177,6 +180,7 @@ impl World { wrapper: Wrapper::new(), projectiles: HashMap::new(), ships: HashMap::new(), + ship_behaviors: HashMap::new(), collider_ship_table: HashMap::new(), collision_handler: ChannelEventCollector::new(collision_send, contact_force_send), collision_queue, @@ -227,16 +231,11 @@ impl World { ); self.collider_ship_table.insert(c, ship.get_handle()); + self.ship_behaviors + .insert(ship.get_handle(), Box::new(behavior::Point::new())); self.ships.insert( ship.get_handle(), - objects::ShipWorldObject::new( - ct, - ship.get_handle(), - Box::new(behavior::Null::new()), - ship.get_faction(), - r, - c, - ), + objects::ShipWorldObject::new(ct, ship.get_handle(), ship.get_faction(), r, c), ); } @@ -245,25 +244,57 @@ impl World { /// - applies ship controls /// - creates projectiles fn step_ships(&mut self, res: &mut StepResources) { + // We can't apply these right away since self is borrowed + // by the iterator + // TODO: don't allocate! let mut projectiles = Vec::new(); let mut to_remove = Vec::new(); - for (_, ship_object) in &mut self.ships { + for (_, handle) in &self.collider_ship_table { + // Borrow immutably for now... + // (required to compute controls) + let ship_object = self.ships.get(handle).unwrap(); if ship_object.should_be_removed() { to_remove.push(ship_object.collider); continue; } - let rigid_body = &mut self.wrapper.rigid_body_set[ship_object.rigid_body]; - let collider = &mut self.wrapper.collider_set[ship_object.collider]; - - if ship_object.data_handle == res.player { - ship_object.controls = res.player_controls.clone(); - } else { - ship_object.update_controls(&res); + // Short-circuit continue if this ship isn't in game data + // (which means it's playing a collapse sequence) + if res.dt.get_ship(*handle).is_none() { + let ship_object = self.ships.get_mut(handle).unwrap(); + ship_object.step( + res, + &mut self.wrapper.rigid_body_set[ship_object.rigid_body], + &mut self.wrapper.collider_set[ship_object.collider], + ); + continue; } - // TODO: unified step info struct - ship_object.step(res, rigid_body, collider); + // Compute new controls + let controls; + if ship_object.data_handle == res.player { + controls = res.player_controls.clone(); + } else { + let b = self.ship_behaviors.get_mut(handle).unwrap(); + controls = b.update_controls( + &res, + &self.wrapper.rigid_body_set, + &self.ships, + ship_object.rigid_body, + *handle, + ); + } + + // Now re-borrow mutably to apply changes + let ship_object = self.ships.get_mut(handle).unwrap(); + ship_object.controls = controls; + ship_object.step( + res, + &mut self.wrapper.rigid_body_set[ship_object.rigid_body], + &mut self.wrapper.collider_set[ship_object.collider], + ); + + // If we're firing, try to fire each gun if ship_object.controls.guns { let ship_data = res.dt.get_ship_mut(ship_object.data_handle).unwrap();