Skip to main content

yog_formats/content/
block.rs

1//! A `.yblock`'s properties: shape, drops, sounds, tools, redstone.
2
3use yog_data::kinds::Item;
4use yog_data::with::{Opt, RefTo};
5use yog_data::{Reader, Schema, Shape, Value, ValueRef};
6use yog_ids::units::{BLOCK_PIXELS, MAX_STACK};
7
8use super::refs::{BlockTag, LevelRef, OneOrMany, SoundRef, ToolTag};
9
10#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq)]
11pub enum RenderLayer {
12    Solid,
13    Cutout,
14    CutoutMipped,
15    Translucent,
16}
17
18/// The collision box: the model's bounds (`auto`), a full cube, or
19/// `[x1, y1, z1, x2, y2, z2]` in pixels.
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum BlockShape {
22    Auto,
23    Full,
24    Box([f64; 6]),
25}
26
27impl Schema for BlockShape {
28    fn shape() -> Shape {
29        Shape::Any
30    }
31    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
32        match v {
33            ValueRef::Str(s) if *s == "auto" => Some(BlockShape::Auto),
34            ValueRef::Str(s) if *s == "full" => Some(BlockShape::Full),
35            ValueRef::List(_) => {
36                let b = <[f64; 6]>::read(v, cx)?;
37                if b.iter()
38                    .any(|n| !(0.0..=f64::from(BLOCK_PIXELS)).contains(n))
39                {
40                    cx.problem("a box is [x1, y1, z1, x2, y2, z2] in pixels, 0–16");
41                    return None;
42                }
43                Some(BlockShape::Box(b))
44            }
45            other => {
46                cx.expected(
47                    "\"auto\" (the model's bounds), \"full\" or [x1, y1, z1, x2, y2, z2] in pixels",
48                    other,
49                );
50                None
51            }
52        }
53    }
54    fn write(&self) -> Value {
55        match self {
56            BlockShape::Auto => Value::from("auto"),
57            BlockShape::Full => Value::from("full"),
58            BlockShape::Box(b) => b.write(),
59        }
60    }
61}
62
63/// How many drop: a number, or `[min, max]`.
64#[derive(Debug, Clone, Copy, PartialEq)]
65pub enum DropCount {
66    Exactly(f64),
67    Between(f64, f64),
68}
69
70impl Schema for DropCount {
71    fn shape() -> Shape {
72        Shape::Any
73    }
74    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
75        let n = match v {
76            ValueRef::List(_) => {
77                let [lo, hi] = <[f64; 2]>::read(v, cx)?;
78                (lo, hi, DropCount::Between(lo, hi))
79            }
80            _ => {
81                let n = f64::read(v, cx)?;
82                (n, n, DropCount::Exactly(n))
83            }
84        };
85        let stack = 0.0..=f64::from(MAX_STACK);
86        if !stack.contains(&n.0) || !stack.contains(&n.1) || n.0 > n.1 {
87            cx.problem("a count is 1–64, or [min, max] within 0–64");
88            return None;
89        }
90        Some(n.2)
91    }
92    fn write(&self) -> Value {
93        match *self {
94            DropCount::Exactly(n) => Value::Float(n),
95            DropCount::Between(a, b) => Value::List(vec![Value::Float(a), Value::Float(b)]),
96        }
97    }
98}
99
100/// A drop of an item: by default the block's own.
101#[derive(Schema, Debug, Clone)]
102pub struct DropItem {
103    #[data(with = "Opt<RefTo<Item>>")]
104    pub item: Option<String>,
105    pub count: Option<DropCount>,
106}
107
108/// What breaking the block drops: itself, nothing, or an item.
109#[derive(Debug, Clone)]
110pub enum Drops {
111    Itself,
112    Nothing,
113    Item(DropItem),
114}
115
116impl Schema for Drops {
117    fn shape() -> Shape {
118        Shape::Any
119    }
120    fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<Self> {
121        match v {
122            ValueRef::Str(s) if *s == "self" => Some(Drops::Itself),
123            ValueRef::Str(s) if *s == "none" => Some(Drops::Nothing),
124            ValueRef::Map(_) => DropItem::read(v, cx).map(Drops::Item),
125            other => {
126                cx.expected(
127                    "\"self\", \"none\" or { item: \"@item:ns:item\", count: [1, 3] }",
128                    other,
129                );
130                None
131            }
132        }
133    }
134    fn write(&self) -> Value {
135        match self {
136            Drops::Itself => Value::from("self"),
137            Drops::Nothing => Value::from("none"),
138            Drops::Item(d) => d.write(),
139        }
140    }
141}
142
143/// Who sets off a button or a pressure plate.
144#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
145pub enum TriggeredBy {
146    /// Any entity — items and arrows too (a wooden one).
147    #[default]
148    Everything,
149    /// Living things only (a stone one).
150    Mobs,
151}
152
153/// A block's properties.
154#[derive(Schema, Debug, Clone, Default)]
155pub struct BlockProps {
156    /// Seconds-ish to mine it (-1: unbreakable).
157    #[data(range = "-1..=1000000")]
158    pub hardness: Option<f64>,
159    /// Against explosions.
160    #[data(range = "0..=10000000")]
161    pub resistance: Option<f64>,
162    /// How it sounds: one of the game's sound groups (`stone`, `wood`,
163    /// `metal`, `amethyst`, `copper`…) or a mod's (`@sound_group:mymod:crystal`, a `.ydef`).
164    #[data(with = "Opt<SoundRef>")]
165    pub sound: Option<String>,
166    /// Light it gives off, 0–15.
167    #[data(range = "0..=15")]
168    pub light: Option<u8>,
169    /// The tools that mine it fast: `pickaxe`, `axe`, `shovel`, `hoe`, or
170    /// a mod's tool tag (`@tag:block:mymod:mineable/hammer`); one or a list.
171    #[data(with = "OneOrMany<ToolTag>")]
172    pub tool: Vec<BlockTag>,
173    /// The tool level it needs: `stone`, `iron`, `diamond`, `netherite`, or
174    /// any level of the tool ladder (`@level:mymod:mythril`, a `.ydef`).
175    #[data(with = "Opt<LevelRef>")]
176    pub tool_level: Option<String>,
177    /// Drops nothing without the tool.
178    #[data(default)]
179    pub requires_tool: bool,
180    /// Entities bump into it (`false`: walk through).
181    pub collision: Option<bool>,
182    #[data(range = "0..=1")]
183    pub slipperiness: Option<f64>,
184    pub shape: Option<BlockShape>,
185    pub render_layer: Option<RenderLayer>,
186    pub drops: Option<Drops>,
187    /// Blocks it connects to (`states.connect`): these groups' members.
188    #[data(default)]
189    pub connect_groups: Vec<String>,
190    /// The inventory screen it opens: a `.yui` `<Screen id>` with `<Slot>`s.
191    pub inventory: Option<String>,
192    /// Gives off redstone power, set by the mod per position.
193    #[data(default)]
194    pub redstone: bool,
195    /// A yes/no property (`states.properties`) the block keeps on while it
196    /// gets redstone power.
197    pub powered: Option<String>,
198    /// A yes/no property a comparator reads: 15 while it's on.
199    pub comparator: Option<String>,
200    /// Hoppers and droppers can put items into it; the mod takes what it
201    /// wants of them (`on_item_insert`).
202    #[data(default)]
203    pub accepts_items: bool,
204    /// It hides the faces of blocks behind it and stops light, as a full
205    /// block does (`false`: as glass; a door's default).
206    pub occludes: Option<bool>,
207    /// The block a `kind` is made from: stairs act as it, a double slab
208    /// shows its model.
209    #[data(with = "Opt<RefTo<yog_data::kinds::Block>>")]
210    pub base: Option<String>,
211    /// A `kind`'s set — the game's `oak`, `dark_oak`, `iron`, `stone`… (any
212    /// registered block set; a fence gate's wood type): its sounds and
213    /// whether a hand opens it. `oak` by default.
214    pub set: Option<String>,
215    /// How long a button stays pressed, ticks (30, as a wooden one).
216    #[data(range = "1..=1000000")]
217    pub press_ticks: Option<u32>,
218    /// Who sets off a button or a pressure plate.
219    pub triggered_by: Option<TriggeredBy>,
220}