steel_core/behavior/blocks/redstone/
observer_block.rs1use std::sync::Arc;
4
5use steel_macros::block_behavior;
6use steel_registry::blocks::BlockRef;
7use steel_registry::blocks::block_state_ext::BlockStateExt as _;
8use steel_registry::blocks::properties::{BlockStateProperties, Direction};
9use steel_utils::types::UpdateFlags;
10use steel_utils::{BlockPos, BlockStateId};
11
12use crate::behavior::{BlockBehavior, BlockPlaceContext};
13use crate::world::{LevelReader, ScheduledTickAccess, SignalQueryContext, World};
14
15const PULSE_DELAY: i32 = 2;
16const PLACEMENT_RESET_FLAGS: UpdateFlags =
17 UpdateFlags::UPDATE_CLIENTS.union(UpdateFlags::UPDATE_KNOWN_SHAPE);
18
19#[block_behavior]
21pub struct ObserverBlock {
22 block: BlockRef,
23}
24
25impl ObserverBlock {
26 #[must_use]
28 pub const fn new(block: BlockRef) -> Self {
29 Self { block }
30 }
31
32 fn start_signal(&self, ticks: &dyn ScheduledTickAccess, pos: BlockPos) {
33 if !ticks.has_scheduled_block_tick(pos, self.block) {
34 ticks.schedule_block_tick_default(pos, self.block, PULSE_DELAY);
35 }
36 }
37
38 fn update_neighbors_in_front(&self, world: &Arc<World>, pos: BlockPos, state: BlockStateId) {
39 let direction = state.get_value(&BlockStateProperties::FACING);
40 let output_pos = pos.relative(direction.opposite());
41 world.neighbor_changed(output_pos, self.block);
42 world.update_neighbors_at_except_from_facing(output_pos, self.block, direction);
43 }
44
45 fn own_signal(state: BlockStateId) -> i32 {
46 if state.get_value(&BlockStateProperties::POWERED) {
47 15
48 } else {
49 0
50 }
51 }
52}
53
54impl BlockBehavior for ObserverBlock {
55 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
56 Some(self.block.default_state().set_value(
57 &BlockStateProperties::FACING,
58 context.get_nearest_looking_direction(),
59 ))
60 }
61
62 fn update_shape(
63 &self,
64 state: BlockStateId,
65 world: &dyn ScheduledTickAccess,
66 pos: BlockPos,
67 direction: Direction,
68 _neighbor_pos: BlockPos,
69 _neighbor_state: BlockStateId,
70 ) -> BlockStateId {
71 if state.get_value(&BlockStateProperties::FACING) == direction
72 && !state.get_value(&BlockStateProperties::POWERED)
73 {
74 self.start_signal(world, pos);
75 }
76 state
77 }
78
79 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
80 if state.get_value(&BlockStateProperties::POWERED) {
81 world.set_block(
82 pos,
83 state.set_value(&BlockStateProperties::POWERED, false),
84 UpdateFlags::UPDATE_CLIENTS,
85 );
86 } else {
87 world.set_block(
88 pos,
89 state.set_value(&BlockStateProperties::POWERED, true),
90 UpdateFlags::UPDATE_CLIENTS,
91 );
92 world.schedule_block_tick_default(pos, self.block, PULSE_DELAY);
93 }
94 self.update_neighbors_in_front(world, pos, state);
95 }
96
97 fn on_place(
98 &self,
99 state: BlockStateId,
100 world: &Arc<World>,
101 pos: BlockPos,
102 old_state: BlockStateId,
103 _moved_by_piston: bool,
104 ) {
105 if state.get_block() == old_state.get_block()
106 || !state.get_value(&BlockStateProperties::POWERED)
107 || world.has_scheduled_block_tick(pos, self.block)
108 {
109 return;
110 }
111
112 let reset_state = state.set_value(&BlockStateProperties::POWERED, false);
113 world.set_block(pos, reset_state, PLACEMENT_RESET_FLAGS);
114 self.update_neighbors_in_front(world, pos, reset_state);
115 }
116
117 fn affect_neighbors_after_removal(
118 &self,
119 state: BlockStateId,
120 world: &Arc<World>,
121 pos: BlockPos,
122 _moved_by_piston: bool,
123 ) {
124 if state.get_value(&BlockStateProperties::POWERED)
125 && world.has_scheduled_block_tick(pos, self.block)
126 {
127 self.update_neighbors_in_front(
128 world,
129 pos,
130 state.set_value(&BlockStateProperties::POWERED, false),
131 );
132 }
133 }
134
135 fn is_signal_source(&self, _state: BlockStateId, _context: SignalQueryContext) -> bool {
136 true
137 }
138
139 fn get_own_signal(
140 &self,
141 state: BlockStateId,
142 _world: &dyn LevelReader,
143 _pos: BlockPos,
144 _context: SignalQueryContext,
145 ) -> i32 {
146 Self::own_signal(state)
147 }
148
149 fn get_signal(
150 &self,
151 state: BlockStateId,
152 _world: &dyn LevelReader,
153 _pos: BlockPos,
154 direction: Direction,
155 _context: SignalQueryContext,
156 ) -> i32 {
157 if state.get_value(&BlockStateProperties::FACING) == direction {
158 Self::own_signal(state)
159 } else {
160 0
161 }
162 }
163
164 fn get_direct_signal(
165 &self,
166 state: BlockStateId,
167 world: &dyn LevelReader,
168 pos: BlockPos,
169 direction: Direction,
170 context: SignalQueryContext,
171 ) -> i32 {
172 self.get_signal(state, world, pos, direction, context)
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use steel_registry::init_vanilla_registry;
179 use steel_registry::vanilla_blocks;
180
181 use super::*;
182 use crate::test_support::TestLevel;
183
184 fn observer_state(facing: Direction, powered: bool) -> BlockStateId {
185 vanilla_blocks::OBSERVER
186 .default_state()
187 .set_value(&BlockStateProperties::FACING, facing)
188 .set_value(&BlockStateProperties::POWERED, powered)
189 }
190
191 #[test]
192 fn observed_face_update_schedules_one_two_tick_pulse() {
193 init_vanilla_registry();
194 let observer = ObserverBlock::new(&vanilla_blocks::OBSERVER);
195 let pos = BlockPos::new(0, 64, 0);
196 let state = observer_state(Direction::East, false);
197 let level = TestLevel::default();
198
199 observer.update_shape(
200 state,
201 &level,
202 pos,
203 Direction::East,
204 pos.east(),
205 vanilla_blocks::STONE.default_state(),
206 );
207 let scheduled = level.scheduled_block_ticks.borrow();
208 assert_eq!(scheduled.len(), 1);
209 assert_eq!(scheduled[0].pos, pos);
210 assert_eq!(scheduled[0].block, &vanilla_blocks::OBSERVER);
211 assert_eq!(scheduled[0].delay, PULSE_DELAY);
212 }
213
214 #[test]
215 fn observer_output_is_powered_and_directional() {
216 init_vanilla_registry();
217 let observer = ObserverBlock::new(&vanilla_blocks::OBSERVER);
218 let state = observer_state(Direction::Down, true);
219 let level = TestLevel::default();
220 let pos = BlockPos::new(0, 64, 0);
221
222 assert_eq!(
223 observer.get_signal(
224 state,
225 &level,
226 pos,
227 Direction::Down,
228 SignalQueryContext::DEFAULT,
229 ),
230 15
231 );
232 assert_eq!(
233 observer.get_signal(
234 state,
235 &level,
236 pos,
237 Direction::Up,
238 SignalQueryContext::DEFAULT,
239 ),
240 0
241 );
242 }
243}