use std::sync::LazyLock;
use yog_data::{Ordered, Schema};
#[derive(Schema, Debug, Clone, PartialEq)]
pub struct SlotSpec {
pub name: Option<String>,
pub description: Option<String>,
#[data(default = "one", range = "1..=64")]
pub max: u32,
#[data(default)]
pub required: bool,
pub field: Option<String>,
#[data(default)]
pub grid: bool,
}
fn one() -> u32 {
1
}
#[derive(Schema, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueType {
Number,
Text,
Bool,
}
#[derive(Schema, Debug, Clone, PartialEq)]
pub struct RecipeKindDef {
pub id: String,
pub name: Option<String>,
#[data(default)]
pub slots: Ordered<SlotSpec>,
#[data(default)]
pub values: Ordered<ValueType>,
#[data(default)]
pub defaults: Ordered<f64>,
}
impl RecipeKindDef {
pub fn slot(&self, name: &str) -> Option<&SlotSpec> {
self.slots.0.iter().find(|(n, _)| n == name).map(|(_, s)| s)
}
pub fn default_of(&self, name: &str) -> Option<f64> {
self.defaults.0.iter().find(|(n, _)| n == name).map(|(_, v)| *v)
}
}
pub mod ids {
pub const SHAPED: &str = "shaped";
pub const SHAPELESS: &str = "shapeless";
pub const SMELTING: &str = "smelting";
pub const BLASTING: &str = "blasting";
pub const SMOKING: &str = "smoking";
pub const CAMPFIRE_COOKING: &str = "campfire_cooking";
pub const STONECUTTING: &str = "stonecutting";
pub const SMITHING: &str = "smithing";
pub const BREWING: &str = "brewing";
pub const ALL: [&str; 9] = [SHAPED, SHAPELESS, SMELTING, BLASTING, SMOKING, CAMPFIRE_COOKING, STONECUTTING, SMITHING, BREWING];
pub const COOKING: [&str; 4] = [SMELTING, BLASTING, SMOKING, CAMPFIRE_COOKING];
}
pub mod fields {
pub const TYPE: &str = "type";
pub const RESULT: &str = "result";
pub const COUNT: &str = "count";
pub const SLOTS: &str = "slots";
pub const VALUES: &str = "values";
pub const PATTERN: &str = "pattern";
pub const KEY: &str = "key";
}
pub const GRID_SIDE: usize = 3;
pub static GAME: LazyLock<Vec<RecipeKindDef>> = LazyLock::new(|| {
yog_data::from_text::<Vec<RecipeKindDef>>(include_str!("../data/recipe_kinds.ydata"))
.expect("the built-in recipe kinds are valid Yog Data")
.value
});
pub fn game_kind(id: &str) -> Option<&'static RecipeKindDef> {
GAME.iter().find(|k| k.id == id)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_game_kinds_are_data() {
let data: Vec<&str> = GAME.iter().map(|k| k.id.as_str()).collect();
assert_eq!(data, ids::ALL, "the data and the code name the same kinds");
let smelting = game_kind(ids::SMELTING).unwrap();
assert_eq!(smelting.slot("input").and_then(|s| s.field.as_deref()), Some("ingredient"));
assert_eq!(smelting.default_of("cook_time"), Some(200.0));
assert!(game_kind(ids::SHAPED).unwrap().slot("grid").unwrap().grid);
let brewing = game_kind(ids::BREWING).unwrap();
assert_eq!(brewing.slot("from").and_then(|s| s.field.as_deref()), Some("from"));
}
}