Added landed object image

master
Mark 2024-02-03 16:06:11 -08:00
parent 6b4c80ce0f
commit eca7e7f5e6
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
10 changed files with 265 additions and 55 deletions

View File

@ -40,7 +40,7 @@ zoom_max = 2000.0
# TODO: move to user config file # TODO: move to user config file
ui_scale = 2 ui_scale = 2
start_ui_scene = "landed" start_ui_scene = "flying"
ui_scene.landed = "ui/landed.rhai" ui_scene.landed = "ui/landed.rhai"
ui_scene.flying = "ui/flying.rhai" ui_scene.flying = "ui/flying.rhai"
ui_scene.outfitter = "ui/outfitter.rhai" ui_scene.outfitter = "ui/outfitter.rhai"

View File

@ -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 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. 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" object.luna.sprite = "planet::luna"

View File

@ -6,6 +6,8 @@ fn config() {
} }
fn init(state) { fn init(state) {
let player = state.player_ship();
let frame = SpriteBuilder( let frame = SpriteBuilder(
"frame", "frame",
"ui::planet", "ui::planet",
@ -18,7 +20,13 @@ fn init(state) {
let landscape = SpriteBuilder( let landscape = SpriteBuilder(
"landscape", "landscape",
"ui::landscape::test", {
if player.is_landed() {
player.landed_on().image();
} else {
"";
}
},
Rect( Rect(
-180.0, 142.0, 274.0, 135.0, -180.0, 142.0, 274.0, 135.0,
SpriteAnchor::NorthWest, SpriteAnchor::NorthWest,
@ -46,13 +54,33 @@ fn init(state) {
SpriteAnchor::Center 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 [ return [
button, button,
landscape, landscape,
frame, frame,
title, title,
desc,
]; ];
} }

View File

@ -25,7 +25,7 @@ fn init(state) {
SpriteAnchor::SouthWest SpriteAnchor::SouthWest
) )
); );
exit_text.set_text("Earth"); exit_text.set_text("Exit");
let exit_button = SpriteBuilder( let exit_button = SpriteBuilder(
"exit_button", "exit_button",
@ -70,7 +70,7 @@ fn init(state) {
SpriteAnchor::NorthWest SpriteAnchor::NorthWest
) )
); );
ship_name.set_text("Earth"); ship_name.set_text("Hyperion");
let ship_type = TextBoxBuilder( let ship_type = TextBoxBuilder(
"ship_type", "ship_type",
@ -81,7 +81,11 @@ fn init(state) {
SpriteAnchor::NorthWest 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( let ship_stats = TextBoxBuilder(
"ship_stats", "ship_stats",
@ -96,10 +100,6 @@ fn init(state) {
let outfit_bg = SpriteBuilder( let outfit_bg = SpriteBuilder(
"outfit_bg", "outfit_bg",
"ui::outfitter-outfit-bg", "ui::outfitter-outfit-bg",

View File

@ -1,4 +1,4 @@
use anyhow::{bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use galactica_util::to_radians; use galactica_util::to_radians;
use nalgebra::{Point2, Point3}; use nalgebra::{Point2, Point3};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
@ -31,6 +31,7 @@ pub(crate) mod syntax {
pub landable: Option<bool>, pub landable: Option<bool>,
pub name: Option<String>, pub name: Option<String>,
pub desc: Option<String>, pub desc: Option<String>,
pub image: Option<String>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -126,10 +127,13 @@ pub struct SystemObject {
pub landable: bool, pub landable: bool,
/// The display name of this object /// The display name of this object
pub name: String, pub name: Option<String>,
/// The description of this object /// The description of this object (shown on landed ui)
pub desc: String, pub desc: Option<String>,
/// This object's image (shown on landed ui)
pub image: Option<SpriteHandle>,
} }
/// Helper function for resolve_position, never called on its own. /// Helper function for resolve_position, never called on its own.
@ -220,8 +224,43 @@ impl crate::Build for System {
Some(t) => *t, 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 { objects.push(SystemObject {
sprite: sprite_handle, sprite: sprite_handle,
image: image_handle,
pos: resolve_position(&system.object, &obj, cycle_detector) pos: resolve_position(&system.object, &obj, cycle_detector)
.with_context(|| format!("in object {:#?}", label))?, .with_context(|| format!("in object {:#?}", label))?,
size: obj.size, size: obj.size,
@ -231,19 +270,13 @@ impl crate::Build for System {
body_index: 0, body_index: 0,
}, },
landable: obj.landable.unwrap_or(false), landable: obj.landable.unwrap_or(false),
name: obj name: obj.name.as_ref().map(|x| x.clone()),
.name
.as_ref()
.map(|x| x.clone())
.unwrap_or("".to_string()),
// TODO: better linebreaks, handle double spaces // TODO: better linebreaks, handle double spaces
// Tabs // Tabs
desc: obj desc: obj
.desc .desc
.as_ref() .as_ref()
.map(|x| x.replace("\n", " ").replace("<br>", "\n")) .map(|x| x.replace("\n", " ").replace("<br>", "\n")),
.unwrap_or("".to_string()),
}); });
} }

View File

@ -26,18 +26,24 @@ use rhai::{exported_module, Engine};
pub fn register_into_engine(engine: &mut Engine) { pub fn register_into_engine(engine: &mut Engine) {
engine engine
.build_type::<State>() // Helpers
.build_type::<ShipState>()
.build_type::<Rect>() .build_type::<Rect>()
.build_type::<Color>() .build_type::<Color>()
.build_type::<SceneConfig>()
// State
.build_type::<State>()
.build_type::<ShipState>()
.build_type::<SystemObjectState>()
// Builders
.build_type::<RadialBuilder>() .build_type::<RadialBuilder>()
.build_type::<SpriteElement>() .build_type::<SpriteElement>()
.build_type::<SceneConfig>()
.build_type::<SpriteBuilder>() .build_type::<SpriteBuilder>()
.build_type::<TextBoxBuilder>() .build_type::<TextBoxBuilder>()
// Events
.build_type::<MouseClickEvent>() .build_type::<MouseClickEvent>()
.build_type::<MouseHoverEvent>() .build_type::<MouseHoverEvent>()
.build_type::<PlayerShipStateEvent>() .build_type::<PlayerShipStateEvent>()
// Larger modules
.register_type_with_name::<SpriteAnchor>("SpriteAnchor") .register_type_with_name::<SpriteAnchor>("SpriteAnchor")
.register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into()) .register_static_module("SpriteAnchor", exported_module!(spriteanchor_mod).into())
.register_type_with_name::<TextBoxFont>("TextBoxFont") .register_type_with_name::<TextBoxFont>("TextBoxFont")

View File

@ -26,7 +26,18 @@ impl SpriteElement {
} }
pub fn take_edge(&mut self, edge_name: &str, duration: f32) { 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 sprite = self.ct.get_sprite(sprite_handle);
let edge = resolve_edge_as_edge(edge_name, duration, |x| { let edge = resolve_edge_as_edge(edge_name, duration, |x| {
@ -48,6 +59,8 @@ impl SpriteElement {
.as_ref() .as_ref()
.borrow_mut() .borrow_mut()
.anim .anim
.as_mut()
.unwrap()
.jump_to(&self.ct, edge); .jump_to(&self.ct, edge);
} }
} }

View File

@ -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 rhai::{CustomType, TypeBuilder};
use std::rc::Rc; use std::rc::Rc;
@ -15,13 +20,41 @@ unsafe impl Send for ShipState {}
unsafe impl Sync for ShipState {} unsafe impl Sync for ShipState {}
impl ShipState { impl ShipState {
pub fn get_state(&mut self) -> &data::ShipState { fn get_content(&mut self) -> &Ship {
let ship = self let ship = self
.input .input
.phys_img .phys_img
.get_ship(self.ship.as_ref().unwrap()) .get_ship(self.ship.as_ref().unwrap())
.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 builder
.with_name("ShipState") .with_name("ShipState")
.with_fn("is_some", |s: &mut Self| s.ship.is_some()) .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_dead", |s: &mut Self| s.get_data().get_state().is_dead())
.with_fn("is_landed", |s: &mut Self| s.get_state().is_landed()) .with_fn("is_landed", |s: &mut Self| {
.with_fn("is_landing", |s: &mut Self| s.get_state().is_landing()) s.get_data().get_state().is_landed()
.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_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| { .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<SystemObjectHandle>,
input: Rc<RenderInput>,
}
// 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<Self>) {
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());
} }
} }

View File

@ -196,8 +196,6 @@ impl UiManager {
self.fps_indicator.step(&input, state); self.fps_indicator.step(&input, state);
} }
let mut keep_doing_actions = true;
// Send player state change events // Send player state change events
if { if {
let player = input.player.ship; let player = input.player.ship;
@ -236,7 +234,9 @@ impl UiManager {
})?; })?;
if let Some(action) = action.try_cast::<SceneAction>() { if let Some(action) = action.try_cast::<SceneAction>() {
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.step(&input, state);
sprite.push_to_buffer(&input, state); sprite.push_to_buffer(&input, state);
if !keep_doing_actions {
continue;
}
let event = sprite.check_events(&input, state); let event = sprite.check_events(&input, state);
// we MUST drop here, since script calls mutate the sprite RefCell // we MUST drop here, since script calls mutate the sprite RefCell
drop(sprite); drop(sprite);
@ -304,7 +300,9 @@ impl UiManager {
})?; })?;
if let Some(action) = action.try_cast::<SceneAction>() { if let Some(action) = action.try_cast::<SceneAction>() {
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. /// 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( fn handle_action(
&mut self, &mut self,
state: &mut RenderState, state: &mut RenderState,
@ -331,10 +329,10 @@ impl UiManager {
action: SceneAction, action: SceneAction,
) -> Result<bool> { ) -> Result<bool> {
Ok(match action { Ok(match action {
SceneAction::None => true, SceneAction::None => false,
SceneAction::GoTo(s) => { SceneAction::GoTo(s) => {
self.set_scene(state, input.clone(), s.clone())?; self.set_scene(state, input.clone(), s.clone())?;
false true
} }
}) })
} }

View File

@ -1,14 +1,17 @@
use galactica_content::{Content, SpriteAutomaton, SpriteHandle}; use galactica_content::{Content, SpriteAutomaton, SpriteHandle};
use galactica_util::to_radians; use galactica_util::to_radians;
use log::error;
use super::super::api::Rect; use super::super::api::Rect;
use crate::{ui::event::Event, vertexbuffer::types::UiInstance, RenderInput, RenderState}; use crate::{ui::event::Event, vertexbuffer::types::UiInstance, RenderInput, RenderState};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Sprite { pub struct Sprite {
pub anim: SpriteAutomaton, /// If this is none, this was constructed with an invalid sprite
pub rect: Rect, pub anim: Option<SpriteAutomaton>,
pub mask: Option<SpriteHandle>,
rect: Rect,
mask: Option<SpriteHandle>,
pub name: String, pub name: String,
has_mouse: bool, has_mouse: bool,
@ -25,10 +28,24 @@ impl Sprite {
mask: Option<String>, mask: Option<String>,
rect: Rect, rect: Rect,
) -> Self { ) -> 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 = { let mask = {
if mask.is_some() { 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 { } else {
None None
} }
@ -36,7 +53,7 @@ impl Sprite {
Self { Self {
name, name,
anim: SpriteAutomaton::new(&ct, sprite_handle), anim: sprite_handle.map(|x| SpriteAutomaton::new(&ct, x)),
rect, rect,
mask, mask,
has_mouse: false, has_mouse: false,
@ -46,11 +63,15 @@ impl Sprite {
} }
pub fn push_to_buffer(&self, input: &RenderInput, state: &mut RenderState) { 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); let rect = self.rect.to_centered(state, input.ct.get_config().ui_scale);
// TODO: use both dimensions, // TODO: use both dimensions,
// not just height // 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 { state.push_ui_buffer(UiInstance {
position: rect.pos.into(), position: rect.pos.into(),
@ -117,6 +138,13 @@ impl Sprite {
} }
pub fn step(&mut self, input: &RenderInput, _state: &mut RenderState) { 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);
} }
} }