Skip to main content

steel_registry/loot_table/
context.rs

1use super::{BlockStateId, DyeColor, Identifier, ItemStack, REGISTRY, RegistryExt, RngExt};
2use crate::equipment::EquipmentSlot;
3
4/// Entity target for loot context lookups.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum LootContextEntity {
7    /// The entity being looted (killed mob, block entity owner).
8    This,
9    /// The entity that killed the target.
10    Killer,
11    /// The direct attacker (e.g., arrow, not the player who shot it).
12    DirectKiller,
13    /// The player who dealt the final damage.
14    KillerPlayer,
15    /// The entity interacting with a block/entity.
16    Interacting,
17}
18
19/// The type of loot table, determining when/how it's used.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum LootType {
22    Block,
23    Entity,
24    Chest,
25    Fishing,
26    Gift,
27    Archaeology,
28    Vault,
29    Shearing,
30    Equipment,
31    Selector,
32    EntityInteract,
33    BlockInteract,
34    Barter,
35}
36
37/// A number provider that can be constant or random.
38#[derive(Debug, Clone)]
39pub enum NumberProvider {
40    Constant(f32),
41    Uniform {
42        min: f32,
43        max: f32,
44    },
45    Binomial {
46        n: i32,
47        p: f32,
48    },
49    /// Get value from entity scoreboard score.
50    Score {
51        target: ScoreboardTarget,
52        score: &'static str,
53        scale: f32,
54    },
55    /// Get value from command storage.
56    Storage {
57        storage: Identifier,
58        path: &'static str,
59    },
60    /// Get enchantment level from context tool.
61    EnchantmentLevel {
62        enchantment: Identifier,
63    },
64}
65
66/// Target for scoreboard number provider.
67#[derive(Debug, Clone, Copy)]
68pub enum ScoreboardTarget {
69    /// The entity being looted.
70    This,
71    /// The entity that killed the target.
72    Killer,
73    /// The direct killer (e.g., arrow vs player).
74    DirectKiller,
75    /// The player who dealt the last damage.
76    KillerPlayer,
77    /// A fixed entity name.
78    Fixed(&'static str),
79}
80
81impl NumberProvider {
82    /// Get a value from this provider using the given RNG.
83    pub fn get<R: rand::Rng>(&self, rng: &mut R, ctx: Option<&LootContextRef<'_>>) -> f32 {
84        match self {
85            Self::Constant(v) => *v,
86            Self::Uniform { min, max } => rng.random_range(*min..=*max),
87            Self::Binomial { n, p } => {
88                let mut count = 0;
89                for _ in 0..*n {
90                    if rng.random::<f32>() < *p {
91                        count += 1;
92                    }
93                }
94                count as f32
95            }
96            Self::Score { .. } => {
97                // TODO: Implement when scoreboard system is available
98                let _ = ctx;
99                0.0
100            }
101            Self::Storage { .. } => {
102                // TODO: Implement when command storage system is available
103                let _ = ctx;
104                0.0
105            }
106            Self::EnchantmentLevel { enchantment } => ctx
107                .and_then(|c| c.tool)
108                .map_or(0.0, |t| t.get_enchantment_level(enchantment) as f32),
109        }
110    }
111
112    /// Get a value using the loot context's RNG and parameters.
113    pub fn get_float<R: rand::Rng>(&self, ctx: &mut LootContext<'_, R>) -> f32 {
114        let ctx_ref = LootContextRef { tool: ctx.tool };
115        self.get(ctx.rng, Some(&ctx_ref))
116    }
117
118    /// Get a value without context (for backwards compatibility).
119    pub fn get_simple(&self, rng: &mut impl rand::Rng) -> f32 {
120        match self {
121            Self::Constant(v) => *v,
122            Self::Uniform { min, max } => rng.random_range(*min..=*max),
123            Self::Binomial { n, p } => {
124                let mut count = 0;
125                for _ in 0..*n {
126                    if rng.random::<f32>() < *p {
127                        count += 1;
128                    }
129                }
130                count as f32
131            }
132            // Context-dependent providers return 0 when no context available
133            Self::Score { .. } | Self::Storage { .. } | Self::EnchantmentLevel { .. } => 0.0,
134        }
135    }
136
137    /// Get the value as an integer.
138    pub fn get_int(&self, rng: &mut impl rand::Rng) -> i32 {
139        match self {
140            Self::Uniform { min, max } => uniform_int(rng, math_round(*min), math_round(*max)),
141            other => math_round(other.get_simple(rng)),
142        }
143    }
144
145    /// Get the value as an integer with context.
146    pub fn get_int_with_ctx<R: rand::Rng>(
147        &self,
148        rng: &mut R,
149        ctx: Option<&LootContextRef<'_>>,
150    ) -> i32 {
151        match self {
152            Self::Uniform { min, max } => uniform_int(rng, math_round(*min), math_round(*max)),
153            other => math_round(other.get(rng, ctx)),
154        }
155    }
156}
157
158/// `java.lang.Math.round` semantics for a float.
159pub(crate) fn math_round(value: f32) -> i32 {
160    (f64::from(value) + 0.5).floor() as i32
161}
162
163/// Vanilla `Mth.nextInt(random, min, max)` is inclusive and clamps to `min`
164/// when `min >= max`.
165fn uniform_int(rng: &mut impl rand::Rng, min: i32, max: i32) -> i32 {
166    if min >= max {
167        min
168    } else {
169        rng.random_range(min..=max)
170    }
171}
172
173/// A range for number comparisons (used in `ValueCheck`, `TimeCheck`, `EntityScores`).
174#[derive(Debug, Clone)]
175pub struct NumberProviderRange {
176    pub min: Option<NumberProvider>,
177    pub max: Option<NumberProvider>,
178}
179
180impl NumberProviderRange {
181    /// Check if a value is within this range.
182    pub fn test(&self, value: f32, rng: &mut impl rand::Rng) -> bool {
183        if let Some(min) = &self.min
184            && value < min.get_simple(rng)
185        {
186            return false;
187        }
188        if let Some(max) = &self.max
189            && value > max.get_simple(rng)
190        {
191            return false;
192        }
193        true
194    }
195
196    /// Create an exact match range.
197    #[must_use]
198    pub const fn exact(value: f32) -> Self {
199        Self {
200            min: Some(NumberProvider::Constant(value)),
201            max: Some(NumberProvider::Constant(value)),
202        }
203    }
204
205    /// Create an at-least range.
206    #[must_use]
207    pub const fn at_least(min: f32) -> Self {
208        Self {
209            min: Some(NumberProvider::Constant(min)),
210            max: None,
211        }
212    }
213
214    /// Create an at-most range.
215    #[must_use]
216    pub const fn at_most(max: f32) -> Self {
217        Self {
218            min: None,
219            max: Some(NumberProvider::Constant(max)),
220        }
221    }
222
223    /// Create a between range.
224    #[must_use]
225    pub const fn between(min: f32, max: f32) -> Self {
226        Self {
227            min: Some(NumberProvider::Constant(min)),
228            max: Some(NumberProvider::Constant(max)),
229        }
230    }
231}
232
233/// Reference to loot context for number provider evaluation.
234/// This is a simplified view to avoid circular references.
235pub struct LootContextRef<'a> {
236    pub tool: Option<&'a ItemStack>,
237    // Add more fields as needed for Score/Storage providers
238}
239
240/// Context for loot table evaluation, containing all relevant game state.
241///
242/// This mirrors vanilla's `LootContext` / `LootParams` system.
243pub struct LootContext<'a, R: rand::Rng> {
244    /// Random number generator.
245    pub rng: &'a mut R,
246    /// Luck value (e.g., from Luck of the Sea enchantment).
247    pub luck: f32,
248    /// The block state being broken (for block loot tables).
249    pub block_state: Option<BlockStateId>,
250    /// The tool used to break the block.
251    pub tool: Option<&'a ItemStack>,
252    /// Explosion radius if caused by an explosion.
253    pub explosion_radius: Option<f32>,
254    /// Whether the entity was killed by a player.
255    pub killed_by_player: bool,
256
257    /// World position where the loot is generated (block position or entity death location).
258    pub origin: Option<(f64, f64, f64)>,
259    /// Current game time in ticks (for `TimeCheck` condition).
260    pub game_time: Option<i64>,
261    /// Current weather state.
262    pub weather: Option<WeatherState>,
263    /// The entity being looted (the killed mob, block entity owner, etc.).
264    /// This is a type-erased pointer; actual entity data depends on game implementation.
265    pub this_entity: Option<EntityRef<'a>>,
266    /// The entity that killed `this_entity` (for mob loot tables).
267    pub killer_entity: Option<EntityRef<'a>>,
268    /// The direct attacker entity (e.g., arrow, not the player who shot it).
269    pub direct_killer_entity: Option<EntityRef<'a>>,
270    /// The player who dealt the final damage (may be different from killer).
271    pub last_damage_player: Option<EntityRef<'a>>,
272    /// Damage source information for entity deaths.
273    pub damage_source: Option<DamageSourceInfo<'a>>,
274    /// Block entity reference for container/block loot.
275    pub block_entity: Option<BlockEntityRef<'a>>,
276    /// The entity interacting with a block/entity (e.g., player opening a chest).
277    pub interacting_entity: Option<EntityRef<'a>>,
278}
279
280/// Weather state for `WeatherCheck` condition.
281#[derive(Debug, Clone, Copy, Default)]
282pub struct WeatherState {
283    pub raining: bool,
284    pub thundering: bool,
285}
286
287/// A reference to an entity for loot context.
288/// This is intentionally opaque - the actual entity type depends on game implementation.
289#[derive(Debug, Clone, Copy)]
290pub struct EntityRef<'a> {
291    /// Type identifier for the entity.
292    pub entity_type: Option<&'a Identifier>,
293    /// Entity flags for predicate checking.
294    pub flags: EntityRefFlags,
295    /// Equipment slots for equipment predicates. `None` for non-living entities.
296    pub equipment: Option<EntityEquipmentRef<'a>>,
297    /// Entity name (for `copy_name` function).
298    pub custom_name: Option<&'a str>,
299    /// Vanilla `minecraft:components.sheep/color` entity data component.
300    pub sheep_color: Option<DyeColor>,
301    /// Vanilla `minecraft:type_specific/sheep.sheared`. `None` when the entity is
302    /// not a sheep, matching vanilla `SheepPredicate.matches`' non-sheep rejection.
303    pub sheep_sheared: Option<bool>,
304    /// Vanilla `minecraft:components.chicken/variant` entity data component key.
305    pub chicken_variant: Option<&'a Identifier>,
306}
307
308/// Entity flags for predicate checking.
309#[derive(Debug, Clone, Copy, Default)]
310pub struct EntityRefFlags {
311    pub is_on_fire: bool,
312    pub is_sneaking: bool,
313    pub is_sprinting: bool,
314    pub is_swimming: bool,
315    pub is_baby: bool,
316}
317
318/// One item stack per equipment slot, indexed by [`EquipmentSlot::index`].
319pub type EntityEquipmentSlots = [ItemStack; EquipmentSlot::ALL.len()];
320
321/// Every equipment slot of a living entity.
322#[derive(Debug, Clone, Copy)]
323pub struct EntityEquipmentRef<'a>(&'a EntityEquipmentSlots);
324
325impl<'a> EntityEquipmentRef<'a> {
326    #[must_use]
327    pub const fn new(slots: &'a EntityEquipmentSlots) -> Self {
328        Self(slots)
329    }
330
331    /// Returns the item in the given equipment slot.
332    #[must_use]
333    pub const fn get(self, slot: EquipmentSlot) -> &'a ItemStack {
334        &self.0[slot.index()]
335    }
336
337    /// Returns the highest enchantment level from matching equipment slots.
338    #[must_use]
339    pub fn get_enchantment_level(self, enchantment: &Identifier) -> i32 {
340        let Some(definition) = REGISTRY.enchantments.by_key(enchantment) else {
341            return 0;
342        };
343        EquipmentSlot::ALL
344            .into_iter()
345            .filter(|slot| definition.matching_slot(*slot))
346            .filter_map(|slot| {
347                let stack = self.get(slot);
348                (!stack.is_empty()).then(|| stack.get_enchantment_level(enchantment))
349            })
350            .max()
351            .unwrap_or(0)
352    }
353}
354
355/// Damage source information for loot context.
356#[derive(Debug, Clone, Copy)]
357pub struct DamageSourceInfo<'a> {
358    /// The damage type identifier.
359    pub damage_type: Option<&'a Identifier>,
360    /// Tags associated with this damage source.
361    pub tags: &'a [Identifier],
362    /// Whether this is direct damage (not from a projectile).
363    pub is_direct: bool,
364}
365
366/// A reference to a block entity for loot context.
367#[derive(Debug, Clone, Copy)]
368pub struct BlockEntityRef<'a> {
369    /// The block entity type identifier.
370    pub block_entity_type: Option<&'a Identifier>,
371    /// Custom name of the block entity (for `copy_name`).
372    pub custom_name: Option<&'a str>,
373    /// Inventory contents (for dynamic/slots entries).
374    pub inventory: Option<&'a [ItemStack]>,
375}
376
377impl<'a, R: rand::Rng> LootContext<'a, R> {
378    /// Create a new loot context with just an RNG.
379    pub const fn new(rng: &'a mut R) -> Self {
380        Self {
381            rng,
382            luck: 0.0,
383            block_state: None,
384            tool: None,
385            explosion_radius: None,
386            killed_by_player: false,
387            origin: None,
388            game_time: None,
389            weather: None,
390            this_entity: None,
391            killer_entity: None,
392            direct_killer_entity: None,
393            last_damage_player: None,
394            damage_source: None,
395            block_entity: None,
396            interacting_entity: None,
397        }
398    }
399
400    /// Set the luck value.
401    #[must_use]
402    pub const fn with_luck(mut self, luck: f32) -> Self {
403        self.luck = luck;
404        self
405    }
406
407    /// Set the block state.
408    #[must_use]
409    pub const fn with_block_state(mut self, state: BlockStateId) -> Self {
410        self.block_state = Some(state);
411        self
412    }
413
414    /// Set the tool used.
415    #[must_use]
416    pub const fn with_tool(mut self, tool: &'a ItemStack) -> Self {
417        self.tool = Some(tool);
418        self
419    }
420
421    /// Set the explosion radius.
422    #[must_use]
423    pub const fn with_explosion(mut self, radius: f32) -> Self {
424        self.explosion_radius = Some(radius);
425        self
426    }
427
428    /// Set whether killed by player.
429    #[must_use]
430    pub const fn with_killed_by_player(mut self, killed: bool) -> Self {
431        self.killed_by_player = killed;
432        self
433    }
434
435    /// Set the world origin position.
436    #[must_use]
437    pub const fn with_origin(mut self, x: f64, y: f64, z: f64) -> Self {
438        self.origin = Some((x, y, z));
439        self
440    }
441
442    /// Set the game time.
443    #[must_use]
444    pub const fn with_game_time(mut self, time: i64) -> Self {
445        self.game_time = Some(time);
446        self
447    }
448
449    /// Set the weather state.
450    #[must_use]
451    pub const fn with_weather(mut self, weather: WeatherState) -> Self {
452        self.weather = Some(weather);
453        self
454    }
455
456    /// Set the entity being looted.
457    #[must_use]
458    pub const fn with_this_entity(mut self, entity: EntityRef<'a>) -> Self {
459        self.this_entity = Some(entity);
460        self
461    }
462
463    /// Set the killer entity.
464    #[must_use]
465    pub const fn with_killer_entity(mut self, entity: EntityRef<'a>) -> Self {
466        self.killer_entity = Some(entity);
467        self
468    }
469
470    /// Set the direct killer entity (e.g., projectile).
471    #[must_use]
472    pub const fn with_direct_killer_entity(mut self, entity: EntityRef<'a>) -> Self {
473        self.direct_killer_entity = Some(entity);
474        self
475    }
476
477    /// Set the player who dealt the final damage.
478    #[must_use]
479    pub const fn with_last_damage_player(mut self, entity: EntityRef<'a>) -> Self {
480        self.last_damage_player = Some(entity);
481        self
482    }
483
484    /// Set the damage source information.
485    #[must_use]
486    pub const fn with_damage_source(mut self, damage_source: DamageSourceInfo<'a>) -> Self {
487        self.damage_source = Some(damage_source);
488        self
489    }
490
491    /// Set the block entity reference.
492    #[must_use]
493    pub const fn with_block_entity(mut self, block_entity: BlockEntityRef<'a>) -> Self {
494        self.block_entity = Some(block_entity);
495        self
496    }
497
498    /// Set the interacting entity (e.g., player opening a chest).
499    #[must_use]
500    pub const fn with_interacting_entity(mut self, entity: EntityRef<'a>) -> Self {
501        self.interacting_entity = Some(entity);
502        self
503    }
504
505    /// Get the level of an enchantment on the tool by identifier.
506    #[must_use]
507    pub fn get_enchantment_level_by_id(&self, enchantment: &Identifier) -> i32 {
508        self.tool
509            .map_or(0, |t| t.get_enchantment_level(enchantment))
510    }
511
512    /// Get the level of an enchantment on the attacking entity's equipment.
513    #[must_use]
514    pub fn get_attacking_entity_enchantment_level(&self, enchantment: &Identifier) -> i32 {
515        self.killer_entity
516            .and_then(|entity| entity.equipment)
517            .map_or(0, |equipment| equipment.get_enchantment_level(enchantment))
518    }
519
520    /// Get an entity reference by target.
521    #[must_use]
522    pub const fn get_entity(&self, target: LootContextEntity) -> Option<EntityRef<'a>> {
523        match target {
524            LootContextEntity::This => self.this_entity,
525            LootContextEntity::Killer => self.killer_entity,
526            LootContextEntity::DirectKiller => self.direct_killer_entity,
527            LootContextEntity::KillerPlayer => self.last_damage_player,
528            LootContextEntity::Interacting => self.interacting_entity,
529        }
530    }
531}