mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
972 lines
35 KiB
Rust
972 lines
35 KiB
Rust
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<String>,
|
|
/// Roll pools contained in this table, if any.
|
|
pools: Option<Vec<LootPoolStruct>>,
|
|
}
|
|
|
|
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<LootPoolEntryStruct>,
|
|
/// Number of times the pool is rolled.
|
|
rolls: LootNumberProviderTypes,
|
|
/// Extra rolls granted by luck-related enchantments.
|
|
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.
|
|
functions: Option<Vec<LootFunctionStruct>>,
|
|
}
|
|
|
|
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<LootPoolEntryStruct>,
|
|
}
|
|
|
|
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<LootPoolEntryStruct>,
|
|
}
|
|
|
|
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<LootPoolEntryStruct>,
|
|
}
|
|
|
|
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) => {
|
|
// TODO
|
|
tokens.extend(quote! { LootPoolEntryTypes::Dynamic });
|
|
}
|
|
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<String>),
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
pub struct ItemPredicateStruct {
|
|
pub items: Option<StringOrVec>,
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
pub struct EntityPredicateStruct {
|
|
#[serde(rename = "type")]
|
|
pub entity_type: Option<StringOrVec>,
|
|
}
|
|
|
|
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<EntityPredicateStruct>,
|
|
pub direct_entity: Option<EntityPredicateStruct>,
|
|
}
|
|
|
|
impl ToTokens for DamageSourcePredicateStruct {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
tokens.extend(quote! { () });
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize, Clone, Debug)]
|
|
pub struct LocationPredicateStruct {
|
|
pub biome: Option<StringOrVec>,
|
|
pub dimension: Option<StringOrVec>,
|
|
}
|
|
|
|
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<LootConditionStruct> },
|
|
/// Passes if any of the child conditions pass.
|
|
#[serde(rename = "minecraft:any_of")]
|
|
AnyOf { terms: Vec<LootConditionStruct> },
|
|
/// Passes if all child conditions pass.
|
|
#[serde(rename = "minecraft:all_of")]
|
|
AllOf { terms: Vec<LootConditionStruct> },
|
|
/// 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<Vec<f32>>,
|
|
},
|
|
/// Passes based on entity NBT predicates.
|
|
#[serde(rename = "minecraft:entity_properties")]
|
|
EntityProperties {
|
|
entity: Option<String>,
|
|
predicate: Option<EntityPredicateStruct>,
|
|
},
|
|
/// 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<String>,
|
|
scores: Option<BTreeMap<String, StringOrVec>>,
|
|
},
|
|
/// 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<String, String>,
|
|
},
|
|
/// Passes if the tool matches an item predicate.
|
|
#[serde(rename = "minecraft:match_tool")]
|
|
MatchTool {
|
|
predicate: Option<ItemPredicateStruct>,
|
|
},
|
|
/// Passes with probability based on an enchantment's level.
|
|
#[serde(rename = "minecraft:table_bonus")]
|
|
TableBonus {
|
|
enchantment: String,
|
|
chances: Vec<f32>,
|
|
},
|
|
/// 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<DamageSourcePredicateStruct>,
|
|
},
|
|
/// Passes based on the block's location.
|
|
#[serde(rename = "minecraft:location_check")]
|
|
LocationCheck {
|
|
predicate: Option<LocationPredicateStruct>,
|
|
#[serde(rename = "offsetX")]
|
|
offset_x: Option<i32>,
|
|
#[serde(rename = "offsetY")]
|
|
offset_y: Option<i32>,
|
|
#[serde(rename = "offsetZ")]
|
|
offset_z: Option<i32>,
|
|
},
|
|
/// Passes based on current weather conditions.
|
|
#[serde(rename = "minecraft:weather_check")]
|
|
WeatherCheck {
|
|
raining: Option<bool>,
|
|
thundering: Option<bool>,
|
|
},
|
|
/// 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<u64>,
|
|
},
|
|
/// 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<Vec<LootConditionStruct>>,
|
|
}
|
|
|
|
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<bool>,
|
|
},
|
|
/// Increases count based on the level of a relevant enchantment.
|
|
#[serde(rename = "minecraft:enchanted_count_increase")]
|
|
EnchantedCountIncrease,
|
|
/// 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,
|
|
/// 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<LootFunctionBonusParameterStruct>,
|
|
},
|
|
/// 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<String>,
|
|
},
|
|
/// 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<String>,
|
|
},
|
|
/// 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 => {
|
|
quote! { LootFunctionTypes::SetPotion }
|
|
}
|
|
Self::EnchantedCountIncrease => {
|
|
quote! { LootFunctionTypes::EnchantedCountIncrease }
|
|
}
|
|
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<f32>,
|
|
/// Inclusive upper bound; count will not exceed this value.
|
|
max: Option<f32>,
|
|
}
|
|
|
|
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<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),*]) }
|
|
} 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);
|
|
}
|
|
}
|