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
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"

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
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"

View File

@ -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,
];
}

View File

@ -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",

View File

@ -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<bool>,
pub name: Option<String>,
pub desc: Option<String>,
pub image: Option<String>,
}
#[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<String>,
/// The description of this object
pub desc: String,
/// The description of this object (shown on landed ui)
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.
@ -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("<br>", "\n"))
.unwrap_or("".to_string()),
.map(|x| x.replace("\n", " ").replace("<br>", "\n")),
});
}

View File

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

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 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<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);
}
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::<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.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::<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.
/// 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<bool> {
Ok(match action {
SceneAction::None => true,
SceneAction::None => false,
SceneAction::GoTo(s) => {
self.set_scene(state, input.clone(), s.clone())?;
false
true
}
})
}

View File

@ -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<SpriteHandle>,
/// If this is none, this was constructed with an invalid sprite
pub anim: Option<SpriteAutomaton>,
rect: Rect,
mask: Option<SpriteHandle>,
pub name: String,
has_mouse: bool,
@ -25,10 +28,24 @@ impl Sprite {
mask: Option<String>,
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);
}
}