use std::collections::BTreeMap; use proc_macro2::{Span, TokenStream}; use pumpkin_util::loot_table::LootNumberProviderTypes; use quote::{ToTokens, quote}; use serde::Deserialize; use syn::LitStr; /// Deserialized loot table as stored in the asset files. /// /// These are required to be defined twice because serde can't deserialize into static context for obvious reasons. #[derive(Deserialize)] pub struct LootTableStruct { /// Category of this loot table (block, entity, chest, etc.). r#type: LootTableTypeStruct, /// Namespaced random-sequence key used for deterministic rolls, if any. random_sequence: Option, /// Roll pools contained in this table, if any. pools: Option>, } impl ToTokens for LootTableStruct { /// Emits a `LootTable { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let loot_table_type = self.r#type.to_token_stream(); let random_sequence = if let Some(seq) = &self.random_sequence { quote! { Some(#seq) } } else { quote! { None } }; let pools = if let Some(pools) = &self.pools { let pool_tokens: Vec<_> = pools.iter().map(ToTokens::to_token_stream).collect(); quote! { Some(&[#(#pool_tokens),*]) } } else { quote! { None } }; tokens.extend(quote! { LootTable { r#type: #loot_table_type, random_sequence: #random_sequence, pools: #pools, } }); } } /// Deserialized loot pool describing a group of entries rolled together. #[derive(Deserialize, Clone, Debug)] pub struct LootPoolStruct { /// Entries that can be selected during a roll of this pool. entries: Vec, /// Number of times the pool is rolled. rolls: LootNumberProviderTypes, /// Extra rolls granted by luck-related enchantments. #[serde(default = "default_bonus_rolls")] bonus_rolls: LootNumberProviderTypes, /// Conditions that must all pass for this pool to be rolled, if any. conditions: Option>, /// Functions applied to the selected entries, if any. functions: Option>, } fn default_bonus_rolls() -> LootNumberProviderTypes { LootNumberProviderTypes::Constant(0.0) } impl ToTokens for LootPoolStruct { /// Emits a `LootPool { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let entries_tokens: Vec<_> = self.entries.iter().map(ToTokens::to_token_stream).collect(); let rolls = &self.rolls; let bonus_rolls = &self.bonus_rolls; 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),*]) } } else { quote! { None } }; let functions_tokens = if let Some(fns) = &self.functions { let cond_tokens: Vec<_> = fns.iter().map(ToTokens::to_token_stream).collect(); quote! { Some(&[#(#cond_tokens),*]) } } else { quote! { None } }; tokens.extend(quote! { LootPool { entries: &[#(#entries_tokens),*], rolls: #rolls, bonus_rolls: #bonus_rolls, conditions: #conditions_tokens, functions: #functions_tokens, } }); } } /// Deserialized single-item loot entry holding the item's registry key. #[derive(Deserialize, Clone, Debug)] pub struct ItemEntryStruct { /// Namespaced item key (e.g., `"minecraft:diamond"`). name: String, } impl ToTokens for ItemEntryStruct { /// Emits an `ItemEntry { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = LitStr::new(&self.name, Span::call_site()); tokens.extend(quote! { ItemEntry { name: #name, } }); } } /// Deserialized loot table reference entry holding the target loot table key. #[derive(Deserialize, Clone, Debug)] pub struct LootTableEntryStruct { /// Namespaced loot table key. value: String, } impl ToTokens for LootTableEntryStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let value = LitStr::new(&self.value, Span::call_site()); tokens.extend(quote! { LootTableEntry { value: #value, } }); } } /// Deserialized alternatives loot entry that tries each child in order until one succeeds. #[derive(Deserialize, Clone, Debug)] pub struct AlternativeEntryStruct { /// Child entries evaluated sequentially until the first successful one. children: Vec, } impl ToTokens for AlternativeEntryStruct { /// Emits an `AlternativeEntry { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let children = self.children.iter().map(ToTokens::to_token_stream); tokens.extend(quote! { AlternativeEntry { children: &[#(#children),*], } }); } } #[derive(Deserialize, Clone, Debug)] pub struct SequenceEntryStruct { /// Child entries evaluated in order until the first condition failure. children: Vec, } impl ToTokens for SequenceEntryStruct { /// Emits a `SequenceEntry { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let children = self.children.iter().map(ToTokens::to_token_stream); tokens.extend(quote! { SequenceEntry { children: &[#(#children),*], } }); } } /// Deserialized group loot entry that evaluates all children regardless of individual success. #[derive(Deserialize, Clone, Debug)] pub struct GroupEntryStruct { /// Child entries all evaluated unconditionally. children: Vec, } impl ToTokens for GroupEntryStruct { /// Emits a `GroupEntry { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let children = self.children.iter().map(ToTokens::to_token_stream); tokens.extend(quote! { GroupEntry { children: &[#(#children),*], } }); } } /// Deserialized dynamic loot entry holding the dynamic drop's namespaced key. #[derive(Deserialize, Clone, Debug)] pub struct DynamicEntryStruct { /// Namespaced key identifying the dynamic drop type (e.g., `"minecraft:contents"`). name: String, } impl ToTokens for DynamicEntryStruct { /// Emits a `DynamicEntry { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = LitStr::new(&self.name, Span::call_site()); tokens.extend(quote! { DynamicEntry { name: #name, } }); } } /// Deserialized tag loot entry holding the item tag's namespaced key. #[derive(Deserialize, Clone, Debug)] pub struct TagEntryStruct { /// Namespaced item tag key (e.g., `"minecraft:planks"`). name: String, /// If `true`, yields one random item from the tag instead of all items. #[serde(default)] expand: bool, } impl ToTokens for TagEntryStruct { /// Emits a `TagEntry { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = LitStr::new(&self.name, Span::call_site()); let expand = self.expand; tokens.extend(quote! { TagEntry { name: #name, expand: #expand, } }); } } /// Deserialized variant of a loot pool entry, tagged by the `"type"` field. #[derive(Deserialize, Clone, Debug)] #[serde(tag = "type")] pub enum LootPoolEntryTypesStruct { /// Yields nothing. #[serde(rename = "minecraft:empty")] Empty, /// Yields a specific item. #[serde(rename = "minecraft:item")] Item(ItemEntryStruct), /// References another loot table by namespaced key. #[serde(rename = "minecraft:loot_table")] LootTable(LootTableEntryStruct), /// Yields dynamically determined drops (e.g., shulker box contents). #[serde(rename = "minecraft:dynamic")] Dynamic(DynamicEntryStruct), /// Yields all items (or one random item) in an item tag. #[serde(rename = "minecraft:tag")] Tag(TagEntryStruct), /// Tries each child entry until one succeeds. #[serde(rename = "minecraft:alternatives")] Alternatives(AlternativeEntryStruct), /// Evaluates all children in order, stopping on the first failure. #[serde(rename = "minecraft:sequence")] Sequence(SequenceEntryStruct), /// Evaluates all children regardless of individual success. #[serde(rename = "minecraft:group")] Group(GroupEntryStruct), } impl ToTokens for LootPoolEntryTypesStruct { fn to_tokens(&self, tokens: &mut TokenStream) { match self { Self::Empty => { tokens.extend(quote! { LootPoolEntryTypes::Empty }); } Self::Item(item) => { tokens.extend(quote! { LootPoolEntryTypes::Item(#item) }); } Self::LootTable(entry) => { tokens.extend(quote! { LootPoolEntryTypes::LootTable(#entry) }); } Self::Dynamic(entry) => { tokens.extend(quote! { LootPoolEntryTypes::Dynamic(#entry) }); } Self::Tag(entry) => { tokens.extend(quote! { LootPoolEntryTypes::Tag(#entry) }); } Self::Alternatives(alt) => { tokens.extend(quote! { LootPoolEntryTypes::Alternatives(#alt) }); } Self::Sequence(seq) => { tokens.extend(quote! { LootPoolEntryTypes::Sequence(#seq) }); } Self::Group(grp) => { tokens.extend(quote! { LootPoolEntryTypes::Group(#grp) }); } } } } #[derive(Deserialize, Clone, Debug)] #[serde(untagged)] pub enum StringOrVec { String(String), Vec(Vec), } #[derive(Deserialize, Clone, Debug)] pub struct ItemPredicateStruct { pub items: Option, } #[derive(Deserialize, Clone, Debug)] pub struct EntityPredicateStruct { #[serde(rename = "type")] pub entity_type: Option, } impl ToTokens for EntityPredicateStruct { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(quote! { () }); } } #[derive(Deserialize, Clone, Debug)] pub struct DamageSourcePredicateStruct { pub source_entity: Option, pub direct_entity: Option, } impl ToTokens for DamageSourcePredicateStruct { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(quote! { () }); } } #[derive(Deserialize, Clone, Debug)] pub struct LocationPredicateStruct { pub biome: Option, pub dimension: Option, } impl ToTokens for LocationPredicateStruct { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.extend(quote! { () }); } } /// Deserialized loot condition, tagged by the `"condition"` field. #[derive(Deserialize, Clone, Debug)] #[serde(tag = "condition")] pub enum LootConditionStruct { /// Passes if the wrapped condition fails. #[serde(rename = "minecraft:inverted")] Inverted { term: Box }, /// Passes if any of the child conditions pass. #[serde(rename = "minecraft:any_of")] AnyOf { terms: Vec }, /// Passes if all child conditions pass. #[serde(rename = "minecraft:all_of")] AllOf { terms: Vec }, /// Passes with the given probability. #[serde(rename = "minecraft:random_chance")] RandomChance { chance: f32 }, /// Passes with probability scaled by an enchantment level. #[serde(rename = "minecraft:random_chance_with_enchanted_bonus")] RandomChanceWithEnchantedBonus { enchantment: String, chances: Option>, }, /// Passes based on entity NBT predicates. #[serde(rename = "minecraft:entity_properties")] EntityProperties { entity: Option, predicate: Option, }, /// Passes if the block was killed by a player. #[serde(rename = "minecraft:killed_by_player")] KilledByPlayer, /// Passes based on entity scoreboard values. #[serde(rename = "minecraft:entity_scores")] EntityScores { entity: Option, scores: Option>, }, /// Passes if the source block has the specified block-state properties. #[serde(rename = "minecraft:block_state_property")] BlockStateProperty { /// Namespaced block key to match. block: String, /// Required block-state property key-value pairs. properties: BTreeMap, }, /// Passes if the tool matches an item predicate. #[serde(rename = "minecraft:match_tool")] MatchTool { predicate: Option, }, /// Passes with probability based on an enchantment's level. #[serde(rename = "minecraft:table_bonus")] TableBonus { enchantment: String, chances: Vec, }, /// Passes if the item survives an explosion. #[serde(rename = "minecraft:survives_explosion")] SurvivesExplosion, /// Passes based on the damage source's properties. #[serde(rename = "minecraft:damage_source_properties")] DamageSourceProperties { predicate: Option, }, /// Passes based on the block's location. #[serde(rename = "minecraft:location_check")] LocationCheck { predicate: Option, #[serde(rename = "offsetX")] offset_x: Option, #[serde(rename = "offsetY")] offset_y: Option, #[serde(rename = "offsetZ")] offset_z: Option, }, /// Passes based on current weather conditions. #[serde(rename = "minecraft:weather_check")] WeatherCheck { raining: Option, thundering: Option, }, /// References an external predicate by ID. #[serde(rename = "minecraft:reference")] Reference { name: String }, /// Passes based on the current in-game time. #[serde(rename = "minecraft:time_check")] TimeCheck { #[serde(rename = "value")] range: LootFunctionLimitCountStruct, period: Option, }, /// Passes based on a numeric value range check. #[serde(rename = "minecraft:value_check")] ValueCheck { value: LootFunctionNumberProviderStruct, range: LootFunctionLimitCountStruct, }, /// Passes if an enchantment is currently active. #[serde(rename = "minecraft:enchantment_active_check")] EnchantmentActiveCheck { active: bool }, } impl ToTokens for LootConditionStruct { /// Emits the matching `LootCondition::*` token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = match self { Self::Inverted { term } => quote! { LootCondition::Inverted(&#term) }, Self::AnyOf { terms } => quote! { LootCondition::AnyOf(&[#(#terms),*]) }, Self::AllOf { terms } => quote! { LootCondition::AllOf(&[#(#terms),*]) }, Self::RandomChance { chance } => { quote! { LootCondition::RandomChance { chance: #chance } } } Self::RandomChanceWithEnchantedBonus { enchantment, chances, } => { let e = LitStr::new(enchantment, Span::call_site()); if let Some(chances) = chances { quote! { LootCondition::RandomChanceWithEnchantedBonus { enchantment: #e, chances: Some(&[#(#chances),*]) } } } else { quote! { LootCondition::RandomChanceWithEnchantedBonus { enchantment: #e, chances: None } } } } Self::EntityProperties { entity, predicate } => { let entity = entity.as_deref().unwrap_or("this"); let e = LitStr::new(entity, Span::call_site()); let expected_type = predicate .as_ref() .and_then(|p| p.entity_type.as_ref()) .map(|t| match t { StringOrVec::String(s) => quote! { Some(#s) }, StringOrVec::Vec(v) => { let s = &v[0]; quote! { Some(#s) } } }) .unwrap_or(quote! { None }); quote! { LootCondition::EntityProperties { entity: #e, expected_type: #expected_type } } } Self::KilledByPlayer => quote! { LootCondition::KilledByPlayer }, Self::EntityScores { entity, scores: _ } => { let entity = entity.as_deref().unwrap_or("this"); let e = LitStr::new(entity, Span::call_site()); quote! { LootCondition::EntityScores { entity: #e } } } Self::BlockStateProperty { block, properties } => { let properties: Vec<_> = properties .iter() .map(|(k, v)| quote! { (#k, #v) }) .collect(); quote! { LootCondition::BlockStateProperty { block: #block, properties: &[#(#properties),*] } } } Self::MatchTool { predicate } => { if let Some(pred) = predicate { if let Some(items) = &pred.items { match items { StringOrVec::String(s) => { let s = LitStr::new(s, Span::call_site()); quote! { LootCondition::MatchTool { items: Some(&[#s]) } } } StringOrVec::Vec(v) => { let v = v.iter().map(|s| LitStr::new(s, Span::call_site())); quote! { LootCondition::MatchTool { items: Some(&[#(#v),*]) } } } } } else { quote! { LootCondition::MatchTool { items: None } } } } else { quote! { LootCondition::MatchTool { items: None } } } } Self::TableBonus { enchantment, chances, } => { let e = LitStr::new(enchantment, Span::call_site()); quote! { LootCondition::TableBonus { enchantment: #e, chances: &[#(#chances),*] } } } Self::SurvivesExplosion => quote! { LootCondition::SurvivesExplosion }, Self::DamageSourceProperties { predicate } => { let expected_source_type = predicate .as_ref() .and_then(|p| p.source_entity.as_ref()) .and_then(|e| e.entity_type.as_ref()) .map(|t| match t { StringOrVec::String(s) => quote! { Some(#s) }, StringOrVec::Vec(v) => { let s = &v[0]; quote! { Some(#s) } } }) .unwrap_or(quote! { None }); let expected_direct_type = predicate .as_ref() .and_then(|p| p.direct_entity.as_ref()) .and_then(|e| e.entity_type.as_ref()) .map(|t| match t { StringOrVec::String(s) => quote! { Some(#s) }, StringOrVec::Vec(v) => { let s = &v[0]; quote! { Some(#s) } } }) .unwrap_or(quote! { None }); quote! { LootCondition::DamageSourceProperties { expected_source_type: #expected_source_type, expected_direct_type: #expected_direct_type } } } Self::LocationCheck { predicate, offset_x, offset_y, offset_z, } => { let ox = offset_x.unwrap_or(0); let oy = offset_y.unwrap_or(0); let oz = offset_z.unwrap_or(0); let expected_biome = predicate .as_ref() .and_then(|p| p.biome.as_ref()) .map(|b| match b { StringOrVec::String(s) => quote! { Some(#s) }, StringOrVec::Vec(v) => { let s = &v[0]; quote! { Some(#s) } } }) .unwrap_or(quote! { None }); quote! { LootCondition::LocationCheck { offset_x: #ox, offset_y: #oy, offset_z: #oz, expected_biome: #expected_biome } } } Self::WeatherCheck { raining, thundering, } => { let r = raining .map(|b| quote! { Some(#b) }) .unwrap_or(quote! { None }); let t = thundering .map(|b| quote! { Some(#b) }) .unwrap_or(quote! { None }); quote! { LootCondition::WeatherCheck { raining: #r, thundering: #t } } } Self::Reference { name } => { let n = LitStr::new(name, Span::call_site()); quote! { LootCondition::Reference { name: #n } } } Self::TimeCheck { range, period } => { let r = range.to_token_stream(); let p = period .map(|val| quote! { Some(#val) }) .unwrap_or(quote! { None }); quote! { LootCondition::TimeCheck { range: #r, period: #p } } } Self::ValueCheck { value, range } => { let v = value.to_token_stream(); let r = range.to_token_stream(); quote! { LootCondition::ValueCheck { value: #v, range: #r } } } Self::EnchantmentActiveCheck { active } => { quote! { LootCondition::EnchantmentActiveCheck { active: #active } } } }; tokens.extend(name); } } /// Deserialized loot function wrapper combining a function type with optional conditions. #[derive(Deserialize, Clone, Debug)] pub struct LootFunctionStruct { /// The concrete function to apply (e.g., set count, apply bonus). #[serde(flatten)] content: LootFunctionTypesStruct, /// Conditions that must all pass for this function to be applied, if any. conditions: Option>, } impl ToTokens for LootFunctionStruct { /// Emits a `LootFunction { … }` struct literal token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let functions_tokens = &self.content.to_token_stream(); 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),*]) } } else { quote! { None } }; tokens.extend(quote! { LootFunction { content: #functions_tokens, conditions: #conditions_tokens, } }); } } /// Deserialized loot function variant, tagged by the `"function"` field. #[derive(Deserialize, Clone, Debug)] #[serde(tag = "function")] pub enum LootFunctionTypesStruct { /// Sets the stack count using a number provider. #[serde(rename = "minecraft:set_count")] SetCount { /// Number provider determining the new stack size. count: LootFunctionNumberProviderStruct, /// If `true`, adds to the existing count instead of replacing it. add: Option, }, /// Increases count based on the level of a relevant enchantment. #[serde(rename = "minecraft:enchanted_count_increase")] EnchantedCountIncrease { enchantment: String, count: LootFunctionNumberProviderStruct, limit: Option, }, /// 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 { id: String }, /// Sets the amplifier on an ominous bottle item. #[serde(rename = "minecraft:set_ominous_bottle_amplifier")] SetOminousBottleAmplifier, /// Clamps the stack count to a min/max range. #[serde(rename = "minecraft:limit_count")] LimitCount { /// The min/max bounds to clamp the count to. limit: LootFunctionLimitCountStruct, }, /// Applies an enchantment bonus using a named formula. #[serde(rename = "minecraft:apply_bonus")] ApplyBonus { /// Namespaced enchantment ID whose level feeds the formula. enchantment: String, /// Name of the bonus formula to apply. formula: String, /// Optional numeric parameters for the chosen formula. parameters: Option, }, /// Copies data components from the block entity source to the dropped item. #[serde(rename = "minecraft:copy_components")] CopyComponents { /// The source to copy components from (e.g., `"block_entity"`). source: String, /// List of component keys to copy. include: Vec, }, /// Copies specified block-state properties onto the item's `block_state` component. #[serde(rename = "minecraft:copy_state")] CopyState { /// Namespaced block whose state properties are copied. block: String, /// Names of the block-state properties to copy. properties: Vec, }, /// Randomly removes items from the stack to simulate explosion damage. #[serde(rename = "minecraft:explosion_decay")] ExplosionDecay, } impl ToTokens for LootFunctionTypesStruct { /// Emits the matching `LootFunctionTypes::*` token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = match self { Self::SetCount { count, add } => { let count = count.to_token_stream(); let add = add.unwrap_or(false); quote! { LootFunctionTypes::SetCount { count: #count, add: #add } } } Self::SetOminousBottleAmplifier => { quote! { LootFunctionTypes::SetOminousBottleAmplifier } } Self::FurnaceSmelt => { quote! { LootFunctionTypes::FurnaceSmelt } } Self::SetPotion { id } => { let i = LitStr::new(id, Span::call_site()); quote! { LootFunctionTypes::SetPotion { id: #i } } } 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 { quote! { Some(#min) } } else { quote! { None } }; let max = if let Some(max) = limit.max { quote! { Some(#max) } } else { quote! { None } }; quote! { LootFunctionTypes::LimitCount { min: #min, max: #max } } } Self::ApplyBonus { enchantment, formula, parameters, } => { let parameters = if let Some(params) = parameters { let params = params.to_token_stream(); quote! { Some(#params) } } else { quote! { None } }; quote! { LootFunctionTypes::ApplyBonus { enchantment: #enchantment, formula: #formula, parameters: #parameters, } } } Self::CopyComponents { source, include } => { quote! { LootFunctionTypes::CopyComponents { source: #source, include: &[#(#include),*], } } } Self::CopyState { block, properties } => { quote! { LootFunctionTypes::CopyState { block: #block, properties: &[#(#properties),*], } } } Self::ExplosionDecay => { quote! { LootFunctionTypes::ExplosionDecay } } }; tokens.extend(name); } } /// Deserialized number provider for loot function count values. #[derive(Deserialize, Clone, Debug)] #[serde(tag = "type")] pub enum LootFunctionNumberProviderStruct { /// Draws a uniformly random value between `min` and `max` (inclusive). #[serde(rename = "minecraft:uniform")] Uniform { /// Lower bound of the uniform range. min: f32, /// Upper bound of the uniform range. max: f32, }, /// Draws from a binomial distribution with `n` trials and success probability `p`. #[serde(rename = "minecraft:binomial")] Binomial { /// Number of trials. n: f32, /// Probability of success per trial. p: f32, }, /// Always returns the fixed value. #[serde(rename = "minecraft:constant", untagged)] Constant(f32), } impl ToTokens for LootFunctionNumberProviderStruct { /// Emits the matching `LootFunctionNumberProvider::*` token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = match self { Self::Constant(value) => { quote! { LootFunctionNumberProvider::Constant { value: #value } } } Self::Uniform { min, max } => { quote! { LootFunctionNumberProvider::Uniform { min: #min, max: #max } } } Self::Binomial { n, p } => { quote! { LootFunctionNumberProvider::Binomial { n: #n, p: #p } } } }; tokens.extend(name); } } /// Deserialized min/max bounds used by the `LimitCount` loot function. #[derive(Deserialize, Clone, Debug)] pub struct LootFunctionLimitCountStruct { /// Inclusive lower bound; count will not go below this value. min: Option, /// Inclusive upper bound; count will not exceed this value. max: Option, } impl ToTokens for LootFunctionLimitCountStruct { fn to_tokens(&self, tokens: &mut TokenStream) { let min = self .min .map(|val| quote! { Some(#val) }) .unwrap_or(quote! { None }); let max = self .max .map(|val| quote! { Some(#val) }) .unwrap_or(quote! { None }); tokens.extend(quote! { (#min, #max) }); } } /// Deserialized bonus parameters for the `ApplyBonus` loot function. #[derive(Deserialize, Clone, Debug)] #[serde(untagged)] pub enum LootFunctionBonusParameterStruct { /// A flat multiplier applied to the enchantment level. Multiplier { /// The bonus multiplier value. #[serde(rename = "bonusMultiplier")] bonus_multiplier: i32, }, /// Probability-based bonus using extra attempts per enchantment level. Probability { /// Extra drop attempts added per enchantment level. extra: i32, /// Per-attempt success probability. probability: f32, }, } impl ToTokens for LootFunctionBonusParameterStruct { /// Emits the matching `LootFunctionBonusParameter::*` token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = match self { Self::Multiplier { bonus_multiplier } => { quote! { LootFunctionBonusParameter::Multiplier { bonus_multiplier: #bonus_multiplier } } } Self::Probability { extra, probability } => { quote! { LootFunctionBonusParameter::Probability { extra: #extra, probability: #probability } } } }; tokens.extend(name); } } /// Deserialized loot pool entry combining an entry type with optional conditions and functions. #[derive(Deserialize, Clone, Debug)] 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>, /// Functions applied to the item if this entry is selected. functions: Option>, } 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),*]) } } else { quote! { None } }; let functions_tokens = if let Some(fns) = &self.functions { let cond_tokens: Vec<_> = fns.iter().map(ToTokens::to_token_stream).collect(); quote! { Some(&[#(#cond_tokens),*]) } } else { quote! { None } }; tokens.extend(quote! { LootPoolEntry { content: #content, weight: #weight, quality: #quality, conditions: #conditions_tokens, functions: #functions_tokens, } }); } } /// Deserialized loot table category, tagged by its `"type"` field. #[derive(Deserialize, Clone, Debug)] #[serde(rename = "snake_case")] pub enum LootTableTypeStruct { /// Nothing will be dropped. #[serde(rename = "minecraft:empty")] Empty, /// Entity loot will be dropped. #[serde(rename = "minecraft:entity")] Entity, /// Block drops will be generated. #[serde(rename = "minecraft:block")] Block, /// Chest loot will be generated. #[serde(rename = "minecraft:chest")] Chest, } impl ToTokens for LootTableTypeStruct { /// Emits the matching `LootTableType::*` token stream for code generation. fn to_tokens(&self, tokens: &mut TokenStream) { let name = match self { Self::Empty => quote! { LootTableType::Empty }, Self::Entity => quote! { LootTableType::Entity }, Self::Block => quote! { LootTableType::Block }, Self::Chest => quote! { LootTableType::Chest }, }; tokens.extend(name); } }