steel_core/behavior/blocks/redstone/
copper_bulb_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, BoolProperty, Direction};
9use steel_registry::sound_events;
10use steel_utils::types::UpdateFlags;
11use steel_utils::{BlockPos, BlockStateId};
12
13use crate::behavior::blocks::redstone::{MAX_REDSTONE_SIGNAL, MIN_REDSTONE_SIGNAL};
14use crate::behavior::blocks::{WeatherState, WeatheringCopper};
15use crate::behavior::{BlockBehavior, BlockPlaceContext};
16use crate::world::{LevelReader, SignalGetter as _, World};
17
18#[block_behavior]
20pub struct CopperBulbBlock {
21 block: BlockRef,
22}
23
24const LIT: &BoolProperty = &BlockStateProperties::LIT;
25const POWERED: &BoolProperty = &BlockStateProperties::POWERED;
26
27impl CopperBulbBlock {
28 #[must_use]
30 pub const fn new(block: BlockRef) -> Self {
31 Self { block }
32 }
33
34 fn check_and_flip(state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
35 let signal = world.has_neighbor_signal(pos);
36 let powered = state.get_value(POWERED);
37 if signal == powered {
38 return;
39 }
40
41 let mut new_state = state;
42 if !powered {
43 let lit = !state.get_value(LIT);
44 new_state = new_state.set_value(LIT, lit);
45 world.play_block_sound(
46 if lit {
47 &sound_events::BLOCK_COPPER_BULB_TURN_ON
48 } else {
49 &sound_events::BLOCK_COPPER_BULB_TURN_OFF
50 },
51 pos,
52 1.0,
53 1.0,
54 None,
55 );
56 }
57 world.set_block(
58 pos,
59 new_state.set_value(POWERED, signal),
60 UpdateFlags::UPDATE_ALL,
61 );
62 }
63
64 fn placed(state: BlockStateId, world: &Arc<World>, pos: BlockPos, old_state: BlockStateId) {
65 if old_state.get_block() != state.get_block() {
66 Self::check_and_flip(state, world, pos);
67 }
68 }
69
70 fn analog_output(world: &dyn LevelReader, pos: BlockPos) -> i32 {
71 if world.get_block_state(pos).get_value(LIT) {
72 MAX_REDSTONE_SIGNAL
73 } else {
74 MIN_REDSTONE_SIGNAL
75 }
76 }
77}
78
79impl BlockBehavior for CopperBulbBlock {
80 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
81 Some(self.block.default_state())
82 }
83
84 fn on_place(
85 &self,
86 state: BlockStateId,
87 world: &Arc<World>,
88 pos: BlockPos,
89 old_state: BlockStateId,
90 _moved_by_piston: bool,
91 ) {
92 Self::placed(state, world, pos, old_state);
93 }
94
95 fn handle_neighbor_changed(
96 &self,
97 state: BlockStateId,
98 world: &Arc<World>,
99 pos: BlockPos,
100 _source_block: BlockRef,
101 _moved_by_piston: bool,
102 ) {
103 Self::check_and_flip(state, world, pos);
104 }
105
106 fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
107 true
108 }
109
110 fn get_analog_output_signal(
111 &self,
112 _state: BlockStateId,
113 world: &dyn LevelReader,
114 pos: BlockPos,
115 _direction: Direction,
116 ) -> i32 {
117 Self::analog_output(world, pos)
118 }
119}
120
121#[block_behavior]
123pub struct WeatheringCopperBulbBlock {
124 bulb: CopperBulbBlock,
125 #[json_arg(r#enum = "WeatherState", json = "weather_state")]
126 weathering: WeatheringCopper,
127}
128
129impl WeatheringCopperBulbBlock {
130 #[must_use]
132 pub const fn new(block: BlockRef, weather_state: WeatherState) -> Self {
133 Self {
134 bulb: CopperBulbBlock::new(block),
135 weathering: WeatheringCopper::new(weather_state),
136 }
137 }
138}
139
140impl BlockBehavior for WeatheringCopperBulbBlock {
141 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
142 self.bulb.get_state_for_placement(context)
143 }
144
145 fn on_place(
146 &self,
147 state: BlockStateId,
148 world: &Arc<World>,
149 pos: BlockPos,
150 old_state: BlockStateId,
151 _moved_by_piston: bool,
152 ) {
153 CopperBulbBlock::placed(state, world, pos, old_state);
154 }
155
156 fn handle_neighbor_changed(
157 &self,
158 state: BlockStateId,
159 world: &Arc<World>,
160 pos: BlockPos,
161 _source_block: BlockRef,
162 _moved_by_piston: bool,
163 ) {
164 CopperBulbBlock::check_and_flip(state, world, pos);
165 }
166
167 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
168 self.weathering.change_over_time(state, world, pos);
169 }
170
171 fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
172 true
173 }
174
175 fn get_analog_output_signal(
176 &self,
177 _state: BlockStateId,
178 world: &dyn LevelReader,
179 pos: BlockPos,
180 _direction: Direction,
181 ) -> i32 {
182 CopperBulbBlock::analog_output(world, pos)
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use steel_registry::init_vanilla_registry;
189 use steel_registry::vanilla_blocks;
190 use steel_utils::ChunkPos;
191
192 use super::*;
193 use crate::behavior::{BLOCK_BEHAVIORS, init_behaviors};
194 use crate::test_support::{fresh_test_world, insert_ready_full_chunk};
195
196 #[test]
197 fn bulb_toggles_lit_only_on_rising_edges() {
198 init_vanilla_registry();
199 init_behaviors();
200 let world = fresh_test_world("copper_bulb_edges");
201 let pos = BlockPos::new(8, 64, 8);
202 let power_pos = pos.west();
203 insert_ready_full_chunk(&world, ChunkPos::from_block_pos(pos));
204 assert!(world.set_block(
205 power_pos,
206 vanilla_blocks::REDSTONE_BLOCK.default_state(),
207 UpdateFlags::UPDATE_NONE,
208 ));
209 assert!(world.set_block(
210 pos,
211 vanilla_blocks::WAXED_COPPER_BULB.default_state(),
212 UpdateFlags::UPDATE_ALL,
213 ));
214
215 let first_rise = world.get_block_state(pos);
216 assert!(first_rise.get_value(POWERED));
217 assert!(first_rise.get_value(LIT));
218 let behavior = BLOCK_BEHAVIORS.get_behavior(first_rise.get_block());
219 assert_eq!(
220 behavior.get_analog_output_signal(first_rise, &world, pos, Direction::North),
221 15
222 );
223
224 assert!(world.remove_block(power_pos, false));
225 let falling = world.get_block_state(pos);
226 assert!(!falling.get_value(POWERED));
227 assert!(falling.get_value(LIT));
228
229 assert!(world.set_block(
230 power_pos,
231 vanilla_blocks::REDSTONE_BLOCK.default_state(),
232 UpdateFlags::UPDATE_ALL,
233 ));
234 let second_rise = world.get_block_state(pos);
235 assert!(second_rise.get_value(POWERED));
236 assert!(!second_rise.get_value(LIT));
237 }
238}