mod consts;
use consts as mcmeta;
use yog_data::{Res, Schema};
#[derive(Schema, Debug, Clone, PartialEq)]
pub struct TextureFile {
pub image: Res,
pub atlas: Option<String>,
pub animation: Option<Animation>,
#[data(default)]
pub blur: bool,
#[data(default)]
pub clamp: bool,
}
#[derive(Schema, Debug, Clone, PartialEq, Default)]
pub struct Animation {
pub frame_time: Option<u32>,
#[data(default)]
pub interpolate: bool,
#[data(default)]
pub frames: Vec<Frame>,
pub width: Option<u32>,
pub height: Option<u32>,
}
#[derive(Schema, Debug, Clone, PartialEq, Default)]
pub struct Frame {
pub index: u32,
pub time: Option<u32>,
}
impl Animation {
pub const DEFAULT_FRAME_TIME: u32 = 1;
#[must_use]
pub fn frame_time(&self) -> u32 {
self.frame_time.unwrap_or(Self::DEFAULT_FRAME_TIME)
}
}
#[must_use]
pub fn id_of(mod_id: &str, rel: &str) -> Option<String> {
let path = rel
.strip_suffix(yog_ids::Format::Texture.ext())?
.strip_suffix('.')?;
Some(yog_ids::join(mod_id, path))
}
use serde_json::{Value, json};
impl TextureFile {
#[must_use]
pub fn game_mcmeta(&self) -> Option<Value> {
let t = self;
let mut meta = serde_json::Map::new();
if let Some(a) = &t.animation {
meta.insert(mcmeta::ANIMATION.into(), animation(a));
}
if t.blur || t.clamp {
meta.insert(
mcmeta::TEXTURE.into(),
json!({ "blur": t.blur, "clamp": t.clamp }),
);
}
(!meta.is_empty()).then_some(Value::Object(meta))
}
}
fn animation(a: &Animation) -> Value {
let mut anim = serde_json::Map::new();
anim.insert("frametime".into(), json!(a.frame_time()));
if a.interpolate {
anim.insert("interpolate".into(), json!(true));
}
if !a.frames.is_empty() {
let frames = a.frames.iter().map(|f| match f.time {
Some(time) => json!({ "index": f.index, "time": time }),
None => json!(f.index),
});
anim.insert("frames".into(), Value::Array(frames.collect()));
}
if let Some(w) = a.width {
anim.insert("width".into(), json!(w));
}
if let Some(h) = a.height {
anim.insert("height".into(), json!(h));
}
Value::Object(anim)
}
impl Animation {
#[must_use]
pub fn from_mcmeta(mcmeta: &Value) -> Option<Animation> {
let a = mcmeta.get(mcmeta::ANIMATION)?;
let u32_of = |v: &Value, k: &str| {
v.get(k)
.and_then(Value::as_u64)
.and_then(|n| u32::try_from(n).ok())
};
Some(Animation {
frame_time: u32_of(a, "frametime"),
interpolate: a
.get("interpolate")
.and_then(Value::as_bool)
.unwrap_or(false),
frames: a
.get("frames")
.and_then(Value::as_array)
.map(|fs| {
fs.iter()
.filter_map(|f| match f.as_u64() {
Some(i) => Some(Frame {
index: u32::try_from(i).ok()?,
time: None,
}),
None => Some(Frame {
index: u32_of(f, "index")?,
time: u32_of(f, "time"),
}),
})
.collect()
})
.unwrap_or_default(),
width: u32_of(a, "width"),
height: u32_of(a, "height"),
})
}
}
#[must_use]
pub fn picture_set(path: &str) -> String {
match path.split(yog_ids::sep::PATH).next().unwrap_or_default() {
GUI_DIR => GUI_SET.to_string(),
top => {
top.split('_')
.map(|w| w[..1].to_ascii_uppercase() + &w[1..])
.collect::<String>()
+ PICTURE_SET_SUFFIX
}
}
}
const GUI_DIR: &str = "gui";
const GUI_SET: &str = "Images";
const PICTURE_SET_SUFFIX: &str = "Images";
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_texture_round_trips_through_its_file_and_names_itself() {
let t = TextureFile {
image: Res {
set: "Images".into(),
key: "block.ruby".into(),
},
atlas: Some("minecraft:blocks".into()),
animation: Some(Animation {
frame_time: Some(2),
frames: vec![Frame {
index: 1,
time: Some(5),
}],
..Animation::default()
}),
blur: false,
clamp: true,
};
assert_eq!(
yog_data::from_text::<TextureFile>(&yog_data::text::to_string_pretty(&t.write()))
.unwrap()
.value,
t
);
assert_eq!(
id_of("mymod", "block/ruby.ytexture").as_deref(),
Some("mymod:block/ruby")
);
assert_eq!(
(
picture_set("block/ruby"),
picture_set("mob_effect/x"),
picture_set("gui/logo")
),
(
"BlockImages".into(),
"MobEffectImages".into(),
"Images".into()
)
);
let meta = json!({ "animation": { "frametime": 2, "interpolate": true, "frames": [1, { "index": 0, "time": 5 }] } });
let a = Animation::from_mcmeta(&meta).unwrap();
assert_eq!(
TextureFile {
animation: Some(a),
clamp: false,
..t
}
.game_mcmeta(),
Some(meta)
);
}
}