From eca7e7f5e6469a689fa8d1b17d7c3e3a70937617 Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 3 Feb 2024 16:06:11 -0800 Subject: [PATCH] Added landed object image --- content/config.toml | 2 +- content/system.toml | 1 + content/ui/landed.rhai | 32 +++++++- content/ui/outfitter.rhai | 14 ++-- crates/content/src/part/system.rs | 57 ++++++++++--- crates/render/src/ui/api/mod.rs | 12 ++- crates/render/src/ui/api/sprite.rs | 15 +++- crates/render/src/ui/api/state.rs | 123 +++++++++++++++++++++++++--- crates/render/src/ui/manager.rs | 20 ++--- crates/render/src/ui/util/sprite.rs | 44 ++++++++-- 10 files changed, 265 insertions(+), 55 deletions(-) diff --git a/content/config.toml b/content/config.toml index e3c5a3b..90a742e 100644 --- a/content/config.toml +++ b/content/config.toml @@ -40,7 +40,7 @@ zoom_max = 2000.0 # TODO: move to user config file ui_scale = 2 -start_ui_scene = "landed" +start_ui_scene = "flying" ui_scene.landed = "ui/landed.rhai" ui_scene.flying = "ui/flying.rhai" ui_scene.outfitter = "ui/outfitter.rhai" diff --git a/content/system.toml b/content/system.toml index 340e2d1..bf12268 100644 --- a/content/system.toml +++ b/content/system.toml @@ -28,6 +28,7 @@ one planet has a greater population than a hundred planets elsewhere. As a resul settlements of less than a million are grouped together into planetary districts that elect a single representative between them - a source of much frustration in the frontier worlds. """ +object.earth.image = "ui::landscape::test" object.luna.sprite = "planet::luna" diff --git a/content/ui/landed.rhai b/content/ui/landed.rhai index 202fa99..ad7031f 100644 --- a/content/ui/landed.rhai +++ b/content/ui/landed.rhai @@ -6,6 +6,8 @@ fn config() { } fn init(state) { + let player = state.player_ship(); + let frame = SpriteBuilder( "frame", "ui::planet", @@ -18,7 +20,13 @@ fn init(state) { let landscape = SpriteBuilder( "landscape", - "ui::landscape::test", + { + if player.is_landed() { + player.landed_on().image(); + } else { + ""; + } + }, Rect( -180.0, 142.0, 274.0, 135.0, SpriteAnchor::NorthWest, @@ -46,13 +54,33 @@ fn init(state) { SpriteAnchor::Center ) ); - title.set_text("Title"); + if player.is_landed() { + title.set_text(player.landed_on().name()); + } else { + title.set_text(""); + } + + let desc = TextBoxBuilder( + "desc", + 7.5, 8.0, TextBoxFont::SansSerif, TextBoxJustify::Left, + Rect( + -178.92, -20.3, 343.0, 81.467, + SpriteAnchor::NorthWest, + SpriteAnchor::Center + ) + ); + if player.is_landed() { + desc.set_text(player.landed_on().desc()); + } else { + desc.set_text(""); + } return [ button, landscape, frame, title, + desc, ]; } diff --git a/content/ui/outfitter.rhai b/content/ui/outfitter.rhai index c778c47..e4e4b91 100644 --- a/content/ui/outfitter.rhai +++ b/content/ui/outfitter.rhai @@ -25,7 +25,7 @@ fn init(state) { SpriteAnchor::SouthWest ) ); - exit_text.set_text("Earth"); + exit_text.set_text("Exit"); let exit_button = SpriteBuilder( "exit_button", @@ -70,7 +70,7 @@ fn init(state) { SpriteAnchor::NorthWest ) ); - ship_name.set_text("Earth"); + ship_name.set_text("Hyperion"); let ship_type = TextBoxBuilder( "ship_type", @@ -81,7 +81,11 @@ fn init(state) { SpriteAnchor::NorthWest ) ); - ship_type.set_text("Earth"); + if state.player_ship().is_some() { + ship_type.set_text(state.player_ship().name()); + } else { + ship_type.set_text("ERR: SHIP IS NONE"); + } let ship_stats = TextBoxBuilder( "ship_stats", @@ -96,10 +100,6 @@ fn init(state) { - - - - let outfit_bg = SpriteBuilder( "outfit_bg", "ui::outfitter-outfit-bg", diff --git a/crates/content/src/part/system.rs b/crates/content/src/part/system.rs index 550e388..8e45a3a 100644 --- a/crates/content/src/part/system.rs +++ b/crates/content/src/part/system.rs @@ -1,4 +1,4 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; use galactica_util::to_radians; use nalgebra::{Point2, Point3}; use std::collections::{HashMap, HashSet}; @@ -31,6 +31,7 @@ pub(crate) mod syntax { pub landable: Option, pub name: Option, pub desc: Option, + pub image: Option, } #[derive(Debug, Deserialize)] @@ -126,10 +127,13 @@ pub struct SystemObject { pub landable: bool, /// The display name of this object - pub name: String, + pub name: Option, - /// The description of this object - pub desc: String, + /// The description of this object (shown on landed ui) + pub desc: Option, + + /// This object's image (shown on landed ui) + pub image: Option, } /// Helper function for resolve_position, never called on its own. @@ -220,8 +224,43 @@ impl crate::Build for System { Some(t) => *t, }; + let image_handle = match &obj.image { + Some(x) => match content.sprite_index.get(x) { + None => bail!( + "In system `{}`: sprite `{}` doesn't exist", + system_name, + obj.sprite + ), + Some(t) => Some(*t), + }, + None => None, + }; + + if obj.landable.unwrap_or(false) { + if obj.name.is_none() { + return Err(anyhow!("if an object is landable, it must have a name")) + .with_context(|| format!("in object labeled `{}`", label)) + .with_context(|| format!("in system `{}`", system_name)); + } + + if obj.desc.is_none() { + return Err(anyhow!( + "if an object is landable, it must have a description" + )) + .with_context(|| format!("in object labeled `{}`", label)) + .with_context(|| format!("in system `{}`", system_name)); + } + + if obj.image.is_none() { + return Err(anyhow!("if an object is landable, it must have an image")) + .with_context(|| format!("in object labeled `{}`", label)) + .with_context(|| format!("in system `{}`", system_name)); + } + } + objects.push(SystemObject { sprite: sprite_handle, + image: image_handle, pos: resolve_position(&system.object, &obj, cycle_detector) .with_context(|| format!("in object {:#?}", label))?, size: obj.size, @@ -231,19 +270,13 @@ impl crate::Build for System { body_index: 0, }, landable: obj.landable.unwrap_or(false), - name: obj - .name - .as_ref() - .map(|x| x.clone()) - .unwrap_or("".to_string()), - + name: obj.name.as_ref().map(|x| x.clone()), // TODO: better linebreaks, handle double spaces // Tabs desc: obj .desc .as_ref() - .map(|x| x.replace("\n", " ").replace("
", "\n")) - .unwrap_or("".to_string()), + .map(|x| x.replace("\n", " ").replace("
", "\n")), }); } diff --git a/crates/render/src/ui/api/mod.rs b/crates/render/src/ui/api/mod.rs index e6078e8..a9e30d4 100644 --- a/crates/render/src/ui/api/mod.rs +++ b/crates/render/src/ui/api/mod.rs @@ -26,18 +26,24 @@ use rhai::{exported_module, Engine}; pub fn register_into_engine(engine: &mut Engine) { engine - .build_type::() - .build_type::() + // Helpers .build_type::() .build_type::() + .build_type::() + // State + .build_type::() + .build_type::() + .build_type::() + // Builders .build_type::() .build_type::() - .build_type::() .build_type::() .build_type::() + // Events .build_type::() .build_type::() .build_type::() + // Larger modules .register_type_with_name::("SpriteAnchor") .register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into()) .register_type_with_name::("TextBoxFont") diff --git a/crates/render/src/ui/api/sprite.rs b/crates/render/src/ui/api/sprite.rs index a06fba2..63c1213 100644 --- a/crates/render/src/ui/api/sprite.rs +++ b/crates/render/src/ui/api/sprite.rs @@ -26,7 +26,18 @@ impl SpriteElement { } pub fn take_edge(&mut self, edge_name: &str, duration: f32) { - let sprite_handle = self.sprite.as_ref().borrow().anim.get_sprite(); + if self.sprite.as_ref().borrow().anim.is_none() { + return; + } + + let sprite_handle = self + .sprite + .as_ref() + .borrow() + .anim + .as_ref() + .unwrap() + .get_sprite(); let sprite = self.ct.get_sprite(sprite_handle); let edge = resolve_edge_as_edge(edge_name, duration, |x| { @@ -48,6 +59,8 @@ impl SpriteElement { .as_ref() .borrow_mut() .anim + .as_mut() + .unwrap() .jump_to(&self.ct, edge); } } diff --git a/crates/render/src/ui/api/state.rs b/crates/render/src/ui/api/state.rs index 75696f6..3fdaf35 100644 --- a/crates/render/src/ui/api/state.rs +++ b/crates/render/src/ui/api/state.rs @@ -1,4 +1,9 @@ -use galactica_system::{data, phys::PhysSimShipHandle}; +use galactica_content::{Ship, SystemObject, SystemObjectHandle}; +use galactica_system::{ + data::{self, ShipData}, + phys::PhysSimShipHandle, +}; +use log::error; use rhai::{CustomType, TypeBuilder}; use std::rc::Rc; @@ -15,13 +20,41 @@ unsafe impl Send for ShipState {} unsafe impl Sync for ShipState {} impl ShipState { - pub fn get_state(&mut self) -> &data::ShipState { + fn get_content(&mut self) -> &Ship { let ship = self .input .phys_img .get_ship(self.ship.as_ref().unwrap()) .unwrap(); - ship.ship.get_data().get_state() + let handle = ship.ship.get_data().get_content(); + self.input.ct.get_ship(handle) + } + + fn get_data(&mut self) -> &ShipData { + let ship = self + .input + .phys_img + .get_ship(self.ship.as_ref().unwrap()) + .unwrap(); + ship.ship.get_data() + } + + fn landed_on(&mut self) -> SystemObjectState { + let input = self.input.clone(); + match self.get_data().get_state() { + data::ShipState::Landed { target } => { + return SystemObjectState { + input, + object: Some(*target), + } + } + _ => { + return SystemObjectState { + input, + object: None, + } + } + }; } } @@ -30,14 +63,84 @@ impl CustomType for ShipState { builder .with_name("ShipState") .with_fn("is_some", |s: &mut Self| s.ship.is_some()) - .with_fn("is_dead", |s: &mut Self| s.get_state().is_dead()) - .with_fn("is_landed", |s: &mut Self| s.get_state().is_landed()) - .with_fn("is_landing", |s: &mut Self| s.get_state().is_landing()) - .with_fn("is_flying", |s: &mut Self| s.get_state().is_flying()) - .with_fn("is_unlanding", |s: &mut Self| s.get_state().is_unlanding()) + .with_fn("is_dead", |s: &mut Self| s.get_data().get_state().is_dead()) + .with_fn("is_landed", |s: &mut Self| { + s.get_data().get_state().is_landed() + }) + .with_fn("is_landing", |s: &mut Self| { + s.get_data().get_state().is_landing() + }) + .with_fn("is_flying", |s: &mut Self| { + s.get_data().get_state().is_flying() + }) + .with_fn("is_unlanding", |s: &mut Self| { + s.get_data().get_state().is_unlanding() + }) .with_fn("is_collapsing", |s: &mut Self| { - s.get_state().is_collapsing() - }); + s.get_data().get_state().is_collapsing() + }) + .with_fn("name", |s: &mut Self| s.get_content().name.clone()) + .with_fn("thumbnail", |s: &mut Self| s.get_content().thumb) + .with_fn("landed_on", |s: &mut Self| s.landed_on()); + } +} + +#[derive(Debug, Clone)] +pub struct SystemObjectState { + object: Option, + input: Rc, +} + +// TODO: remove this +unsafe impl Send for SystemObjectState {} +unsafe impl Sync for SystemObjectState {} + +impl SystemObjectState { + fn get_content(&mut self) -> &SystemObject { + self.input.ct.get_system_object(self.object.unwrap()) + } +} + +impl CustomType for SystemObjectState { + fn build(mut builder: TypeBuilder) { + builder + .with_name("SystemObjectState") + // + // Get landable name + .with_fn("name", |s: &mut Self| { + s.get_content() + .name + .as_ref() + .map(|x| x.to_string()) + .unwrap_or_else(|| { + error!("UI called `name()` on a system object which doesn't provide one"); + "".to_string() + }) + }) + // + // Get landable description + .with_fn("desc", |s: &mut Self| { + s.get_content() + .desc + .as_ref() + .map(|x| x.to_string()) + .unwrap_or_else(|| { + error!("UI called `name()` on a system object which doesn't provide one"); + "".to_string() + }) + }) + // + // Get landable landscape image + .with_fn("image", |s: &mut Self| { + let handle = s.get_content().image; + if let Some(handle) = handle { + s.input.ct.get_sprite(handle).name.clone() + } else { + error!("UI called `image()` on a system object which doesn't provide one"); + "".to_string() + } + }) + .with_fn("is_some", |s: &mut Self| s.object.is_some()); } } diff --git a/crates/render/src/ui/manager.rs b/crates/render/src/ui/manager.rs index 0ed2e6c..2ff7e6b 100644 --- a/crates/render/src/ui/manager.rs +++ b/crates/render/src/ui/manager.rs @@ -196,8 +196,6 @@ impl UiManager { self.fps_indicator.step(&input, state); } - let mut keep_doing_actions = true; - // Send player state change events if { let player = input.player.ship; @@ -236,7 +234,9 @@ impl UiManager { })?; if let Some(action) = action.try_cast::() { - keep_doing_actions = self.handle_action(state, input.clone(), action)?; + if self.handle_action(state, input.clone(), action)? { + return Ok(()); + } } } @@ -248,10 +248,6 @@ impl UiManager { sprite.step(&input, state); sprite.push_to_buffer(&input, state); - if !keep_doing_actions { - continue; - } - let event = sprite.check_events(&input, state); // we MUST drop here, since script calls mutate the sprite RefCell drop(sprite); @@ -304,7 +300,9 @@ impl UiManager { })?; if let Some(action) = action.try_cast::() { - keep_doing_actions = self.handle_action(state, input.clone(), action)?; + if self.handle_action(state, input.clone(), action)? { + return Ok(()); + } } } @@ -323,7 +321,7 @@ impl UiManager { } /// Do a SceneAction. - /// Returns true if we should execute the action that is next, and false otherwise. + /// If this returns true, all evaluation stops immediately. fn handle_action( &mut self, state: &mut RenderState, @@ -331,10 +329,10 @@ impl UiManager { action: SceneAction, ) -> Result { Ok(match action { - SceneAction::None => true, + SceneAction::None => false, SceneAction::GoTo(s) => { self.set_scene(state, input.clone(), s.clone())?; - false + true } }) } diff --git a/crates/render/src/ui/util/sprite.rs b/crates/render/src/ui/util/sprite.rs index 7d425bf..6e805c5 100644 --- a/crates/render/src/ui/util/sprite.rs +++ b/crates/render/src/ui/util/sprite.rs @@ -1,14 +1,17 @@ use galactica_content::{Content, SpriteAutomaton, SpriteHandle}; use galactica_util::to_radians; +use log::error; use super::super::api::Rect; use crate::{ui::event::Event, vertexbuffer::types::UiInstance, RenderInput, RenderState}; #[derive(Debug, Clone)] pub struct Sprite { - pub anim: SpriteAutomaton, - pub rect: Rect, - pub mask: Option, + /// If this is none, this was constructed with an invalid sprite + pub anim: Option, + + rect: Rect, + mask: Option, pub name: String, has_mouse: bool, @@ -25,10 +28,24 @@ impl Sprite { mask: Option, rect: Rect, ) -> Self { - let sprite_handle = ct.get_sprite_handle(&sprite).unwrap(); // TODO: errors + let sprite_handle = ct.get_sprite_handle(&sprite); + + if sprite_handle.is_none() { + error!("UI constructed a sprite named `{name}` using an invalid source `{sprite}`") + } + let mask = { if mask.is_some() { - Some(ct.get_sprite_handle(&mask.unwrap()).unwrap()) + let m = ct.get_sprite_handle(mask.as_ref().unwrap()); + if m.is_none() { + error!( + "UI constructed a sprite named `{name}` using an invalid mask` {}`", + mask.unwrap() + ); + None + } else { + m + } } else { None } @@ -36,7 +53,7 @@ impl Sprite { Self { name, - anim: SpriteAutomaton::new(&ct, sprite_handle), + anim: sprite_handle.map(|x| SpriteAutomaton::new(&ct, x)), rect, mask, has_mouse: false, @@ -46,11 +63,15 @@ impl Sprite { } pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { + if self.anim.is_none() { + return; + } + let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale); // TODO: use both dimensions, // not just height - let anim_state = self.anim.get_texture_idx(); + let anim_state = self.anim.as_ref().unwrap().get_texture_idx(); state.push_ui_buffer(UiInstance { position: rect.pos.into(), @@ -117,6 +138,13 @@ impl Sprite { } pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) { - self.anim.step(&input.ct, input.time_since_last_run); + if self.anim.is_none() { + return; + } + + self.anim + .as_mut() + .unwrap() + .step(&input.ct, input.time_since_last_run); } }