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