Add Entity loot

not done, but works
This commit is contained in:
Alexander Medvedev 2025-06-21 16:38:05 +02:00
parent 9004d307bf
commit f16d4a6247
15 changed files with 817 additions and 580 deletions

View File

@ -9,6 +9,8 @@ use std::{
}; };
use syn::{Ident, LitInt, LitStr}; use syn::{Ident, LitInt, LitStr};
use crate::loot::LootTableStruct;
fn const_block_name_from_block_name(block: &str) -> String { fn const_block_name_from_block_name(block: &str) -> String {
block.to_shouty_snake_case() 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<String>,
pools: Option<Vec<LootPoolStruct>>,
}
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<LootPoolEntryStruct>,
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<LootPoolEntryStruct>,
}
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<String, String>,
},
#[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<Vec<LootConditionStruct>>,
}
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<bool>,
},
#[serde(rename = "minecraft:limit_count")]
LimitCount { limit: LootFunctionLimitCountStruct },
#[serde(rename = "minecraft:apply_bonus")]
ApplyBonus {
enchantment: String,
formula: String,
parameters: Option<LootFunctionBonusParameterStruct>,
},
#[serde(rename = "minecraft:copy_components")]
CopyComponents {
source: String,
include: Vec<String>,
},
#[serde(rename = "minecraft:copy_state")]
CopyState {
block: String,
properties: Vec<String>,
},
#[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<f32>,
max: Option<f32>,
}
#[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<Vec<LootConditionStruct>>,
functions: Option<Vec<LootFunctionStruct>>,
}
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)] #[derive(Deserialize, Clone, Debug)]
pub struct Block { pub struct Block {
pub id: u16, pub id: u16,
@ -1404,21 +926,19 @@ pub(crate) fn build() -> TokenStream {
pub fn get_block_collision_shapes(state_id: u16) -> Option<Vec<CollisionShape>> { pub fn get_block_collision_shapes(state_id: u16) -> Option<Vec<CollisionShape>> {
let state = get_state_by_state_id(state_id)?; let state = get_state_by_state_id(state_id)?;
let mut shapes: Vec<CollisionShape> = vec![]; let shapes: Vec<CollisionShape> = state.collision_shapes
for i in 0..state.collision_shapes.len() { .iter()
let shape = &COLLISION_SHAPES[state.collision_shapes[i] as usize]; .map(|&id| COLLISION_SHAPES[id as usize])
shapes.push(*shape); .collect();
}
Some(shapes) Some(shapes)
} }
pub fn get_block_outline_shapes(state_id: u16) -> Option<Vec<CollisionShape>> { pub fn get_block_outline_shapes(state_id: u16) -> Option<Vec<CollisionShape>> {
let state = get_state_by_state_id(state_id)?; let state = get_state_by_state_id(state_id)?;
let mut shapes: Vec<CollisionShape> = vec![]; let mut shapes: Vec<CollisionShape> = state.outline_shapes
for i in 0..state.outline_shapes.len() { .iter()
let shape = &COLLISION_SHAPES[state.outline_shapes[i] as usize]; .map(|&id| COLLISION_SHAPES[id as usize])
shapes.push(*shape); .collect();
}
let block = get_block_by_state_id(state_id)?; let block = get_block_by_state_id(state_id)?;
if block.properties(state.id).and_then(|properties| { if block.properties(state.id).and_then(|properties| {
properties properties

View File

@ -14,6 +14,7 @@ mod entity_type;
mod fluid; mod fluid;
mod game_event; mod game_event;
mod item; mod item;
pub mod loot;
mod message_type; mod message_type;
mod noise_parameter; mod noise_parameter;
mod noise_router; mod noise_router;

View File

@ -6,11 +6,14 @@ use quote::{ToTokens, format_ident, quote};
use serde::Deserialize; use serde::Deserialize;
use syn::LitInt; use syn::LitInt;
use crate::loot::LootTableStruct;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct EntityType { pub struct EntityType {
pub id: u16, pub id: u16,
pub max_health: Option<f32>, pub max_health: Option<f32>,
pub attackable: Option<bool>, pub attackable: Option<bool>,
pub loot_table: Option<LootTableStruct>,
pub summonable: bool, pub summonable: bool,
pub fire_immune: bool, pub fire_immune: bool,
pub dimension: [f32; 2], pub dimension: [f32; 2],
@ -79,6 +82,14 @@ impl ToTokens for NamedEntityType<'_> {
let dimension0 = entity.dimension[0]; let dimension0 = entity.dimension[0];
let dimension1 = entity.dimension[1]; 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! { tokens.extend(quote! {
EntityType { EntityType {
id: #id, id: #id,
@ -86,6 +97,7 @@ impl ToTokens for NamedEntityType<'_> {
attackable: #attackable, attackable: #attackable,
summonable: #summonable, summonable: #summonable,
fire_immune: #fire_immune, fire_immune: #fire_immune,
loot_table: #loot_table,
dimension: [#dimension0, #dimension1], // Correctly construct the array dimension: [#dimension0, #dimension1], // Correctly construct the array
eye_height: #eye_height, eye_height: #eye_height,
spawn_restriction: #spawn_restriction, spawn_restriction: #spawn_restriction,
@ -126,6 +138,7 @@ pub(crate) fn build() -> TokenStream {
}); });
} }
quote! { quote! {
use pumpkin_util::loot_table::*;
use pumpkin_util::HeightMap; use pumpkin_util::HeightMap;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
@ -135,6 +148,7 @@ pub(crate) fn build() -> TokenStream {
pub attackable: Option<bool>, pub attackable: Option<bool>,
pub summonable: bool, pub summonable: bool,
pub fire_immune: bool, pub fire_immune: bool,
pub loot_table: Option<LootTable>,
pub dimension: [f32; 2], pub dimension: [f32; 2],
pub eye_height: f32, pub eye_height: f32,
pub spawn_restriction: SpawnRestriction, pub spawn_restriction: SpawnRestriction,

511
pumpkin-data/build/loot.rs Normal file
View File

@ -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<String>,
pools: Option<Vec<LootPoolStruct>>,
}
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<LootPoolEntryStruct>,
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<LootPoolEntryStruct>,
}
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<String, String>,
},
#[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<Vec<LootConditionStruct>>,
}
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<bool>,
},
#[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<LootFunctionBonusParameterStruct>,
},
#[serde(rename = "minecraft:copy_components")]
CopyComponents {
source: String,
include: Vec<String>,
},
#[serde(rename = "minecraft:copy_state")]
CopyState {
block: String,
properties: Vec<String>,
},
#[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<f32>,
max: Option<f32>,
}
#[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<Vec<LootConditionStruct>>,
functions: Option<Vec<LootFunctionStruct>>,
}
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);
}
}

View File

@ -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 struct LootTable {
pub r#type: LootTableType, pub r#type: LootTableType,
pub random_sequence: Option<&'static str>, pub random_sequence: Option<&'static str>,
pub pools: Option<&'static [LootPool]>, pub pools: Option<&'static [LootPool]>,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct LootPool { pub struct LootPool {
pub entries: &'static [LootPoolEntry], pub entries: &'static [LootPoolEntry],
pub rolls: f32, pub rolls: LootNumberProviderTypes,
pub bonus_rolls: f32, pub bonus_rolls: f32,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct ItemEntry { pub struct ItemEntry {
pub name: &'static str, pub name: &'static str,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct AlternativeEntry { pub struct AlternativeEntry {
pub children: &'static [LootPoolEntry], pub children: &'static [LootPoolEntry],
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum LootPoolEntryTypes { pub enum LootPoolEntryTypes {
Empty, Empty,
Item(ItemEntry), Item(ItemEntry),
@ -34,7 +40,7 @@ pub enum LootPoolEntryTypes {
Group, Group,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum LootCondition { pub enum LootCondition {
Inverted, Inverted,
AnyOf, AnyOf,
@ -60,18 +66,22 @@ pub enum LootCondition {
EnchantmentActiveCheck, EnchantmentActiveCheck,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct LootFunction { pub struct LootFunction {
pub content: LootFunctionTypes, pub content: LootFunctionTypes,
pub conditions: Option<&'static [LootCondition]>, pub conditions: Option<&'static [LootCondition]>,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum LootFunctionTypes { pub enum LootFunctionTypes {
SetCount { SetCount {
count: LootFunctionNumberProvider, count: LootFunctionNumberProvider,
add: bool, add: bool,
}, },
EnchantedCountIncrease,
FurnaceSmelt,
SetPotion,
SetOminousBottleAmplifier,
LimitCount { LimitCount {
min: Option<f32>, min: Option<f32>,
max: Option<f32>, max: Option<f32>,
@ -92,32 +102,131 @@ pub enum LootFunctionTypes {
ExplosionDecay, ExplosionDecay,
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum LootFunctionNumberProvider { pub enum LootFunctionNumberProvider {
Constant { value: f32 }, Constant { value: f32 },
Uniform { min: f32, max: f32 }, Uniform { min: f32, max: f32 },
Binomial { n: f32, p: f32 }, Binomial { n: f32, p: f32 },
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum LootFunctionBonusParameter { pub enum LootFunctionBonusParameter {
Multiplier { bonus_multiplier: i32 }, Multiplier { bonus_multiplier: i32 },
Probability { extra: i32, probability: f32 }, Probability { extra: i32, probability: f32 },
} }
#[derive(Clone, Debug)] #[derive(Clone, PartialEq, Debug)]
pub struct LootPoolEntry { pub struct LootPoolEntry {
pub content: LootPoolEntryTypes, pub content: LootPoolEntryTypes,
pub conditions: Option<&'static [LootCondition]>, pub conditions: Option<&'static [LootCondition]>,
pub functions: Option<&'static [LootFunction]>, pub functions: Option<&'static [LootFunction]>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
pub enum LootTableType { pub enum LootTableType {
/// Nothing will be dropped
Empty, Empty,
/// A block will be dropped Entity,
Block, Block,
/// An item will be dropped
Chest, 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,
}
}
}

View File

@ -1,26 +1,40 @@
use pumpkin_data::item::Item; use pumpkin_data::{Block, BlockState, block_properties::get_block_by_state_id, item::Item};
use pumpkin_util::loot_table::{ use pumpkin_util::{
LootCondition, LootFunctionNumberProvider, LootFunctionTypes, LootPoolEntry, loot_table::{
LootPoolEntryTypes, LootTable, LootCondition, LootFunctionNumberProvider, LootFunctionTypes, LootPoolEntry,
LootPoolEntryTypes, LootTable,
},
random::{RandomGenerator, xoroshiro128::Xoroshiro},
}; };
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
use rand::Rng; use rand::Rng;
pub(super) trait LootTableExt { #[derive(Default)]
fn get_loot(&self, block_props: &[(String, String)]) -> Vec<ItemStack>; pub struct LootContextParameters {
pub explosion_radius: Option<f32>,
pub block_state: Option<BlockState>,
}
pub trait LootTableExt {
fn get_loot(&self, params: LootContextParameters) -> Vec<ItemStack>;
} }
impl LootTableExt for LootTable { impl LootTableExt for LootTable {
fn get_loot(&self, block_props: &[(String, String)]) -> Vec<ItemStack> { fn get_loot(&self, params: LootContextParameters) -> Vec<ItemStack> {
let mut stacks = Vec::new(); let mut stacks = Vec::new();
if let Some(pools) = self.pools { if let Some(pools) = self.pools {
for pool in 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 _ in 0..(rolls as i32) {
for entry in pool.entries { for entry in pool.entries {
if let Some(loot) = entry.get_loot(block_props) { if let Some(loot) = entry.get_loot(&params) {
stacks.extend(loot); stacks.extend(loot);
} }
} }
@ -33,23 +47,23 @@ impl LootTableExt for LootTable {
} }
trait LootPoolEntryExt { trait LootPoolEntryExt {
fn get_loot(&self, block_props: &[(String, String)]) -> Option<Vec<ItemStack>>; fn get_loot(&self, params: &LootContextParameters) -> Option<Vec<ItemStack>>;
} }
impl LootPoolEntryExt for LootPoolEntry { impl LootPoolEntryExt for LootPoolEntry {
fn get_loot(&self, block_props: &[(String, String)]) -> Option<Vec<ItemStack>> { fn get_loot(&self, params: &LootContextParameters) -> Option<Vec<ItemStack>> {
if let Some(conditions) = self.conditions { 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; 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 { if let Some(functions) = self.functions {
for function in functions { for function in functions {
if let Some(conditions) = function.conditions { 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; continue;
} }
} }
@ -94,6 +108,10 @@ impl LootPoolEntryExt for LootPoolEntry {
block: _, block: _,
properties: _, properties: _,
} }
| LootFunctionTypes::EnchantedCountIncrease
| LootFunctionTypes::SetOminousBottleAmplifier
| LootFunctionTypes::SetPotion
| LootFunctionTypes::FurnaceSmelt
| LootFunctionTypes::ExplosionDecay => { | LootFunctionTypes::ExplosionDecay => {
// TODO: shouldnt crash here but needs to be implemented someday // TODO: shouldnt crash here but needs to be implemented someday
} }
@ -106,11 +124,11 @@ impl LootPoolEntryExt for LootPoolEntry {
} }
trait LootPoolEntryTypesExt { trait LootPoolEntryTypesExt {
fn get_stacks(&self, block_props: &[(String, String)]) -> Vec<ItemStack>; fn get_stacks(&self, params: &LootContextParameters) -> Vec<ItemStack>;
} }
impl LootPoolEntryTypesExt for LootPoolEntryTypes { impl LootPoolEntryTypesExt for LootPoolEntryTypes {
fn get_stacks(&self, block_props: &[(String, String)]) -> Vec<ItemStack> { fn get_stacks(&self, params: &LootContextParameters) -> Vec<ItemStack> {
match self { match self {
Self::Empty => Vec::new(), Self::Empty => Vec::new(),
Self::Item(item_entry) => { Self::Item(item_entry) => {
@ -123,7 +141,7 @@ impl LootPoolEntryTypesExt for LootPoolEntryTypes {
Self::Alternatives(alternative_entry) => alternative_entry Self::Alternatives(alternative_entry) => alternative_entry
.children .children
.iter() .iter()
.filter_map(|entry| entry.get_loot(block_props)) .filter_map(|entry| entry.get_loot(params))
.flatten() .flatten()
.collect(), .collect(),
Self::Sequence => todo!(), Self::Sequence => todo!(),
@ -133,20 +151,34 @@ impl LootPoolEntryTypesExt for LootPoolEntryTypes {
} }
trait LootConditionExt { trait LootConditionExt {
fn is_fulfilled(&self, block_props: &[(String, String)]) -> bool; fn is_fulfilled(&self, params: &LootContextParameters) -> bool;
} }
impl LootConditionExt for LootCondition { impl LootConditionExt for LootCondition {
// TODO: This is trash. Make this right // 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 { match self {
Self::SurvivesExplosion => true, Self::SurvivesExplosion => {
if let Some(radius) = params.explosion_radius {
return rand::rng().random::<f32>() <= 1.0 / radius;
}
true
}
Self::BlockStateProperty { Self::BlockStateProperty {
block: _, block: _,
properties, properties,
} => properties } => {
.iter() if let Some(state) = &params.block_state {
.all(|(key, value)| block_props.iter().any(|(k, v)| k == key && v == value)), 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, _ => false,
} }
} }

View File

@ -65,19 +65,15 @@ use fluid::lava::FlowingLava;
use fluid::water::FlowingWater; use fluid::water::FlowingWater;
use loot::LootTableExt; use loot::LootTableExt;
use pumpkin_data::block_properties::Integer0To15; use pumpkin_data::block_properties::Integer0To15;
use pumpkin_data::entity::EntityType;
use pumpkin_data::{Block, BlockState}; use pumpkin_data::{Block, BlockState};
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::{RandomGenerator, get_seed, xoroshiro128::Xoroshiro}; use pumpkin_util::random::{RandomGenerator, get_seed, xoroshiro128::Xoroshiro};
use pumpkin_world::BlockStateId; use pumpkin_world::BlockStateId;
use pumpkin_world::item::ItemStack;
use rand::Rng;
use crate::block::blocks::plant::roots::RootsBlock; use crate::block::blocks::plant::roots::RootsBlock;
use crate::block::loot::LootContextParameters;
use crate::block::registry::BlockRegistry; use crate::block::registry::BlockRegistry;
use crate::entity::item::ItemEntity;
use crate::world::World; use crate::world::World;
use crate::{block::blocks::crafting_table::CraftingTableBlock, entity::player::Player}; use crate::{block::blocks::crafting_table::CraftingTableBlock, entity::player::Player};
use crate::{block::blocks::jukebox::JukeboxBlock, entity::experience_orb::ExperienceOrbEntity}; use crate::{block::blocks::jukebox::JukeboxBlock, entity::experience_orb::ExperienceOrbEntity};
@ -85,7 +81,7 @@ use std::sync::Arc;
pub mod blocks; pub mod blocks;
mod fluid; mod fluid;
mod loot; pub mod loot;
pub mod pumpkin_block; pub mod pumpkin_block;
pub mod pumpkin_fluid; pub mod pumpkin_fluid;
pub mod registry; pub mod registry;
@ -192,14 +188,11 @@ pub async fn drop_loot(
block: &Block, block: &Block,
pos: &BlockPos, pos: &BlockPos,
experience: bool, experience: bool,
state_id: BlockStateId, params: LootContextParameters,
) { ) {
if let Some(loot_table) = &block.loot_table { if let Some(loot_table) = &block.loot_table {
let props = for stack in loot_table.get_loot(params) {
Block::properties(block, state_id).map_or_else(Vec::new, |props| props.to_props()); world.drop_stack(pos, stack).await;
for stack in loot_table.get_loot(&props) {
drop_stack(world, pos, stack).await;
} }
} }
@ -215,19 +208,6 @@ pub async fn drop_loot(
} }
} }
async fn drop_stack(world: &Arc<World>, 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 { pub async fn calc_block_breaking(player: &Player, state: &BlockState, block_name: &str) -> f32 {
let hardness = state.hardness; let hardness = state.hardness;
#[expect(clippy::float_cmp)] #[expect(clippy::float_cmp)]

View File

@ -57,7 +57,7 @@ impl CommandExecutor for Executor {
(player.world().await, pos) (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; world.spawn_entity(mob).await;
sender sender

View File

@ -1,5 +1,6 @@
use pumpkin_data::block_properties::get_block_collision_shapes;
use pumpkin_protocol::client::play::CUpdateEntityPos; 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; 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 best_move = Vector3::new(0.0, 0.0, 0.0);
let mut lowest_cost = f64::MAX; let mut lowest_cost = f64::MAX;
let world = entity.entity.world.read().await;
for x in -1..=1 { for x in -1..=1 {
for z in -1..=1 { for z in -1..=1 {
let x = f64::from(x); let x = f64::from(x);
@ -45,6 +48,16 @@ impl Navigator {
goal.current_progress.y, goal.current_progress.y,
goal.current_progress.z + z, 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 node = Node::new(potential_pos);
let cost = node.get_expense(goal.destination); let cost = node.get_expense(goal.destination);
@ -61,6 +74,7 @@ impl Navigator {
if best_move.x == 0.0 && best_move.z == 0.0 { if best_move.x == 0.0 && best_move.z == 0.0 {
return; return;
} }
// Update current progress based on the best move // Update current progress based on the best move
goal.current_progress += best_move.normalize() * goal.speed; goal.current_progress += best_move.normalize() * goal.speed;

View File

@ -4,6 +4,7 @@ use std::{collections::HashMap, sync::atomic::AtomicI32};
use super::EntityBase; use super::EntityBase;
use super::{Entity, EntityId, NBTStorage, effect::Effect}; use super::{Entity, EntityId, NBTStorage, effect::Effect};
use crate::block::loot::{LootContextParameters, LootTableExt};
use crate::server::Server; use crate::server::Server;
use async_trait::async_trait; use async_trait::async_trait;
use crossbeam::atomic::AtomicCell; use crossbeam::atomic::AtomicCell;
@ -268,6 +269,20 @@ impl LivingEntity {
EntityStatus::PlayDeathSoundOrAddProjectileHitParticles, EntityStatus::PlayDeathSoundOrAddProjectileHitParticles,
) )
.await; .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) { async fn tick_move(&self, entity: &dyn EntityBase, server: &Server) {

View File

@ -51,29 +51,24 @@ impl EntityBase for MobEntity {
} }
} }
pub async fn from_type( pub fn from_type(
entity_type: EntityType, entity_type: EntityType,
position: Vector3<f64>, position: Vector3<f64>,
world: &Arc<World>, world: &Arc<World>,
) -> Arc<dyn EntityBase> { ) -> Arc<dyn EntityBase> {
let entity = world.create_entity(position, entity_type); 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)] #[allow(clippy::single_match)]
match entity_type { let mob = match entity_type {
EntityType::ZOMBIE => Zombie::make(&mob).await, EntityType::ZOMBIE => Zombie::make(entity),
// TODO // TODO
_ => (), _ => MobEntity {
} living_entity: LivingEntity::new(entity),
goals: Mutex::new(vec![]),
navigator: Mutex::new(Navigator::default()),
},
};
Arc::new(mob) Arc::new(mob)
} }
impl MobEntity { impl MobEntity {}
pub async fn goal<T: Goal + 'static>(&self, goal: T) {
self.goals.lock().await.push((Arc::new(goal), false));
}
}

View File

@ -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; use super::MobEntity;
pub struct Zombie; pub struct Zombie;
impl Zombie { impl Zombie {
pub async fn make(mob: &MobEntity) { pub fn make(entity: Entity) -> MobEntity {
mob.goal(LookAtEntityGoal::new(8.0)).await; MobEntity {
mob.goal(TargetGoal::new(16.0)).await; 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()),
}
} }
} }

View File

@ -1623,7 +1623,7 @@ impl Player {
let world = self.world().await; let world = self.world().await;
// Create a new mob and UUID based on the spawn egg id // 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 // Set the rotation
mob.get_entity().set_rotation(yaw, 0.0); mob.get_entity().set_rotation(yaw, 0.0);

View File

@ -1,9 +1,13 @@
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc; use std::sync::Arc;
use pumpkin_data::block_properties::get_state_by_state_id;
use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; 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}; use super::{BlockFlags, World};
@ -83,7 +87,11 @@ impl Explosion {
world.set_block_state(&pos, 0, BlockFlags::NOTIFY_ALL).await; world.set_block_state(&pos, 0, BlockFlags::NOTIFY_ALL).await;
if pumpkin_block.is_none_or(|s| s.should_drop_items_on_explosion()) { 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 { if let Some(pumpkin_block) = pumpkin_block {
pumpkin_block.explode(&block, world, pos).await; pumpkin_block.explode(&block, world, pos).await;

View File

@ -8,7 +8,6 @@ pub mod explosion;
pub mod portal; pub mod portal;
pub mod time; pub mod time;
use crate::block::BlockEvent;
use crate::{ use crate::{
PLUGIN_MANAGER, PLUGIN_MANAGER,
block::{self, registry::BlockRegistry}, block::{self, registry::BlockRegistry},
@ -22,6 +21,10 @@ use crate::{
}, },
server::Server, server::Server,
}; };
use crate::{
block::{BlockEvent, loot::LootContextParameters},
entity::item::ItemEntity,
};
use async_trait::async_trait; use async_trait::async_trait;
use border::Worldborder; use border::Worldborder;
use bytes::{BufMut, Bytes}; use bytes::{BufMut, Bytes};
@ -77,6 +80,7 @@ use pumpkin_util::{
}; };
use pumpkin_world::{ use pumpkin_world::{
BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity, BlockStateId, GENERATION_SETTINGS, GeneratorSetting, biome, block::entities::BlockEntity,
item::ItemStack,
}; };
use pumpkin_world::{chunk::ChunkData, world::BlockAccessor}; use pumpkin_world::{chunk::ChunkData, world::BlockAccessor};
use pumpkin_world::{chunk::TickPriority, level::Level}; use pumpkin_world::{chunk::TickPriority, level::Level};
@ -1607,7 +1611,11 @@ impl World {
); );
if !flags.contains(BlockFlags::SKIP_DROPS) { 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 { match cause {
@ -1620,6 +1628,19 @@ impl World {
} }
} }
pub async fn drop_stack(self: &Arc<Self>, 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) { 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)) self.broadcast_packet_all(&CWorldEvent::new(world_event as i32, position, data, false))
.await; .await;