Skip to main content

steel_core/behavior/blocks/building/
drop_experience_block.rs

1//! Vanilla blocks whose only class-specific behavior is dropping experience.
2
3use 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/// Vanilla `DropExperienceBlock` behavior.
15#[block_behavior]
16pub struct DropExperienceBlock {
17    block: BlockRef,
18    #[json_arg(int_provider, json = "xp_range")]
19    experience: IntProvider,
20}
21
22impl DropExperienceBlock {
23    /// Creates a block behavior with its extracted experience provider.
24    #[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}