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_fixture = fresh_test_world("redstone_ore_steps");
173        let world = &world_fixture.world;
174        let first_pos = BlockPos::new(8, 64, 8);
175        insert_ready_full_chunk(world, ChunkPos::from_block_pos(first_pos));
176
177        let careful_entity = StepEntity::new(7_003, world, true);
178        let ordinary_entity = StepEntity::new(7_004, world, false);
179
180        for (offset, block) in [
181            &vanilla_blocks::REDSTONE_ORE,
182            &vanilla_blocks::DEEPSLATE_REDSTONE_ORE,
183        ]
184        .into_iter()
185        .enumerate()
186        {
187            let pos = first_pos.offset(offset as i32, 0, 0);
188            let unlit = block.default_state();
189            assert!(!unlit.is_randomly_ticking());
190            assert!(world.set_block(pos, unlit, UpdateFlags::UPDATE_NONE));
191
192            let behavior = BLOCK_BEHAVIORS.get_behavior(block);
193            behavior.step_on(unlit, world, pos, &careful_entity);
194            assert!(!world.get_block_state(pos).get_value(LIT));
195
196            behavior.step_on(unlit, world, pos, &ordinary_entity);
197            let lit = world.get_block_state(pos);
198            assert!(lit.get_value(LIT));
199            assert!(lit.is_randomly_ticking());
200
201            behavior.random_tick(lit, world, pos);
202            assert!(!world.get_block_state(pos).get_value(LIT));
203        }
204    }
205
206    #[test]
207    fn world_drop_resources_dispatches_redstone_ore_experience() {
208        init_vanilla_registry();
209        init_behaviors();
210        let world_fixture = fresh_test_world("redstone_ore_post_break");
211        let world = &world_fixture.world;
212        let pos = BlockPos::new(8, 64, 8);
213        insert_ready_full_chunk(world, ChunkPos::from_block_pos(pos));
214
215        world.drop_resources(vanilla_blocks::REDSTONE_ORE.default_state(), pos);
216
217        let query = WorldAabb::new(7.0, 63.0, 7.0, 10.0, 67.0, 10.0);
218        assert!(
219            world
220                .get_entities_in_aabb(&query)
221                .iter()
222                .any(|entity| entity.entity_type() == &vanilla_entities::EXPERIENCE_ORB)
223        );
224    }
225}