use anyhow::Ok; use heck::ToPascalCase; use proc_macro2::TokenStream; use quote::quote; use serde::Deserialize; use crate::ident; #[derive(Deserialize, Clone, Debug)] struct Item { id: u16, name: String, translation_key: String, max_stack: u8, max_durability: u16, enchantability: u8, fireproof: bool, food: Option, } #[derive(Deserialize, Clone, Debug)] struct FoodComponent { hunger: u16, saturation: f32, always_edible: bool, meat: bool, snack: bool, // TODO: effects } pub fn build() -> anyhow::Result { let items = serde_json::from_str::>(include_str!("../extracted/items.json"))?; let item_kind_count = items.len(); let item_kind_from_raw_id_arms = items .iter() .map(|item| { let id = &item.id; let name = ident(item.name.to_pascal_case()); quote! { #id => Some(Self::#name), } }) .collect::(); let item_kind_to_raw_id_arms = items .iter() .map(|item| { let id = &item.id; let name = ident(item.name.to_pascal_case()); quote! { Self::#name => #id, } }) .collect::(); let item_kind_from_str_arms = items .iter() .map(|item| { let str_name = &item.name; let name = ident(str_name.to_pascal_case()); quote! { #str_name => Some(Self::#name), } }) .collect::(); let item_kind_to_str_arms = items .iter() .map(|item| { let str_name = &item.name; let name = ident(str_name.to_pascal_case()); quote! { Self::#name => #str_name, } }) .collect::(); let item_kind_to_translation_key_arms = items .iter() .map(|item| { let name = ident(item.name.to_pascal_case()); let translation_key = &item.translation_key; quote! { Self::#name => #translation_key, } }) .collect::(); let item_kind_variants = items .iter() .map(|item| ident(item.name.to_pascal_case())) .collect::>(); let item_kind_to_max_stack_arms = items .iter() .map(|item| { let name = ident(item.name.to_pascal_case()); let max_stack = item.max_stack; quote! { Self::#name => #max_stack, } }) .collect::(); let item_kind_to_food_component_arms = items .iter() .map(|item| match &item.food { Some(food_component) => { let name = ident(item.name.to_pascal_case()); let hunger = food_component.hunger; let saturation = food_component.saturation; let always_edible = food_component.always_edible; let meat = food_component.meat; let snack = food_component.snack; quote! { Self::#name => Some(FoodComponent { hunger: #hunger, saturation: #saturation, always_edible: #always_edible, meat: #meat, snack: #snack, } ), } } None => quote! {}, }) .collect::(); let item_kind_to_max_durability_arms = items .iter() .filter(|item| item.max_durability != 0) .map(|item| { let name = ident(item.name.to_pascal_case()); let max_durability = item.max_durability; quote! { Self::#name => #max_durability, } }) .collect::(); let item_kind_to_enchantability_arms = items .iter() .filter(|item| item.enchantability != 0) .map(|item| { let name = ident(item.name.to_pascal_case()); let ench = item.enchantability; quote! { Self::#name => #ench, } }) .collect::(); let item_kind_to_fireproof_arms = items .iter() .filter(|item| item.fireproof) .map(|item| { let name = ident(item.name.to_pascal_case()); quote! { Self::#name => true, } }) .collect::(); Ok(quote! { /// Represents an item from the game #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[repr(u16)] pub enum ItemKind { #(#item_kind_variants,)* } /// Contains food information about an item. /// /// Only food items have a food component. #[derive(Clone, Copy, PartialEq, PartialOrd, Debug)] pub struct FoodComponent { pub hunger: u16, pub saturation: f32, pub always_edible: bool, pub meat: bool, pub snack: bool, } impl ItemKind { /// Constructs a item kind from a raw item ID. /// /// If the given ID is invalid, `None` is returned. pub const fn from_raw(id: u16) -> Option { match id { #item_kind_from_raw_id_arms _ => None } } /// Gets the raw item ID from the item kind pub const fn to_raw(self) -> u16 { match self { #item_kind_to_raw_id_arms } } /// Construct an item kind for its snake_case name. /// /// Returns `None` if the name is invalid. #[allow(clippy::should_implement_trait)] pub fn from_str(name: &str) -> Option { match name { #item_kind_from_str_arms _ => None } } /// Gets the snake_case name of this item kind. pub const fn to_str(self) -> &'static str { match self { #item_kind_to_str_arms } } /// Gets the translation key of this item kind. pub const fn translation_key(self) -> &'static str { match self { #item_kind_to_translation_key_arms } } /// Returns the maximum stack count. pub const fn max_stack(self) -> u8 { match self { #item_kind_to_max_stack_arms } } /// Returns a food component which stores hunger, saturation etc. /// /// If the item kind can't be eaten, `None` will be returned. pub const fn food_component(self) -> Option { match self { #item_kind_to_food_component_arms _ => None } } /// Returns the maximum durability before the item will break. /// /// If the item doesn't have durability, `0` is returned. pub const fn max_durability(self) -> u16 { match self { #item_kind_to_max_durability_arms _ => 0, } } /// Returns the enchantability of the item kind. /// /// If the item doesn't have durability, `0` is returned. pub const fn enchantability(self) -> u8 { match self { #item_kind_to_enchantability_arms _ => 0, } } /// Returns if the item can survive in fire/lava. pub const fn fireproof(self) -> bool { #[allow(clippy::match_like_matches_macro)] match self { #item_kind_to_fireproof_arms _ => false } } /// Constructs an item kind from a block kind. /// /// [`ItemKind::Air`] is used to indicate the absence of an item. pub const fn from_block_kind(kind: BlockKind) -> Self { kind.to_item_kind() } /// Constructs a block kind from an item kind. /// /// If the given item kind doesn't have a corresponding block kind, `None` is returned. pub const fn to_block_kind(self) -> Option { BlockKind::from_item_kind(self) } /// An array of all item kinds. pub const ALL: [Self; #item_kind_count] = [#(Self::#item_kind_variants,)*]; } }) }