use serde_json::{Map, Value, json};
use yog_data::Path as At;
use super::state::{state_properties, variant};
use crate::content::block_item::Emit;
use crate::content::ctx::{Ctx, Loc};
pub(in crate::content) struct KindBlock<'a> {
pub(in crate::content) kind: &'a yog_formats::content::BlockKind,
pub(in crate::content) base: Option<&'a str>,
pub(in crate::content) render_layer: Option<&'a str>,
}
pub(in crate::content) fn with_render_type(json: &mut Value, layer: Option<&str>) {
if let (Some(layer), Value::Object(o)) = (layer, json) {
o.insert(
yog_ids::model::RENDER_TYPE.into(),
Value::String(yog_ids::qualify(layer)),
);
}
}
pub(in crate::content) fn kind_models(
cx: &mut Ctx,
loc: Loc,
kb: &KindBlock,
m: &yog_formats::content::Model,
emit: &mut Emit,
) -> Option<String> {
let k = kb.kind;
if m.parent.is_some()
|| m.elements.is_some()
|| m.display.is_some()
|| m.ambient_occlusion.is_some()
|| m.gui_light.is_some()
{
let at = At::root().key(yog_ids::content_field::MODEL);
cx.err_at(
loc,
&at,
format!(
"kind {} has the game's models — give only textures ({})",
k.name,
k.slots.join(", ")
),
);
}
let textures = kind_textures(cx, loc, k, m);
let names = Names {
p: emit.p.clone(),
ns: emit.ns.clone(),
base: kb.base,
};
for (name, parent) in &k.models.0 {
let mut json =
json!({ yog_ids::model::PARENT: parent, yog_ids::model::TEXTURES: textures });
with_render_type(&mut json, kb.render_layer);
let file = yog_ids::model_file(&format!("{}{}", yog_ids::res::BLOCK_DIR, names.path(name)));
emit.asset(cx, file, &json);
}
emit.asset(
cx,
yog_ids::blockstate_file(&names.p),
&blockstate(k, &names),
);
k.item.as_deref().map(|name| names.id(name))
}
struct Names<'a> {
p: String,
ns: String,
base: Option<&'a str>,
}
impl Names<'_> {
fn path(&self, name: &str) -> String {
if name.is_empty() {
self.p.clone()
} else {
format!("{}_{name}", self.p)
}
}
fn id(&self, name: &str) -> String {
let block_model =
|ns: &str, path: &str| yog_ids::join(ns, &format!("{}{path}", yog_ids::res::BLOCK_DIR));
match (name, self.base) {
(BASE, Some(b)) => {
let (bns, bp) = yog_ids::split(b);
block_model(bns, bp)
}
_ => block_model(&self.ns, &self.path(name)),
}
}
}
fn blockstate(k: &yog_formats::content::BlockKind, names: &Names) -> Value {
let shown = |km: &yog_formats::content::KindModel| {
variant(
&names.id(&km.model),
f64::from(km.x.unwrap_or(0)),
f64::from(km.y.unwrap_or(0)),
km.uvlock,
None,
)
};
if k.multipart.is_empty() {
let variants: Map<String, Value> = k
.variants
.0
.iter()
.map(|(state, km)| (state.clone(), shown(km)))
.collect();
return json!({ yog_ids::model::VARIANTS: variants });
}
let parts: Vec<Value> = k
.multipart
.iter()
.map(|part| {
let mut o = Map::new();
o.insert(yog_ids::model::APPLY.into(), shown(&part.apply));
if let Some(when) = &part.when {
o.insert(
yog_ids::model::WHEN.into(),
Value::Object(state_properties(when)),
);
}
Value::Object(o)
})
.collect();
json!({ yog_ids::model::MULTIPART: parts })
}
const BASE: &str = "base";
fn kind_textures(
cx: &mut Ctx,
loc: Loc,
k: &yog_formats::content::BlockKind,
m: &yog_formats::content::Model,
) -> Map<String, Value> {
let at = At::root().key(yog_ids::content_field::MODEL);
let mut textures = Map::new();
for (slot, tex) in &m.textures.0 {
let here = at.key(yog_ids::model::TEXTURES).key(slot);
if slot != yog_ids::model::PARTICLE && !k.slots.contains(slot) {
cx.warn_at(
loc,
&here,
format!(
"kind {} has no slot `{slot}` ({})",
k.name,
k.slots.join(", ")
),
);
}
if let Some(t) = cx.texture(loc, &here, tex) {
textures.insert(slot.clone(), Value::String(t));
}
}
for slot in k.slots.iter().filter(|s| !textures.contains_key(*s)) {
cx.err_at(loc, &at, format!("kind {} needs textures.{slot}", k.name));
}
textures
}