use yog_data::{Ordered, Reader, Schema, Shape, Value, ValueRef};
#[derive(Schema, Debug, Clone)]
pub struct BlockKind {
pub name: String,
pub slots: Vec<String>,
pub models: Ordered<String>,
pub item: Option<String>,
#[data(default)]
pub takes: Vec<String>,
pub occludes: Option<bool>,
pub collision: Option<bool>,
pub drops_when: Option<String>,
pub drops_two_when: Option<String>,
#[data(default)]
pub variants: Ordered<KindModel>,
#[data(default)]
pub multipart: Vec<KindPart>,
}
#[derive(Schema, Debug, Clone)]
pub struct KindModel {
pub model: String,
pub x: Option<u32>,
pub y: Option<u32>,
#[data(default)]
pub uvlock: bool,
}
#[derive(Schema, Debug, Clone)]
pub struct KindPart {
pub apply: KindModel,
pub when: Option<String>,
}
pub const KIND_FIELDS: [&str; 4] = ["base", "set", "press_ticks", "triggered_by"];
static BUILT_IN_KINDS: yog_sync::LazyLock<Vec<BlockKind>> = yog_sync::LazyLock::new(|| {
yog_data::built_in(
"yog-formats' built-in block kinds",
include_str!("../../data/block_kinds.ydata"),
)
});
impl BlockKind {
pub fn built_ins() -> &'static [BlockKind] {
&BUILT_IN_KINDS
}
pub fn built_in(name: &str) -> Option<&'static BlockKind> {
Self::built_ins().iter().find(|k| k.name == name)
}
pub fn takes(&self, field: &str) -> bool {
self.takes.iter().any(|t| t == field)
}
}
pub struct KindRef;
impl yog_data::with::With<String> for KindRef {
fn shape() -> Shape {
Shape::Any
}
fn read(v: &ValueRef<'_>, cx: &mut Reader) -> Option<String> {
let s = String::read(v, cx)?;
if BlockKind::built_in(&s).is_none() {
let names: Vec<&str> = BlockKind::built_ins()
.iter()
.map(|k| k.name.as_str())
.collect();
cx.problem(format!(
"`{s}`: one of the game's kinds of block — {}",
names.join(", ")
));
return None;
}
Some(s)
}
fn write(f: &String) -> Value {
Value::from(f.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_game_kinds_of_block_read_and_name_their_own_models() {
let kinds = BlockKind::built_ins();
assert_eq!(kinds.len(), 8);
for k in kinds {
let names = k
.variants
.0
.iter()
.map(|(_, m)| m)
.chain(k.multipart.iter().map(|p| &p.apply))
.map(|m| m.model.as_str());
for name in names.chain(k.item.as_deref()) {
assert!(
name == "base" && k.takes("base") || k.models.0.iter().any(|(n, _)| n == name),
"{}: no model `{name}`",
k.name
);
}
for (n, _) in &k.models.0 {
assert_ne!(n, "base", "{}: `base` is block.base's model", k.name);
}
assert!(
k.takes.iter().all(|t| KIND_FIELDS.contains(&t.as_str())),
"{}",
k.name
);
assert!(
k.variants.0.is_empty() != k.multipart.is_empty(),
"{}: variants or multipart",
k.name
);
}
}
}