Skip to content

Commit

Permalink
Use Loot tables for blocks (#605)
Browse files Browse the repository at this point in the history
* more loot table work

* make Loot Tables work
  • Loading branch information
Snowiiii authored Mar 2, 2025
1 parent d518732 commit 440008f
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 19 deletions.
57 changes: 57 additions & 0 deletions pumpkin-world/src/loot/condition/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use serde::Deserialize;

mod survives_explosion;

#[derive(Deserialize, Clone)]
#[serde(tag = "condition")]
pub enum LootCondition {
#[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,
#[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,
}

#[expect(clippy::match_like_matches_macro)]
impl LootCondition {
// TODO: This is trash, Make this right
pub fn test(&self) -> bool {
match self {
LootCondition::SurvivesExplosion => true,
_ => false,
}
}
}
1 change: 1 addition & 0 deletions pumpkin-world/src/loot/condition/survives_explosion.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

30 changes: 30 additions & 0 deletions pumpkin-world/src/loot/entry/alternative.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use serde::Deserialize;

use crate::{item::ItemStack, loot::LootPoolEntry};

#[derive(Deserialize, Clone)]
pub struct AlternativeEntry {
children: Vec<LootPoolEntry>,
}

impl AlternativeEntry {
pub fn get_items(&self) -> Vec<ItemStack> {
let mut items = vec![];
for child in &self.children {
if let Some(conditions) = &child.conditions {
let mut conditions_met = true;
for condition in conditions {
if !condition.test() {
conditions_met = false;
break;
}
}
if !conditions_met {
continue;
}
}
items.extend_from_slice(&child.content.get_items());
}
items
}
}
16 changes: 16 additions & 0 deletions pumpkin-world/src/loot/entry/item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use pumpkin_data::item::Item;
use serde::Deserialize;

use crate::item::ItemStack;

#[derive(Deserialize, Clone)]
pub struct ItemEntry {
name: String,
}

impl ItemEntry {
pub fn get_items(&self) -> Vec<ItemStack> {
let item = Item::from_registry_key(&self.name.replace("minecraft:", "")).unwrap();
vec![ItemStack::new(1, item)]
}
}
44 changes: 44 additions & 0 deletions pumpkin-world/src/loot/entry/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use alternative::AlternativeEntry;
use item::ItemEntry;
use serde::Deserialize;

use crate::item::ItemStack;

mod alternative;
mod item;

#[derive(Deserialize, Clone)]
#[serde(tag = "type")]
pub enum LootPoolEntryTypes {
#[serde(rename = "minecraft:empty")]
Empty,
#[serde(rename = "minecraft:item")]
Item(ItemEntry),
#[serde(rename = "minecraft:loot_table")]
LootTable,
#[serde(rename = "minecraft:dynamic")]
Dynamic,
#[serde(rename = "minecraft:tag")]
Tag,
#[serde(rename = "minecraft:alternatives")]
Alternatives(AlternativeEntry),
#[serde(rename = "minecraft:sequence")]
Sequence,
#[serde(rename = "minecraft:group")]
Group,
}

impl LootPoolEntryTypes {
pub fn get_items(&self) -> Vec<ItemStack> {
match self {
LootPoolEntryTypes::Empty => todo!(),
LootPoolEntryTypes::Item(item_entry) => item_entry.get_items(),
LootPoolEntryTypes::LootTable => todo!(),
LootPoolEntryTypes::Dynamic => todo!(),
LootPoolEntryTypes::Tag => todo!(),
LootPoolEntryTypes::Alternatives(alternative) => alternative.get_items(),
LootPoolEntryTypes::Sequence => todo!(),
LootPoolEntryTypes::Group => todo!(),
}
}
}
56 changes: 50 additions & 6 deletions pumpkin-world/src/loot/mod.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,69 @@
use condition::LootCondition;
use entry::LootPoolEntryTypes;
use serde::Deserialize;

use crate::item::ItemStack;

mod condition;
mod entry;

#[expect(dead_code)]
#[derive(Deserialize, Clone)]
pub struct LootTable {
r#type: LootTableType,
random_sequence: Option<String>,
pools: Option<Vec<LootPool>>,
}

#[expect(dead_code)]
impl LootTable {
pub fn get_loot(&self) -> Vec<ItemStack> {
let mut items = vec![];
if let Some(pools) = &self.pools {
for pool in pools {
items.extend_from_slice(&pool.get_loot());
}
}
items
}
}

#[derive(Deserialize, Clone)]
pub struct LootPool {
entries: Vec<LootPoolEntry>,
rolls: f32, // TODO
bonus_rolls: f32,
}

impl LootPool {
pub fn get_loot(&self) -> Vec<ItemStack> {
let i = self.rolls.round() as i32 + self.bonus_rolls.floor() as i32; // TODO: mul by luck
let mut items = vec![];
for _ in 0..i {
for entry in &self.entries {
if let Some(conditions) = &entry.conditions {
let mut conditions_met = true;
for condition in conditions {
if !condition.test() {
conditions_met = false;
break;
}
}
if !conditions_met {
continue;
}
}
items.extend_from_slice(&entry.content.get_items());
}
}
items
}
}

#[expect(dead_code)]
#[derive(Deserialize, Clone)]
pub struct LootPoolEntry {
// TODO
r#type: Option<String>,
// TODO
name: Option<String>,
#[serde(flatten)]
content: LootPoolEntryTypes,
conditions: Option<Vec<LootCondition>>,
}

#[derive(Deserialize, Clone)]
Expand Down
41 changes: 28 additions & 13 deletions pumpkin/src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ use properties::{
use pumpkin_data::entity::EntityType;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_world::block::registry::{Block, State};
use pumpkin_world::{
block::registry::{Block, State},
item::ItemStack,
};
use rand::Rng;

use crate::block::registry::BlockRegistry;
Expand Down Expand Up @@ -57,7 +60,25 @@ pub async fn drop_loot(
pos: &BlockPos,
experience: bool,
) {
// TODO: Currently only the item block is dropped, We should drop the loop table
if let Some(table) = &block.loot_table {
let loot = table.get_loot();
for item in loot {
drop_stack(server, world, pos, item).await;
}
}

if experience {
if let Some(experience) = &block.experience {
let amount = experience.experience.get();
// TODO: Silk touch gives no exp
if amount > 0 {
ExperienceOrbEntity::spawn(world, server, pos.to_f64(), amount as u32).await;
}
}
}
}

async fn drop_stack(server: &Server, 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::thread_rng().gen_range(-0.25..0.25),
Expand All @@ -66,19 +87,13 @@ pub async fn drop_loot(
);

let entity = server.add_entity(pos, EntityType::ITEM, world);
let item_entity = Arc::new(ItemEntity::new(entity, block.item_id, 1));
let item_entity = Arc::new(ItemEntity::new(
entity,
stack.item.id,
u32::from(stack.item_count),
));
world.spawn_entity(item_entity.clone()).await;
item_entity.send_meta_packet().await;

if experience {
if let Some(experience) = &block.experience {
let amount = experience.experience.get();
// TODO: Silk touch gives no exp
if amount > 0 {
ExperienceOrbEntity::spawn(world, server, pos, amount as u32).await;
}
}
}
}

pub async fn calc_block_breaking(player: &Player, state: &State, block_name: &str) -> f32 {
Expand Down

0 comments on commit 440008f

Please sign in to comment.