Skip to main content

yog_formats/
def.rs

1//! `.ydef` — definitions: an entry of some kind (`ladder`, `level`,
2//! `sound_group`, …), named elsewhere by reference (`@<kind>:<id>`). No
3//! vocabulary is a closed list: the game's values are built-in definitions
4//! of the same kinds.
5
6use yog_data::Ordered;
7use yog_data::Schema;
8use yog_data::with::{Opt, RefTo};
9pub use yog_registry::{ArmorMaterial, SoundGroup, ToolMaterial};
10
11yog_data::kind! {
12    AdvancementKind = yog_ids::kind::ADVANCEMENT,
13    LadderKind = yog_ids::kind::LADDER,
14    LevelKind = yog_ids::kind::LEVEL,
15    SoundGroupKind = yog_ids::kind::SOUND_GROUP,
16    ToolMaterialKind = yog_ids::kind::TOOL_MATERIAL,
17    ArmorMaterialKind = yog_ids::kind::ARMOR_MATERIAL,
18}
19
20/// A `.ydef`: an entry of some kind of definition, named by reference
21/// (`@<kind>:<id>`) everywhere else. Kinds: `ladder` (an ordered scale),
22/// `level` (a step on one), `sound_group` (what a block sounds like),
23/// `recipe_kind` (a mod's kind of recipe; how it's shown is a `.yui`
24/// `recipe_layout`, see [`crate::book::layout`]), `tool_material`,
25/// `armor_material`, `ore`, `dimension`, `table`, `tag` (content of one
26/// kind grouped under a name) and `damage_type`.
27#[derive(Schema, Debug, Clone)]
28#[data(tag = "kind")]
29pub enum Def {
30    /// An ordered scale — tool tiers (`minecraft:tool`, built in), a mod's
31    /// voltages, rarities, magic circles…
32    Ladder { id: String },
33    /// A step on a ladder, just above another (the bottom if none).
34    Level {
35        id: String,
36        #[data(with = "RefTo<LadderKind>")]
37        ladder: String,
38        #[data(with = "Opt<RefTo<LevelKind>>")]
39        above: Option<String>,
40    },
41    /// What blocks naming it (`sound: "@sound_group:mymod:crystal"`) sound
42    /// like: five sound events and a volume and pitch. The game's groups
43    /// are built-in ones (`stone`, `amethyst`…).
44    SoundGroup(SoundGroup),
45    /// A mod's own kind of recipe (`mymod:infusion`): its slots and values,
46    /// checked in every recipe of the kind.
47    RecipeKind(RecipeKindDef),
48    /// What tools are made of (`@tool_material:mymod:mythril`); the game's
49    /// are built in (`iron`, `diamond`…).
50    ToolMaterial(ToolMaterialDef),
51    /// What armor is made of (`@armor_material:mymod:mythril`); the game's
52    /// are built in (`iron`, `diamond`…).
53    ArmorMaterial(ArmorMaterialDef),
54    /// An ore in the world: veins of the mod's blocks in place of stone,
55    /// in the biomes it says — the overworld's or any dimension's.
56    Ore(OreDef),
57    /// A dimension: its type (sky, light, height…), its biome, and its
58    /// ground as data (`terrain`, see `yog_worldgen`) — or a generator the
59    /// mod's code registers (`generator`).
60    Dimension(DimensionDef),
61    /// A mod's own table — tiers, materials, voltages…: rows of the columns
62    /// it declares, which `yog build` makes an enum in the mod's code.
63    Table(TableDef),
64    /// A tag: content of one kind under a name (`hexcasting:water_plants`,
65    /// blocks), or more of it added to another's (`minecraft:logs`).
66    Tag(TagDef),
67    /// A kind of damage (`hexcasting:overcast`): how it's scaled, what it
68    /// exhausts, its death message, the tags it's in (`minecraft:bypasses_armor`).
69    DamageType(DamageTypeDef),
70    /// A tree the world (or a sapling, a spell) grows: its trunk and leaves.
71    Tree(TreeDef),
72    /// Changes to the loot of some tables (chests, block drops, mobs):
73    /// items added — how many, at what chance, finished by a loot function
74    /// of the mod's code — and items scaled.
75    LootModifier(yog_registry::LootModifier),
76    /// A loot table of the mod's own (`hexcasting:random_scroll`, for `/loot`
77    /// and other tables to roll): what it gives, as a loot modifier's `add`.
78    LootTable(LootTableDef),
79    /// A number every player has, which effects change
80    /// (`hexcasting:grid_zoom`).
81    Attribute(yog_registry::AttributeDef),
82    /// A mob effect: good or bad, its color, the attributes it changes.
83    Effect(yog_registry::EffectDef),
84    /// An advancement: where it hangs, how it looks, what earns it.
85    Advancement(AdvancementDef),
86}
87
88/// `{ kind: "advancement", id: "lore/cardamom1", parent: "@advancement:hexcasting:lore",
89/// icon: "@item:hexcasting:lore_fragment", title: "@Lang.…", hidden: true,
90/// criteria: [{ granted: true }] }` — the game's advancement, its JSON for
91/// every version.
92#[derive(Schema, Debug, Clone)]
93pub struct AdvancementDef {
94    /// `lore/cardamom1` (the mod's).
95    pub id: String,
96    /// The one it hangs under; a tab's root has none.
97    #[data(with = "Opt<RefTo<AdvancementKind>>")]
98    pub parent: Option<String>,
99    #[data(with = "RefTo<yog_data::kinds::Item>")]
100    pub icon: String,
101    /// A `.yres` text (`@Lang.…`), a translation key or words.
102    pub title: yog_data::Text,
103    pub description: Option<yog_data::Text>,
104    /// `task` (default), `goal`, `challenge`.
105    #[data(default = "task_frame")]
106    pub frame: String,
107    /// Not shown before it's had.
108    #[data(default)]
109    pub hidden: bool,
110    /// A root's background texture (`minecraft:textures/block/calcite.png`).
111    pub background: Option<String>,
112    /// What earns it — every one of them.
113    pub criteria: Vec<CriterionDef>,
114}
115
116fn task_frame() -> String {
117    yog_ids::advancement::TASK.into()
118}
119
120/// One way to earn an advancement — one of: `granted` (the mod's code
121/// awards it), `has_item` (an item or a tag of them in the inventory),
122/// `used_item` (eaten, drunk, used up).
123#[derive(Schema, Debug, Clone, Default)]
124pub struct CriterionDef {
125    #[data(default)]
126    pub granted: bool,
127    pub has_item: Option<yog_data::ItemIdOrTag>,
128    pub used_item: Option<yog_data::ItemId>,
129}
130
131/// `{ kind: "loot_table", id: "random_scroll", add: [{ item: "@item:…",
132/// function: "mymod:…" }] }`.
133#[derive(Schema, Debug, Clone)]
134pub struct LootTableDef {
135    /// `random_scroll` (the mod's).
136    pub id: String,
137    pub add: Vec<yog_registry::LootAdd>,
138}
139
140/// `{ kind: "tree", id: "amethyst_edified_tree", trunk: { blocks: [{ block:
141/// "@block:hexcasting:edified_log", weight: 8 }, …], shape: "fancy",
142/// base_height: 5, random_a: 5, random_b: 3 }, leaves: { blocks: [{ block:
143/// "@block:hexcasting:amethyst_edified_leaves" }], shape: "fancy", radius:
144/// 1, offset: 5, height: 5 } }` — the game's `minecraft:tree` feature.
145#[derive(Schema, Debug, Clone)]
146pub struct TreeDef {
147    /// `amethyst_edified_tree` (the mod's).
148    pub id: String,
149    pub trunk: TreeTrunk,
150    pub leaves: TreeLeaves,
151    /// What the ground under it turns into (default dirt).
152    pub dirt: Option<yog_data::BlockId>,
153    /// It grows no shorter than this, clipping into what's above (the
154    /// game's `min_clipped_height`).
155    pub min_clipped_height: Option<u32>,
156    #[data(default)]
157    pub ignore_vines: bool,
158    /// The ground turns to `dirt` even where it's grass.
159    #[data(default)]
160    pub force_dirt: bool,
161}
162
163/// A trunk: its blocks, how it's shaped and how tall.
164#[derive(Schema, Debug, Clone)]
165pub struct TreeTrunk {
166    pub blocks: Vec<WeightedBlock>,
167    /// `straight`, `forking`, `giant`, `mega_jungle`, `dark_oak`, `fancy`,
168    /// `bending`, `upwards_branching`, `cherry` (the game's) or a mod's
169    /// trunk placer by id.
170    pub shape: String,
171    pub base_height: u32,
172    /// Random height added: `0..=random_a`, then `0..=random_b`.
173    #[data(default)]
174    pub random_a: u32,
175    #[data(default)]
176    pub random_b: u32,
177}
178
179/// Leaves: their blocks and the shape they're placed in.
180#[derive(Schema, Debug, Clone)]
181pub struct TreeLeaves {
182    pub blocks: Vec<WeightedBlock>,
183    /// `blob`, `spruce`, `pine`, `acacia`, `bush`, `fancy`, `jungle`,
184    /// `mega_pine`, `dark_oak`, `random_spread`, `cherry` (the game's) or a
185    /// mod's foliage placer by id.
186    pub shape: String,
187    pub radius: u32,
188    #[data(default)]
189    pub offset: i32,
190    /// How many layers (the shapes that have it: `blob`, `fancy`, `bush`, `jungle`).
191    pub height: Option<u32>,
192}
193
194/// A block, picked `weight` times as often as a weight-1 one.
195#[derive(Schema, Debug, Clone)]
196pub struct WeightedBlock {
197    pub block: yog_data::BlockId,
198    #[data(default = "one")]
199    pub weight: u32,
200}
201
202/// `{ kind: "tag", of: "entity", id: "sticky_teleporters", values:
203/// ["@entity:minecraft:pig", "@tag:entity:c:bosses"], optional:
204/// ["@entity:create:seat"] }` — written for each game version the way it
205/// spells tags (`tags/entity_type/`, `tags/entity_types/`).
206#[derive(Schema, Debug, Clone)]
207pub struct TagDef {
208    /// The kind of content: `item`, `block`, `entity`, `fluid`, `biome`,
209    /// `damage_type` — or any of the game's registries by its name.
210    pub of: String,
211    /// `water_plants` (the mod's) or another's to add to (`minecraft:logs`).
212    pub id: String,
213    /// What's in it: `@<kind>:<id>` — checked to exist — or `@tag:<kind>:<id>`.
214    #[data(default)]
215    pub values: Vec<yog_data::AnyRefOrTag>,
216    /// What's in it if it's there — another mod's content
217    /// (`@entity:create:seat`), not checked.
218    #[data(default)]
219    pub optional: Vec<yog_data::AnyRefOrTag>,
220}
221
222fn scaled_by_mobs() -> String {
223    "when_caused_by_living_non_player".into()
224}
225
226/// `{ kind: "damage_type", id: "overcast", scaling: "never", exhaustion:
227/// 0, tags: ["minecraft:bypasses_armor"] }`.
228#[derive(Schema, Debug, Clone)]
229pub struct DamageTypeDef {
230    /// `overcast` (the mod's).
231    pub id: String,
232    /// Whether difficulty scales it: `never`, `always`,
233    /// `when_caused_by_living_non_player` (the game's default).
234    #[data(default = "scaled_by_mobs")]
235    pub scaling: String,
236    /// Hunger it costs.
237    #[data(default)]
238    pub exhaustion: f32,
239    /// Its death message is `death.attack.<message_id>` (default `<ns>.<id>`).
240    pub message_id: Option<String>,
241    /// The sound when it hurts: `hurt` (default), `thorns`, `drowning`,
242    /// `burning`, `poking`, `freezing`.
243    pub effects: Option<String>,
244    /// How its death message reads: `default`, `fall_variants`,
245    /// `intentional_game_design`.
246    pub death_message_type: Option<String>,
247    /// Damage type tags it's in (`minecraft:bypasses_armor`,
248    /// `minecraft:bypasses_effects`, a mod's).
249    #[data(default)]
250    pub tags: Vec<String>,
251}
252
253/// `{ kind: "table", id: "tier", columns: { size: "u32", chip: "item",
254/// name: "text" }, rows: { wood: { size: 16, chip: "@item:mymod:chip_wood",
255/// name: "@Strings.tier_wood" }, … } }` — in the mod's code an enum `Tier`,
256/// a variant per row (`Tier::Wood`, in order), a method per column
257/// (`Tier::Wood.size()`), `Tier::ALL`, `id()`, `from_id()` and, for an
258/// `item`, `block` or `string` column, `from_<column>()`.
259///
260/// A column is `bool`, an integer (`u8` … `u64`, `i32`, `i64`, `usize`),
261/// `f32`, `f64`, `string`, `text` (a `.yres` string, translated), `color`
262/// (a `.yres` color), `item` or `block` (`@item:`/`@block:`), a list of one
263/// of those but text (`[item]`) or a map of them (`{item: u64}`, in order).
264#[derive(Schema, Debug, Clone)]
265pub struct TableDef {
266    /// `tier`: the enum is `Tier`.
267    pub id: String,
268    pub columns: Ordered<String>,
269    pub rows: Ordered<yog_data::Value>,
270}
271
272/// `{ kind: "dimension", id: "crystal_caves", dimension_type: { has_sky_light:
273/// false }, biome: "@biome:minecraft:plains", terrain: { … } }`.
274#[derive(Schema, Debug, Clone)]
275pub struct DimensionDef {
276    /// `crystal_caves` (the mod's), or the game's to change it (`minecraft:overworld`).
277    pub id: String,
278    #[data(default)]
279    pub dimension_type: DimensionTypeProps,
280    /// The biome it's made of (default `minecraft:plains`).
281    pub biome: Option<yog_data::Id<yog_data::kinds::Biome>>,
282    /// Structures (villages…) may start in it.
283    #[data(default = "yes")]
284    pub structures: bool,
285    /// The biome's features (trees, flowers, its ores) are placed.
286    #[data(default = "yes")]
287    pub features: bool,
288    #[data(default = "yes")]
289    pub mob_spawning: bool,
290    /// Its ground, as data.
291    pub terrain: Option<yog_worldgen::Terrain>,
292    /// Or a generator the mod's code registers (`Registry::register_chunk_generator`).
293    pub generator: Option<String>,
294}
295
296fn yes() -> bool {
297    true
298}
299
300/// A dimension type's properties; what's left out is the overworld's.
301#[derive(Schema, Debug, Clone, Default)]
302pub struct DimensionTypeProps {
303    pub min_y: Option<i32>,
304    pub height: Option<i32>,
305    pub logical_height: Option<i32>,
306    pub has_sky_light: Option<bool>,
307    pub has_ceiling: Option<bool>,
308    #[data(range = "0..=1")]
309    pub ambient_light: Option<f32>,
310    pub coordinate_scale: Option<f64>,
311    pub ultrawarm: Option<bool>,
312    pub natural: Option<bool>,
313    pub beds_explode: Option<bool>,
314    pub respawn_anchors_explode: Option<bool>,
315    pub piglin_safe: Option<bool>,
316    /// The sky and fog the game draws: `minecraft:overworld`, `minecraft:the_nether`, `minecraft:the_end`.
317    pub effects: Option<String>,
318}
319
320/// `{ kind: "ore", id: "mythril_ore", targets: [{ replaces: "stone", block:
321/// "@block:mymod:mythril_ore" }, { replaces: "deepslate", block:
322/// "@block:mymod:deepslate_mythril_ore" }], size: 6, per_chunk: 4, height:
323/// { min: -48, max: 16, shape: "triangle" } }`.
324#[derive(Schema, Debug, Clone)]
325pub struct OreDef {
326    pub id: String,
327    /// What it replaces with what: one ore block for each kind of stone.
328    pub targets: Vec<OreTarget>,
329    /// Blocks in a vein, at most.
330    #[data(range = "1..=64")]
331    pub size: u32,
332    /// Veins a chunk.
333    #[data(range = "0..=256")]
334    pub per_chunk: u32,
335    pub height: OreHeight,
336    /// The chance that a block of a vein touching air isn't placed (0–1;
337    /// the game's diamonds: 0.5 for small veins).
338    #[data(default, range = "0..=1")]
339    pub air_exposure: f32,
340    /// Where: a tag of biomes — the overworld's by default.
341    pub biomes: Option<yog_data::TagId<yog_data::kinds::Biome>>,
342}
343
344/// One kind of stone an ore replaces, and the block that takes its place.
345#[derive(Schema, Debug, Clone)]
346pub struct OreTarget {
347    /// `stone` (the overworld's stones), `deepslate`, `netherrack`,
348    /// `end_stone`, a tag of blocks (`@tag:block:mymod:marble`) or a block
349    /// (`@block:minecraft:basalt`).
350    #[data(with = "OreReplaces")]
351    pub replaces: String,
352    pub block: yog_data::BlockId,
353}
354
355/// Where a vein may be: `min` to `max` blocks high, evenly (`uniform`) or
356/// mostly in the middle (`triangle`).
357#[derive(Schema, Debug, Clone)]
358pub struct OreHeight {
359    pub min: i32,
360    pub max: i32,
361    #[data(default)]
362    pub shape: HeightShape,
363}
364
365#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
366pub enum HeightShape {
367    #[default]
368    Uniform,
369    Triangle,
370}
371
372/// What an ore replaces: `#tag` or a block id, as the game's ore targets write it.
373pub struct OreReplaces;
374
375/// The game's stones by short name: what their ores replace.
376pub const ORE_STONES: [(&str, &str); 4] = [
377    ("stone", "#minecraft:stone_ore_replaceables"),
378    ("deepslate", "#minecraft:deepslate_ore_replaceables"),
379    (
380        "netherrack",
381        yog_ids::id!(yog_ids::ns::MINECRAFT, "netherrack"),
382    ),
383    (
384        "end_stone",
385        yog_ids::id!(yog_ids::ns::MINECRAFT, "end_stone"),
386    ),
387];
388
389impl yog_data::with::With<String> for OreReplaces {
390    fn shape() -> yog_data::Shape {
391        yog_data::Shape::RefOrTag(&[yog_ids::kind::BLOCK])
392    }
393    fn read(v: &yog_data::ValueRef<'_>, cx: &mut yog_data::Reader) -> Option<String> {
394        use yog_data::ValueRef;
395        match v {
396            ValueRef::Str(s) if !s.starts_with('@') => ORE_STONES.iter().find(|(n, _)| n == s).map(|(_, t)| t.to_string()).or_else(|| {
397                let names: Vec<&str> = ORE_STONES.iter().map(|(n, _)| *n).collect();
398                cx.problem(format!("`{s}`: one of {} — or a tag of blocks (`@tag:block:…`) or a block (`@block:…`)", names.join(", ")));
399                None
400            }),
401            ValueRef::Tag { .. } => yog_data::TagId::<yog_data::kinds::Block>::read(v, cx).map(|t| format!("#{}", t.id)),
402            ValueRef::Str(s) if s.starts_with("@tag:") => yog_data::TagId::<yog_data::kinds::Block>::read(v, cx).map(|t| format!("#{}", t.id)),
403            _ => yog_data::with::RefTo::<yog_data::kinds::Block>::read(v, cx),
404        }
405    }
406    fn write(f: &String) -> yog_data::Value {
407        if let Some((n, _)) = ORE_STONES.iter().find(|(_, t)| t == f) {
408            return yog_data::Value::from(*n);
409        }
410        match f.strip_prefix('#') {
411            Some(t) => yog_data::Value::Tag(yog_data::Ref::new(yog_ids::kind::BLOCK, t)),
412            None => yog_data::Value::Ref(yog_data::Ref::new(yog_ids::kind::BLOCK, f.clone())),
413        }
414    }
415}
416
417/// `{ kind: "tool_material", id: "mythril", durability: 1800, speed: 10,
418/// attack_damage: 3, enchantability: 18, level: "@level:mymod:mythril",
419/// repair: "@item:mymod:mythril_ingot" }`.
420#[derive(Schema, Debug, Clone)]
421pub struct ToolMaterialDef {
422    pub id: String,
423    /// Uses before it breaks.
424    #[data(range = "1..=1000000")]
425    pub durability: u32,
426    /// How fast it mines (wood 2, stone 4, iron 6, diamond 8, netherite 9, gold 12).
427    pub speed: f32,
428    /// Attack damage it adds (wood 0, stone 1, iron 2, diamond 3, netherite 4).
429    pub attack_damage: f32,
430    #[data(range = "0..=1000")]
431    pub enchantability: u32,
432    /// Its level on the tool ladder: the game's (`iron`) or a mod's (`@level:mymod:mythril`).
433    #[data(with = "crate::content::LevelRef")]
434    pub level: String,
435    /// What repairs it: an item or a tag of items.
436    pub repair: yog_data::ItemIdOrTag,
437}
438
439/// `{ kind: "armor_material", id: "mythril", durability: 30, defense:
440/// { head: 3, chest: 8, legs: 6, feet: 3 }, enchantability: 18,
441/// equip_sound: "minecraft:item.armor.equip_diamond", repair: "@item:mymod:mythril_ingot" }`.
442#[derive(Schema, Debug, Clone)]
443pub struct ArmorMaterialDef {
444    pub id: String,
445    /// Multiplies each piece's base durability (helmet 11, chestplate 16, leggings 15, boots 13).
446    #[data(range = "1..=10000")]
447    pub durability: u32,
448    pub defense: yog_registry::ArmorDefense,
449    #[data(range = "0..=1000")]
450    pub enchantability: u32,
451    /// The sound event of putting it on (`minecraft:item.armor.equip_iron`).
452    pub equip_sound: String,
453    #[data(default)]
454    pub toughness: f32,
455    #[data(default, range = "0..=1")]
456    pub knockback_resistance: f32,
457    /// What repairs it: an item or a tag of items.
458    pub repair: yog_data::ItemIdOrTag,
459    /// Its look on a player (`mymod:mythril`: `mymod:textures/models/armor/mythril_layer_1.png`);
460    /// its id by default.
461    pub texture: Option<String>,
462}
463
464/// Kinds of recipe are the registry's: the game's built in, a mod's here.
465pub use yog_registry::recipe_kinds::{RecipeKindDef, SlotSpec, ValueType};
466
467fn one() -> u32 {
468    1
469}
470
471#[cfg(test)]
472mod tests {
473    #[test]
474    fn every_kind_of_definition_is_a_kind_of_content() {
475        use yog_data::{Schema, Shape};
476        let Shape::Enum(e) = super::Def::shape() else {
477            panic!("a definition is an enum by its kind")
478        };
479        let kinds = yog_ids::groups()
480            .find(|(g, _)| *g == "kind")
481            .expect("yog-ids has kinds")
482            .1;
483        for v in &e.variants {
484            assert!(
485                kinds.iter().any(|(_, k)| *k == v.name),
486                "`{}` isn't in yog_ids::kind",
487                v.name
488            );
489        }
490    }
491}