feat: add enchanting table

This commit is contained in:
Alexander Medvedev 2026-05-06 22:29:29 +02:00
parent fd56a5be7a
commit 63d39f46d4
36 changed files with 1377 additions and 85 deletions

29
Cargo.lock generated
View File

@ -892,9 +892,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]] [[package]]
name = "crossfire" name = "crossfire"
version = "3.1.7" version = "3.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0042e53977a94d5d10de04ce7eb6016aa212c08e83ea202f7a9f102ff104303" checksum = "ebed7d0a3a92ebc771f880516a5eecef5942a0fc67855dc4f30e719b04c77ac5"
dependencies = [ dependencies = [
"crossbeam-utils", "crossbeam-utils",
"futures-core", "futures-core",
@ -936,9 +936,9 @@ dependencies = [
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [ dependencies = [
"generic-array", "generic-array",
"typenum", "typenum",
@ -1035,7 +1035,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [ dependencies = [
"block-buffer 0.10.4", "block-buffer 0.10.4",
"const-oid 0.9.6", "const-oid 0.9.6",
"crypto-common 0.1.6", "crypto-common 0.1.7",
"subtle", "subtle",
] ]
@ -1394,9 +1394,9 @@ dependencies = [
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.9" version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"typenum", "typenum",
"version_check", "version_check",
@ -2836,7 +2836,7 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
"ureq", "ureq",
"uuid", "uuid",
"wasmparser 0.247.0", "wasmparser 0.248.0",
"wasmtime", "wasmtime",
"wasmtime-wasi", "wasmtime-wasi",
] ]
@ -2893,6 +2893,7 @@ dependencies = [
"pumpkin-protocol", "pumpkin-protocol",
"pumpkin-util", "pumpkin-util",
"pumpkin-world", "pumpkin-world",
"rand 0.10.1",
"thiserror 2.0.18", "thiserror 2.0.18",
"tokio", "tokio",
"tracing", "tracing",
@ -4464,6 +4465,18 @@ name = "wasmparser"
version = "0.247.0" version = "0.247.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6fb4c2bee46c5ea4d40f8cdb5c131725cd976718ec56f1c8e82fbde5fa2a80" 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 = [ dependencies = [
"bitflags 2.11.1", "bitflags 2.11.1",
"hashbrown 0.17.0", "hashbrown 0.17.0",

View File

@ -120,7 +120,7 @@ console-subscriber = { version = "0.5.0", default-features = false }
crc-fast = "1.10.0" crc-fast = "1.10.0"
criterion = { version = "0.8", default-features = false } criterion = { version = "0.8", default-features = false }
crossbeam-utils = "0.8.21" crossbeam-utils = "0.8.21"
crossfire = { version = "3.1.7", features = ["compat"] } crossfire = { version = "3.1.10", features = ["compat"] }
crypto-bigint = "0.7.3" crypto-bigint = "0.7.3"
dashmap = "6.1" dashmap = "6.1"
ecdsa = "0.16.9" ecdsa = "0.16.9"
@ -160,7 +160,7 @@ ruzstd = "0.8.2"
serde_json5 = "0.2.1" serde_json5 = "0.2.1"
sha1 = "=0.11.0" sha1 = "=0.11.0"
sha2 = "=0.11.0" sha2 = "=0.11.0"
signature = "2.2.0" signature = "3.0.0"
tracing = "0.1.44" tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = [ tracing-subscriber = { version = "0.3.23", features = [
"env-filter", "env-filter",
@ -181,7 +181,7 @@ notify = "8.2.0"
wasmtime = "44.0" wasmtime = "44.0"
wasmtime-wasi = "44.0" wasmtime-wasi = "44.0"
wit-bindgen = "0.57" wit-bindgen = "0.57"
wasmparser = "0.247" wasmparser = "0.248"
postcard = { version = "1.1", features = ["alloc"] } postcard = { version = "1.1", features = ["alloc"] }
tracing-serde-structured = "0.4" tracing-serde-structured = "0.4"

View File

@ -159,7 +159,7 @@ mod test {
fn encoding() { fn encoding() {
assert_encode_success!(3, JsonOps, json!(3)); assert_encode_success!(3, JsonOps, json!(3));
assert_encode_success!(-68i8, JsonOps, json!(-68)); 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!("Hello, world!".to_string(), JsonOps, json!("Hello, world!"));
assert_encode_success!(String::new(), JsonOps, json!("")); assert_encode_success!(String::new(), JsonOps, json!(""));
assert_encode_success!(ByteBuffer::from(vec![1, 2, 3]), JsonOps, json!([1, 2, 3])); 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!(3u8, JsonOps, json!(3));
assert_encode_success!(923482312u64, JsonOps, json!(923482312)); assert_encode_success!(923_482_312u64, JsonOps, json!(923_482_312));
} }
#[test] #[test]
@ -192,7 +192,7 @@ mod test {
assert_decode!(String, json!(1), JsonOps, is_error); assert_decode!(String, json!(1), JsonOps, is_error);
assert_decode!(u32, json!(-45), JsonOps, is_error); assert_decode!(u32, json!(-45), JsonOps, is_error);
assert_decode!(u64, json!(-132541235), JsonOps, is_error); assert_decode!(u64, json!(-132_541_235), JsonOps, is_error);
assert_decode!(u64, json!(132541235), JsonOps, is_success); assert_decode!(u64, json!(132_541_235), JsonOps, is_success);
} }
} }

View File

@ -24,6 +24,18 @@ pub struct Enchantment {
pub max_level: i32, pub max_level: i32,
/// Equipment slots this enchantment's attribute modifiers apply to. /// Equipment slots this enchantment's attribute modifiers apply to.
pub slots: Vec<AttributeModifierSlot>, // TODO: add more pub slots: Vec<AttributeModifierSlot>, // 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. /// 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"); .expect("Failed to parse enchantments.json");
let mut variants = TokenStream::new(); let mut variants = TokenStream::new();
let mut all_variants = TokenStream::new();
let mut name_to_type = TokenStream::new(); let mut name_to_type = TokenStream::new();
let mut id_to_type = TokenStream::new(); let mut id_to_type = TokenStream::new();
@ -88,6 +101,7 @@ pub fn build() -> TokenStream {
let id = enchantment.id; let id = enchantment.id;
let raw_name = name.strip_prefix("minecraft:").unwrap(); let raw_name = name.strip_prefix("minecraft:").unwrap();
let format_name = format_ident!("{}", raw_name.to_shouty_snake_case()); 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 anvil_cost = enchantment.anvil_cost;
let supported_items = format_ident!( let supported_items = format_ident!(
"{}", "{}",
@ -99,6 +113,12 @@ pub fn build() -> TokenStream {
.to_uppercase() .to_uppercase()
); );
let max_level = enchantment.max_level; 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 = enchantment.slots;
let slots = slots.iter().map(AttributeModifierSlot::to_tokens); let slots = slots.iter().map(AttributeModifierSlot::to_tokens);
let Translate { translate, bedrock_translate: _, with: _ } = &*enchantment.description.0.content else { let Translate { translate, bedrock_translate: _, with: _ } = &*enchantment.description.0.content else {
@ -125,7 +145,16 @@ pub fn build() -> TokenStream {
supported_items: &ItemTag::#supported_items, supported_items: &ItemTag::#supported_items,
exclusive_set: Some(&EnchantmentTag::#exclusive_set), exclusive_set: Some(&EnchantmentTag::#exclusive_set),
max_level: #max_level, 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 { } else {
@ -139,7 +168,16 @@ pub fn build() -> TokenStream {
supported_items: &ItemTag::#supported_items, supported_items: &ItemTag::#supported_items,
exclusive_set: None, exclusive_set: None,
max_level: #max_level, 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 supported_items: &'static Tag,
pub exclusive_set: Option<&'static Tag>, pub exclusive_set: Option<&'static Tag>,
pub max_level: i32, 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 // 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 { impl Taggable for Enchantment {
#[inline] #[inline]
fn tag_key() -> RegistryKey { fn tag_key() -> RegistryKey {
@ -212,6 +265,12 @@ pub fn build() -> TokenStream {
} }
impl Enchantment { impl Enchantment {
pub const ALL: &'static [&'static Self] = &[#all_variants];
pub fn all() -> Iter<'static, &'static Self> {
Self::ALL.iter()
}
#variants #variants
pub fn from_name(name: &str) -> Option<&'static Self> { pub fn from_name(name: &str) -> Option<&'static Self> {

View File

@ -62,6 +62,13 @@ pub struct ItemComponents {
pub damage_resistant: Option<DamageResistantComponent>, pub damage_resistant: Option<DamageResistantComponent>,
#[serde(rename = "minecraft:weapon")] #[serde(rename = "minecraft:weapon")]
pub weapon: Option<WeaponComponent>, pub weapon: Option<WeaponComponent>,
#[serde(rename = "minecraft:enchantable")]
pub enchantable: Option<EnchantableComponent>,
}
#[derive(Deserialize)]
pub struct EnchantableComponent {
pub value: i32,
} }
impl ToTokens for ItemComponents { impl ToTokens for ItemComponents {
@ -573,6 +580,11 @@ impl ToTokens for ItemComponents {
shearing_sound: #shearing_sound 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 }), });
}
} }
} }

View File

@ -4,9 +4,9 @@ use crate::attributes::Attributes;
use crate::data_component::DataComponent; use crate::data_component::DataComponent;
use crate::data_component::DataComponent::{ use crate::data_component::DataComponent::{
AttributeModifiers, BlocksAttacks, Consumable, CustomData, CustomName, Damage, DamageResistant, AttributeModifiers, BlocksAttacks, Consumable, CustomData, CustomName, Damage, DamageResistant,
DeathProtection, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, ItemModel, DeathProtection, Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food,
ItemName, JukeboxPlayable, MaxDamage, MaxStackSize, PotionContents, StoredEnchantments, Tool, ItemModel, ItemName, JukeboxPlayable, MaxDamage, MaxStackSize, PotionContents,
Unbreakable, UseCooldown, Weapon, StoredEnchantments, Tool, Unbreakable, UseCooldown, Weapon,
}; };
use crate::effect::{self, StatusEffect}; use crate::effect::{self, StatusEffect};
use crate::entity_type::EntityType; use crate::entity_type::EntityType;
@ -274,7 +274,7 @@ impl DataComponentImpl for ItemModelImpl {
pub struct LoreImpl; pub struct LoreImpl;
#[derive(Clone, Hash, PartialEq, Eq)] #[derive(Clone, Hash, PartialEq, Eq)]
pub struct RarityImpl; pub struct RarityImpl;
#[derive(Clone, Hash, PartialEq, Eq)] #[derive(Clone, Hash, PartialEq, Eq, Default)]
pub struct EnchantmentsImpl { pub struct EnchantmentsImpl {
pub enchantment: Cow<'static, [(&'static Enchantment, i32)]>, pub enchantment: Cow<'static, [(&'static Enchantment, i32)]>,
} }
@ -1377,7 +1377,12 @@ impl Hash for EntityTypeOrTag {
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[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)] #[derive(Clone, Hash, PartialEq)]
pub struct EquippableImpl { pub struct EquippableImpl {
pub slot: &'static EquipmentSlot, pub slot: &'static EquipmentSlot,

View File

@ -18,6 +18,19 @@ pub struct Enchantment {
pub exclusive_set: Option<&'static Tag>, pub exclusive_set: Option<&'static Tag>,
pub max_level: i32, pub max_level: i32,
pub slots: &'static [AttributeModifierSlot], 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 { impl Taggable for Enchantment {
#[inline] #[inline]
@ -59,6 +72,54 @@ pub enum AttributeModifierSlot {
Saddle, Saddle,
} }
impl Enchantment { 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 { pub const AQUA_AFFINITY: Self = Self {
id: 0u8, id: 0u8,
name: "minecraft:aqua_affinity", name: "minecraft:aqua_affinity",
@ -69,6 +130,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::Head], 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 { pub const BANE_OF_ARTHROPODS: Self = Self {
id: 1u8, id: 1u8,
@ -80,6 +150,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE),
max_level: 5i32, max_level: 5i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const BINDING_CURSE: Self = Self {
id: 2u8, id: 2u8,
@ -91,6 +170,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::Armor], 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 { pub const BLAST_PROTECTION: Self = Self {
id: 3u8, id: 3u8,
@ -102,6 +190,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR),
max_level: 4i32, max_level: 4i32,
slots: &[AttributeModifierSlot::Armor], 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 { pub const BREACH: Self = Self {
id: 4u8, id: 4u8,
@ -113,6 +210,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE),
max_level: 4i32, max_level: 4i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const CHANNELING: Self = Self {
id: 5u8, id: 5u8,
@ -124,6 +230,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const DENSITY: Self = Self {
id: 6u8, id: 6u8,
@ -135,6 +250,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE),
max_level: 5i32, max_level: 5i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const DEPTH_STRIDER: Self = Self {
id: 7u8, id: 7u8,
@ -146,6 +270,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOOTS), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOOTS),
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Feet], 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 { pub const EFFICIENCY: Self = Self {
id: 8u8, id: 8u8,
@ -157,6 +290,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 5i32, max_level: 5i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const FEATHER_FALLING: Self = Self {
id: 9u8, id: 9u8,
@ -168,6 +310,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 4i32, max_level: 4i32,
slots: &[AttributeModifierSlot::Armor], 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 { pub const FIRE_ASPECT: Self = Self {
id: 10u8, id: 10u8,
@ -179,6 +330,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 2i32, max_level: 2i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const FIRE_PROTECTION: Self = Self {
id: 11u8, id: 11u8,
@ -190,6 +350,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR),
max_level: 4i32, max_level: 4i32,
slots: &[AttributeModifierSlot::Armor], 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 { pub const FLAME: Self = Self {
id: 12u8, id: 12u8,
@ -201,6 +370,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const FORTUNE: Self = Self {
id: 13u8, id: 13u8,
@ -212,6 +390,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_MINING), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_MINING),
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const FROST_WALKER: Self = Self {
id: 14u8, id: 14u8,
@ -223,6 +410,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOOTS), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOOTS),
max_level: 2i32, max_level: 2i32,
slots: &[AttributeModifierSlot::Feet], 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 { pub const IMPALING: Self = Self {
id: 15u8, id: 15u8,
@ -234,6 +430,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE),
max_level: 5i32, max_level: 5i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const INFINITY: Self = Self {
id: 16u8, id: 16u8,
@ -245,6 +450,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOW), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_BOW),
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const KNOCKBACK: Self = Self {
id: 17u8, id: 17u8,
@ -256,6 +470,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 2i32, max_level: 2i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const LOOTING: Self = Self {
id: 18u8, id: 18u8,
@ -267,6 +490,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const LOYALTY: Self = Self {
id: 19u8, id: 19u8,
@ -278,6 +510,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const LUCK_OF_THE_SEA: Self = Self {
id: 20u8, id: 20u8,
@ -289,6 +530,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const LUNGE: Self = Self {
id: 21u8, id: 21u8,
@ -300,6 +550,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Hand], 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 { pub const LURE: Self = Self {
id: 22u8, id: 22u8,
@ -311,6 +570,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const MENDING: Self = Self {
id: 23u8, id: 23u8,
@ -322,6 +590,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::Any], 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 { pub const MULTISHOT: Self = Self {
id: 24u8, id: 24u8,
@ -333,6 +610,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_CROSSBOW), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_CROSSBOW),
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const PIERCING: Self = Self {
id: 25u8, id: 25u8,
@ -344,6 +630,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_CROSSBOW), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_CROSSBOW),
max_level: 4i32, max_level: 4i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const POWER: Self = Self {
id: 26u8, id: 26u8,
@ -355,6 +650,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 5i32, max_level: 5i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const PROJECTILE_PROTECTION: Self = Self {
id: 27u8, id: 27u8,
@ -366,6 +670,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR),
max_level: 4i32, max_level: 4i32,
slots: &[AttributeModifierSlot::Armor], 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 { pub const PROTECTION: Self = Self {
id: 28u8, id: 28u8,
@ -377,6 +690,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_ARMOR),
max_level: 4i32, max_level: 4i32,
slots: &[AttributeModifierSlot::Armor], 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 { pub const PUNCH: Self = Self {
id: 29u8, id: 29u8,
@ -388,6 +710,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 2i32, max_level: 2i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const QUICK_CHARGE: Self = Self {
id: 30u8, id: 30u8,
@ -402,6 +733,15 @@ impl Enchantment {
AttributeModifierSlot::MainHand, AttributeModifierSlot::MainHand,
AttributeModifierSlot::OffHand, 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 { pub const RESPIRATION: Self = Self {
id: 31u8, id: 31u8,
@ -413,6 +753,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Head], 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 { pub const RIPTIDE: Self = Self {
id: 32u8, id: 32u8,
@ -424,6 +773,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_RIPTIDE), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_RIPTIDE),
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Hand], 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 { pub const SHARPNESS: Self = Self {
id: 33u8, id: 33u8,
@ -435,6 +793,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE),
max_level: 5i32, max_level: 5i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const SILK_TOUCH: Self = Self {
id: 34u8, id: 34u8,
@ -446,6 +813,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_MINING), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_MINING),
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const SMITE: Self = Self {
id: 35u8, id: 35u8,
@ -457,6 +833,15 @@ impl Enchantment {
exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE), exclusive_set: Some(&EnchantmentTag::MINECRAFT_EXCLUSIVE_SET_DAMAGE),
max_level: 5i32, max_level: 5i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const SOUL_SPEED: Self = Self {
id: 36u8, id: 36u8,
@ -468,6 +853,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Feet], 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 { pub const SWEEPING_EDGE: Self = Self {
id: 37u8, id: 37u8,
@ -479,6 +873,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::MainHand], 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 { pub const SWIFT_SNEAK: Self = Self {
id: 38u8, id: 38u8,
@ -490,6 +893,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Legs], 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 { pub const THORNS: Self = Self {
id: 39u8, id: 39u8,
@ -501,6 +913,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Any], 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 { pub const UNBREAKING: Self = Self {
id: 40u8, id: 40u8,
@ -512,6 +933,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::Any], 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 { pub const VANISHING_CURSE: Self = Self {
id: 41u8, id: 41u8,
@ -523,6 +953,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 1i32, max_level: 1i32,
slots: &[AttributeModifierSlot::Any], 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 { pub const WIND_BURST: Self = Self {
id: 42u8, id: 42u8,
@ -534,6 +973,15 @@ impl Enchantment {
exclusive_set: None, exclusive_set: None,
max_level: 3i32, max_level: 3i32,
slots: &[AttributeModifierSlot::MainHand], 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> { pub fn from_name(name: &str) -> Option<&'static Self> {
match name { match name {

View File

@ -2949,6 +2949,7 @@ impl Item {
attribute_modifiers: Cow::Borrowed(&[]), attribute_modifiers: Cow::Borrowed(&[]),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 1 }),
], ],
}; };
pub const BOOKSHELF: Item = Item { pub const BOOKSHELF: Item = Item {
@ -3008,6 +3009,7 @@ impl Item {
attribute_modifiers: Cow::Borrowed(&[]), attribute_modifiers: Cow::Borrowed(&[]),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 1 }),
], ],
}; };
pub const BOWL: Item = Item { pub const BOWL: Item = Item {
@ -4259,6 +4261,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 12 }),
], ],
}; };
pub const CHAINMAIL_CHESTPLATE: Item = Item { pub const CHAINMAIL_CHESTPLATE: Item = Item {
@ -4311,6 +4314,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 12 }),
], ],
}; };
pub const CHAINMAIL_HELMET: Item = Item { pub const CHAINMAIL_HELMET: Item = Item {
@ -4363,6 +4367,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 12 }),
], ],
}; };
pub const CHAINMAIL_LEGGINGS: Item = Item { pub const CHAINMAIL_LEGGINGS: Item = Item {
@ -4415,6 +4420,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 12 }),
], ],
}; };
pub const CHARCOAL: Item = Item { pub const CHARCOAL: Item = Item {
@ -6118,6 +6124,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 13 }),
], ],
}; };
pub const COPPER_BARS: Item = Item { pub const COPPER_BARS: Item = Item {
@ -6208,6 +6215,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 8 }),
], ],
}; };
pub const COPPER_BULB: Item = Item { pub const COPPER_BULB: Item = Item {
@ -6317,6 +6325,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 8 }),
], ],
}; };
pub const COPPER_DOOR: Item = Item { pub const COPPER_DOOR: Item = Item {
@ -6445,6 +6454,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 8 }),
], ],
}; };
pub const COPPER_HOE: Item = Item { pub const COPPER_HOE: Item = Item {
@ -6507,6 +6517,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 13 }),
], ],
}; };
pub const COPPER_HORSE_ARMOR: Item = Item { pub const COPPER_HORSE_ARMOR: Item = Item {
@ -6647,6 +6658,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 8 }),
], ],
}; };
pub const COPPER_NAUTILUS_ARMOR: Item = Item { pub const COPPER_NAUTILUS_ARMOR: Item = Item {
@ -6797,6 +6809,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 13 }),
], ],
}; };
pub const COPPER_SHOVEL: Item = Item { pub const COPPER_SHOVEL: Item = Item {
@ -6859,6 +6872,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 13 }),
], ],
}; };
pub const COPPER_SPEAR: Item = Item { pub const COPPER_SPEAR: Item = Item {
@ -6901,6 +6915,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 13 }),
], ],
}; };
pub const COPPER_SWORD: Item = Item { pub const COPPER_SWORD: Item = Item {
@ -6968,6 +6983,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 13 }),
], ],
}; };
pub const COPPER_TORCH: Item = Item { pub const COPPER_TORCH: Item = Item {
@ -7638,6 +7654,7 @@ impl Item {
attribute_modifiers: Cow::Borrowed(&[]), attribute_modifiers: Cow::Borrowed(&[]),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 1 }),
], ],
}; };
pub const CRYING_OBSIDIAN: Item = Item { pub const CRYING_OBSIDIAN: Item = Item {
@ -9350,6 +9367,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_BLOCK: Item = Item { pub const DIAMOND_BLOCK: Item = Item {
@ -9421,6 +9439,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_CHESTPLATE: Item = Item { pub const DIAMOND_CHESTPLATE: Item = Item {
@ -9473,6 +9492,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_HELMET: Item = Item { pub const DIAMOND_HELMET: Item = Item {
@ -9525,6 +9545,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_HOE: Item = Item { pub const DIAMOND_HOE: Item = Item {
@ -9587,6 +9608,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_HORSE_ARMOR: Item = Item { pub const DIAMOND_HORSE_ARMOR: Item = Item {
@ -9689,6 +9711,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_NAUTILUS_ARMOR: Item = Item { pub const DIAMOND_NAUTILUS_ARMOR: Item = Item {
@ -9820,6 +9843,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_SHOVEL: Item = Item { pub const DIAMOND_SHOVEL: Item = Item {
@ -9882,6 +9906,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_SPEAR: Item = Item { pub const DIAMOND_SPEAR: Item = Item {
@ -9924,6 +9949,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIAMOND_SWORD: Item = Item { pub const DIAMOND_SWORD: Item = Item {
@ -9991,6 +10017,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 10 }),
], ],
}; };
pub const DIORITE: Item = Item { pub const DIORITE: Item = Item {
@ -11568,6 +11595,7 @@ impl Item {
attribute_modifiers: Cow::Borrowed(&[]), attribute_modifiers: Cow::Borrowed(&[]),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 1 }),
], ],
}; };
pub const FLETCHING_TABLE: Item = Item { pub const FLETCHING_TABLE: Item = Item {
@ -12410,6 +12438,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 22 }),
], ],
}; };
pub const GOLDEN_BOOTS: Item = Item { pub const GOLDEN_BOOTS: Item = Item {
@ -12462,6 +12491,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 25 }),
], ],
}; };
pub const GOLDEN_CARROT: Item = Item { pub const GOLDEN_CARROT: Item = Item {
@ -12551,6 +12581,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 25 }),
], ],
}; };
pub const GOLDEN_DANDELION: Item = Item { pub const GOLDEN_DANDELION: Item = Item {
@ -12622,6 +12653,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 25 }),
], ],
}; };
pub const GOLDEN_HOE: Item = Item { pub const GOLDEN_HOE: Item = Item {
@ -12684,6 +12716,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 22 }),
], ],
}; };
pub const GOLDEN_HORSE_ARMOR: Item = Item { pub const GOLDEN_HORSE_ARMOR: Item = Item {
@ -12786,6 +12819,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 25 }),
], ],
}; };
pub const GOLDEN_NAUTILUS_ARMOR: Item = Item { pub const GOLDEN_NAUTILUS_ARMOR: Item = Item {
@ -12898,6 +12932,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 22 }),
], ],
}; };
pub const GOLDEN_SHOVEL: Item = Item { pub const GOLDEN_SHOVEL: Item = Item {
@ -12960,6 +12995,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 22 }),
], ],
}; };
pub const GOLDEN_SPEAR: Item = Item { pub const GOLDEN_SPEAR: Item = Item {
@ -13002,6 +13038,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 22 }),
], ],
}; };
pub const GOLDEN_SWORD: Item = Item { pub const GOLDEN_SWORD: Item = Item {
@ -13069,6 +13106,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 22 }),
], ],
}; };
pub const GRANITE: Item = Item { pub const GRANITE: Item = Item {
@ -14589,6 +14627,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 14 }),
], ],
}; };
pub const IRON_BARS: Item = Item { pub const IRON_BARS: Item = Item {
@ -14679,6 +14718,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 9 }),
], ],
}; };
pub const IRON_CHAIN: Item = Item { pub const IRON_CHAIN: Item = Item {
@ -14750,6 +14790,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 9 }),
], ],
}; };
pub const IRON_DOOR: Item = Item { pub const IRON_DOOR: Item = Item {
@ -14840,6 +14881,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 9 }),
], ],
}; };
pub const IRON_HOE: Item = Item { pub const IRON_HOE: Item = Item {
@ -14902,6 +14944,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 14 }),
], ],
}; };
pub const IRON_HORSE_ARMOR: Item = Item { pub const IRON_HORSE_ARMOR: Item = Item {
@ -15023,6 +15066,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 9 }),
], ],
}; };
pub const IRON_NAUTILUS_ARMOR: Item = Item { pub const IRON_NAUTILUS_ARMOR: Item = Item {
@ -15173,6 +15217,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 14 }),
], ],
}; };
pub const IRON_SHOVEL: Item = Item { pub const IRON_SHOVEL: Item = Item {
@ -15235,6 +15280,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 14 }),
], ],
}; };
pub const IRON_SPEAR: Item = Item { pub const IRON_SPEAR: Item = Item {
@ -15277,6 +15323,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 14 }),
], ],
}; };
pub const IRON_SWORD: Item = Item { pub const IRON_SWORD: Item = Item {
@ -15344,6 +15391,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 14 }),
], ],
}; };
pub const IRON_TRAPDOOR: Item = Item { pub const IRON_TRAPDOOR: Item = Item {
@ -16080,6 +16128,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const LEATHER_CHESTPLATE: Item = Item { pub const LEATHER_CHESTPLATE: Item = Item {
@ -16132,6 +16181,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const LEATHER_HELMET: Item = Item { pub const LEATHER_HELMET: Item = Item {
@ -16184,6 +16234,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const LEATHER_HORSE_ARMOR: Item = Item { pub const LEATHER_HORSE_ARMOR: Item = Item {
@ -16286,6 +16337,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const LECTERN: Item = Item { pub const LECTERN: Item = Item {
@ -17525,6 +17577,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const MAGENTA_BANNER: Item = Item { pub const MAGENTA_BANNER: Item = Item {
@ -19819,6 +19872,7 @@ impl Item {
res_type: DamageResistantType::Fire, res_type: DamageResistantType::Fire,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_BLOCK: Item = Item { pub const NETHERITE_BLOCK: Item = Item {
@ -19909,6 +19963,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_CHESTPLATE: Item = Item { pub const NETHERITE_CHESTPLATE: Item = Item {
@ -19974,6 +20029,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_HELMET: Item = Item { pub const NETHERITE_HELMET: Item = Item {
@ -20039,6 +20095,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_HOE: Item = Item { pub const NETHERITE_HOE: Item = Item {
@ -20107,6 +20164,7 @@ impl Item {
res_type: DamageResistantType::Fire, res_type: DamageResistantType::Fire,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_HORSE_ARMOR: Item = Item { pub const NETHERITE_HORSE_ARMOR: Item = Item {
@ -20260,6 +20318,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_NAUTILUS_ARMOR: Item = Item { pub const NETHERITE_NAUTILUS_ARMOR: Item = Item {
@ -20391,6 +20450,7 @@ impl Item {
res_type: DamageResistantType::Fire, res_type: DamageResistantType::Fire,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_SCRAP: Item = Item { pub const NETHERITE_SCRAP: Item = Item {
@ -20484,6 +20544,7 @@ impl Item {
res_type: DamageResistantType::Fire, res_type: DamageResistantType::Fire,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_SPEAR: Item = Item { pub const NETHERITE_SPEAR: Item = Item {
@ -20532,6 +20593,7 @@ impl Item {
res_type: DamageResistantType::Fire, res_type: DamageResistantType::Fire,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_SWORD: Item = Item { pub const NETHERITE_SWORD: Item = Item {
@ -20605,6 +20667,7 @@ impl Item {
res_type: DamageResistantType::Fire, res_type: DamageResistantType::Fire,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const NETHERITE_UPGRADE_SMITHING_TEMPLATE: Item = Item { pub const NETHERITE_UPGRADE_SMITHING_TEMPLATE: Item = Item {
@ -28151,6 +28214,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 5 }),
], ],
}; };
pub const STONE_BRICK_SLAB: Item = Item { pub const STONE_BRICK_SLAB: Item = Item {
@ -28308,6 +28372,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 5 }),
], ],
}; };
pub const STONE_PICKAXE: Item = Item { pub const STONE_PICKAXE: Item = Item {
@ -28370,6 +28435,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 5 }),
], ],
}; };
pub const STONE_PRESSURE_PLATE: Item = Item { pub const STONE_PRESSURE_PLATE: Item = Item {
@ -28451,6 +28517,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 5 }),
], ],
}; };
pub const STONE_SLAB: Item = Item { pub const STONE_SLAB: Item = Item {
@ -28512,6 +28579,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 5 }),
], ],
}; };
pub const STONE_STAIRS: Item = Item { pub const STONE_STAIRS: Item = Item {
@ -28598,6 +28666,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 5 }),
], ],
}; };
pub const STONECUTTER: Item = Item { pub const STONECUTTER: Item = Item {
@ -29769,6 +29838,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 1 }),
], ],
}; };
pub const TRIPWIRE_HOOK: Item = Item { pub const TRIPWIRE_HOOK: Item = Item {
@ -30151,6 +30221,7 @@ impl Item {
shearing_sound: IdOr::Id(Sound::ItemShearsSnip), shearing_sound: IdOr::Id(Sound::ItemShearsSnip),
}, },
), ),
(Enchantable, &EnchantableImpl { value: 9 }),
], ],
}; };
pub const TURTLE_SCUTE: Item = Item { pub const TURTLE_SCUTE: Item = Item {
@ -32948,6 +33019,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const WOODEN_HOE: Item = Item { pub const WOODEN_HOE: Item = Item {
@ -33010,6 +33082,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const WOODEN_PICKAXE: Item = Item { pub const WOODEN_PICKAXE: Item = Item {
@ -33072,6 +33145,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const WOODEN_SHOVEL: Item = Item { pub const WOODEN_SHOVEL: Item = Item {
@ -33134,6 +33208,7 @@ impl Item {
item_damage_per_attack: 2, item_damage_per_attack: 2,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const WOODEN_SPEAR: Item = Item { pub const WOODEN_SPEAR: Item = Item {
@ -33176,6 +33251,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const WOODEN_SWORD: Item = Item { pub const WOODEN_SWORD: Item = Item {
@ -33243,6 +33319,7 @@ impl Item {
item_damage_per_attack: 1, item_damage_per_attack: 1,
}, },
), ),
(Enchantable, &EnchantableImpl { value: 15 }),
], ],
}; };
pub const WRITABLE_BOOK: Item = Item { pub const WRITABLE_BOOK: Item = Item {

View File

@ -95,17 +95,54 @@ impl ItemStack {
None None
} }
pub fn get_data_component_mut<T: DataComponentImpl + 'static>(&mut self) -> Option<&mut T> { pub fn get_data_component_mut<T: DataComponentImpl + 'static>(&mut self) -> Option<&mut T> {
let to_get_id = &T::get_enum(); let to_get_id = T::get_enum();
for (id, component) in &mut self.patch { if let Some(index) = self.patch.iter().position(|(id, _)| *id == to_get_id) {
if id == to_get_id { return self.patch[index]
return component .1
.as_mut() .as_mut()
.map(|component| get_mut::<T>(component.as_mut())); .map(|component| get_mut::<T>(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::<T>(c.as_mut()));
} }
None None
} }
pub fn has_enchantments(&self) -> bool {
self.get_data_component::<EnchantmentsImpl>()
.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::<EnchantmentsImpl>() {
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 { pub const EMPTY: &'static Self = &Self {
item_count: 0, item_count: 0,
item: &Item::AIR, item: &Item::AIR,

View File

@ -12,6 +12,7 @@ pumpkin-data.workspace = true
pumpkin-world.workspace = true pumpkin-world.workspace = true
pumpkin-util.workspace = true pumpkin-util.workspace = true
rand.workspace = true
tracing.workspace = true tracing.workspace = true
tokio.workspace = true tokio.workspace = true
thiserror.workspace = true thiserror.workspace = true

View File

@ -8,7 +8,7 @@ use crate::{
player::player_inventory::PlayerInventory, player::player_inventory::PlayerInventory,
screen_handler::{ screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture, ScreenHandlerFuture, offer_or_drop_stack,
}, },
slot::NormalSlot, slot::NormalSlot,
window_property::{Anvil, WindowProperty}, window_property::{Anvil, WindowProperty},
@ -121,7 +121,7 @@ impl ScreenHandler for AnvilScreenHandler {
for i in 0..2 { for i in 0..2 {
let stack = self.inventory.remove_stack(i).await; let stack = self.inventory.remove_stack(i).await;
if !stack.is_empty() { 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; self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;

View File

@ -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<dyn Inventory>,
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<PlayerInventory>,
inventory: &Arc<dyn Inventory>,
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<dyn Inventory> = 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::<EnchantableImpl>()
.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::<EnchantableImpl>()
.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;
}
})
}
}

View File

@ -0,0 +1,6 @@
//! Enchanting module.
//!
//! This module handles enchanting mechanics including:
//! - [`EnchantingTableScreenHandler`] - Screen handler for enchanting tables
pub mod enchanting_screen_handler;

View File

@ -36,6 +36,7 @@ pub mod container_click;
pub mod crafting; pub mod crafting;
pub mod double; pub mod double;
pub mod drag_handler; pub mod drag_handler;
pub mod enchanting;
pub mod entity_equipment; pub mod entity_equipment;
mod error; mod error;
pub mod furnace_like; pub mod furnace_like;

View File

@ -8,7 +8,7 @@ use crate::{
player::player_inventory::PlayerInventory, player::player_inventory::PlayerInventory,
screen_handler::{ screen_handler::{
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour, InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
ScreenHandlerFuture, ScreenHandlerFuture, offer_or_drop_stack,
}, },
slot::NormalSlot, slot::NormalSlot,
}; };
@ -110,7 +110,7 @@ impl ScreenHandler for MerchantScreenHandler {
// Drop inputs only, output is virtual/ghost in some sense or just cleared // Drop inputs only, output is virtual/ghost in some sense or just cleared
let stack = self.inventory.remove_stack(i).await; let stack = self.inventory.remove_stack(i).await;
if !stack.is_empty() { if !stack.is_empty() {
player.drop_item(stack, false).await; offer_or_drop_stack(player, stack).await;
} }
} }
// Clear output slot // Clear output slot

View File

@ -142,6 +142,12 @@ pub trait InventoryPlayer: Send + Sync {
/// Adds or removes experience levels. /// Adds or removes experience levels.
fn add_experience_levels(&self, levels: i32) -> PlayerFuture<'_, ()>; 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. /// Sends a full container content packet.
fn enqueue_inventory_packet<'a>( fn enqueue_inventory_packet<'a>(
&'a self, &'a self,
@ -673,6 +679,15 @@ pub trait ScreenHandler: Send + Sync {
slot_index: i32, slot_index: i32,
) -> ItemStackFuture<'a>; ) -> 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. /// Inserts an item into a range of slots.
/// ///
/// First tries to stack with existing items, then fills empty slots. /// First tries to stack with existing items, then fills empty slots.

View File

@ -13,11 +13,12 @@ use pumpkin_protocol::java::{
play::{ play::{
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand, SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
SClientInformationPlay, SCloseContainer, SCommandSuggestion, SConfirmTeleport, SClientInformationPlay, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
SCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle, SPaddleBoat, SContainerButtonClick, SCookieResponse, SCustomPayload, SInteract, SKeepAlive,
SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SPlayerAction, SPlayerCommand, SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities,
SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
SPlayerSession, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SSetCommandBlock,
SSwingArm, SUpdateSign, SUseItem, SUseItemOn, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem,
SUseItemOn,
}, },
status::SStatusPingRequest, status::SStatusPingRequest,
}, },
@ -84,6 +85,7 @@ fn fuzz_all_deserializers(payload: &[u8]) {
SPlayerLoaded, SPlayerLoaded,
SPlayPingRequest, SPlayPingRequest,
SClickSlot, SClickSlot,
SContainerButtonClick,
SSetHeldItem, SSetHeldItem,
SSetCreativeSlot, SSetCreativeSlot,
SSwingArm, SSwingArm,

View File

@ -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,
}

View File

@ -10,6 +10,7 @@ mod client_tick_end;
mod close_container; mod close_container;
mod command_suggestion; mod command_suggestion;
mod confirm_teleport; mod confirm_teleport;
mod container_button_click;
mod cookie_response; mod cookie_response;
mod custom_payload; mod custom_payload;
mod interact; mod interact;
@ -53,6 +54,7 @@ pub use client_tick_end::*;
pub use close_container::*; pub use close_container::*;
pub use command_suggestion::*; pub use command_suggestion::*;
pub use confirm_teleport::*; pub use confirm_teleport::*;
pub use container_button_click::*;
pub use cookie_response::*; pub use cookie_response::*;
pub use custom_payload::*; pub use custom_payload::*;
pub use interact::*; pub use interact::*;

View File

@ -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<crate::world::World>, 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<PlayerInventory>,
_player: &'a dyn InventoryPlayer,
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
Box::pin(async move {
let inventory: Arc<dyn Inventory> = 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,
&[],
)
}
}

View File

@ -15,6 +15,7 @@ pub mod shulker_box;
pub mod blast_furnace; pub mod blast_furnace;
pub mod brewing_stand; pub mod brewing_stand;
pub mod crafting_table; pub mod crafting_table;
pub mod enchanting_table;
pub mod furnace; pub mod furnace;
pub mod grindstone; pub mod grindstone;
pub mod smoker; pub mod smoker;

View File

@ -216,7 +216,7 @@ pub struct NormalUseArgs<'a> {
pub world: &'a Arc<World>, pub world: &'a Arc<World>,
pub block: &'a Block, pub block: &'a Block,
pub position: &'a BlockPos, pub position: &'a BlockPos,
pub player: &'a Player, pub player: &'a Arc<Player>,
pub hit: &'a BlockHitResult<'a>, pub hit: &'a BlockHitResult<'a>,
} }
@ -225,7 +225,7 @@ pub struct UseWithItemArgs<'a> {
pub world: &'a Arc<World>, pub world: &'a Arc<World>,
pub block: &'a Block, pub block: &'a Block,
pub position: &'a BlockPos, pub position: &'a BlockPos,
pub player: &'a Player, pub player: &'a Arc<Player>,
pub hit: &'a BlockHitResult<'a>, pub hit: &'a BlockHitResult<'a>,
pub item_stack: &'a Arc<Mutex<ItemStack>>, pub item_stack: &'a Arc<Mutex<ItemStack>>,
} }

View File

@ -160,6 +160,7 @@ use crate::block::blocks::chain::ChainBlock;
use crate::block::blocks::cobweb::CobwebBlock; use crate::block::blocks::cobweb::CobwebBlock;
use crate::block::blocks::crafting_table::CraftingTableBlock; use crate::block::blocks::crafting_table::CraftingTableBlock;
use crate::block::blocks::dragon_egg::DragonEggBlock; 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::end_rod::EndRodBlock;
use crate::block::blocks::ender_chest::EnderChestBlock; use crate::block::blocks::ender_chest::EnderChestBlock;
use crate::block::blocks::hopper::HopperBlock; use crate::block::blocks::hopper::HopperBlock;
@ -194,6 +195,7 @@ pub fn default_registry() -> Arc<BlockRegistry> {
manager.register(CopperChestBlock); manager.register(CopperChestBlock);
manager.register(EnderChestBlock); manager.register(EnderChestBlock);
manager.register(CraftingTableBlock); manager.register(CraftingTableBlock);
manager.register(EnchantingTableBlock);
manager.register(DirtPathBlock); manager.register(DirtPathBlock);
manager.register(DoorBlock); manager.register(DoorBlock);
manager.register(FarmlandBlock); manager.register(FarmlandBlock);
@ -491,7 +493,7 @@ impl BlockRegistry {
pub async fn on_use( pub async fn on_use(
&self, &self,
block: &Block, block: &Block,
player: &Player, player: &Arc<Player>,
position: &BlockPos, position: &BlockPos,
hit: &BlockHitResult<'_>, hit: &BlockHitResult<'_>,
server: &Server, server: &Server,
@ -530,7 +532,7 @@ impl BlockRegistry {
pub async fn use_with_item( pub async fn use_with_item(
&self, &self,
block: &Block, block: &Block,
player: &Player, player: &Arc<Player>,
position: &BlockPos, position: &BlockPos,
hit: &BlockHitResult<'_>, hit: &BlockHitResult<'_>,
item_stack: &Arc<Mutex<ItemStack>>, item_stack: &Arc<Mutex<ItemStack>>,
@ -557,7 +559,7 @@ impl BlockRegistry {
pub async fn use_with_item_fluid( pub async fn use_with_item_fluid(
&self, &self,
fluid: &Fluid, fluid: &Fluid,
player: &Player, player: &Arc<Player>,
position: BlockPos, position: BlockPos,
item: &Item, item: &Item,
server: &Server, server: &Server,

View File

@ -44,12 +44,12 @@ impl ServerPlayerData {
/// # Returns /// # Returns
/// ///
/// A Result indicating success or the error that occurred. /// 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<Player>) -> Result<(), PlayerDataError> {
player player
.player_screen_handler .player_screen_handler
.lock() .lock()
.await .await
.on_closed(player) .on_closed(player.as_ref())
.await; .await;
player.on_handled_screen_closed().await; player.on_handled_screen_closed().await;

View File

@ -194,7 +194,7 @@ impl Mob for CreeperEntity {
fn mob_interact<'a>( fn mob_interact<'a>(
&'a self, &'a self,
player: &'a Player, player: &'a Arc<Player>,
item_stack: &'a mut ItemStack, item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {

View File

@ -384,7 +384,7 @@ pub trait Mob: EntityBase + Send + Sync {
fn mob_interact<'a>( fn mob_interact<'a>(
&'a self, &'a self,
_player: &'a Player, _player: &'a Arc<Player>,
_item_stack: &'a mut ItemStack, _item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async { false }) Box::pin(async { false })
@ -526,7 +526,7 @@ impl<T: Mob + Send + 'static> EntityBase for T {
fn interact<'a>( fn interact<'a>(
&'a self, &'a self,
player: &'a Player, player: &'a Arc<Player>,
item_stack: &'a mut ItemStack, item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { self.mob_interact(player, item_stack).await }) Box::pin(async move { self.mob_interact(player, item_stack).await })

View File

@ -264,7 +264,7 @@ pub trait EntityBase: Send + Sync + NBTStorage + std::any::Any {
/// Returns true if the interaction was handled. /// Returns true if the interaction was handled.
fn interact<'a>( fn interact<'a>(
&'a self, &'a self,
_player: &'a Player, _player: &'a Arc<Player>,
_item_stack: &'a mut ItemStack, _item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async { false }) Box::pin(async { false })

View File

@ -113,7 +113,7 @@ impl Mob for ChickenEntity {
fn mob_interact<'a>( fn mob_interact<'a>(
&'a self, &'a self,
player: &'a Player, player: &'a Arc<Player>,
item_stack: &'a mut ItemStack, item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {

View File

@ -74,7 +74,7 @@ impl Mob for CowEntity {
fn mob_interact<'a>( fn mob_interact<'a>(
&'a self, &'a self,
player: &'a Player, player: &'a Arc<Player>,
item_stack: &'a mut ItemStack, item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {

View File

@ -79,7 +79,7 @@ impl Mob for PigEntity {
fn mob_interact<'a>( fn mob_interact<'a>(
&'a self, &'a self,
player: &'a Player, player: &'a Arc<Player>,
item_stack: &'a mut ItemStack, item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {

View File

@ -140,7 +140,7 @@ impl Mob for SheepEntity {
fn mob_interact<'a>( fn mob_interact<'a>(
&'a self, &'a self,
player: &'a Player, player: &'a Arc<Player>,
item_stack: &'a mut ItemStack, item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {

View File

@ -268,7 +268,7 @@ impl VillagerEntity {
.await; .await;
} }
pub async fn open_trading_screen(&self, player: &Player) { pub async fn open_trading_screen(&self, player: &Arc<Player>) {
let self_weak = self.self_arc.lock().await; let self_weak = self.self_arc.lock().await;
if let Some(self_arc) = self_weak.as_ref().and_then(std::sync::Weak::upgrade) { if let Some(self_arc) = self_weak.as_ref().and_then(std::sync::Weak::upgrade) {
player.open_handled_screen(&*self_arc, None).await; player.open_handled_screen(&*self_arc, None).await;
@ -435,9 +435,10 @@ impl Mob for VillagerEntity {
fn mob_interact<'a>( fn mob_interact<'a>(
&'a self, &'a self,
player: &'a crate::entity::player::Player, player: &'a Arc<Player>,
_item_stack: &'a mut pumpkin_data::item_stack::ItemStack, _item_stack: &'a mut pumpkin_data::item_stack::ItemStack,
) -> crate::entity::EntityBaseFuture<'a, bool> { ) -> crate::entity::EntityBaseFuture<'a, bool> {
let player = player.clone();
Box::pin(async move { Box::pin(async move {
if self.get_entity().age.load(Ordering::Relaxed) < 0 { if self.get_entity().age.load(Ordering::Relaxed) < 0 {
self.set_unhappy().await; self.set_unhappy().await;
@ -451,7 +452,7 @@ impl Mob for VillagerEntity {
} }
drop(offers); drop(offers);
self.open_trading_screen(player).await; self.open_trading_screen(&player).await;
true true
}) })

View File

@ -68,7 +68,9 @@ use pumpkin_protocol::java::client::play::{
CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent, Metadata, CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent, Metadata,
PlayerAction, PlayerInfoFlags, PreviousMessage, 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::{ use pumpkin_util::math::{
boundingbox::BoundingBox, experience, position::BlockPos, vector2::Vector2, vector3::Vector3, boundingbox::BoundingBox, experience, position::BlockPos, vector2::Vector2, vector3::Vector3,
}; };
@ -493,6 +495,7 @@ pub struct Player {
pub tab_list_order: AtomicI32, pub tab_list_order: AtomicI32,
pub tab_list_latency: AtomicI32, pub tab_list_latency: AtomicI32,
pub tab_list_listed: AtomicBool, pub tab_list_listed: AtomicBool,
pub enchantment_seed: AtomicI32,
} }
impl Player { impl Player {
@ -548,6 +551,7 @@ impl Player {
// TODO: Load this from previous instance // TODO: Load this from previous instance
hunger_manager: HungerManager::default(), hunger_manager: HungerManager::default(),
current_block_destroy_stage: AtomicI32::new(-1), current_block_destroy_stage: AtomicI32::new(-1),
enchantment_seed: AtomicI32::new(rand::random()),
open_container: AtomicCell::new(None), open_container: AtomicCell::new(None),
open_container_pos: AtomicCell::new(None), open_container_pos: AtomicCell::new(None),
tick_counter: AtomicI32::new(0), tick_counter: AtomicI32::new(0),
@ -3063,7 +3067,7 @@ impl Player {
.store(current_id % 100 + 1, Ordering::Relaxed); .store(current_id % 100 + 1, Ordering::Relaxed);
} }
pub async fn close_handled_screen(&self) { pub async fn close_handled_screen(self: &Arc<Self>) {
self.client self.client
.enqueue_packet(&CCloseContainer::new( .enqueue_packet(&CCloseContainer::new(
self.current_screen_handler self.current_screen_handler
@ -3078,27 +3082,31 @@ impl Player {
self.on_handled_screen_closed().await; self.on_handled_screen_closed().await;
} }
pub async fn on_handled_screen_closed(&self) { pub async fn on_handled_screen_closed(self: &Arc<Self>) {
// let window_type = { let current_screen_handler: Arc<Mutex<dyn ScreenHandler>> =
// let handler_lock = self.current_screen_handler.lock().await; self.current_screen_handler.lock().await.clone();
// let mut handler = handler_lock.lock().await;
// let wt = handler.window_type();
// handler.on_closed(self).await;
// wt
// };
// TODO let window_type = {
// if let Some(server) = self.living_entity.entity.world.load().server.upgrade() { let mut handler = current_screen_handler.lock().await;
// server let wt = handler.window_type();
// .plugin_manager handler.on_closed(self.as_ref()).await;
// .fire(InventoryCloseEvent::new(&self, window_type)) wt
// .await; };
// }
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<Mutex<dyn ScreenHandler>> = let player_screen_handler: Arc<Mutex<dyn ScreenHandler>> =
self.player_screen_handler.clone(); self.player_screen_handler.clone();
let current_screen_handler: Arc<Mutex<dyn ScreenHandler>> =
self.current_screen_handler.lock().await.clone();
if !Arc::ptr_eq(&player_screen_handler, &current_screen_handler) { if !Arc::ptr_eq(&player_screen_handler, &current_screen_handler) {
player_screen_handler player_screen_handler
@ -3138,7 +3146,7 @@ impl Player {
} }
pub async fn open_handled_screen( pub async fn open_handled_screen(
&self, self: &Arc<Self>,
screen_handler_factory: &dyn ScreenHandlerFactory, screen_handler_factory: &dyn ScreenHandlerFactory,
block_pos: Option<BlockPos>, block_pos: Option<BlockPos>,
) -> Option<u8> { ) -> Option<u8> {
@ -3160,7 +3168,7 @@ impl Player {
.create_screen_handler( .create_screen_handler(
self.screen_handler_sync_id.load(Ordering::Relaxed), self.screen_handler_sync_id.load(Ordering::Relaxed),
&self.inventory, &self.inventory,
self, self.as_ref(),
) )
.await .await
{ {
@ -3188,7 +3196,7 @@ impl Player {
} }
pub async fn open_handled_screen_direct( pub async fn open_handled_screen_direct(
&self, self: &Arc<Self>,
screen_handler: Arc<Mutex<dyn ScreenHandler>>, screen_handler: Arc<Mutex<dyn ScreenHandler>>,
title: TextComponent, 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<Self>, 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<Self>, server: &Server, node: &str) -> bool { pub async fn has_permission(self: &Arc<Self>, server: &Server, node: &str) -> bool {
let perm_manager = server.permission_manager.read().await; let perm_manager = server.permission_manager.read().await;
let result = perm_manager let result = perm_manager
@ -3670,6 +3691,7 @@ impl NBTStorage for Player {
} else { } else {
nbt.put_bool(false); nbt.put_bool(false);
} }
nbt.put_int(self.enchantment_seed.load(Ordering::Relaxed));
}) })
} }
@ -3721,6 +3743,8 @@ impl NBTStorage for Player {
force, 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<PlayerInventory> { fn get_inventory(&self) -> Arc<PlayerInventory> {
self.inventory.clone() self.inventory.clone()
} }

View File

@ -185,7 +185,7 @@ impl EntityBase for BoatEntity {
fn interact<'a>( fn interact<'a>(
&'a self, &'a self,
player: &'a Player, player: &'a Arc<Player>,
_item_stack: &'a mut ItemStack, _item_stack: &'a mut ItemStack,
) -> EntityBaseFuture<'a, bool> { ) -> EntityBaseFuture<'a, bool> {
Box::pin(async move { Box::pin(async move {

View File

@ -8,9 +8,9 @@ use pumpkin_data::packet::CURRENT_MC_VERSION;
use pumpkin_protocol::java::server::play::{ use pumpkin_protocol::java::server::play::{
SAttack, SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand, SAttack, SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport, SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
SCookieResponse as SPCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle, SContainerButtonClick, SCookieResponse as SPCookieResponse, SCustomPayload, SInteract,
SPaddleBoat, SPickItemFromBlock, SPlaceRecipe, SPlayPingRequest, SPlayerAbilities, SKeepAlive, SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlaceRecipe, SPlayPingRequest,
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition, SPlayerAbilities, SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings, SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings,
SRecipeBookSeenRecipe, SRenameItem, SSelectTrade, SSetCommandBlock, SSetCreativeSlot, SRecipeBookSeenRecipe, SRenameItem, SSelectTrade, SSetCommandBlock, SSetCreativeSlot,
SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn, SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn,
@ -817,6 +817,11 @@ impl JavaClient {
.on_slot_click(SClickSlot::read(payload, &version)?, server) .on_slot_click(SClickSlot::read(payload, &version)?, server)
.await; .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) => { id if id == SSetHeldItem::to_id(version) => {
self.handle_set_held_item(player, SSetHeldItem::read(payload, &version)?) self.handle_set_held_item(player, SSetHeldItem::read(payload, &version)?)
.await; .await;

View File

@ -2166,7 +2166,7 @@ impl JavaClient {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
async fn call_use_item_on( async fn call_use_item_on(
&self, &self,
player: &Player, player: &Arc<Player>,
position: &BlockPos, position: &BlockPos,
cursor_pos: &Vector3<f32>, cursor_pos: &Vector3<f32>,
face: &BlockDirection, face: &BlockDirection,
@ -2509,7 +2509,7 @@ impl JavaClient {
pub async fn handle_close_container( pub async fn handle_close_container(
&self, &self,
player: &Player, player: &Arc<Player>,
_server: &Server, _server: &Server,
_packet: SCloseContainer, _packet: SCloseContainer,
) { ) {