Parse Items at Compile time

This commit is contained in:
Alexander Medvedev 2025-02-08 19:23:59 +01:00
parent 5c5bc67b3a
commit 8592d095f3
43 changed files with 463 additions and 272 deletions

1
assets/spawn_egg.json Normal file
View File

@ -0,0 +1 @@
{"1130":"wither_skeleton","1071":"creeper","1094":"ocelot","1114":"snow_golem","1125":"vindicator","1090":"llama","1129":"wither","1134":"zombie","1080":"fox","1085":"guardian","1076":"ender_dragon","1102":"polar_bear","1081":"frog","1131":"wolf","1070":"cow","1111":"skeleton_horse","1122":"turtle","1096":"parrot","1104":"rabbit","1093":"mule","1092":"mooshroom","1106":"salmon","1082":"ghast","1126":"wandering_trader","1109":"silverfish","1132":"zoglin","1065":"cat","1062":"blaze","1068":"chicken","1072":"dolphin","1103":"pufferfish","1108":"shulker","1119":"tadpole","1059":"axolotl","1069":"cod","1112":"slime","1113":"sniffer","1137":"zombified_piglin","1087":"horse","1105":"ravager","1110":"skeleton","1083":"glow_squid","1066":"camel","1067":"cave_spider","1057":"armadillo","1086":"hoglin","1118":"strider","1077":"enderman","1124":"villager","1128":"witch","1107":"sheep","1063":"bogged","1120":"trader_llama","1074":"drowned","1064":"breeze","1075":"elder_guardian","1115":"spider","1097":"phantom","1116":"squid","1101":"pillager","1127":"warden","1078":"endermite","1061":"bee","1089":"iron_golem","1098":"pig","1135":"zombie_horse","1117":"stray","1133":"creaking","1073":"donkey","1095":"panda","1136":"zombie_villager","1091":"magma_cube","1099":"piglin","1100":"piglin_brute","1084":"goat","1058":"allay","1121":"tropical_fish","1060":"bat","1079":"evoker","1088":"husk","1123":"vex"}

View File

@ -5,7 +5,10 @@ edition.workspace = true
build = "build/build.rs" build = "build/build.rs"
[dependencies] [dependencies]
pumpkin-util = { path = "../pumpkin-util" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true
[build-dependencies] [build-dependencies]
pumpkin-util = { path = "../pumpkin-util" } pumpkin-util = { path = "../pumpkin-util" }

View File

@ -7,8 +7,8 @@ pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/biome.json"); println!("cargo:rerun-if-changed=../assets/biome.json");
let biomes: Vec<String> = serde_json::from_str(include_str!("../../assets/biome.json")) let biomes: Vec<String> = serde_json::from_str(include_str!("../../assets/biome.json"))
.expect("Failed to parse entity_pose.json"); .expect("Failed to parse biome.json");
let variants = array_to_tokenstream(biomes); let variants = array_to_tokenstream(&biomes);
quote! { quote! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View File

@ -10,6 +10,7 @@ mod damage_type;
mod entity_pose; mod entity_pose;
mod entity_type; mod entity_type;
mod game_event; mod game_event;
mod item;
mod message_type; mod message_type;
mod noise_parameter; mod noise_parameter;
mod packet; mod packet;
@ -18,6 +19,7 @@ mod scoreboard_slot;
mod screen; mod screen;
mod sound; mod sound;
mod sound_category; mod sound_category;
mod spawn_egg;
mod world_event; mod world_event;
pub fn main() { pub fn main() {
@ -36,9 +38,11 @@ pub fn main() {
write_generated_file(biome::build(), "biome.rs"); write_generated_file(biome::build(), "biome.rs");
write_generated_file(damage_type::build(), "damage_type.rs"); write_generated_file(damage_type::build(), "damage_type.rs");
write_generated_file(message_type::build(), "message_type.rs"); write_generated_file(message_type::build(), "message_type.rs");
write_generated_file(spawn_egg::build(), "spawn_egg.rs");
write_generated_file(item::build(), "item.rs");
} }
pub fn array_to_tokenstream(array: Vec<String>) -> TokenStream { pub fn array_to_tokenstream(array: &[String]) -> TokenStream {
let mut variants = TokenStream::new(); let mut variants = TokenStream::new();
for item in array.iter() { for item in array.iter() {

View File

@ -3,7 +3,7 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use syn::LitInt; use syn::{Ident, LitInt};
#[derive(Deserialize)] #[derive(Deserialize)]
struct DamageTypeEntry { struct DamageTypeEntry {
@ -13,10 +13,26 @@ struct DamageTypeEntry {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct DamageTypeData { pub struct DamageTypeData {
death_message_type: Option<String>, death_message_type: Option<DeathMessageType>,
exhaustion: f32, exhaustion: f32,
message_id: String, message_id: String,
scaling: String, scaling: DamageScaling,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum DamageScaling {
Never,
WhenCausedByLivingNonPlayer,
Always,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "snake_case")]
pub enum DeathMessageType {
Default,
FallVariants,
IntentionalGameDesign,
} }
pub(crate) fn build() -> TokenStream { pub(crate) fn build() -> TokenStream {
@ -36,13 +52,20 @@ pub(crate) fn build() -> TokenStream {
let data = &entry.components; let data = &entry.components;
let death_message_type = match &data.death_message_type { let death_message_type = match &data.death_message_type {
Some(msg) => quote! { Some(#msg) }, Some(msg) => {
let msg_ident = Ident::new(&format!("{:?}", msg), proc_macro2::Span::call_site());
quote! { Some(DeathMessageType::#msg_ident) }
}
None => quote! { None }, None => quote! { None },
}; };
let exhaustion = data.exhaustion; let exhaustion = data.exhaustion;
let message_id = &data.message_id; let message_id = &data.message_id;
let scaling = &data.scaling; let scaling_ident = Ident::new(
&format!("{:?}", data.scaling),
proc_macro2::Span::call_site(),
);
let scaling = quote! {DamageScaling::#scaling_ident};
let id_lit = LitInt::new(&entry.id.to_string(), proc_macro2::Span::call_site()); let id_lit = LitInt::new(&entry.id.to_string(), proc_macro2::Span::call_site());
constants.push(quote! { constants.push(quote! {
@ -68,13 +91,27 @@ pub(crate) fn build() -> TokenStream {
quote! { quote! {
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct DamageType { pub struct DamageType {
pub death_message_type: Option<&'static str>, pub death_message_type: Option<DeathMessageType>,
pub exhaustion: f32, pub exhaustion: f32,
pub message_id: &'static str, pub message_id: &'static str,
pub scaling: &'static str, pub scaling: DamageScaling,
pub id: u32, pub id: u32,
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DeathMessageType {
Default,
FallVariants,
IntentionalGameDesign,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DamageScaling {
Never,
WhenCausedByLivingNonPlayer,
Always,
}
impl DamageType { impl DamageType {
#(#constants)* #(#constants)*

View File

@ -8,7 +8,7 @@ pub(crate) fn build() -> TokenStream {
let poses: Vec<String> = serde_json::from_str(include_str!("../../assets/entity_pose.json")) let poses: Vec<String> = serde_json::from_str(include_str!("../../assets/entity_pose.json"))
.expect("Failed to parse entity_pose.json"); .expect("Failed to parse entity_pose.json");
let variants = array_to_tokenstream(poses); let variants = array_to_tokenstream(&poses);
quote! { quote! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View File

@ -9,7 +9,7 @@ pub(crate) fn build() -> TokenStream {
let game_events: Vec<String> = let game_events: Vec<String> =
serde_json::from_str(include_str!("../../assets/game_event.json")) serde_json::from_str(include_str!("../../assets/game_event.json"))
.expect("Failed to parse game_event.json"); .expect("Failed to parse game_event.json");
let variants = array_to_tokenstream(game_events); let variants = array_to_tokenstream(&game_events);
quote! { quote! {
pub enum GameEvent { pub enum GameEvent {

239
pumpkin-data/build/item.rs Normal file
View File

@ -0,0 +1,239 @@
use heck::ToShoutySnakeCase;
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use serde::Deserialize;
use std::collections::HashMap;
use syn::{Ident, LitInt, LitStr};
#[derive(Deserialize, Clone, Debug)]
pub struct Item {
pub id: u16,
pub components: ItemComponents,
}
#[derive(Deserialize, Clone, Debug)]
pub struct ItemComponents {
#[serde(rename = "minecraft:item_name")]
// TODO: TextComponent
pub item_name: Option<String>,
#[serde(rename = "minecraft:max_stack_size")]
pub max_stack_size: u8,
#[serde(rename = "minecraft:jukebox_playable")]
pub jukebox_playable: Option<JukeboxPlayable>,
#[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<AttributeModifiers>,
}
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());
let jukebox_playable = match &self.jukebox_playable {
Some(playable) => {
let song = LitStr::new(&playable.song, Span::call_site());
quote! { Some(JukeboxPlayable { song: #song }) }
}
None => quote! { None },
};
let item_name = match &self.item_name {
Some(d) => {
let item_name = LitStr::new(d, Span::call_site());
quote! { Some(#item_name) }
}
None => quote! { None },
};
let damage = match self.damage {
Some(d) => {
let damage_lit = LitInt::new(&d.to_string(), Span::call_site());
quote! { Some(#damage_lit) }
}
None => quote! { None },
};
let max_damage = match self.max_damage {
Some(md) => {
let max_damage_lit = LitInt::new(&md.to_string(), Span::call_site());
quote! { Some(#max_damage_lit) }
}
None => quote! { None },
};
let attribute_modifiers = match &self.attribute_modifiers {
Some(modifiers) => {
let modifier_code = modifiers.modifiers.iter().map(|modifier| {
let r#type = LitStr::new(&modifier.r#type, Span::call_site());
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 = LitStr::new(&modifier.slot, Span::call_site());
quote! {
Modifier {
r#type: #r#type,
id: #id,
amount: #amount,
operation: Operation::#operation,
slot: #slot,
}
}
});
quote! { Some(AttributeModifiers { modifiers: &[#(#modifier_code),*] }) }
}
None => quote! { None },
};
tokens.extend(quote! {
ItemComponents {
item_name: #item_name,
max_stack_size: #max_stack_size,
jukebox_playable: #jukebox_playable,
damage: #damage,
max_damage: #max_damage,
attribute_modifiers: #attribute_modifiers,
}
});
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct JukeboxPlayable {
pub song: String,
}
#[derive(Deserialize, Clone, Debug)]
pub struct AttributeModifiers {
pub modifiers: Vec<Modifier>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct Modifier {
pub r#type: String,
pub id: String,
pub amount: f64,
pub operation: Operation,
// TODO: Make this an enum
pub slot: String,
}
#[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(include_str!("../../assets/items.json"))
.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(), proc_macro2::Span::call_site());
constants.extend(quote! {
pub const #const_ident: Item = Item {
id: #id_lit,
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 pumpkin_util::text::TextComponent;
#[derive(Clone, Copy, Debug)]
pub struct Item {
pub id: u16,
pub components: ItemComponents,
}
#[derive(Clone, Copy, Debug)]
pub struct ItemComponents {
pub item_name: Option<&'static str>,
pub max_stack_size: u8,
pub jukebox_playable: Option<JukeboxPlayable>,
pub damage: Option<u16>,
pub max_damage: Option<u16>,
pub attribute_modifiers: Option<AttributeModifiers>,
}
#[derive(Clone, Copy, Debug)]
pub struct JukeboxPlayable {
pub song: &'static str,
}
#[derive(Clone, Copy, Debug)]
pub struct AttributeModifiers {
pub modifiers: &'static [Modifier],
}
#[derive(Clone, Copy, Debug)]
pub struct Modifier {
pub r#type: &'static str,
pub id: &'static str,
pub amount: f64,
pub operation: Operation,
// TODO: Make this an enum
pub slot: &'static str,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Operation {
AddValue,
AddMultipliedBase,
AddMultipliedTotal,
}
impl Item {
#constants
pub fn translated_name(&self) -> TextComponent {
serde_json::from_str(self.components.item_name.unwrap()).expect("Could not parse item name.")
}
#[doc = r" Try to parse a Item from a resource location string"]
pub fn from_name(name: &str) -> Option<Self> {
match name {
#type_from_name
_ => None
}
}
#[doc = r" Try to parse a Item from a raw id"]
pub const fn from_id(id: u16) -> Option<Self> {
match id {
#type_from_raw_id_arms
_ => None
}
}
}
}
}

View File

@ -8,7 +8,7 @@ pub(crate) fn build() -> TokenStream {
let particle: Vec<String> = serde_json::from_str(include_str!("../../assets/particles.json")) let particle: Vec<String> = serde_json::from_str(include_str!("../../assets/particles.json"))
.expect("Failed to parse particles.json"); .expect("Failed to parse particles.json");
let variants = array_to_tokenstream(particle); let variants = array_to_tokenstream(&particle);
quote! { quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View File

@ -9,7 +9,7 @@ pub(crate) fn build() -> TokenStream {
let sound_categories: Vec<String> = let sound_categories: Vec<String> =
serde_json::from_str(include_str!("../../assets/scoreboard_display_slot.json")) serde_json::from_str(include_str!("../../assets/scoreboard_display_slot.json"))
.expect("Failed to parse scoreboard_display_slot.json"); .expect("Failed to parse scoreboard_display_slot.json");
let variants = array_to_tokenstream(sound_categories); let variants = array_to_tokenstream(&sound_categories);
quote! { quote! {
pub enum ScoreboardDisplaySlot { pub enum ScoreboardDisplaySlot {

View File

@ -8,7 +8,7 @@ pub(crate) fn build() -> TokenStream {
let screens: Vec<String> = serde_json::from_str(include_str!("../../assets/screens.json")) let screens: Vec<String> = serde_json::from_str(include_str!("../../assets/screens.json"))
.expect("Failed to parse screens.json"); .expect("Failed to parse screens.json");
let variants = array_to_tokenstream(screens); let variants = array_to_tokenstream(&screens);
quote! { quote! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

View File

@ -9,7 +9,7 @@ pub(crate) fn build() -> TokenStream {
let sound: Vec<String> = serde_json::from_str(include_str!("../../assets/sounds.json")) let sound: Vec<String> = serde_json::from_str(include_str!("../../assets/sounds.json"))
.expect("Failed to parse sounds.json"); .expect("Failed to parse sounds.json");
let variants = array_to_tokenstream(sound.clone()); let variants = array_to_tokenstream(&sound);
let type_from_name = &sound let type_from_name = &sound
.iter() .iter()

View File

@ -9,7 +9,7 @@ pub(crate) fn build() -> TokenStream {
let sound_categories: Vec<String> = let sound_categories: Vec<String> =
serde_json::from_str(include_str!("../../assets/sound_category.json")) serde_json::from_str(include_str!("../../assets/sound_category.json"))
.expect("Failed to parse sound_category.json"); .expect("Failed to parse sound_category.json");
let variants = array_to_tokenstream(sound_categories); let variants = array_to_tokenstream(&sound_categories);
quote! { quote! {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]

View File

@ -0,0 +1,29 @@
use std::collections::HashMap;
use heck::ToShoutySnakeCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub(crate) fn build() -> TokenStream {
println!("cargo:rerun-if-changed=../assets/spawn_egg.json");
let eggs: HashMap<u16, String> =
serde_json::from_str(include_str!("../../assets/spawn_egg.json"))
.expect("Failed to parse spawn_egg.json");
let mut names = TokenStream::new();
for (egg, entity) in &eggs {
let entity = entity.to_shouty_snake_case();
let entity = format_ident!("{}", entity);
names.extend(quote! { #egg => Some(EntityType::#entity), });
}
quote! {
pub fn entity_from_egg(id: u16) -> Option<EntityType> {
match id {
#names
_ => None
}
}
}
}

View File

@ -1,3 +1,7 @@
pub mod item {
include!(concat!(env!("OUT_DIR"), "/item.rs"));
}
pub mod packet { pub mod packet {
include!(concat!(env!("OUT_DIR"), "/packet.rs")); include!(concat!(env!("OUT_DIR"), "/packet.rs"));
} }
@ -26,6 +30,7 @@ pub mod game_event {
} }
pub mod entity { pub mod entity {
include!(concat!(env!("OUT_DIR"), "/spawn_egg.rs"));
include!(concat!(env!("OUT_DIR"), "/entity_type.rs")); include!(concat!(env!("OUT_DIR"), "/entity_type.rs"));
include!(concat!(env!("OUT_DIR"), "/entity_pose.rs")); include!(concat!(env!("OUT_DIR"), "/entity_pose.rs"));
} }

View File

@ -1,7 +1,7 @@
use pumpkin_data::item::Item;
use pumpkin_registry::{ use pumpkin_registry::{
flatten_3x3, get_tag_values, IngredientSlot, IngredientType, RecipeResult, TagCategory, RECIPES, flatten_3x3, get_tag_values, IngredientSlot, IngredientType, RecipeResult, TagCategory, RECIPES,
}; };
use pumpkin_world::item::registry::get_item;
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
use rayon::prelude::*; use rayon::prelude::*;
@ -17,7 +17,9 @@ fn check_ingredient_type(ingredient_type: &IngredientType, input: ItemStack) ->
.iter() .iter()
.any(|tag| check_ingredient_type(&tag.to_ingredient_type(), input)) .any(|tag| check_ingredient_type(&tag.to_ingredient_type(), input))
} }
IngredientType::Item(item) => get_item(item).is_some_and(|item| item.id == input.item_id), IngredientType::Item(item) => {
Item::from_name(item).is_some_and(|item| item.id == input.item.id)
}
} }
} }
@ -52,11 +54,11 @@ pub fn check_if_matches_crafting(input: [[Option<ItemStack>; 3]; 3]) -> Option<I
}) })
.map(|recipe| match recipe.result() { .map(|recipe| match recipe.result() {
RecipeResult::Single { id, .. } => Some(ItemStack { RecipeResult::Single { id, .. } => Some(ItemStack {
item_id: get_item(id).unwrap().id, item: Item::from_name(id).unwrap(),
item_count: 1, item_count: 1,
}), }),
RecipeResult::Many { id, count, .. } => Some(ItemStack { RecipeResult::Many { id, count, .. } => Some(ItemStack {
item_id: get_item(id).unwrap().id, item: Item::from_name(id).unwrap(),
item_count: *count, item_count: *count,
}), }),
RecipeResult::Special => None, RecipeResult::Special => None,

View File

@ -91,7 +91,7 @@ impl DragHandler {
single_item.item_count = 1; single_item.item_count = 1;
let changing_slots = let changing_slots =
drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); drag.possibly_changing_slots(&slots_cloned, carried_item.item.id);
changing_slots.for_each(|slot| { changing_slots.for_each(|slot| {
if carried_item.item_count != 0 { if carried_item.item_count != 0 {
carried_item.item_count -= 1; carried_item.item_count -= 1;
@ -117,7 +117,7 @@ impl DragHandler {
// In that specific case, follow MouseDragType::Right behaviours instead! // In that specific case, follow MouseDragType::Right behaviours instead!
let changing_slots = let changing_slots =
drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); drag.possibly_changing_slots(&slots_cloned, carried_item.item.id);
let amount_of_slots = changing_slots.clone().count(); let amount_of_slots = changing_slots.clone().count();
let (amount_per_slot, remainder) = if amount_of_slots == 0 { let (amount_per_slot, remainder) = if amount_of_slots == 0 {
// TODO: please work lol // TODO: please work lol
@ -160,7 +160,7 @@ impl Drag {
match slot { match slot {
Some(item_slot) => { Some(item_slot) => {
if item_slot.item_id == carried_item_id { if item_slot.item.id == carried_item_id {
Some(*slot_index) Some(*slot_index)
} else { } else {
None None

View File

@ -36,7 +36,7 @@ pub trait Container: Sync + Send {
if taking_crafted { if taking_crafted {
match (all_slots[slot].as_mut(), carried_item.as_mut()) { match (all_slots[slot].as_mut(), carried_item.as_mut()) {
(Some(s1), Some(s2)) => { (Some(s1), Some(s2)) => {
if s1.item_id == s2.item_id { if s1.item.id == s2.item.id {
handle_item_change(all_slots[slot], carried_item, mouse_click); handle_item_change(all_slots[slot], carried_item, mouse_click);
} }
} }
@ -152,7 +152,7 @@ pub fn handle_item_change(
match (current_slot.as_mut(), carried_slot.as_mut()) { match (current_slot.as_mut(), carried_slot.as_mut()) {
// Swap or combine current and carried // Swap or combine current and carried
(Some(current), Some(carried)) => { (Some(current), Some(carried)) => {
if current.item_id == carried.item_id { if current.item.id == carried.item.id {
combine_stacks(carried_slot, current, mouse_click); combine_stacks(carried_slot, current, mouse_click);
} else if mouse_click == MouseClick::Left { } else if mouse_click == MouseClick::Left {
let carried = *carried; let carried = *carried;

View File

@ -135,7 +135,7 @@ impl PlayerInventory {
pub fn get_slot_with_item(&self, item_id: u16, max_stack: u8) -> Option<usize> { pub fn get_slot_with_item(&self, item_id: u16, max_stack: u8) -> Option<usize> {
for slot in 9..=44 { for slot in 9..=44 {
match &self.items[slot - 9] { match &self.items[slot - 9] {
Some(item) if item.item_id == item_id && item.item_count <= max_stack => { Some(item) if item.item.id == item_id && item.item_count <= max_stack => {
return Some(slot) return Some(slot)
} }
_ => continue, _ => continue,

View File

@ -7,6 +7,8 @@ edition.workspace = true
proc-macro = true proc-macro = true
[dependencies] [dependencies]
pumpkin-data = { path = "../pumpkin-data" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true

View File

@ -1,4 +1,5 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use pumpkin_data::item::Item;
use quote::quote; use quote::quote;
use syn::{ use syn::{
parse::{Nothing, Parser}, parse::{Nothing, Parser},
@ -222,18 +223,15 @@ pub fn pumpkin_item(input: TokenStream, item: TokenStream) -> TokenStream {
let input_string = input.to_string(); let input_string = input.to_string();
let packet_name = input_string.trim_matches('"'); let packet_name = input_string.trim_matches('"');
let packet_name_split: Vec<&str> = packet_name.split(":").collect(); let item_id = Item::from_name(packet_name).unwrap();
let id = item_id.id;
let namespace = packet_name_split[0];
let id = packet_name_split[1];
let item: proc_macro2::TokenStream = item.into(); let item: proc_macro2::TokenStream = item.into();
let gen = quote! { let gen = quote! {
#item #item
impl #impl_generics crate::item::pumpkin_item::ItemMetadata for #name #ty_generics { impl #impl_generics crate::item::pumpkin_item::ItemMetadata for #name #ty_generics {
const NAMESPACE: &'static str = #namespace; const ID: u16 = #id;
const ID: &'static str = #id;
} }
}; };

View File

@ -1,4 +1,5 @@
use crate::VarInt; use crate::VarInt;
use pumpkin_data::item::Item;
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
use serde::ser::SerializeSeq; use serde::ser::SerializeSeq;
use serde::{ use serde::{
@ -131,8 +132,9 @@ impl Serialize for Slot {
impl Slot { impl Slot {
pub fn to_item(self) -> Option<ItemStack> { pub fn to_item(self) -> Option<ItemStack> {
let item_id = self.item_id?.0.try_into().unwrap(); let item_id = self.item_id?.0.try_into().unwrap();
let item = Item::from_id(item_id)?;
Some(ItemStack { Some(ItemStack {
item_id, item,
item_count: self.item_count.0.try_into().unwrap(), item_count: self.item_count.0.try_into().unwrap(),
}) })
} }
@ -153,7 +155,7 @@ impl From<&ItemStack> for Slot {
fn from(item: &ItemStack) -> Self { fn from(item: &ItemStack) -> Self {
Slot { Slot {
item_count: item.item_count.into(), item_count: item.item_count.into(),
item_id: Some(VarInt(item.item_id as i32)), item_id: Some(VarInt(item.item.id as i32)),
// TODO: add these // TODO: add these
num_components_to_add: None, num_components_to_add: None,
num_components_to_remove: None, num_components_to_remove: None,

View File

@ -10,7 +10,7 @@ impl ItemStack {
838, // Diamond 838, // Diamond
843, // Netherite 843, // Netherite
] ]
.contains(&self.item_id) .contains(&self.item.id)
} }
pub fn is_helmet(&self) -> bool { pub fn is_helmet(&self) -> bool {
@ -24,7 +24,7 @@ impl ItemStack {
872, // Iron 872, // Iron
864, 864,
] ]
.contains(&self.item_id) .contains(&self.item.id)
} }
pub fn is_chestplate(&self) -> bool { pub fn is_chestplate(&self) -> bool {
@ -38,7 +38,7 @@ impl ItemStack {
865, // Elytra 865, // Elytra
773, 773,
] ]
.contains(&self.item_id) .contains(&self.item.id)
} }
pub fn is_leggings(&self) -> bool { pub fn is_leggings(&self) -> bool {
@ -51,7 +51,7 @@ impl ItemStack {
874, // Iron 874, // Iron
866, 866,
] ]
.contains(&self.item_id) .contains(&self.item.id)
} }
pub fn is_boots(&self) -> bool { pub fn is_boots(&self) -> bool {
@ -64,6 +64,6 @@ impl ItemStack {
875, // Iron 875, // Iron
867, 867,
] ]
.contains(&self.item_id) .contains(&self.item.id)
} }
} }

View File

@ -1,6 +1,6 @@
use pumpkin_data::item::Item;
mod categories; mod categories;
pub mod registry;
pub use registry::ITEMS;
#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
/// Item Rarity /// Item Rarity
@ -13,23 +13,18 @@ pub enum Rarity {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ItemStack { pub struct ItemStack {
// This ID is the numerical protocol ID, not the usual minecraft::block ID.
pub item_id: u16,
pub item_count: u8, pub item_count: u8,
// TODO: Add Item Components pub item: Item,
} }
impl PartialEq for ItemStack { impl PartialEq for ItemStack {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.item_id == other.item_id self.item.id == other.item.id
} }
} }
impl ItemStack { impl ItemStack {
pub fn new(item_count: u8, item_id: u16) -> Self { pub fn new(item_count: u8, item: Item) -> Self {
Self { Self { item_count, item }
item_count,
item_id,
}
} }
} }

View File

@ -1,98 +0,0 @@
use std::collections::HashMap;
use std::sync::LazyLock;
use pumpkin_util::text::TextComponent;
use serde::Deserialize;
const ITEMS_JSON: &str = include_str!("../../../assets/items.json");
pub static ITEMS: LazyLock<HashMap<String, Item>> = LazyLock::new(|| {
serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry.")
});
pub static ITEMS_REGISTRY_NAME_BY_ID: LazyLock<HashMap<u16, String>> = LazyLock::new(|| {
let mut map = HashMap::new();
for item in ITEMS.clone() {
map.insert(item.1.id, item.0.clone());
}
map
});
pub fn get_item(name: &str) -> Option<&Item> {
ITEMS.get(&name.replace("minecraft:", ""))
}
pub fn get_name_by_id<'a>(item_id: u16) -> Option<&'a String> {
ITEMS_REGISTRY_NAME_BY_ID.get(&item_id)
}
pub fn get_item_by_id<'a>(item_id: u16) -> Option<&'a Item> {
ITEMS.values().find(|&item| item.id == item_id)
}
pub fn get_spawn_egg(item_id: u16) -> Option<String> {
if let Some(item_name) = ITEMS_REGISTRY_NAME_BY_ID.get(&item_id) {
if item_name.ends_with("_spawn_egg") {
if let Some(res) = item_name.strip_suffix("_spawn_egg") {
return Some(res.to_owned());
}
}
};
None
}
#[derive(Deserialize, Clone, Debug)]
pub struct Item {
pub id: u16,
pub components: ItemComponents,
}
impl Item {
pub fn translated_name(&self) -> TextComponent {
serde_json::from_str(&self.components.item_name).expect("Could not parse item name.")
}
}
#[derive(Deserialize, Clone, Debug)]
pub struct ItemComponents {
#[serde(rename = "minecraft:item_name")]
pub item_name: String,
#[serde(rename = "minecraft:max_stack_size")]
pub max_stack_size: u8,
#[serde(rename = "minecraft:jukebox_playable")]
pub jukebox_playable: Option<JukeboxPlayable>,
#[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<AttributeModifiers>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct JukeboxPlayable {
pub song: String,
}
#[derive(Deserialize, Clone, Debug)]
pub struct AttributeModifiers {
pub modifiers: Vec<Modifier>,
}
#[derive(Deserialize, Clone, Debug)]
pub struct Modifier {
pub r#type: String,
pub id: String,
pub amount: f64,
pub operation: Operation,
// TODO: Make this an enum
pub slot: String,
}
#[derive(Deserialize, Clone, Debug, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum Operation {
AddValue,
AddMultipliedBase,
AddMultipliedTotal,
}

View File

@ -1,4 +1,5 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_data::{ use pumpkin_data::{
screen::WindowType, screen::WindowType,
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
@ -7,10 +8,7 @@ use pumpkin_inventory::{Chest, OpenContainer};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_protocol::{client::play::CBlockAction, codec::var_int::VarInt}; use pumpkin_protocol::{client::play::CBlockAction, codec::var_int::VarInt};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{ use pumpkin_world::block::registry::{get_block, Block};
block::registry::{get_block, Block},
item::registry::Item,
};
use crate::{ use crate::{
block::{pumpkin_block::PumpkinBlock, registry::BlockActionResult}, block::{pumpkin_block::PumpkinBlock, registry::BlockActionResult},

View File

@ -3,11 +3,12 @@ use crate::block::registry::BlockActionResult;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_data::screen::WindowType; use pumpkin_data::screen::WindowType;
use pumpkin_inventory::{CraftingTable, OpenContainer}; use pumpkin_inventory::{CraftingTable, OpenContainer};
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::{block::registry::Block, item::registry::Item}; use pumpkin_world::block::registry::Block;
#[pumpkin_block("minecraft:crafting_table")] #[pumpkin_block("minecraft:crafting_table")]
pub struct CraftingTableBlock; pub struct CraftingTableBlock;

View File

@ -1,12 +1,12 @@
use crate::block::registry::BlockActionResult; use crate::block::registry::BlockActionResult;
use crate::entity::player::Player; use crate::entity::player::Player;
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_data::screen::WindowType; use pumpkin_data::screen::WindowType;
use pumpkin_inventory::Furnace; use pumpkin_inventory::Furnace;
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
use crate::{block::pumpkin_block::PumpkinBlock, server::Server}; use crate::{block::pumpkin_block::PumpkinBlock, server::Server};

View File

@ -3,11 +3,11 @@ use crate::block::registry::BlockActionResult;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_macros::pumpkin_block; use pumpkin_macros::pumpkin_block;
use pumpkin_registry::SYNCED_REGISTRIES; use pumpkin_registry::SYNCED_REGISTRIES;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
#[pumpkin_block("minecraft:jukebox")] #[pumpkin_block("minecraft:jukebox")]
pub struct JukeboxBlock; pub struct JukeboxBlock;

View File

@ -2,6 +2,7 @@ use blocks::chest::ChestBlock;
use blocks::furnace::FurnaceBlock; use blocks::furnace::FurnaceBlock;
use properties::BlockPropertiesManager; use properties::BlockPropertiesManager;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
@ -43,7 +44,10 @@ pub async fn drop_loot(server: &Server, world: &Arc<World>, block: &Block, pos:
); );
let entity = server.add_entity(pos, EntityType::ITEM, world); let entity = server.add_entity(pos, EntityType::ITEM, world);
let item_entity = Arc::new(ItemEntity::new(entity, &ItemStack::new(1, block.item_id))); let item_entity = Arc::new(ItemEntity::new(
entity,
&ItemStack::new(1, Item::from_id(block.item_id).unwrap()),
));
world.spawn_entity(item_entity.clone()).await; world.spawn_entity(item_entity.clone()).await;
item_entity.send_meta_packet().await; item_entity.send_meta_packet().await;
} }

View File

@ -2,10 +2,10 @@ use crate::block::registry::BlockActionResult;
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_inventory::OpenContainer; use pumpkin_inventory::OpenContainer;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
pub trait BlockMetadata { pub trait BlockMetadata {
const NAMESPACE: &'static str; const NAMESPACE: &'static str;

View File

@ -1,10 +1,10 @@
use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock}; use crate::block::pumpkin_block::{BlockMetadata, PumpkinBlock};
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use pumpkin_data::item::Item;
use pumpkin_inventory::OpenContainer; use pumpkin_inventory::OpenContainer;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;

View File

@ -52,7 +52,7 @@ impl ArgumentConsumer for DamageTypeArgumentConsumer {
impl DefaultNameArgConsumer for DamageTypeArgumentConsumer { impl DefaultNameArgConsumer for DamageTypeArgumentConsumer {
fn default_name(&self) -> &'static str { fn default_name(&self) -> &'static str {
"damage_type" "damageType"
} }
} }

View File

@ -1,6 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_protocol::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders}; use pumpkin_protocol::client::play::{ArgumentType, CommandSuggestion, SuggestionProviders};
use pumpkin_world::item::registry::{self, Item};
use crate::{command::dispatcher::CommandError, server::Server}; use crate::{command::dispatcher::CommandError, server::Server};
@ -53,11 +53,11 @@ impl DefaultNameArgConsumer for ItemArgumentConsumer {
} }
impl<'a> FindArg<'a> for ItemArgumentConsumer { impl<'a> FindArg<'a> for ItemArgumentConsumer {
type Data = (&'a str, &'a Item); type Data = (&'a str, Item);
fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> { fn find_arg(args: &'a super::ConsumedArgs, name: &str) -> Result<Self::Data, CommandError> {
match args.get(name) { match args.get(name) {
Some(Arg::Item(name)) => registry::get_item(name).map_or_else( Some(Arg::Item(name)) => Item::from_name(&name.replace("minecraft:", "")).map_or_else(
|| { || {
Err(CommandError::GeneralCommandIssue(format!( Err(CommandError::GeneralCommandIssue(format!(
"Item {name} does not exist." "Item {name} does not exist."

View File

@ -56,7 +56,6 @@ impl CommandExecutor for GiveExecutor {
for target in targets { for target in targets {
target.give_items(item, item_count as u32).await; target.give_items(item, item_count as u32).await;
} }
let msg = if targets.len() == 1 { let msg = if targets.len() == 1 {
TextComponent::translate( TextComponent::translate(
"commands.give.success.single", "commands.give.success.single",

View File

@ -22,7 +22,7 @@ impl ItemEntity {
let slot = Slot::from(stack); let slot = Slot::from(stack);
Self { Self {
entity, entity,
id: stack.item_id, id: stack.item.id,
count: stack.item_count, count: stack.item_count,
item: slot, item: slot,
pickup_delay: AtomicI8::new(10), // Vanilla pickup_delay: AtomicI8::new(10), // Vanilla

View File

@ -13,6 +13,7 @@ use pumpkin_config::{ADVANCED_CONFIG, BASIC_CONFIG};
use pumpkin_data::{ use pumpkin_data::{
damage::DamageType, damage::DamageType,
entity::EntityType, entity::EntityType,
item::Operation,
sound::{Sound, SoundCategory}, sound::{Sound, SoundCategory},
}; };
use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::player::PlayerInventory;
@ -57,13 +58,7 @@ use pumpkin_util::{
text::TextComponent, text::TextComponent,
GameMode, GameMode,
}; };
use pumpkin_world::{ use pumpkin_world::{cylindrical_chunk_iterator::Cylindrical, item::ItemStack};
cylindrical_chunk_iterator::Cylindrical,
item::{
registry::{get_item_by_id, Operation},
ItemStack,
},
};
use tokio::sync::{Mutex, Notify, RwLock}; use tokio::sync::{Mutex, Notify, RwLock};
use super::{ use super::{
@ -294,10 +289,9 @@ impl Player {
// get attack damage // get attack damage
if let Some(item_stack) = item_slot { if let Some(item_stack) = item_slot {
if let Some(item) = get_item_by_id(item_stack.item_id) {
// TODO: this should be cached in memory // TODO: this should be cached in memory
if let Some(modifiers) = &item.components.attribute_modifiers { if let Some(modifiers) = &item_stack.item.components.attribute_modifiers {
for item_mod in &modifiers.modifiers { for item_mod in modifiers.modifiers {
if item_mod.operation == Operation::AddValue { if item_mod.operation == Operation::AddValue {
if item_mod.id == "minecraft:base_attack_damage" { if item_mod.id == "minecraft:base_attack_damage" {
add_damage = item_mod.amount; add_damage = item_mod.amount;
@ -309,7 +303,6 @@ impl Player {
} }
} }
} }
}
drop(inventory); drop(inventory);
let attack_speed = base_attack_speed + add_speed; let attack_speed = base_attack_speed + add_speed;

View File

@ -6,10 +6,11 @@ use crate::item::pumpkin_item::PumpkinItem;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound; use pumpkin_data::sound::Sound;
use pumpkin_macros::pumpkin_item; use pumpkin_macros::pumpkin_item;
use pumpkin_world::item::registry::Item;
#[pumpkin_item("minecraft:egg")] #[pumpkin_item("egg")]
pub struct EggItem; pub struct EggItem;
const POWER: f32 = 1.5; const POWER: f32 = 1.5;

View File

@ -6,10 +6,11 @@ use crate::item::pumpkin_item::PumpkinItem;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::entity::EntityType; use pumpkin_data::entity::EntityType;
use pumpkin_data::item::Item;
use pumpkin_data::sound::Sound; use pumpkin_data::sound::Sound;
use pumpkin_macros::pumpkin_item; use pumpkin_macros::pumpkin_item;
use pumpkin_world::item::registry::Item;
#[pumpkin_item("minecraft:snowball")] #[pumpkin_item("snowball")]
pub struct SnowBallItem; pub struct SnowBallItem;
const POWER: f32 = 1.5; const POWER: f32 = 1.5;

View File

@ -1,16 +1,12 @@
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use async_trait::async_trait;
use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
pub trait ItemMetadata { pub trait ItemMetadata {
const NAMESPACE: &'static str; const ID: u16;
const ID: &'static str;
fn name(&self) -> String {
format!("{}:{}", Self::NAMESPACE, Self::ID)
}
} }
#[async_trait] #[async_trait]

View File

@ -1,8 +1,8 @@
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use pumpkin_data::item::Item;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::Item;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -10,16 +10,16 @@ use super::pumpkin_item::{ItemMetadata, PumpkinItem};
#[derive(Default)] #[derive(Default)]
pub struct ItemRegistry { pub struct ItemRegistry {
blocks: HashMap<String, Arc<dyn PumpkinItem>>, items: HashMap<u16, Arc<dyn PumpkinItem>>,
} }
impl ItemRegistry { impl ItemRegistry {
pub fn register<T: PumpkinItem + ItemMetadata + 'static>(&mut self, block: T) { pub fn register<T: PumpkinItem + ItemMetadata + 'static>(&mut self, item: T) {
self.blocks.insert(block.name(), Arc::new(block)); self.items.insert(T::ID, Arc::new(item));
} }
pub async fn on_use(&self, item_name: &str, item: &Item, player: &Player, server: &Server) { pub async fn on_use(&self, item: &Item, player: &Player, server: &Server) {
let pumpkin_block = self.get_pumpkin_item(item_name); let pumpkin_block = self.get_pumpkin_item(item.id);
if let Some(pumpkin_block) = pumpkin_block { if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block.normal_use(item, player, server).await; pumpkin_block.normal_use(item, player, server).await;
} }
@ -27,23 +27,22 @@ impl ItemRegistry {
pub async fn use_on_block( pub async fn use_on_block(
&self, &self,
item_name: &str,
item: &Item, item: &Item,
player: &Player, player: &Player,
location: BlockPos, location: BlockPos,
block: &Block, block: &Block,
server: &Server, server: &Server,
) { ) {
let pumpkin_block = self.get_pumpkin_item(item_name); let pumpkin_item = self.get_pumpkin_item(item.id);
if let Some(pumpkin_block) = pumpkin_block { if let Some(pumpkin_item) = pumpkin_item {
return pumpkin_block return pumpkin_item
.use_on_block(item, player, location, block, server) .use_on_block(item, player, location, block, server)
.await; .await;
} }
} }
#[must_use] #[must_use]
pub fn get_pumpkin_item(&self, item_name: &str) -> Option<&Arc<dyn PumpkinItem>> { pub fn get_pumpkin_item(&self, item_id: u16) -> Option<&Arc<dyn PumpkinItem>> {
self.blocks.get(format!("minecraft:{item_name}").as_str()) self.items.get(&item_id)
} }
} }

View File

@ -1,5 +1,6 @@
use crate::entity::player::Player; use crate::entity::player::Player;
use crate::server::Server; use crate::server::Server;
use pumpkin_data::item::Item;
use pumpkin_data::screen::WindowType; use pumpkin_data::screen::WindowType;
use pumpkin_inventory::container_click::{ use pumpkin_inventory::container_click::{
Click, ClickType, KeyClick, MouseClick, MouseDragState, MouseDragType, Click, ClickType, KeyClick, MouseClick, MouseDragState, MouseDragType,
@ -16,7 +17,6 @@ use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::server::play::SClickContainer;
use pumpkin_util::text::TextComponent; use pumpkin_util::text::TextComponent;
use pumpkin_util::GameMode; use pumpkin_util::GameMode;
use pumpkin_world::item::registry::Item;
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
use std::sync::Arc; use std::sync::Arc;
@ -323,7 +323,7 @@ impl Player {
let find_condition = |(slot_number, slot): (usize, &mut Option<ItemStack>)| { let find_condition = |(slot_number, slot): (usize, &mut Option<ItemStack>)| {
// TODO: Check for max item count here // TODO: Check for max item count here
match slot { match slot {
Some(item) => (item.item_id == item_in_pressed_slot.item_id Some(item) => (item.item.id == item_in_pressed_slot.item.id
&& item.item_count != 64) && item.item_count != 64)
.then_some(slot_number), .then_some(slot_number),
None => Some(slot_number), None => Some(slot_number),
@ -411,7 +411,7 @@ impl Player {
**item = None; **item = None;
for slot in slots.iter_mut().filter_map(|slot| slot.as_mut()) { for slot in slots.iter_mut().filter_map(|slot| slot.as_mut()) {
if slot.item_id == carried_item.item_id { if slot.item.id == carried_item.item.id {
// TODO: Check for max stack size // TODO: Check for max stack size
if slot.item_count + carried_item.item_count <= 64 { if slot.item_count + carried_item.item_count <= 64 {
slot.item_count = 0; slot.item_count = 0;
@ -553,14 +553,14 @@ impl Player {
} }
} }
async fn pickup_items(&self, item: &Item, mut amount: u32) { async fn pickup_items(&self, item: Item, mut amount: u32) {
let max_stack = item.components.max_stack_size; let max_stack = item.components.max_stack_size;
let mut inventory = self.inventory().lock().await; let mut inventory = self.inventory().lock().await;
let slots = inventory.slots_with_hotbar_first(); let slots = inventory.slots_with_hotbar_first();
let matching_slots = slots.filter_map(|slot| { let matching_slots = slots.filter_map(|slot| {
if let Some(item_slot) = slot.as_mut() { if let Some(item_slot) = slot.as_mut() {
(item_slot.item_id == item.id && item_slot.item_count < max_stack).then(|| { (item_slot.item.id == item.id && item_slot.item_count < max_stack).then(|| {
let item_count = item_slot.item_count; let item_count = item_slot.item_count;
(item_slot, item_count) (item_slot, item_count)
}) })
@ -577,12 +577,12 @@ impl Player {
if let Some(amount_left) = amount.checked_sub(u32::from(amount_to_add)) { if let Some(amount_left) = amount.checked_sub(u32::from(amount_to_add)) {
amount = amount_left; amount = amount_left;
*slot = ItemStack { *slot = ItemStack {
item_id: item.id, item,
item_count: item.components.max_stack_size, item_count: item.components.max_stack_size,
}; };
} else { } else {
*slot = ItemStack { *slot = ItemStack {
item_id: item.id, item,
item_count: max_stack - (amount_to_add - amount as u8), item_count: max_stack - (amount_to_add - amount as u8),
}; };
return; return;
@ -599,12 +599,12 @@ impl Player {
if let Some(remaining_amount) = amount.checked_sub(u32::from(max_stack)) { if let Some(remaining_amount) = amount.checked_sub(u32::from(max_stack)) {
amount = remaining_amount; amount = remaining_amount;
*slot = Some(ItemStack { *slot = Some(ItemStack {
item_id: item.id, item,
item_count: max_stack, item_count: max_stack,
}); });
} else { } else {
*slot = Some(ItemStack { *slot = Some(ItemStack {
item_id: item.id, item,
item_count: amount as u8, item_count: amount as u8,
}); });
return; return;
@ -618,7 +618,7 @@ impl Player {
/// Add items to inventory if there's space, else drop them to the ground. /// Add items to inventory if there's space, else drop them to the ground.
/// ///
/// This method automatically syncs changes with the client. /// This method automatically syncs changes with the client.
pub async fn give_items(&self, item: &Item, amount: u32) { pub async fn give_items(&self, item: Item, amount: u32) {
self.pickup_items(item, amount).await; self.pickup_items(item, amount).await;
self.set_container_content(None).await; self.set_container_content(None).await;
} }

View File

@ -13,7 +13,8 @@ use crate::{
world::chunker, world::chunker,
}; };
use pumpkin_config::ADVANCED_CONFIG; use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_data::entity::{EntityPose, EntityType}; use pumpkin_data::entity::{entity_from_egg, EntityPose, EntityType};
use pumpkin_data::item::Item;
use pumpkin_data::world::CHAT; use pumpkin_data::world::CHAT;
use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::player::PlayerInventory;
use pumpkin_inventory::InventoryError; use pumpkin_inventory::InventoryError;
@ -49,12 +50,8 @@ use pumpkin_util::{
use pumpkin_world::block::interactive::sign::Sign; use pumpkin_world::block::interactive::sign::Sign;
use pumpkin_world::block::registry::get_block_collision_shapes; use pumpkin_world::block::registry::get_block_collision_shapes;
use pumpkin_world::block::registry::Block; use pumpkin_world::block::registry::Block;
use pumpkin_world::item::registry::{get_item_by_id, get_name_by_id}; use pumpkin_world::block::{registry::get_block_by_item, BlockDirection};
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
use pumpkin_world::{
block::{registry::get_block_by_item, BlockDirection},
item::registry::get_spawn_egg,
};
use pumpkin_world::{WORLD_LOWEST_Y, WORLD_MAX_Y}; use pumpkin_world::{WORLD_LOWEST_Y, WORLD_MAX_Y};
use thiserror::Error; use thiserror::Error;
@ -464,7 +461,7 @@ impl Player {
} }
None if self.gamemode.load() == GameMode::Creative => { None if self.gamemode.load() == GameMode::Creative => {
// Case where item is not present, if in creative mode create the item // Case where item is not present, if in creative mode create the item
let item_stack = ItemStack::new(1, block.item_id); let item_stack = ItemStack::new(1, Item::from_id(block.item_id).unwrap());
let slot_data = Slot::from(&item_stack); let slot_data = Slot::from(&item_stack);
self.update_single_slot(&mut inventory, dest_slot as i16 + 36, slot_data) self.update_single_slot(&mut inventory, dest_slot as i16 + 36, slot_data)
.await; .await;
@ -975,11 +972,6 @@ impl Player {
} }
return Ok(()); return Ok(());
}; };
let Some(item) = get_item_by_id(stack.item_id) else {
// If there is an item stack, there should be an item
return Err(BlockPlacingError::InventoryInvalid.into());
};
if !self if !self
.living_entity .living_entity
.entity .entity
@ -988,7 +980,7 @@ impl Player {
{ {
let action_result = server let action_result = server
.block_registry .block_registry
.use_with_item(block, self, location, item, server) .use_with_item(block, self, location, &stack.item, server)
.await; .await;
match action_result { match action_result {
BlockActionResult::Continue => {} BlockActionResult::Continue => {}
@ -998,16 +990,16 @@ impl Player {
} }
} }
// check if item is a block, Because Not every item can be placed :D // check if item is a block, Because Not every item can be placed :D
if let Some(block) = get_block_by_item(stack.item_id) { if let Some(block) = get_block_by_item(stack.item.id) {
should_try_decrement = self should_try_decrement = self
.run_is_block_place(block.clone(), server, use_item_on, location, &face) .run_is_block_place(block.clone(), server, use_item_on, location, &face)
.await?; .await?;
} }
// check if item is a spawn egg // check if item is a spawn egg
if let Some(item_t) = get_spawn_egg(stack.item_id) { if let Some(entity) = entity_from_egg(stack.item.id) {
should_try_decrement = self self.spawn_entity_from_egg(entity, server, location, &face)
.run_is_spawn_egg(item_t, server, location, &face) .await;
.await?; should_try_decrement = true;
}; };
if should_try_decrement { if should_try_decrement {
@ -1062,11 +1054,7 @@ impl Player {
return; return;
} }
if let Some(held) = self.inventory().lock().await.held_item() { if let Some(held) = self.inventory().lock().await.held_item() {
// this is so ugly and slow brooo... server.item_registry.on_use(&held.item, self, server).await;
if let Some(item) = get_item_by_id(held.item_id) {
let name = get_name_by_id(item.id).unwrap();
server.item_registry.on_use(name, item, self, server).await;
}
} }
} }
@ -1170,15 +1158,13 @@ impl Player {
); );
} }
async fn run_is_spawn_egg( async fn spawn_entity_from_egg(
&self, &self,
item_t: String, entity_type: EntityType,
server: &Server, server: &Server,
location: BlockPos, location: BlockPos,
face: &BlockDirection, face: &BlockDirection,
) -> Result<bool, Box<dyn PumpkinError>> { ) {
// checks if spawn egg has a corresponding entity name
if let Some(entity_type) = EntityType::from_name(&item_t) {
let world_pos = BlockPos(location.0 + face.to_offset()); let world_pos = BlockPos(location.0 + face.to_offset());
// align position like Vanilla does // align position like Vanilla does
let pos = Vector3::new( let pos = Vector3::new(
@ -1206,12 +1192,6 @@ impl Player {
world.spawn_entity(mob).await; world.spawn_entity(mob).await;
// TODO: send/configure additional commands/data based on type of entity (horse, slime, etc) // TODO: send/configure additional commands/data based on type of entity (horse, slime, etc)
} else {
// TODO: maybe include additional error types
return Ok(false);
};
Ok(true)
} }
fn get_player_direction(&self) -> Direction { fn get_player_direction(&self) -> Direction {