Files
Pumpkin/pumpkin-data/build/item.rs
spr-equinox 8ce65cc42d Refactor ItemStack to Support Vanilla-Like Data Components (#1059)
* improve tag v1

* improve tag v2

* fix typo and make BlockPredicate better

* improve get state from state id

* impl DataComponent

* make tag pub

* make component work

* apply clippy

* apply clippy

* remove unused crate

* apply to itemstack and no lifetime mark anymore

* Update tag.rs

* use Cow

* remove unnecessary lazy_static and Cow

* Update mod.rs

* Update mod.rs

* make DataComponent dyn

* remove unused crate

* fix error

* Update Cargo.toml

* Update Cargo.lock

* fix

* Update composter.rs

* update rust-version, remove unused crate, and merge

* Update furnace.rs
2025-07-28 12:24:40 +02:00

404 lines
13 KiB
Rust

use crate::enchantments::AttributeModifierSlot;
use heck::ToShoutySnakeCase;
use proc_macro2::{Span, TokenStream};
use pumpkin_util::registry::TagType;
use pumpkin_util::text::TextContent;
use pumpkin_util::{registry::RegistryEntryList, text::TextComponent};
use quote::{ToTokens, format_ident, quote};
use serde::Deserialize;
use std::{collections::HashMap, fs};
use syn::{Ident, LitBool, LitFloat, LitInt, LitStr};
#[derive(Deserialize, Clone)]
pub struct Item {
pub id: u16,
pub components: ItemComponents,
}
#[derive(Deserialize, Clone)]
pub struct ItemComponents {
#[serde(rename = "minecraft:item_name")]
pub item_name: TextComponent,
#[serde(rename = "minecraft:max_stack_size")]
pub max_stack_size: u8,
#[serde(rename = "minecraft:jukebox_playable")]
pub jukebox_playable: Option<String>,
#[serde(rename = "minecraft:damage")]
pub damage: Option<u16>,
#[serde(rename = "minecraft:max_damage")]
pub max_damage: Option<u16>,
#[serde(rename = "minecraft:attribute_modifiers")]
pub attribute_modifiers: Option<Vec<Modifier>>,
#[serde(rename = "minecraft:tool")]
pub tool: Option<ToolComponent>,
#[serde(rename = "minecraft:food")]
pub food: Option<FoodComponent>,
}
impl ToTokens for ItemComponents {
fn to_tokens(&self, tokens: &mut TokenStream) {
let max_stack_size = LitInt::new(&self.max_stack_size.to_string(), Span::call_site());
tokens.extend(quote! {
(MaxStackSize, &MaxStackSizeImpl {
size: #max_stack_size,
}),
});
if let Some(playable) = &self.jukebox_playable {
let song = LitStr::new(playable, Span::call_site());
tokens.extend(quote! {
(JukeboxPlayable, &JukeboxPlayableImpl{
song: #song,
}),
});
}
let TextContent::Translate {
translate: text,
with: _,
} = self.item_name.clone().0.content
else {
unreachable!()
};
let item_name = LitStr::new(&text, Span::call_site());
tokens.extend(quote! {
(ItemName, &ItemNameImpl {
name: #item_name,
}),
});
if let Some(d) = self.damage {
let damage_lit = LitInt::new(&d.to_string(), Span::call_site());
tokens.extend(quote! {
(Damage, &DamageImpl {
damage: #damage_lit,
}),
});
};
if let Some(md) = self.max_damage {
let max_damage_lit = LitInt::new(&md.to_string(), Span::call_site());
tokens.extend(quote! {
(MaxDamage, &MaxDamageImpl {
max_damage: #max_damage_lit,
}),
});
};
if let Some(modifiers) = &self.attribute_modifiers {
let modifier_code = modifiers.iter().map(|modifier| {
let r#type = format_ident!(
"{}",
modifier
.r#type
.strip_prefix("minecraft:")
.unwrap()
.to_uppercase()
);
let id = LitStr::new(&modifier.id, Span::call_site());
let amount = modifier.amount;
let operation = Ident::new(&format!("{:?}", modifier.operation), Span::call_site());
let slot = modifier.slot.to_tokens();
quote! {
Modifier {
r#type: &Attributes::#r#type,
id: #id,
amount: #amount,
operation: Operation::#operation,
slot: #slot,
}
}
});
tokens.extend(quote! {
(AttributeModifiers, &AttributeModifiersImpl {
attribute_modifiers: Cow::Borrowed(&[#(#modifier_code),*])
}),
});
};
if let Some(tool) = &self.tool {
let rules_code = tool.rules.iter().map(|rule| {
let block_array;
if let RegistryEntryList::Single(t) = &rule.blocks {
if let TagType::Item(str) = t {
let ident = format_ident!(
"{}",
str.strip_prefix("minecraft:").unwrap().to_uppercase()
);
block_array = quote! {
Blocks(Cow::Borrowed(&[&Block::#ident]))
}
} else if let TagType::Tag(str) = t {
let ident = format_ident!(
"{}",
str.replace(":", "_").replace("/", "_").to_uppercase()
);
block_array = quote! {
Tag(&tag::Block::#ident)
}
} else {
unreachable!();
}
} else if let RegistryEntryList::Many(t) = &rule.blocks {
let mut array = vec![];
for i in t {
let TagType::Item(str) = i else {
unreachable!();
};
let ident = format_ident!(
"{}",
str.strip_prefix("minecraft:").unwrap().to_uppercase()
);
array.push(quote! {
&Block::#ident
});
}
block_array = quote! {
Blocks(Cow::Borrowed(&[#(#array),*]))
}
} else {
unreachable!();
}
let speed = match rule.speed {
Some(speed) => {
quote! { Some(#speed) }
}
None => quote! { None },
};
let correct_for_drops = match rule.correct_for_drops {
Some(correct_for_drops) => {
let correct_for_drops = LitBool::new(correct_for_drops, Span::call_site());
quote! { Some(#correct_for_drops) }
}
None => quote! { None },
};
quote! {
ToolRule {
blocks: #block_array,
speed: #speed,
correct_for_drops: #correct_for_drops
}
}
});
let damage_per_block = {
let speed = LitInt::new(&tool.damage_per_block.to_string(), Span::call_site());
quote! { #speed }
};
let default_mining_speed = {
let speed = LitFloat::new(
&format!("{:.1}", tool.default_mining_speed),
Span::call_site(),
);
quote! { #speed }
};
let can_destroy_blocks_in_creative =
LitBool::new(tool.can_destroy_blocks_in_creative, Span::call_site());
tokens.extend(quote! { (Tool, &ToolImpl {
rules: Cow::Borrowed(&[#(#rules_code),*]),
default_mining_speed: #default_mining_speed,
damage_per_block: #damage_per_block,
can_destroy_blocks_in_creative: #can_destroy_blocks_in_creative
}), });
};
if let Some(food) = &self.food {
let nutrition = LitInt::new(&food.nutrition.to_string(), Span::call_site());
let saturation = LitFloat::new(&format!("{:.1}", food.saturation), Span::call_site());
let can_always_eat = {
let can = LitBool::new(food.can_always_eat, Span::call_site());
quote! { #can }
};
tokens.extend(quote! { (Food, &FoodImpl {
nutrition: #nutrition,
saturation: #saturation,
can_always_eat: #can_always_eat,
}), });
};
}
}
fn return_1u32() -> u32 {
1
}
fn return_1f32() -> f32 {
1.
}
fn return_true() -> bool {
true
}
#[derive(Deserialize, Clone, Debug)]
pub struct ToolComponent {
rules: Vec<ToolRule>,
#[serde(default = "return_1f32")]
default_mining_speed: f32,
#[serde(default = "return_1u32")]
damage_per_block: u32,
#[serde(default = "return_true")]
can_destroy_blocks_in_creative: bool,
}
fn return_false() -> bool {
false
}
#[derive(Deserialize, Copy, Clone, Debug)]
pub struct FoodComponent {
nutrition: u8,
saturation: f32,
#[serde(default = "return_false")]
can_always_eat: bool,
}
#[derive(Deserialize, Clone, Debug)]
pub struct ToolRule {
blocks: RegistryEntryList,
speed: Option<f32>,
correct_for_drops: Option<bool>,
}
#[derive(Deserialize, Clone)]
pub struct Modifier {
pub r#type: String,
pub id: String,
pub amount: f64,
pub operation: Operation,
// TODO: Make this an enum
pub slot: AttributeModifierSlot,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
#[allow(clippy::enum_variant_names)]
pub enum Operation {
AddValue,
AddMultipliedBase,
AddMultipliedTotal,
}
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/items.json");
let items: HashMap<String, Item> =
serde_json::from_str(&fs::read_to_string("../assets/items.json").unwrap())
.expect("Failed to parse items.json");
let mut type_from_raw_id_arms = TokenStream::new();
let mut type_from_name = TokenStream::new();
let mut constants = TokenStream::new();
for (name, item) in items {
let const_ident = format_ident!("{}", name.to_shouty_snake_case());
let components = &item.components;
let components_tokens = components.to_token_stream();
let id_lit = LitInt::new(&item.id.to_string(), Span::call_site());
constants.extend(quote! {
pub const #const_ident: Item = Item {
id: #id_lit,
registry_key: #name,
components: &[#components_tokens],
};
});
type_from_raw_id_arms.extend(quote! {
#id_lit => Some(&Self::#const_ident),
});
type_from_name.extend(quote! {
#name => Some(&Self::#const_ident),
});
}
quote! {
use crate::data_component::DataComponent::*;
use crate::data_component_impl::*;
use crate::tag::{RegistryKey, Taggable};
use pumpkin_util::text::TextComponent;
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
use crate::{tag, AttributeModifierSlot};
use crate::attributes::Attributes;
use crate::data_component_impl::IDSet::{Blocks, Tag};
use crate::data_component::DataComponent;
use crate::Block;
#[derive(Clone, Debug)]
pub struct Item {
pub id: u16,
pub registry_key: &'static str,
pub components: &'static [(DataComponent, &'static dyn DataComponentImpl)],
}
impl PartialEq for Item {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for Item {}
impl Hash for Item {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl Item {
#constants
pub fn translated_name(&self) -> TextComponent {
TextComponent::translate(
self.components
.iter()
.find_map(|(id, data)| if id == &ItemName {
Some(data.as_any().downcast_ref::<ItemNameImpl>().unwrap().name)
} else {
None
}
).unwrap(),
&[],
)
}
#[doc = "Try to parse an item from a resource location string."]
pub fn from_registry_key(name: &str) -> Option<&'static Self> {
match name {
#type_from_name
_ => None
}
}
#[doc = "Try to parse an item from a raw id."]
pub const fn from_id(id: u16) -> Option<&'static Self> {
match id {
#type_from_raw_id_arms
_ => None
}
}
}
impl Taggable for Item {
#[inline]
fn tag_key() -> RegistryKey {
RegistryKey::Item
}
#[inline]
fn registry_key(&self) -> &str {
self.registry_key
}
#[inline]
fn registry_id(&self) -> u16 {
self.id
}
}
}
}