use yog_data::schema::{Field, StructShape, read_both};
use yog_data::{ItemId, ItemIdOrTag, OneOrTag, Ordered, Reader, Schema, Seg, Shape, Value};
pub const TYPES: [&str; 8] = ["shaped", "shapeless", "smelting", "blasting", "smoking", "campfire_cooking", "stonecutting", "smithing"];
#[derive(Schema)]
pub struct Common {
pub format: Option<u32>,
pub id: Option<String>,
pub result: ItemId,
#[data(default = "one")]
pub count: u32,
pub group: Option<String>,
pub category: Option<String>,
}
fn one() -> u32 {
1
}
pub struct Ingredient(pub Vec<ItemIdOrTag>);
impl Ingredient {
pub fn ids(&self) -> Vec<String> {
self.0
.iter()
.map(|i| match i {
OneOrTag::One(id) => id.id.clone(),
OneOrTag::Tag(t) => format!("#{}", t.id),
})
.collect()
}
}
impl Schema for Ingredient {
fn shape() -> Shape {
ItemIdOrTag::shape()
}
fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
match v {
Value::List(items) if items.is_empty() => {
cx.problem("an empty list: at least one item or tag");
None
}
Value::List(_) => Vec::<ItemIdOrTag>::read(v, cx).map(Ingredient),
_ => ItemIdOrTag::read(v, cx).map(|i| Ingredient(vec![i])),
}
}
fn write(&self) -> Value {
match self.0.as_slice() {
[one] => one.write(),
many => Value::List(many.iter().map(Schema::write).collect()),
}
}
}
#[derive(Schema)]
pub struct Shaped {
pub pattern: Vec<String>,
pub key: Ordered<Ingredient>,
}
#[derive(Schema)]
pub struct Shapeless {
pub ingredients: Vec<Ingredient>,
}
#[derive(Schema)]
pub struct Cooking {
pub ingredient: Ingredient,
#[data(default = "experience")]
pub experience: f64,
pub cook_time: Option<u32>,
}
fn experience() -> f64 {
0.1
}
#[derive(Schema)]
pub struct Stonecutting {
pub ingredient: Ingredient,
}
#[derive(Schema)]
pub struct Smithing {
pub template: Ingredient,
pub base: Ingredient,
pub addition: Ingredient,
}
#[derive(Schema)]
pub struct Custom {
pub slots: Ordered<Vec<Ingredient>>,
#[data(default)]
pub values: Ordered<Value>,
}
pub enum Body {
Shaped(Shaped),
Shapeless(Shapeless),
Cooking(String, Cooking),
Stonecutting(Stonecutting),
Smithing(Smithing),
Custom(String, Custom),
}
pub struct RecipeFile {
pub common: Common,
pub body: Body,
}
impl Schema for RecipeFile {
fn shape() -> Shape {
let mut fields = vec![Field { name: "type", doc: "The game's recipe type, or the mod's own kind (`mymod:infusion`).", shape: String::shape, required: true }];
if let Shape::Struct(c) = Common::shape() {
fields.extend(c.fields);
}
Shape::Struct(StructShape { name: "Recipe", doc: "A recipe.", fields })
}
fn read(v: &Value, cx: &mut Reader) -> Option<Self> {
let map = cx.map(v)?;
let ty = match map.get("type") {
Some(Value::Str(t)) => t.clone(),
Some(other) => {
cx.at(Seg::Key("type".into()), |cx| cx.expected("a recipe type", other));
return None;
}
None => {
cx.problem(format!("missing `type` ({}, or the mod's own `mymod:kind`)", TYPES.join(", ")));
return None;
}
};
let also = &["type"];
let (common, body) = match ty.as_str() {
"shaped" => read_both::<Common, Shaped>(v, cx, also).map(|(c, b)| (c, Body::Shaped(b)))?,
"shapeless" => read_both::<Common, Shapeless>(v, cx, also).map(|(c, b)| (c, Body::Shapeless(b)))?,
"smelting" | "blasting" | "smoking" | "campfire_cooking" => read_both::<Common, Cooking>(v, cx, also).map(|(c, b)| (c, Body::Cooking(ty.clone(), b)))?,
"stonecutting" => read_both::<Common, Stonecutting>(v, cx, also).map(|(c, b)| (c, Body::Stonecutting(b)))?,
"smithing" => read_both::<Common, Smithing>(v, cx, also).map(|(c, b)| (c, Body::Smithing(b)))?,
own if own.contains(':') => read_both::<Common, Custom>(v, cx, also).map(|(c, b)| (c, Body::Custom(ty.clone(), b)))?,
other => {
cx.at(Seg::Key("type".into()), |cx| cx.unknown_name(other, &TYPES));
return None;
}
};
Some(RecipeFile { common, body })
}
fn write(&self) -> Value {
let (ty, body) = match &self.body {
Body::Shaped(b) => ("shaped".to_string(), b.write()),
Body::Shapeless(b) => ("shapeless".to_string(), b.write()),
Body::Cooking(t, b) => (t.clone(), b.write()),
Body::Stonecutting(b) => ("stonecutting".to_string(), b.write()),
Body::Smithing(b) => ("smithing".to_string(), b.write()),
Body::Custom(t, b) => (t.clone(), b.write()),
};
let mut m = yog_data::Map::new();
m.insert("type", Value::from(ty));
for part in [self.common.write(), body] {
if let Value::Map(fields) = part {
for (k, v) in fields {
m.insert(k, v);
}
}
}
Value::Map(m)
}
}