steel_core/behavior/blocks/redstone/diode/
base.rs1use std::sync::Arc;
4
5use steel_registry::blocks::BlockRef;
6use steel_registry::blocks::block_state_ext::BlockStateExt as _;
7use steel_registry::blocks::properties::{
8 BlockStateProperties, BoolProperty, Direction, EnumProperty, IntProperty,
9};
10use steel_registry::blocks::shapes::SupportType;
11use steel_registry::vanilla_blocks;
12use steel_utils::types::UpdateFlags;
13use steel_utils::{BlockPos, BlockStateId};
14
15use crate::behavior::BLOCK_BEHAVIORS;
16use crate::behavior::BlockPlaceContext;
17use crate::behavior::blocks::redstone::{MAX_REDSTONE_SIGNAL, MIN_REDSTONE_SIGNAL};
18use crate::world::{
19 LevelReader, SignalQueryContext, World, get_control_input_signal,
20 get_signal as get_redstone_signal, tick_scheduler::TickPriority,
21};
22
23const HORIZONTAL_FACING: &EnumProperty<Direction> = &BlockStateProperties::HORIZONTAL_FACING;
24const POWER: &IntProperty = &BlockStateProperties::POWER;
25const POWERED: &BoolProperty = &BlockStateProperties::POWERED;
26
27pub(super) struct DiodeBlock {
29 pub(super) block: BlockRef,
30}
31
32impl DiodeBlock {
33 #[must_use]
34 pub(super) const fn new(block: BlockRef) -> Self {
35 Self { block }
36 }
37
38 pub(super) fn can_survive_on(
39 level: &dyn LevelReader,
40 neighbor_pos: BlockPos,
41 neighbor_state: BlockStateId,
42 ) -> bool {
43 level.is_face_sturdy_for(
44 neighbor_state,
45 neighbor_pos,
46 Direction::Up,
47 SupportType::Rigid,
48 )
49 }
50
51 pub(super) fn can_survive(level: &dyn LevelReader, pos: BlockPos) -> bool {
52 let below_pos = pos.below();
53 Self::can_survive_on(level, below_pos, level.get_block_state(below_pos))
54 }
55
56 pub(super) fn state_for_placement(&self, context: &BlockPlaceContext<'_>) -> BlockStateId {
57 self.block
58 .default_state()
59 .set_value(HORIZONTAL_FACING, context.horizontal_direction().opposite())
60 }
61
62 pub(super) fn get_input_signal(
63 level: &dyn LevelReader,
64 pos: BlockPos,
65 state: BlockStateId,
66 ) -> i32 {
67 let direction = state.get_value(HORIZONTAL_FACING);
68 let target_pos = pos.relative(direction);
69 let input = get_redstone_signal(level, target_pos, direction, SignalQueryContext::DEFAULT);
70 if input >= MAX_REDSTONE_SIGNAL {
71 return input;
72 }
73
74 let target_state = level.get_block_state(target_pos);
75 let wire_power = if target_state.get_block() == &vanilla_blocks::REDSTONE_WIRE {
76 i32::from(target_state.get_value(POWER))
77 } else {
78 MIN_REDSTONE_SIGNAL
79 };
80 input.max(wire_power)
81 }
82
83 pub(super) fn get_alternate_signal(
84 level: &dyn LevelReader,
85 pos: BlockPos,
86 state: BlockStateId,
87 side_input_diodes_only: bool,
88 ) -> i32 {
89 let direction = state.get_value(HORIZONTAL_FACING);
90 let clockwise = direction.rotate_y_clockwise();
91 let counter_clockwise = direction.rotate_y_counter_clockwise();
92 get_control_input_signal(
93 level,
94 pos.relative(clockwise),
95 clockwise,
96 side_input_diodes_only,
97 )
98 .max(get_control_input_signal(
99 level,
100 pos.relative(counter_clockwise),
101 counter_clockwise,
102 side_input_diodes_only,
103 ))
104 }
105
106 pub(super) fn should_prioritize(
107 level: &dyn LevelReader,
108 pos: BlockPos,
109 state: BlockStateId,
110 ) -> bool {
111 let direction = state.get_value(HORIZONTAL_FACING).opposite();
112 let opposite_state = level.get_block_state(pos.relative(direction));
113 BLOCK_BEHAVIORS
114 .get_behavior(opposite_state.get_block())
115 .is_diode()
116 && opposite_state.get_value(HORIZONTAL_FACING) != direction
117 }
118
119 pub(super) fn check_tick_on_neighbor(
120 &self,
121 world: &Arc<World>,
122 pos: BlockPos,
123 state: BlockStateId,
124 is_locked: bool,
125 should_turn_on: bool,
126 delay: i32,
127 ) {
128 if is_locked {
129 return;
130 }
131
132 let powered = state.get_value(POWERED);
133 if powered == should_turn_on || world.will_tick_block_this_tick(pos, self.block) {
134 return;
135 }
136
137 let priority = Self::tick_priority(world.as_ref(), pos, state, powered);
138 world.schedule_block_tick(pos, self.block, delay, priority);
139 }
140
141 pub(super) fn tick_priority(
142 level: &dyn LevelReader,
143 pos: BlockPos,
144 state: BlockStateId,
145 powered: bool,
146 ) -> TickPriority {
147 if Self::should_prioritize(level, pos, state) {
148 TickPriority::ExtremelyHigh
149 } else if powered {
150 TickPriority::VeryHigh
151 } else {
152 TickPriority::High
153 }
154 }
155
156 pub(super) fn tick(
157 &self,
158 state: BlockStateId,
159 world: &Arc<World>,
160 pos: BlockPos,
161 is_locked: bool,
162 should_turn_on: bool,
163 delay: i32,
164 ) {
165 if is_locked {
166 return;
167 }
168
169 let powered = state.get_value(POWERED);
170 if powered && !should_turn_on {
171 world.set_block(
172 pos,
173 state.set_value(POWERED, false),
174 UpdateFlags::UPDATE_CLIENTS,
175 );
176 } else if !powered {
177 world.set_block(
178 pos,
179 state.set_value(POWERED, true),
180 UpdateFlags::UPDATE_CLIENTS,
181 );
182 if !should_turn_on {
183 world.schedule_block_tick(pos, self.block, delay, TickPriority::VeryHigh);
184 }
185 }
186 }
187
188 pub(super) fn handle_neighbor_changed(
189 &self,
190 state: BlockStateId,
191 world: &Arc<World>,
192 pos: BlockPos,
193 on_supported: impl FnOnce(),
194 ) {
195 if world.get_block_state(pos).get_block() != self.block {
196 return;
197 }
198 if Self::can_survive(world.as_ref(), pos) {
199 on_supported();
200 return;
201 }
202
203 world.drop_resources(state, pos);
204 world.remove_block(pos, false);
205 for direction in Direction::ALL {
206 world.update_neighbors_at(pos.relative(direction), self.block);
207 }
208 }
209
210 pub(super) fn set_placed_by(&self, world: &Arc<World>, pos: BlockPos, should_turn_on: bool) {
211 if should_turn_on {
212 world.schedule_block_tick_default(pos, self.block, 1);
213 }
214 }
215
216 pub(super) fn on_place(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
217 self.update_neighbors_in_front(world, pos, state);
218 }
219
220 pub(super) fn affect_neighbors_after_removal(
221 &self,
222 state: BlockStateId,
223 world: &Arc<World>,
224 pos: BlockPos,
225 moved_by_piston: bool,
226 ) {
227 if !moved_by_piston {
228 self.update_neighbors_in_front(world, pos, state);
229 }
230 }
231
232 pub(super) fn update_neighbors_in_front(
233 &self,
234 world: &Arc<World>,
235 pos: BlockPos,
236 state: BlockStateId,
237 ) {
238 let direction = state.get_value(HORIZONTAL_FACING);
239 let opposite_pos = pos.relative(direction.opposite());
240 world.neighbor_changed(opposite_pos, self.block);
241 world.update_neighbors_at_except_from_facing(opposite_pos, self.block, direction);
242 }
243
244 pub(super) fn own_signal(state: BlockStateId, output_signal: i32) -> i32 {
245 if state.get_value(POWERED) {
246 output_signal
247 } else {
248 0
249 }
250 }
251
252 pub(super) fn signal(state: BlockStateId, direction: Direction, output_signal: i32) -> i32 {
253 if state.get_value(HORIZONTAL_FACING) == direction {
254 Self::own_signal(state, output_signal)
255 } else {
256 0
257 }
258 }
259}