yog_cli/content.rs
1//! `.yblock` / `.yitem` content files — a mod's blocks and items: their
2//! properties, 3D model, block states and item form, in one JSON file each,
3//! edited in Yog-IDLE's 3D model editor — and `.yrecipe` files, one recipe
4//! each (crafting, shapeless, smelting), edited in its recipe editor. See
5//! Yog-IDLE's `docs/blocks-items.md` for the formats.
6//!
7//! `yog build` reads `content/**/*.yblock` and `content/**/*.yitem` and
8//!
9//! - writes Minecraft's JSON for them: block models, blockstates, item
10//! models, 1.21.4+ item definitions (`assets/<ns>/items/`), block loot
11//! tables, the `mineable/<tool>` and `needs_<level>_tool` block tags;
12//! - puts their names and tooltips into the lang files (a `@<Set>.<name>`
13//! text of a `.yres` file under the key Minecraft looks up, a literal into
14//! `en_us`);
15//! - builds the mod's textures (`.ytexture`, see `textures`);
16//! - generates `obj/generated/yog-content.g.rs` — `Blocks`/`Items`
17//! with an id constant and a `BlockDef`/`ItemDef` builder per entry and
18//! a `register` function — pulled in by `yog_api::content!();`, which
19//! also registers them (unless an entry says `"register": "manual"`) and
20//! every recipe.
21
22pub(crate) mod formats;
23pub(crate) mod media;
24pub(crate) mod pipeline;
25
26mod import;
27pub(crate) use import::font_copy_path;
28pub(crate) mod refs;
29mod vocab;
30
31pub use import::{Imported, import_command};
32pub use pipeline::compile::compile;
33pub use pipeline::outputs::{OutputSet, TAGS_FILE, TagLists, merge_outputs, read_tags};
34pub use pipeline::source::{GENERATED_PARTS_DIR, Source, write_source};
35pub(crate) use vocab::TEMPLATES;
36
37use serde_json::Value;
38use std::collections::{BTreeMap, BTreeSet};
39use std::path::{Path, PathBuf};
40use yog_ids::Format;
41
42/// Where content files live, under the mod root.
43pub const CONTENT_DIR: &str = yog_ids::project::CONTENT;
44/// The generated Rust code, under the mod root.
45pub const GENERATED_FILE: &str = yog_ids::concatcp!(
46 yog_ids::project::GENERATED,
47 yog_ids::sep::PATH,
48 "yog-content.g.rs"
49);
50
51/// The generated Yog Script, under the mod root: the content's ids by name
52/// for the mod's `.ys` (one of its sources when it's built).
53pub const GENERATED_SCRIPT: &str = yog_ids::concatcp!(
54 yog_ids::project::GENERATED,
55 yog_ids::sep::PATH,
56 "yog-content.g.",
57 yog_ids::Format::Script.ext()
58);
59
60/// A generated file: path inside the `.yog` and its bytes.
61pub(crate) type Output = (String, Vec<u8>);
62
63/// The formats a content file under `content/` is written in.
64const CONTENT: [Format; 9] = [
65 Format::Block,
66 Format::Item,
67 Format::Recipe,
68 Format::Book,
69 Format::Def,
70 Format::Model,
71 Format::Anim,
72 Format::Entity,
73 Format::Font,
74];
75
76/// The content format of the file at `path`.
77fn content_format(path: &Path) -> Option<Format> {
78 Format::of_path(path).filter(|f| CONTENT.contains(f))
79}
80
81/// Everything `yog build` makes of a mod's content files.
82#[derive(Debug, Default)]
83pub struct Compiled {
84 /// Models, blockstates, item definitions, loot tables, textures.
85 pub outputs: Vec<Output>,
86 /// Lang entries: `(namespace, locale)` → key → text, merged into the
87 /// lang files by [`merge_outputs`].
88 lang: Lang,
89 /// `.yres` text keys to copy under another key: (from, to).
90 lang_aliases: Vec<(String, String)>,
91 /// Tag path (`data/minecraft/tags/block/...json`) → its entries.
92 tags: BTreeMap<String, formats::defs::data::TagEntries>,
93 /// The generated Rust code; `None` when the mod has no content files.
94 pub source: Option<Source>,
95 /// The generated Yog Script ([`GENERATED_SCRIPT`]); `None` when the
96 /// content has no ids to name.
97 pub script: Option<String>,
98 /// Data the code includes (`include_bytes!`), by path under
99 /// `obj/generated/`: the books, as Yog Data.
100 pub data_files: Vec<(String, Vec<u8>)>,
101 /// Each block, item and entity file's (kind, id): what the mod's
102 /// Yog Script content declarations bind to.
103 pub ids: Vec<(String, String)>,
104 /// What's suspect but builds.
105 pub warnings: Vec<crate::diagnostics::Diagnostic>,
106 /// [`yog_registry::REGISTRATIONS_FILE`]: what the mod registers at the
107 /// game's start — packaged by dev builds only, for hot reload.
108 pub registrations: String,
109 /// The pictures (`.yres` images' files) an atlas took in: left out of the build.
110 pub packed: BTreeSet<String>,
111 /// Textures the game loads as files of their own as well (a tab's
112 /// background): their pictures stay in the build though packed.
113 loose: BTreeSet<String>,
114 /// How many blocks, items, recipes and books it compiled.
115 pub blocks: usize,
116 /// See [`Compiled::blocks`].
117 pub items: usize,
118 /// See [`Compiled::blocks`].
119 pub recipes: usize,
120 /// See [`Compiled::blocks`].
121 pub books: usize,
122}
123
124pub(crate) use yog_ids::{valid_id, valid_namespace, valid_path};
125
126/// Every content file (`CONTENT`) under `content/` of the mod at `root`, sorted.
127pub(crate) fn find(root: &Path) -> Vec<PathBuf> {
128 yog_analysis::files::under(&root.join(CONTENT_DIR), &|p| content_format(p).is_some())
129}
130
131fn pretty(v: &Value) -> Vec<u8> {
132 let mut s = serde_json::to_string_pretty(v).unwrap_or_default();
133 s.push('\n');
134 s.into_bytes()
135}
136
137/// The mod's lang: (namespace, locale) → key → text.
138type Lang = BTreeMap<(String, String), BTreeMap<String, String>>;
139
140#[cfg(test)]
141mod tests;
142
143/// Whole numbers below this are written as integers.
144const WHOLE_BELOW: f64 = 1e15;
145
146/// A number in the game's JSON: integral values without a fraction (`16`, not `16.0`).
147pub(crate) fn num_value(n: f64) -> serde_json::Value {
148 if n.fract() == 0.0 && n.abs() < WHOLE_BELOW {
149 serde_json::Value::from(n as i64)
150 } else {
151 serde_json::Number::from_f64(n)
152 .map(serde_json::Value::Number)
153 .unwrap_or(serde_json::Value::Null)
154 }
155}