Skip to main content

steel_registry/loot_table/
context.rs

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