use std::path::Path;
use yog_data::{Map, Value};
use yog_ids::Format;
use super::{ICON_SLOTS, content_file, model_textures, texture_bytes};
use crate::design::project::mod_root;
pub fn content_block_faces(root: &Path, id: &str) -> Option<[Option<Vec<u8>>; 6]> {
let (file, doc) = content_file(root, id, &[Format::Block])?;
let textures = model_textures(root, &doc, yog_ids::content_field::MODEL);
let pick = |keys: &[&str]| {
keys.iter()
.find_map(|k| textures.get(k).and_then(Value::as_str))
.or_else(|| textures.values().find_map(Value::as_str))
.and_then(|t| texture_bytes(&file, t).ok())
};
Some(crate::design::blocks3d::FACE_TEXTURES.map(pick))
}
pub type BlockModel = (Vec<Value>, std::collections::HashMap<String, Vec<u8>>);
pub fn content_block_model(root: &Path, id: &str) -> Option<BlockModel> {
let (file, doc) = content_file(root, id, &[Format::Block])?;
let elements = match crate::design::data::at(
&doc,
&[yog_ids::content_field::MODEL, yog_ids::model::ELEMENTS],
)
.and_then(Value::as_list)
{
Some(elements) => elements.to_vec(),
None if doc
.get(yog_ids::content_field::KIND)
.and_then(Value::as_str)
== Some("stairs") =>
{
vec![
stair_element([0, 0, 0], [16, 8, 16]),
stair_element([0, 8, 8], [16, 16, 16]),
]
}
None => return None,
};
let textures = model_textures(root, &doc, yog_ids::content_field::MODEL)
.iter()
.filter_map(|(k, v)| Some((k.to_string(), texture_bytes(&file, v.as_str()?).ok()?)))
.collect();
Some((elements, textures))
}
fn stair_element(from: [u8; 3], to: [u8; 3]) -> Value {
use yog_ids::direction as d;
let face = |texture: &str| -> Value {
let slot = format!("{}{texture}", yog_formats::content::SLOT_MARK);
Map::from_iter([(yog_ids::model::TEXTURE, Value::from(slot))]).into()
};
let corner = |c: [u8; 3]| Value::List(c.iter().map(|&n| Value::Int(n.into())).collect());
let faces = Map::from_iter([
(d::UP, face("top")),
(d::DOWN, face("bottom")),
(d::NORTH, face("side")),
(d::SOUTH, face("side")),
(d::WEST, face("side")),
(d::EAST, face("side")),
]);
Map::from_iter([
(yog_ids::model::FROM, corner(from)),
(yog_ids::model::TO, corner(to)),
(yog_ids::model::FACES, faces.into()),
])
.into()
}
pub fn content_item_appearance(root: &Path, id: &str) -> Option<(Value, Option<u32>)> {
let ns = crate::design::project::mod_id(root)?;
let (want_ns, want) = yog_ids::split(id);
let want = if want_ns == yog_ids::ns::YOG {
want.strip_prefix(yog_ids::split(yog_ids::yog::POTION_ALIAS).1)?
} else if want_ns == ns {
want
} else {
return None;
};
for file in crate::files::of(&root.join("content/items"), Format::Item) {
let Ok(text) = crate::files::read_source(&file) else {
continue;
};
let Some(doc) = crate::design::data::read_object(&text) else {
continue;
};
let Some(fid) = doc
.get(yog_ids::content_field::ID)
.and_then(Value::as_str)
.or_else(|| file.file_stem().and_then(|s| s.to_str()))
else {
continue;
};
if fid.split_once(':').map_or(fid, |(_, p)| p) != want {
continue;
}
let definition = doc;
let color = potion_effect_color(root, &definition);
return Some((definition, color));
}
None
}
fn potion_effect_color(root: &Path, item: &Value) -> Option<u32> {
let effects = crate::design::data::at(
item,
&[
yog_ids::content_field::ITEM,
yog_ids::content_field::POTION,
yog_ids::content_field::EFFECTS,
],
)?
.as_list()?;
let mut channels = [0u64; 3];
let mut count = 0u64;
for effect in effects {
let Some(id) = effect
.get(yog_ids::content_field::EFFECT)
.and_then(Value::as_str)
else {
continue;
};
let Some(color) = content_effect_color(root, id) else {
continue;
};
channels[0] += u64::from((color >> 16) & 0xFF);
channels[1] += u64::from((color >> 8) & 0xFF);
channels[2] += u64::from(color & 0xFF);
count += 1;
}
(count > 0).then(|| {
(((channels[0] / count) as u32) << 16)
| (((channels[1] / count) as u32) << 8)
| (channels[2] / count) as u32
})
}
pub fn item_potion_color(file: &Path) -> Option<u32> {
let root = mod_root(file)?;
let text = crate::files::read_source(file).ok()?;
let item = crate::design::data::read_object(&text)?;
potion_effect_color(&root, &item)
}
fn content_effect_color(root: &Path, reference: &str) -> Option<u32> {
let mod_ns = crate::design::project::mod_id(root)?;
let id = reference.strip_prefix("@effect:").unwrap_or(reference);
let (ns, want) = yog_ids::split(id);
if ns != mod_ns {
return None;
}
for file in crate::files::of(&root.join("content/effects"), Format::Def) {
let Ok(text) = crate::files::read_source(&file) else {
continue;
};
let Some(doc) = crate::design::data::read_object(&text) else {
continue;
};
let Some(id) = doc
.get(yog_ids::content_field::ID)
.and_then(Value::as_str)
.or_else(|| file.file_stem().and_then(|s| s.to_str()))
else {
continue;
};
if id.split_once(':').map_or(id, |(_, p)| p) == want {
return doc
.get(yog_ids::content_field::COLOR)
.and_then(Value::as_i64)
.and_then(|v| u32::try_from(v).ok());
}
}
None
}
pub fn content_icon(root: &Path, id: &str) -> Option<Vec<u8>> {
let ns = crate::design::project::mod_id(root)?;
let (want_ns, want) = yog_ids::split(id);
if want_ns != ns {
return None;
}
let files = [
crate::files::of(&root.join(yog_ids::project::CONTENT), Format::Item),
crate::files::of(&root.join(yog_ids::project::CONTENT), Format::Block),
]
.concat();
for file in files {
let Ok(text) = crate::files::read_source(&file) else {
continue;
};
let Some(doc) = crate::design::data::read_object(&text) else {
continue;
};
let fid = doc
.get(yog_ids::content_field::ID)
.and_then(Value::as_str)
.map(str::to_string)
.or_else(|| file.file_stem().map(|s| s.to_string_lossy().into_owned()))?;
let fid = fid
.split_once(':')
.map_or(fid.as_str(), |(_, p)| p)
.to_string();
if fid != want {
continue;
}
let textures = model_textures(root, &doc, yog_ids::content_field::MODEL);
let tex = textures
.get(yog_ids::model::LAYER0)
.or_else(|| ICON_SLOTS.iter().find_map(|k| textures.get(k)))
.or_else(|| textures.values().next())?
.as_str()?;
return texture_bytes(&file, tex).ok();
}
None
}