From f16d4a6247be8543819f9857eee7a2cfad5d7bc5 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sat, 21 Jun 2025 16:38:05 +0200 Subject: [PATCH] Add Entity loot not done, but works --- pumpkin-data/build/block.rs | 500 +----------------------- pumpkin-data/build/build.rs | 1 + pumpkin-data/build/entity_type.rs | 14 + pumpkin-data/build/loot.rs | 511 +++++++++++++++++++++++++ pumpkin-util/src/loot_table.rs | 141 ++++++- pumpkin/src/block/loot.rs | 78 ++-- pumpkin/src/block/mod.rs | 30 +- pumpkin/src/command/commands/summon.rs | 2 +- pumpkin/src/entity/ai/path/mod.rs | 16 +- pumpkin/src/entity/living.rs | 15 + pumpkin/src/entity/mob/mod.rs | 25 +- pumpkin/src/entity/mob/zombie.rs | 25 +- pumpkin/src/net/packet/play.rs | 2 +- pumpkin/src/world/explosion.rs | 12 +- pumpkin/src/world/mod.rs | 25 +- 15 files changed, 817 insertions(+), 580 deletions(-) create mode 100644 pumpkin-data/build/loot.rs diff --git a/pumpkin-data/build/block.rs b/pumpkin-data/build/block.rs index 8fbbad725..2ef76d8c1 100644 --- a/pumpkin-data/build/block.rs +++ b/pumpkin-data/build/block.rs @@ -9,6 +9,8 @@ use std::{ }; use syn::{Ident, LitInt, LitStr}; +use crate::loot::LootTableStruct; + fn const_block_name_from_block_name(block: &str) -> String { block.to_shouty_snake_case() } @@ -469,486 +471,6 @@ impl ToTokens for BlockStateRef { } } -/// These are required to be defined twice because serde can't deseralize into static context for obvious reasons. -#[derive(Deserialize, Clone, Debug)] -pub struct LootTableStruct { - r#type: LootTableTypeStruct, - random_sequence: Option, - pools: Option>, -} - -impl ToTokens for LootTableStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let loot_table_type = self.r#type.to_token_stream(); - let random_sequence = match &self.random_sequence { - Some(seq) => quote! { Some(#seq) }, - None => quote! { None }, - }; - let pools = match &self.pools { - Some(pools) => { - let pool_tokens: Vec<_> = pools.iter().map(|pool| pool.to_token_stream()).collect(); - quote! { Some(&[#(#pool_tokens),*]) } - } - None => quote! { None }, - }; - - tokens.extend(quote! { - LootTable { - r#type: #loot_table_type, - random_sequence: #random_sequence, - pools: #pools, - } - }); - } -} - -#[derive(Deserialize, Clone, Debug)] -pub struct LootPoolStruct { - entries: Vec, - rolls: f32, // TODO - bonus_rolls: f32, -} - -impl ToTokens for LootPoolStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let entries_tokens: Vec<_> = self - .entries - .iter() - .map(|entry| entry.to_token_stream()) - .collect(); - let rolls = &self.rolls; - let bonus_rolls = &self.bonus_rolls; - - tokens.extend(quote! { - LootPool { - entries: &[#(#entries_tokens),*], - rolls: #rolls, - bonus_rolls: #bonus_rolls, - } - }); - } -} - -#[derive(Deserialize, Clone, Debug)] -pub struct ItemEntryStruct { - name: String, -} - -impl ToTokens for ItemEntryStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = LitStr::new(&self.name, Span::call_site()); - - tokens.extend(quote! { - ItemEntry { - name: #name, - } - }); - } -} - -#[derive(Deserialize, Clone, Debug)] -pub struct AlternativeEntryStruct { - children: Vec, -} - -impl ToTokens for AlternativeEntryStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let children = self.children.iter().map(|entry| entry.to_token_stream()); - - tokens.extend(quote! { - AlternativeEntry { - children: &[#(#children),*], - } - }); - } -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(tag = "type")] -pub enum LootPoolEntryTypesStruct { - #[serde(rename = "minecraft:empty")] - Empty, - #[serde(rename = "minecraft:item")] - Item(ItemEntryStruct), - #[serde(rename = "minecraft:loot_table")] - LootTable, - #[serde(rename = "minecraft:dynamic")] - Dynamic, - #[serde(rename = "minecraft:tag")] - Tag, - #[serde(rename = "minecraft:alternatives")] - Alternatives(AlternativeEntryStruct), - #[serde(rename = "minecraft:sequence")] - Sequence, - #[serde(rename = "minecraft:group")] - Group, -} - -impl ToTokens for LootPoolEntryTypesStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - LootPoolEntryTypesStruct::Empty => { - tokens.extend(quote! { LootPoolEntryTypes::Empty }); - } - LootPoolEntryTypesStruct::Item(item) => { - tokens.extend(quote! { LootPoolEntryTypes::Item(#item) }); - } - LootPoolEntryTypesStruct::LootTable => { - tokens.extend(quote! { LootPoolEntryTypes::LootTable }); - } - LootPoolEntryTypesStruct::Dynamic => { - tokens.extend(quote! { LootPoolEntryTypes::Dynamic }); - } - LootPoolEntryTypesStruct::Tag => { - tokens.extend(quote! { LootPoolEntryTypes::Tag }); - } - LootPoolEntryTypesStruct::Alternatives(alt) => { - tokens.extend(quote! { LootPoolEntryTypes::Alternatives(#alt) }); - } - LootPoolEntryTypesStruct::Sequence => { - tokens.extend(quote! { LootPoolEntryTypes::Sequence }); - } - LootPoolEntryTypesStruct::Group => { - tokens.extend(quote! { LootPoolEntryTypes::Group }); - } - } - } -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(tag = "condition")] -pub enum LootConditionStruct { - #[serde(rename = "minecraft:inverted")] - Inverted, - #[serde(rename = "minecraft:any_of")] - AnyOf, - #[serde(rename = "minecraft:all_of")] - AllOf, - #[serde(rename = "minecraft:random_chance")] - RandomChance, - #[serde(rename = "minecraft:random_chance_with_enchanted_bonus")] - RandomChanceWithEnchantedBonus, - #[serde(rename = "minecraft:entity_properties")] - EntityProperties, - #[serde(rename = "minecraft:killed_by_player")] - KilledByPlayer, - #[serde(rename = "minecraft:entity_scores")] - EntityScores, - #[serde(rename = "minecraft:block_state_property")] - BlockStateProperty { - block: String, - properties: HashMap, - }, - #[serde(rename = "minecraft:match_tool")] - MatchTool, - #[serde(rename = "minecraft:table_bonus")] - TableBonus, - #[serde(rename = "minecraft:survives_explosion")] - SurvivesExplosion, - #[serde(rename = "minecraft:damage_source_properties")] - DamageSourceProperties, - #[serde(rename = "minecraft:location_check")] - LocationCheck, - #[serde(rename = "minecraft:weather_check")] - WeatherCheck, - #[serde(rename = "minecraft:reference")] - Reference, - #[serde(rename = "minecraft:time_check")] - TimeCheck, - #[serde(rename = "minecraft:value_check")] - ValueCheck, - #[serde(rename = "minecraft:enchantment_active_check")] - EnchantmentActiveCheck, -} - -impl ToTokens for LootConditionStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = match self { - LootConditionStruct::Inverted => quote! { LootCondition::Inverted }, - LootConditionStruct::AnyOf => quote! { LootCondition::AnyOf }, - LootConditionStruct::AllOf => quote! { LootCondition::AllOf }, - LootConditionStruct::RandomChance => quote! { LootCondition::RandomChance }, - LootConditionStruct::RandomChanceWithEnchantedBonus => { - quote! { LootCondition::RandomChanceWithEnchantedBonus } - } - LootConditionStruct::EntityProperties => quote! { LootCondition::EntityProperties }, - LootConditionStruct::KilledByPlayer => quote! { LootCondition::KilledByPlayer }, - LootConditionStruct::EntityScores => quote! { LootCondition::EntityScores }, - LootConditionStruct::BlockStateProperty { block, properties } => { - let properties: Vec<_> = properties - .iter() - .map(|(k, v)| quote! { (#k, #v) }) - .collect(); - quote! { LootCondition::BlockStateProperty { block: #block, properties: &[#(#properties),*] } } - } - LootConditionStruct::MatchTool => quote! { LootCondition::MatchTool }, - LootConditionStruct::TableBonus => quote! { LootCondition::TableBonus }, - LootConditionStruct::SurvivesExplosion => quote! { LootCondition::SurvivesExplosion }, - LootConditionStruct::DamageSourceProperties => { - quote! { LootCondition::DamageSourceProperties } - } - LootConditionStruct::LocationCheck => quote! { LootCondition::LocationCheck }, - LootConditionStruct::WeatherCheck => quote! { LootCondition::WeatherCheck }, - LootConditionStruct::Reference => quote! { LootCondition::Reference }, - LootConditionStruct::TimeCheck => quote! { LootCondition::TimeCheck }, - LootConditionStruct::ValueCheck => quote! { LootCondition::ValueCheck }, - LootConditionStruct::EnchantmentActiveCheck => { - quote! { LootCondition::EnchantmentActiveCheck } - } - }; - - tokens.extend(name); - } -} - -#[derive(Deserialize, Clone, Debug)] -pub struct LootFunctionStruct { - #[serde(flatten)] - content: LootFunctionTypesStruct, - conditions: Option>, -} - -impl ToTokens for LootFunctionStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let functions_tokens = &self.content.to_token_stream(); - - let conditions_tokens = match &self.conditions { - Some(conds) => { - let cond_tokens: Vec<_> = conds.iter().map(|c| c.to_token_stream()).collect(); - quote! { Some(&[#(#cond_tokens),*]) } - } - None => quote! { None }, - }; - - tokens.extend(quote! { - LootFunction { - content: #functions_tokens, - conditions: #conditions_tokens, - } - }); - } -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(tag = "function")] -pub enum LootFunctionTypesStruct { - #[serde(rename = "minecraft:set_count")] - SetCount { - count: LootFunctionNumberProviderStruct, - add: Option, - }, - #[serde(rename = "minecraft:limit_count")] - LimitCount { limit: LootFunctionLimitCountStruct }, - #[serde(rename = "minecraft:apply_bonus")] - ApplyBonus { - enchantment: String, - formula: String, - parameters: Option, - }, - #[serde(rename = "minecraft:copy_components")] - CopyComponents { - source: String, - include: Vec, - }, - #[serde(rename = "minecraft:copy_state")] - CopyState { - block: String, - properties: Vec, - }, - #[serde(rename = "minecraft:explosion_decay")] - ExplosionDecay, -} - -impl ToTokens for LootFunctionTypesStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = match self { - LootFunctionTypesStruct::SetCount { count, add } => { - let count = count.to_token_stream(); - let add = add.unwrap_or(false); - quote! { LootFunctionTypes::SetCount { count: #count, add: #add } } - } - LootFunctionTypesStruct::LimitCount { limit } => { - let min = match limit.min { - Some(min) => quote! { Some(#min) }, - None => quote! { None }, - }; - let max = match limit.max { - Some(max) => quote! { Some(#max) }, - None => quote! { None }, - }; - quote! { LootFunctionTypes::LimitCount { min: #min, max: #max } } - } - LootFunctionTypesStruct::ApplyBonus { - enchantment, - formula, - parameters, - } => { - let parameters = match parameters { - Some(params) => { - let params = params.to_token_stream(); - quote! { Some(#params) } - } - None => quote! { None }, - }; - - quote! { - LootFunctionTypes::ApplyBonus { - enchantment: #enchantment, - formula: #formula, - parameters: #parameters, - } - } - } - LootFunctionTypesStruct::CopyComponents { source, include } => { - quote! { - LootFunctionTypes::CopyComponents { - source: #source, - include: &[#(#include),*], - } - } - } - LootFunctionTypesStruct::CopyState { block, properties } => { - quote! { - LootFunctionTypes::CopyState { - block: #block, - properties: &[#(#properties),*], - } - } - } - LootFunctionTypesStruct::ExplosionDecay => { - quote! { LootFunctionTypes::ExplosionDecay } - } - }; - - tokens.extend(name); - } -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(tag = "type")] -pub enum LootFunctionNumberProviderStruct { - #[serde(rename = "minecraft:uniform")] - Uniform { min: f32, max: f32 }, - #[serde(rename = "minecraft:binomial")] - Binomial { n: f32, p: f32 }, - #[serde(rename = "minecraft:constant", untagged)] - Constant(f32), -} - -impl ToTokens for LootFunctionNumberProviderStruct { - 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); - } -} - -#[derive(Deserialize, Clone, Debug)] -pub struct LootFunctionLimitCountStruct { - min: Option, - max: Option, -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(untagged)] -pub enum LootFunctionBonusParameterStruct { - Multiplier { - #[serde(rename = "bonusMultiplier")] - bonus_multiplier: i32, - }, - Probability { - extra: i32, - probability: f32, - }, -} - -impl ToTokens for LootFunctionBonusParameterStruct { - 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); - } -} - -#[derive(Deserialize, Clone, Debug)] -pub struct LootPoolEntryStruct { - #[serde(flatten)] - content: LootPoolEntryTypesStruct, - conditions: Option>, - functions: Option>, -} - -impl ToTokens for LootPoolEntryStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let content = &self.content; - let conditions_tokens = match &self.conditions { - Some(conds) => { - let cond_tokens: Vec<_> = conds.iter().map(|c| c.to_token_stream()).collect(); - quote! { Some(&[#(#cond_tokens),*]) } - } - None => quote! { None }, - }; - let functions_tokens = match &self.functions { - Some(fns) => { - let cond_tokens: Vec<_> = fns.iter().map(|c| c.to_token_stream()).collect(); - quote! { Some(&[#(#cond_tokens),*]) } - } - None => quote! { None }, - }; - - tokens.extend(quote! { - LootPoolEntry { - content: #content, - conditions: #conditions_tokens, - functions: #functions_tokens, - } - }); - } -} - -#[derive(Deserialize, Clone, Debug)] -#[serde(rename = "snake_case")] -pub enum LootTableTypeStruct { - #[serde(rename = "minecraft:empty")] - /// Nothing will be dropped. - Empty, - #[serde(rename = "minecraft:block")] - /// A block will be dropped. - Block, - #[serde(rename = "minecraft:chest")] - /// An item will be dropped. - Chest, -} - -impl ToTokens for LootTableTypeStruct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let name = match self { - LootTableTypeStruct::Empty => quote! { LootTableType::Empty }, - LootTableTypeStruct::Block => quote! { LootTableType::Block }, - LootTableTypeStruct::Chest => quote! { LootTableType::Chest }, - }; - - tokens.extend(name); - } -} - #[derive(Deserialize, Clone, Debug)] pub struct Block { pub id: u16, @@ -1404,21 +926,19 @@ pub(crate) fn build() -> TokenStream { pub fn get_block_collision_shapes(state_id: u16) -> Option> { let state = get_state_by_state_id(state_id)?; - let mut shapes: Vec = vec![]; - for i in 0..state.collision_shapes.len() { - let shape = &COLLISION_SHAPES[state.collision_shapes[i] as usize]; - shapes.push(*shape); - } + let shapes: Vec = state.collision_shapes + .iter() + .map(|&id| COLLISION_SHAPES[id as usize]) + .collect(); Some(shapes) } pub fn get_block_outline_shapes(state_id: u16) -> Option> { let state = get_state_by_state_id(state_id)?; - let mut shapes: Vec = vec![]; - for i in 0..state.outline_shapes.len() { - let shape = &COLLISION_SHAPES[state.outline_shapes[i] as usize]; - shapes.push(*shape); - } + let mut shapes: Vec = state.outline_shapes + .iter() + .map(|&id| COLLISION_SHAPES[id as usize]) + .collect(); let block = get_block_by_state_id(state_id)?; if block.properties(state.id).and_then(|properties| { properties diff --git a/pumpkin-data/build/build.rs b/pumpkin-data/build/build.rs index eec66566c..6216ed84f 100644 --- a/pumpkin-data/build/build.rs +++ b/pumpkin-data/build/build.rs @@ -14,6 +14,7 @@ mod entity_type; mod fluid; mod game_event; mod item; +pub mod loot; mod message_type; mod noise_parameter; mod noise_router; diff --git a/pumpkin-data/build/entity_type.rs b/pumpkin-data/build/entity_type.rs index 9af264b06..c3b97985b 100644 --- a/pumpkin-data/build/entity_type.rs +++ b/pumpkin-data/build/entity_type.rs @@ -6,11 +6,14 @@ use quote::{ToTokens, format_ident, quote}; use serde::Deserialize; use syn::LitInt; +use crate::loot::LootTableStruct; + #[derive(Deserialize)] pub struct EntityType { pub id: u16, pub max_health: Option, pub attackable: Option, + pub loot_table: Option, pub summonable: bool, pub fire_immune: bool, pub dimension: [f32; 2], @@ -79,6 +82,14 @@ impl ToTokens for NamedEntityType<'_> { let dimension0 = entity.dimension[0]; let dimension1 = entity.dimension[1]; + let loot_table = match &entity.loot_table { + Some(table) => { + let table_tokens = table.to_token_stream(); + quote! { Some(#table_tokens) } + } + None => quote! { None }, + }; + tokens.extend(quote! { EntityType { id: #id, @@ -86,6 +97,7 @@ impl ToTokens for NamedEntityType<'_> { attackable: #attackable, summonable: #summonable, fire_immune: #fire_immune, + loot_table: #loot_table, dimension: [#dimension0, #dimension1], // Correctly construct the array eye_height: #eye_height, spawn_restriction: #spawn_restriction, @@ -126,6 +138,7 @@ pub(crate) fn build() -> TokenStream { }); } quote! { + use pumpkin_util::loot_table::*; use pumpkin_util::HeightMap; #[derive(Clone, Copy, Debug, PartialEq)] @@ -135,6 +148,7 @@ pub(crate) fn build() -> TokenStream { pub attackable: Option, pub summonable: bool, pub fire_immune: bool, + pub loot_table: Option, pub dimension: [f32; 2], pub eye_height: f32, pub spawn_restriction: SpawnRestriction, diff --git a/pumpkin-data/build/loot.rs b/pumpkin-data/build/loot.rs new file mode 100644 index 000000000..e1438f92e --- /dev/null +++ b/pumpkin-data/build/loot.rs @@ -0,0 +1,511 @@ +use std::collections::HashMap; + +use proc_macro2::{Span, TokenStream}; +use pumpkin_util::loot_table::LootNumberProviderTypes; +use quote::{ToTokens, quote}; +use serde::Deserialize; +use syn::LitStr; + +/// These are required to be defined twice because serde can't deseralize into static context for obvious reasons. +#[derive(Deserialize, Clone, Debug)] +pub struct LootTableStruct { + r#type: LootTableTypeStruct, + random_sequence: Option, + pools: Option>, +} + +impl ToTokens for LootTableStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let loot_table_type = self.r#type.to_token_stream(); + let random_sequence = match &self.random_sequence { + Some(seq) => quote! { Some(#seq) }, + None => quote! { None }, + }; + let pools = match &self.pools { + Some(pools) => { + let pool_tokens: Vec<_> = pools.iter().map(|pool| pool.to_token_stream()).collect(); + quote! { Some(&[#(#pool_tokens),*]) } + } + None => quote! { None }, + }; + + tokens.extend(quote! { + LootTable { + r#type: #loot_table_type, + random_sequence: #random_sequence, + pools: #pools, + } + }); + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct LootPoolStruct { + entries: Vec, + rolls: LootNumberProviderTypes, // TODO + bonus_rolls: f32, +} + +impl ToTokens for LootPoolStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let entries_tokens: Vec<_> = self + .entries + .iter() + .map(|entry| entry.to_token_stream()) + .collect(); + let rolls = &self.rolls; + let bonus_rolls = &self.bonus_rolls; + + tokens.extend(quote! { + LootPool { + entries: &[#(#entries_tokens),*], + rolls: #rolls, + bonus_rolls: #bonus_rolls, + } + }); + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct ItemEntryStruct { + name: String, +} + +impl ToTokens for ItemEntryStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = LitStr::new(&self.name, Span::call_site()); + + tokens.extend(quote! { + ItemEntry { + name: #name, + } + }); + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct AlternativeEntryStruct { + children: Vec, +} + +impl ToTokens for AlternativeEntryStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let children = self.children.iter().map(|entry| entry.to_token_stream()); + + tokens.extend(quote! { + AlternativeEntry { + children: &[#(#children),*], + } + }); + } +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(tag = "type")] +pub enum LootPoolEntryTypesStruct { + #[serde(rename = "minecraft:empty")] + Empty, + #[serde(rename = "minecraft:item")] + Item(ItemEntryStruct), + #[serde(rename = "minecraft:loot_table")] + LootTable, + #[serde(rename = "minecraft:dynamic")] + Dynamic, + #[serde(rename = "minecraft:tag")] + Tag, + #[serde(rename = "minecraft:alternatives")] + Alternatives(AlternativeEntryStruct), + #[serde(rename = "minecraft:sequence")] + Sequence, + #[serde(rename = "minecraft:group")] + Group, +} + +impl ToTokens for LootPoolEntryTypesStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + LootPoolEntryTypesStruct::Empty => { + tokens.extend(quote! { LootPoolEntryTypes::Empty }); + } + LootPoolEntryTypesStruct::Item(item) => { + tokens.extend(quote! { LootPoolEntryTypes::Item(#item) }); + } + LootPoolEntryTypesStruct::LootTable => { + tokens.extend(quote! { LootPoolEntryTypes::LootTable }); + } + LootPoolEntryTypesStruct::Dynamic => { + tokens.extend(quote! { LootPoolEntryTypes::Dynamic }); + } + LootPoolEntryTypesStruct::Tag => { + tokens.extend(quote! { LootPoolEntryTypes::Tag }); + } + LootPoolEntryTypesStruct::Alternatives(alt) => { + tokens.extend(quote! { LootPoolEntryTypes::Alternatives(#alt) }); + } + LootPoolEntryTypesStruct::Sequence => { + tokens.extend(quote! { LootPoolEntryTypes::Sequence }); + } + LootPoolEntryTypesStruct::Group => { + tokens.extend(quote! { LootPoolEntryTypes::Group }); + } + } + } +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(tag = "condition")] +pub enum LootConditionStruct { + #[serde(rename = "minecraft:inverted")] + Inverted, + #[serde(rename = "minecraft:any_of")] + AnyOf, + #[serde(rename = "minecraft:all_of")] + AllOf, + #[serde(rename = "minecraft:random_chance")] + RandomChance, + #[serde(rename = "minecraft:random_chance_with_enchanted_bonus")] + RandomChanceWithEnchantedBonus, + #[serde(rename = "minecraft:entity_properties")] + EntityProperties, + #[serde(rename = "minecraft:killed_by_player")] + KilledByPlayer, + #[serde(rename = "minecraft:entity_scores")] + EntityScores, + #[serde(rename = "minecraft:block_state_property")] + BlockStateProperty { + block: String, + properties: HashMap, + }, + #[serde(rename = "minecraft:match_tool")] + MatchTool, + #[serde(rename = "minecraft:table_bonus")] + TableBonus, + #[serde(rename = "minecraft:survives_explosion")] + SurvivesExplosion, + #[serde(rename = "minecraft:damage_source_properties")] + DamageSourceProperties, + #[serde(rename = "minecraft:location_check")] + LocationCheck, + #[serde(rename = "minecraft:weather_check")] + WeatherCheck, + #[serde(rename = "minecraft:reference")] + Reference, + #[serde(rename = "minecraft:time_check")] + TimeCheck, + #[serde(rename = "minecraft:value_check")] + ValueCheck, + #[serde(rename = "minecraft:enchantment_active_check")] + EnchantmentActiveCheck, +} + +impl ToTokens for LootConditionStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = match self { + LootConditionStruct::Inverted => quote! { LootCondition::Inverted }, + LootConditionStruct::AnyOf => quote! { LootCondition::AnyOf }, + LootConditionStruct::AllOf => quote! { LootCondition::AllOf }, + LootConditionStruct::RandomChance => quote! { LootCondition::RandomChance }, + LootConditionStruct::RandomChanceWithEnchantedBonus => { + quote! { LootCondition::RandomChanceWithEnchantedBonus } + } + LootConditionStruct::EntityProperties => quote! { LootCondition::EntityProperties }, + LootConditionStruct::KilledByPlayer => quote! { LootCondition::KilledByPlayer }, + LootConditionStruct::EntityScores => quote! { LootCondition::EntityScores }, + LootConditionStruct::BlockStateProperty { block, properties } => { + let properties: Vec<_> = properties + .iter() + .map(|(k, v)| quote! { (#k, #v) }) + .collect(); + quote! { LootCondition::BlockStateProperty { block: #block, properties: &[#(#properties),*] } } + } + LootConditionStruct::MatchTool => quote! { LootCondition::MatchTool }, + LootConditionStruct::TableBonus => quote! { LootCondition::TableBonus }, + LootConditionStruct::SurvivesExplosion => quote! { LootCondition::SurvivesExplosion }, + LootConditionStruct::DamageSourceProperties => { + quote! { LootCondition::DamageSourceProperties } + } + LootConditionStruct::LocationCheck => quote! { LootCondition::LocationCheck }, + LootConditionStruct::WeatherCheck => quote! { LootCondition::WeatherCheck }, + LootConditionStruct::Reference => quote! { LootCondition::Reference }, + LootConditionStruct::TimeCheck => quote! { LootCondition::TimeCheck }, + LootConditionStruct::ValueCheck => quote! { LootCondition::ValueCheck }, + LootConditionStruct::EnchantmentActiveCheck => { + quote! { LootCondition::EnchantmentActiveCheck } + } + }; + + tokens.extend(name); + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct LootFunctionStruct { + #[serde(flatten)] + content: LootFunctionTypesStruct, + conditions: Option>, +} + +impl ToTokens for LootFunctionStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let functions_tokens = &self.content.to_token_stream(); + + let conditions_tokens = match &self.conditions { + Some(conds) => { + let cond_tokens: Vec<_> = conds.iter().map(|c| c.to_token_stream()).collect(); + quote! { Some(&[#(#cond_tokens),*]) } + } + None => quote! { None }, + }; + + tokens.extend(quote! { + LootFunction { + content: #functions_tokens, + conditions: #conditions_tokens, + } + }); + } +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(tag = "function")] +pub enum LootFunctionTypesStruct { + #[serde(rename = "minecraft:set_count")] + SetCount { + count: LootFunctionNumberProviderStruct, + add: Option, + }, + #[serde(rename = "minecraft:enchanted_count_increase")] + EnchantedCountIncrease, + #[serde(rename = "minecraft:furnace_smelt")] + FurnaceSmelt, + #[serde(rename = "minecraft:set_potion")] + SetPotion, + #[serde(rename = "minecraft:set_ominous_bottle_amplifier")] + SetOminousBottleAmplifier, + #[serde(rename = "minecraft:limit_count")] + LimitCount { limit: LootFunctionLimitCountStruct }, + #[serde(rename = "minecraft:apply_bonus")] + ApplyBonus { + enchantment: String, + formula: String, + parameters: Option, + }, + #[serde(rename = "minecraft:copy_components")] + CopyComponents { + source: String, + include: Vec, + }, + #[serde(rename = "minecraft:copy_state")] + CopyState { + block: String, + properties: Vec, + }, + #[serde(rename = "minecraft:explosion_decay")] + ExplosionDecay, +} + +impl ToTokens for LootFunctionTypesStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = match self { + LootFunctionTypesStruct::SetCount { count, add } => { + let count = count.to_token_stream(); + let add = add.unwrap_or(false); + quote! { LootFunctionTypes::SetCount { count: #count, add: #add } } + } + LootFunctionTypesStruct::SetOminousBottleAmplifier => { + quote! { LootFunctionTypes::SetOminousBottleAmplifier } + } + LootFunctionTypesStruct::FurnaceSmelt => { + quote! { LootFunctionTypes::FurnaceSmelt } + } + LootFunctionTypesStruct::SetPotion => { + quote! { LootFunctionTypes::SetPotion } + } + LootFunctionTypesStruct::EnchantedCountIncrease => { + quote! { LootFunctionTypes::EnchantedCountIncrease } + } + LootFunctionTypesStruct::LimitCount { limit } => { + let min = match limit.min { + Some(min) => quote! { Some(#min) }, + None => quote! { None }, + }; + let max = match limit.max { + Some(max) => quote! { Some(#max) }, + None => quote! { None }, + }; + quote! { LootFunctionTypes::LimitCount { min: #min, max: #max } } + } + LootFunctionTypesStruct::ApplyBonus { + enchantment, + formula, + parameters, + } => { + let parameters = match parameters { + Some(params) => { + let params = params.to_token_stream(); + quote! { Some(#params) } + } + None => quote! { None }, + }; + + quote! { + LootFunctionTypes::ApplyBonus { + enchantment: #enchantment, + formula: #formula, + parameters: #parameters, + } + } + } + LootFunctionTypesStruct::CopyComponents { source, include } => { + quote! { + LootFunctionTypes::CopyComponents { + source: #source, + include: &[#(#include),*], + } + } + } + LootFunctionTypesStruct::CopyState { block, properties } => { + quote! { + LootFunctionTypes::CopyState { + block: #block, + properties: &[#(#properties),*], + } + } + } + LootFunctionTypesStruct::ExplosionDecay => { + quote! { LootFunctionTypes::ExplosionDecay } + } + }; + + tokens.extend(name); + } +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(tag = "type")] +pub enum LootFunctionNumberProviderStruct { + #[serde(rename = "minecraft:uniform")] + Uniform { min: f32, max: f32 }, + #[serde(rename = "minecraft:binomial")] + Binomial { n: f32, p: f32 }, + #[serde(rename = "minecraft:constant", untagged)] + Constant(f32), +} + +impl ToTokens for LootFunctionNumberProviderStruct { + 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); + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct LootFunctionLimitCountStruct { + min: Option, + max: Option, +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(untagged)] +pub enum LootFunctionBonusParameterStruct { + Multiplier { + #[serde(rename = "bonusMultiplier")] + bonus_multiplier: i32, + }, + Probability { + extra: i32, + probability: f32, + }, +} + +impl ToTokens for LootFunctionBonusParameterStruct { + 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); + } +} + +#[derive(Deserialize, Clone, Debug)] +pub struct LootPoolEntryStruct { + #[serde(flatten)] + content: LootPoolEntryTypesStruct, + conditions: Option>, + functions: Option>, +} + +impl ToTokens for LootPoolEntryStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let content = &self.content; + let conditions_tokens = match &self.conditions { + Some(conds) => { + let cond_tokens: Vec<_> = conds.iter().map(|c| c.to_token_stream()).collect(); + quote! { Some(&[#(#cond_tokens),*]) } + } + None => quote! { None }, + }; + let functions_tokens = match &self.functions { + Some(fns) => { + let cond_tokens: Vec<_> = fns.iter().map(|c| c.to_token_stream()).collect(); + quote! { Some(&[#(#cond_tokens),*]) } + } + None => quote! { None }, + }; + + tokens.extend(quote! { + LootPoolEntry { + content: #content, + conditions: #conditions_tokens, + functions: #functions_tokens, + } + }); + } +} + +#[derive(Deserialize, Clone, Debug)] +#[serde(rename = "snake_case")] +pub enum LootTableTypeStruct { + #[serde(rename = "minecraft:empty")] + /// Nothing will be dropped. + Empty, + #[serde(rename = "minecraft:entity")] + /// The Entity loot will be dropped. + Entity, + #[serde(rename = "minecraft:block")] + /// A block will be dropped. + Block, + #[serde(rename = "minecraft:chest")] + /// An item will be dropped. + Chest, +} + +impl ToTokens for LootTableTypeStruct { + fn to_tokens(&self, tokens: &mut TokenStream) { + let name = match self { + LootTableTypeStruct::Empty => quote! { LootTableType::Empty }, + LootTableTypeStruct::Entity => quote! { LootTableType::Entity }, + LootTableTypeStruct::Block => quote! { LootTableType::Block }, + LootTableTypeStruct::Chest => quote! { LootTableType::Chest }, + }; + + tokens.extend(name); + } +} diff --git a/pumpkin-util/src/loot_table.rs b/pumpkin-util/src/loot_table.rs index c3284b535..461d99033 100644 --- a/pumpkin-util/src/loot_table.rs +++ b/pumpkin-util/src/loot_table.rs @@ -1,28 +1,34 @@ -#[derive(Clone, Debug)] +use proc_macro2::TokenStream; +use quote::{ToTokens, quote}; +use serde::Deserialize; + +use crate::random::RandomImpl; + +#[derive(Clone, Copy, PartialEq, Debug)] pub struct LootTable { pub r#type: LootTableType, pub random_sequence: Option<&'static str>, pub pools: Option<&'static [LootPool]>, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct LootPool { pub entries: &'static [LootPoolEntry], - pub rolls: f32, + pub rolls: LootNumberProviderTypes, pub bonus_rolls: f32, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct ItemEntry { pub name: &'static str, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct AlternativeEntry { pub children: &'static [LootPoolEntry], } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum LootPoolEntryTypes { Empty, Item(ItemEntry), @@ -34,7 +40,7 @@ pub enum LootPoolEntryTypes { Group, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum LootCondition { Inverted, AnyOf, @@ -60,18 +66,22 @@ pub enum LootCondition { EnchantmentActiveCheck, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct LootFunction { pub content: LootFunctionTypes, pub conditions: Option<&'static [LootCondition]>, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum LootFunctionTypes { SetCount { count: LootFunctionNumberProvider, add: bool, }, + EnchantedCountIncrease, + FurnaceSmelt, + SetPotion, + SetOminousBottleAmplifier, LimitCount { min: Option, max: Option, @@ -92,32 +102,131 @@ pub enum LootFunctionTypes { ExplosionDecay, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum LootFunctionNumberProvider { Constant { value: f32 }, Uniform { min: f32, max: f32 }, Binomial { n: f32, p: f32 }, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub enum LootFunctionBonusParameter { Multiplier { bonus_multiplier: i32 }, Probability { extra: i32, probability: f32 }, } -#[derive(Clone, Debug)] +#[derive(Clone, PartialEq, Debug)] pub struct LootPoolEntry { pub content: LootPoolEntryTypes, pub conditions: Option<&'static [LootCondition]>, pub functions: Option<&'static [LootFunction]>, } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, PartialEq, Debug)] pub enum LootTableType { - /// Nothing will be dropped Empty, - /// A block will be dropped + Entity, Block, - /// An item will be dropped Chest, } + +#[derive(Deserialize, PartialEq, Clone, Copy, Debug)] +#[serde(tag = "type")] +pub enum LootNumberProviderTypesProvider { + #[serde(rename = "minecraft:uniform")] + Uniform(UniformLootNumberProvider), +} +impl ToTokens for LootNumberProviderTypesProvider { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + LootNumberProviderTypesProvider::Uniform(uniform) => { + tokens.extend(quote! { + LootNumberProviderTypesProvider::Uniform(#uniform) + }); + } + } + } +} + +#[derive(Deserialize, PartialEq, Clone, Copy, Debug)] +pub struct UniformLootNumberProvider { + pub min: f32, + pub max: f32, +} + +impl ToTokens for UniformLootNumberProvider { + fn to_tokens(&self, tokens: &mut TokenStream) { + let min_inclusive = self.min; + let max_inclusive = self.max; + + tokens.extend(quote! { + UniformLootNumberProvider { min: #min_inclusive, max: #max_inclusive } + }); + } +} + +impl UniformLootNumberProvider { + pub fn get_min(&self) -> f32 { + self.min + } + + pub fn get(&self, random: &mut impl RandomImpl) -> f32 { + // TODO + random.next_f32() + } + + pub fn get_max(&self) -> f32 { + self.max + } +} + +#[derive(Deserialize, PartialEq, Clone, Copy, Debug)] +#[serde(untagged)] +pub enum LootNumberProviderTypes { + Object(LootNumberProviderTypesProvider), + Constant(f32), +} + +impl ToTokens for LootNumberProviderTypes { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + LootNumberProviderTypes::Object(provider) => { + tokens.extend(quote! { + LootNumberProviderTypes::Object(#provider) + }); + } + LootNumberProviderTypes::Constant(i) => tokens.extend(quote! { + LootNumberProviderTypes::Constant(#i) + }), + } + } +} + +impl LootNumberProviderTypes { + pub fn get_min(&self) -> f32 { + match self { + LootNumberProviderTypes::Object(int_provider) => match int_provider { + LootNumberProviderTypesProvider::Uniform(uniform) => uniform.get_min(), + }, + LootNumberProviderTypes::Constant(i) => *i, + } + } + + pub fn get(&self, random: &mut impl RandomImpl) -> f32 { + match self { + LootNumberProviderTypes::Object(int_provider) => match int_provider { + LootNumberProviderTypesProvider::Uniform(uniform) => uniform.get(random), + }, + LootNumberProviderTypes::Constant(i) => *i, + } + } + + pub fn get_max(&self) -> f32 { + match self { + LootNumberProviderTypes::Object(int_provider) => match int_provider { + LootNumberProviderTypesProvider::Uniform(uniform) => uniform.get_max(), + }, + LootNumberProviderTypes::Constant(i) => *i, + } + } +} diff --git a/pumpkin/src/block/loot.rs b/pumpkin/src/block/loot.rs index 0b2391a2a..3e379fd7b 100644 --- a/pumpkin/src/block/loot.rs +++ b/pumpkin/src/block/loot.rs @@ -1,26 +1,40 @@ -use pumpkin_data::item::Item; -use pumpkin_util::loot_table::{ - LootCondition, LootFunctionNumberProvider, LootFunctionTypes, LootPoolEntry, - LootPoolEntryTypes, LootTable, +use pumpkin_data::{Block, BlockState, block_properties::get_block_by_state_id, item::Item}; +use pumpkin_util::{ + loot_table::{ + LootCondition, LootFunctionNumberProvider, LootFunctionTypes, LootPoolEntry, + LootPoolEntryTypes, LootTable, + }, + random::{RandomGenerator, xoroshiro128::Xoroshiro}, }; use pumpkin_world::item::ItemStack; use rand::Rng; -pub(super) trait LootTableExt { - fn get_loot(&self, block_props: &[(String, String)]) -> Vec; +#[derive(Default)] +pub struct LootContextParameters { + pub explosion_radius: Option, + pub block_state: Option, +} + +pub trait LootTableExt { + fn get_loot(&self, params: LootContextParameters) -> Vec; } impl LootTableExt for LootTable { - fn get_loot(&self, block_props: &[(String, String)]) -> Vec { + fn get_loot(&self, params: LootContextParameters) -> Vec { let mut stacks = Vec::new(); if let Some(pools) = self.pools { for pool in pools { - let rolls = pool.rolls.round() + pool.bonus_rolls.floor(); // TODO: multiply by luck + // TODO + let rolls = pool + .rolls + .get(&mut RandomGenerator::Xoroshiro(Xoroshiro::from_seed(123))) + .round() + + pool.bonus_rolls.floor(); // TODO: multiply by luck for _ in 0..(rolls as i32) { for entry in pool.entries { - if let Some(loot) = entry.get_loot(block_props) { + if let Some(loot) = entry.get_loot(¶ms) { stacks.extend(loot); } } @@ -33,23 +47,23 @@ impl LootTableExt for LootTable { } trait LootPoolEntryExt { - fn get_loot(&self, block_props: &[(String, String)]) -> Option>; + fn get_loot(&self, params: &LootContextParameters) -> Option>; } impl LootPoolEntryExt for LootPoolEntry { - fn get_loot(&self, block_props: &[(String, String)]) -> Option> { + fn get_loot(&self, params: &LootContextParameters) -> Option> { if let Some(conditions) = self.conditions { - if !conditions.iter().all(|cond| cond.is_fulfilled(block_props)) { + if !conditions.iter().all(|cond| cond.is_fulfilled(params)) { return None; } } - let mut stacks = self.content.get_stacks(block_props); + let mut stacks = self.content.get_stacks(params); if let Some(functions) = self.functions { for function in functions { if let Some(conditions) = function.conditions { - if !conditions.iter().all(|cond| cond.is_fulfilled(block_props)) { + if !conditions.iter().all(|cond| cond.is_fulfilled(params)) { continue; } } @@ -94,6 +108,10 @@ impl LootPoolEntryExt for LootPoolEntry { block: _, properties: _, } + | LootFunctionTypes::EnchantedCountIncrease + | LootFunctionTypes::SetOminousBottleAmplifier + | LootFunctionTypes::SetPotion + | LootFunctionTypes::FurnaceSmelt | LootFunctionTypes::ExplosionDecay => { // TODO: shouldnt crash here but needs to be implemented someday } @@ -106,11 +124,11 @@ impl LootPoolEntryExt for LootPoolEntry { } trait LootPoolEntryTypesExt { - fn get_stacks(&self, block_props: &[(String, String)]) -> Vec; + fn get_stacks(&self, params: &LootContextParameters) -> Vec; } impl LootPoolEntryTypesExt for LootPoolEntryTypes { - fn get_stacks(&self, block_props: &[(String, String)]) -> Vec { + fn get_stacks(&self, params: &LootContextParameters) -> Vec { match self { Self::Empty => Vec::new(), Self::Item(item_entry) => { @@ -123,7 +141,7 @@ impl LootPoolEntryTypesExt for LootPoolEntryTypes { Self::Alternatives(alternative_entry) => alternative_entry .children .iter() - .filter_map(|entry| entry.get_loot(block_props)) + .filter_map(|entry| entry.get_loot(params)) .flatten() .collect(), Self::Sequence => todo!(), @@ -133,20 +151,34 @@ impl LootPoolEntryTypesExt for LootPoolEntryTypes { } trait LootConditionExt { - fn is_fulfilled(&self, block_props: &[(String, String)]) -> bool; + fn is_fulfilled(&self, params: &LootContextParameters) -> bool; } impl LootConditionExt for LootCondition { // TODO: This is trash. Make this right - fn is_fulfilled(&self, block_props: &[(String, String)]) -> bool { + fn is_fulfilled(&self, params: &LootContextParameters) -> bool { match self { - Self::SurvivesExplosion => true, + Self::SurvivesExplosion => { + if let Some(radius) = params.explosion_radius { + return rand::rng().random::() <= 1.0 / radius; + } + true + } Self::BlockStateProperty { block: _, properties, - } => properties - .iter() - .all(|(key, value)| block_props.iter().any(|(k, v)| k == key && v == value)), + } => { + if let Some(state) = ¶ms.block_state { + let props = + Block::properties(&get_block_by_state_id(state.id).unwrap(), state.id) + .map_or_else(Vec::new, |props| props.to_props()); + + return properties + .iter() + .all(|(key, value)| props.iter().any(|(k, v)| k == key && v == value)); + } + false + } _ => false, } } diff --git a/pumpkin/src/block/mod.rs b/pumpkin/src/block/mod.rs index eb343373a..252bfa769 100644 --- a/pumpkin/src/block/mod.rs +++ b/pumpkin/src/block/mod.rs @@ -65,19 +65,15 @@ use fluid::lava::FlowingLava; use fluid::water::FlowingWater; use loot::LootTableExt; use pumpkin_data::block_properties::Integer0To15; -use pumpkin_data::entity::EntityType; use pumpkin_data::{Block, BlockState}; use pumpkin_util::math::position::BlockPos; -use pumpkin_util::math::vector3::Vector3; use pumpkin_util::random::{RandomGenerator, get_seed, xoroshiro128::Xoroshiro}; use pumpkin_world::BlockStateId; -use pumpkin_world::item::ItemStack; -use rand::Rng; use crate::block::blocks::plant::roots::RootsBlock; +use crate::block::loot::LootContextParameters; use crate::block::registry::BlockRegistry; -use crate::entity::item::ItemEntity; use crate::world::World; use crate::{block::blocks::crafting_table::CraftingTableBlock, entity::player::Player}; use crate::{block::blocks::jukebox::JukeboxBlock, entity::experience_orb::ExperienceOrbEntity}; @@ -85,7 +81,7 @@ use std::sync::Arc; pub mod blocks; mod fluid; -mod loot; +pub mod loot; pub mod pumpkin_block; pub mod pumpkin_fluid; pub mod registry; @@ -192,14 +188,11 @@ pub async fn drop_loot( block: &Block, pos: &BlockPos, experience: bool, - state_id: BlockStateId, + params: LootContextParameters, ) { if let Some(loot_table) = &block.loot_table { - let props = - Block::properties(block, state_id).map_or_else(Vec::new, |props| props.to_props()); - - for stack in loot_table.get_loot(&props) { - drop_stack(world, pos, stack).await; + for stack in loot_table.get_loot(params) { + world.drop_stack(pos, stack).await; } } @@ -215,19 +208,6 @@ pub async fn drop_loot( } } -async fn drop_stack(world: &Arc, pos: &BlockPos, stack: ItemStack) { - let height = EntityType::ITEM.dimension[1] / 2.0; - let pos = Vector3::new( - f64::from(pos.0.x) + 0.5 + rand::rng().random_range(-0.25..0.25), - f64::from(pos.0.y) + 0.5 + rand::rng().random_range(-0.25..0.25) - f64::from(height), - f64::from(pos.0.z) + 0.5 + rand::rng().random_range(-0.25..0.25), - ); - - let entity = world.create_entity(pos, EntityType::ITEM); - let item_entity = Arc::new(ItemEntity::new(entity, stack).await); - world.spawn_entity(item_entity).await; -} - pub async fn calc_block_breaking(player: &Player, state: &BlockState, block_name: &str) -> f32 { let hardness = state.hardness; #[expect(clippy::float_cmp)] diff --git a/pumpkin/src/command/commands/summon.rs b/pumpkin/src/command/commands/summon.rs index 0e6b03583..92cb9a342 100644 --- a/pumpkin/src/command/commands/summon.rs +++ b/pumpkin/src/command/commands/summon.rs @@ -57,7 +57,7 @@ impl CommandExecutor for Executor { (player.world().await, pos) } }; - let mob = mob::from_type(entity, pos, &world).await; + let mob = mob::from_type(entity, pos, &world); world.spawn_entity(mob).await; sender diff --git a/pumpkin/src/entity/ai/path/mod.rs b/pumpkin/src/entity/ai/path/mod.rs index 8eec00bf9..9d516e1a1 100644 --- a/pumpkin/src/entity/ai/path/mod.rs +++ b/pumpkin/src/entity/ai/path/mod.rs @@ -1,5 +1,6 @@ +use pumpkin_data::block_properties::get_block_collision_shapes; use pumpkin_protocol::client::play::CUpdateEntityPos; -use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use crate::entity::living::LivingEntity; @@ -36,6 +37,8 @@ impl Navigator { let mut best_move = Vector3::new(0.0, 0.0, 0.0); let mut lowest_cost = f64::MAX; + let world = entity.entity.world.read().await; + for x in -1..=1 { for z in -1..=1 { let x = f64::from(x); @@ -45,6 +48,16 @@ impl Navigator { goal.current_progress.y, goal.current_progress.z + z, ); + let shapes = get_block_collision_shapes( + world + .get_block_state(&BlockPos(potential_pos.to_i32())) + .await + .id, + ) + .unwrap(); + if !shapes.is_empty() { + continue; + } let node = Node::new(potential_pos); let cost = node.get_expense(goal.destination); @@ -61,6 +74,7 @@ impl Navigator { if best_move.x == 0.0 && best_move.z == 0.0 { return; } + // Update current progress based on the best move goal.current_progress += best_move.normalize() * goal.speed; diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index dea62550b..7915ca069 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -4,6 +4,7 @@ use std::{collections::HashMap, sync::atomic::AtomicI32}; use super::EntityBase; use super::{Entity, EntityId, NBTStorage, effect::Effect}; +use crate::block::loot::{LootContextParameters, LootTableExt}; use crate::server::Server; use async_trait::async_trait; use crossbeam::atomic::AtomicCell; @@ -268,6 +269,20 @@ impl LivingEntity { EntityStatus::PlayDeathSoundOrAddProjectileHitParticles, ) .await; + self.drop_loot().await; + } + + async fn drop_loot(&self) { + if let Some(loot_table) = &self.get_entity().entity_type.loot_table { + let world = self.entity.world.read().await; + let pos = self.entity.block_pos.load(); + let params = LootContextParameters { + ..Default::default() + }; + for stack in loot_table.get_loot(params) { + world.drop_stack(&pos, stack).await; + } + } } async fn tick_move(&self, entity: &dyn EntityBase, server: &Server) { diff --git a/pumpkin/src/entity/mob/mod.rs b/pumpkin/src/entity/mob/mod.rs index b42275677..6a1b3978e 100644 --- a/pumpkin/src/entity/mob/mod.rs +++ b/pumpkin/src/entity/mob/mod.rs @@ -51,29 +51,24 @@ impl EntityBase for MobEntity { } } -pub async fn from_type( +pub fn from_type( entity_type: EntityType, position: Vector3, world: &Arc, ) -> Arc { let entity = world.create_entity(position, entity_type); - let mob = MobEntity { - living_entity: LivingEntity::new(entity), - goals: Mutex::new(vec![]), - navigator: Mutex::new(Navigator::default()), - }; #[allow(clippy::single_match)] - match entity_type { - EntityType::ZOMBIE => Zombie::make(&mob).await, + let mob = match entity_type { + EntityType::ZOMBIE => Zombie::make(entity), // TODO - _ => (), - } + _ => MobEntity { + living_entity: LivingEntity::new(entity), + goals: Mutex::new(vec![]), + navigator: Mutex::new(Navigator::default()), + }, + }; Arc::new(mob) } -impl MobEntity { - pub async fn goal(&self, goal: T) { - self.goals.lock().await.push((Arc::new(goal), false)); - } -} +impl MobEntity {} diff --git a/pumpkin/src/entity/mob/zombie.rs b/pumpkin/src/entity/mob/zombie.rs index 814860e3c..521763af0 100644 --- a/pumpkin/src/entity/mob/zombie.rs +++ b/pumpkin/src/entity/mob/zombie.rs @@ -1,12 +1,29 @@ -use crate::entity::ai::goal::{look_at_entity::LookAtEntityGoal, target_goal::TargetGoal}; +use std::sync::Arc; + +use tokio::sync::Mutex; + +use crate::entity::{ + Entity, + ai::{ + goal::{look_at_entity::LookAtEntityGoal, target_goal::TargetGoal}, + path::Navigator, + }, + living::LivingEntity, +}; use super::MobEntity; pub struct Zombie; impl Zombie { - pub async fn make(mob: &MobEntity) { - mob.goal(LookAtEntityGoal::new(8.0)).await; - mob.goal(TargetGoal::new(16.0)).await; + pub fn make(entity: Entity) -> MobEntity { + MobEntity { + living_entity: LivingEntity::new(entity), + goals: Mutex::new(vec![ + (Arc::new(LookAtEntityGoal::new(8.0)), false), + (Arc::new(TargetGoal::new(16.0)), false), + ]), + navigator: Mutex::new(Navigator::default()), + } } } diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index a629626d3..3fadc6b95 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -1623,7 +1623,7 @@ impl Player { let world = self.world().await; // Create a new mob and UUID based on the spawn egg id - let mob = mob::from_type(EntityType::from_raw(entity_type.id).unwrap(), pos, &world).await; + let mob = mob::from_type(EntityType::from_raw(entity_type.id).unwrap(), pos, &world); // Set the rotation mob.get_entity().set_rotation(yaw, 0.0); diff --git a/pumpkin/src/world/explosion.rs b/pumpkin/src/world/explosion.rs index b94693528..72f7b2480 100644 --- a/pumpkin/src/world/explosion.rs +++ b/pumpkin/src/world/explosion.rs @@ -1,9 +1,13 @@ use std::collections::HashSet; use std::sync::Arc; +use pumpkin_data::block_properties::get_state_by_state_id; use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; -use crate::{block::drop_loot, server::Server}; +use crate::{ + block::{drop_loot, loot::LootContextParameters}, + server::Server, +}; use super::{BlockFlags, World}; @@ -83,7 +87,11 @@ impl Explosion { world.set_block_state(&pos, 0, BlockFlags::NOTIFY_ALL).await; if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) { - drop_loot(world, &block, &pos, false, block_state.id).await; + let params = LootContextParameters { + block_state: get_state_by_state_id(block_state.id), + explosion_radius: Some(self.power), + }; + drop_loot(world, &block, &pos, false, params).await; } if let Some(pumpkin_block) = pumpkin_block { pumpkin_block.explode(&block, world, pos).await; diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b5a02b0a7..1f98cac1d 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -8,7 +8,6 @@ pub mod explosion; pub mod portal; pub mod time; -use crate::block::BlockEvent; use crate::{ PLUGIN_MANAGER, block::{self, registry::BlockRegistry}, @@ -22,6 +21,10 @@ use crate::{ }, server::Server, }; +use crate::{ + block::{BlockEvent, loot::LootContextParameters}, + entity::item::ItemEntity, +}; use async_trait::async_trait; use border::Worldborder; use bytes::{BufMut, Bytes}; @@ -77,6 +80,7 @@ use pumpkin_util::{ }; use pumpkin_world::{ BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity, + item::ItemStack, }; use pumpkin_world::{chunk::ChunkData, world::BlockAccessor}; use pumpkin_world::{chunk::TickPriority, level::Level}; @@ -1607,7 +1611,11 @@ impl World { ); if !flags.contains(BlockFlags::SKIP_DROPS) { - block::drop_loot(self, &broken_block, position, true, broken_state_id).await; + let params = LootContextParameters { + block_state: get_state_by_state_id(broken_state_id), + ..Default::default() + }; + block::drop_loot(self, &broken_block, position, true, params).await; } match cause { @@ -1620,6 +1628,19 @@ impl World { } } + pub async fn drop_stack(self: &Arc, pos: &BlockPos, stack: ItemStack) { + let height = EntityType::ITEM.dimension[1] / 2.0; + let pos = Vector3::new( + f64::from(pos.0.x) + 0.5 + rand::rng().random_range(-0.25..0.25), + f64::from(pos.0.y) + 0.5 + rand::rng().random_range(-0.25..0.25) - f64::from(height), + f64::from(pos.0.z) + 0.5 + rand::rng().random_range(-0.25..0.25), + ); + + let entity = self.create_entity(pos, EntityType::ITEM); + let item_entity = Arc::new(ItemEntity::new(entity, stack).await); + self.spawn_entity(item_entity).await; + } + pub async fn sync_world_event(&self, world_event: WorldEvent, position: BlockPos, data: i32) { self.broadcast_packet_all(&CWorldEvent::new(world_event as i32, position, data, false)) .await;