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

View File

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

View File

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

View File

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

View File

@ -62,6 +62,13 @@ pub struct ItemComponents {
pub damage_resistant: Option<DamageResistantComponent>,
#[serde(rename = "minecraft:weapon")]
pub weapon: Option<WeaponComponent>,
#[serde(rename = "minecraft:enchantable")]
pub enchantable: Option<EnchantableComponent>,
}
#[derive(Deserialize)]
pub struct EnchantableComponent {
pub value: i32,
}
impl ToTokens for ItemComponents {
@ -573,6 +580,11 @@ impl ToTokens for ItemComponents {
shearing_sound: #shearing_sound
}), });
}
if let Some(enchantable) = &self.enchantable {
let value = LitInt::new(&enchantable.value.to_string(), Span::call_site());
tokens.extend(quote! { (Enchantable, &EnchantableImpl { value: #value }), });
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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 double;
pub mod drag_handler;
pub mod enchanting;
pub mod entity_equipment;
mod error;
pub mod furnace_like;

View File

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

View File

@ -142,6 +142,12 @@ pub trait InventoryPlayer: Send + Sync {
/// Adds or removes experience levels.
fn add_experience_levels(&self, levels: i32) -> PlayerFuture<'_, ()>;
/// Gets the player's enchantment seed.
fn enchantment_seed(&self) -> i32;
/// Sets the player's enchantment seed.
fn set_enchantment_seed(&self, seed: i32) -> PlayerFuture<'_, ()>;
/// Sends a full container content packet.
fn enqueue_inventory_packet<'a>(
&'a self,
@ -673,6 +679,15 @@ pub trait ScreenHandler: Send + Sync {
slot_index: i32,
) -> ItemStackFuture<'a>;
/// Handles a button click event (e.g., enchantment selection, beacon effects).
fn on_button_click<'a>(
&'a mut self,
_player: &'a dyn InventoryPlayer,
_button_id: i32,
) -> ScreenHandlerFuture<'a, bool> {
Box::pin(async { false })
}
/// Inserts an item into a range of slots.
///
/// First tries to stack with existing items, then fills empty slots.

View File

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

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 command_suggestion;
mod confirm_teleport;
mod container_button_click;
mod cookie_response;
mod custom_payload;
mod interact;
@ -53,6 +54,7 @@ pub use client_tick_end::*;
pub use close_container::*;
pub use command_suggestion::*;
pub use confirm_teleport::*;
pub use container_button_click::*;
pub use cookie_response::*;
pub use custom_payload::*;
pub use interact::*;

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 brewing_stand;
pub mod crafting_table;
pub mod enchanting_table;
pub mod furnace;
pub mod grindstone;
pub mod smoker;

View File

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

View File

@ -44,12 +44,12 @@ impl ServerPlayerData {
/// # Returns
///
/// A Result indicating success or the error that occurred.
pub async fn handle_player_leave(&self, player: &Player) -> Result<(), PlayerDataError> {
pub async fn handle_player_leave(&self, player: &Arc<Player>) -> Result<(), PlayerDataError> {
player
.player_screen_handler
.lock()
.await
.on_closed(player)
.on_closed(player.as_ref())
.await;
player.on_handled_screen_closed().await;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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