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_fixture = fresh_test_world("drop_experience_provider_shapes");
74 let world = &world_fixture.world;
75 let zero_pos = BlockPos::new(8, 64, 8);
76 let ranged_pos = BlockPos::new(12, 64, 8);
77 insert_ready_full_chunk(world, ChunkPos::from_block_pos(zero_pos));
78
79 world.drop_resources(vanilla_blocks::IRON_ORE.default_state(), zero_pos);
80 let zero_bounds = WorldAabb::new(7.0, 63.0, 7.0, 10.0, 67.0, 10.0);
81 assert_eq!(experience_in(world, &zero_bounds), 0);
82
83 world.drop_resources(vanilla_blocks::DIAMOND_ORE.default_state(), ranged_pos);
84 let ranged_bounds = WorldAabb::new(11.0, 63.0, 7.0, 14.0, 67.0, 10.0);
85 assert!((3..=7).contains(&experience_in(world, &ranged_bounds)));
86 }
87
88 #[test]
89 fn silk_touch_and_disabled_experience_suppress_ore_experience() {
90 init_vanilla_registry();
91 init_behaviors();
92 let world_fixture = fresh_test_world("drop_experience_suppression");
93 let world = &world_fixture.world;
94 let pos = BlockPos::new(8, 64, 8);
95 insert_ready_full_chunk(world, ChunkPos::from_block_pos(pos));
96 let state = vanilla_blocks::DIAMOND_ORE.default_state();
97 let behavior = BLOCK_BEHAVIORS.get_behavior(&vanilla_blocks::DIAMOND_ORE);
98
99 let mut silk_touch_tool = ItemStack::new(&vanilla_items::DIAMOND_PICKAXE);
100 silk_touch_tool.set_enchantments(&[(Identifier::vanilla_static("silk_touch"), 1)], false);
101 behavior.spawn_after_break(state, world, pos, &silk_touch_tool, true);
102
103 let plain_tool = ItemStack::new(&vanilla_items::DIAMOND_PICKAXE);
104 assert!(plain_tool.is_correct_tool_for_drops(state));
105 behavior.spawn_after_break(state, world, pos.offset(1, 0, 0), &plain_tool, false);
106
107 let bounds = WorldAabb::new(7.0, 63.0, 7.0, 11.0, 67.0, 10.0);
108 assert_eq!(experience_in(world, &bounds), 0);
109 }
110}