Skip to main content

yog_registry/
blocks.rs

1//! Blocks: what a mod declares a block as.
2
3/// Which ways a block can turn.
4#[derive(yog_data::Schema, Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Facing {
6    /// North, east, south or west.
7    Horizontal,
8    /// Any of the six.
9    All,
10}
11
12/// Which way a block turns when placed.
13#[derive(yog_data::Schema, Debug, Clone, Copy, PartialEq, Eq, Default)]
14pub enum Placement {
15    /// Its front toward the player.
16    #[default]
17    Toward,
18    /// Its front the way the player looks.
19    Away,
20}
21
22/// A custom block to register; it also gets a matching block-item.
23#[derive(yog_data::Schema, Debug, Clone)]
24pub struct BlockDef {
25    pub id: String,
26    pub hardness: f32,
27    pub resistance: f32,
28    pub name: Option<String>,
29    /// Optional collision/outline box in pixel units (0–16): `[x1,y1,z1,x2,y2,z2]`.
30    /// `None` = full cube.
31    pub shape: Option<[f32; 6]>,
32    /// Light emitted by this block (0 = none, 15 = max, like a torch).
33    pub light_level: u8,
34    /// Its [`SoundGroup`]: one of the game's by name (`"stone"`,
35    /// `"amethyst"`, `"copper"` — [`SoundGroup::built_in`]) or a mod's
36    /// (`"mymod:crystal"`). `None` = stone (Minecraft default).
37    pub sound: Option<String>,
38    /// If `true`, the correct tool (from the block's tags) is required for drops.
39    pub requires_tool: bool,
40    /// If `true`, entities pass through this block (like flowers or torches).
41    pub no_collision: bool,
42    /// Friction coefficient. `0.0` = default (0.6). Ice = 0.989.
43    pub slipperiness: f32,
44    /// If `true`, this block dynamically grows arms toward neighbors it's
45    /// compatible with (fence/pipe-style): the Java host tracks N/S/E/W/U/D
46    /// boolean blockstate properties, recomputed on placement and on
47    /// neighbor change, and grows the collision/outline shape from the
48    /// `shape` core box (or a default post) out to each connected side.
49    pub connects: bool,
50    /// Connection compatibility tags (configured in code via
51    /// `.connect_groups(&[...])` — see that method's docs for how this
52    /// drives which blocks link up). Independent of `connects`: a block can
53    /// carry tags purely as a connection *target* (e.g. an ALU accepting a
54    /// Digital Cable) without dynamically growing its own shape.
55    pub connect_groups: Vec<String>,
56    /// Id of a `YogInventoryDef` this block is backed by — `None` = plain
57    /// block (default). See `yog_inventory`'s DESIGN.md.
58    pub inventory_id: Option<String>,
59    /// Gives off redstone power — how much, per position, the mod sets with
60    /// `block::set_redstone_output`.
61    pub redstone_source: bool,
62    /// How it turns when placed (a `facing` property); `None`: it doesn't.
63    pub facing: Option<Facing>,
64    /// Which way it turns when placed.
65    pub placement: Placement,
66    /// It hangs on a floor, wall or ceiling, as a button (a `face`
67    /// property and a horizontal `facing`).
68    pub attach: bool,
69    /// It can stand in water (a `waterlogged` property).
70    pub waterlogged: bool,
71    /// Properties of its own.
72    pub properties: Vec<BlockProperty>,
73    /// A yes/no property the block keeps on while it gets redstone power.
74    pub powered: Option<String>,
75    /// A yes/no property a comparator reads 15 from while it's on.
76    pub comparator: Option<String>,
77    /// Hoppers and droppers can put items into it (`on_item_insert`).
78    pub accepts_items: bool,
79    /// It doesn't hide the faces behind it or stop light (as glass, a door).
80    #[data(default)]
81    pub no_occlusion: bool,
82    /// One of the game's kinds of block it is (`stairs`, `slab`, `door`,
83    /// `trapdoor`, `button`, `pressure_plate`, `fence`, `fence_gate`): the
84    /// host makes it of the game's class, with that class's states; `""`: a
85    /// block of Yog's own.
86    #[data(default)]
87    pub kind: String,
88    /// The block a kind is made from (stairs act as it).
89    #[data(default)]
90    pub base: Option<String>,
91    /// A kind's set — a registered block set by name (`oak`, `iron`…; a
92    /// fence gate's wood type); `None`: `oak`.
93    #[data(default)]
94    pub set: Option<String>,
95    /// How long a button stays pressed, ticks.
96    #[data(default = "thirty")]
97    pub press_ticks: u32,
98    /// Only living things set off a button or a pressure plate (not items
99    /// or arrows).
100    #[data(default)]
101    pub mobs_only: bool,
102}
103
104fn thirty() -> u32 {
105    30
106}
107
108/// A property of a block's own: its name and values (the first the
109/// default; `false`, `true` is a yes/no).
110#[derive(yog_data::Schema, Debug, Clone, PartialEq, Eq)]
111pub struct BlockProperty {
112    pub name: String,
113    pub values: Vec<String>,
114}
115
116/// A block's hardness and blast resistance unless it says: stone's.
117const STONE_HARDNESS: f32 = 1.5;
118const STONE_RESISTANCE: f32 = 6.0;
119
120impl BlockDef {
121    pub fn new(id: impl Into<String>) -> Self {
122        Self {
123            id: id.into(),
124            hardness: STONE_HARDNESS,
125            resistance: STONE_RESISTANCE,
126            name: None,
127            shape: None,
128            light_level: 0,
129            sound: None,
130            requires_tool: false,
131            no_collision: false,
132            slipperiness: 0.0,
133            connects: false,
134            connect_groups: Vec::new(),
135            inventory_id: None,
136            redstone_source: false,
137            facing: None,
138            placement: Placement::Toward,
139            attach: false,
140            waterlogged: false,
141            properties: Vec::new(),
142            powered: None,
143            comparator: None,
144            accepts_items: false,
145            no_occlusion: false,
146            kind: String::new(),
147            base: None,
148            set: None,
149            press_ticks: thirty(),
150            mobs_only: false,
151        }
152    }
153
154    /// Mining hardness and blast resistance (defaults 1.5 / 6.0).
155    pub fn strength(mut self, hardness: f32, resistance: f32) -> Self {
156        self.hardness = hardness;
157        self.resistance = resistance;
158        self
159    }
160
161    /// Display name shown in-game.
162    pub fn name(mut self, name: impl Into<String>) -> Self {
163        self.name = Some(name.into());
164        self
165    }
166
167    /// Custom hitbox/outline in pixel units (0–16): its lowest corner and its highest.
168    pub fn shape(mut self, [x1, y1, z1]: [f32; 3], [x2, y2, z2]: [f32; 3]) -> Self {
169        self.shape = Some([x1, y1, z1, x2, y2, z2]);
170        self
171    }
172
173    /// Emitted light level (0–15).
174    pub fn light_level(mut self, level: u8) -> Self {
175        self.light_level = level.min(15);
176        self
177    }
178
179    /// Sound group: one of the game's (`"stone"`, `"amethyst"`…) or a
180    /// mod's [`SoundGroup`] (`"mymod:crystal"`).
181    pub fn sound(mut self, group: impl Into<String>) -> Self {
182        self.sound = Some(group.into());
183        self
184    }
185
186    /// Correct tool required for loot drops (equivalent to `requiresTool()`).
187    pub fn requires_tool(mut self) -> Self {
188        self.requires_tool = true;
189        self
190    }
191
192    /// No physical collision — entities pass through (like flowers).
193    pub fn no_collision(mut self) -> Self {
194        self.no_collision = true;
195        self
196    }
197
198    /// Friction (default 0.6). Set to 0.989 for ice-like slipperiness.
199    pub fn slipperiness(mut self, value: f32) -> Self {
200        self.slipperiness = value;
201        self
202    }
203
204    /// Dynamically grow arms toward compatible neighbors — fence/pipe-style.
205    /// "Compatible" means the neighbor also has a `connect_groups` tag in
206    /// common (set here, or via `connect_groups` alone on a static target
207    /// block). Call `.connect_groups(&[...])` too, or this connects to
208    /// nothing. See the `connects` field doc.
209    pub fn connects_to_neighbors(mut self) -> Self {
210        self.connects = true;
211        self
212    }
213
214    /// Connection compatibility tags — two blocks link up (for
215    /// `connects_to_neighbors` arm growth, and as valid connection targets
216    /// generally) when their tag sets share at least one entry. Example:
217    /// `analog_cable` and `redstone_port` both carry `"analog"`;
218    /// `digital_cable` carries `"digital"`; an ALU block carries both, so it
219    /// accepts either cable while a Redstone Port only accepts the analog one.
220    pub fn connect_groups(mut self, groups: &[&str]) -> Self {
221        self.connect_groups = groups
222            .iter()
223            .map(std::string::ToString::to_string)
224            .collect();
225        self
226    }
227
228    /// Back this block with a real Container/Menu inventory screen, whose
229    /// shape/layout was registered separately via `Registry::register_inventory`
230    /// — see `yog_inventory::InventoryDef`. `id` is that def's id.
231    /// A redstone power source: what each placed block gives off is set
232    /// with `block::set_redstone_output` (0 until then).
233    pub fn redstone_source(mut self) -> Self {
234        self.redstone_source = true;
235        self
236    }
237
238    /// Turns when placed: `"horizontal"` or `"all"` ways, its front (the
239    /// model's north side) `"toward"` the placer or `"away"` (the way they look).
240    pub fn facing(mut self, ways: Facing, placement: Placement) -> Self {
241        self.facing = Some(ways);
242        self.placement = placement;
243        self
244    }
245
246    /// Hangs on a floor, wall or ceiling, as a button.
247    pub fn attach(mut self) -> Self {
248        self.attach = true;
249        self
250    }
251
252    /// Can stand in water.
253    pub fn waterlogged(mut self) -> Self {
254        self.waterlogged = true;
255        self
256    }
257
258    /// A property of its own: `values`, the first the default.
259    pub fn property(mut self, name: &str, values: &[&str]) -> Self {
260        self.properties.push(BlockProperty {
261            name: name.into(),
262            values: values.iter().map(|v| v.to_string()).collect(),
263        });
264        self
265    }
266
267    /// Keeps yes/no property `name` on while it gets redstone power.
268    pub fn powered(mut self, name: &str) -> Self {
269        self.powered = Some(name.into());
270        self
271    }
272
273    /// A comparator reads 15 from it while yes/no property `name` is on.
274    pub fn comparator(mut self, name: &str) -> Self {
275        self.comparator = Some(name.into());
276        self
277    }
278
279    /// Hoppers and droppers can put items into it (`on_item_insert`).
280    pub fn accepts_items(mut self) -> Self {
281        self.accepts_items = true;
282        self
283    }
284
285    pub fn inventory(mut self, id: impl Into<String>) -> Self {
286        self.inventory_id = Some(id.into());
287        self
288    }
289
290    /// Doesn't hide the faces behind it or stop light (as glass).
291    pub fn no_occlusion(mut self) -> Self {
292        self.no_occlusion = true;
293        self
294    }
295
296    /// One of the game's kinds of block (`"stairs"`, `"door"`…).
297    pub fn kind(mut self, kind: &str) -> Self {
298        self.kind = kind.into();
299        self
300    }
301
302    /// The block a kind is made from.
303    pub fn base(mut self, id: impl Into<String>) -> Self {
304        self.base = Some(id.into());
305        self
306    }
307
308    /// A kind's block set (`"oak"`, `"dark_oak"`, `"iron"`…).
309    pub fn set(mut self, name: &str) -> Self {
310        self.set = Some(name.into());
311        self
312    }
313
314    /// How long a button stays pressed, ticks (30 by default).
315    pub fn press_ticks(mut self, ticks: u32) -> Self {
316        self.press_ticks = ticks;
317        self
318    }
319
320    /// Only living things set off a button or a pressure plate.
321    pub fn mobs_only(mut self) -> Self {
322        self.mobs_only = true;
323        self
324    }
325}