diff --git a/Cargo.lock b/Cargo.lock index 5740129..55eec09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -702,6 +702,7 @@ dependencies = [ "cgmath", "galactica-content", "galactica-packer", + "galactica-playeragent", "galactica-system", "galactica-util", "glyphon", diff --git a/crates/content/src/handle.rs b/crates/content/src/handle.rs index 3fd7680..6d5ab93 100644 --- a/crates/content/src/handle.rs +++ b/crates/content/src/handle.rs @@ -37,6 +37,15 @@ impl PartialEq for SpriteHandle { } } +/// A lightweight representation of system body +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct SystemObjectHandle { + /// TODO: pub in crate + pub system_handle: SystemHandle, + /// The index of this object in system.objects + pub body_index: usize, +} + /// A lightweight representation of an outfit #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct OutfitHandle { diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index a1b526e..ace5697 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -18,9 +18,7 @@ use std::{ use toml; use walkdir::WalkDir; -pub use handle::{ - EffectHandle, FactionHandle, GunHandle, OutfitHandle, ShipHandle, SpriteHandle, SystemHandle, -}; +pub use handle::*; pub use part::*; mod syntax { @@ -333,6 +331,11 @@ impl Content { return &self.systems[h.index]; } + /// Get a system object from a handle + pub fn get_system_object(&self, h: SystemObjectHandle) -> &SystemObject { + return &self.get_system(h.system_handle).objects[h.body_index]; + } + /// Get a faction from a handle pub fn get_faction(&self, h: FactionHandle) -> &Faction { return &self.factions[h.index]; diff --git a/crates/content/src/part/mod.rs b/crates/content/src/part/mod.rs index 48e50d7..52dd207 100644 --- a/crates/content/src/part/mod.rs +++ b/crates/content/src/part/mod.rs @@ -19,4 +19,4 @@ pub use ship::{ ShipCollapse, }; pub use sprite::{RepeatMode, Sprite}; -pub use system::{Object, System}; +pub use system::{System, SystemObject}; diff --git a/crates/content/src/part/system.rs b/crates/content/src/part/system.rs index b95432e..2fa9981 100644 --- a/crates/content/src/part/system.rs +++ b/crates/content/src/part/system.rs @@ -2,7 +2,10 @@ use anyhow::{bail, Context, Result}; use cgmath::{Deg, Point3, Rad}; use std::collections::{HashMap, HashSet}; -use crate::{handle::SpriteHandle, util::Polar, Content, ContentBuildContext}; +use crate::{ + handle::SpriteHandle, util::Polar, Content, ContentBuildContext, SystemHandle, + SystemObjectHandle, +}; pub(crate) mod syntax { use serde::Deserialize; @@ -83,8 +86,11 @@ pub struct System { /// This star system's name pub name: String, + /// This star system's handle + pub handle: SystemHandle, + /// Objects in this system - pub objects: Vec, + pub objects: Vec, } /// Represents an orbiting body in a star system @@ -92,10 +98,13 @@ pub struct System { /// These may be landable and may be decorative. /// System objects to not interact with the physics engine. #[derive(Debug, Clone)] -pub struct Object { +pub struct SystemObject { /// This object's sprite pub sprite: SpriteHandle, + /// This object's handle + pub handle: SystemObjectHandle, + /// This object's size. /// Measured as height in game units. /// This value is scaled for distance @@ -185,11 +194,15 @@ impl crate::Build for System { for (system_name, system) in system { let mut objects = Vec::new(); + let system_handle = SystemHandle { + index: content.systems.len(), + }; + for (label, obj) in &system.object { let mut cycle_detector = HashSet::new(); cycle_detector.insert(label.clone()); - let handle = match content.sprite_index.get(&obj.sprite) { + let sprite_handle = match content.sprite_index.get(&obj.sprite) { None => bail!( "In system `{}`: sprite `{}` doesn't exist", system_name, @@ -198,17 +211,33 @@ impl crate::Build for System { Some(t) => *t, }; - objects.push(Object { - sprite: handle, + objects.push(SystemObject { + sprite: sprite_handle, pos: resolve_position(&system.object, &obj, cycle_detector) .with_context(|| format!("In object {:#?}", label))?, size: obj.size, angle: Deg(obj.angle.unwrap_or(0.0)).into(), + handle: SystemObjectHandle { + system_handle, + body_index: 0, + }, }); } + // Sort by z-distance. This is important, since these are + // rendered in this order. We need far objects to be behind + // near objects! objects.sort_by(|a, b| b.pos.z.total_cmp(&a.pos.z)); + + // Update object handles + let mut i = 0; + for o in &mut objects { + o.handle.body_index = i; + i += 1; + } + content.systems.push(Self { + handle: system_handle, name: system_name, objects, }); diff --git a/crates/galactica/src/game.rs b/crates/galactica/src/game.rs index df560c4..b035044 100644 --- a/crates/galactica/src/game.rs +++ b/crates/galactica/src/game.rs @@ -104,8 +104,10 @@ impl<'a> Game { } } - pub fn update_player_controls(&mut self, player: &PlayerAgent) { - self.state.systemsim.update_player_controls(player) + pub fn update_player_controls(&mut self, player: &mut PlayerAgent) { + self.state + .systemsim + .update_player_controls(&self.ct, player) } pub fn get_state(&self) -> &GameState { diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index 51cf66d..7023de1 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -1,10 +1,14 @@ mod game; use anyhow::{bail, Result}; +use cgmath::Point2; use galactica_content::{Content, SystemHandle}; use galactica_playeragent::{PlayerAgent, PlayerStatus}; use galactica_render::RenderInput; -use galactica_system::phys::{util, PhysSimShipHandle}; +use galactica_system::{ + data::ShipState, + phys::{util, PhysSimShipHandle}, +}; use galactica_util::constants::ASSET_CACHE; use std::{ fs, @@ -56,7 +60,7 @@ fn main() -> Result<()> { ct: &content, systemsim: &game.get_state().systemsim, particles: game.get_particles(), - player_ship: PhysSimShipHandle(player.ship.unwrap()), + player: &player, current_system: SystemHandle { index: 0 }, timing: game.get_state().timing.clone(), }; @@ -71,9 +75,10 @@ fn main() -> Result<()> { } Event::MainEventsCleared => { - game.update_player_controls(&player); + game.update_player_controls(&mut player); game.update(); + // TODO: clean up let player_status = { let pos = { let o = &game @@ -81,11 +86,23 @@ fn main() -> Result<()> { .systemsim .get_ship(&PhysSimShipHandle(player.ship.unwrap())); if let Some(o) = o { - let r = &game.get_state().systemsim.get_rigid_body(o.rigid_body); - if let Some(r) = r { - Some(util::rigidbody_position(r)) - } else { - None + match o.data.get_state() { + ShipState::Collapsing { .. } | ShipState::Flying => { + let r = + &game.get_state().systemsim.get_rigid_body(o.rigid_body); + if let Some(r) = r { + Some(util::rigidbody_position(r)) + } else { + None + } + } + ShipState::Landed { target } => { + let b = content.get_system_object(*target); + Some(Point2 { + x: b.pos.x, + y: b.pos.y, + }) + } } } else { None @@ -98,6 +115,7 @@ fn main() -> Result<()> { // This must be updated BEFORE rendering! player.step(&content, player_status); + player.input.clear_inputs(); gpu.window().request_redraw(); } diff --git a/crates/playeragent/src/inputstatus.rs b/crates/playeragent/src/inputstatus.rs index 3bd1fd9..3eb5ca8 100644 --- a/crates/playeragent/src/inputstatus.rs +++ b/crates/playeragent/src/inputstatus.rs @@ -1,12 +1,18 @@ use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; pub struct InputStatus { + // Parameters scroll_speed: f32, - pub key_left: bool, - pub key_right: bool, - pub key_thrust: bool, - pub key_guns: bool, - pub v_scroll: f32, + + // Continuous keys + key_left: bool, + key_right: bool, + key_thrust: bool, + key_guns: bool, + + // One-shot keys (audomatically released at the end of each frame) + key_land: bool, + v_scroll: f32, } impl InputStatus { @@ -16,6 +22,7 @@ impl InputStatus { key_right: false, key_thrust: false, key_guns: false, + key_land: false, v_scroll: 0.0, scroll_speed: 10.0, } @@ -26,15 +33,34 @@ impl InputStatus { self.key_right = false; self.key_thrust = false; self.key_guns = false; + self.key_land = false; + } + + /// Called at the end of every frame, + /// resets one-shot keys. + pub fn clear_inputs(&mut self) { + self.key_land = false; + self.v_scroll = 0.0; } pub fn process_key(&mut self, state: &ElementState, key: &VirtualKeyCode) { let down = state == &ElementState::Pressed; match key { - VirtualKeyCode::Left => self.key_left = down, - VirtualKeyCode::Right => self.key_right = down, + VirtualKeyCode::Left => { + self.key_left = down; + if down { + self.key_right = false; + } + } + VirtualKeyCode::Right => { + self.key_right = down; + if down { + self.key_left = false; + } + } VirtualKeyCode::Up => self.key_thrust = down, VirtualKeyCode::Space => self.key_guns = down, + VirtualKeyCode::L => self.key_land = down, _ => {} } } @@ -58,3 +84,41 @@ impl InputStatus { } } } + +// Public get-state methods +impl InputStatus { + /// Has the player applied vertical scroll? + /// This is measured in lines, scaled by scroll_speed + /// + /// A positive value means scroll up, a negative value means scroll down. + /// This is reset to zero at the end of each frame. + pub fn get_v_scroll(&self) -> f32 { + self.v_scroll + } + + /// Is the player pressing the "turn left" key? + pub fn pressed_left(&self) -> bool { + self.key_left + } + + /// Is the player pressing the "turn right" key? + pub fn pressed_right(&self) -> bool { + self.key_right + } + + /// Is the player pressing the "fowards" key? + pub fn pressed_thrust(&self) -> bool { + self.key_thrust + } + + /// Is the player pressing the "fire guns" key? + pub fn pressed_guns(&self) -> bool { + self.key_guns + } + + /// Has the player pressed the "land" key? + /// (One-shot, reset to false at the start of each frame) + pub fn pressed_land(&self) -> bool { + self.key_land + } +} diff --git a/crates/playeragent/src/playeragent.rs b/crates/playeragent/src/playeragent.rs index 4483da1..380f578 100644 --- a/crates/playeragent/src/playeragent.rs +++ b/crates/playeragent/src/playeragent.rs @@ -1,21 +1,43 @@ -use galactica_content::Content; +use galactica_content::{Content, SystemHandle, SystemObjectHandle}; use rapier2d::geometry::ColliderHandle; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, VirtualKeyCode}; use crate::{camera::Camera, inputstatus::InputStatus, PlayerStatus}; +/// What the player has selected +#[derive(Debug, Clone, Copy)] +pub enum PlayerSelection { + /// We have nothing selected + None, + + /// We have a system body selected + OrbitingBody(SystemObjectHandle), + + /// We have a ship selected + Ship(ColliderHandle), +} + +impl PlayerSelection { + pub fn get_planet(&self) -> Option { + match self { + Self::OrbitingBody(h) => Some(*h), + _ => None, + } + } +} + pub struct PlayerAgent { /// Which ship this player is controlling pub ship: Option, + /// What the player has selected + pub selection: PlayerSelection, + /// This player's camera pub camera: Camera, /// What buttons this player is pressing pub input: InputStatus, - - /// What the player currently has selected - pub selection: Option<()>, } impl PlayerAgent { @@ -24,7 +46,10 @@ impl PlayerAgent { input: InputStatus::new(), camera: Camera::new(), ship: Some(ship), - selection: None, + selection: PlayerSelection::OrbitingBody(SystemObjectHandle { + system_handle: SystemHandle { index: 0 }, + body_index: 1, + }), } } @@ -45,10 +70,9 @@ impl PlayerAgent { } pub fn step(&mut self, ct: &Content, status: PlayerStatus) { - if self.input.v_scroll != 0.0 { - self.camera.zoom = (self.camera.zoom + self.input.v_scroll) + if self.input.get_v_scroll() != 0.0 { + self.camera.zoom = (self.camera.zoom + self.input.get_v_scroll()) .clamp(ct.get_config().zoom_min, ct.get_config().zoom_max); - self.input.v_scroll = 0.0; } if status.pos.is_some() { diff --git a/crates/render/Cargo.toml b/crates/render/Cargo.toml index 68bae95..7a824e9 100644 --- a/crates/render/Cargo.toml +++ b/crates/render/Cargo.toml @@ -21,6 +21,7 @@ galactica-content = { workspace = true } galactica-util = { workspace = true } galactica-packer = { workspace = true } galactica-system = { workspace = true } +galactica-playeragent = { workspace = true } anyhow = { workspace = true } cgmath = { workspace = true } diff --git a/crates/render/src/datastructs.rs b/crates/render/src/datastructs.rs index 4ce7b28..7526587 100644 --- a/crates/render/src/datastructs.rs +++ b/crates/render/src/datastructs.rs @@ -1,6 +1,7 @@ use cgmath::Point2; use galactica_content::{Content, SystemHandle}; -use galactica_system::phys::{ParticleBuilder, PhysSim, PhysSimShipHandle}; +use galactica_playeragent::PlayerAgent; +use galactica_system::phys::{ParticleBuilder, PhysSim}; use galactica_util::timing::Timing; use glyphon::{FontSystem, SwashCache, TextAtlas, TextRenderer}; use std::rc::Rc; @@ -15,7 +16,7 @@ pub struct RenderInput<'a> { pub camera_pos: Point2, /// Player ship data - pub player_ship: PhysSimShipHandle, + pub player: &'a PlayerAgent, /// The system we're currently in pub current_system: SystemHandle, diff --git a/crates/render/src/gpustate/phys.rs b/crates/render/src/gpustate/phys.rs index 09a49cd..9b41868 100644 --- a/crates/render/src/gpustate/phys.rs +++ b/crates/render/src/gpustate/phys.rs @@ -2,7 +2,7 @@ use bytemuck; use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2}; -use galactica_system::phys::util; +use galactica_system::{data::ShipState, phys::util}; use galactica_util::constants::OBJECT_SPRITE_INSTANCE_LIMIT; use crate::{ @@ -19,6 +19,11 @@ impl GPUState { screen_clip: (Point2, Point2), ) { for ship in state.systemsim.iter_ships() { + match ship.data.get_state() { + ShipState::Collapsing { .. } | ShipState::Flying => {} + ShipState::Landed { .. } => continue, + } + let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); let ship_pos = util::rigidbody_position(&r); let ship_rot = util::rigidbody_rotation(r); diff --git a/crates/render/src/ui/radar.rs b/crates/render/src/ui/radar.rs index 927af2d..31ca848 100644 --- a/crates/render/src/ui/radar.rs +++ b/crates/render/src/ui/radar.rs @@ -31,9 +31,21 @@ impl Radar { // TODO: maybe a cleaner solution for last posititon? // This is necessary because the player may be dead or landed - let player_ship = input.systemsim.get_ship(&input.player_ship).unwrap(); + let player_ship = input + .systemsim + .get_ship(&galactica_system::phys::PhysSimShipHandle( + input.player.ship.unwrap(), + )) + .unwrap(); match player_ship.data.get_state() { + ShipState::Landed { target } => { + let landed_body = input.ct.get_system_object(*target); + self.last_player_position = Point2 { + x: landed_body.pos.x, + y: landed_body.pos.y, + }; + } ShipState::Flying | ShipState::Collapsing { .. } => { let player_body = input .systemsim @@ -44,6 +56,7 @@ impl Radar { } }; + // TODO: don't hard-code these, add config options let planet_sprite = input.ct.get_sprite_handle("ui::planetblip"); let ship_sprite = input.ct.get_sprite_handle("ui::shipblip"); let arrow_sprite = input.ct.get_sprite_handle("ui::centerarrow"); @@ -120,6 +133,9 @@ impl Radar { // This will be None if this ship is dead. // Stays around while the physics system runs a collapse sequence let color = match s.data.get_state() { + ShipState::Landed { .. } => { + continue; + } ShipState::Collapsing { .. } => { // TODO: configurable [0.2, 0.2, 0.2, 1.0] diff --git a/crates/render/src/ui/status.rs b/crates/render/src/ui/status.rs index 4a09eb8..90f86e7 100644 --- a/crates/render/src/ui/status.rs +++ b/crates/render/src/ui/status.rs @@ -1,7 +1,6 @@ -use std::f32::consts::TAU; - use galactica_system::data::ShipState; use galactica_util::constants::{RADIALBAR_SPRITE_INSTANCE_LIMIT, UI_SPRITE_INSTANCE_LIMIT}; +use std::f32::consts::TAU; use crate::{ datastructs::RenderState, @@ -30,10 +29,15 @@ impl Status { let current_shields; let current_hull; let max_hull; - let player_ship = input.systemsim.get_ship(&input.player_ship).unwrap(); + let player_ship = input + .systemsim + .get_ship(&galactica_system::phys::PhysSimShipHandle( + input.player.ship.unwrap(), + )) + .unwrap(); match player_ship.data.get_state() { - ShipState::Collapsing { .. } | ShipState::Flying => { + ShipState::Landed { .. } | ShipState::Collapsing { .. } | ShipState::Flying => { max_shields = player_ship.data.get_outfits().get_shield_strength(); current_shields = player_ship.data.get_shields(); current_hull = player_ship.data.get_hull(); diff --git a/crates/system/src/data/ship/ship.rs b/crates/system/src/data/ship/ship.rs index f6360c7..1da3ba4 100644 --- a/crates/system/src/data/ship/ship.rs +++ b/crates/system/src/data/ship/ship.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, time::Instant}; use super::{OutfitSet, ShipPersonality}; -use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle}; +use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle}; use rand::{rngs::ThreadRng, Rng}; /// Ship state machine. @@ -21,9 +21,38 @@ pub enum ShipState { /// How many seconds of the collapse sequence we've played elapsed: f32, }, + + /// This ship is landed on a planet + Landed { + /// The planet this ship is landed on + target: SystemObjectHandle, + }, } impl ShipState { + /// Is this ship playing its collapse sequence? + pub fn is_collapsing(&self) -> bool { + match self { + Self::Collapsing { .. } => true, + _ => false, + } + } + /// Is this ship flying in open space? + pub fn is_flying(&self) -> bool { + match self { + Self::Flying => true, + _ => false, + } + } + + /// Is this ship landed on a planet? + pub fn is_landed(&self) -> bool { + match self { + Self::Landed { .. } => true, + _ => false, + } + } + /// True if this ship has been destroyed and has finished it's collapse sequence. /// Ships are deleted once this is true. pub fn should_be_removed(&self) -> bool { @@ -33,11 +62,11 @@ impl ShipState { } } - /// Is this ship playing its collapse sequence? - pub fn is_collapsing(&self) -> bool { + /// What planet is this ship landed on? + pub fn landed_on(&self) -> Option { match self { - Self::Collapsing { .. } => true, - _ => false, + Self::Landed { target } => Some(*target), + _ => None, } } @@ -52,14 +81,6 @@ impl ShipState { _ => None, } } - - /// Is this ship flying in open space? - pub fn is_flying(&self) -> bool { - match self { - Self::Flying => true, - _ => false, - } - } } /// Represents all attributes of a single ship @@ -115,6 +136,28 @@ impl ShipData { } } + /// Land this ship on `target` + pub fn land_on(&mut self, target: SystemObjectHandle) -> bool { + if self.state.is_flying() { + self.state = ShipState::Landed { target }; + } else { + return false; + } + + return true; + } + + /// Take off from `target` + pub fn unland(&mut self) -> bool { + if self.state.is_landed() { + self.state = ShipState::Flying; + } else { + return false; + } + + return true; + } + /// Add an outfit to this ship pub fn add_outfit(&mut self, o: &Outfit) -> super::OutfitAddResult { let r = self.outfits.add(o); @@ -181,6 +224,20 @@ impl ShipData { *elapsed += t; } + ShipState::Landed { .. } => { + // Cooldown guns + for (_, c) in &mut self.gun_cooldowns { + if *c > 0.0 { + *c = 0.0; + } + } + + // Regenerate shields + if self.shields != self.outfits.get_shield_strength() { + self.shields = self.outfits.get_shield_strength(); + } + } + ShipState::Flying => { // Cooldown guns for (_, c) in &mut self.gun_cooldowns { diff --git a/crates/system/src/phys/objects/ship.rs b/crates/system/src/phys/objects/ship.rs index 0e03ad8..f6e79c2 100644 --- a/crates/system/src/phys/objects/ship.rs +++ b/crates/system/src/phys/objects/ship.rs @@ -99,6 +99,7 @@ impl PhysSimShip { ShipState::Flying => { return self.step_live(res, rigid_body, collider); } + ShipState::Landed { .. } => {} } } diff --git a/crates/system/src/phys/systemsim.rs b/crates/system/src/phys/systemsim.rs index dd4f8d9..105b950 100644 --- a/crates/system/src/phys/systemsim.rs +++ b/crates/system/src/phys/systemsim.rs @@ -1,7 +1,7 @@ use cgmath::{Deg, EuclideanSpace, InnerSpace, Matrix2, Point2, Rad, Vector2, Zero}; use galactica_content::{ Content, FactionHandle, GunPoint, OutfitHandle, ProjectileCollider, Relationship, ShipHandle, - SystemHandle, + SystemHandle, SystemObjectHandle, }; use galactica_playeragent::PlayerAgent; use nalgebra::{point, vector}; @@ -13,7 +13,7 @@ use rapier2d::{ }; use std::{collections::HashMap, f32::consts::PI}; -use crate::data::ShipPersonality; +use crate::data::{ShipPersonality, ShipState}; use super::{ controller::ShipController, @@ -67,6 +67,40 @@ impl<'a> PhysSim { return Some((r, p)); } + fn land_ship(&mut self, collider: ColliderHandle, target: SystemObjectHandle) { + let ship = self.ships.get_mut(&collider).unwrap(); + self.rigid_body_set + .get_mut(ship.rigid_body) + .unwrap() + .set_enabled(false); + self.collider_set + .get_mut(ship.collider) + .unwrap() + .set_enabled(false); + + ship.data.land_on(target); + } + + fn unland_ship(&mut self, ct: &Content, collider: ColliderHandle) { + let ship = self.ships.get_mut(&collider).unwrap(); + let obj = ship.data.get_state().landed_on().unwrap(); + let obj = ct.get_system_object(obj); + ship.data.unland(); + + self.rigid_body_set + .get_mut(ship.rigid_body) + .unwrap() + .set_position(point![obj.pos.x, obj.pos.y].into(), true); + self.rigid_body_set + .get_mut(ship.rigid_body) + .unwrap() + .set_enabled(true); + self.collider_set + .get_mut(ship.collider) + .unwrap() + .set_enabled(true); + } + fn remove_ship(&mut self, res: &mut PhysStepResources, colliderhandle: ColliderHandle) { let ship = match self.ships.get(&colliderhandle) { None => return, @@ -213,16 +247,31 @@ impl PhysSim { } /// Update a player ship's controls - pub fn update_player_controls(&mut self, player: &PlayerAgent) { + pub fn update_player_controls(&mut self, ct: &Content, player: &PlayerAgent) { if player.ship.is_none() { return; } let ship_object = self.ships.get_mut(&player.ship.unwrap()); if let Some(ship_object) = ship_object { - ship_object.controls.guns = player.input.key_guns; - ship_object.controls.left = player.input.key_left; - ship_object.controls.right = player.input.key_right; - ship_object.controls.thrust = player.input.key_thrust; + ship_object.controls.guns = player.input.pressed_guns(); + ship_object.controls.left = player.input.pressed_left(); + ship_object.controls.right = player.input.pressed_right(); + ship_object.controls.thrust = player.input.pressed_thrust(); + + if player.input.pressed_land() { + match ship_object.data.get_state() { + ShipState::Flying => { + self.land_ship( + player.ship.unwrap(), + player.selection.get_planet().unwrap(), + ); + } + ShipState::Landed { .. } => { + self.unland_ship(ct, player.ship.unwrap()); + } + _ => {} + } + } } } @@ -247,48 +296,54 @@ impl PhysSim { continue; } - // This ship is playing a collapse sequence - // and needs no additional logic - if ship.data.get_state().is_collapsing() { - let ship = self.ships.get_mut(&collider).unwrap(); - ship.step( - res, - &mut self.rigid_body_set[ship.rigid_body], - &mut self.collider_set[ship.collider], - ); - continue; - } + match ship.data.get_state() { + ShipState::Landed { .. } => {} - // Compute new controls - let controls; - let b = self.ship_behaviors.get_mut(&collider).unwrap(); - controls = b.update_controls(&res, &self.rigid_body_set, &self.ships, ship.collider); + ShipState::Collapsing { .. } => { + let ship = self.ships.get_mut(&collider).unwrap(); + ship.step( + res, + &mut self.rigid_body_set[ship.rigid_body], + &mut self.collider_set[ship.collider], + ); + } - let ship = self.ships.get_mut(&collider).unwrap(); + ShipState::Flying => { + // Compute new controls + // This is why we borrow immutably first + let controls; + let b = self.ship_behaviors.get_mut(&collider).unwrap(); + controls = + b.update_controls(&res, &self.rigid_body_set, &self.ships, ship.collider); - if let Some(controls) = controls { - ship.controls = controls; - } - ship.step( - res, - &mut self.rigid_body_set[ship.rigid_body], - &mut self.collider_set[ship.collider], - ); + // Re-borrow mutably to apply changes + let ship = self.ships.get_mut(&collider).unwrap(); - // If we're firing, try to fire each gun - if ship.controls.guns { - // TODO: don't allocate here. This is a hack to satisfy the borrow checker, - // convert this to a refcell or do the replace dance. - let pairs: Vec<(GunPoint, Option)> = ship - .data - .get_outfits() - .iter_gun_points() - .map(|(p, o)| (p.clone(), o.clone())) - .collect(); + if let Some(controls) = controls { + ship.controls = controls; + } + ship.step( + res, + &mut self.rigid_body_set[ship.rigid_body], + &mut self.collider_set[ship.collider], + ); - for (gun, outfit) in pairs { - if ship.data.fire_gun(res.ct, &gun) { - projectiles.push((ship.collider, gun.clone(), outfit.unwrap())); + // If we're firing, try to fire each gun + if ship.controls.guns { + // TODO: don't allocate here. This is a hack to satisfy the borrow checker, + // convert this to a refcell or do the replace dance. + let pairs: Vec<(GunPoint, Option)> = ship + .data + .get_outfits() + .iter_gun_points() + .map(|(p, o)| (p.clone(), o.clone())) + .collect(); + + for (gun, outfit) in pairs { + if ship.data.fire_gun(res.ct, &gun) { + projectiles.push((ship.collider, gun.clone(), outfit.unwrap())); + } + } } } }