From 088d1f7fb97fb4e883679992472f25abf5ab4392 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 20 Jan 2024 12:42:20 -0800 Subject: [PATCH] Added engine ease in/out --- content/outfits.toml | 4 +- crates/content/src/animautomaton.rs | 135 +++++++++++---------- crates/content/src/handle.rs | 18 +-- crates/content/src/lib.rs | 6 + crates/content/src/part/outfit.rs | 64 ++++++++-- crates/content/src/part/sprite.rs | 93 +++++++++----- crates/render/src/gpustate/phys.rs | 39 +++--- crates/render/src/ui/planet.rs | 8 +- crates/render/src/ui/radar.rs | 2 +- crates/render/src/ui/util/sprite.rs | 5 +- crates/render/src/ui/util/textarea.rs | 15 +-- crates/system/src/phys/objects/collapse.rs | 11 +- crates/system/src/phys/objects/ship.rs | 63 +++++++--- 13 files changed, 288 insertions(+), 175 deletions(-) diff --git a/content/outfits.toml b/content/outfits.toml index d926f5c..ebc5985 100644 --- a/content/outfits.toml +++ b/content/outfits.toml @@ -3,7 +3,9 @@ space.engine = 20 engine.thrust = 100 -engine.flare_sprite = "flare::ion" +engine.flare.sprite = "flare::ion" +engine.flare.on_start = "rise:top" +engine.flare.on_stop = "rise:bot" steering.power = 20 diff --git a/crates/content/src/animautomaton.rs b/crates/content/src/animautomaton.rs index 23dfebf..23b2947 100644 --- a/crates/content/src/animautomaton.rs +++ b/crates/content/src/animautomaton.rs @@ -66,6 +66,9 @@ pub struct AnimAutomaton { /// The texture we're fading to /// (if we're moving downwards) next_texture: u32, + + /// If this is some, take this edge next + next_edge_override: Option, } impl AnimAutomaton { @@ -90,6 +93,7 @@ impl AnimAutomaton { sprite: sprite.handle, current_frame: 0, current_fade: 0.0, + next_edge_override: None, current_direction, current_section, @@ -98,21 +102,56 @@ impl AnimAutomaton { } } - /// Reset this animation - pub fn reset(&mut self, ct: &Content) { - *self = Self::new(ct, self.sprite); + /// Override the next section transition + pub fn next_edge(&mut self, s: SectionEdge) { + self.next_edge_override = Some(s); } - /// Reverse this animation's direction - pub fn reverse(&mut self) { - match self.current_direction { - AnimDirection::Stop => {} - AnimDirection::Up => { + /// Force a transition to the given section right now + pub fn jump_to(&mut self, ct: &Content, start: StartEdge) { + self.take_edge(ct, start.into()); + } + + fn take_edge(&mut self, ct: &Content, e: SectionEdge) { + let sprite = ct.get_sprite(self.sprite); + let current_section = sprite.get_section(self.current_section); + + match e { + SectionEdge::Stop => { + self.current_fade = 0.0; + self.current_frame = current_section.frames.len() - 1; + self.current_direction = AnimDirection::Stop; + } + SectionEdge::Top { section } => { + self.current_section = section; + self.current_frame = 0; self.current_direction = AnimDirection::Down; } - AnimDirection::Down => { + SectionEdge::Bot { section } => { + let s = sprite.get_section(section); + self.current_section = section; + self.current_frame = s.frames.len() - 1; self.current_direction = AnimDirection::Up; } + SectionEdge::Restart => { + self.current_frame = 0; + } + SectionEdge::Reverse => { + // Jump to SECOND frame, since we've already shown the + // first during the fade transition + + match self.current_direction { + AnimDirection::Stop => {} + AnimDirection::Up => { + self.current_frame = 0; + self.current_direction = AnimDirection::Down; + } + AnimDirection::Down => { + self.current_frame = current_section.frames.len() - 1; + self.current_direction = AnimDirection::Up; + } + } + } } } @@ -129,15 +168,21 @@ impl AnimAutomaton { // and we switch to the next frame when it hits 1.0. If we are stepping foward, it increases, // and if we are stepping backwards, it decreases. - // If this is zero, this section isn't animated. - if current_section.frame_duration == 0.0 { - return; - } + // This should always be positive, and this fact is enforced by the content loader. + // if we get here, something is very wrong. + assert!(current_section.frame_duration > 0.0); match self.current_direction { AnimDirection::Down => self.current_fade += t / current_section.frame_duration, AnimDirection::Up => self.current_fade -= t / current_section.frame_duration, - AnimDirection::Stop => {} + AnimDirection::Stop => { + // Edge case: we're stopped and got a request to transition. + // we should transition right away. + + if let Some(e) = self.next_edge_override { + self.take_edge(ct, e); + } + } } // We're stepping foward and finished this frame @@ -150,32 +195,14 @@ impl AnimAutomaton { if self.current_frame < current_section.frames.len() - 1 { self.current_frame += 1; } else { - match current_section.edge_bot { - SectionEdge::Stop => { - self.current_fade = 0.0; - self.current_frame = current_section.frames.len() - 1; - self.current_direction = AnimDirection::Stop; + let e = { + if self.next_edge_override.is_some() { + self.next_edge_override.take().unwrap() + } else { + current_section.edge_bot.clone() } - SectionEdge::Top { section } => { - self.current_section = section; - self.current_frame = 0; - } - SectionEdge::Bot { section } => { - let s = sprite.get_section(section); - self.current_section = section; - self.current_frame = s.frames.len() - 1; - self.reverse(); - } - SectionEdge::Restart => { - self.current_frame = 0; - } - SectionEdge::Reverse => { - // Jump to SECOND frame, since we've already shown the - // first during the fade transition - self.current_frame = current_section.frames.len() - 1; - self.reverse() - } - } + }; + self.take_edge(ct, e); } let current_section = sprite.get_section(self.current_section); @@ -193,30 +220,14 @@ impl AnimAutomaton { if self.current_frame > 0 { self.current_frame -= 1; } else { - match current_section.edge_top { - SectionEdge::Stop => { - self.current_fade = 0.0; - self.current_frame = 0; - self.current_direction = AnimDirection::Stop; + let e = { + if self.next_edge_override.is_some() { + self.next_edge_override.take().unwrap() + } else { + current_section.edge_top.clone() } - SectionEdge::Top { section } => { - self.current_section = section; - self.current_frame = 0; - self.reverse(); - } - SectionEdge::Bot { section } => { - let s = sprite.get_section(section); - self.current_section = section; - self.current_frame = s.frames.len() - 1; - } - SectionEdge::Reverse => { - self.current_frame = 0; - self.reverse(); - } - SectionEdge::Restart => { - self.current_frame = current_section.frames.len() - 1; - } - } + }; + self.take_edge(ct, e); } let current_section = sprite.get_section(self.current_section); diff --git a/crates/content/src/handle.rs b/crates/content/src/handle.rs index 3447527..3e40b89 100644 --- a/crates/content/src/handle.rs +++ b/crates/content/src/handle.rs @@ -8,25 +8,9 @@ use std::{cmp::Eq, hash::Hash}; /// A lightweight representation of a sprite -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct SpriteHandle { pub(crate) index: usize, - - /// The aspect ratio of this sprite (width / height) - pub aspect: f32, -} - -impl Hash for SpriteHandle { - fn hash(&self, state: &mut H) { - self.index.hash(state) - } -} - -impl Eq for SpriteHandle {} -impl PartialEq for SpriteHandle { - fn eq(&self, other: &Self) -> bool { - self.index.eq(&other.index) - } } /// A lightweight representation of system body diff --git a/crates/content/src/lib.rs b/crates/content/src/lib.rs index 7319fd1..f08e416 100644 --- a/crates/content/src/lib.rs +++ b/crates/content/src/lib.rs @@ -13,6 +13,7 @@ use galactica_packer::{SpriteAtlas, SpriteAtlasImage}; use std::{ collections::HashMap, fs::File, + hash::Hash, io::Read, path::{Path, PathBuf}, }; @@ -122,13 +123,18 @@ trait Build { /// Stores temporary data while building context objects #[derive(Debug)] pub(crate) struct ContentBuildContext { + /// Map effect names to handles pub effect_index: HashMap, + + /// Maps sprite handles to a map of section name -> section index + pub sprite_section_index: HashMap>, } impl ContentBuildContext { fn new() -> Self { Self { effect_index: HashMap::new(), + sprite_section_index: HashMap::new(), } } } diff --git a/crates/content/src/part/outfit.rs b/crates/content/src/part/outfit.rs index 8bd4ef6..72c0b5f 100644 --- a/crates/content/src/part/outfit.rs +++ b/crates/content/src/part/outfit.rs @@ -1,13 +1,14 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, Context, Result}; use serde::Deserialize; use std::collections::HashMap; use crate::{ handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace, + StartEdge, }; pub(crate) mod syntax { - use crate::{effect, part::outfitspace, ContentBuildContext}; + use crate::{effect, part::outfitspace, sprite::syntax::SectionEdge, ContentBuildContext}; use anyhow::{bail, Result}; use galactica_util::to_radians; use serde::Deserialize; @@ -34,7 +35,14 @@ pub(crate) mod syntax { #[derive(Debug, Deserialize)] pub struct Engine { pub thrust: f32, - pub flare_sprite: String, + pub flare: EngineFlare, + } + + #[derive(Debug, Deserialize)] + pub struct EngineFlare { + pub sprite: String, + pub on_start: Option, + pub on_stop: Option, } #[derive(Debug, Deserialize)] @@ -139,6 +147,12 @@ pub struct Outfit { /// engine points. pub engine_flare_sprite: Option, + /// Jump to this edge when engines turn on + pub engine_flare_on_start: Option, + + /// Jump to this edge when engines turn off + pub engine_flare_on_stop: Option, + /// Shield hit points pub shield_strength: f32, @@ -253,6 +267,8 @@ impl crate::Build for Outfit { engine_thrust: 0.0, steer_power: 0.0, engine_flare_sprite: None, + engine_flare_on_start: None, + engine_flare_on_stop: None, space: OutfitSpace::from(outfit.space), shield_delay: 0.0, shield_generation: 0.0, @@ -261,16 +277,44 @@ impl crate::Build for Outfit { // Engine stats if let Some(engine) = outfit.engine { - let th = match content.sprite_index.get(&engine.flare_sprite) { - None => bail!( - "In outfit `{}`: flare sprite `{}` doesn't exist", - outfit_name, - engine.flare_sprite - ), + let sprite_handle = match content.sprite_index.get(&engine.flare.sprite) { + None => { + return Err(anyhow!( + "flare sprite `{}` doesn't exist", + engine.flare.sprite + )) + .with_context(|| format!("in outfit `{}`", outfit_name)); + } Some(t) => *t, }; o.engine_thrust = engine.thrust; - o.engine_flare_sprite = Some(th); + o.engine_flare_sprite = Some(sprite_handle); + + o.engine_flare_on_start = { + let x = engine.flare.on_start; + if x.is_none() { + None + } else { + let x = x.unwrap(); + Some( + x.resolve_as_start(sprite_handle, build_context) + .with_context(|| format!("in outfit `{}`", outfit_name))?, + ) + } + }; + + o.engine_flare_on_stop = { + let x = engine.flare.on_stop; + if x.is_none() { + None + } else { + let x = x.unwrap(); + Some( + x.resolve_as_start(sprite_handle, build_context) + .with_context(|| format!("in outfit `{}`", outfit_name))?, + ) + } + }; } // Steering stats diff --git a/crates/content/src/part/sprite.rs b/crates/content/src/part/sprite.rs index f89596d..f5e85d1 100644 --- a/crates/content/src/part/sprite.rs +++ b/crates/content/src/part/sprite.rs @@ -4,13 +4,11 @@ use std::collections::HashMap; use crate::{handle::SpriteHandle, Content, ContentBuildContext}; pub(crate) mod syntax { - use crate::{Content, ContentBuildContext}; + use crate::{Content, ContentBuildContext, SpriteHandle}; use anyhow::{anyhow, bail, Context, Ok, Result}; use serde::Deserialize; use std::{collections::HashMap, path::PathBuf}; - use super::AnimSectionHandle; - // Raw serde syntax structs. // These are never seen by code outside this crate. @@ -66,12 +64,9 @@ pub(crate) mod syntax { impl SpriteSection { pub fn add_to( &self, - _build_context: &mut ContentBuildContext, + this_sprite: SpriteHandle, + build_context: &mut ContentBuildContext, content: &mut Content, - - // An index of all sections in this sprite, used to resolve - // top and bot edges. - all_sections: &HashMap, ) -> Result<((u32, u32), super::SpriteSection)> { // Make sure all frames have the same size and add them // to the frame vector @@ -109,12 +104,12 @@ pub(crate) mod syntax { } let edge_top = match &self.top { - Some(x) => x.resolve_as_edge(all_sections)?, + Some(x) => x.resolve_as_edge(this_sprite, build_context)?, None => super::SectionEdge::Stop, }; let edge_bot = match &self.bot { - Some(x) => x.resolve_as_edge(all_sections)?, + Some(x) => x.resolve_as_edge(this_sprite, build_context)?, None => super::SectionEdge::Stop, }; @@ -140,10 +135,11 @@ pub(crate) mod syntax { impl SectionEdge { pub fn resolve_as_start( &self, - all_sections: &HashMap, + sprite: SpriteHandle, + build_context: &ContentBuildContext, ) -> Result { let e = self - .resolve_as_edge(all_sections) + .resolve_as_edge(sprite, build_context) .with_context(|| format!("while resolving start edge"))?; match e { super::SectionEdge::Bot { section } => Ok(super::StartEdge::Bot { section }), @@ -156,8 +152,11 @@ pub(crate) mod syntax { pub fn resolve_as_edge( &self, - all_sections: &HashMap, + sprite: SpriteHandle, + build_context: &ContentBuildContext, ) -> Result { + let all_sections = build_context.sprite_section_index.get(&sprite).unwrap(); + if self.val == "stop" { return Ok(super::SectionEdge::Stop); } @@ -203,7 +202,7 @@ pub(crate) mod syntax { pub struct AnimSectionHandle(pub(crate) usize); /// An edge between two animation sections -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum SectionEdge { /// Stop at the last frame of this section Stop, @@ -228,7 +227,7 @@ pub enum SectionEdge { } /// Where to start an animation -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum StartEdge { /// Play the given section from the bottm Bot { @@ -243,6 +242,15 @@ pub enum StartEdge { }, } +impl Into for StartEdge { + fn into(self) -> SectionEdge { + match self { + Self::Bot { section } => SectionEdge::Bot { section }, + Self::Top { section } => SectionEdge::Top { section }, + } + } +} + /// Represents a sprite that may be used in the game. #[derive(Debug, Clone)] pub struct Sprite { @@ -276,6 +284,14 @@ impl Sprite { } } + /// Get this sprite's starting section + pub fn get_start_section(&self) -> AnimSectionHandle { + match self.start_at { + StartEdge::Bot { section } => section, + StartEdge::Top { section } => section, + } + } + /// Iterate this sprite's sections pub fn iter_sections(&self) -> impl Iterator { self.sections.iter() @@ -330,10 +346,12 @@ impl crate::Build for Sprite { let h = SpriteHandle { index: content.sprites.len(), - aspect, }; content.sprite_index.insert(sprite_name.clone(), h); + let mut smap = HashMap::new(); + smap.insert("anim".to_string(), AnimSectionHandle(0)); + build_context.sprite_section_index.insert(h, smap); content.sprites.push(Self { name: sprite_name, @@ -357,26 +375,33 @@ impl crate::Build for Sprite { // Name the one section in this sprite "anim" section_names.insert("anim".to_owned(), AnimSectionHandle(0)); + let sprite_handle = SpriteHandle { + index: content.sprites.len(), + }; + content + .sprite_index + .insert(sprite_name.clone(), sprite_handle); + let mut smap = HashMap::new(); + smap.insert("anim".to_string(), AnimSectionHandle(0)); + build_context + .sprite_section_index + .insert(sprite_handle, smap); + let (dim, section) = s - .add_to(build_context, content, §ion_names) + .add_to(sprite_handle, build_context, content) .with_context(|| format!("while parsing sprite `{}`", sprite_name))?; let aspect = dim.0 as f32 / dim.1 as f32; - let h = SpriteHandle { - index: content.sprites.len(), - aspect, - }; let mut sections = Vec::new(); sections.push(section); - content.sprite_index.insert(sprite_name.clone(), h); content.sprites.push(Self { name: sprite_name, sections, start_at: StartEdge::Bot { section: AnimSectionHandle(0), }, - handle: h, + handle: sprite_handle, aspect, }); } @@ -388,9 +413,19 @@ impl crate::Build for Sprite { idx += 1; } + let sprite_handle = SpriteHandle { + index: content.sprites.len(), + }; + content + .sprite_index + .insert(sprite_name.clone(), sprite_handle); + build_context + .sprite_section_index + .insert(sprite_handle, section_names.clone()); + let start_at = s .start_at - .resolve_as_start(§ion_names) + .resolve_as_start(sprite_handle, build_context) .with_context(|| format!("while loading sprite `{}`", sprite_name))?; let mut sections = Vec::with_capacity(idx); @@ -403,7 +438,7 @@ impl crate::Build for Sprite { for (k, _) in names { let v = s.section.get(k).unwrap(); let (d, s) = v - .add_to(build_context, content, §ion_names) + .add_to(sprite_handle, build_context, content) .with_context(|| format!("while parsing sprite `{}`", sprite_name)) .with_context(|| format!("while parsing section `{}`", k))?; @@ -423,17 +458,11 @@ impl crate::Build for Sprite { let dim = dim.unwrap(); let aspect = dim.0 as f32 / dim.1 as f32; - let h = SpriteHandle { - index: content.sprites.len(), - aspect, - }; - - content.sprite_index.insert(sprite_name.clone(), h); content.sprites.push(Self { name: sprite_name, sections, start_at, - handle: h, + handle: sprite_handle, aspect, }); } diff --git a/crates/render/src/gpustate/phys.rs b/crates/render/src/gpustate/phys.rs index 7d5479e..fdc320b 100644 --- a/crates/render/src/gpustate/phys.rs +++ b/crates/render/src/gpustate/phys.rs @@ -12,11 +12,11 @@ use crate::{ impl GPUState { pub(super) fn phys_push_ship( &mut self, - state: &RenderInput, + input: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), ) { - for ship in state.systemsim.iter_ships() { + for ship in input.systemsim.iter_ships() { // TODO: move collapse sequence here? let ship_pos; @@ -26,21 +26,21 @@ impl GPUState { ShipState::Dead | ShipState::Landed { .. } => continue, ShipState::Collapsing { .. } | ShipState::Flying { .. } => { - let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); + let r = input.systemsim.get_rigid_body(ship.rigid_body).unwrap(); let pos = *r.translation(); ship_pos = Point3::new(pos.x, pos.y, 1.0); let ship_rot = r.rotation(); ship_ang = ship_rot.angle(); - ship_cnt = state.ct.get_ship(ship.get_data().get_content()); + ship_cnt = input.ct.get_ship(ship.get_data().get_content()); } ShipState::UnLanding { current_z, .. } | ShipState::Landing { current_z, .. } => { - let r = state.systemsim.get_rigid_body(ship.rigid_body).unwrap(); + let r = input.systemsim.get_rigid_body(ship.rigid_body).unwrap(); let pos = *r.translation(); ship_pos = Point3::new(pos.x, pos.y, *current_z); let ship_rot = r.rotation(); ship_ang = ship_rot.angle(); - ship_cnt = state.ct.get_ship(ship.get_data().get_content()); + ship_cnt = input.ct.get_ship(ship.get_data().get_content()); } } @@ -48,14 +48,15 @@ impl GPUState { // TODO: adjust parallax for zoom? // 1.0 is z-coordinate, which is constant for ships let pos: Point2 = - (Point2::new(ship_pos.x, ship_pos.y) - state.camera_pos) / ship_pos.z; + (Point2::new(ship_pos.x, ship_pos.y) - input.camera_pos) / 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 / ship_pos.z) * ship_cnt.sprite.aspect.max(1.0); + let m = + (ship_cnt.size / ship_pos.z) * input.ct.get_sprite(ship_cnt.sprite).aspect.max(1.0); // Don't draw sprites that are off the screen if pos.x < screen_clip.0.x - m @@ -98,7 +99,7 @@ impl GPUState { | ShipState::Landing { .. } => true, _ => false, }; - ship.get_controls().thrust && is_flying + is_flying } { for (engine_point, anim) in ship.iter_engine_anim() { self.state.queue.write_buffer( @@ -136,12 +137,12 @@ impl GPUState { pub(super) fn phys_push_projectile( &mut self, - state: &RenderInput, + input: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), ) { - for p in state.systemsim.iter_projectiles() { - let r = state.systemsim.get_rigid_body(p.rigid_body).unwrap(); + for p in input.systemsim.iter_projectiles() { + let r = input.systemsim.get_rigid_body(p.rigid_body).unwrap(); let proj_pos = *r.translation(); let proj_rot = r.rotation(); let proj_ang = proj_rot.angle(); @@ -150,14 +151,14 @@ impl GPUState { // Position adjusted for parallax // TODO: adjust parallax for zoom? // 1.0 is z-coordinate, which is constant for projectiles - let pos = (proj_pos - state.camera_pos) / 1.0; + let pos = (proj_pos - input.camera_pos) / 1.0; // 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 = (proj_cnt.size / 1.0) * proj_cnt.sprite.aspect.max(1.0); + let m = (proj_cnt.size / 1.0) * input.ct.get_sprite(proj_cnt.sprite).aspect.max(1.0); // Don't draw sprites that are off the screen if pos.x < screen_clip.0.x - m @@ -196,22 +197,22 @@ impl GPUState { pub(super) fn phys_push_system( &mut self, - state: &RenderInput, + input: &RenderInput, // NE and SW corners of screen screen_clip: (Point2, Point2), ) { - let system = state.ct.get_system(state.current_system); + let system = input.ct.get_system(input.current_system); for o in &system.objects { // Position adjusted for parallax - let pos: Point2 = (Point2::new(o.pos.x, o.pos.y) - state.camera_pos) / o.pos.z; + let pos: Point2 = (Point2::new(o.pos.x, o.pos.y) - input.camera_pos) / o.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 = (o.size / o.pos.z) * o.sprite.aspect.max(1.0); + let m = (o.size / o.pos.z) * input.ct.get_sprite(o.sprite).aspect.max(1.0); // Don't draw sprites that are off the screen if pos.x < screen_clip.0.x - m @@ -239,7 +240,7 @@ impl GPUState { }]), ); - let sprite = state.ct.get_sprite(o.sprite); + let sprite = input.ct.get_sprite(o.sprite); let texture_a = sprite.get_first_frame(); // ANIMATE // Push this object's instance diff --git a/crates/render/src/ui/planet.rs b/crates/render/src/ui/planet.rs index ab3777e..72330ba 100644 --- a/crates/render/src/ui/planet.rs +++ b/crates/render/src/ui/planet.rs @@ -53,6 +53,7 @@ impl Planet { current_object: None, planet_desc: UiTextArea::new( + ct, state, ct.get_sprite_handle("ui::planet"), Point2::new(0.0, 0.0), @@ -67,6 +68,7 @@ impl Planet { ), planet_name: UiTextArea::new( + ct, state, ct.get_sprite_handle("ui::planet"), Point2::new(0.0, 0.0), @@ -135,10 +137,10 @@ impl Planet { self.sprite.push_to_buffer(input, state); } - pub fn get_textarea(&self, _input: &RenderInput, state: &RenderState) -> [TextArea; 2] { + pub fn get_textarea(&self, input: &RenderInput, state: &RenderState) -> [TextArea; 2] { [ - self.planet_desc.get_textarea(state), - self.planet_name.get_textarea(state), + self.planet_desc.get_textarea(input, state), + self.planet_name.get_textarea(input, state), ] } } diff --git a/crates/render/src/ui/radar.rs b/crates/render/src/ui/radar.rs index 6b398c1..3899ca1 100644 --- a/crates/render/src/ui/radar.rs +++ b/crates/render/src/ui/radar.rs @@ -133,7 +133,7 @@ impl Radar { ([c[0], c[1], c[2], 1.0], 1.0) } }; - let size = (ship.size * ship.sprite.aspect) * ship_scale * z_scale; + let size = (ship.size * input.ct.get_sprite(ship.sprite).aspect) * ship_scale * z_scale; let p: Point2 = { if s.collider == input.player.ship.unwrap() { self.last_player_position diff --git a/crates/render/src/ui/util/sprite.rs b/crates/render/src/ui/util/sprite.rs index 88fdfc1..cc2784e 100644 --- a/crates/render/src/ui/util/sprite.rs +++ b/crates/render/src/ui/util/sprite.rs @@ -38,7 +38,10 @@ impl UiSprite { /// Add this image to the gpu sprite buffer pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { let pos = Point2::new(self.rect.pos.x, self.rect.pos.y); - let dim = Vector2::new(self.rect.dim.y * self.sprite.aspect, self.rect.dim.y); + let dim = Vector2::new( + self.rect.dim.y * input.ct.get_sprite(self.sprite).aspect, + self.rect.dim.y, + ); for c in &self.children_under { c.push_to_buffer_child(input, state, pos, dim); diff --git a/crates/render/src/ui/util/textarea.rs b/crates/render/src/ui/util/textarea.rs index 69452cc..c55b97d 100644 --- a/crates/render/src/ui/util/textarea.rs +++ b/crates/render/src/ui/util/textarea.rs @@ -1,9 +1,9 @@ -use galactica_content::SpriteHandle; +use galactica_content::{Content, SpriteHandle}; use glyphon::{cosmic_text::Align, Attrs, Buffer, Color, Metrics, Shaping, TextArea, TextBounds}; use nalgebra::{Point2, Vector2}; use super::SpriteRect; -use crate::RenderState; +use crate::{RenderInput, RenderState}; /// Represents a text area inside a sprite. pub(crate) struct UiTextArea { @@ -32,6 +32,7 @@ pub(crate) struct UiTextArea { impl UiTextArea { pub fn new( + ct: &Content, state: &mut RenderState, sprite: SpriteHandle, sprite_position: Point2, @@ -50,16 +51,16 @@ impl UiTextArea { align, color, }; - s.set_size(state, sprite_size); + s.set_size(ct, state, sprite_size); return s; } - pub fn set_size(&mut self, state: &mut RenderState, sprite_size: f32) { + pub fn set_size(&mut self, ct: &Content, state: &mut RenderState, sprite_size: f32) { self.sprite_size = sprite_size; self.buffer.set_size( &mut state.text_font_system, (self.rect.dim.x * self.sprite_size) * state.window.scale_factor() as f32, - (self.rect.dim.y * self.sprite_size * self.sprite.aspect) + (self.rect.dim.y * self.sprite_size * ct.get_sprite(self.sprite).aspect) * state.window.scale_factor() as f32, ); } @@ -74,9 +75,9 @@ impl UiTextArea { self.buffer.shape_until_scroll(&mut state.text_font_system); } - pub fn get_textarea(&self, state: &RenderState) -> TextArea { + pub fn get_textarea(&self, input: &RenderInput, state: &RenderState) -> TextArea { let h = self.sprite_size; - let w = self.sprite.aspect * h; + let w = input.ct.get_sprite(self.sprite).aspect * h; // Glypon works with physical pixels, so we must convert let fac = state.window.scale_factor() as f32; diff --git a/crates/system/src/phys/objects/collapse.rs b/crates/system/src/phys/objects/collapse.rs index 5ff9750..e253ea7 100644 --- a/crates/system/src/phys/objects/collapse.rs +++ b/crates/system/src/phys/objects/collapse.rs @@ -1,4 +1,4 @@ -use galactica_content::{CollapseEvent, Ship}; +use galactica_content::{CollapseEvent, Content, Ship}; use nalgebra::{Point2, Vector2}; use rand::{rngs::ThreadRng, Rng}; use rapier2d::{dynamics::RigidBody, geometry::Collider}; @@ -32,13 +32,14 @@ impl ShipCollapseSequence { } /// Pick a random points inside a ship's collider - fn random_in_ship(&mut self, ship: &Ship, collider: &Collider) -> Vector2 { + fn random_in_ship(&mut self, ct: &Content, ship: &Ship, collider: &Collider) -> Vector2 { let mut y = 0.0; let mut x = 0.0; let mut a = false; while !a { x = self.rng.gen_range(-1.0..=1.0) * ship.size / 2.0; - y = self.rng.gen_range(-1.0..=1.0) * ship.size * ship.sprite.aspect / 2.0; + y = self.rng.gen_range(-1.0..=1.0) * ship.size * ct.get_sprite(ship.sprite).aspect + / 2.0; a = collider.shape().contains_local_point(&Point2::new(x, y)); } Vector2::new(x, y) @@ -76,7 +77,7 @@ impl ShipCollapseSequence { let pos: Vector2 = if let Some(pos) = spawner.pos { Vector2::new(pos.x, pos.y) } else { - self.random_in_ship(ship_content, collider) + self.random_in_ship(res.ct, ship_content, collider) }; let pos = ship_pos + (ship_rot * pos); @@ -120,7 +121,7 @@ impl ShipCollapseSequence { let pos = if let Some(pos) = spawner.pos { Vector2::new(pos.x, pos.y) } else { - self.random_in_ship(ship_content, collider) + self.random_in_ship(res.ct, ship_content, collider) }; // Position, adjusted for ship rotation diff --git a/crates/system/src/phys/objects/ship.rs b/crates/system/src/phys/objects/ship.rs index 99418b4..f96381e 100644 --- a/crates/system/src/phys/objects/ship.rs +++ b/crates/system/src/phys/objects/ship.rs @@ -64,6 +64,9 @@ pub struct PhysSimShip { /// This ship's controls pub(crate) controls: ShipControls, + /// This ship's controls during the last frame + last_controls: ShipControls, + /// This ship's collapse sequence collapse_sequence: Option, } @@ -86,6 +89,7 @@ impl PhysSimShip { data: ShipData::new(ct, handle, faction, personality), engine_anim: Vec::new(), controls: ShipControls::new(), + last_controls: ShipControls::new(), collapse_sequence: Some(ShipCollapseSequence::new(ship_ct.collapse.length)), } } @@ -100,14 +104,32 @@ impl PhysSimShip { self.data.step(res.t); self.anim.step(res.ct, res.t); - if self.controls.thrust { - for (_, e) in &mut self.engine_anim { - e.step(res.ct, res.t); - } - } else { - for (_, e) in &mut self.engine_anim { - e.reset(res.ct); - } + for (_, e) in &mut self.engine_anim { + e.step(res.ct, res.t); + } + + if !self.controls.thrust && self.last_controls.thrust { + let flare = self.get_flare(res.ct); + if flare.is_some() { + let flare_outfit = flare.unwrap(); + let flare = res.ct.get_outfit(flare_outfit); + if flare.engine_flare_on_stop.is_some() { + for (_, e) in &mut self.engine_anim { + e.next_edge(flare.engine_flare_on_stop.unwrap().into()); + } + } + }; + } else if self.controls.thrust && !self.last_controls.thrust { + let flare = self.get_flare(res.ct); + if flare.is_some() { + let flare_outfit = flare.unwrap(); + let flare = res.ct.get_outfit(flare_outfit); + if flare.engine_flare_on_start.is_some() { + for (_, e) in &mut self.engine_anim { + e.next_edge(flare.engine_flare_on_start.unwrap().into()); + } + } + }; } match self.data.get_state() { @@ -133,6 +155,8 @@ impl PhysSimShip { ShipState::Dead | ShipState::Landed { .. } => {} } + + self.last_controls = self.controls.clone(); } /// Update this frame's physics @@ -196,7 +220,7 @@ impl PhysSimShip { while !a { x = rng.gen_range(-1.0..=1.0) * ship_content.size / 2.0; y = rng.gen_range(-1.0..=1.0) - * ship_content.size * ship_content.sprite.aspect + * ship_content.size * res.ct.get_sprite(ship_content.sprite).aspect / 2.0; a = collider.shape().contains_local_point(&point![x, y]); } @@ -221,25 +245,30 @@ impl PhysSimShip { /// Public mutable impl PhysSimShip { - /// Re-create this ship's engine flare animations - /// Should be called whenever we change outfits - fn update_flares(&mut self, ct: &Content) { + fn get_flare(&mut self, ct: &Content) -> Option { // TODO: better way to pick flare sprite - let mut flare = None; for (h, _) in self.data.get_outfits().iter_outfits() { let c = ct.get_outfit(*h); if c.engine_flare_sprite.is_some() { - flare = c.engine_flare_sprite; - break; + return Some(*h); } } + return None; + } - if flare.is_none() { + /// Re-create this ship's engine flare animations + /// Should be called whenever we change outfits + fn update_flares(&mut self, ct: &Content) { + let flare_outfit = self.get_flare(ct); + if flare_outfit.is_none() { self.engine_anim = Vec::new(); return; } + let flare = ct + .get_outfit(flare_outfit.unwrap()) + .engine_flare_sprite + .unwrap(); - let flare = flare.unwrap(); self.engine_anim = ct .get_ship(self.data.get_content()) .engines