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