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