use std::sync::LazyLock;
use serde::{Deserialize, Serialize};
use yog_data::Schema;
use yog_data::kinds::Item;
use yog_data::with::RefTo;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Schema)]
pub struct RecipeLayout {
pub kinds: Vec<String>,
pub size: [f32; 2],
#[serde(default)]
#[data(default)]
pub parts: Vec<LayoutPart>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Schema)]
#[serde(tag = "type", rename_all = "snake_case")]
#[data(tag = "type")]
pub enum LayoutPart {
Image {
source: String,
uv: [f32; 4],
#[serde(default)]
#[data(default)]
at: [f32; 2],
},
Slots {
slot: String,
at: Vec<[f32; 2]>,
#[serde(default)]
#[data(default)]
frame: bool,
},
Result {
at: [f32; 2],
#[serde(default)]
#[data(default)]
frame: bool,
},
Count { at: [f32; 2] },
Item {
#[data(with = "RefTo<Item>")]
item: String,
at: [f32; 2],
},
Text { text: String, at: [f32; 2] },
}
pub const SHEET: &str = crate::renderer::CRAFTING_SHEET;
pub const FRAME_UV: [f32; 4] = [83.0, 71.0, 24.0, 24.0];
pub static BUILT_IN: LazyLock<Vec<RecipeLayout>> = LazyLock::new(|| {
yog_data::from_text::<Vec<RecipeLayout>>(include_str!("../assets/layouts.ydata"))
.unwrap_or_else(|e| panic!("yog-book's built-in layouts: {e}"))
.value
});
pub fn find<'a>(own: &'a [RecipeLayout], kind: &str) -> Option<&'a RecipeLayout> {
own.iter()
.chain(BUILT_IN.iter())
.find(|l| l.kinds.iter().any(|k| k == kind))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_game_kind_has_a_built_in_layout() {
for kind in [
"shaped",
"shapeless",
"smelting",
"blasting",
"smoking",
"campfire_cooking",
"stonecutting",
"smithing",
] {
assert!(find(&[], kind).is_some(), "{kind}");
}
}
#[test]
fn a_mod_layout_reads_and_wins() {
let text = r#"{ kinds: ["shaped", "mymod:infusion"], size: [96, 44], parts: [
{ type: "slots", slot: "core", at: [[40, 14]], frame: true },
{ type: "item", item: "@item:minecraft:beacon", at: [0, 0] },
] }"#;
let l = yog_data::from_text::<RecipeLayout>(text).unwrap().value;
assert!(matches!(&l.parts[1], LayoutPart::Item { item, .. } if item == "minecraft:beacon"));
let own = [l];
assert_eq!(find(&own, "shaped").unwrap().size, [96.0, 44.0]);
assert_eq!(find(&own, "mymod:infusion").unwrap().kinds.len(), 2);
let e = yog_data::from_text::<RecipeLayout>(
r#"{ kinds: [], size: [1, 1], parts: [{ type: "slot", at: [] }] }"#,
)
.unwrap_err();
assert!(
e.to_string()
.contains("unknown `slot` — did you mean `slots`?"),
"{e}"
);
}
}