steel_core/behavior/blocks/building/
drop_experience_block.rs1use std::sync::Arc;
4
5use steel_macros::block_behavior;
6use steel_registry::blocks::BlockRef;
7use steel_registry::item_stack::ItemStack;
8use steel_utils::value_providers::IntProvider;
9use steel_utils::{BlockPos, BlockStateId};
10
11use crate::behavior::{BlockBehavior, BlockPlaceContext, try_drop_experience};
12use crate::world::World;
13
14#[block_behavior]
16pub struct DropExperienceBlock {
17 block: BlockRef,
18 #[json_arg(int_provider, json = "xp_range")]
19 experience: IntProvider,
20}
21
22impl DropExperienceBlock {
23 #[must_use]
25 pub const fn new(block: BlockRef, experience: IntProvider) -> Self {
26 Self { block, experience }
27 }
28}
29
30impl BlockBehavior for DropExperienceBlock {
31 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
32 Some(self.block.default_state())
33 }
34
35 fn spawn_after_break(
36 &self,
37 _state: BlockStateId,
38 world: &Arc<World>,
39 pos: BlockPos,
40 tool: &ItemStack,
41 drop_experience: bool,
42 ) {
43 if drop_experience {
44 try_drop_experience(world, pos, tool, &self.experience);
45 }
46 }
47}
48
49#[cfg(test)]
50mod tests {
51 use steel_registry::{init_vanilla_registry, vanilla_blocks, vanilla_entities, vanilla_items};
52 use steel_utils::{ChunkPos, Downcast as _, Identifier, WorldAabb};
53
54 use super::*;
55 use crate::behavior::{BLOCK_BEHAVIORS, init_behaviors};
56 use crate::entity::entities::ExperienceOrbEntity;
57 use crate::test_support::{fresh_test_world, insert_ready_full_chunk};
58
59 fn experience_in(world: &Arc<World>, bounds: &WorldAabb) -> i32 {
60 world
61 .get_entities_in_aabb(bounds)
62 .iter()
63 .filter(|entity| entity.entity_type() == &vanilla_entities::EXPERIENCE_ORB)
64 .filter_map(|entity| entity.as_ref().downcast_ref::<ExperienceOrbEntity>())
65 .map(|orb| orb.value() * orb.count())
66 .sum()
67 }
68
69 #[test]
70 fn generated_constant_and_uniform_providers_control_ore_experience() {
71 init_vanilla_registry();
72 init_behaviors();
73 let world = fresh_test_world("drop_experience_provider_shapes");
74 let zero_pos = BlockPos::new(8, 64, 8);
75 let ranged_pos = BlockPos::new(12, 64, 8);
76 insert_ready_full_chunk(&world, ChunkPos::from_block_pos(zero_pos));
77
78 world.drop_resources(vanilla_blocks::IRON_ORE.default_state(), zero_pos);
79 let zero_bounds = WorldAabb::new(7.0, 63.0, 7.0, 10.0, 67.0, 10.0);
80 assert_eq!(experience_in(&world, &zero_bounds), 0);
81
82 world.drop_resources(vanilla_blocks::DIAMOND_ORE.default_state(), ranged_pos);
83 let ranged_bounds = WorldAabb::new(11.0, 63.0, 7.0, 14.0, 67.0, 10.0);
84 assert!((3..=7).contains(&experience_in(&world, &ranged_bounds)));
85 }
86
87 #[test]
88 fn silk_touch_and_disabled_experience_suppress_ore_experience() {
89 init_vanilla_registry();
90 init_behaviors();
91 let world = fresh_test_world("drop_experience_suppression");
92 let pos = BlockPos::new(8, 64, 8);
93 insert_ready_full_chunk(&world, ChunkPos::from_block_pos(pos));
94 let state = vanilla_blocks::DIAMOND_ORE.default_state();
95 let behavior = BLOCK_BEHAVIORS.get_behavior(&vanilla_blocks::DIAMOND_ORE);
96
97 let mut silk_touch_tool = ItemStack::new(&vanilla_items::DIAMOND_PICKAXE);
98 silk_touch_tool.set_enchantments(&[(Identifier::vanilla_static("silk_touch"), 1)], false);
99 behavior.spawn_after_break(state, &world, pos, &silk_touch_tool, true);
100
101 let plain_tool = ItemStack::new(&vanilla_items::DIAMOND_PICKAXE);
102 assert!(plain_tool.is_correct_tool_for_drops(state));
103 behavior.spawn_after_break(state, &world, pos.offset(1, 0, 0), &plain_tool, false);
104
105 let bounds = WorldAabb::new(7.0, 63.0, 7.0, 11.0, 67.0, 10.0);
106 assert_eq!(experience_in(&world, &bounds), 0);
107 }
108}