diff --git a/crates/galactica/src/main.rs b/crates/galactica/src/main.rs index 7023de1..8ae3f17 100644 --- a/crates/galactica/src/main.rs +++ b/crates/galactica/src/main.rs @@ -42,6 +42,7 @@ fn main() -> Result<()> { let mut gpu = pollster::block_on(galactica_render::GPUState::new(window, &content))?; gpu.init(&content); + // TODO: don't clone content let mut game = game::Game::new(content.clone()); let p = game.make_player(); @@ -96,6 +97,11 @@ fn main() -> Result<()> { None } } + ShipState::Landing { .. } => { + let pos = + o.data.get_state().landing_position(&content).unwrap(); + Some(Point2 { x: pos.x, y: pos.y }) + } ShipState::Landed { target } => { let b = content.get_system_object(*target); Some(Point2 { @@ -103,6 +109,8 @@ fn main() -> Result<()> { y: b.pos.y, }) } + + ShipState::Dead => None, } } else { None diff --git a/crates/render/src/gpustate/phys.rs b/crates/render/src/gpustate/phys.rs index 9b41868..e9ae430 100644 --- a/crates/render/src/gpustate/phys.rs +++ b/crates/render/src/gpustate/phys.rs @@ -1,7 +1,7 @@ //! GPUState routines for drawing items in a systemsim use bytemuck; -use cgmath::{EuclideanSpace, InnerSpace, Point2, Vector2}; +use cgmath::{EuclideanSpace, InnerSpace, Point2, Point3, Vector2}; use galactica_system::{data::ShipState, phys::util}; use galactica_util::constants::OBJECT_SPRITE_INSTANCE_LIMIT; @@ -19,28 +19,64 @@ 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, - } + // TODO: move collapse sequence here? - 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); - let ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix! - let ship_cnt = state.ct.get_ship(ship.data.get_content()); + let ship_pos; + let ship_ang; + let ship_cnt; + match ship.data.get_state() { + ShipState::Dead | ShipState::Landed { .. } => continue, + + ShipState::Collapsing { .. } | ShipState::Flying => { + let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); + let pos = util::rigidbody_position(&r); + ship_pos = Point3 { + x: pos.x, + y: pos.y, + z: 1.0, + }; + let ship_rot = util::rigidbody_rotation(r); + ship_ang = -ship_rot.angle(Vector2 { x: 0.0, y: 1.0 }); // TODO: inconsistent angles. Fix! + ship_cnt = state.ct.get_ship(ship.data.get_content()); + } + ShipState::Landing { + from_position, + from_angle, + target, + total: _total, + elapsed, + } => { + let target = state.ct.get_system_object(*target); + + let diff = Point2 { + x: target.pos.x, + y: target.pos.y, + } - from_position; + + ship_pos = ship.data.get_state().landing_position(state.ct).unwrap(); + + let target_angle = -diff.angle(Vector2 { x: 0.0, y: 1.0 }); + + ship_ang = from_angle + ((target_angle - from_angle) * 1f32.min(elapsed / 1.0)); + ship_cnt = state.ct.get_ship(ship.data.get_content()); + } + } // Position adjusted for parallax // TODO: adjust parallax for zoom? // 1.0 is z-coordinate, which is constant for ships - let pos: Point2 = (ship_pos - state.camera_pos.to_vec()) / 1.0; + let pos: Point2 = (Point2 { + x: ship_pos.x, + y: ship_pos.y, + } - state.camera_pos.to_vec()) + / ship_pos.z; // Game dimensions of this sprite post-scale. // Post-scale width or height, whichever is larger. // This is in game units. // // We take the maximum to account for rotated sprites. - let m = (ship_cnt.size / 1.0) * ship_cnt.sprite.aspect.max(1.0); + let m = (ship_cnt.size / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0); // Don't draw sprites that are off the screen if pos.x < screen_clip.0.x - m @@ -59,7 +95,7 @@ impl GPUState { bytemuck::cast_slice(&[ObjectData { xpos: ship_pos.x, ypos: ship_pos.y, - zpos: 1.0, + zpos: ship_pos.z, angle: ship_ang.0, size: ship_cnt.size, parent: 0, @@ -86,7 +122,13 @@ impl GPUState { self.state.vertex_buffers.object_counter += 1; let flare = ship.data.get_outfits().get_flare_sprite(state.ct); - if ship.get_controls().thrust && flare.is_some() && ship.data.get_state().is_flying() { + if { + let is_flying = match ship.data.get_state() { + ShipState::Flying => true, + _ => false, + }; + ship.get_controls().thrust && flare.is_some() && is_flying + } { for engine_point in &ship_cnt.engines { self.state.queue.write_buffer( &self.state.global_uniform.object_buffer, diff --git a/crates/render/src/ui/radar.rs b/crates/render/src/ui/radar.rs index 31ca848..39a4ee6 100644 --- a/crates/render/src/ui/radar.rs +++ b/crates/render/src/ui/radar.rs @@ -39,6 +39,18 @@ impl Radar { .unwrap(); match player_ship.data.get_state() { + ShipState::Dead => {} + + // TODO: radar follows while landing + ShipState::Landing { .. } => { + let pos = player_ship + .data + .get_state() + .landing_position(&input.ct) + .unwrap(); + self.last_player_position = Point2 { x: pos.x, y: pos.y } + } + ShipState::Landed { target } => { let landed_body = input.ct.get_system_object(*target); self.last_player_position = Point2 { @@ -133,10 +145,12 @@ 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 { .. } => { + ShipState::Dead | ShipState::Landed { .. } => { continue; } - ShipState::Collapsing { .. } => { + + // TODO: different color for landing? + ShipState::Landing { .. } | 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 90f86e7..da47569 100644 --- a/crates/render/src/ui/status.rs +++ b/crates/render/src/ui/status.rs @@ -37,10 +37,19 @@ impl Status { .unwrap(); match player_ship.data.get_state() { - ShipState::Landed { .. } | ShipState::Collapsing { .. } | ShipState::Flying => { + ShipState::Dead => { + current_shields = 0.0; + current_hull = 0.0; max_shields = player_ship.data.get_outfits().get_shield_strength(); + max_hull = input.ct.get_ship(player_ship.data.get_content()).hull; + } + ShipState::Landing { .. } + | ShipState::Landed { .. } + | ShipState::Collapsing { .. } + | ShipState::Flying => { current_shields = player_ship.data.get_shields(); current_hull = player_ship.data.get_hull(); + max_shields = player_ship.data.get_outfits().get_shield_strength(); max_hull = input.ct.get_ship(player_ship.data.get_content()).hull; } } diff --git a/crates/system/src/data/ship/ship.rs b/crates/system/src/data/ship/ship.rs index 1da3ba4..8ebcc37 100644 --- a/crates/system/src/data/ship/ship.rs +++ b/crates/system/src/data/ship/ship.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, time::Instant}; use super::{OutfitSet, ShipPersonality}; +use cgmath::{InnerSpace, Point2, Point3, Rad}; use galactica_content::{Content, FactionHandle, GunPoint, Outfit, ShipHandle, SystemObjectHandle}; use rand::{rngs::ThreadRng, Rng}; @@ -10,6 +11,9 @@ use rand::{rngs::ThreadRng, Rng}; /// sequence fully plays out. #[derive(Debug, Clone)] pub enum ShipState { + /// This ship is dead, and should be removed from the game. + Dead, + /// This ship is alive and well in open space Flying, // TODO: system, position (also in collapse)? @@ -27,41 +31,28 @@ pub enum ShipState { /// The planet this ship is landed on target: SystemObjectHandle, }, + + /// This ship is landing on a planet + /// (playing the animation) + Landing { + /// The point, in world coordinates, where we started + from_position: Point2, + + /// The ship's angle when we started landing + from_angle: Rad, + + /// The planet we're landing on + target: SystemObjectHandle, + + /// The total amount of time, in seconds, we will spend landing + total: f32, + + /// The amount of time we've already spent playing the landing sequence + elapsed: f32, + }, } 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 { - match self { - Self::Collapsing { elapsed, total } => elapsed >= total, - _ => false, - } - } - /// What planet is this ship landed on? pub fn landed_on(&self) -> Option { match self { @@ -81,6 +72,42 @@ impl ShipState { _ => None, } } + + /// Compute position of this ship's sprite during its landing sequence + pub fn landing_position(&self, ct: &Content) -> Option> { + match self { + Self::Landing { + from_position, + target, + total, + elapsed, + .. + } => Some({ + let target = ct.get_system_object(*target); + + let diff = Point2 { + x: target.pos.x, + y: target.pos.y, + } - from_position; + let diff = diff - diff.normalize() * (target.size / 2.0) * 0.8; + + // TODO: improve animation + // TODO: fade + // TODO: atmosphere burn + // TODO: land at random point + // TODO: don't jump camera + + let pos = from_position + (diff * (elapsed / total)); + + Point3 { + x: pos.x, + y: pos.y, + z: 1.0 + ((target.pos.z - 1.0) * (elapsed / total)), + } + }), + _ => None, + } + } } /// Represents all attributes of a single ship @@ -136,26 +163,41 @@ impl ShipData { } } + // TODO: position in data? /// 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; + pub fn land_on( + &mut self, + target: SystemObjectHandle, + from_position: Point2, + from_angle: Rad, + ) -> bool { + match self.state { + ShipState::Flying => { + self.state = ShipState::Landing { + elapsed: 0.0, + total: 5.0, + target, + from_position, + from_angle, + }; + return true; + } + _ => { + unreachable!("Called `land_on` on a ship that isn't flying!") + } + }; } /// Take off from `target` - pub fn unland(&mut self) -> bool { - if self.state.is_landed() { - self.state = ShipState::Flying; - } else { - return false; - } - - return true; + pub fn unland(&mut self) { + match self.state { + ShipState::Landed { .. } => { + self.state = ShipState::Flying; + } + _ => { + unreachable!("Called `unland` on a ship that isn't landed!") + } + }; } /// Add an outfit to this ship @@ -194,9 +236,13 @@ impl ShipData { /// Hit this ship with the given amount of damage pub(crate) fn apply_damage(&mut self, ct: &Content, mut d: f32) { - if self.state.is_collapsing() { - return; + match self.state { + ShipState::Flying => {} + _ => { + unreachable!("Cannot apply damage to a ship that is not flying!") + } } + if self.shields >= d { self.shields -= d } else { @@ -218,10 +264,16 @@ impl ShipData { /// Update this ship's state by `t` seconds pub(crate) fn step(&mut self, t: f32) { match self.state { - ShipState::Collapsing { - ref mut elapsed, .. + ShipState::Landing { + ref mut elapsed, + total, + target, + .. } => { *elapsed += t; + if *elapsed >= total { + self.state = ShipState::Landed { target }; + } } ShipState::Landed { .. } => { @@ -260,6 +312,18 @@ impl ShipData { } } } + + ShipState::Collapsing { + ref mut elapsed, + total, + } => { + *elapsed += t; + if *elapsed >= total { + self.state = ShipState::Dead + } + } + + ShipState::Dead => {} } } } diff --git a/crates/system/src/phys/controller/point.rs b/crates/system/src/phys/controller/point.rs index 53df5db..fda5161 100644 --- a/crates/system/src/phys/controller/point.rs +++ b/crates/system/src/phys/controller/point.rs @@ -3,6 +3,8 @@ use galactica_content::Relationship; use rapier2d::{dynamics::RigidBodySet, geometry::ColliderHandle}; use std::collections::HashMap; +use crate::data::ShipState; + use super::{ super::{ objects::{PhysSimShip, ShipControls}, @@ -46,7 +48,10 @@ impl ShipControllerStruct for PointShipController { .filter( // Target only flying ships we're hostile towards |s| match my_faction.relationships.get(&s.data.get_faction()).unwrap() { - Relationship::Hostile => s.data.get_state().is_flying(), + Relationship::Hostile => match s.data.get_state() { + ShipState::Flying => true, + _ => false, + }, _ => false, }, ) diff --git a/crates/system/src/phys/objects/ship.rs b/crates/system/src/phys/objects/ship.rs index f6e79c2..bd06e04 100644 --- a/crates/system/src/phys/objects/ship.rs +++ b/crates/system/src/phys/objects/ship.rs @@ -99,7 +99,7 @@ impl PhysSimShip { ShipState::Flying => { return self.step_live(res, rigid_body, collider); } - ShipState::Landed { .. } => {} + ShipState::Dead | ShipState::Landing { .. } | ShipState::Landed { .. } => {} } } diff --git a/crates/system/src/phys/systemsim.rs b/crates/system/src/phys/systemsim.rs index 105b950..6b50b99 100644 --- a/crates/system/src/phys/systemsim.rs +++ b/crates/system/src/phys/systemsim.rs @@ -69,6 +69,14 @@ impl<'a> PhysSim { fn land_ship(&mut self, collider: ColliderHandle, target: SystemObjectHandle) { let ship = self.ships.get_mut(&collider).unwrap(); + + let r = self.rigid_body_set.get(ship.rigid_body).unwrap(); + ship.data.land_on( + target, + util::rigidbody_position(r), + -util::rigidbody_rotation(r).angle(Vector2 { x: 0.0, y: 1.0 }), + ); + self.rigid_body_set .get_mut(ship.rigid_body) .unwrap() @@ -77,8 +85,6 @@ impl<'a> PhysSim { .get_mut(ship.collider) .unwrap() .set_enabled(false); - - ship.data.land_on(target); } fn unland_ship(&mut self, ct: &Content, collider: ColliderHandle) { @@ -136,10 +142,14 @@ impl<'a> PhysSim { let f = res.ct.get_faction(projectile.faction); let r = f.relationships.get(&ship.data.get_faction()).unwrap(); let destory_projectile = match r { - Relationship::Hostile => { - ship.data.apply_damage(res.ct, projectile.content.damage); - true - } + Relationship::Hostile => match ship.data.get_state() { + ShipState::Flying => { + ship.data.apply_damage(res.ct, projectile.content.damage); + true + } + ShipState::Collapsing { .. } => true, + _ => false, + }, _ => false, }; @@ -291,15 +301,14 @@ impl PhysSim { // Borrow immutably for now... // (required to compute controls) let ship = self.ships.get(&collider).unwrap(); - if ship.data.get_state().should_be_removed() { - to_remove.push(collider); - continue; - } - match ship.data.get_state() { - ShipState::Landed { .. } => {} + ShipState::Dead => { + to_remove.push(collider); + } - ShipState::Collapsing { .. } => { + ShipState::Landing { .. } + | ShipState::Landed { .. } + | ShipState::Collapsing { .. } => { let ship = self.ships.get_mut(&collider).unwrap(); ship.step( res,