chore: expand loot table

This commit is contained in:
Alexander Medvedev 2026-06-27 10:35:46 +02:00
parent 322b7af6ae
commit 03a90a40b1
13 changed files with 1015 additions and 149 deletions

View File

@ -548,12 +548,15 @@ impl TreeNodePosition {
) { ) {
inner_left = next_inner_left; inner_left = next_inner_left;
inner_right = next_inner_right; inner_right = next_inner_right;
outer_left = Self::previous_or_thread(nodes, outer_left).expect("Tree invariant broken"); outer_left =
Self::previous_or_thread(nodes, outer_left).expect("Tree invariant broken");
outer_right = Self::next_or_thread(nodes, outer_right).expect("Tree invariant broken"); outer_right = Self::next_or_thread(nodes, outer_right).expect("Tree invariant broken");
nodes[outer_right].ancestor = idx; nodes[outer_right].ancestor = idx;
let shift = (nodes[inner_left].y + shift_inner_left) - (nodes[inner_right].y + shift_inner_right) + 1.0; let shift = (nodes[inner_left].y + shift_inner_left)
- (nodes[inner_right].y + shift_inner_right)
+ 1.0;
if shift > 0.0 { if shift > 0.0 {
let ancestor_idx = Self::get_ancestor(nodes, inner_left, idx, default_ancestor); let ancestor_idx = Self::get_ancestor(nodes, inner_left, idx, default_ancestor);
Self::move_subtree(nodes, ancestor_idx, idx, shift); Self::move_subtree(nodes, ancestor_idx, idx, shift);
@ -659,7 +662,7 @@ pub struct AdvancementStruct {
pub sends_telemetry: bool, pub sends_telemetry: bool,
pub requirements: Vec<Vec<String>>, pub requirements: Vec<Vec<String>>,
#[serde(deserialize_with = "deserialize_first_key")] #[serde(deserialize_with = "deserialize_first_key")]
pub criteria : Vec<String>, pub criteria: Vec<String>,
} }
fn deserialize_first_key<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error> fn deserialize_first_key<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
@ -846,10 +849,7 @@ pub(crate) fn build() -> TokenStream {
let advancement_tree = quote! { let advancement_tree = quote! {
pub static ADVANCEMENT_TREE : LazyLock<AdvancementTree> = #tree; pub static ADVANCEMENT_TREE : LazyLock<AdvancementTree> = #tree;
}; };
let advancements_holder = tree let advancements_holder = tree.nodes_vector.into_iter().map(|node| node.value);
.nodes_vector
.into_iter()
.map(|node| node.value);
for AdvancementHolder(identifier, advancement) in advancements_holder { for AdvancementHolder(identifier, advancement) in advancements_holder {
let raw_name = identifier.path(); let raw_name = identifier.path();
let format_name = format_ident!("{}", raw_name.to_shouty_snake_case()); let format_name = format_ident!("{}", raw_name.to_shouty_snake_case());

View File

@ -1250,7 +1250,7 @@ pub fn build() -> TokenStream {
#state_from_state_id #state_from_state_id
]; ];
} }
impl Block { impl Block {
#(#constants_list)* #(#constants_list)*

View File

@ -29,7 +29,9 @@ pub fn build() -> TokenStream {
(quote! { Int }, quote! { i64 }, quote! { #i }) (quote! { Int }, quote! { i64 }, quote! { #i })
} }
Value::Object(obj) => { Value::Object(obj) => {
let default_val = obj.get("default").expect("Object game rule missing default value"); let default_val = obj
.get("default")
.expect("Object game rule missing default value");
match default_val { match default_val {
Value::Number(n) if n.is_i64() => { Value::Number(n) if n.is_i64() => {
let i = n.as_i64().unwrap(); let i = n.as_i64().unwrap();

View File

@ -281,9 +281,8 @@ impl ToTokens for LootPoolEntryTypesStruct {
Self::LootTable(entry) => { Self::LootTable(entry) => {
tokens.extend(quote! { LootPoolEntryTypes::LootTable(#entry) }); tokens.extend(quote! { LootPoolEntryTypes::LootTable(#entry) });
} }
Self::Dynamic(_entry) => { Self::Dynamic(entry) => {
// TODO tokens.extend(quote! { LootPoolEntryTypes::Dynamic(#entry) });
tokens.extend(quote! { LootPoolEntryTypes::Dynamic });
} }
Self::Tag(entry) => { Self::Tag(entry) => {
tokens.extend(quote! { LootPoolEntryTypes::Tag(#entry) }); tokens.extend(quote! { LootPoolEntryTypes::Tag(#entry) });
@ -662,13 +661,17 @@ pub enum LootFunctionTypesStruct {
}, },
/// Increases count based on the level of a relevant enchantment. /// Increases count based on the level of a relevant enchantment.
#[serde(rename = "minecraft:enchanted_count_increase")] #[serde(rename = "minecraft:enchanted_count_increase")]
EnchantedCountIncrease, EnchantedCountIncrease {
enchantment: String,
count: LootFunctionNumberProviderStruct,
limit: Option<f32>,
},
/// Smelts the item as if processed in a furnace. /// Smelts the item as if processed in a furnace.
#[serde(rename = "minecraft:furnace_smelt")] #[serde(rename = "minecraft:furnace_smelt")]
FurnaceSmelt, FurnaceSmelt,
/// Sets the potion type on the item. /// Sets the potion type on the item.
#[serde(rename = "minecraft:set_potion")] #[serde(rename = "minecraft:set_potion")]
SetPotion, SetPotion { id: String },
/// Sets the amplifier on an ominous bottle item. /// Sets the amplifier on an ominous bottle item.
#[serde(rename = "minecraft:set_ominous_bottle_amplifier")] #[serde(rename = "minecraft:set_ominous_bottle_amplifier")]
SetOminousBottleAmplifier, SetOminousBottleAmplifier,
@ -724,11 +727,21 @@ impl ToTokens for LootFunctionTypesStruct {
Self::FurnaceSmelt => { Self::FurnaceSmelt => {
quote! { LootFunctionTypes::FurnaceSmelt } quote! { LootFunctionTypes::FurnaceSmelt }
} }
Self::SetPotion => { Self::SetPotion { id } => {
quote! { LootFunctionTypes::SetPotion } let i = LitStr::new(id, Span::call_site());
quote! { LootFunctionTypes::SetPotion { id: #i } }
} }
Self::EnchantedCountIncrease => { Self::EnchantedCountIncrease {
quote! { LootFunctionTypes::EnchantedCountIncrease } enchantment,
count,
limit,
} => {
let e = LitStr::new(enchantment, Span::call_site());
let c = count.to_token_stream();
let l = limit
.map(|val| quote! { Some(#val) })
.unwrap_or(quote! { None });
quote! { LootFunctionTypes::EnchantedCountIncrease { enchantment: #e, count: #c, limit: #l } }
} }
Self::LimitCount { limit } => { Self::LimitCount { limit } => {
let min = if let Some(min) = limit.min { let min = if let Some(min) = limit.min {

View File

@ -3,11 +3,11 @@
use crate::attributes::Attributes; 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, BlockEntityData, BlocksAttacks, BundleContents, ChargedProjectiles, AttributeModifiers, BlockEntityData, BlockState, BlocksAttacks, BundleContents,
Consumable, Container, CustomData, CustomName, Damage, DamageResistant, DeathProtection, ChargedProjectiles, Consumable, Container, CustomData, CustomName, Damage, DamageResistant,
Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, ItemModel, ItemName, DeathProtection, Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food,
JukeboxPlayable, MapId, MaxDamage, MaxStackSize, PotionContents, StoredEnchantments, Tool, ItemModel, ItemName, JukeboxPlayable, MapId, MaxDamage, MaxStackSize, OminousBottleAmplifier,
Unbreakable, UseCooldown, Weapon, PotionContents, 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;
@ -29,6 +29,7 @@ use std::error::Error;
use std::fmt::Debug; use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
use std::{ use std::{
collections::HashMap,
collections::hash_map::DefaultHasher, collections::hash_map::DefaultHasher,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
}; };
@ -81,6 +82,10 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option<Box<dyn DataCompone
DataComponent::WritableBookContent => { DataComponent::WritableBookContent => {
Some(WritableBookContentImpl::read_data(data)?.to_dyn()) Some(WritableBookContentImpl::read_data(data)?.to_dyn())
} }
DataComponent::OminousBottleAmplifier => {
Some(OminousBottleAmplifierImpl::read_data(data)?.to_dyn())
}
DataComponent::BlockState => Some(BlockStateImpl::read_data(data)?.to_dyn()),
_ => None, _ => None,
} }
} }
@ -2062,7 +2067,23 @@ pub struct InstrumentImpl;
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ProvidesTrimMaterialImpl; pub struct ProvidesTrimMaterialImpl;
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct OminousBottleAmplifierImpl; pub struct OminousBottleAmplifierImpl {
pub amplifier: i32,
}
impl OminousBottleAmplifierImpl {
fn read_data(data: &NbtTag) -> Option<Self> {
data.extract_int().map(|amplifier| Self { amplifier })
}
}
impl DataComponentImpl for OminousBottleAmplifierImpl {
fn write_data(&self) -> NbtTag {
NbtTag::Int(self.amplifier)
}
fn get_hash(&self) -> i32 {
get_i32_hash(self.amplifier) as i32
}
default_impl!(OminousBottleAmplifier);
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct JukeboxPlayableImpl { pub struct JukeboxPlayableImpl {
pub song: &'static str, pub song: &'static str,
@ -2311,8 +2332,58 @@ impl ContainerImpl {
impl DataComponentImpl for ContainerImpl { impl DataComponentImpl for ContainerImpl {
default_impl!(Container); default_impl!(Container);
} }
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug)]
pub struct BlockStateImpl; pub struct BlockStateImpl {
pub properties: HashMap<String, String>,
}
impl PartialEq for BlockStateImpl {
fn eq(&self, other: &Self) -> bool {
self.properties == other.properties
}
}
impl Eq for BlockStateImpl {}
impl std::hash::Hash for BlockStateImpl {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let mut keys: Vec<&String> = self.properties.keys().collect();
keys.sort();
for key in keys {
key.hash(state);
self.properties.get(key).hash(state);
}
}
}
impl BlockStateImpl {
fn read_data(data: &NbtTag) -> Option<Self> {
let compound = data.extract_compound()?;
let mut properties = HashMap::new();
for (key, val) in compound.child_tags.iter() {
if let Some(s) = val.extract_string() {
properties.insert(key.to_string(), s.to_string());
}
}
Some(Self { properties })
}
}
impl DataComponentImpl for BlockStateImpl {
fn write_data(&self) -> NbtTag {
let mut compound = NbtCompound::new();
for (k, v) in &self.properties {
compound.put_string(k, v.clone());
}
NbtTag::Compound(compound)
}
fn get_hash(&self) -> i32 {
let mut digest = Digest::new(Crc32Iscsi);
let mut keys: Vec<&String> = self.properties.keys().collect();
keys.sort();
for key in keys {
digest.update(key.as_bytes());
digest.update(self.properties.get(key).unwrap().as_bytes());
}
digest.finalize() as i32
}
default_impl!(BlockState);
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BeesImpl; pub struct BeesImpl;
#[derive(Clone, Debug, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]

View File

@ -532782,7 +532782,9 @@ impl Block {
content: LootPoolEntryTypes::Alternatives(AlternativeEntry { content: LootPoolEntryTypes::Alternatives(AlternativeEntry {
children: &[ children: &[
LootPoolEntry { LootPoolEntry {
content: LootPoolEntryTypes::Dynamic, content: LootPoolEntryTypes::Dynamic(DynamicEntry {
name: "minecraft:sherds",
}),
weight: 1i32, weight: 1i32,
quality: 0i32, quality: 0i32,
conditions: Some(&[LootCondition::BlockStateProperty { conditions: Some(&[LootCondition::BlockStateProperty {

File diff suppressed because it is too large Load Diff

View File

@ -67,12 +67,17 @@ pub struct LootTableEntry {
pub value: &'static str, pub value: &'static str,
} }
#[derive(Clone, Debug)]
pub struct DynamicEntry {
pub name: &'static str,
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum LootPoolEntryTypes { pub enum LootPoolEntryTypes {
Empty, Empty,
Item(ItemEntry), Item(ItemEntry),
LootTable(LootTableEntry), LootTable(LootTableEntry),
Dynamic, Dynamic(DynamicEntry),
Tag(TagEntry), Tag(TagEntry),
Alternatives(AlternativeEntry), Alternatives(AlternativeEntry),
Sequence(SequenceEntry), Sequence(SequenceEntry),
@ -173,11 +178,15 @@ pub enum LootFunctionTypes {
add: bool, add: bool,
}, },
/// Increases the count of items based on the item's enchantments. /// Increases the count of items based on the item's enchantments.
EnchantedCountIncrease, EnchantedCountIncrease {
enchantment: &'static str,
count: LootFunctionNumberProvider,
limit: Option<f32>,
},
/// Smelts the item as if it passed through a furnace. /// Smelts the item as if it passed through a furnace.
FurnaceSmelt, FurnaceSmelt,
/// Sets the potion type for potion items. /// Sets the potion type for potion items.
SetPotion, SetPotion { id: &'static str },
/// Sets the amplifier for ominous bottles. /// Sets the amplifier for ominous bottles.
SetOminousBottleAmplifier, SetOminousBottleAmplifier,
/// Limits the count of items to a minimum and/or maximum value. /// Limits the count of items to a minimum and/or maximum value.

View File

@ -1317,6 +1317,27 @@ impl LivingEntity {
// Plays the death sound // Plays the death sound
world.send_entity_status(&self.entity, EntityStatus::Death); world.send_entity_status(&self.entity, EntityStatus::Death);
let tool = if let Some(cause_ent) = cause {
if let Some(player) = cause_ent
.cast_any()
.downcast_ref::<crate::entity::player::Player>()
{
let hand_stack = player
.inventory
.get_stack_in_hand(pumpkin_util::Hand::Right)
.await;
let stack_guard = hand_stack.lock().await;
(stack_guard.item_count > 0).then(|| stack_guard.clone())
} else {
None
}
} else {
None
};
let is_raining = world.is_raining().await;
let is_thundering = world.is_thundering().await;
let params = LootContextParameters { let params = LootContextParameters {
killed_by_player: cause.map(|c| c.get_entity().entity_type == &EntityType::PLAYER), killed_by_player: cause.map(|c| c.get_entity().entity_type == &EntityType::PLAYER),
this_entity: Some(self.entity.entity_type), this_entity: Some(self.entity.entity_type),
@ -1325,11 +1346,14 @@ impl LivingEntity {
position: Some(self.entity.pos.load()), position: Some(self.entity.pos.load()),
world_time: world.level_info.load().day_time as u64, world_time: world.level_info.load().day_time as u64,
damage_type: Some(damage_type), damage_type: Some(damage_type),
tool,
is_raining: Some(is_raining),
is_thundering: Some(is_thundering),
..Default::default() ..Default::default()
}; };
// Drop loot // Drop loot
self.drop_loot(params).await; self.drop_loot(params.clone()).await;
// Award experience // Award experience
if params.killed_by_player.unwrap_or(false) if params.killed_by_player.unwrap_or(false)

View File

@ -89,7 +89,14 @@ impl VehicleEntity {
if entity_drops && let Some(loot_table) = &self.entity.entity_type.loot_table { if entity_drops && let Some(loot_table) = &self.entity.entity_type.loot_table {
let pos = self.entity.block_pos.load(); let pos = self.entity.block_pos.load();
let params = LootContextParameters::default(); let is_raining = world.is_raining().await;
let is_thundering = world.is_thundering().await;
let params = LootContextParameters {
is_raining: Some(is_raining),
is_thundering: Some(is_thundering),
world_time: world.level_info.load().day_time as u64,
..Default::default()
};
for stack in loot_table.get_loot(params) { for stack in loot_table.get_loot(params) {
world.drop_stack(&pos, stack).await; world.drop_stack(&pos, stack).await;
} }

View File

@ -272,6 +272,8 @@ impl Explosion {
let pumpkin_block = world.block_registry.get_pumpkin_block(block.id); let pumpkin_block = world.block_registry.get_pumpkin_block(block.id);
if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) { if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) {
let is_raining = world.is_raining().await;
let is_thundering = world.is_thundering().await;
let params = LootContextParameters { let params = LootContextParameters {
block_state: Some(state), block_state: Some(state),
explosion_radius: Some(self.power), explosion_radius: Some(self.power),
@ -281,6 +283,8 @@ impl Explosion {
pos.0.z as f64, pos.0.z as f64,
)), )),
world_time: world.level_info.load().day_time as u64, world_time: world.level_info.load().day_time as u64,
is_raining: Some(is_raining),
is_thundering: Some(is_thundering),
..Default::default() ..Default::default()
}; };
drop_loot(world, block, pos, false, params).await; drop_loot(world, block, pos, false, params).await;

View File

@ -12,7 +12,7 @@ use pumpkin_util::{
}; };
use rand::RngExt; use rand::RngExt;
#[derive(Default, Copy, Clone)] #[derive(Default, Clone)]
pub struct LootContextParameters { pub struct LootContextParameters {
pub explosion_radius: Option<f32>, pub explosion_radius: Option<f32>,
pub block_state: Option<&'static BlockState>, pub block_state: Option<&'static BlockState>,
@ -24,6 +24,9 @@ pub struct LootContextParameters {
pub position: Option<pumpkin_util::math::vector3::Vector3<f64>>, pub position: Option<pumpkin_util::math::vector3::Vector3<f64>>,
pub world_time: u64, pub world_time: u64,
pub damage_type: Option<DamageType>, pub damage_type: Option<DamageType>,
pub tool: Option<ItemStack>,
pub is_raining: Option<bool>,
pub is_thundering: Option<bool>,
} }
pub trait LootTableExt { pub trait LootTableExt {
@ -100,12 +103,15 @@ trait LootFunctionExt {
fn apply_bonus( fn apply_bonus(
stacks: &mut [ItemStack], stacks: &mut [ItemStack],
enchantment_name: &str,
formula: &str, formula: &str,
parameters: Option<&LootFunctionBonusParameter>, parameters: Option<&LootFunctionBonusParameter>,
params: &LootContextParameters,
) { ) {
// We currently don't have tool/enchantment data in LootContextParameters. let enchantment_level = params.tool.as_ref().map_or(0, |tool| {
// Assuming enchantment level is 0 for now. pumpkin_data::Enchantment::from_name(enchantment_name)
let enchantment_level = 0; .map_or(0, |enchantment| tool.get_enchantment_level(enchantment))
});
if enchantment_level > 0 { if enchantment_level > 0 {
for stack in stacks { for stack in stacks {
match formula { match formula {
@ -145,6 +151,7 @@ fn apply_bonus(
} }
impl LootFunctionExt for LootFunction { impl LootFunctionExt for LootFunction {
#[allow(clippy::too_many_lines)]
fn apply(&self, stacks: &mut Vec<ItemStack>, params: &LootContextParameters) { fn apply(&self, stacks: &mut Vec<ItemStack>, params: &LootContextParameters) {
if let Some(conditions) = self.conditions if let Some(conditions) = self.conditions
&& !conditions.iter().all(|cond| cond.is_fulfilled(params)) && !conditions.iter().all(|cond| cond.is_fulfilled(params))
@ -196,25 +203,128 @@ impl LootFunctionExt for LootFunction {
} }
} }
LootFunctionTypes::ApplyBonus { LootFunctionTypes::ApplyBonus {
enchantment: _, enchantment,
formula, formula,
parameters, parameters,
} => { } => {
apply_bonus(stacks, formula, parameters.as_ref()); apply_bonus(stacks, enchantment, formula, parameters.as_ref(), params);
} }
LootFunctionTypes::CopyComponents { LootFunctionTypes::EnchantedCountIncrease {
source: _, enchantment,
include: _, count,
limit,
} => {
let level = params.tool.as_ref().map_or(0.0, |tool| {
pumpkin_data::Enchantment::from_name(enchantment)
.map_or(0.0, |enc| tool.get_enchantment_level(enc) as f32)
});
let mut additional = (count.generate() * level).round() as u32;
if let Some(lim) = limit {
let lim_u32 = lim.round() as u32;
if additional > lim_u32 {
additional = lim_u32;
}
}
for stack in stacks {
stack.item_count = stack.item_count.saturating_add(additional as u8);
}
} }
| LootFunctionTypes::CopyState { LootFunctionTypes::CopyComponents { source, include } => {
tracing::warn!(
"CopyComponents not supported from source: {} for {:?}",
source,
include
);
}
LootFunctionTypes::CopyState {
block: _, block: _,
properties: _, properties,
} => {
if let Some(state) = params.block_state
&& let Some(props_data) =
Block::properties(Block::from_state_id(state.id), state.id)
{
let actual_props = props_data.to_props();
let mut properties_to_copy = std::collections::HashMap::new();
for &prop_name in *properties {
if let Some((_, value)) = actual_props.iter().find(|(k, _)| k == &prop_name)
{
properties_to_copy.insert(prop_name.to_string(), value.to_string());
}
}
if !properties_to_copy.is_empty() {
for stack in stacks.iter_mut() {
if let Some(block_state_comp) = stack.get_data_component_mut::<pumpkin_data::data_component_impl::BlockStateImpl>() {
for (k, v) in &properties_to_copy {
block_state_comp.properties.insert(k.clone(), v.clone());
}
} else {
stack.patch.push((
pumpkin_data::data_component::DataComponent::BlockState,
Some(Box::new(pumpkin_data::data_component_impl::BlockStateImpl {
properties: properties_to_copy.clone(),
})),
));
}
}
}
}
} }
| LootFunctionTypes::EnchantedCountIncrease LootFunctionTypes::SetOminousBottleAmplifier => {
| LootFunctionTypes::SetOminousBottleAmplifier let amplifier = rand::random_range(0..5); // Random 0 to 4
| LootFunctionTypes::SetPotion for stack in stacks.iter_mut() {
| LootFunctionTypes::FurnaceSmelt => { if let Some(amplifier_comp) = stack.get_data_component_mut::<pumpkin_data::data_component_impl::OminousBottleAmplifierImpl>() {
// TODO: shouldnt crash here but needs to be implemented someday amplifier_comp.amplifier = amplifier;
} else {
stack.patch.push((
pumpkin_data::data_component::DataComponent::OminousBottleAmplifier,
Some(Box::new(pumpkin_data::data_component_impl::OminousBottleAmplifierImpl {
amplifier,
})),
));
}
}
}
LootFunctionTypes::SetPotion { id } => {
let name = id.strip_prefix("minecraft:").unwrap_or(id);
if let Some(potion) = pumpkin_data::potion::Potion::from_name(name) {
let potion_id = Some(potion.id as i32);
for stack in stacks.iter_mut() {
if let Some(potion_contents) = stack.get_data_component_mut::<pumpkin_data::data_component_impl::PotionContentsImpl>() {
potion_contents.potion_id = potion_id;
} else {
stack.patch.push((
pumpkin_data::data_component::DataComponent::PotionContents,
Some(Box::new(pumpkin_data::data_component_impl::PotionContentsImpl {
potion_id,
custom_color: None,
custom_effects: Vec::new(),
custom_name: None,
})),
));
}
}
}
}
LootFunctionTypes::FurnaceSmelt => {
for stack in stacks.iter_mut() {
for recipe_type in pumpkin_data::recipes::RECIPES_COOKING {
if let pumpkin_data::recipes::CookingRecipeType::Smelting(recipe) =
recipe_type
&& recipe.ingredient.match_item(stack.item)
{
let result_key = recipe
.result
.id
.strip_prefix("minecraft:")
.unwrap_or(recipe.result.id);
if let Some(smelted_item) = Item::from_registry_key(result_key) {
stack.item = smelted_item;
}
break;
}
}
}
} }
} }
} }
@ -247,7 +357,7 @@ trait LootPoolEntryTypesExt {
impl LootPoolEntryTypesExt for LootPoolEntryTypes { impl LootPoolEntryTypesExt for LootPoolEntryTypes {
fn get_stacks(&self, params: &LootContextParameters) -> Vec<ItemStack> { fn get_stacks(&self, params: &LootContextParameters) -> Vec<ItemStack> {
match self { match self {
Self::Empty | Self::Dynamic => Vec::new(), Self::Empty | Self::Dynamic(_) => Vec::new(),
Self::LootTable(entry) => { Self::LootTable(entry) => {
let key = entry let key = entry
.value .value
@ -395,7 +505,7 @@ fn check_damage_source_properties(
} }
impl LootConditionExt for LootCondition { impl LootConditionExt for LootCondition {
// TODO: This is trash. Make this right #[allow(clippy::too_many_lines)]
fn is_fulfilled(&self, params: &LootContextParameters) -> bool { fn is_fulfilled(&self, params: &LootContextParameters) -> bool {
match self { match self {
Self::SurvivesExplosion => { Self::SurvivesExplosion => {
@ -437,32 +547,27 @@ impl LootConditionExt for LootCondition {
Self::AnyOf(terms) => terms.iter().any(|cond| cond.is_fulfilled(params)), Self::AnyOf(terms) => terms.iter().any(|cond| cond.is_fulfilled(params)),
Self::AllOf(terms) => terms.iter().all(|cond| cond.is_fulfilled(params)), Self::AllOf(terms) => terms.iter().all(|cond| cond.is_fulfilled(params)),
Self::RandomChanceWithEnchantedBonus { Self::RandomChanceWithEnchantedBonus {
enchantment: _, enchantment,
chances, chances,
} => chances.as_ref().is_some_and(|chances| { } => chances.as_ref().is_some_and(|chances| {
// Missing tool context in params, assume enchantment level 0 let level = params.tool.as_ref().map_or(0, |tool| {
let level = 0; pumpkin_data::Enchantment::from_name(enchantment)
.map_or(0, |enc| tool.get_enchantment_level(enc) as usize)
});
let chance = chances.get(level).unwrap_or(chances.last().unwrap_or(&0.0)); let chance = chances.get(level).unwrap_or(chances.last().unwrap_or(&0.0));
rand::rng().random::<f32>() < *chance rand::rng().random::<f32>() < *chance
}), }),
Self::TableBonus { Self::TableBonus {
enchantment: _, enchantment,
chances, chances,
} => { } => {
// Missing tool context in params, assume enchantment level 0 let level = params.tool.as_ref().map_or(0, |tool| {
let level = 0; pumpkin_data::Enchantment::from_name(enchantment)
.map_or(0, |enc| tool.get_enchantment_level(enc) as usize)
});
let chance = chances.get(level).unwrap_or(chances.last().unwrap_or(&0.0)); let chance = chances.get(level).unwrap_or(chances.last().unwrap_or(&0.0));
rand::rng().random::<f32>() < *chance rand::rng().random::<f32>() < *chance
} }
Self::EntityScores { .. }
| Self::WeatherCheck { .. }
| Self::MatchTool { .. }
| Self::Reference { .. }
| Self::EnchantmentActiveCheck { .. }
| Self::LocationCheck { .. } => {
// TODO: Implement these
false
}
Self::TimeCheck { range, period } => { Self::TimeCheck { range, period } => {
let mut time = params.world_time; let mut time = params.world_time;
if let Some(period) = period { if let Some(period) = period {
@ -484,6 +589,51 @@ impl LootConditionExt for LootCondition {
} => { } => {
check_damage_source_properties(params, *expected_source_type, *expected_direct_type) check_damage_source_properties(params, *expected_source_type, *expected_direct_type)
} }
Self::WeatherCheck {
raining,
thundering,
} => {
let r_match = raining.is_none_or(|r| params.is_raining.unwrap_or(false) == r);
let t_match = thundering.is_none_or(|t| params.is_thundering.unwrap_or(false) == t);
r_match && t_match
}
Self::MatchTool { items } => params.tool.as_ref().is_some_and(|tool| {
items.as_ref().map_or_else(
|| {
pumpkin_data::Enchantment::from_name("minecraft:silk_touch")
.is_some_and(|silk_touch| tool.get_enchantment_level(silk_touch) > 0)
},
|items| {
items.iter().any(|&item_name| {
let expected =
item_name.strip_prefix("minecraft:").unwrap_or(item_name);
let actual = tool
.item
.registry_key
.strip_prefix("minecraft:")
.unwrap_or(tool.item.registry_key);
expected == actual
})
},
)
}),
Self::LocationCheck { expected_biome, .. } => expected_biome.is_none(),
Self::EntityScores { entity } => {
tracing::warn!("EntityScores check not supported for entity: {}", entity);
false
}
Self::Reference { name } => {
tracing::warn!("Loot condition reference not supported: {}", name);
false
}
Self::EnchantmentActiveCheck { active } => {
params.tool.as_ref().map_or(!*active, |tool| {
let has_enchantments = tool
.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>()
.is_some_and(|e| !e.enchantment.is_empty());
has_enchantments == *active
})
}
} }
} }
} }

View File

@ -4226,6 +4226,7 @@ impl World {
} }
// Return new state // Return new state
#[allow(clippy::too_many_lines)]
pub async fn break_block( pub async fn break_block(
self: &Arc<Self>, self: &Arc<Self>,
position: &BlockPos, position: &BlockPos,
@ -4295,7 +4296,7 @@ impl World {
false, false,
); );
let chunk_pos = position.chunk_position(); let chunk_pos = position.chunk_position();
match cause { match &cause {
Some(player) => { Some(player) => {
self.broadcast_to_chunk_except( self.broadcast_to_chunk_except(
chunk_pos, chunk_pos,
@ -4307,6 +4308,20 @@ impl World {
} }
} }
if !flags.contains(BlockFlags::SKIP_DROPS) { if !flags.contains(BlockFlags::SKIP_DROPS) {
let tool = if let Some(player) = &cause {
let hand_stack = player
.inventory
.get_stack_in_hand(pumpkin_util::Hand::Right)
.await;
let stack_guard = hand_stack.lock().await;
(stack_guard.item_count > 0).then(|| stack_guard.clone())
} else {
None
};
let is_raining = self.is_raining().await;
let is_thundering = self.is_thundering().await;
let params = LootContextParameters { let params = LootContextParameters {
block_state: Some(BlockState::from_id(broken_state_id)), block_state: Some(BlockState::from_id(broken_state_id)),
luck, luck,
@ -4316,6 +4331,9 @@ impl World {
position.0.z as f64, position.0.z as f64,
)), )),
world_time: self.level_info.load().day_time as u64, world_time: self.level_info.load().day_time as u64,
tool,
is_raining: Some(is_raining),
is_thundering: Some(is_thundering),
..Default::default() ..Default::default()
}; };
block::drop_loot(self, broken_block, position, true, params).await; block::drop_loot(self, broken_block, position, true, params).await;