Repaired ship behaviors

master
Mark 2024-01-09 20:51:28 -08:00
parent 7854245a4b
commit 64885a8b6d
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
7 changed files with 142 additions and 79 deletions

View File

@ -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: 1 }));
s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 })); s.add_outfit(&ct.get_outfit(content::OutfitHandle { index: 2 }));
gamedata.create_ship( let a = gamedata.create_ship(
&ct, &ct,
content::ShipHandle { index: 0 }, content::ShipHandle { index: 0 },
content::FactionHandle { index: 1 }, content::FactionHandle { index: 1 },
ShipPersonality::Dummy, ShipPersonality::Dummy,
&content::SystemHandle { index: 0 }, &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( let a = gamedata.create_ship(
&ct, &ct,
content::ShipHandle { index: 0 }, content::ShipHandle { index: 0 },
content::FactionHandle { index: 1 }, content::FactionHandle { index: 0 },
ShipPersonality::Point, ShipPersonality::Point,
&content::SystemHandle { index: 0 }, &content::SystemHandle { index: 0 },
); );
let s = gamedata.get_ship_mut(a).unwrap(); 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: 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 }); let physics = World::new(&ct, &gamedata, content::SystemHandle { index: 0 });

View File

@ -56,7 +56,9 @@ impl Ship {
/// Add an outfit to this ship /// Add an outfit to this ship
pub fn add_outfit(&mut self, o: &content::Outfit) -> super::OutfitAddResult { 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 /// Remove an outfit from this ship

View File

@ -1,12 +1,19 @@
//! Various implementations of [`crate::ShipBehavior`] //! Various implementations of [`crate::ShipBehavior`]
mod null; mod null;
//mod point; mod point;
use std::collections::HashMap;
use galactica_gameobject::GameShipHandle;
pub use null::*; 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 /// Main behavior trait. Any struct that implements this
/// may be used to control a ship. /// may be used to control a ship.
@ -14,5 +21,12 @@ pub trait ShipBehavior {
/// Update a ship's controls based on world state. /// Update a ship's controls based on world state.
/// This method does not return anything, it modifies /// This method does not return anything, it modifies
/// the ship's controls in-place. /// 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<GameShipHandle, ShipWorldObject>,
this_ship: RigidBodyHandle,
this_data: GameShipHandle,
) -> ShipControls;
} }

View File

@ -1,5 +1,13 @@
use std::collections::HashMap;
use galactica_gameobject::GameShipHandle;
use rapier2d::dynamics::{RigidBodyHandle, RigidBodySet};
use super::ShipBehavior; 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. /// The Null behaviors is assigned to objects that are not controlled by the computer.
/// Most notably, the player's ship has a Null behavior. /// Most notably, the player's ship has a Null behavior.
@ -13,7 +21,14 @@ impl Null {
} }
impl ShipBehavior for 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<GameShipHandle, ShipWorldObject>,
_this_ship: RigidBodyHandle,
_this_data: GameShipHandle,
) -> ShipControls {
ShipControls::new() ShipControls::new()
} }
} }

View File

@ -1,9 +1,13 @@
use cgmath::{Deg, InnerSpace}; 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 galactica_content as content;
use galactica_gameobject::GameShipHandle;
use crate::World;
use super::ShipBehavior; use super::ShipBehavior;
@ -21,43 +25,47 @@ impl Point {
impl ShipBehavior for Point { impl ShipBehavior for Point {
fn update_controls( fn update_controls(
&mut self, &mut self,
physics: &mut World, res: &StepResources,
content: &content::Content, rigid_bodies: &RigidBodySet,
data: &GameData, ships: &HashMap<GameShipHandle, ShipWorldObject>,
) { this_ship: RigidBodyHandle,
// Turn off all controls this_data: GameShipHandle,
let s = physics.get_ship_mut(&self.handle).unwrap(); ) -> ShipControls {
s.controls.left = false; let mut controls = ShipControls::new();
s.controls.right = false;
s.controls.guns = false;
s.controls.thrust = false;
let (my_s, my_r) = physics.get_ship_body(self.handle).unwrap(); let this_rigidbody = rigid_bodies.get(this_ship).unwrap();
let my_data = data.get_ship(my_s.data_handle).unwrap(); let my_data = res.dt.get_ship(this_data).unwrap();
let my_position = util::rigidbody_position(my_r); let my_position = util::rigidbody_position(this_rigidbody);
let my_rotation = util::rigidbody_rotation(my_r); let my_rotation = util::rigidbody_rotation(this_rigidbody);
let my_angvel = my_r.angvel(); let my_angvel = this_rigidbody.angvel();
let my_faction = content.get_faction(my_data.get_faction()); let my_faction = res.ct.get_faction(my_data.get_faction());
// Iterate all possible targets // Iterate all possible targets
let mut it = physics let mut hostile_ships = ships
.iter_ship_body() .values()
.filter(|(s, _)| { .filter(|s| {
let data = data.get_ship(s.data_handle).unwrap(); let data = res.dt.get_ship(s.data_handle);
match my_faction.relationships.get(&data.get_faction()).unwrap() { if let Some(data) = data {
content::Relationship::Hostile => true, match my_faction.relationships.get(&data.get_faction()).unwrap() {
_ => false, 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 // 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), 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(); 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 p = util::rigidbody_position(r);
let new_d = (my_position - p).magnitude(); let new_d = (my_position - p).magnitude();
if new_d < d { if new_d < d {
@ -70,17 +78,13 @@ impl ShipBehavior for Point {
.angle(closest_enemy_position - my_position) .angle(closest_enemy_position - my_position)
.into(); .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 { 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 { } else if angle_delta > Deg(0.0) && my_angvel < 0.3 {
s.controls.left = true; controls.left = true;
} }
s.controls.guns = true; controls.guns = true;
s.controls.thrust = false; return controls;
} }
} }

View File

@ -1,7 +1,6 @@
use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero}; use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Rad, Vector2, Zero};
use content::{FactionHandle, ShipHandle}; use content::{FactionHandle, ShipHandle};
use nalgebra::{point, vector}; use nalgebra::{point, vector};
use object::GameShipHandle; use object::GameShipHandle;
use rand::{rngs::ThreadRng, Rng}; use rand::{rngs::ThreadRng, Rng};
use rapier2d::{ use rapier2d::{
@ -9,7 +8,7 @@ use rapier2d::{
geometry::{Collider, ColliderHandle}, geometry::{Collider, ColliderHandle},
}; };
use crate::{behavior::ShipBehavior, util, ParticleBuilder, StepResources}; use crate::{util, ParticleBuilder, StepResources};
use galactica_content as content; use galactica_content as content;
use galactica_gameobject as object; use galactica_gameobject as object;
@ -41,6 +40,7 @@ impl ShipControls {
} }
} }
#[derive(Debug)]
struct ShipCollapseSequence { struct ShipCollapseSequence {
total_length: f32, total_length: f32,
time_elapsed: f32, time_elapsed: f32,
@ -181,6 +181,7 @@ impl ShipCollapseSequence {
} }
/// A ship instance in the physics system /// A ship instance in the physics system
#[derive(Debug)]
pub struct ShipWorldObject { pub struct ShipWorldObject {
/// This ship's physics handle /// This ship's physics handle
pub rigid_body: RigidBodyHandle, pub rigid_body: RigidBodyHandle,
@ -194,9 +195,6 @@ pub struct ShipWorldObject {
/// This ship's controls /// This ship's controls
pub(crate) controls: ShipControls, pub(crate) controls: ShipControls,
/// This ship's behavior
behavior: Box<dyn ShipBehavior>,
/// This ship's collapse sequence /// This ship's collapse sequence
collapse_sequence: ShipCollapseSequence, collapse_sequence: ShipCollapseSequence,
@ -209,10 +207,9 @@ pub struct ShipWorldObject {
impl ShipWorldObject { impl ShipWorldObject {
/// Make a new ship /// Make a new ship
pub fn new( pub(crate) fn new(
ct: &content::Content, ct: &content::Content,
data_handle: GameShipHandle, data_handle: GameShipHandle,
behavior: Box<dyn ShipBehavior>,
faction: FactionHandle, faction: FactionHandle,
rigid_body: RigidBodyHandle, rigid_body: RigidBodyHandle,
collider: ColliderHandle, collider: ColliderHandle,
@ -222,18 +219,12 @@ impl ShipWorldObject {
rigid_body, rigid_body,
collider, collider,
data_handle, data_handle,
behavior,
controls: ShipControls::new(), controls: ShipControls::new(),
faction, faction,
collapse_sequence: ShipCollapseSequence::new(ship_content.collapse.length), 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. /// If this is true, remove this ship from the physics system.
pub fn should_be_removed(&self) -> bool { pub fn should_be_removed(&self) -> bool {
self.collapse_sequence.is_done() self.collapse_sequence.is_done()

View File

@ -12,7 +12,8 @@ use rapier2d::{
use std::{collections::HashMap, f32::consts::PI}; use std::{collections::HashMap, f32::consts::PI};
use crate::{ use crate::{
behavior, objects, behavior::{self, ShipBehavior},
objects,
objects::{ProjectileWorldObject, ShipWorldObject}, objects::{ProjectileWorldObject, ShipWorldObject},
util, util,
wrapper::Wrapper, wrapper::Wrapper,
@ -29,6 +30,7 @@ pub struct World {
wrapper: Wrapper, wrapper: Wrapper,
projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>, projectiles: HashMap<ColliderHandle, objects::ProjectileWorldObject>,
ships: HashMap<GameShipHandle, objects::ShipWorldObject>, ships: HashMap<GameShipHandle, objects::ShipWorldObject>,
ship_behaviors: HashMap<GameShipHandle, Box<dyn ShipBehavior>>,
collider_ship_table: HashMap<ColliderHandle, GameShipHandle>, collider_ship_table: HashMap<ColliderHandle, GameShipHandle>,
collision_handler: ChannelEventCollector, collision_handler: ChannelEventCollector,
@ -79,6 +81,7 @@ impl<'a> World {
true, true,
); );
let h = self.collider_ship_table.remove(&s.collider).unwrap(); let h = self.collider_ship_table.remove(&s.collider).unwrap();
self.ship_behaviors.remove(&h);
self.ships.remove(&h); self.ships.remove(&h);
} }
@ -177,6 +180,7 @@ impl World {
wrapper: Wrapper::new(), wrapper: Wrapper::new(),
projectiles: HashMap::new(), projectiles: HashMap::new(),
ships: HashMap::new(), ships: HashMap::new(),
ship_behaviors: HashMap::new(),
collider_ship_table: 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,
@ -227,16 +231,11 @@ impl World {
); );
self.collider_ship_table.insert(c, ship.get_handle()); self.collider_ship_table.insert(c, ship.get_handle());
self.ship_behaviors
.insert(ship.get_handle(), Box::new(behavior::Point::new()));
self.ships.insert( self.ships.insert(
ship.get_handle(), ship.get_handle(),
objects::ShipWorldObject::new( objects::ShipWorldObject::new(ct, ship.get_handle(), ship.get_faction(), r, c),
ct,
ship.get_handle(),
Box::new(behavior::Null::new()),
ship.get_faction(),
r,
c,
),
); );
} }
@ -245,25 +244,57 @@ impl World {
/// - applies ship controls /// - applies ship controls
/// - creates projectiles /// - creates projectiles
fn step_ships(&mut self, res: &mut StepResources) { 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 projectiles = Vec::new();
let mut to_remove = 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() { if ship_object.should_be_removed() {
to_remove.push(ship_object.collider); to_remove.push(ship_object.collider);
continue; continue;
} }
let rigid_body = &mut self.wrapper.rigid_body_set[ship_object.rigid_body]; // Short-circuit continue if this ship isn't in game data
let collider = &mut self.wrapper.collider_set[ship_object.collider]; // (which means it's playing a collapse sequence)
if res.dt.get_ship(*handle).is_none() {
if ship_object.data_handle == res.player { let ship_object = self.ships.get_mut(handle).unwrap();
ship_object.controls = res.player_controls.clone(); ship_object.step(
} else { res,
ship_object.update_controls(&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 // Compute new controls
ship_object.step(res, rigid_body, collider); 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 { if ship_object.controls.guns {
let ship_data = res.dt.get_ship_mut(ship_object.data_handle).unwrap(); let ship_data = res.dt.get_ship_mut(ship_object.data_handle).unwrap();