Reworked content loader for scripting
parent
c4754b053a
commit
40fb31ad7e
|
@ -40,6 +40,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"const-random",
|
||||||
|
"getrandom",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"version_check",
|
"version_check",
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
|
@ -409,6 +411,26 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642"
|
checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5aaf16c9c2c612020bcfd042e170f6e32de9b9d75adb5277cdbbd2e2c8c8299a"
|
||||||
|
dependencies = [
|
||||||
|
"const-random-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "const-random-macro"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"tiny-keccak",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
|
@ -795,6 +817,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
"rapier2d",
|
"rapier2d",
|
||||||
|
"rhai",
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
@ -842,6 +865,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rhai",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
@ -1934,6 +1958,36 @@ version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"
|
checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rhai"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a0de8df6dd1f4886ad635d7e0fc7afc086e4f6daeff129d157287b78738516b"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"bitflags 2.4.1",
|
||||||
|
"instant",
|
||||||
|
"num-traits",
|
||||||
|
"once_cell",
|
||||||
|
"rhai_codegen",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"smallvec",
|
||||||
|
"smartstring",
|
||||||
|
"thin-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rhai_codegen"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89789ba6e8fd0889ae70b39c09000148431c9a1d618eb9d388373f391a55c988"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.42",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "robust"
|
name = "robust"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2137,6 +2191,21 @@ name = "smallvec"
|
||||||
version = "1.11.2"
|
version = "1.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smartstring"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"serde",
|
||||||
|
"static_assertions",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smithay-client-toolkit"
|
name = "smithay-client-toolkit"
|
||||||
|
@ -2262,6 +2331,15 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thin-vec"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.51"
|
version = "1.0.51"
|
||||||
|
@ -2303,6 +2381,15 @@ dependencies = [
|
||||||
"weezl",
|
"weezl",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-keccak"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||||
|
dependencies = [
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tiny-skia"
|
name = "tiny-skia"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
|
|
@ -74,3 +74,9 @@ lazy_static = "1.4.0"
|
||||||
clap = { version = "4.4.18", features = ["derive"] }
|
clap = { version = "4.4.18", features = ["derive"] }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
log4rs = { version = "1.2.0", features = ["console_appender"] }
|
log4rs = { version = "1.2.0", features = ["console_appender"] }
|
||||||
|
rhai = { version = "1.17.0", features = [
|
||||||
|
"f32_float",
|
||||||
|
"metadata",
|
||||||
|
"sync",
|
||||||
|
"no_custom_syntax",
|
||||||
|
] }
|
||||||
|
|
|
@ -36,3 +36,7 @@ starfield.texture = "starfield.png"
|
||||||
# Zoom is measured as "window height in game units."
|
# Zoom is measured as "window height in game units."
|
||||||
zoom_min = 200.0
|
zoom_min = 200.0
|
||||||
zoom_max = 2000.0
|
zoom_max = 2000.0
|
||||||
|
|
||||||
|
# TODO: move to user config file
|
||||||
|
ui_scale = 2
|
||||||
|
ui_landed_scene = "landed.rhai"
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
|
||||||
|
fn init(state) {
|
||||||
|
let frame = SpriteBuilder(
|
||||||
|
"frame",
|
||||||
|
"ui::planet",
|
||||||
|
Rect(
|
||||||
|
0.0, 0.0, 400.0, 297.866,
|
||||||
|
SpriteAnchor::Center,
|
||||||
|
SpriteAnchor::Center
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let landscape = SpriteBuilder(
|
||||||
|
"landscape",
|
||||||
|
state.planet_landscape,
|
||||||
|
Rect(
|
||||||
|
-180.0, 142.0, 274.0, 135.0,
|
||||||
|
SpriteAnchor::NorthWest,
|
||||||
|
SpriteAnchor::Center
|
||||||
|
)
|
||||||
|
);
|
||||||
|
landscape.set_mask("ui::landscapemask");
|
||||||
|
|
||||||
|
let button = SpriteBuilder(
|
||||||
|
"button",
|
||||||
|
"ui::planet::button",
|
||||||
|
Rect(
|
||||||
|
99.0, 128.0, 73.898, 18.708,
|
||||||
|
SpriteAnchor::NorthWest,
|
||||||
|
SpriteAnchor::Center
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let title = TextBoxBuilder(
|
||||||
|
"title",
|
||||||
|
10.0, 10.0, TextBoxFont::Serif, TextBoxJustify::Center,
|
||||||
|
Rect(
|
||||||
|
-70.79, 138.0, 59.867, 10.0,
|
||||||
|
SpriteAnchor::NorthWest,
|
||||||
|
SpriteAnchor::Center
|
||||||
|
)
|
||||||
|
);
|
||||||
|
title.set_text(state.planet_name);
|
||||||
|
|
||||||
|
return [
|
||||||
|
button,
|
||||||
|
landscape,
|
||||||
|
frame,
|
||||||
|
title,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hover(element, hover_state) {
|
||||||
|
if element.has_name("button") {
|
||||||
|
if hover_state {
|
||||||
|
element.take_edge("on:top", 0.1);
|
||||||
|
} else {
|
||||||
|
element.take_edge("off:top", 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//fn click(scene, name) {
|
||||||
|
// if name = "button" {
|
||||||
|
// return SceneAction::Outfitter();
|
||||||
|
// }
|
||||||
|
//}
|
|
@ -1,5 +1,6 @@
|
||||||
[ship."Gypsum"]
|
[ship."Gypsum"]
|
||||||
sprite = "ship::gypsum"
|
sprite = "ship::gypsum"
|
||||||
|
thumb = "icon::gypsum"
|
||||||
size = 100
|
size = 100
|
||||||
mass = 1
|
mass = 1
|
||||||
hull = 200
|
hull = 200
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
[ui.status]
|
|
||||||
|
|
||||||
# TODO: unified color value
|
|
||||||
# TODO: bar type: linear/radial
|
|
||||||
# TODO: bar as ui util struct
|
|
||||||
# TODO: mouse collider
|
|
||||||
# TODO: modular UI (how?)
|
|
||||||
|
|
||||||
# shield_bar.pos = [-19, -19]
|
|
||||||
# shield_bar.diameter = 182
|
|
||||||
# shield_bar.stroke = 5
|
|
||||||
# shield_bar.color = [0.3, 0.6, 0.8, 1.0]
|
|
||||||
#
|
|
||||||
# hull_bar.pos = [-27.0, -27.0]
|
|
||||||
# hull_bar.diameter = 166.0
|
|
||||||
# hull_bar.stroke = 5
|
|
||||||
# hull_bar.color = [0.8, 0.7, 0.5, 1.0]
|
|
||||||
# frame.sprite = "ui::status"
|
|
||||||
# frame.pos = [-10.0, -10.0]
|
|
||||||
# frame.dim = [200.0, 200.0]
|
|
||||||
|
|
||||||
|
|
||||||
[ui.landed]
|
|
||||||
frame.sprite = "ui::planet"
|
|
||||||
frame.rect.pos = [0.0, 0.0]
|
|
||||||
frame.rect.dim = [800.0, 800.0]
|
|
||||||
frame.rect.anchor_self = "center"
|
|
||||||
frame.rect.anchor_parent = "center"
|
|
||||||
|
|
||||||
landscape.mask = "ui::landscapemask"
|
|
||||||
landscape.rect.pos = [-350.0, 282.8]
|
|
||||||
landscape.rect.dim = [537.5, 270.31]
|
|
||||||
landscape.rect.anchor_self = "northwest"
|
|
||||||
landscape.rect.anchor_parent = "center"
|
|
||||||
|
|
||||||
|
|
||||||
button.sprite = "ui::planet::button"
|
|
||||||
button.rect.pos = [178.12, 254.6]
|
|
||||||
button.rect.dim = [175.16, 38.437]
|
|
||||||
button.rect.anchor_self = "northwest"
|
|
||||||
button.rect.anchor_parent = "center"
|
|
||||||
button.on_mouse_enter.edge = "on:top"
|
|
||||||
button.on_mouse_enter.duration = 0.1
|
|
||||||
button.on_mouse_leave.edge = "off:top"
|
|
||||||
button.on_mouse_leave.duration = 0.1
|
|
||||||
|
|
||||||
planet_name.rect.pos = [-143.89, 273]
|
|
||||||
planet_name.rect.dim = [121.98, 18.094]
|
|
||||||
planet_name.rect.anchor_self = "northwest"
|
|
||||||
planet_name.rect.anchor_parent = "center"
|
|
||||||
planet_name.font_size = 19
|
|
||||||
planet_name.line_height = 19
|
|
||||||
planet_name.align = "center"
|
|
||||||
|
|
||||||
planet_desc.rect.pos = [-358.43, -32]
|
|
||||||
planet_desc.rect.dim = [673.91, 153.75]
|
|
||||||
planet_desc.rect.anchor_self = "northwest"
|
|
||||||
planet_desc.rect.anchor_parent = "center"
|
|
||||||
planet_desc.font_size = 16
|
|
||||||
planet_desc.line_height = 18
|
|
||||||
planet_desc.align = "left"
|
|
||||||
|
|
||||||
|
|
||||||
[ui.outfitter]
|
|
||||||
|
|
||||||
se_box.sprite = "ui::outfitterbox"
|
|
||||||
se_box.rect.pos = [-10.0, -10.0]
|
|
||||||
se_box.rect.dim = [512.0, 337.0] # todo: auto aspect
|
|
||||||
se_box.rect.anchor_self = "southwest"
|
|
||||||
se_box.rect.anchor_parent = "southwest"
|
|
||||||
|
|
||||||
exit_button.sprite = "ui::button"
|
|
||||||
exit_button.rect.pos = [279.07, 135.38]
|
|
||||||
exit_button.rect.dim = [173.44, 45.01]
|
|
||||||
exit_button.rect.anchor_self = "northwest"
|
|
||||||
exit_button.rect.anchor_parent = "southwest"
|
|
||||||
exit_button.on_mouse_enter.edge = "on:top"
|
|
||||||
exit_button.on_mouse_enter.duration = 0.1
|
|
||||||
exit_button.on_mouse_leave.edge = "off:top"
|
|
||||||
exit_button.on_mouse_leave.duration = 0.1
|
|
|
@ -29,3 +29,4 @@ image = { workspace = true }
|
||||||
rapier2d = { workspace = true }
|
rapier2d = { workspace = true }
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
rhai = { workspace = true }
|
||||||
|
|
|
@ -12,6 +12,7 @@ use anyhow::{bail, Context, Result};
|
||||||
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
use galactica_packer::{SpriteAtlas, SpriteAtlasImage};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use std::{
|
use std::{
|
||||||
|
//cell::OnceCell,
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::Read,
|
io::Read,
|
||||||
|
@ -33,7 +34,6 @@ mod syntax {
|
||||||
use crate::{
|
use crate::{
|
||||||
config,
|
config,
|
||||||
part::{effect, faction, outfit, ship, sprite, system},
|
part::{effect, faction, outfit, ship, sprite, system},
|
||||||
ui,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -45,7 +45,6 @@ mod syntax {
|
||||||
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
pub faction: Option<HashMap<String, faction::syntax::Faction>>,
|
||||||
pub effect: Option<HashMap<String, effect::syntax::Effect>>,
|
pub effect: Option<HashMap<String, effect::syntax::Effect>>,
|
||||||
pub config: Option<config::syntax::Config>,
|
pub config: Option<config::syntax::Config>,
|
||||||
pub ui: Option<ui::syntax::Ui>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_hashmap<K, V>(
|
fn merge_hashmap<K, V>(
|
||||||
|
@ -83,7 +82,6 @@ mod syntax {
|
||||||
faction: None,
|
faction: None,
|
||||||
effect: None,
|
effect: None,
|
||||||
config: None,
|
config: None,
|
||||||
ui: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,15 +105,6 @@ mod syntax {
|
||||||
} else {
|
} else {
|
||||||
self.config = other.config;
|
self.config = other.config;
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ui.is_some() {
|
|
||||||
if other.ui.is_some() {
|
|
||||||
bail!("invalid content dir, multiple ui tables")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.ui = other.ui;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,16 +128,12 @@ trait Build {
|
||||||
pub(crate) struct ContentBuildContext {
|
pub(crate) struct ContentBuildContext {
|
||||||
/// Map effect names to handles
|
/// Map effect names to handles
|
||||||
pub effect_index: HashMap<String, EffectHandle>,
|
pub effect_index: HashMap<String, EffectHandle>,
|
||||||
|
|
||||||
/// Maps sprite handles to a map of section name -> section index
|
|
||||||
pub sprite_section_index: HashMap<SpriteHandle, HashMap<String, AnimSectionHandle>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContentBuildContext {
|
impl ContentBuildContext {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
effect_index: HashMap::new(),
|
effect_index: HashMap::new(),
|
||||||
sprite_section_index: HashMap::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +157,6 @@ pub struct Content {
|
||||||
factions: Vec<Faction>,
|
factions: Vec<Faction>,
|
||||||
effects: Vec<Effect>,
|
effects: Vec<Effect>,
|
||||||
config: Config,
|
config: Config,
|
||||||
ui: Option<Ui>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading methods
|
// Loading methods
|
||||||
|
@ -185,10 +169,17 @@ impl Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load content from a directory.
|
/// Load content from a directory.
|
||||||
pub fn load_dir(path: PathBuf, asset_root: PathBuf, atlas_index: PathBuf) -> Result<Self> {
|
pub fn load_dir(
|
||||||
|
content_root: PathBuf,
|
||||||
|
asset_root: PathBuf,
|
||||||
|
atlas_index: PathBuf,
|
||||||
|
) -> Result<Self> {
|
||||||
let mut root = syntax::Root::new();
|
let mut root = syntax::Root::new();
|
||||||
|
|
||||||
for e in WalkDir::new(path).into_iter().filter_map(|e| e.ok()) {
|
for e in WalkDir::new(&content_root)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
if e.metadata().unwrap().is_file() {
|
if e.metadata().unwrap().is_file() {
|
||||||
// TODO: better warnings
|
// TODO: better warnings
|
||||||
match e.path().extension() {
|
match e.path().extension() {
|
||||||
|
@ -225,7 +216,7 @@ impl Content {
|
||||||
let mut content = Self {
|
let mut content = Self {
|
||||||
config: {
|
config: {
|
||||||
if let Some(c) = root.config {
|
if let Some(c) = root.config {
|
||||||
c.build(&asset_root, &atlas)
|
c.build(&asset_root, &content_root, &atlas)
|
||||||
.with_context(|| "while parsing config table")?
|
.with_context(|| "while parsing config table")?
|
||||||
} else {
|
} else {
|
||||||
bail!("failed loading content: no config table specified")
|
bail!("failed loading content: no config table specified")
|
||||||
|
@ -241,7 +232,6 @@ impl Content {
|
||||||
factions: Vec::new(),
|
factions: Vec::new(),
|
||||||
effects: Vec::new(),
|
effects: Vec::new(),
|
||||||
sprite_index: HashMap::new(),
|
sprite_index: HashMap::new(),
|
||||||
ui: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: enforce sprite and image limits
|
// TODO: enforce sprite and image limits
|
||||||
|
@ -263,10 +253,6 @@ impl Content {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.ui.is_some() {
|
|
||||||
part::ui::Ui::build(root.ui.take().unwrap(), &mut build_context, &mut content)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order below this line does not matter
|
// Order below this line does not matter
|
||||||
if root.ship.is_some() {
|
if root.ship.is_some() {
|
||||||
part::ship::Ship::build(root.ship.take().unwrap(), &mut build_context, &mut content)?;
|
part::ship::Ship::build(root.ship.take().unwrap(), &mut build_context, &mut content)?;
|
||||||
|
@ -305,11 +291,8 @@ impl Content {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a handle from a sprite name
|
/// Get a handle from a sprite name
|
||||||
pub fn get_sprite_handle(&self, name: &str) -> SpriteHandle {
|
pub fn get_sprite_handle(&self, name: &str) -> Option<SpriteHandle> {
|
||||||
return match self.sprite_index.get(name) {
|
self.sprite_index.get(name).map(|x| *x)
|
||||||
Some(s) => *s,
|
|
||||||
None => unreachable!("get_sprite_handle was called with a bad name!"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a sprite from a handle
|
/// Get a sprite from a handle
|
||||||
|
@ -373,9 +356,23 @@ impl Content {
|
||||||
pub fn get_config(&self) -> &Config {
|
pub fn get_config(&self) -> &Config {
|
||||||
return &self.config;
|
return &self.config;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get ui configuration
|
/*
|
||||||
pub fn get_ui(&self) -> &Ui {
|
TODO: don't pass content around?
|
||||||
return self.ui.as_ref().unwrap();
|
static mut CONTENT: OnceCell<Content> = OnceCell::new();
|
||||||
|
|
||||||
|
/// Initialize content::CONTENT with the given paths
|
||||||
|
pub fn init(content_dir: PathBuf, asset_dir: PathBuf, atlas_index: PathBuf) -> Result<()> {
|
||||||
|
let content = Content::load_dir(content_dir, asset_dir, atlas_index)?;
|
||||||
|
unsafe {
|
||||||
|
match CONTENT.set(content) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(_) => {
|
||||||
|
bail!("cannot initialize content, already set.")
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use std::{num::NonZeroU32, path::PathBuf};
|
use std::{num::NonZeroU32, path::PathBuf};
|
||||||
|
|
||||||
|
use rhai::AST;
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use galactica_packer::SpriteAtlas;
|
use galactica_packer::SpriteAtlas;
|
||||||
|
use rhai::Engine;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -16,11 +19,18 @@ pub(crate) mod syntax {
|
||||||
pub starfield: Starfield,
|
pub starfield: Starfield,
|
||||||
pub zoom_min: f32,
|
pub zoom_min: f32,
|
||||||
pub zoom_max: f32,
|
pub zoom_max: f32,
|
||||||
|
pub ui_scale: f32,
|
||||||
|
pub ui_landed_scene: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
// TODO: clean up build trait
|
// TODO: clean up build trait
|
||||||
pub fn build(self, asset_root: &Path, atlas: &SpriteAtlas) -> Result<super::Config> {
|
pub fn build(
|
||||||
|
self,
|
||||||
|
asset_root: &Path,
|
||||||
|
content_root: &Path,
|
||||||
|
atlas: &SpriteAtlas,
|
||||||
|
) -> Result<super::Config> {
|
||||||
for i in &self.fonts.files {
|
for i in &self.fonts.files {
|
||||||
if !asset_root.join(i).exists() {
|
if !asset_root.join(i).exists() {
|
||||||
bail!("font file `{}` doesn't exist", i.display());
|
bail!("font file `{}` doesn't exist", i.display());
|
||||||
|
@ -46,6 +56,12 @@ pub(crate) mod syntax {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let engine = Engine::new();
|
||||||
|
let ui_landed_scene = engine
|
||||||
|
.compile_file(content_root.join(self.ui_landed_scene))
|
||||||
|
.with_context(|| format!("while loading `landed` scene"))
|
||||||
|
.with_context(|| format!("while loading config"))?;
|
||||||
|
|
||||||
return Ok(super::Config {
|
return Ok(super::Config {
|
||||||
sprite_root: asset_root.join(self.sprite_root),
|
sprite_root: asset_root.join(self.sprite_root),
|
||||||
font_files: self
|
font_files: self
|
||||||
|
@ -71,6 +87,8 @@ pub(crate) mod syntax {
|
||||||
starfield_instance_limit,
|
starfield_instance_limit,
|
||||||
zoom_max: self.zoom_max,
|
zoom_max: self.zoom_max,
|
||||||
zoom_min: self.zoom_min,
|
zoom_min: self.zoom_min,
|
||||||
|
ui_scale: self.ui_scale,
|
||||||
|
ui_landed_scene,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,4 +171,10 @@ pub struct Config {
|
||||||
|
|
||||||
/// Maximum zoom,in game units
|
/// Maximum zoom,in game units
|
||||||
pub zoom_max: f32,
|
pub zoom_max: f32,
|
||||||
|
|
||||||
|
/// Ui scale factor
|
||||||
|
pub ui_scale: f32,
|
||||||
|
|
||||||
|
/// Ui landed scene
|
||||||
|
pub ui_landed_scene: AST,
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ pub(crate) mod outfitspace;
|
||||||
pub(crate) mod ship;
|
pub(crate) mod ship;
|
||||||
pub(crate) mod sprite;
|
pub(crate) mod sprite;
|
||||||
pub(crate) mod system;
|
pub(crate) mod system;
|
||||||
pub(crate) mod ui;
|
|
||||||
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use effect::*;
|
pub use effect::*;
|
||||||
|
@ -21,4 +20,3 @@ pub use ship::{
|
||||||
};
|
};
|
||||||
pub use sprite::*;
|
pub use sprite::*;
|
||||||
pub use system::{System, SystemObject};
|
pub use system::{System, SystemObject};
|
||||||
pub use ui::*;
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
handle::SpriteHandle, Content, ContentBuildContext, EffectHandle, OutfitHandle, OutfitSpace,
|
handle::SpriteHandle, resolve_edge_as_edge, Content, ContentBuildContext, EffectHandle,
|
||||||
SectionEdge,
|
OutfitHandle, OutfitSpace, SectionEdge,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
|
@ -299,8 +299,9 @@ impl crate::Build for Outfit {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let x = x.unwrap();
|
let x = x.unwrap();
|
||||||
let mut e = x
|
let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| {
|
||||||
.resolve_as_edge(sprite_handle, build_context, 0.0)
|
sprite.get_section_handle_by_name(x)
|
||||||
|
})
|
||||||
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
||||||
match e {
|
match e {
|
||||||
// Inherit duration from transition sequence
|
// Inherit duration from transition sequence
|
||||||
|
@ -334,8 +335,9 @@ impl crate::Build for Outfit {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let x = x.unwrap();
|
let x = x.unwrap();
|
||||||
let mut e = x
|
let mut e = resolve_edge_as_edge(&x.val, 0.0, |x| {
|
||||||
.resolve_as_edge(sprite_handle, build_context, 0.0)
|
sprite.get_section_handle_by_name(x)
|
||||||
|
})
|
||||||
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
.with_context(|| format!("in outfit `{}`", outfit_name))?;
|
||||||
match e {
|
match e {
|
||||||
// Inherit duration from transition sequence
|
// Inherit duration from transition sequence
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub(crate) mod syntax {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct Ship {
|
pub struct Ship {
|
||||||
pub sprite: String,
|
pub sprite: String,
|
||||||
|
pub thumb: String,
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
pub engines: Vec<Engine>,
|
pub engines: Vec<Engine>,
|
||||||
pub guns: Vec<Gun>,
|
pub guns: Vec<Gun>,
|
||||||
|
@ -96,6 +97,9 @@ pub struct Ship {
|
||||||
/// This ship's sprite
|
/// This ship's sprite
|
||||||
pub sprite: SpriteHandle,
|
pub sprite: SpriteHandle,
|
||||||
|
|
||||||
|
/// This ship's thumbnail
|
||||||
|
pub thumb: SpriteHandle,
|
||||||
|
|
||||||
/// The size of this ship.
|
/// The size of this ship.
|
||||||
/// Measured as unrotated height,
|
/// Measured as unrotated height,
|
||||||
/// in terms of game units.
|
/// in terms of game units.
|
||||||
|
@ -263,7 +267,7 @@ impl crate::Build for Ship {
|
||||||
ct: &mut Content,
|
ct: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (ship_name, ship) in ship {
|
for (ship_name, ship) in ship {
|
||||||
let handle = match ct.sprite_index.get(&ship.sprite) {
|
let sprite = match ct.sprite_index.get(&ship.sprite) {
|
||||||
None => bail!(
|
None => bail!(
|
||||||
"In ship `{}`: sprite `{}` doesn't exist",
|
"In ship `{}`: sprite `{}` doesn't exist",
|
||||||
ship_name,
|
ship_name,
|
||||||
|
@ -272,8 +276,17 @@ impl crate::Build for Ship {
|
||||||
Some(t) => *t,
|
Some(t) => *t,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let thumb = match ct.sprite_index.get(&ship.thumb) {
|
||||||
|
None => bail!(
|
||||||
|
"In ship `{}`: thumbnail sprite `{}` doesn't exist",
|
||||||
|
ship_name,
|
||||||
|
ship.thumb
|
||||||
|
),
|
||||||
|
Some(t) => *t,
|
||||||
|
};
|
||||||
|
|
||||||
let size = ship.size;
|
let size = ship.size;
|
||||||
let aspect = ct.get_sprite(handle).aspect;
|
let aspect = ct.get_sprite(sprite).aspect;
|
||||||
|
|
||||||
let collapse = {
|
let collapse = {
|
||||||
if let Some(c) = ship.collapse {
|
if let Some(c) = ship.collapse {
|
||||||
|
@ -415,11 +428,12 @@ impl crate::Build for Ship {
|
||||||
};
|
};
|
||||||
|
|
||||||
ct.ships.push(Self {
|
ct.ships.push(Self {
|
||||||
|
sprite,
|
||||||
|
thumb,
|
||||||
aspect,
|
aspect,
|
||||||
collapse,
|
collapse,
|
||||||
damage,
|
damage,
|
||||||
name: ship_name,
|
name: ship_name,
|
||||||
sprite: handle,
|
|
||||||
mass: ship.mass,
|
mass: ship.mass,
|
||||||
space: OutfitSpace::from(ship.space),
|
space: OutfitSpace::from(ship.space),
|
||||||
angular_drag: ship.angular_drag,
|
angular_drag: ship.angular_drag,
|
||||||
|
|
|
@ -5,8 +5,8 @@ use std::collections::HashMap;
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
use crate::{handle::SpriteHandle, Content, ContentBuildContext};
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
pub(crate) mod syntax {
|
||||||
use crate::{Content, ContentBuildContext, SpriteHandle};
|
use crate::{AnimSectionHandle, Content};
|
||||||
use anyhow::{anyhow, bail, Context, Ok, Result};
|
use anyhow::{bail, Ok, Result};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
|
@ -63,24 +63,26 @@ pub(crate) mod syntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteSection {
|
impl SpriteSection {
|
||||||
pub fn add_to(
|
pub fn add_to<F>(
|
||||||
&self,
|
&self,
|
||||||
this_sprite: SpriteHandle,
|
ct: &mut Content,
|
||||||
build_context: &mut ContentBuildContext,
|
get_handle: F,
|
||||||
content: &mut Content,
|
) -> Result<((u32, u32), super::SpriteSection)>
|
||||||
) -> Result<((u32, u32), super::SpriteSection)> {
|
where
|
||||||
|
F: Fn(&str) -> Option<AnimSectionHandle>,
|
||||||
|
{
|
||||||
// Make sure all frames have the same size and add them
|
// Make sure all frames have the same size and add them
|
||||||
// to the frame vector
|
// to the frame vector
|
||||||
let mut dim = None;
|
let mut dim = None;
|
||||||
let mut frames: Vec<u32> = Vec::new();
|
let mut frames: Vec<u32> = Vec::new();
|
||||||
for f in &self.frames {
|
for f in &self.frames {
|
||||||
let idx = match content.sprite_atlas.get_idx_by_path(f) {
|
let idx = match ct.sprite_atlas.get_idx_by_path(f) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => {
|
None => {
|
||||||
bail!("error: file `{}` isn't in the sprite atlas", f.display());
|
bail!("error: file `{}` isn't in the sprite atlas", f.display());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let img = &content.sprite_atlas.get_by_idx(idx);
|
let img = &ct.sprite_atlas.get_by_idx(idx);
|
||||||
|
|
||||||
match dim {
|
match dim {
|
||||||
None => dim = Some(img.true_size),
|
None => dim = Some(img.true_size),
|
||||||
|
@ -110,12 +112,12 @@ pub(crate) mod syntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
let edge_top = match &self.top {
|
let edge_top = match &self.top {
|
||||||
Some(x) => x.resolve_as_edge(this_sprite, build_context, frame_duration)?,
|
Some(x) => super::resolve_edge_as_edge(&x.val, frame_duration, &get_handle)?,
|
||||||
None => super::SectionEdge::Stop,
|
None => super::SectionEdge::Stop,
|
||||||
};
|
};
|
||||||
|
|
||||||
let edge_bot = match &self.bot {
|
let edge_bot = match &self.bot {
|
||||||
Some(x) => x.resolve_as_edge(this_sprite, build_context, frame_duration)?,
|
Some(x) => super::resolve_edge_as_edge(&x.val, frame_duration, &get_handle)?,
|
||||||
None => super::SectionEdge::Stop,
|
None => super::SectionEdge::Stop,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -137,77 +139,6 @@ pub(crate) mod syntax {
|
||||||
pub struct SectionEdge {
|
pub struct SectionEdge {
|
||||||
pub val: String,
|
pub val: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SectionEdge {
|
|
||||||
pub fn resolve_as_start(
|
|
||||||
&self,
|
|
||||||
sprite: SpriteHandle,
|
|
||||||
build_context: &ContentBuildContext,
|
|
||||||
) -> Result<super::StartEdge> {
|
|
||||||
let e = self
|
|
||||||
.resolve_as_edge(sprite, build_context, 0.0)
|
|
||||||
.with_context(|| format!("while resolving start edge"))?;
|
|
||||||
match e {
|
|
||||||
super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }),
|
|
||||||
super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { section }),
|
|
||||||
_ => {
|
|
||||||
bail!("bad section start specification `{}`", self.val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_as_edge(
|
|
||||||
&self,
|
|
||||||
sprite: SpriteHandle,
|
|
||||||
build_context: &ContentBuildContext,
|
|
||||||
duration: f32,
|
|
||||||
) -> Result<super::SectionEdge> {
|
|
||||||
let all_sections = build_context.sprite_section_index.get(&sprite).unwrap();
|
|
||||||
|
|
||||||
if self.val == "hidden" {
|
|
||||||
return Ok(super::SectionEdge::Top {
|
|
||||||
section: crate::AnimSectionHandle::Hidden,
|
|
||||||
duration,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.val == "stop" {
|
|
||||||
return Ok(super::SectionEdge::Stop);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.val == "reverse" {
|
|
||||||
return Ok(super::SectionEdge::Reverse { duration });
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.val == "repeat" {
|
|
||||||
return Ok(super::SectionEdge::Repeat { duration });
|
|
||||||
}
|
|
||||||
|
|
||||||
let (s, p) = match self.val.split_once(":") {
|
|
||||||
Some(x) => x,
|
|
||||||
None => {
|
|
||||||
bail!("bad section edge specification `{}`", self.val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let section = match all_sections.get(s) {
|
|
||||||
Some(s) => *s,
|
|
||||||
None => {
|
|
||||||
return Err(anyhow!("bad section edge specification `{}`", self.val))
|
|
||||||
.with_context(|| format!("section `{}` doesn't exist", s));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match p {
|
|
||||||
"top" => Ok(super::SectionEdge::Top { section, duration }),
|
|
||||||
"bot" => Ok(super::SectionEdge::Bot { section, duration }),
|
|
||||||
_ => {
|
|
||||||
return Err(anyhow!("bad section edge specification `{}`", self.val))
|
|
||||||
.with_context(|| format!("invalid target `{}`", p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A handle for an animation section inside a sprite
|
/// A handle for an animation section inside a sprite
|
||||||
|
@ -312,6 +243,9 @@ pub struct Sprite {
|
||||||
/// This sprite's animation sections
|
/// This sprite's animation sections
|
||||||
sections: Vec<SpriteSection>,
|
sections: Vec<SpriteSection>,
|
||||||
|
|
||||||
|
/// Allows us to get sprite sections by name
|
||||||
|
sections_by_name: HashMap<String, AnimSectionHandle>,
|
||||||
|
|
||||||
/// Aspect ratio of this sprite (width / height)
|
/// Aspect ratio of this sprite (width / height)
|
||||||
pub aspect: f32,
|
pub aspect: f32,
|
||||||
}
|
}
|
||||||
|
@ -334,6 +268,24 @@ impl Sprite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get an animation section by name.
|
||||||
|
/// Returns None for invalid names
|
||||||
|
pub fn get_section_by_name(&self, name: &str) -> Option<&SpriteSection> {
|
||||||
|
match self.sections_by_name.get(name) {
|
||||||
|
None => return None,
|
||||||
|
Some(h) => Some(self.get_section(*h)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an animation section's handle by name.
|
||||||
|
/// Returns None for invalid names
|
||||||
|
pub fn get_section_handle_by_name(&self, name: &str) -> Option<AnimSectionHandle> {
|
||||||
|
match self.sections_by_name.get(name) {
|
||||||
|
None => return None,
|
||||||
|
Some(h) => Some(*h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the index of the texture of this sprite's first frame
|
/// Get the index of the texture of this sprite's first frame
|
||||||
pub fn get_first_frame(&self) -> u32 {
|
pub fn get_first_frame(&self) -> u32 {
|
||||||
match self.start_at {
|
match self.start_at {
|
||||||
|
@ -374,18 +326,83 @@ pub struct SpriteSection {
|
||||||
pub edge_bot: SectionEdge,
|
pub edge_bot: SectionEdge,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve an edge specification string as a StartEdge
|
||||||
|
pub fn resolve_edge_as_start<F>(s: &str, get_handle: F) -> Result<super::StartEdge>
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> Option<AnimSectionHandle>,
|
||||||
|
{
|
||||||
|
let e = resolve_edge_as_edge(s, 0.0, get_handle)
|
||||||
|
.with_context(|| format!("while resolving start edge"))?;
|
||||||
|
match e {
|
||||||
|
super::SectionEdge::Bot { section, .. } => Ok(super::StartEdge::Bot { section }),
|
||||||
|
super::SectionEdge::Top { section, .. } => Ok(super::StartEdge::Top { section }),
|
||||||
|
_ => {
|
||||||
|
bail!("bad section start specification `{}`", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve an edge specifiation string as a SectionEdge
|
||||||
|
pub fn resolve_edge_as_edge<F>(s: &str, duration: f32, get_handle: F) -> Result<super::SectionEdge>
|
||||||
|
where
|
||||||
|
F: Fn(&str) -> Option<AnimSectionHandle>,
|
||||||
|
{
|
||||||
|
if s == "hidden" {
|
||||||
|
return Ok(super::SectionEdge::Top {
|
||||||
|
section: crate::AnimSectionHandle::Hidden,
|
||||||
|
duration,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "stop" {
|
||||||
|
return Ok(super::SectionEdge::Stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "reverse" {
|
||||||
|
return Ok(super::SectionEdge::Reverse { duration });
|
||||||
|
}
|
||||||
|
|
||||||
|
if s == "repeat" {
|
||||||
|
return Ok(super::SectionEdge::Repeat { duration });
|
||||||
|
}
|
||||||
|
|
||||||
|
let (s, p) = match s.split_once(":") {
|
||||||
|
Some(x) => x,
|
||||||
|
None => {
|
||||||
|
bail!("bad section edge specification `{}`", s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let section = match get_handle(s) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow!("bad section edge specification `{}`", s))
|
||||||
|
.with_context(|| format!("section `{}` doesn't exist", s));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match p {
|
||||||
|
"top" => Ok(super::SectionEdge::Top { section, duration }),
|
||||||
|
"bot" => Ok(super::SectionEdge::Bot { section, duration }),
|
||||||
|
_ => {
|
||||||
|
return Err(anyhow!("bad section edge specification `{}`", s))
|
||||||
|
.with_context(|| format!("invalid target `{}`", p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl crate::Build for Sprite {
|
impl crate::Build for Sprite {
|
||||||
type InputSyntaxType = HashMap<String, syntax::Sprite>;
|
type InputSyntaxType = HashMap<String, syntax::Sprite>;
|
||||||
|
|
||||||
fn build(
|
fn build(
|
||||||
sprites: Self::InputSyntaxType,
|
sprites: Self::InputSyntaxType,
|
||||||
build_context: &mut ContentBuildContext,
|
_build_context: &mut ContentBuildContext,
|
||||||
content: &mut Content,
|
ct: &mut Content,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
for (sprite_name, t) in sprites {
|
for (sprite_name, t) in sprites {
|
||||||
match t {
|
match t {
|
||||||
syntax::Sprite::Static(t) => {
|
syntax::Sprite::Static(t) => {
|
||||||
let idx = match content.sprite_atlas.get_idx_by_path(&t.file) {
|
let idx = match ct.sprite_atlas.get_idx_by_path(&t.file) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => {
|
None => {
|
||||||
return Err(
|
return Err(
|
||||||
|
@ -399,19 +416,16 @@ impl crate::Build for Sprite {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let img = &content.sprite_atlas.get_by_idx(idx);
|
let img = &ct.sprite_atlas.get_by_idx(idx);
|
||||||
let aspect = img.w / img.h;
|
let aspect = img.w / img.h;
|
||||||
|
|
||||||
let h = SpriteHandle {
|
let h = SpriteHandle {
|
||||||
index: content.sprites.len(),
|
index: ct.sprites.len(),
|
||||||
};
|
};
|
||||||
|
|
||||||
content.sprite_index.insert(sprite_name.clone(), h);
|
ct.sprite_index.insert(sprite_name.clone(), h);
|
||||||
let mut smap = HashMap::new();
|
|
||||||
smap.insert("anim".to_string(), AnimSectionHandle::Idx(0));
|
|
||||||
build_context.sprite_section_index.insert(h, smap);
|
|
||||||
|
|
||||||
content.sprites.push(Self {
|
ct.sprites.push(Self {
|
||||||
name: sprite_name,
|
name: sprite_name,
|
||||||
start_at: StartEdge::Top {
|
start_at: StartEdge::Top {
|
||||||
section: AnimSectionHandle::Idx(0),
|
section: AnimSectionHandle::Idx(0),
|
||||||
|
@ -424,41 +438,42 @@ impl crate::Build for Sprite {
|
||||||
edge_top: SectionEdge::Stop,
|
edge_top: SectionEdge::Stop,
|
||||||
edge_bot: SectionEdge::Stop,
|
edge_bot: SectionEdge::Stop,
|
||||||
}],
|
}],
|
||||||
|
sections_by_name: HashMap::new(),
|
||||||
handle: h,
|
handle: h,
|
||||||
aspect,
|
aspect,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
syntax::Sprite::OneSection(s) => {
|
syntax::Sprite::OneSection(s) => {
|
||||||
let mut section_names: HashMap<String, _> = HashMap::new();
|
|
||||||
// Name the one section in this sprite "anim"
|
|
||||||
section_names.insert("anim".to_owned(), AnimSectionHandle::Idx(0));
|
|
||||||
|
|
||||||
let sprite_handle = SpriteHandle {
|
let sprite_handle = SpriteHandle {
|
||||||
index: content.sprites.len(),
|
index: ct.sprites.len(),
|
||||||
};
|
};
|
||||||
content
|
ct.sprite_index.insert(sprite_name.clone(), sprite_handle);
|
||||||
.sprite_index
|
|
||||||
.insert(sprite_name.clone(), sprite_handle);
|
|
||||||
let mut smap = HashMap::new();
|
|
||||||
smap.insert("anim".to_string(), AnimSectionHandle::Idx(0));
|
|
||||||
build_context
|
|
||||||
.sprite_section_index
|
|
||||||
.insert(sprite_handle, smap);
|
|
||||||
|
|
||||||
let (dim, section) = s
|
let (dim, section) = s
|
||||||
.add_to(sprite_handle, build_context, content)
|
.add_to(ct, |s| {
|
||||||
|
if s == "anim" {
|
||||||
|
Some(AnimSectionHandle::Idx(0))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
||||||
let aspect = dim.0 as f32 / dim.1 as f32;
|
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||||
|
|
||||||
let mut sections = Vec::new();
|
let mut sections = Vec::new();
|
||||||
sections.push(section);
|
sections.push(section);
|
||||||
|
|
||||||
content.sprites.push(Self {
|
ct.sprites.push(Self {
|
||||||
name: sprite_name,
|
name: sprite_name,
|
||||||
sections,
|
sections,
|
||||||
start_at: StartEdge::Top {
|
start_at: StartEdge::Top {
|
||||||
section: AnimSectionHandle::Idx(0),
|
section: AnimSectionHandle::Idx(0),
|
||||||
},
|
},
|
||||||
|
sections_by_name: {
|
||||||
|
let mut h = HashMap::new();
|
||||||
|
h.insert("anim".to_string(), AnimSectionHandle::Idx(0));
|
||||||
|
h
|
||||||
|
},
|
||||||
handle: sprite_handle,
|
handle: sprite_handle,
|
||||||
aspect,
|
aspect,
|
||||||
});
|
});
|
||||||
|
@ -471,18 +486,12 @@ impl crate::Build for Sprite {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sprite_handle = SpriteHandle {
|
let sprite_handle = SpriteHandle {
|
||||||
index: content.sprites.len(),
|
index: ct.sprites.len(),
|
||||||
};
|
};
|
||||||
content
|
ct.sprite_index.insert(sprite_name.clone(), sprite_handle);
|
||||||
.sprite_index
|
|
||||||
.insert(sprite_name.clone(), sprite_handle);
|
|
||||||
build_context
|
|
||||||
.sprite_section_index
|
|
||||||
.insert(sprite_handle, section_names.clone());
|
|
||||||
|
|
||||||
let start_at = s
|
let start_at =
|
||||||
.start_at
|
resolve_edge_as_start(&s.start_at.val, |x| section_names.get(x).copied())
|
||||||
.resolve_as_start(sprite_handle, build_context)
|
|
||||||
.with_context(|| format!("while loading sprite `{}`", sprite_name))?;
|
.with_context(|| format!("while loading sprite `{}`", sprite_name))?;
|
||||||
|
|
||||||
let mut sections = Vec::with_capacity(section_names.len());
|
let mut sections = Vec::with_capacity(section_names.len());
|
||||||
|
@ -495,7 +504,7 @@ impl crate::Build for Sprite {
|
||||||
for (k, _) in names {
|
for (k, _) in names {
|
||||||
let v = s.section.get(k).unwrap();
|
let v = s.section.get(k).unwrap();
|
||||||
let (d, s) = v
|
let (d, s) = v
|
||||||
.add_to(sprite_handle, build_context, content)
|
.add_to(ct, |x| section_names.get(x).copied())
|
||||||
.with_context(|| format!("while parsing section `{}`", k))
|
.with_context(|| format!("while parsing section `{}`", k))
|
||||||
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
.with_context(|| format!("while parsing sprite `{}`", sprite_name))?;
|
||||||
|
|
||||||
|
@ -515,11 +524,12 @@ impl crate::Build for Sprite {
|
||||||
let dim = dim.unwrap();
|
let dim = dim.unwrap();
|
||||||
let aspect = dim.0 as f32 / dim.1 as f32;
|
let aspect = dim.0 as f32 / dim.1 as f32;
|
||||||
|
|
||||||
content.sprites.push(Self {
|
ct.sprites.push(Self {
|
||||||
name: sprite_name,
|
name: sprite_name,
|
||||||
sections,
|
sections,
|
||||||
start_at,
|
start_at,
|
||||||
handle: sprite_handle,
|
handle: sprite_handle,
|
||||||
|
sections_by_name: section_names,
|
||||||
aspect,
|
aspect,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ fn resolve_coordinates(
|
||||||
if sum.is_empty() {
|
if sum.is_empty() {
|
||||||
a.to_string()
|
a.to_string()
|
||||||
} else {
|
} else {
|
||||||
sum + " -> " + a
|
format!("{sum} -> {a}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,342 +0,0 @@
|
||||||
use anyhow::{Context, Result};
|
|
||||||
use nalgebra::{Point2, Vector2};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{handle::SpriteHandle, Content, ContentBuildContext, SectionEdge};
|
|
||||||
|
|
||||||
pub(crate) mod syntax {
|
|
||||||
use crate::{sprite::syntax::SectionEdge, Content, ContentBuildContext};
|
|
||||||
use anyhow::{bail, Context, Result};
|
|
||||||
use nalgebra::{Point2, Vector2};
|
|
||||||
use serde::Deserialize;
|
|
||||||
// Raw serde syntax structs.
|
|
||||||
// These are never seen by code outside this crate.
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct Ui {
|
|
||||||
pub landed: UiLanded,
|
|
||||||
pub outfitter: UiOutfitter,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct UiLanded {
|
|
||||||
pub frame: UiSprite,
|
|
||||||
pub landscape: UiSprite,
|
|
||||||
pub button: UiSprite,
|
|
||||||
pub planet_name: UiText,
|
|
||||||
pub planet_desc: UiText,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct UiOutfitter {
|
|
||||||
pub se_box: UiSprite,
|
|
||||||
pub exit_button: UiSprite,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct UiRect {
|
|
||||||
pub pos: [f32; 2],
|
|
||||||
pub dim: [f32; 2],
|
|
||||||
pub anchor_self: super::UiPositionAnchor,
|
|
||||||
pub anchor_parent: super::UiPositionAnchor,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct EdgeSpec {
|
|
||||||
pub edge: SectionEdge,
|
|
||||||
pub duration: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct UiText {
|
|
||||||
pub rect: UiRect,
|
|
||||||
pub font_size: f32,
|
|
||||||
pub line_height: f32,
|
|
||||||
pub align: super::UiTextAlign,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiText {
|
|
||||||
pub fn build(
|
|
||||||
self,
|
|
||||||
_build_context: &ContentBuildContext,
|
|
||||||
_ct: &Content,
|
|
||||||
) -> Result<super::UiTextConfig> {
|
|
||||||
let rect = {
|
|
||||||
super::UiRect {
|
|
||||||
pos: Point2::new(self.rect.pos[0], self.rect.pos[1]),
|
|
||||||
dim: Vector2::new(self.rect.dim[0], self.rect.dim[1]),
|
|
||||||
anchor_self: self.rect.anchor_self,
|
|
||||||
anchor_parent: self.rect.anchor_parent,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(super::UiTextConfig {
|
|
||||||
rect,
|
|
||||||
font_size: self.font_size,
|
|
||||||
line_height: self.line_height,
|
|
||||||
align: self.align,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
pub struct UiSprite {
|
|
||||||
pub sprite: Option<String>,
|
|
||||||
pub rect: UiRect,
|
|
||||||
pub mask: Option<String>,
|
|
||||||
pub on_mouse_enter: Option<EdgeSpec>,
|
|
||||||
pub on_mouse_leave: Option<EdgeSpec>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UiSprite {
|
|
||||||
pub fn build(
|
|
||||||
self,
|
|
||||||
build_context: &ContentBuildContext,
|
|
||||||
ct: &Content,
|
|
||||||
|
|
||||||
// If true, fail if self.sprite is missing.
|
|
||||||
// If false, fail if self.sprite exists.
|
|
||||||
// This is false for sprites that may change---for example, planet landscapes
|
|
||||||
should_have_sprite: bool,
|
|
||||||
) -> Result<super::UiSpriteConfig> {
|
|
||||||
let sprite = {
|
|
||||||
if should_have_sprite {
|
|
||||||
if self.sprite.is_none() {
|
|
||||||
bail!("no sprite given, but expected a value")
|
|
||||||
}
|
|
||||||
match ct.sprite_index.get(self.sprite.as_ref().unwrap()) {
|
|
||||||
None => bail!("ui sprite `{}` doesn't exist", self.sprite.unwrap()),
|
|
||||||
Some(t) => Some(*t),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if self.sprite.is_some() {
|
|
||||||
bail!("got a sprite, but didn't expect one")
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mask = if let Some(mask) = self.mask {
|
|
||||||
Some(match ct.sprite_index.get(&mask) {
|
|
||||||
None => bail!("mask `{}` doesn't exist", mask),
|
|
||||||
Some(t) => *t,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let on_mouse_enter = {
|
|
||||||
if let Some(x) = self.on_mouse_enter {
|
|
||||||
if sprite.is_none() {
|
|
||||||
bail!("got `on_mouse_enter` on a ui element with no fixed sprite")
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(
|
|
||||||
x.edge
|
|
||||||
.resolve_as_edge(sprite.unwrap(), build_context, x.duration)
|
|
||||||
.with_context(|| format!("failed to resolve mouse enter edge"))?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let on_mouse_leave = {
|
|
||||||
if let Some(x) = self.on_mouse_leave {
|
|
||||||
if sprite.is_none() {
|
|
||||||
bail!("got `on_mouse_leave` on a ui element with no fixed sprite")
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(
|
|
||||||
x.edge
|
|
||||||
.resolve_as_edge(sprite.unwrap(), build_context, x.duration)
|
|
||||||
.with_context(|| format!("failed to resolve mouse leave edge"))?,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let rect = {
|
|
||||||
super::UiRect {
|
|
||||||
pos: Point2::new(self.rect.pos[0], self.rect.pos[1]),
|
|
||||||
dim: Vector2::new(self.rect.dim[0], self.rect.dim[1]),
|
|
||||||
anchor_self: self.rect.anchor_self,
|
|
||||||
anchor_parent: self.rect.anchor_parent,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(super::UiSpriteConfig {
|
|
||||||
sprite,
|
|
||||||
mask,
|
|
||||||
on_mouse_enter,
|
|
||||||
on_mouse_leave,
|
|
||||||
rect,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How to align text in a text box
|
|
||||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
|
||||||
pub enum UiTextAlign {
|
|
||||||
/// Center-align
|
|
||||||
#[serde(rename = "center")]
|
|
||||||
Center,
|
|
||||||
|
|
||||||
/// Left-align
|
|
||||||
#[serde(rename = "left")]
|
|
||||||
Left,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// How to position a UI sprite
|
|
||||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
|
||||||
pub enum UiPositionAnchor {
|
|
||||||
/// Anchored at center
|
|
||||||
#[serde(rename = "center")]
|
|
||||||
Center,
|
|
||||||
|
|
||||||
/// Anchored at top-left
|
|
||||||
#[serde(rename = "northwest")]
|
|
||||||
NorthWest,
|
|
||||||
|
|
||||||
/// Anchored at top-right
|
|
||||||
#[serde(rename = "northeast")]
|
|
||||||
NorthEast,
|
|
||||||
|
|
||||||
/// Anchored at bottom-left
|
|
||||||
#[serde(rename = "southwest")]
|
|
||||||
SouthWest,
|
|
||||||
|
|
||||||
/// Anchored at bottom-right
|
|
||||||
#[serde(rename = "southeast")]
|
|
||||||
SouthEast,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// UI Configuration
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Ui {
|
|
||||||
/// Landed interface frame
|
|
||||||
pub landed_frame: UiSpriteConfig,
|
|
||||||
|
|
||||||
/// Landed interface image
|
|
||||||
pub landed_landscape: UiSpriteConfig,
|
|
||||||
|
|
||||||
/// Test button
|
|
||||||
pub landed_button: UiSpriteConfig,
|
|
||||||
|
|
||||||
/// Landed planet name
|
|
||||||
pub landed_planet_name: UiTextConfig,
|
|
||||||
|
|
||||||
/// Landed planet description
|
|
||||||
pub landed_planet_desc: UiTextConfig,
|
|
||||||
|
|
||||||
/// Outfitter exit button
|
|
||||||
pub outfitter_exit_button: UiSpriteConfig,
|
|
||||||
|
|
||||||
/// Outfitter south-east box
|
|
||||||
pub outfitter_se_box: UiSpriteConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A UI sprite's position
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct UiRect {
|
|
||||||
/// The position of the center of this sprite, in logical pixels,
|
|
||||||
/// with 0, 0 at the center of the screen
|
|
||||||
pub pos: Point2<f32>,
|
|
||||||
|
|
||||||
/// This sprite's w and h, in logical pixels.
|
|
||||||
pub dim: Vector2<f32>,
|
|
||||||
|
|
||||||
/// The point on this sprite that pos is anchored to
|
|
||||||
pub anchor_self: UiPositionAnchor,
|
|
||||||
|
|
||||||
/// The point on the parent that pos is relative to
|
|
||||||
pub anchor_parent: UiPositionAnchor,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A single UI sprite instance
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct UiSpriteConfig {
|
|
||||||
/// The sprite to show
|
|
||||||
pub sprite: Option<SpriteHandle>,
|
|
||||||
|
|
||||||
/// The mask to use
|
|
||||||
pub mask: Option<SpriteHandle>,
|
|
||||||
|
|
||||||
/// This sprite's position and size
|
|
||||||
pub rect: UiRect,
|
|
||||||
|
|
||||||
/// Animation edge to take when mouse enters this sprite
|
|
||||||
pub on_mouse_enter: Option<SectionEdge>,
|
|
||||||
|
|
||||||
/// Animation edge to take when mouse leaves this sprite
|
|
||||||
pub on_mouse_leave: Option<SectionEdge>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A UI text box
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct UiTextConfig {
|
|
||||||
/// Text box location and dimensions
|
|
||||||
pub rect: UiRect,
|
|
||||||
|
|
||||||
/// Text box font size
|
|
||||||
pub font_size: f32,
|
|
||||||
|
|
||||||
/// Text box line height
|
|
||||||
pub line_height: f32,
|
|
||||||
|
|
||||||
/// Text box alignment
|
|
||||||
pub align: UiTextAlign,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl crate::Build for Ui {
|
|
||||||
type InputSyntaxType = syntax::Ui;
|
|
||||||
|
|
||||||
fn build(
|
|
||||||
ui: Self::InputSyntaxType,
|
|
||||||
build_context: &mut ContentBuildContext,
|
|
||||||
ct: &mut Content,
|
|
||||||
) -> Result<()> {
|
|
||||||
ct.ui = Some(Ui {
|
|
||||||
landed_frame: ui
|
|
||||||
.landed
|
|
||||||
.frame
|
|
||||||
.build(build_context, ct, true)
|
|
||||||
.with_context(|| format!("in ui config (landed_frame)"))?,
|
|
||||||
landed_landscape: ui
|
|
||||||
.landed
|
|
||||||
.landscape
|
|
||||||
.build(build_context, ct, false)
|
|
||||||
.with_context(|| format!("in ui config (landed_landscape)"))?,
|
|
||||||
landed_button: ui
|
|
||||||
.landed
|
|
||||||
.button
|
|
||||||
.build(build_context, ct, true)
|
|
||||||
.with_context(|| format!("in ui config (landed_button)"))?,
|
|
||||||
landed_planet_name: ui
|
|
||||||
.landed
|
|
||||||
.planet_name
|
|
||||||
.build(build_context, ct)
|
|
||||||
.with_context(|| format!("in ui config (landed_planet_name)"))?,
|
|
||||||
landed_planet_desc: ui
|
|
||||||
.landed
|
|
||||||
.planet_desc
|
|
||||||
.build(build_context, ct)
|
|
||||||
.with_context(|| format!("in ui config (landed_planet_desc)"))?,
|
|
||||||
|
|
||||||
outfitter_exit_button: ui
|
|
||||||
.outfitter
|
|
||||||
.exit_button
|
|
||||||
.build(build_context, ct, true)
|
|
||||||
.with_context(|| format!("in ui config (outfitter_exit_button)"))?,
|
|
||||||
outfitter_se_box: ui
|
|
||||||
.outfitter
|
|
||||||
.se_box
|
|
||||||
.build(build_context, ct, true)
|
|
||||||
.with_context(|| format!("in ui config (outfitter_se_box)"))?,
|
|
||||||
});
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -247,7 +247,7 @@ impl SpriteAutomaton {
|
||||||
// Edge case: we're stopped and got a request to transition.
|
// Edge case: we're stopped and got a request to transition.
|
||||||
// we should transition right away.
|
// we should transition right away.
|
||||||
|
|
||||||
if let Some(e) = self.next_edge_override {
|
if let Some(e) = self.next_edge_override.take() {
|
||||||
self.take_edge(ct, e);
|
self.take_edge(ct, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue