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_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");
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 {
let ancestor_idx = Self::get_ancestor(nodes, inner_left, idx, default_ancestor);
Self::move_subtree(nodes, ancestor_idx, idx, shift);
@ -659,7 +662,7 @@ pub struct AdvancementStruct {
pub sends_telemetry: bool,
pub requirements: Vec<Vec<String>>,
#[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>
@ -846,10 +849,7 @@ pub(crate) fn build() -> TokenStream {
let advancement_tree = quote! {
pub static ADVANCEMENT_TREE : LazyLock<AdvancementTree> = #tree;
};
let advancements_holder = tree
.nodes_vector
.into_iter()
.map(|node| node.value);
let advancements_holder = tree.nodes_vector.into_iter().map(|node| node.value);
for AdvancementHolder(identifier, advancement) in advancements_holder {
let raw_name = identifier.path();
let format_name = format_ident!("{}", raw_name.to_shouty_snake_case());

View File

@ -29,7 +29,9 @@ pub fn build() -> TokenStream {
(quote! { Int }, quote! { i64 }, quote! { #i })
}
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 {
Value::Number(n) if n.is_i64() => {
let i = n.as_i64().unwrap();

View File

@ -281,9 +281,8 @@ impl ToTokens for LootPoolEntryTypesStruct {
Self::LootTable(entry) => {
tokens.extend(quote! { LootPoolEntryTypes::LootTable(#entry) });
}
Self::Dynamic(_entry) => {
// TODO
tokens.extend(quote! { LootPoolEntryTypes::Dynamic });
Self::Dynamic(entry) => {
tokens.extend(quote! { LootPoolEntryTypes::Dynamic(#entry) });
}
Self::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.
#[serde(rename = "minecraft:enchanted_count_increase")]
EnchantedCountIncrease,
EnchantedCountIncrease {
enchantment: String,
count: LootFunctionNumberProviderStruct,
limit: Option<f32>,
},
/// Smelts the item as if processed in a furnace.
#[serde(rename = "minecraft:furnace_smelt")]
FurnaceSmelt,
/// Sets the potion type on the item.
#[serde(rename = "minecraft:set_potion")]
SetPotion,
SetPotion { id: String },
/// Sets the amplifier on an ominous bottle item.
#[serde(rename = "minecraft:set_ominous_bottle_amplifier")]
SetOminousBottleAmplifier,
@ -724,11 +727,21 @@ impl ToTokens for LootFunctionTypesStruct {
Self::FurnaceSmelt => {
quote! { LootFunctionTypes::FurnaceSmelt }
}
Self::SetPotion => {
quote! { LootFunctionTypes::SetPotion }
Self::SetPotion { id } => {
let i = LitStr::new(id, Span::call_site());
quote! { LootFunctionTypes::SetPotion { id: #i } }
}
Self::EnchantedCountIncrease => {
quote! { LootFunctionTypes::EnchantedCountIncrease }
Self::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 } => {
let min = if let Some(min) = limit.min {

View File

@ -3,11 +3,11 @@
use crate::attributes::Attributes;
use crate::data_component::DataComponent;
use crate::data_component::DataComponent::{
AttributeModifiers, BlockEntityData, BlocksAttacks, BundleContents, ChargedProjectiles,
Consumable, Container, CustomData, CustomName, Damage, DamageResistant, DeathProtection,
Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food, ItemModel, ItemName,
JukeboxPlayable, MapId, MaxDamage, MaxStackSize, PotionContents, StoredEnchantments, Tool,
Unbreakable, UseCooldown, Weapon,
AttributeModifiers, BlockEntityData, BlockState, BlocksAttacks, BundleContents,
ChargedProjectiles, Consumable, Container, CustomData, CustomName, Damage, DamageResistant,
DeathProtection, Enchantable, Enchantments, Equippable, FireworkExplosion, Fireworks, Food,
ItemModel, ItemName, JukeboxPlayable, MapId, MaxDamage, MaxStackSize, OminousBottleAmplifier,
PotionContents, StoredEnchantments, Tool, Unbreakable, UseCooldown, Weapon,
};
use crate::effect::{self, StatusEffect};
use crate::entity_type::EntityType;
@ -29,6 +29,7 @@ use std::error::Error;
use std::fmt::Debug;
use std::str::FromStr;
use std::{
collections::HashMap,
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
@ -81,6 +82,10 @@ pub fn read_data(id: DataComponent, data: &NbtTag) -> Option<Box<dyn DataCompone
DataComponent::WritableBookContent => {
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,
}
}
@ -2062,7 +2067,23 @@ pub struct InstrumentImpl;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ProvidesTrimMaterialImpl;
#[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)]
pub struct JukeboxPlayableImpl {
pub song: &'static str,
@ -2311,8 +2332,58 @@ impl ContainerImpl {
impl DataComponentImpl for ContainerImpl {
default_impl!(Container);
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlockStateImpl;
#[derive(Clone, Debug)]
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)]
pub struct BeesImpl;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]

View File

@ -532782,7 +532782,9 @@ impl Block {
content: LootPoolEntryTypes::Alternatives(AlternativeEntry {
children: &[
LootPoolEntry {
content: LootPoolEntryTypes::Dynamic,
content: LootPoolEntryTypes::Dynamic(DynamicEntry {
name: "minecraft:sherds",
}),
weight: 1i32,
quality: 0i32,
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,
}
#[derive(Clone, Debug)]
pub struct DynamicEntry {
pub name: &'static str,
}
#[derive(Clone, Debug)]
pub enum LootPoolEntryTypes {
Empty,
Item(ItemEntry),
LootTable(LootTableEntry),
Dynamic,
Dynamic(DynamicEntry),
Tag(TagEntry),
Alternatives(AlternativeEntry),
Sequence(SequenceEntry),
@ -173,11 +178,15 @@ pub enum LootFunctionTypes {
add: bool,
},
/// 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.
FurnaceSmelt,
/// Sets the potion type for potion items.
SetPotion,
SetPotion { id: &'static str },
/// Sets the amplifier for ominous bottles.
SetOminousBottleAmplifier,
/// Limits the count of items to a minimum and/or maximum value.

View File

@ -1317,6 +1317,27 @@ impl LivingEntity {
// Plays the death sound
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 {
killed_by_player: cause.map(|c| c.get_entity().entity_type == &EntityType::PLAYER),
this_entity: Some(self.entity.entity_type),
@ -1325,11 +1346,14 @@ impl LivingEntity {
position: Some(self.entity.pos.load()),
world_time: world.level_info.load().day_time as u64,
damage_type: Some(damage_type),
tool,
is_raining: Some(is_raining),
is_thundering: Some(is_thundering),
..Default::default()
};
// Drop loot
self.drop_loot(params).await;
self.drop_loot(params.clone()).await;
// Award experience
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 {
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) {
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);
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 {
block_state: Some(state),
explosion_radius: Some(self.power),
@ -281,6 +283,8 @@ impl Explosion {
pos.0.z as f64,
)),
world_time: world.level_info.load().day_time as u64,
is_raining: Some(is_raining),
is_thundering: Some(is_thundering),
..Default::default()
};
drop_loot(world, block, pos, false, params).await;

View File

@ -12,7 +12,7 @@ use pumpkin_util::{
};
use rand::RngExt;
#[derive(Default, Copy, Clone)]
#[derive(Default, Clone)]
pub struct LootContextParameters {
pub explosion_radius: Option<f32>,
pub block_state: Option<&'static BlockState>,
@ -24,6 +24,9 @@ pub struct LootContextParameters {
pub position: Option<pumpkin_util::math::vector3::Vector3<f64>>,
pub world_time: u64,
pub damage_type: Option<DamageType>,
pub tool: Option<ItemStack>,
pub is_raining: Option<bool>,
pub is_thundering: Option<bool>,
}
pub trait LootTableExt {
@ -100,12 +103,15 @@ trait LootFunctionExt {
fn apply_bonus(
stacks: &mut [ItemStack],
enchantment_name: &str,
formula: &str,
parameters: Option<&LootFunctionBonusParameter>,
params: &LootContextParameters,
) {
// We currently don't have tool/enchantment data in LootContextParameters.
// Assuming enchantment level is 0 for now.
let enchantment_level = 0;
let enchantment_level = params.tool.as_ref().map_or(0, |tool| {
pumpkin_data::Enchantment::from_name(enchantment_name)
.map_or(0, |enchantment| tool.get_enchantment_level(enchantment))
});
if enchantment_level > 0 {
for stack in stacks {
match formula {
@ -145,6 +151,7 @@ fn apply_bonus(
}
impl LootFunctionExt for LootFunction {
#[allow(clippy::too_many_lines)]
fn apply(&self, stacks: &mut Vec<ItemStack>, params: &LootContextParameters) {
if let Some(conditions) = self.conditions
&& !conditions.iter().all(|cond| cond.is_fulfilled(params))
@ -196,25 +203,128 @@ impl LootFunctionExt for LootFunction {
}
}
LootFunctionTypes::ApplyBonus {
enchantment: _,
enchantment,
formula,
parameters,
} => {
apply_bonus(stacks, formula, parameters.as_ref());
apply_bonus(stacks, enchantment, formula, parameters.as_ref(), params);
}
LootFunctionTypes::CopyComponents {
source: _,
include: _,
LootFunctionTypes::EnchantedCountIncrease {
enchantment,
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: _,
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::SetPotion
| LootFunctionTypes::FurnaceSmelt => {
// TODO: shouldnt crash here but needs to be implemented someday
LootFunctionTypes::SetOminousBottleAmplifier => {
let amplifier = rand::random_range(0..5); // Random 0 to 4
for stack in stacks.iter_mut() {
if let Some(amplifier_comp) = stack.get_data_component_mut::<pumpkin_data::data_component_impl::OminousBottleAmplifierImpl>() {
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 {
fn get_stacks(&self, params: &LootContextParameters) -> Vec<ItemStack> {
match self {
Self::Empty | Self::Dynamic => Vec::new(),
Self::Empty | Self::Dynamic(_) => Vec::new(),
Self::LootTable(entry) => {
let key = entry
.value
@ -395,7 +505,7 @@ fn check_damage_source_properties(
}
impl LootConditionExt for LootCondition {
// TODO: This is trash. Make this right
#[allow(clippy::too_many_lines)]
fn is_fulfilled(&self, params: &LootContextParameters) -> bool {
match self {
Self::SurvivesExplosion => {
@ -437,32 +547,27 @@ impl LootConditionExt for LootCondition {
Self::AnyOf(terms) => terms.iter().any(|cond| cond.is_fulfilled(params)),
Self::AllOf(terms) => terms.iter().all(|cond| cond.is_fulfilled(params)),
Self::RandomChanceWithEnchantedBonus {
enchantment: _,
enchantment,
chances,
} => chances.as_ref().is_some_and(|chances| {
// Missing tool context in params, assume enchantment level 0
let level = 0;
let level = params.tool.as_ref().map_or(0, |tool| {
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));
rand::rng().random::<f32>() < *chance
}),
Self::TableBonus {
enchantment: _,
enchantment,
chances,
} => {
// Missing tool context in params, assume enchantment level 0
let level = 0;
let level = params.tool.as_ref().map_or(0, |tool| {
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));
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 } => {
let mut time = params.world_time;
if let Some(period) = period {
@ -484,6 +589,51 @@ impl LootConditionExt for LootCondition {
} => {
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
#[allow(clippy::too_many_lines)]
pub async fn break_block(
self: &Arc<Self>,
position: &BlockPos,
@ -4295,7 +4296,7 @@ impl World {
false,
);
let chunk_pos = position.chunk_position();
match cause {
match &cause {
Some(player) => {
self.broadcast_to_chunk_except(
chunk_pos,
@ -4307,6 +4308,20 @@ impl World {
}
}
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 {
block_state: Some(BlockState::from_id(broken_state_id)),
luck,
@ -4316,6 +4331,9 @@ impl World {
position.0.z as f64,
)),
world_time: self.level_info.load().day_time as u64,
tool,
is_raining: Some(is_raining),
is_thundering: Some(is_thundering),
..Default::default()
};
block::drop_loot(self, broken_block, position, true, params).await;