yog_formats/book/layout/mod.rs
1//! Recipe layouts: how a kind of recipe is shown — in books and in
2//! Yog-IDLE's recipe editor. The game's kinds have built-in layouts
3//! (Patchouli's: the crafting grid, the furnace strip, the smithing
4//! strip, …); a mod lays out its own kinds, or the game's, with a `.yui`
5//! component — drawn and arranged in the screen designer like any UI:
6//!
7//! ```xml
8//! <Component name="InfusionLayout" recipe_layout="infusion" width="96" height="44">
9//! <Image source="mymod:gui/infusion" uv="0 0 96 44"/>
10//! <RecipeSlot slot="ring" x="4" y="4" frame="true"/>
11//! <RecipeSlot slot="ring" x="4" y="24" frame="true"/>
12//! <RecipeSlot slot="core" x="40" y="14" frame="true"/>
13//! <ItemSlot item="minecraft:beacon" x="40" y="0"/>
14//! <RecipeResult x="76" y="14"/>
15//! <RecipeCount x="78" y="32"/>
16//! <Label text="{x:Bind time} ticks" x="40" y="34"/>
17//! </Component>
18//! ```
19//!
20//! `recipe_layout` names the kinds it shows (several: space-separated):
21//! the mod's own (`infusion`, its namespace added) or the game's
22//! (`shaped`, `smelting`, …). Positions are GUI pixels from the layout's
23//! top-left; an item is 16×16. A slot's places take its ingredients in
24//! order (`index` to say which). The game's kinds name their slots:
25//! `grid` (crafting, 9 places; a shaped recipe's cells by position),
26//! `input` (cooking, stonecutting), `template`/`base`/`addition`
27//! (smithing), `from`/`input` (brewing). A label's bindings are the recipe's values (`time`,
28//! `experience`, or a mod kind's `values`).
29
30use yog_sync::LazyLock;
31
32use yog_data::Schema;
33use yog_data::kinds::Item;
34use yog_data::with::RefTo;
35
36mod from_yui;
37
38pub use from_yui::{compile, is_recipe_layout};
39
40/// How one or more kinds of recipe are shown.
41#[derive(Debug, Clone, PartialEq, Schema)]
42pub struct RecipeLayout {
43 /// The recipe kinds it shows: the game's (`shaped`, `smelting`, …) or a mod's (`mymod:infusion`).
44 pub kinds: Vec<String>,
45 /// Width and height in GUI pixels.
46 pub size: [f32; 2],
47 /// What's drawn, in order.
48 #[data(default)]
49 pub parts: Vec<LayoutPart>,
50}
51
52/// One part of a [`RecipeLayout`].
53#[derive(Debug, Clone, PartialEq, Schema)]
54#[data(tag = "type")]
55pub enum LayoutPart {
56 /// A region of an image (a Yog image source).
57 Image {
58 source: String,
59 /// `[u, v, width, height]` in the image's pixels.
60 uv: [f32; 4],
61 #[data(default)]
62 at: [f32; 2],
63 },
64 /// Where a slot's ingredients go, in order.
65 Slots {
66 slot: String,
67 at: Vec<[f32; 2]>,
68 /// A slot frame behind each (for layouts without their own picture).
69 #[data(default)]
70 frame: bool,
71 },
72 /// The recipe's result.
73 Result {
74 at: [f32; 2],
75 #[data(default)]
76 frame: bool,
77 },
78 /// The result's count, when more than one.
79 Count { at: [f32; 2] },
80 /// An item drawn as it is: the station (a furnace, the crafting table).
81 Item {
82 #[data(with = "RefTo<Item>")]
83 item: String,
84 at: [f32; 2],
85 },
86 /// A block in 3D, at a slot's angle (a station that's a block: the
87 /// brewing stand with its bottles): a full block state
88 /// (`minecraft:brewing_stand[has_bottle_0=true]`).
89 Block {
90 block: String,
91 at: [f32; 2],
92 /// Its side in GUI pixels: a slot's item by default.
93 #[data(default = "icon_px")]
94 size: f32,
95 },
96 /// Text; `{name}` is the recipe's value of that name.
97 Text { text: String, at: [f32; 2] },
98}
99
100/// The game's kinds' layouts.
101pub static BUILT_IN: LazyLock<Vec<RecipeLayout>> = LazyLock::new(|| {
102 yog_data::built_in(
103 "yog-formats' built-in recipe layouts",
104 include_str!("../../../assets/layouts.ydata"),
105 )
106});
107
108/// The layout for recipes of `kind`: one of `own` (a book's), else built in.
109pub fn find<'a>(own: &'a [RecipeLayout], kind: &str) -> Option<&'a RecipeLayout> {
110 own.iter()
111 .chain(BUILT_IN.iter())
112 .find(|l| l.kinds.iter().any(|k| k == kind))
113}
114
115/// The game's kinds of recipe (those with built-in layouts).
116pub fn game_kinds() -> impl Iterator<Item = &'static str> {
117 BUILT_IN
118 .iter()
119 .flat_map(|l| l.kinds.iter().map(String::as_str))
120}
121
122/// An item's side, in GUI pixels.
123pub const ICON_PX: f32 = yog_ids::units::ITEM_PIXELS as f32;
124
125fn icon_px() -> f32 {
126 ICON_PX
127}