steel_core/behavior/blocks/redstone/
redstone_ore_block.rs1use 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#[block_behavior]
25pub struct RedStoneOreBlock {
26 block: BlockRef,
27}
28
29const LIT: &BoolProperty = &BlockStateProperties::LIT;
30
31impl RedStoneOreBlock {
32 #[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 }
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}