valence/build/item.rs
EmperialDev 1f996f7549
Add Items (#92)
Adding all the items, just like the blocks are. This will adress 1. and 2. bullet from issue #53
This will also make it easier to convert between block <-> item.
2022-10-05 09:28:08 -07:00

302 lines
8.8 KiB
Rust

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<FoodComponent>,
}
#[derive(Deserialize, Clone, Debug)]
struct FoodComponent {
hunger: u16,
saturation: f32,
always_edible: bool,
meat: bool,
snack: bool,
// TODO: effects
}
pub fn build() -> anyhow::Result<TokenStream> {
let items = serde_json::from_str::<Vec<Item>>(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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
let item_kind_variants = items
.iter()
.map(|item| ident(item.name.to_pascal_case()))
.collect::<Vec<_>>();
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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
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::<TokenStream>();
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<Self> {
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<ItemKind> {
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<FoodComponent> {
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> {
BlockKind::from_item_kind(self)
}
/// An array of all item kinds.
pub const ALL: [Self; #item_kind_count] = [#(Self::#item_kind_variants,)*];
}
})
}