From 63d39f46d4d263a12579448c7b2cd39e734fd700 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Wed, 6 May 2026 22:29:29 +0200 Subject: [PATCH] feat: add enchanting table --- Cargo.lock | 29 +- Cargo.toml | 6 +- pumpkin-codecs/src/codec/primitive.rs | 8 +- pumpkin-codegen/src/enchantments.rs | 65 ++- pumpkin-codegen/src/item.rs | 12 + pumpkin-data/src/data_component_impl.rs | 15 +- pumpkin-data/src/generated/enchantment.rs | 448 ++++++++++++++++++ pumpkin-data/src/generated/item.rs | 77 +++ pumpkin-data/src/item_stack/mod.rs | 49 +- pumpkin-inventory/Cargo.toml | 1 + .../src/anvil/anvil_screen_handler.rs | 4 +- .../enchanting/enchanting_screen_handler.rs | 437 +++++++++++++++++ pumpkin-inventory/src/enchanting/mod.rs | 6 + pumpkin-inventory/src/lib.rs | 1 + .../src/merchant/merchant_screen_handler.rs | 4 +- pumpkin-inventory/src/screen_handler.rs | 15 + .../fuzz/fuzz_targets/decoder_java.rs | 12 +- .../server/play/container_button_click.rs | 11 + pumpkin-protocol/src/java/server/play/mod.rs | 2 + pumpkin/src/block/blocks/enchanting_table.rs | 123 +++++ pumpkin/src/block/blocks/mod.rs | 1 + pumpkin/src/block/mod.rs | 4 +- pumpkin/src/block/registry.rs | 8 +- pumpkin/src/data/player_server.rs | 4 +- pumpkin/src/entity/mob/creeper.rs | 2 +- pumpkin/src/entity/mob/mod.rs | 4 +- pumpkin/src/entity/mod.rs | 2 +- pumpkin/src/entity/passive/chicken.rs | 2 +- pumpkin/src/entity/passive/cow.rs | 2 +- pumpkin/src/entity/passive/pig.rs | 2 +- pumpkin/src/entity/passive/sheep.rs | 2 +- pumpkin/src/entity/passive/villager/mod.rs | 7 +- pumpkin/src/entity/player.rs | 80 +++- pumpkin/src/entity/vehicle/boat.rs | 2 +- pumpkin/src/net/java/mod.rs | 11 +- pumpkin/src/net/java/play.rs | 4 +- 36 files changed, 1377 insertions(+), 85 deletions(-) create mode 100644 pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs create mode 100644 pumpkin-inventory/src/enchanting/mod.rs create mode 100644 pumpkin-protocol/src/java/server/play/container_button_click.rs create mode 100644 pumpkin/src/block/blocks/enchanting_table.rs diff --git a/Cargo.lock b/Cargo.lock index df495e283..823dd7ce4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -892,9 +892,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossfire" -version = "3.1.7" +version = "3.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0042e53977a94d5d10de04ce7eb6016aa212c08e83ea202f7a9f102ff104303" +checksum = "ebed7d0a3a92ebc771f880516a5eecef5942a0fc67855dc4f30e719b04c77ac5" dependencies = [ "crossbeam-utils", "futures-core", @@ -936,9 +936,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -1035,7 +1035,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid 0.9.6", - "crypto-common 0.1.6", + "crypto-common 0.1.7", "subtle", ] @@ -1394,9 +1394,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -2836,7 +2836,7 @@ dependencies = [ "tracing-subscriber", "ureq", "uuid", - "wasmparser 0.247.0", + "wasmparser 0.248.0", "wasmtime", "wasmtime-wasi", ] @@ -2893,6 +2893,7 @@ dependencies = [ "pumpkin-protocol", "pumpkin-util", "pumpkin-world", + "rand 0.10.1", "thiserror 2.0.18", "tokio", "tracing", @@ -4464,6 +4465,18 @@ name = "wasmparser" version = "0.247.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" +dependencies = [ + "bitflags 2.11.1", + "hashbrown 0.17.0", + "indexmap", + "semver", +] + +[[package]] +name = "wasmparser" +version = "0.248.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4439c5eee9df71ee0c6efb37f63b1fcb1fec38f85f5142c54e7ed05d33091a" dependencies = [ "bitflags 2.11.1", "hashbrown 0.17.0", diff --git a/Cargo.toml b/Cargo.toml index ddd6193ee..5b707f1d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,7 +120,7 @@ console-subscriber = { version = "0.5.0", default-features = false } crc-fast = "1.10.0" criterion = { version = "0.8", default-features = false } crossbeam-utils = "0.8.21" -crossfire = { version = "3.1.7", features = ["compat"] } +crossfire = { version = "3.1.10", features = ["compat"] } crypto-bigint = "0.7.3" dashmap = "6.1" ecdsa = "0.16.9" @@ -160,7 +160,7 @@ ruzstd = "0.8.2" serde_json5 = "0.2.1" sha1 = "=0.11.0" sha2 = "=0.11.0" -signature = "2.2.0" +signature = "3.0.0" tracing = "0.1.44" tracing-subscriber = { version = "0.3.23", features = [ "env-filter", @@ -181,7 +181,7 @@ notify = "8.2.0" wasmtime = "44.0" wasmtime-wasi = "44.0" wit-bindgen = "0.57" -wasmparser = "0.247" +wasmparser = "0.248" postcard = { version = "1.1", features = ["alloc"] } tracing-serde-structured = "0.4" diff --git a/pumpkin-codecs/src/codec/primitive.rs b/pumpkin-codecs/src/codec/primitive.rs index f8e323801..fb19fa723 100644 --- a/pumpkin-codecs/src/codec/primitive.rs +++ b/pumpkin-codecs/src/codec/primitive.rs @@ -159,7 +159,7 @@ mod test { fn encoding() { assert_encode_success!(3, JsonOps, json!(3)); assert_encode_success!(-68i8, JsonOps, json!(-68)); - assert_encode_success!(-913813743, JsonOps, json!(-913813743)); + assert_encode_success!(-913_813_743, JsonOps, json!(-913_813_743)); assert_encode_success!("Hello, world!".to_string(), JsonOps, json!("Hello, world!")); assert_encode_success!(String::new(), JsonOps, json!("")); assert_encode_success!(ByteBuffer::from(vec![1, 2, 3]), JsonOps, json!([1, 2, 3])); @@ -175,7 +175,7 @@ mod test { ); assert_encode_success!(3u8, JsonOps, json!(3)); - assert_encode_success!(923482312u64, JsonOps, json!(923482312)); + assert_encode_success!(923_482_312u64, JsonOps, json!(923_482_312)); } #[test] @@ -192,7 +192,7 @@ mod test { assert_decode!(String, json!(1), JsonOps, is_error); assert_decode!(u32, json!(-45), JsonOps, is_error); - assert_decode!(u64, json!(-132541235), JsonOps, is_error); - assert_decode!(u64, json!(132541235), JsonOps, is_success); + assert_decode!(u64, json!(-132_541_235), JsonOps, is_error); + assert_decode!(u64, json!(132_541_235), JsonOps, is_success); } } diff --git a/pumpkin-codegen/src/enchantments.rs b/pumpkin-codegen/src/enchantments.rs index 7c9f4ac2e..826c2aa55 100644 --- a/pumpkin-codegen/src/enchantments.rs +++ b/pumpkin-codegen/src/enchantments.rs @@ -24,6 +24,18 @@ pub struct Enchantment { pub max_level: i32, /// Equipment slots this enchantment's attribute modifiers apply to. pub slots: Vec, // TODO: add more + /// The weight of this enchantment (used for random selection). + pub weight: i32, + /// The minimum cost to get this enchantment. + pub min_cost: Cost, + /// The maximum cost to get this enchantment. + pub max_cost: Cost, +} + +#[derive(Deserialize, Clone, Copy)] +pub struct Cost { + pub base: i32, + pub per_level_above_first: i32, } /// Equipment slot category that an enchantment's attribute modifier applies to. @@ -81,6 +93,7 @@ pub fn build() -> TokenStream { .expect("Failed to parse enchantments.json"); let mut variants = TokenStream::new(); + let mut all_variants = TokenStream::new(); let mut name_to_type = TokenStream::new(); let mut id_to_type = TokenStream::new(); @@ -88,6 +101,7 @@ pub fn build() -> TokenStream { let id = enchantment.id; let raw_name = name.strip_prefix("minecraft:").unwrap(); let format_name = format_ident!("{}", raw_name.to_shouty_snake_case()); + all_variants.extend(quote! { &Self::#format_name, }); let anvil_cost = enchantment.anvil_cost; let supported_items = format_ident!( "{}", @@ -99,6 +113,12 @@ pub fn build() -> TokenStream { .to_uppercase() ); let max_level = enchantment.max_level; + let weight = enchantment.weight; + let min_cost_base = enchantment.min_cost.base; + let min_cost_per_level = enchantment.min_cost.per_level_above_first; + let max_cost_base = enchantment.max_cost.base; + let max_cost_per_level = enchantment.max_cost.per_level_above_first; + let slots = enchantment.slots; let slots = slots.iter().map(AttributeModifierSlot::to_tokens); let Translate { translate, bedrock_translate: _, with: _ } = &*enchantment.description.0.content else { @@ -125,7 +145,16 @@ pub fn build() -> TokenStream { supported_items: &ItemTag::#supported_items, exclusive_set: Some(&EnchantmentTag::#exclusive_set), max_level: #max_level, - slots: &[#(#slots),*] + slots: &[#(#slots),*], + weight: #weight, + min_cost: Cost { + base: #min_cost_base, + per_level_above_first: #min_cost_per_level, + }, + max_cost: Cost { + base: #max_cost_base, + per_level_above_first: #max_cost_per_level, + }, }; }]); } else { @@ -139,7 +168,16 @@ pub fn build() -> TokenStream { supported_items: &ItemTag::#supported_items, exclusive_set: None, max_level: #max_level, - slots: &[#(#slots),*] + slots: &[#(#slots),*], + weight: #weight, + min_cost: Cost { + base: #min_cost_base, + per_level_above_first: #min_cost_per_level, + }, + max_cost: Cost { + base: #max_cost_base, + per_level_above_first: #max_cost_per_level, + }, }; }]); } @@ -168,9 +206,24 @@ pub fn build() -> TokenStream { pub supported_items: &'static Tag, pub exclusive_set: Option<&'static Tag>, pub max_level: i32, - pub slots: &'static [AttributeModifierSlot] + pub slots: &'static [AttributeModifierSlot], + pub weight: i32, + pub min_cost: Cost, + pub max_cost: Cost, // TODO: add more } + + #[derive(Clone, Copy, Debug)] + pub struct Cost { + pub base: i32, + pub per_level_above_first: i32, + } + + impl Cost { + pub fn calculate(&self, level: i32) -> i32 { + self.base + self.per_level_above_first * (level - 1) + } + } impl Taggable for Enchantment { #[inline] fn tag_key() -> RegistryKey { @@ -212,6 +265,12 @@ pub fn build() -> TokenStream { } impl Enchantment { + pub const ALL: &'static [&'static Self] = &[#all_variants]; + + pub fn all() -> Iter<'static, &'static Self> { + Self::ALL.iter() + } + #variants pub fn from_name(name: &str) -> Option<&'static Self> { diff --git a/pumpkin-codegen/src/item.rs b/pumpkin-codegen/src/item.rs index e4ac7081e..d86670c02 100644 --- a/pumpkin-codegen/src/item.rs +++ b/pumpkin-codegen/src/item.rs @@ -62,6 +62,13 @@ pub struct ItemComponents { pub damage_resistant: Option, #[serde(rename = "minecraft:weapon")] pub weapon: Option, + #[serde(rename = "minecraft:enchantable")] + pub enchantable: Option, +} + +#[derive(Deserialize)] +pub struct EnchantableComponent { + pub value: i32, } impl ToTokens for ItemComponents { @@ -573,6 +580,11 @@ impl ToTokens for ItemComponents { shearing_sound: #shearing_sound }), }); } + + if let Some(enchantable) = &self.enchantable { + let value = LitInt::new(&enchantable.value.to_string(), Span::call_site()); + tokens.extend(quote! { (Enchantable, &EnchantableImpl { value: #value }), }); + } } } diff --git a/pumpkin-data/src/data_component_impl.rs b/pumpkin-data/src/data_component_impl.rs index 208492cfe..0ac0a855d 100644 --- a/pumpkin-data/src/data_component_impl.rs +++ b/pumpkin-data/src/data_component_impl.rs @@ -4,9 +4,9 @@ use crate::attributes::Attributes; use crate::data_component::DataComponent; use crate::data_component::DataComponent::{ AttributeModifiers, BlocksAttacks, Consumable, CustomData, CustomName, Damage, DamageResistant, - DeathProtection, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, ItemModel, - ItemName, JukeboxPlayable, MaxDamage, MaxStackSize, PotionContents, StoredEnchantments, Tool, - Unbreakable, UseCooldown, Weapon, + DeathProtection, Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, + ItemModel, ItemName, JukeboxPlayable, MaxDamage, MaxStackSize, PotionContents, + StoredEnchantments, Tool, Unbreakable, UseCooldown, Weapon, }; use crate::effect::{self, StatusEffect}; use crate::entity_type::EntityType; @@ -274,7 +274,7 @@ impl DataComponentImpl for ItemModelImpl { pub struct LoreImpl; #[derive(Clone, Hash, PartialEq, Eq)] pub struct RarityImpl; -#[derive(Clone, Hash, PartialEq, Eq)] +#[derive(Clone, Hash, PartialEq, Eq, Default)] pub struct EnchantmentsImpl { pub enchantment: Cow<'static, [(&'static Enchantment, i32)]>, } @@ -1377,7 +1377,12 @@ impl Hash for EntityTypeOrTag { } #[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct EnchantableImpl; +pub struct EnchantableImpl { + pub value: i32, +} +impl DataComponentImpl for EnchantableImpl { + default_impl!(Enchantable); +} #[derive(Clone, Hash, PartialEq)] pub struct EquippableImpl { pub slot: &'static EquipmentSlot, diff --git a/pumpkin-data/src/generated/enchantment.rs b/pumpkin-data/src/generated/enchantment.rs index 50f498cd9..9fd05acc4 100644 --- a/pumpkin-data/src/generated/enchantment.rs +++ b/pumpkin-data/src/generated/enchantment.rs @@ -18,6 +18,19 @@ pub struct Enchantment { pub exclusive_set: Option<&'static Tag>, pub max_level: i32, pub slots: &'static [AttributeModifierSlot], + pub weight: i32, + pub min_cost: Cost, + pub max_cost: Cost, +} +#[derive(Clone, Copy, Debug)] +pub struct Cost { + pub base: i32, + pub per_level_above_first: i32, +} +impl Cost { + pub fn calculate(&self, level: i32) -> i32 { + self.base + self.per_level_above_first * (level - 1) + } } impl Taggable for Enchantment { #[inline] @@ -59,6 +72,54 @@ pub enum AttributeModifierSlot { Saddle, } impl Enchantment { + pub const ALL: &'static [&'static Self] = &[ + &Self::AQUA_AFFINITY, + &Self::BANE_OF_ARTHROPODS, + &Self::BINDING_CURSE, + &Self::BLAST_PROTECTION, + &Self::BREACH, + &Self::CHANNELING, + &Self::DENSITY, + &Self::DEPTH_STRIDER, + &Self::EFFICIENCY, + &Self::FEATHER_FALLING, + &Self::FIRE_ASPECT, + &Self::FIRE_PROTECTION, + &Self::FLAME, + &Self::FORTUNE, + &Self::FROST_WALKER, + &Self::IMPALING, + &Self::INFINITY, + &Self::KNOCKBACK, + &Self::LOOTING, + &Self::LOYALTY, + &Self::LUCK_OF_THE_SEA, + &Self::LUNGE, + &Self::LURE, + &Self::MENDING, + &Self::MULTISHOT, + &Self::PIERCING, + &Self::POWER, + &Self::PROJECTILE_PROTECTION, + &Self::PROTECTION, + &Self::PUNCH, + &Self::QUICK_CHARGE, + &Self::RESPIRATION, + &Self::RIPTIDE, + &Self::SHARPNESS, + &Self::SILK_TOUCH, + &Self::SMITE, + &Self::SOUL_SPEED, + &Self::SWEEPING_EDGE, + &Self::SWIFT_SNEAK, + &Self::THORNS, + &Self::UNBREAKING, + &Self::VANISHING_CURSE, + &Self::WIND_BURST, + ]; + pub fn all() -> Iter<'static, &'static Self> { + Self::ALL.iter() + } pub const AQUA_AFFINITY: Self = Self { id: 0u8, name: "minecraft:aqua_affinity", @@ -69,6 +130,15 @@ impl Enchantment { exclusive_set: None, max_level: 1i32, slots: &[AttributeModifierSlot::Head], + weight: 2i32, + min_cost: Cost { + base: 1i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 41i32, + per_level_above_first: 0i32, + }, }; pub const BANE_OF_ARTHROPODS: Self = Self { id: 1u8, @@ -80,6 +150,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), max_level: 5i32, slots: &[AttributeModifierSlot::MainHand], + weight: 5i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 25i32, + per_level_above_first: 8i32, + }, }; pub const BINDING_CURSE: Self = Self { id: 2u8, @@ -91,6 +170,15 @@ impl Enchantment { exclusive_set: None, max_level: 1i32, slots: &[AttributeModifierSlot::Armor], + weight: 1i32, + min_cost: Cost { + base: 25i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const BLAST_PROTECTION: Self = Self { id: 3u8, @@ -102,6 +190,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), max_level: 4i32, slots: &[AttributeModifierSlot::Armor], + weight: 2i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 13i32, + per_level_above_first: 8i32, + }, }; pub const BREACH: Self = Self { id: 4u8, @@ -113,6 +210,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), max_level: 4i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 15i32, + per_level_above_first: 9i32, + }, + max_cost: Cost { + base: 65i32, + per_level_above_first: 9i32, + }, }; pub const CHANNELING: Self = Self { id: 5u8, @@ -124,6 +230,15 @@ impl Enchantment { exclusive_set: None, max_level: 1i32, slots: &[AttributeModifierSlot::MainHand], + weight: 1i32, + min_cost: Cost { + base: 25i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const DENSITY: Self = Self { id: 6u8, @@ -135,6 +250,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), max_level: 5i32, slots: &[AttributeModifierSlot::MainHand], + weight: 5i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 25i32, + per_level_above_first: 8i32, + }, }; pub const DEPTH_STRIDER: Self = Self { id: 7u8, @@ -146,6 +270,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOOTS), max_level: 3i32, slots: &[AttributeModifierSlot::Feet], + weight: 2i32, + min_cost: Cost { + base: 10i32, + per_level_above_first: 10i32, + }, + max_cost: Cost { + base: 25i32, + per_level_above_first: 10i32, + }, }; pub const EFFICIENCY: Self = Self { id: 8u8, @@ -157,6 +290,15 @@ impl Enchantment { exclusive_set: None, max_level: 5i32, slots: &[AttributeModifierSlot::MainHand], + weight: 10i32, + min_cost: Cost { + base: 1i32, + per_level_above_first: 10i32, + }, + max_cost: Cost { + base: 51i32, + per_level_above_first: 10i32, + }, }; pub const FEATHER_FALLING: Self = Self { id: 9u8, @@ -168,6 +310,15 @@ impl Enchantment { exclusive_set: None, max_level: 4i32, slots: &[AttributeModifierSlot::Armor], + weight: 5i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 6i32, + }, + max_cost: Cost { + base: 11i32, + per_level_above_first: 6i32, + }, }; pub const FIRE_ASPECT: Self = Self { id: 10u8, @@ -179,6 +330,15 @@ impl Enchantment { exclusive_set: None, max_level: 2i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 10i32, + per_level_above_first: 20i32, + }, + max_cost: Cost { + base: 60i32, + per_level_above_first: 20i32, + }, }; pub const FIRE_PROTECTION: Self = Self { id: 11u8, @@ -190,6 +350,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), max_level: 4i32, slots: &[AttributeModifierSlot::Armor], + weight: 5i32, + min_cost: Cost { + base: 10i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 18i32, + per_level_above_first: 8i32, + }, }; pub const FLAME: Self = Self { id: 12u8, @@ -201,6 +370,15 @@ impl Enchantment { exclusive_set: None, max_level: 1i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 20i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const FORTUNE: Self = Self { id: 13u8, @@ -212,6 +390,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_MINING), max_level: 3i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 15i32, + per_level_above_first: 9i32, + }, + max_cost: Cost { + base: 65i32, + per_level_above_first: 9i32, + }, }; pub const FROST_WALKER: Self = Self { id: 14u8, @@ -223,6 +410,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOOTS), max_level: 2i32, slots: &[AttributeModifierSlot::Feet], + weight: 2i32, + min_cost: Cost { + base: 10i32, + per_level_above_first: 10i32, + }, + max_cost: Cost { + base: 25i32, + per_level_above_first: 10i32, + }, }; pub const IMPALING: Self = Self { id: 15u8, @@ -234,6 +430,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), max_level: 5i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 1i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 21i32, + per_level_above_first: 8i32, + }, }; pub const INFINITY: Self = Self { id: 16u8, @@ -245,6 +450,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOW), max_level: 1i32, slots: &[AttributeModifierSlot::MainHand], + weight: 1i32, + min_cost: Cost { + base: 20i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const KNOCKBACK: Self = Self { id: 17u8, @@ -256,6 +470,15 @@ impl Enchantment { exclusive_set: None, max_level: 2i32, slots: &[AttributeModifierSlot::MainHand], + weight: 5i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 20i32, + }, + max_cost: Cost { + base: 55i32, + per_level_above_first: 20i32, + }, }; pub const LOOTING: Self = Self { id: 18u8, @@ -267,6 +490,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 15i32, + per_level_above_first: 9i32, + }, + max_cost: Cost { + base: 65i32, + per_level_above_first: 9i32, + }, }; pub const LOYALTY: Self = Self { id: 19u8, @@ -278,6 +510,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::MainHand], + weight: 5i32, + min_cost: Cost { + base: 12i32, + per_level_above_first: 7i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const LUCK_OF_THE_SEA: Self = Self { id: 20u8, @@ -289,6 +530,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 15i32, + per_level_above_first: 9i32, + }, + max_cost: Cost { + base: 65i32, + per_level_above_first: 9i32, + }, }; pub const LUNGE: Self = Self { id: 21u8, @@ -300,6 +550,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::Hand], + weight: 5i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 25i32, + per_level_above_first: 8i32, + }, }; pub const LURE: Self = Self { id: 22u8, @@ -311,6 +570,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 15i32, + per_level_above_first: 9i32, + }, + max_cost: Cost { + base: 65i32, + per_level_above_first: 9i32, + }, }; pub const MENDING: Self = Self { id: 23u8, @@ -322,6 +590,15 @@ impl Enchantment { exclusive_set: None, max_level: 1i32, slots: &[AttributeModifierSlot::Any], + weight: 2i32, + min_cost: Cost { + base: 25i32, + per_level_above_first: 25i32, + }, + max_cost: Cost { + base: 75i32, + per_level_above_first: 25i32, + }, }; pub const MULTISHOT: Self = Self { id: 24u8, @@ -333,6 +610,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_CROSSBOW), max_level: 1i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 20i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const PIERCING: Self = Self { id: 25u8, @@ -344,6 +630,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_CROSSBOW), max_level: 4i32, slots: &[AttributeModifierSlot::MainHand], + weight: 10i32, + min_cost: Cost { + base: 1i32, + per_level_above_first: 10i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const POWER: Self = Self { id: 26u8, @@ -355,6 +650,15 @@ impl Enchantment { exclusive_set: None, max_level: 5i32, slots: &[AttributeModifierSlot::MainHand], + weight: 10i32, + min_cost: Cost { + base: 1i32, + per_level_above_first: 10i32, + }, + max_cost: Cost { + base: 16i32, + per_level_above_first: 10i32, + }, }; pub const PROJECTILE_PROTECTION: Self = Self { id: 27u8, @@ -366,6 +670,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), max_level: 4i32, slots: &[AttributeModifierSlot::Armor], + weight: 5i32, + min_cost: Cost { + base: 3i32, + per_level_above_first: 6i32, + }, + max_cost: Cost { + base: 9i32, + per_level_above_first: 6i32, + }, }; pub const PROTECTION: Self = Self { id: 28u8, @@ -377,6 +690,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), max_level: 4i32, slots: &[AttributeModifierSlot::Armor], + weight: 10i32, + min_cost: Cost { + base: 1i32, + per_level_above_first: 11i32, + }, + max_cost: Cost { + base: 12i32, + per_level_above_first: 11i32, + }, }; pub const PUNCH: Self = Self { id: 29u8, @@ -388,6 +710,15 @@ impl Enchantment { exclusive_set: None, max_level: 2i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 12i32, + per_level_above_first: 20i32, + }, + max_cost: Cost { + base: 37i32, + per_level_above_first: 20i32, + }, }; pub const QUICK_CHARGE: Self = Self { id: 30u8, @@ -402,6 +733,15 @@ impl Enchantment { AttributeModifierSlot::MainHand, AttributeModifierSlot::OffHand, ], + weight: 5i32, + min_cost: Cost { + base: 12i32, + per_level_above_first: 20i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const RESPIRATION: Self = Self { id: 31u8, @@ -413,6 +753,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::Head], + weight: 2i32, + min_cost: Cost { + base: 10i32, + per_level_above_first: 10i32, + }, + max_cost: Cost { + base: 40i32, + per_level_above_first: 10i32, + }, }; pub const RIPTIDE: Self = Self { id: 32u8, @@ -424,6 +773,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_RIPTIDE), max_level: 3i32, slots: &[AttributeModifierSlot::Hand], + weight: 2i32, + min_cost: Cost { + base: 17i32, + per_level_above_first: 7i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const SHARPNESS: Self = Self { id: 33u8, @@ -435,6 +793,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), max_level: 5i32, slots: &[AttributeModifierSlot::MainHand], + weight: 10i32, + min_cost: Cost { + base: 1i32, + per_level_above_first: 11i32, + }, + max_cost: Cost { + base: 21i32, + per_level_above_first: 11i32, + }, }; pub const SILK_TOUCH: Self = Self { id: 34u8, @@ -446,6 +813,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_MINING), max_level: 1i32, slots: &[AttributeModifierSlot::MainHand], + weight: 1i32, + min_cost: Cost { + base: 15i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 65i32, + per_level_above_first: 0i32, + }, }; pub const SMITE: Self = Self { id: 35u8, @@ -457,6 +833,15 @@ impl Enchantment { exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), max_level: 5i32, slots: &[AttributeModifierSlot::MainHand], + weight: 5i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 25i32, + per_level_above_first: 8i32, + }, }; pub const SOUL_SPEED: Self = Self { id: 36u8, @@ -468,6 +853,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::Feet], + weight: 1i32, + min_cost: Cost { + base: 10i32, + per_level_above_first: 10i32, + }, + max_cost: Cost { + base: 25i32, + per_level_above_first: 10i32, + }, }; pub const SWEEPING_EDGE: Self = Self { id: 37u8, @@ -479,6 +873,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 9i32, + }, + max_cost: Cost { + base: 20i32, + per_level_above_first: 9i32, + }, }; pub const SWIFT_SNEAK: Self = Self { id: 38u8, @@ -490,6 +893,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::Legs], + weight: 1i32, + min_cost: Cost { + base: 25i32, + per_level_above_first: 25i32, + }, + max_cost: Cost { + base: 75i32, + per_level_above_first: 25i32, + }, }; pub const THORNS: Self = Self { id: 39u8, @@ -501,6 +913,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::Any], + weight: 1i32, + min_cost: Cost { + base: 10i32, + per_level_above_first: 20i32, + }, + max_cost: Cost { + base: 60i32, + per_level_above_first: 20i32, + }, }; pub const UNBREAKING: Self = Self { id: 40u8, @@ -512,6 +933,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::Any], + weight: 5i32, + min_cost: Cost { + base: 5i32, + per_level_above_first: 8i32, + }, + max_cost: Cost { + base: 55i32, + per_level_above_first: 8i32, + }, }; pub const VANISHING_CURSE: Self = Self { id: 41u8, @@ -523,6 +953,15 @@ impl Enchantment { exclusive_set: None, max_level: 1i32, slots: &[AttributeModifierSlot::Any], + weight: 1i32, + min_cost: Cost { + base: 25i32, + per_level_above_first: 0i32, + }, + max_cost: Cost { + base: 50i32, + per_level_above_first: 0i32, + }, }; pub const WIND_BURST: Self = Self { id: 42u8, @@ -534,6 +973,15 @@ impl Enchantment { exclusive_set: None, max_level: 3i32, slots: &[AttributeModifierSlot::MainHand], + weight: 2i32, + min_cost: Cost { + base: 15i32, + per_level_above_first: 9i32, + }, + max_cost: Cost { + base: 65i32, + per_level_above_first: 9i32, + }, }; pub fn from_name(name: &str) -> Option<&'static Self> { match name { diff --git a/pumpkin-data/src/generated/item.rs b/pumpkin-data/src/generated/item.rs index 6e8f0835f..2aee9a15d 100644 --- a/pumpkin-data/src/generated/item.rs +++ b/pumpkin-data/src/generated/item.rs @@ -2949,6 +2949,7 @@ impl Item { attribute_modifiers: Cow::Borrowed(&[]), }, ), + (Enchantable, &EnchantableImpl { value: 1 }), ], }; pub const BOOKSHELF: Item = Item { @@ -3008,6 +3009,7 @@ impl Item { attribute_modifiers: Cow::Borrowed(&[]), }, ), + (Enchantable, &EnchantableImpl { value: 1 }), ], }; pub const BOWL: Item = Item { @@ -4259,6 +4261,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 12 }), ], }; pub const CHAINMAIL_CHESTPLATE: Item = Item { @@ -4311,6 +4314,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 12 }), ], }; pub const CHAINMAIL_HELMET: Item = Item { @@ -4363,6 +4367,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 12 }), ], }; pub const CHAINMAIL_LEGGINGS: Item = Item { @@ -4415,6 +4420,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 12 }), ], }; pub const CHARCOAL: Item = Item { @@ -6118,6 +6124,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 13 }), ], }; pub const COPPER_BARS: Item = Item { @@ -6208,6 +6215,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 8 }), ], }; pub const COPPER_BULB: Item = Item { @@ -6317,6 +6325,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 8 }), ], }; pub const COPPER_DOOR: Item = Item { @@ -6445,6 +6454,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 8 }), ], }; pub const COPPER_HOE: Item = Item { @@ -6507,6 +6517,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 13 }), ], }; pub const COPPER_HORSE_ARMOR: Item = Item { @@ -6647,6 +6658,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 8 }), ], }; pub const COPPER_NAUTILUS_ARMOR: Item = Item { @@ -6797,6 +6809,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 13 }), ], }; pub const COPPER_SHOVEL: Item = Item { @@ -6859,6 +6872,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 13 }), ], }; pub const COPPER_SPEAR: Item = Item { @@ -6901,6 +6915,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 13 }), ], }; pub const COPPER_SWORD: Item = Item { @@ -6968,6 +6983,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 13 }), ], }; pub const COPPER_TORCH: Item = Item { @@ -7638,6 +7654,7 @@ impl Item { attribute_modifiers: Cow::Borrowed(&[]), }, ), + (Enchantable, &EnchantableImpl { value: 1 }), ], }; pub const CRYING_OBSIDIAN: Item = Item { @@ -9350,6 +9367,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_BLOCK: Item = Item { @@ -9421,6 +9439,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_CHESTPLATE: Item = Item { @@ -9473,6 +9492,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_HELMET: Item = Item { @@ -9525,6 +9545,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_HOE: Item = Item { @@ -9587,6 +9608,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_HORSE_ARMOR: Item = Item { @@ -9689,6 +9711,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_NAUTILUS_ARMOR: Item = Item { @@ -9820,6 +9843,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_SHOVEL: Item = Item { @@ -9882,6 +9906,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_SPEAR: Item = Item { @@ -9924,6 +9949,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIAMOND_SWORD: Item = Item { @@ -9991,6 +10017,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 10 }), ], }; pub const DIORITE: Item = Item { @@ -11568,6 +11595,7 @@ impl Item { attribute_modifiers: Cow::Borrowed(&[]), }, ), + (Enchantable, &EnchantableImpl { value: 1 }), ], }; pub const FLETCHING_TABLE: Item = Item { @@ -12410,6 +12438,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 22 }), ], }; pub const GOLDEN_BOOTS: Item = Item { @@ -12462,6 +12491,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 25 }), ], }; pub const GOLDEN_CARROT: Item = Item { @@ -12551,6 +12581,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 25 }), ], }; pub const GOLDEN_DANDELION: Item = Item { @@ -12622,6 +12653,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 25 }), ], }; pub const GOLDEN_HOE: Item = Item { @@ -12684,6 +12716,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 22 }), ], }; pub const GOLDEN_HORSE_ARMOR: Item = Item { @@ -12786,6 +12819,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 25 }), ], }; pub const GOLDEN_NAUTILUS_ARMOR: Item = Item { @@ -12898,6 +12932,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 22 }), ], }; pub const GOLDEN_SHOVEL: Item = Item { @@ -12960,6 +12995,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 22 }), ], }; pub const GOLDEN_SPEAR: Item = Item { @@ -13002,6 +13038,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 22 }), ], }; pub const GOLDEN_SWORD: Item = Item { @@ -13069,6 +13106,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 22 }), ], }; pub const GRANITE: Item = Item { @@ -14589,6 +14627,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 14 }), ], }; pub const IRON_BARS: Item = Item { @@ -14679,6 +14718,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 9 }), ], }; pub const IRON_CHAIN: Item = Item { @@ -14750,6 +14790,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 9 }), ], }; pub const IRON_DOOR: Item = Item { @@ -14840,6 +14881,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 9 }), ], }; pub const IRON_HOE: Item = Item { @@ -14902,6 +14944,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 14 }), ], }; pub const IRON_HORSE_ARMOR: Item = Item { @@ -15023,6 +15066,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 9 }), ], }; pub const IRON_NAUTILUS_ARMOR: Item = Item { @@ -15173,6 +15217,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 14 }), ], }; pub const IRON_SHOVEL: Item = Item { @@ -15235,6 +15280,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 14 }), ], }; pub const IRON_SPEAR: Item = Item { @@ -15277,6 +15323,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 14 }), ], }; pub const IRON_SWORD: Item = Item { @@ -15344,6 +15391,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 14 }), ], }; pub const IRON_TRAPDOOR: Item = Item { @@ -16080,6 +16128,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const LEATHER_CHESTPLATE: Item = Item { @@ -16132,6 +16181,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const LEATHER_HELMET: Item = Item { @@ -16184,6 +16234,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const LEATHER_HORSE_ARMOR: Item = Item { @@ -16286,6 +16337,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const LECTERN: Item = Item { @@ -17525,6 +17577,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const MAGENTA_BANNER: Item = Item { @@ -19819,6 +19872,7 @@ impl Item { res_type: DamageResistantType::Fire, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_BLOCK: Item = Item { @@ -19909,6 +19963,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_CHESTPLATE: Item = Item { @@ -19974,6 +20029,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_HELMET: Item = Item { @@ -20039,6 +20095,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_HOE: Item = Item { @@ -20107,6 +20164,7 @@ impl Item { res_type: DamageResistantType::Fire, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_HORSE_ARMOR: Item = Item { @@ -20260,6 +20318,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_NAUTILUS_ARMOR: Item = Item { @@ -20391,6 +20450,7 @@ impl Item { res_type: DamageResistantType::Fire, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_SCRAP: Item = Item { @@ -20484,6 +20544,7 @@ impl Item { res_type: DamageResistantType::Fire, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_SPEAR: Item = Item { @@ -20532,6 +20593,7 @@ impl Item { res_type: DamageResistantType::Fire, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_SWORD: Item = Item { @@ -20605,6 +20667,7 @@ impl Item { res_type: DamageResistantType::Fire, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const NETHERITE_UPGRADE_SMITHING_TEMPLATE: Item = Item { @@ -28151,6 +28214,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 5 }), ], }; pub const STONE_BRICK_SLAB: Item = Item { @@ -28308,6 +28372,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 5 }), ], }; pub const STONE_PICKAXE: Item = Item { @@ -28370,6 +28435,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 5 }), ], }; pub const STONE_PRESSURE_PLATE: Item = Item { @@ -28451,6 +28517,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 5 }), ], }; pub const STONE_SLAB: Item = Item { @@ -28512,6 +28579,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 5 }), ], }; pub const STONE_STAIRS: Item = Item { @@ -28598,6 +28666,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 5 }), ], }; pub const STONECUTTER: Item = Item { @@ -29769,6 +29838,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 1 }), ], }; pub const TRIPWIRE_HOOK: Item = Item { @@ -30151,6 +30221,7 @@ impl Item { shearing_sound: IdOr::Id(Sound::ItemShearsSnip), }, ), + (Enchantable, &EnchantableImpl { value: 9 }), ], }; pub const TURTLE_SCUTE: Item = Item { @@ -32948,6 +33019,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const WOODEN_HOE: Item = Item { @@ -33010,6 +33082,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const WOODEN_PICKAXE: Item = Item { @@ -33072,6 +33145,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const WOODEN_SHOVEL: Item = Item { @@ -33134,6 +33208,7 @@ impl Item { item_damage_per_attack: 2, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const WOODEN_SPEAR: Item = Item { @@ -33176,6 +33251,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const WOODEN_SWORD: Item = Item { @@ -33243,6 +33319,7 @@ impl Item { item_damage_per_attack: 1, }, ), + (Enchantable, &EnchantableImpl { value: 15 }), ], }; pub const WRITABLE_BOOK: Item = Item { diff --git a/pumpkin-data/src/item_stack/mod.rs b/pumpkin-data/src/item_stack/mod.rs index 9282315b2..91e71c2cd 100644 --- a/pumpkin-data/src/item_stack/mod.rs +++ b/pumpkin-data/src/item_stack/mod.rs @@ -95,17 +95,54 @@ impl ItemStack { None } pub fn get_data_component_mut(&mut self) -> Option<&mut T> { - let to_get_id = &T::get_enum(); - for (id, component) in &mut self.patch { - if id == to_get_id { - return component - .as_mut() - .map(|component| get_mut::(component.as_mut())); + let to_get_id = T::get_enum(); + if let Some(index) = self.patch.iter().position(|(id, _)| *id == to_get_id) { + return self.patch[index] + .1 + .as_mut() + .map(|component| get_mut::(component.as_mut())); + } + + // If not in patch, clone from item to patch and return mut + let mut cloned = None; + for (id, component) in self.item.components { + if *id == to_get_id { + cloned = Some((*id, Some(component.clone_dyn()))); + break; } } + if let Some((id, component)) = cloned { + self.patch.push((id, component)); + return self + .patch + .last_mut() + .unwrap() + .1 + .as_mut() + .map(|c| get_mut::(c.as_mut())); + } None } + pub fn has_enchantments(&self) -> bool { + self.get_data_component::() + .is_some_and(|e| !e.enchantment.is_empty()) + } + + pub fn add_enchantment(&mut self, enchantment: &'static Enchantment, level: u16) { + if let Some(enchantments) = self.get_data_component_mut::() { + let mut new_vec = enchantments.enchantment.to_vec(); + new_vec.push((enchantment, level as i32)); + enchantments.enchantment = Cow::Owned(new_vec); + } else { + let enchantments = EnchantmentsImpl { + enchantment: Cow::Owned(vec![(enchantment, level as i32)]), + }; + self.patch + .push((DataComponent::Enchantments, Some(Box::new(enchantments)))); + } + } + pub const EMPTY: &'static Self = &Self { item_count: 0, item: &Item::AIR, diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 931e9b963..ceb63c273 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -12,6 +12,7 @@ pumpkin-data.workspace = true pumpkin-world.workspace = true pumpkin-util.workspace = true +rand.workspace = true tracing.workspace = true tokio.workspace = true thiserror.workspace = true diff --git a/pumpkin-inventory/src/anvil/anvil_screen_handler.rs b/pumpkin-inventory/src/anvil/anvil_screen_handler.rs index c758b39d8..041924c55 100644 --- a/pumpkin-inventory/src/anvil/anvil_screen_handler.rs +++ b/pumpkin-inventory/src/anvil/anvil_screen_handler.rs @@ -8,7 +8,7 @@ use crate::{ player::player_inventory::PlayerInventory, screen_handler::{ InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, - ScreenHandlerFuture, + ScreenHandlerFuture, offer_or_drop_stack, }, slot::NormalSlot, window_property::{Anvil, WindowProperty}, @@ -121,7 +121,7 @@ impl ScreenHandler for AnvilScreenHandler { for i in 0..2 { let stack = self.inventory.remove_stack(i).await; if !stack.is_empty() { - player.drop_item(stack, false).await; + offer_or_drop_stack(player, stack).await; } } self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await; diff --git a/pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs b/pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs new file mode 100644 index 000000000..0a0dea887 --- /dev/null +++ b/pumpkin-inventory/src/enchanting/enchanting_screen_handler.rs @@ -0,0 +1,437 @@ +use std::any::Any; +use std::sync::Arc; + +use pumpkin_data::Enchantment; +use pumpkin_data::data_component_impl::EnchantableImpl; +use pumpkin_data::item::Item; +use pumpkin_data::item_stack::ItemStack; +use pumpkin_data::screen::WindowType; +use pumpkin_data::tag::{Enchantment as EnchantmentTag, Taggable}; +use pumpkin_util::random::RandomImpl; +use pumpkin_world::inventory::Inventory; + +use crate::{ + player::player_inventory::PlayerInventory, + screen_handler::{ + InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, + ScreenHandlerFuture, offer_or_drop_stack, + }, + slot::NormalSlot, + window_property::{EnchantmentTable, WindowProperty}, +}; + +pub struct EnchantingTableScreenHandler { + pub inventory: Arc, + behaviour: ScreenHandlerBehaviour, + pub level_requirements: [i32; 3], + pub enchantment_id: [i32; 3], + pub enchantment_level: [i32; 3], + pub enchantment_seed: i32, + pub bookshelf_count: i32, +} + +impl EnchantingTableScreenHandler { + pub fn new( + sync_id: u8, + player_inventory: &Arc, + inventory: &Arc, + enchantment_seed: i32, + bookshelf_count: i32, + ) -> Self { + let mut handler = Self { + inventory: inventory.clone(), + behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Enchantment)), + level_requirements: [0; 3], + enchantment_id: [-1; 3], + enchantment_level: [-1; 3], + enchantment_seed, + bookshelf_count, + }; + + // Enchanting slots: 0 is item, 1 is lapis + handler.add_slot(Arc::new(NormalSlot::new(inventory.clone(), 0))); + handler.add_slot(Arc::new(NormalSlot::new(inventory.clone(), 1))); + + let player_inventory: Arc = player_inventory.clone(); + handler.add_player_slots(&player_inventory); + + handler + } + + pub async fn update_enchantments(&mut self, _player: &dyn InventoryPlayer) { + let item = self.inventory.get_stack(0).await; + let item = item.lock().await; + + if item.is_empty() || item.has_enchantments() { + for i in 0..3 { + self.level_requirements[i] = 0; + self.enchantment_id[i] = -1; + self.enchantment_level[i] = -1; + } + } else { + let enchantability = item + .get_data_component::() + .map_or(0, |e| e.value); + + if enchantability <= 0 { + for i in 0..3 { + self.level_requirements[i] = 0; + self.enchantment_id[i] = -1; + self.enchantment_level[i] = -1; + } + } else { + let mut random = pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed( + self.enchantment_seed as u64, + ); + + for i in 0..3 { + let level = self.calculate_level_requirement(&mut random, i, enchantability); + self.level_requirements[i] = level; + } + + for i in 0..3 { + if self.level_requirements[i] > 0 { + let enchantments = Self::get_enchantment_list( + &mut random, + &item, + i, + self.level_requirements[i], + ); + if let Some(first) = enchantments.first() { + self.enchantment_id[i] = first.0.id as i32; + self.enchantment_level[i] = first.1; + } else { + self.enchantment_id[i] = -1; + self.enchantment_level[i] = -1; + } + } else { + self.enchantment_id[i] = -1; + self.enchantment_level[i] = -1; + } + } + } + } + self.send_property_updates().await; + } + + fn calculate_level_requirement( + &self, + random: &mut pumpkin_util::random::xoroshiro128::Xoroshiro, + slot: usize, + _enchantability: i32, + ) -> i32 { + let b = self.bookshelf_count; + let level = random.next_bounded_i32(8) + 1 + (b >> 1) + random.next_bounded_i32(b + 1); + + match slot { + 0 => (level / 3).max(1), + 1 => (level * 2 / 3 + 7).max(1), + 2 => level.max(b * 2).max(1), + _ => 0, + } + } + + fn get_enchantment_list( + random: &mut pumpkin_util::random::xoroshiro128::Xoroshiro, + item: &ItemStack, + _slot: usize, + level: i32, + ) -> Vec<(&'static Enchantment, i32)> { + let enchantability = item + .get_data_component::() + .map_or(0, |e| e.value); + let mut enchant_level = level + + 1 + + random.next_bounded_i32(enchantability / 4 + 1) + + random.next_bounded_i32(enchantability / 4 + 1); + let bonus = (random.next_f32() + random.next_f32() - 1.0) * 0.15; + enchant_level = (enchant_level as f32 * (1.0 + bonus)).round() as i32; + enchant_level = enchant_level.max(1); + + let mut available = Vec::new(); + for enchant in Enchantment::all() { + if enchant.has_tag(&EnchantmentTag::MINECRAFT_IN_ENCHANTING_TABLE) + && enchant.can_enchant(item.item) + { + for l in (1..=enchant.max_level).rev() { + if enchant_level >= enchant.min_cost.calculate(l) + && enchant_level <= enchant.max_cost.calculate(l) + { + available.push((*enchant, l)); + break; + } + } + } + } + + if available.is_empty() { + return Vec::new(); + } + + let total_weight: i32 = available.iter().map(|(e, _)| e.weight).sum(); + if total_weight <= 0 { + return Vec::new(); + } + + let mut weight = random.next_bounded_i32(total_weight); + let mut selected = None; + for (e, l) in &available { + weight -= e.weight; + if weight < 0 { + selected = Some((*e, *l)); + break; + } + } + + let mut result = Vec::new(); + if let Some(s) = selected { + result.push(s); + + // Add more? + let mut current_level = enchant_level; + while random.next_bounded_i32(50) <= (current_level + 1) / 2 { + available.retain(|(e, _)| { + for (se, _) in &result { + if !e.are_compatible(se) { + return false; + } + } + true + }); + + if available.is_empty() { + break; + } + + let total_weight: i32 = available.iter().map(|(e, _)| e.weight).sum(); + let mut weight = random.next_bounded_i32(total_weight); + for (e, l) in &available { + weight -= e.weight; + if weight < 0 { + result.push((*e, *l)); + break; + } + } + current_level /= 2; + } + } + + result + } + + async fn send_property_updates(&self) { + if let Some(sync_handler) = self.behaviour.sync_handler.as_ref() { + for i in 0..3 { + let (id, val) = WindowProperty::new( + EnchantmentTable::LevelRequirement { slot: i as u8 }, + self.level_requirements[i] as i16, + ) + .into_tuple(); + sync_handler + .update_property(&self.behaviour, id as i32, val as i32) + .await; + + let (id, val) = WindowProperty::new( + EnchantmentTable::EnchantmentId { slot: i as u8 }, + self.enchantment_id[i] as i16, + ) + .into_tuple(); + sync_handler + .update_property(&self.behaviour, id as i32, val as i32) + .await; + + let (id, val) = WindowProperty::new( + EnchantmentTable::EnchantmentLevel { slot: i as u8 }, + self.enchantment_level[i] as i16, + ) + .into_tuple(); + sync_handler + .update_property(&self.behaviour, id as i32, val as i32) + .await; + } + + let (id, val) = WindowProperty::new( + EnchantmentTable::EnchantmentSeed, + (self.enchantment_seed & 0xFFFF) as i16, + ) + .into_tuple(); + sync_handler + .update_property(&self.behaviour, id as i32, val as i32) + .await; + } + } +} + +impl ScreenHandler for EnchantingTableScreenHandler { + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn get_behaviour(&self) -> &ScreenHandlerBehaviour { + &self.behaviour + } + + fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour { + &mut self.behaviour + } + + fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> { + Box::pin(async move { + self.default_on_closed(player).await; + self.inventory.on_close().await; + // Return items to player + for i in 0..2 { + let stack = self.inventory.remove_stack(i).await; + if !stack.is_empty() { + offer_or_drop_stack(player, stack).await; + } + } + }) + } + + fn on_button_click<'a>( + &'a mut self, + player: &'a dyn InventoryPlayer, + id: i32, + ) -> ScreenHandlerFuture<'a, bool> { + Box::pin(async move { + if !(0..3).contains(&id) { + return false; + } + + let level_req = self.level_requirements[id as usize]; + if player.experience_level() < level_req && !player.is_creative() { + return false; + } + + let lapis_slot = self.inventory.get_stack(1).await; + let mut lapis_stack = lapis_slot.lock().await; + let lapis_cost = (id + 1) as u8; + + if !player.is_creative() + && (lapis_stack.is_empty() || lapis_stack.item_count < lapis_cost) + { + return false; + } + + // Perform enchantment + let item_slot = self.inventory.get_stack(0).await; + let mut item_stack = item_slot.lock().await; + + if item_stack.is_empty() || item_stack.has_enchantments() { + return false; + } + + let mut random = pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed( + self.enchantment_seed as u64, + ); + let enchantments = + Self::get_enchantment_list(&mut random, &item_stack, id as usize, level_req); + + if enchantments.is_empty() { + return false; + } + + if !player.is_creative() { + player.add_experience_levels(-(id + 1)).await; + lapis_stack.decrement(lapis_cost); + } + + for (enchant, level) in enchantments { + item_stack.add_enchantment(enchant, level as u16); + } + + // CRITICAL FIX: Drop locks *before* calling `update_enchantments` + // Otherwise, update_enchantments will try to lock slot 0 again and deadlock! + drop(item_stack); + drop(lapis_stack); + + // Update seed + player.set_enchantment_seed(rand::random()).await; + self.enchantment_seed = player.enchantment_seed(); + + self.update_enchantments(player).await; + self.send_content_updates().await; + + true + }) + } + + fn quick_move<'a>( + &'a mut self, + player: &'a dyn InventoryPlayer, // FIX: Changed _player to player + slot_index: i32, + ) -> ItemStackFuture<'a> { + Box::pin(async move { + let mut stack_left = ItemStack::EMPTY.clone(); + let slot = self.get_behaviour().slots[slot_index as usize].clone(); + + if slot.has_stack().await { + let slot_stack_lock = slot.get_stack().await; + let slot_stack_guard = slot_stack_lock.lock().await; + stack_left = slot_stack_guard.clone(); + drop(slot_stack_guard); + + let mut slot_stack_mut = slot_stack_lock.lock().await; + + if slot_index < 2 { + // From enchanting to player + if !self + .insert_item( + &mut slot_stack_mut, + 2, + self.get_behaviour().slots.len() as i32, + true, + ) + .await + { + return ItemStack::EMPTY.clone(); + } + } else { + // From player to enchanting + // Lapis check + if slot_stack_mut.item == &Item::LAPIS_LAZULI { + if !self.insert_item(&mut slot_stack_mut, 1, 2, false).await { + return ItemStack::EMPTY.clone(); + } + } else if !self.insert_item(&mut slot_stack_mut, 0, 1, false).await { + return ItemStack::EMPTY.clone(); + } + } + + if slot_stack_mut.is_empty() { + drop(slot_stack_mut); + slot.set_stack(ItemStack::EMPTY.clone()).await; + } else { + drop(slot_stack_mut); + slot.mark_dirty().await; + } + + // CRITICAL FIX: Ensure the client is notified when shift-clicking items into the slots + self.update_enchantments(player).await; + } + + stack_left + }) + } + + fn on_slot_click<'a>( + &'a mut self, + slot_index: i32, + button: i32, + action_type: pumpkin_protocol::java::server::play::SlotActionType, + player: &'a dyn InventoryPlayer, + ) -> ScreenHandlerFuture<'a, ()> { + Box::pin(async move { + self.internal_on_slot_click(slot_index, button, action_type, player) + .await; + if slot_index == 0 || slot_index == 1 { + self.update_enchantments(player).await; + } + }) + } +} diff --git a/pumpkin-inventory/src/enchanting/mod.rs b/pumpkin-inventory/src/enchanting/mod.rs new file mode 100644 index 000000000..f30ca4b08 --- /dev/null +++ b/pumpkin-inventory/src/enchanting/mod.rs @@ -0,0 +1,6 @@ +//! Enchanting module. +//! +//! This module handles enchanting mechanics including: +//! - [`EnchantingTableScreenHandler`] - Screen handler for enchanting tables + +pub mod enchanting_screen_handler; diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 80af27a15..17009b17e 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -36,6 +36,7 @@ pub mod container_click; pub mod crafting; pub mod double; pub mod drag_handler; +pub mod enchanting; pub mod entity_equipment; mod error; pub mod furnace_like; diff --git a/pumpkin-inventory/src/merchant/merchant_screen_handler.rs b/pumpkin-inventory/src/merchant/merchant_screen_handler.rs index 652879314..1c0b39efa 100644 --- a/pumpkin-inventory/src/merchant/merchant_screen_handler.rs +++ b/pumpkin-inventory/src/merchant/merchant_screen_handler.rs @@ -8,7 +8,7 @@ use crate::{ player::player_inventory::PlayerInventory, screen_handler::{ InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, - ScreenHandlerFuture, + ScreenHandlerFuture, offer_or_drop_stack, }, slot::NormalSlot, }; @@ -110,7 +110,7 @@ impl ScreenHandler for MerchantScreenHandler { // Drop inputs only, output is virtual/ghost in some sense or just cleared let stack = self.inventory.remove_stack(i).await; if !stack.is_empty() { - player.drop_item(stack, false).await; + offer_or_drop_stack(player, stack).await; } } // Clear output slot diff --git a/pumpkin-inventory/src/screen_handler.rs b/pumpkin-inventory/src/screen_handler.rs index a715e9064..165e84a45 100644 --- a/pumpkin-inventory/src/screen_handler.rs +++ b/pumpkin-inventory/src/screen_handler.rs @@ -142,6 +142,12 @@ pub trait InventoryPlayer: Send + Sync { /// Adds or removes experience levels. fn add_experience_levels(&self, levels: i32) -> PlayerFuture<'_, ()>; + /// Gets the player's enchantment seed. + fn enchantment_seed(&self) -> i32; + + /// Sets the player's enchantment seed. + fn set_enchantment_seed(&self, seed: i32) -> PlayerFuture<'_, ()>; + /// Sends a full container content packet. fn enqueue_inventory_packet<'a>( &'a self, @@ -673,6 +679,15 @@ pub trait ScreenHandler: Send + Sync { slot_index: i32, ) -> ItemStackFuture<'a>; + /// Handles a button click event (e.g., enchantment selection, beacon effects). + fn on_button_click<'a>( + &'a mut self, + _player: &'a dyn InventoryPlayer, + _button_id: i32, + ) -> ScreenHandlerFuture<'a, bool> { + Box::pin(async { false }) + } + /// Inserts an item into a range of slots. /// /// First tries to stack with existing items, then fills empty slots. diff --git a/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs b/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs index 9711b321e..7d666b11c 100644 --- a/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs +++ b/pumpkin-protocol/fuzz/fuzz_targets/decoder_java.rs @@ -13,11 +13,12 @@ use pumpkin_protocol::java::{ play::{ SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand, SClientInformationPlay, SCloseContainer, SCommandSuggestion, SConfirmTeleport, - SCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle, SPaddleBoat, - SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SPlayerAction, SPlayerCommand, - SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, - SPlayerSession, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, - SSwingArm, SUpdateSign, SUseItem, SUseItemOn, + SContainerButtonClick, SCookieResponse, SCustomPayload, SInteract, SKeepAlive, + SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, + SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition, + SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SSetCommandBlock, + SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, + SUseItemOn, }, status::SStatusPingRequest, }, @@ -84,6 +85,7 @@ fn fuzz_all_deserializers(payload: &[u8]) { SPlayerLoaded, SPlayPingRequest, SClickSlot, + SContainerButtonClick, SSetHeldItem, SSetCreativeSlot, SSwingArm, diff --git a/pumpkin-protocol/src/java/server/play/container_button_click.rs b/pumpkin-protocol/src/java/server/play/container_button_click.rs new file mode 100644 index 000000000..d4ef53d54 --- /dev/null +++ b/pumpkin-protocol/src/java/server/play/container_button_click.rs @@ -0,0 +1,11 @@ +use crate::VarInt; +use pumpkin_data::packet::serverbound::PLAY_CONTAINER_BUTTON_CLICK; +use pumpkin_macros::java_packet; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[java_packet(PLAY_CONTAINER_BUTTON_CLICK)] +pub struct SContainerButtonClick { + pub window_id: VarInt, + pub button_id: VarInt, +} diff --git a/pumpkin-protocol/src/java/server/play/mod.rs b/pumpkin-protocol/src/java/server/play/mod.rs index eb8d58c92..12969aa73 100644 --- a/pumpkin-protocol/src/java/server/play/mod.rs +++ b/pumpkin-protocol/src/java/server/play/mod.rs @@ -10,6 +10,7 @@ mod client_tick_end; mod close_container; mod command_suggestion; mod confirm_teleport; +mod container_button_click; mod cookie_response; mod custom_payload; mod interact; @@ -53,6 +54,7 @@ pub use client_tick_end::*; pub use close_container::*; pub use command_suggestion::*; pub use confirm_teleport::*; +pub use container_button_click::*; pub use cookie_response::*; pub use custom_payload::*; pub use interact::*; diff --git a/pumpkin/src/block/blocks/enchanting_table.rs b/pumpkin/src/block/blocks/enchanting_table.rs new file mode 100644 index 000000000..38396b040 --- /dev/null +++ b/pumpkin/src/block/blocks/enchanting_table.rs @@ -0,0 +1,123 @@ +use std::sync::Arc; + +use crate::block::registry::BlockActionResult; +use crate::block::{BlockBehaviour, BlockFuture, NormalUseArgs}; +use pumpkin_data::translation; +use pumpkin_inventory::enchanting::enchanting_screen_handler::EnchantingTableScreenHandler; +use pumpkin_inventory::player::player_inventory::PlayerInventory; +use pumpkin_inventory::screen_handler::{ + BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler, +}; +use pumpkin_macros::pumpkin_block; +use pumpkin_util::math::position::BlockPos; +use pumpkin_util::text::TextComponent; +use pumpkin_world::inventory::{Inventory, SimpleInventory}; +use tokio::sync::Mutex; + +#[pumpkin_block("minecraft:enchanting_table")] +pub struct EnchantingTableBlock; + +impl BlockBehaviour for EnchantingTableBlock { + fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> { + Box::pin(async move { + let mut bookshelf_count = 0; + + for off_z in -1..=1 { + for off_x in -1..=1 { + if (off_z != 0 || off_x != 0) + && args.world.get_block_state(&args.position.add(off_x, 0, off_z)).await.id == 0 // Air + && args.world.get_block_state(&args.position.add(off_x, 1, off_z)).await.id == 0 + // Air + { + for off_y in 0..=1 { + if self + .is_bookshelf( + args.world, + &args.position.add(off_x * 2, off_y, off_z * 2), + ) + .await + { + bookshelf_count += 1; + } + if off_x != 0 && off_z != 0 { + if self + .is_bookshelf( + args.world, + &args.position.add(off_x * 2, off_y, off_z), + ) + .await + { + bookshelf_count += 1; + } + if self + .is_bookshelf( + args.world, + &args.position.add(off_x, off_y, off_z * 2), + ) + .await + { + bookshelf_count += 1; + } + } + } + } + } + } + let bookshelf_count = bookshelf_count.min(15); + + args.player + .open_handled_screen( + &EnchantingTableScreenFactory { + bookshelf_count, + seed: args.player.enchantment_seed(), + }, + Some(*args.position), + ) + .await; + BlockActionResult::Success + }) + } +} + +impl EnchantingTableBlock { + async fn is_bookshelf(&self, world: &Arc, pos: &BlockPos) -> bool { + let state = world.get_block_state(pos).await; + let block = pumpkin_data::Block::from_state_id(state.id); + block.name == "bookshelf" + } +} + +struct EnchantingTableScreenFactory { + bookshelf_count: i32, + seed: i32, +} + +impl ScreenHandlerFactory for EnchantingTableScreenFactory { + fn create_screen_handler<'a>( + &'a self, + sync_id: u8, + player_inventory: &'a Arc, + _player: &'a dyn InventoryPlayer, + ) -> BoxFuture<'a, Option> { + Box::pin(async move { + let inventory: Arc = Arc::new(SimpleInventory::new(2)); + let handler = EnchantingTableScreenHandler::new( + sync_id, + player_inventory, + &inventory, + self.seed, + self.bookshelf_count, + ); + let screen_handler_arc = Arc::new(Mutex::new(handler)); + Some(screen_handler_arc as SharedScreenHandler) + }) + } + + fn get_display_name(&self) -> TextComponent { + TextComponent::translate_cross( + translation::java::CONTAINER_ENCHANT, + translation::bedrock::CONTAINER_ENCHANT, + &[], + ) + } +} diff --git a/pumpkin/src/block/blocks/mod.rs b/pumpkin/src/block/blocks/mod.rs index de40b1c37..cfffb4efc 100644 --- a/pumpkin/src/block/blocks/mod.rs +++ b/pumpkin/src/block/blocks/mod.rs @@ -15,6 +15,7 @@ pub mod shulker_box; pub mod blast_furnace; pub mod brewing_stand; pub mod crafting_table; +pub mod enchanting_table; pub mod furnace; pub mod grindstone; pub mod smoker; diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index 4f9aaf753..cf57facf4 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -216,7 +216,7 @@ pub struct NormalUseArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, - pub player: &'a Player, + pub player: &'a Arc, pub hit: &'a BlockHitResult<'a>, } @@ -225,7 +225,7 @@ pub struct UseWithItemArgs<'a> { pub world: &'a Arc, pub block: &'a Block, pub position: &'a BlockPos, - pub player: &'a Player, + pub player: &'a Arc, pub hit: &'a BlockHitResult<'a>, pub item_stack: &'a Arc>, } diff --git a/pumpkin/src/block/registry.rs b/pumpkin/src/block/registry.rs index 1ee46def5..f2253b998 100644 --- a/pumpkin/src/block/registry.rs +++ b/pumpkin/src/block/registry.rs @@ -160,6 +160,7 @@ use crate::block::blocks::chain::ChainBlock; use crate::block::blocks::cobweb::CobwebBlock; use crate::block::blocks::crafting_table::CraftingTableBlock; use crate::block::blocks::dragon_egg::DragonEggBlock; +use crate::block::blocks::enchanting_table::EnchantingTableBlock; use crate::block::blocks::end_rod::EndRodBlock; use crate::block::blocks::ender_chest::EnderChestBlock; use crate::block::blocks::hopper::HopperBlock; @@ -194,6 +195,7 @@ pub fn default_registry() -> Arc { manager.register(CopperChestBlock); manager.register(EnderChestBlock); manager.register(CraftingTableBlock); + manager.register(EnchantingTableBlock); manager.register(DirtPathBlock); manager.register(DoorBlock); manager.register(FarmlandBlock); @@ -491,7 +493,7 @@ impl BlockRegistry { pub async fn on_use( &self, block: &Block, - player: &Player, + player: &Arc, position: &BlockPos, hit: &BlockHitResult<'_>, server: &Server, @@ -530,7 +532,7 @@ impl BlockRegistry { pub async fn use_with_item( &self, block: &Block, - player: &Player, + player: &Arc, position: &BlockPos, hit: &BlockHitResult<'_>, item_stack: &Arc>, @@ -557,7 +559,7 @@ impl BlockRegistry { pub async fn use_with_item_fluid( &self, fluid: &Fluid, - player: &Player, + player: &Arc, position: BlockPos, item: &Item, server: &Server, diff --git a/pumpkin/src/data/player_server.rs b/pumpkin/src/data/player_server.rs index 6641d01bb..0b8d29fb9 100644 --- a/pumpkin/src/data/player_server.rs +++ b/pumpkin/src/data/player_server.rs @@ -44,12 +44,12 @@ impl ServerPlayerData { /// # Returns /// /// A Result indicating success or the error that occurred. - pub async fn handle_player_leave(&self, player: &Player) -> Result<(), PlayerDataError> { + pub async fn handle_player_leave(&self, player: &Arc) -> Result<(), PlayerDataError> { player .player_screen_handler .lock() .await - .on_closed(player) + .on_closed(player.as_ref()) .await; player.on_handled_screen_closed().await; diff --git a/pumpkin/src/entity/mob/creeper.rs b/pumpkin/src/entity/mob/creeper.rs index d69487b6d..17ec20566 100644 --- a/pumpkin/src/entity/mob/creeper.rs +++ b/pumpkin/src/entity/mob/creeper.rs @@ -194,7 +194,7 @@ impl Mob for CreeperEntity { fn mob_interact<'a>( &'a self, - player: &'a Player, + player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async move { diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index c79ca5734..65ebe80b4 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -384,7 +384,7 @@ pub trait Mob: EntityBase + Send + Sync { fn mob_interact<'a>( &'a self, - _player: &'a Player, + _player: &'a Arc, _item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async { false }) @@ -526,7 +526,7 @@ impl EntityBase for T { fn interact<'a>( &'a self, - player: &'a Player, + player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async move { self.mob_interact(player, item_stack).await }) diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index a06c98a4a..71a3a040f 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -264,7 +264,7 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any { /// Returns true if the interaction was handled. fn interact<'a>( &'a self, - _player: &'a Player, + _player: &'a Arc, _item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async { false }) diff --git a/pumpkin/src/entity/passive/chicken.rs b/pumpkin/src/entity/passive/chicken.rs index bf3e354df..6c342117e 100644 --- a/pumpkin/src/entity/passive/chicken.rs +++ b/pumpkin/src/entity/passive/chicken.rs @@ -113,7 +113,7 @@ impl Mob for ChickenEntity { fn mob_interact<'a>( &'a self, - player: &'a Player, + player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async move { diff --git a/pumpkin/src/entity/passive/cow.rs b/pumpkin/src/entity/passive/cow.rs index 9cf24aea2..017e94ff5 100644 --- a/pumpkin/src/entity/passive/cow.rs +++ b/pumpkin/src/entity/passive/cow.rs @@ -74,7 +74,7 @@ impl Mob for CowEntity { fn mob_interact<'a>( &'a self, - player: &'a Player, + player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async move { diff --git a/pumpkin/src/entity/passive/pig.rs b/pumpkin/src/entity/passive/pig.rs index e8316d25f..1f8d26e42 100644 --- a/pumpkin/src/entity/passive/pig.rs +++ b/pumpkin/src/entity/passive/pig.rs @@ -79,7 +79,7 @@ impl Mob for PigEntity { fn mob_interact<'a>( &'a self, - player: &'a Player, + player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async move { diff --git a/pumpkin/src/entity/passive/sheep.rs b/pumpkin/src/entity/passive/sheep.rs index 0ddd0cf08..ddbe086f1 100644 --- a/pumpkin/src/entity/passive/sheep.rs +++ b/pumpkin/src/entity/passive/sheep.rs @@ -140,7 +140,7 @@ impl Mob for SheepEntity { fn mob_interact<'a>( &'a self, - player: &'a Player, + player: &'a Arc, item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async move { diff --git a/pumpkin/src/entity/passive/villager/mod.rs b/pumpkin/src/entity/passive/villager/mod.rs index 2f71d7eff..711640d33 100644 --- a/pumpkin/src/entity/passive/villager/mod.rs +++ b/pumpkin/src/entity/passive/villager/mod.rs @@ -268,7 +268,7 @@ impl VillagerEntity { .await; } - pub async fn open_trading_screen(&self, player: &Player) { + pub async fn open_trading_screen(&self, player: &Arc) { let self_weak = self.self_arc.lock().await; if let Some(self_arc) = self_weak.as_ref().and_then(std::sync::Weak::upgrade) { player.open_handled_screen(&*self_arc, None).await; @@ -435,9 +435,10 @@ impl Mob for VillagerEntity { fn mob_interact<'a>( &'a self, - player: &'a crate::entity::player::Player, + player: &'a Arc, _item_stack: &'a mut pumpkin_data::item_stack::ItemStack, ) -> crate::entity::EntityBaseFuture<'a, bool> { + let player = player.clone(); Box::pin(async move { if self.get_entity().age.load(Ordering::Relaxed) < 0 { self.set_unhappy().await; @@ -451,7 +452,7 @@ impl Mob for VillagerEntity { } drop(offers); - self.open_trading_screen(player).await; + self.open_trading_screen(&player).await; true }) diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 063bf4385..0427c9704 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -68,7 +68,9 @@ use pumpkin_protocol::java::client::play::{ CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent, Metadata, PlayerAction, PlayerInfoFlags, PreviousMessage, }; -use pumpkin_protocol::java::server::play::{SClickSlot, SRenameItem, SlotActionType}; +use pumpkin_protocol::java::server::play::{ + SClickSlot, SContainerButtonClick, SRenameItem, SlotActionType, +}; use pumpkin_util::math::{ boundingbox::BoundingBox, experience, position::BlockPos, vector2::Vector2, vector3::Vector3, }; @@ -493,6 +495,7 @@ pub struct Player { pub tab_list_order: AtomicI32, pub tab_list_latency: AtomicI32, pub tab_list_listed: AtomicBool, + pub enchantment_seed: AtomicI32, } impl Player { @@ -548,6 +551,7 @@ impl Player { // TODO: Load this from previous instance hunger_manager: HungerManager::default(), current_block_destroy_stage: AtomicI32::new(-1), + enchantment_seed: AtomicI32::new(rand::random()), open_container: AtomicCell::new(None), open_container_pos: AtomicCell::new(None), tick_counter: AtomicI32::new(0), @@ -3063,7 +3067,7 @@ impl Player { .store(current_id % 100 + 1, Ordering::Relaxed); } - pub async fn close_handled_screen(&self) { + pub async fn close_handled_screen(self: &Arc) { self.client .enqueue_packet(&CCloseContainer::new( self.current_screen_handler @@ -3078,27 +3082,31 @@ impl Player { self.on_handled_screen_closed().await; } - pub async fn on_handled_screen_closed(&self) { - // let window_type = { - // let handler_lock = self.current_screen_handler.lock().await; - // let mut handler = handler_lock.lock().await; - // let wt = handler.window_type(); - // handler.on_closed(self).await; - // wt - // }; + pub async fn on_handled_screen_closed(self: &Arc) { + let current_screen_handler: Arc> = + self.current_screen_handler.lock().await.clone(); - // TODO - // if let Some(server) = self.living_entity.entity.world.load().server.upgrade() { - // server - // .plugin_manager - // .fire(InventoryCloseEvent::new(&self, window_type)) - // .await; - // } + let window_type = { + let mut handler = current_screen_handler.lock().await; + let wt = handler.window_type(); + handler.on_closed(self.as_ref()).await; + wt + }; + + if let Some(server) = self.living_entity.entity.world.load().server.upgrade() { + server + .plugin_manager + .fire( + crate::plugin::api::events::player::inventory_close::InventoryCloseEvent::new( + self, + window_type, + ), + ) + .await; + } let player_screen_handler: Arc> = self.player_screen_handler.clone(); - let current_screen_handler: Arc> = - self.current_screen_handler.lock().await.clone(); if !Arc::ptr_eq(&player_screen_handler, ¤t_screen_handler) { player_screen_handler @@ -3138,7 +3146,7 @@ impl Player { } pub async fn open_handled_screen( - &self, + self: &Arc, screen_handler_factory: &dyn ScreenHandlerFactory, block_pos: Option, ) -> Option { @@ -3160,7 +3168,7 @@ impl Player { .create_screen_handler( self.screen_handler_sync_id.load(Ordering::Relaxed), &self.inventory, - self, + self.as_ref(), ) .await { @@ -3188,7 +3196,7 @@ impl Player { } pub async fn open_handled_screen_direct( - &self, + self: &Arc, screen_handler: Arc>, title: TextComponent, ) { @@ -3432,7 +3440,20 @@ impl Player { } } - /// Check if the player has a specific permission + /// Handles when the player clicks a button in a container (e.g. Enchantment Table) + pub async fn on_container_button_click(self: &Arc, packet: SContainerButtonClick) { + let screen_handler = self.current_screen_handler.lock().await.clone(); + let mut screen_handler = screen_handler.lock().await; + + if i32::from(screen_handler.sync_id()) != packet.window_id.0 { + return; + } + + screen_handler + .on_button_click(self.as_ref(), packet.button_id.0) + .await; + } + pub async fn has_permission(self: &Arc, server: &Server, node: &str) -> bool { let perm_manager = server.permission_manager.read().await; let result = perm_manager @@ -3670,6 +3691,7 @@ impl NBTStorage for Player { } else { nbt.put_bool(false); } + nbt.put_int(self.enchantment_seed.load(Ordering::Relaxed)); }) } @@ -3721,6 +3743,8 @@ impl NBTStorage for Player { force, })); } + self.enchantment_seed + .store(nbt.get_int().unwrap_or(rand::random()), Ordering::Relaxed); }) } } @@ -4285,6 +4309,16 @@ impl InventoryPlayer for Player { }) } + fn enchantment_seed(&self) -> i32 { + self.enchantment_seed.load(Ordering::Relaxed) + } + + fn set_enchantment_seed(&self, seed: i32) -> PlayerFuture<'_, ()> { + Box::pin(async move { + self.enchantment_seed.store(seed, Ordering::Relaxed); + }) + } + fn get_inventory(&self) -> Arc { self.inventory.clone() } diff --git a/pumpkin/src/entity/vehicle/boat.rs b/pumpkin/src/entity/vehicle/boat.rs index c1e1fd16c..41f873d4c 100644 --- a/pumpkin/src/entity/vehicle/boat.rs +++ b/pumpkin/src/entity/vehicle/boat.rs @@ -185,7 +185,7 @@ impl EntityBase for BoatEntity { fn interact<'a>( &'a self, - player: &'a Player, + player: &'a Arc, _item_stack: &'a mut ItemStack, ) -> EntityBaseFuture<'a, bool> { Box::pin(async move { diff --git a/pumpkin/src/net/java/mod.rs b/pumpkin/src/net/java/mod.rs index 96054f977..f04371d6a 100644 --- a/pumpkin/src/net/java/mod.rs +++ b/pumpkin/src/net/java/mod.rs @@ -8,9 +8,9 @@ use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_protocol::java::server::play::{ SAttack, SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand, SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport, - SCookieResponse as SPCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle, - SPaddleBoat, SPickItemFromBlock, SPlaceRecipe, SPlayPingRequest, SPlayerAbilities, - SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition, + SContainerButtonClick, SCookieResponse as SPCookieResponse, SCustomPayload, SInteract, + SKeepAlive, SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlaceRecipe, SPlayPingRequest, + SPlayerAbilities, SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings, SRecipeBookSeenRecipe, SRenameItem, SSelectTrade, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn, @@ -817,6 +817,11 @@ impl JavaClient { .on_slot_click(SClickSlot::read(payload, &version)?, server) .await; } + id if id == SContainerButtonClick::to_id(version) => { + player + .on_container_button_click(SContainerButtonClick::read(payload, &version)?) + .await; + } id if id == SSetHeldItem::to_id(version) => { self.handle_set_held_item(player, SSetHeldItem::read(payload, &version)?) .await; diff --git a/pumpkin/src/net/java/play.rs b/pumpkin/src/net/java/play.rs index f124ee842..6d3aa2823 100644 --- a/pumpkin/src/net/java/play.rs +++ b/pumpkin/src/net/java/play.rs @@ -2166,7 +2166,7 @@ impl JavaClient { #[expect(clippy::too_many_arguments)] async fn call_use_item_on( &self, - player: &Player, + player: &Arc, position: &BlockPos, cursor_pos: &Vector3, face: &BlockDirection, @@ -2509,7 +2509,7 @@ impl JavaClient { pub async fn handle_close_container( &self, - player: &Player, + player: &Arc, _server: &Server, _packet: SCloseContainer, ) {