yog_formats/content/
kind.rs1use yog_data::{Ordered, Reader, Schema, Shape, Value, ValueRef};
4
5#[derive(Schema, Debug, Clone)]
11pub struct BlockKind {
12 pub name: String,
13 pub slots: Vec<String>,
15 pub models: Ordered<String>,
18 pub item: Option<String>,
21 #[data(default)]
23 pub takes: Vec<String>,
24 pub occludes: Option<bool>,
26 pub collision: Option<bool>,
28 pub drops_when: Option<String>,
31 pub drops_two_when: Option<String>,
33 #[data(default)]
35 pub variants: Ordered<KindModel>,
36 #[data(default)]
39 pub multipart: Vec<KindPart>,
40}
41
42#[derive(Schema, Debug, Clone)]
45pub struct KindModel {
46 pub model: String,
47 pub x: Option<u32>,
48 pub y: Option<u32>,
49 #[data(default)]
50 pub uvlock: bool,
51}
52
53#[derive(Schema, Debug, Clone)]
56pub struct KindPart {
57 pub apply: KindModel,
58 pub when: Option<String>,
59}
60
61pub const KIND_FIELDS: [&str; 4] = ["base", "set", "press_ticks", "triggered_by"];
63
64static BUILT_IN_KINDS: yog_sync::LazyLock<Vec<BlockKind>> = yog_sync::LazyLock::new(|| {
65 yog_data::built_in(
66 "yog-formats' built-in block kinds",
67 include_str!("../../data/block_kinds.ydata"),
68 )
69});
70
71impl BlockKind {
72 pub fn built_ins() -> &'static [BlockKind] {
75 &BUILT_IN_KINDS
76 }
77
78 pub fn built_in(name: &str) -> Option<&'static BlockKind> {
79 Self::built_ins().iter().find(|k| k.name == name)
80 }
81
82 pub fn takes(&self, field: &str) -> bool {
83 self.takes.iter().any(|t| t == field)
84 }
85}
86
87pub struct KindRef;
89
90impl yog_data::with::With<String> for KindRef {
91 fn shape() -> Shape {
92 Shape::Any
93 }
94 fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<String> {
95 let s = String::read(v, cx)?;
96 if BlockKind::built_in(&s).is_none() {
97 let names: Vec<&str> = BlockKind::built_ins()
98 .iter()
99 .map(|k| k.name.as_str())
100 .collect();
101 cx.problem(format!(
102 "`{s}`: one of the game's kinds of block — {}",
103 names.join(", ")
104 ));
105 return None;
106 }
107 Some(s)
108 }
109 fn write(f: &String) -> Value {
110 Value::from(f.as_str())
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn the_game_kinds_of_block_read_and_name_their_own_models() {
120 let kinds = BlockKind::built_ins();
121 assert_eq!(kinds.len(), 8);
122 for k in kinds {
123 let names = k
124 .variants
125 .0
126 .iter()
127 .map(|(_, m)| m)
128 .chain(k.multipart.iter().map(|p| &p.apply))
129 .map(|m| m.model.as_str());
130 for name in names.chain(k.item.as_deref()) {
131 assert!(
132 name == "base" && k.takes("base") || k.models.0.iter().any(|(n, _)| n == name),
133 "{}: no model `{name}`",
134 k.name
135 );
136 }
137 for (n, _) in &k.models.0 {
138 assert_ne!(n, "base", "{}: `base` is block.base's model", k.name);
139 }
140 assert!(
141 k.takes.iter().all(|t| KIND_FIELDS.contains(&t.as_str())),
142 "{}",
143 k.name
144 );
145 assert!(
146 k.variants.0.is_empty() != k.multipart.is_empty(),
147 "{}: variants or multipart",
148 k.name
149 );
150 }
151 }
152}