Skip to main content

steel_core/behavior/blocks/redstone/
redstone_ore_block.rs

1//! Vanilla redstone and deepslate redstone ore behavior.
2
3use std::sync::Arc;
4
5use steel_macros::block_behavior;
6use steel_registry::REGISTRY;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt as _;
9use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty};
10use steel_registry::item_stack::ItemStack;
11use steel_utils::types::{InteractionHand, UpdateFlags};
12use steel_utils::value_providers::IntProvider;
13use steel_utils::{BlockPos, BlockStateId};
14
15use crate::behavior::{
16    BlockBehavior, BlockHitResult, BlockPlaceContext, InteractionResult, InventoryAccess,
17    PlacementSource, try_drop_experience,
18};
19use crate::entity::Entity;
20use crate::player::Player;
21use crate::world::World;
22
23/// Vanilla `RedStoneOreBlock` behavior shared by both ore variants.
24#[block_behavior]
25pub struct RedStoneOreBlock {
26    block: BlockRef,
27}
28
29const LIT: &BoolProperty = &BlockStateProperties::LIT;
30
31impl RedStoneOreBlock {
32    /// Creates a redstone ore behavior.
33    #[must_use]
34    pub const fn new(block: BlockRef) -> Self {
35        Self { block }
36    }
37
38    fn interact(state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
39        if state.get_value(LIT) {
40            return;
41        }
42
43        world.set_block(pos, state.set_value(LIT, true), UpdateFlags::UPDATE_ALL);
44    }
45}
46
47impl BlockBehavior for RedStoneOreBlock {
48    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
49        Some(self.block.default_state())
50    }
51
52    fn attack(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos, _player: &Player) {
53        Self::interact(state, world, pos);
54    }
55
56    fn step_on(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos, entity: &dyn Entity) {
57        if !entity.is_stepping_carefully() {
58            Self::interact(state, world, pos);
59        }
60        self.default_step_on(state, world, pos, entity);
61    }
62
63    fn use_item_on(
64        &self,
65        state: BlockStateId,
66        world: &Arc<World>,
67        pos: BlockPos,
68        player: &Player,
69        _hand: InteractionHand,
70        hit_result: &BlockHitResult,
71        inv: &mut InventoryAccess,
72    ) -> InteractionResult {
73        Self::interact(state, world, pos);
74
75        let held_item = inv.with_item(|stack| stack.item());
76        if REGISTRY.items.is_block_item(held_item)
77            && BlockPlaceContext::new(world, PlacementSource::player_hand(player, inv), hit_result)
78                .can_place()
79        {
80            InteractionResult::Pass
81        } else {
82            InteractionResult::Success
83        }
84    }
85
86    fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
87        if state.get_value(LIT) {
88            world.set_block(pos, state.set_value(LIT, false), UpdateFlags::UPDATE_ALL);
89        }
90    }
91
92    fn spawn_after_break(
93        &self,
94        _state: BlockStateId,
95        world: &Arc<World>,
96        pos: BlockPos,
97        tool: &ItemStack,
98        drop_experience: bool,
99    ) {
100        if !drop_experience {
101            return;
102        }
103
104        try_drop_experience(
105            world,
106            pos,
107            tool,
108            &IntProvider::Uniform {
109                min_inclusive: 1,
110                max_inclusive: 5,
111            },
112        );
113    }
114
115    // `animateTick` and interaction particles use client-local `Level.addParticle`.
116}
117
118#[cfg(test)]
119mod tests {
120    use std::sync::Arc;
121
122    use glam::DVec3;
123    use steel_registry::entity_type::EntityTypeRef;
124    use steel_registry::init_vanilla_registry;
125    use steel_registry::{vanilla_blocks, vanilla_entities};
126    use steel_utils::{ChunkPos, WorldAabb};
127
128    use super::*;
129    use crate::behavior::{BLOCK_BEHAVIORS, init_behaviors};
130    use crate::entity::EntityBase;
131    use crate::test_support::{fresh_test_world, insert_ready_full_chunk};
132
133    struct StepEntity {
134        base: EntityBase,
135        careful: bool,
136    }
137
138    crate::entity::impl_test_downcast_type!(StepEntity);
139
140    impl StepEntity {
141        fn new(id: i32, world: &Arc<World>, careful: bool) -> Self {
142            Self {
143                base: EntityBase::new(
144                    id,
145                    DVec3::new(8.5, 65.0, 8.5),
146                    vanilla_entities::PIG.dimensions,
147                    Arc::downgrade(world),
148                ),
149                careful,
150            }
151        }
152    }
153
154    impl Entity for StepEntity {
155        fn base(&self) -> &EntityBase {
156            &self.base
157        }
158
159        fn entity_type(&self) -> EntityTypeRef {
160            &vanilla_entities::PIG
161        }
162
163        fn is_stepping_carefully(&self) -> bool {
164            self.careful
165        }
166    }
167
168    #[test]
169    fn both_ore_variants_light_from_steps_and_extinguish_on_random_ticks() {
170        init_vanilla_registry();
171        init_behaviors();
172        let world = fresh_test_world("redstone_ore_steps");
173        let first_pos = BlockPos::new(8, 64, 8);
174        insert_ready_full_chunk(&world, ChunkPos::from_block_pos(first_pos));
175
176        let careful_entity = StepEntity::new(7_003, &world, true);
177        let ordinary_entity = StepEntity::new(7_004, &world, false);
178
179        for (offset, block) in [
180            &vanilla_blocks::REDSTONE_ORE,
181            &vanilla_blocks::DEEPSLATE_REDSTONE_ORE,
182        ]
183        .into_iter()
184        .enumerate()
185        {
186            let pos = first_pos.offset(offset as i32, 0, 0);
187            let unlit = block.default_state();
188            assert!(!unlit.is_randomly_ticking());
189            assert!(world.set_block(pos, unlit, UpdateFlags::UPDATE_NONE));
190
191            let behavior = BLOCK_BEHAVIORS.get_behavior(block);
192            behavior.step_on(unlit, &world, pos, &careful_entity);
193            assert!(!world.get_block_state(pos).get_value(LIT));
194
195            behavior.step_on(unlit, &world, pos, &ordinary_entity);
196            let lit = world.get_block_state(pos);
197            assert!(lit.get_value(LIT));
198            assert!(lit.is_randomly_ticking());
199
200            behavior.random_tick(lit, &world, pos);
201            assert!(!world.get_block_state(pos).get_value(LIT));
202        }
203    }
204
205    #[test]
206    fn world_drop_resources_dispatches_redstone_ore_experience() {
207        init_vanilla_registry();
208        init_behaviors();
209        let world = fresh_test_world("redstone_ore_post_break");
210        let pos = BlockPos::new(8, 64, 8);
211        insert_ready_full_chunk(&world, ChunkPos::from_block_pos(pos));
212
213        world.drop_resources(vanilla_blocks::REDSTONE_ORE.default_state(), pos);
214
215        let query = WorldAabb::new(7.0, 63.0, 7.0, 10.0, 67.0, 10.0);
216        assert!(
217            world
218                .get_entities_in_aabb(&query)
219                .iter()
220                .any(|entity| entity.entity_type() == &vanilla_entities::EXPERIENCE_ORB)
221        );
222    }
223}