This commit is contained in:
parent
161b90ed28
commit
878116ceff
@ -131,6 +131,90 @@ impl ToTokens for AlternativeEntryStruct {
|
||||
}
|
||||
}
|
||||
|
||||
#[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")]
|
||||
@ -141,28 +225,27 @@ pub enum LootPoolEntryTypesStruct {
|
||||
/// Yields a specific item.
|
||||
#[serde(rename = "minecraft:item")]
|
||||
Item(ItemEntryStruct),
|
||||
/// References another loot table.
|
||||
/// References another loot table by namespaced key.
|
||||
#[serde(rename = "minecraft:loot_table")]
|
||||
LootTable,
|
||||
/// Yields dynamically determined drops (e.g., shulker box contents).
|
||||
#[serde(rename = "minecraft:dynamic")]
|
||||
Dynamic,
|
||||
/// Yields all items in a tag.
|
||||
Dynamic(DynamicEntryStruct),
|
||||
/// Yields all items (or one random item) in an item tag.
|
||||
#[serde(rename = "minecraft:tag")]
|
||||
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,
|
||||
Sequence(SequenceEntryStruct),
|
||||
/// Evaluates all children regardless of individual success.
|
||||
#[serde(rename = "minecraft:group")]
|
||||
Group,
|
||||
Group(GroupEntryStruct),
|
||||
}
|
||||
|
||||
impl ToTokens for LootPoolEntryTypesStruct {
|
||||
/// Emits the matching `LootPoolEntryTypes::*` token stream for code generation.
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
Self::Empty => {
|
||||
@ -174,20 +257,21 @@ impl ToTokens for LootPoolEntryTypesStruct {
|
||||
Self::LootTable => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::LootTable });
|
||||
}
|
||||
Self::Dynamic => {
|
||||
Self::Dynamic(entry) => {
|
||||
// TODO
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Dynamic });
|
||||
}
|
||||
Self::Tag => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Tag });
|
||||
Self::Tag(entry) => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Tag(#entry) });
|
||||
}
|
||||
Self::Alternatives(alt) => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Alternatives(#alt) });
|
||||
}
|
||||
Self::Sequence => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Sequence });
|
||||
Self::Sequence(seq) => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Sequence(#seq) });
|
||||
}
|
||||
Self::Group => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Group });
|
||||
Self::Group(grp) => {
|
||||
tokens.extend(quote! { LootPoolEntryTypes::Group(#grp) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,7 +292,9 @@ pub enum LootConditionStruct {
|
||||
AllOf,
|
||||
/// Passes with the given probability.
|
||||
#[serde(rename = "minecraft:random_chance")]
|
||||
RandomChance,
|
||||
RandomChance {
|
||||
chance: f32,
|
||||
},
|
||||
/// Passes with probability scaled by an enchantment level.
|
||||
#[serde(rename = "minecraft:random_chance_with_enchanted_bonus")]
|
||||
RandomChanceWithEnchantedBonus,
|
||||
@ -268,7 +354,9 @@ impl ToTokens for LootConditionStruct {
|
||||
Self::Inverted => quote! { LootCondition::Inverted },
|
||||
Self::AnyOf => quote! { LootCondition::AnyOf },
|
||||
Self::AllOf => quote! { LootCondition::AllOf },
|
||||
Self::RandomChance => quote! { LootCondition::RandomChance },
|
||||
Self::RandomChance { chance } => {
|
||||
quote! { LootCondition::RandomChance { chance: #chance } }
|
||||
}
|
||||
Self::RandomChanceWithEnchantedBonus => {
|
||||
quote! { LootCondition::RandomChanceWithEnchantedBonus }
|
||||
}
|
||||
|
||||
@ -41378,7 +41378,9 @@ impl Block {
|
||||
content: LootPoolEntryTypes::Item(ItemEntry {
|
||||
name: "minecraft:wheat_seeds",
|
||||
}),
|
||||
conditions: Some(&[LootCondition::RandomChance]),
|
||||
conditions: Some(&[LootCondition::RandomChance {
|
||||
chance: 0.125f32,
|
||||
}]),
|
||||
functions: Some(&[
|
||||
LootFunction {
|
||||
content: LootFunctionTypes::ApplyBonus {
|
||||
@ -41468,7 +41470,9 @@ impl Block {
|
||||
content: LootPoolEntryTypes::Item(ItemEntry {
|
||||
name: "minecraft:wheat_seeds",
|
||||
}),
|
||||
conditions: Some(&[LootCondition::RandomChance]),
|
||||
conditions: Some(&[LootCondition::RandomChance {
|
||||
chance: 0.125f32,
|
||||
}]),
|
||||
functions: Some(&[
|
||||
LootFunction {
|
||||
content: LootFunctionTypes::ApplyBonus {
|
||||
@ -166451,7 +166455,7 @@ impl Block {
|
||||
content: LootPoolEntryTypes::Item(ItemEntry {
|
||||
name: "minecraft:poisonous_potato",
|
||||
}),
|
||||
conditions: Some(&[LootCondition::RandomChance]),
|
||||
conditions: Some(&[LootCondition::RandomChance { chance: 0.02f32 }]),
|
||||
functions: None,
|
||||
}],
|
||||
rolls: LootNumberProviderTypes::Constant(1f32),
|
||||
@ -201193,7 +201197,7 @@ impl Block {
|
||||
}),
|
||||
conditions: Some(&[
|
||||
LootCondition::SurvivesExplosion,
|
||||
LootCondition::RandomChance,
|
||||
LootCondition::RandomChance { chance: 0.125f32 },
|
||||
]),
|
||||
functions: None,
|
||||
},
|
||||
@ -201238,7 +201242,7 @@ impl Block {
|
||||
}),
|
||||
conditions: Some(&[
|
||||
LootCondition::SurvivesExplosion,
|
||||
LootCondition::RandomChance,
|
||||
LootCondition::RandomChance { chance: 0.125f32 },
|
||||
]),
|
||||
functions: None,
|
||||
},
|
||||
@ -201345,7 +201349,7 @@ impl Block {
|
||||
}),
|
||||
conditions: Some(&[
|
||||
LootCondition::SurvivesExplosion,
|
||||
LootCondition::RandomChance,
|
||||
LootCondition::RandomChance { chance: 0.125f32 },
|
||||
]),
|
||||
functions: None,
|
||||
},
|
||||
@ -201390,7 +201394,7 @@ impl Block {
|
||||
}),
|
||||
conditions: Some(&[
|
||||
LootCondition::SurvivesExplosion,
|
||||
LootCondition::RandomChance,
|
||||
LootCondition::RandomChance { chance: 0.125f32 },
|
||||
]),
|
||||
functions: None,
|
||||
},
|
||||
|
||||
@ -1441,7 +1441,7 @@ impl EntityType {
|
||||
}],
|
||||
rolls: LootNumberProviderTypes::Constant(1f32),
|
||||
bonus_rolls: 0f32,
|
||||
conditions: Some(&[LootCondition::RandomChance]),
|
||||
conditions: Some(&[LootCondition::RandomChance { chance: 0.05f32 }]),
|
||||
functions: None,
|
||||
},
|
||||
]),
|
||||
@ -1774,7 +1774,10 @@ impl EntityType {
|
||||
},
|
||||
LootPool {
|
||||
entries: &[LootPoolEntry {
|
||||
content: LootPoolEntryTypes::Tag,
|
||||
content: LootPoolEntryTypes::Tag(TagEntry {
|
||||
name: "minecraft:creeper_drop_music_discs",
|
||||
expand: true,
|
||||
}),
|
||||
conditions: None,
|
||||
functions: None,
|
||||
}],
|
||||
@ -5590,7 +5593,7 @@ impl EntityType {
|
||||
}],
|
||||
rolls: LootNumberProviderTypes::Constant(1f32),
|
||||
bonus_rolls: 0f32,
|
||||
conditions: Some(&[LootCondition::RandomChance]),
|
||||
conditions: Some(&[LootCondition::RandomChance { chance: 0.05f32 }]),
|
||||
functions: None,
|
||||
},
|
||||
]),
|
||||
@ -5856,7 +5859,7 @@ impl EntityType {
|
||||
}],
|
||||
rolls: LootNumberProviderTypes::Constant(1f32),
|
||||
bonus_rolls: 0f32,
|
||||
conditions: Some(&[LootCondition::RandomChance]),
|
||||
conditions: Some(&[LootCondition::RandomChance { chance: 0.05f32 }]),
|
||||
functions: None,
|
||||
},
|
||||
]),
|
||||
@ -7358,7 +7361,7 @@ impl EntityType {
|
||||
}],
|
||||
rolls: LootNumberProviderTypes::Constant(1f32),
|
||||
bonus_rolls: 0f32,
|
||||
conditions: Some(&[LootCondition::RandomChance]),
|
||||
conditions: Some(&[LootCondition::RandomChance { chance: 0.05f32 }]),
|
||||
functions: None,
|
||||
},
|
||||
]),
|
||||
|
||||
@ -5,7 +5,7 @@ use serde::Deserialize;
|
||||
use crate::random::RandomImpl;
|
||||
|
||||
/// Represents a loot table used to generate items or rewards.
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct LootTable {
|
||||
/// The type of loot table (e.g., `Entity`, `Block`, `Chest`).
|
||||
pub r#type: LootTableType,
|
||||
@ -16,7 +16,7 @@ pub struct LootTable {
|
||||
}
|
||||
|
||||
/// Defines a loot pool containing entries, rolls, and conditions.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LootPool {
|
||||
/// Entries contained in this loot pool.
|
||||
pub entries: &'static [LootPoolEntry],
|
||||
@ -38,35 +38,42 @@ pub struct ItemEntry {
|
||||
}
|
||||
|
||||
/// Represents an alternative loot entry, which chooses one of its children.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AlternativeEntry {
|
||||
/// Child entries for this alternative.
|
||||
pub children: &'static [LootPoolEntry],
|
||||
}
|
||||
|
||||
/// Types of entries that can exist in a loot pool.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TagEntry {
|
||||
pub name: &'static str,
|
||||
pub expand: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SequenceEntry {
|
||||
pub children: &'static [LootPoolEntry],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GroupEntry {
|
||||
pub children: &'static [LootPoolEntry],
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum LootPoolEntryTypes {
|
||||
/// Empty entry; no item is generated.
|
||||
Empty,
|
||||
/// Entry that generates a specific item.
|
||||
Item(ItemEntry),
|
||||
/// Entry that references another loot table.
|
||||
LootTable,
|
||||
/// Dynamic entry, resolved at runtime.
|
||||
Dynamic,
|
||||
/// Entry that references a tag.
|
||||
Tag,
|
||||
/// Entry that provides alternatives.
|
||||
Tag(TagEntry),
|
||||
Alternatives(AlternativeEntry),
|
||||
/// Entry that executes sequentially.
|
||||
Sequence,
|
||||
/// Entry that groups multiple entries.
|
||||
Group,
|
||||
Sequence(SequenceEntry),
|
||||
Group(GroupEntry),
|
||||
}
|
||||
|
||||
/// Conditions that can modify whether loot entries are applied.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum LootCondition {
|
||||
/// Inverts the result of another condition.
|
||||
Inverted,
|
||||
@ -75,7 +82,7 @@ pub enum LootCondition {
|
||||
/// Passes only if all the given conditions are met.
|
||||
AllOf,
|
||||
/// Passes based on a random chance.
|
||||
RandomChance,
|
||||
RandomChance { chance: f32 },
|
||||
/// Passes based on a random chance modified by item enchantments.
|
||||
RandomChanceWithEnchantedBonus,
|
||||
/// Checks properties of the entity (e.g. type, attributes).
|
||||
@ -181,7 +188,7 @@ pub enum LootFunctionBonusParameter {
|
||||
}
|
||||
|
||||
/// Single entry in a loot pool with optional conditions and functions.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LootPoolEntry {
|
||||
/// The type of entry.
|
||||
pub content: LootPoolEntryTypes,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::tag;
|
||||
use pumpkin_data::{Block, BlockState, item::Item};
|
||||
use pumpkin_util::{
|
||||
loot_table::{
|
||||
@ -168,7 +169,33 @@ impl LootPoolEntryTypesExt for LootPoolEntryTypes {
|
||||
}
|
||||
Self::LootTable => todo!(),
|
||||
Self::Dynamic => todo!(),
|
||||
Self::Tag => todo!(),
|
||||
Self::Tag(tag) => {
|
||||
let key = tag.name.strip_prefix("minecraft:").unwrap_or(tag.name);
|
||||
|
||||
let items = pumpkin_data::tag::get_tag_values(tag::RegistryKey::Item, key)
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.filter_map(|registry_key| {
|
||||
let item_key = registry_key
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(registry_key);
|
||||
Item::from_registry_key(item_key)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if items.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
if tag.expand {
|
||||
// Pick one random item from the tag
|
||||
let index = rand::random_range(0..items.len() as i32) as usize;
|
||||
vec![ItemStack::new(1, items[index])]
|
||||
} else {
|
||||
// Yield one stack of every item in the tag
|
||||
items.iter().map(|&item| ItemStack::new(1, item)).collect()
|
||||
}
|
||||
}
|
||||
Self::Alternatives(alternative_entry) => {
|
||||
for entry in alternative_entry.children {
|
||||
if let Some(loot) = entry.get_loot(params) {
|
||||
@ -177,8 +204,35 @@ impl LootPoolEntryTypesExt for LootPoolEntryTypes {
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
Self::Sequence => todo!(),
|
||||
Self::Group => todo!(),
|
||||
Self::Sequence(sequence_entry) => {
|
||||
let mut stacks = Vec::new();
|
||||
for entry in sequence_entry.children {
|
||||
if entry
|
||||
.conditions
|
||||
.as_ref()
|
||||
.is_some_and(|c| !c.iter().all(|cond| cond.is_fulfilled(params)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
match entry.get_loot(params) {
|
||||
Some(loot) => stacks.extend(loot),
|
||||
// get_loot returning None also signals failure — stop.
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
stacks
|
||||
}
|
||||
|
||||
Self::Group(group_entry) => {
|
||||
let mut stacks = Vec::new();
|
||||
for entry in group_entry.children {
|
||||
if let Some(loot) = entry.get_loot(params) {
|
||||
stacks.extend(loot);
|
||||
}
|
||||
}
|
||||
stacks
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -197,6 +251,7 @@ impl LootConditionExt for LootCondition {
|
||||
}
|
||||
true
|
||||
}
|
||||
Self::RandomChance { chance } => rand::rng().random::<f32>() < *chance,
|
||||
Self::KilledByPlayer => params.killed_by_player.unwrap_or(false),
|
||||
Self::BlockStateProperty {
|
||||
block: _,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user