feat(loot-table): add luck

This commit is contained in:
Alexander Medvedev
2026-05-21 22:20:00 +02:00
parent fcaf852a05
commit d47ac2cd51
9 changed files with 4539 additions and 1576 deletions

446
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -126,7 +126,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.12", features = ["compat"] }
crossfire = { version = "3.1.15", features = ["compat"] }
crypto-bigint = "0.7.3"
dashmap = "6.2"
ecdsa = "0.16.9"
@@ -183,9 +183,9 @@ toml = "1.1"
ureq = "3.3.0"
notify = "8.2.0"
wasmtime = "44.0"
wasmtime-wasi = { version = "44.0", default-features = false, features = ["p2"] }
wasmtime-wasi-http = "44.0"
wasmtime = "45.0"
wasmtime-wasi = { version = "45.0", default-features = false, features = ["p2"] }
wasmtime-wasi-http = "45.0"
# needed for interacting with wasmtime-wasi-http - keep in sync
hyper = { version = "^1.9", features = ["full"] }
wit-bindgen = "0.57"

View File

@@ -319,9 +319,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
[[package]]
name = "elliptic-curve"

View File

@@ -51,9 +51,9 @@ pub struct LootPoolStruct {
/// Entries that can be selected during a roll of this pool.
entries: Vec<LootPoolEntryStruct>,
/// Number of times the pool is rolled.
rolls: LootNumberProviderTypes, // TODO
rolls: LootNumberProviderTypes,
/// Extra rolls granted by luck-related enchantments.
bonus_rolls: f32,
bonus_rolls: LootNumberProviderTypes,
/// Conditions that must all pass for this pool to be rolled, if any.
conditions: Option<Vec<LootConditionStruct>>,
/// Functions applied to the selected entries, if any.
@@ -91,6 +91,7 @@ impl ToTokens for LootPoolStruct {
}
}
/// Deserialized single-item loot entry holding the item's registry key.
#[derive(Deserialize, Clone, Debug)]
pub struct ItemEntryStruct {
@@ -653,16 +654,28 @@ pub struct LootPoolEntryStruct {
/// The concrete entry type (item, alternatives, etc.).
#[serde(flatten)]
content: LootPoolEntryTypesStruct,
/// Relative probability weight; higher values are more likely.
#[serde(default = "default_weight")]
weight: i32,
/// Quality of the entry, used to modify weight based on luck.
#[serde(default)]
quality: i32,
/// Conditions that must all pass for this entry to be evaluated.
conditions: Option<Vec<LootConditionStruct>>,
/// Functions applied to the item if this entry is selected.
functions: Option<Vec<LootFunctionStruct>>,
}
fn default_weight() -> i32 {
1
}
impl ToTokens for LootPoolEntryStruct {
/// Emits a `LootPoolEntry { … }` struct literal token stream for code generation.
fn to_tokens(&self, tokens: &mut TokenStream) {
let content = &self.content;
let weight = self.weight;
let quality = self.quality;
let conditions_tokens = if let Some(conds) = &self.conditions {
let cond_tokens: Vec<_> = conds.iter().map(ToTokens::to_token_stream).collect();
quote! { Some(&[#(#cond_tokens),*]) }
@@ -679,6 +692,8 @@ impl ToTokens for LootPoolEntryStruct {
tokens.extend(quote! {
LootPoolEntry {
content: #content,
weight: #weight,
quality: #quality,
conditions: #conditions_tokens,
functions: #functions_tokens,
}
@@ -686,6 +701,7 @@ impl ToTokens for LootPoolEntryStruct {
}
}
/// Deserialized loot table category, tagged by its `"type"` field.
#[derive(Deserialize, Clone, Debug)]
#[serde(rename = "snake_case")]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@ pub struct LootPool {
/// Number of rolls, specified using a number provider.
pub rolls: LootNumberProviderTypes,
/// Additional bonus rolls to apply.
pub bonus_rolls: f32,
pub bonus_rolls: LootNumberProviderTypes,
/// Optional conditions that must be met for this pool to be applied.
pub conditions: Option<&'static [LootCondition]>,
/// Optional functions applied to each entry in the pool.
@@ -192,6 +192,10 @@ pub enum LootFunctionBonusParameter {
pub struct LootPoolEntry {
/// The type of entry.
pub content: LootPoolEntryTypes,
/// Relative probability weight; higher values are more likely.
pub weight: i32,
/// Quality of the entry, used to modify weight based on luck.
pub quality: i32,
/// Optional conditions for this entry.
pub conditions: Option<&'static [LootCondition]>,
/// Optional functions for this entry.

View File

@@ -15,6 +15,7 @@ pub struct LootContextParameters {
pub explosion_radius: Option<f32>,
pub block_state: Option<&'static BlockState>,
pub killed_by_player: Option<bool>,
pub luck: f32,
}
pub trait LootTableExt {
@@ -34,7 +35,8 @@ impl LootTableExt for LootTable {
continue;
}
let rolls = pool.rolls.get(&mut random).round() as i32;
let rolls = pool.rolls.get(&mut random) as i32
+ (pool.bonus_rolls.get(&mut random) * params.luck).floor() as i32;
for _ in 0..rolls {
let mut total_weight = 0;
@@ -46,9 +48,11 @@ impl LootTableExt for LootTable {
.as_ref()
.is_none_or(|c| c.iter().all(|cond| cond.is_fulfilled(&params)))
{
let w = 1; // TODO: weight
total_weight += w;
valid_entries.push((entry, w));
let weight = (entry.weight as f32 + entry.quality as f32 * params.luck)
.floor() as i32;
let weight = weight.max(0);
total_weight += weight;
valid_entries.push((entry, weight));
}
}

View File

@@ -1,5 +1,6 @@
use crate::block::entities::BlockEntity;
use dashmap::DashMap;
use pumpkin_data::attributes::Attributes;
use pumpkin_data::chunk::Biome;
use pumpkin_protocol::bedrock::client::level_event::{CLevelEvent, LevelEvent};
use pumpkin_protocol::codec::data_component::data_to_proto_sound;
@@ -3572,6 +3573,10 @@ impl World {
// Close container screens for any players viewing this block
self.close_container_screens_at(position).await;
let luck = cause.as_ref().map_or(0.0, |player| {
player.living_entity.get_attribute_value(&Attributes::LUCK) as f32
});
if Block::from_state_id(broken_state_id) != &Block::FIRE {
let particles_packet = CWorldEvent::new(
WorldEvent::ParticlesDestroyBlock as i32,
@@ -3591,10 +3596,10 @@ impl World {
None => self.broadcast_to_chunk(chunk_pos, &particles_packet),
}
}
if !flags.contains(BlockFlags::SKIP_DROPS) {
let params = LootContextParameters {
block_state: Some(BlockState::from_id(broken_state_id)),
luck,
..Default::default()
};
block::drop_loot(self, broken_block, position, true, params).await;