Skip to main content

steel_core/behavior/blocks/redstone/
lightning_rod_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::BlockRef;
5use steel_registry::blocks::block_state_ext::BlockStateExt as _;
6use steel_registry::blocks::properties::{
7    BlockStateProperties, BoolProperty, Direction, EnumProperty,
8};
9use steel_registry::level_events;
10use steel_utils::types::UpdateFlags;
11use steel_utils::{BlockPos, BlockStateId};
12
13use crate::behavior::block::schedule_water_tick_if_waterlogged;
14use crate::behavior::blocks::redstone::{MAX_REDSTONE_SIGNAL, MIN_REDSTONE_SIGNAL};
15use crate::behavior::{BlockBehavior, BlockPlaceContext};
16use crate::entity::ai::path::PathComputationType;
17use crate::world::{LevelReader, ScheduledTickAccess, SignalQueryContext, World};
18
19/// Lightning rod behavior.
20#[block_behavior]
21pub struct LightningRodBlock {
22    block: BlockRef,
23}
24
25const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
26const FACING: &EnumProperty<Direction> = &BlockStateProperties::FACING;
27const POWERED: &BoolProperty = &BlockStateProperties::POWERED;
28
29const ACTIVATION_TICKS: i32 = 8;
30
31impl LightningRodBlock {
32    /// Creates a lightning rod behavior.
33    #[must_use]
34    pub const fn new(block: BlockRef) -> Self {
35        Self { block }
36    }
37    fn update_neighbors(block: BlockRef, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
38        let front = state.get_value(FACING).opposite();
39        // Experimental redstone orientations are intentionally omitted.
40        world.update_neighbors_at(pos.relative(front), block);
41    }
42    #[expect(
43        dead_code,
44        reason = "We need to call this function once on_lightning_strike gets implemented in BlockBehavior"
45    )]
46    fn on_lightning_strike(
47        block: BlockRef,
48        state: BlockStateId,
49        world: &Arc<World>,
50        pos: BlockPos,
51    ) {
52        world.set_block(pos, state.set_value(POWERED, true), UpdateFlags::UPDATE_ALL);
53        Self::update_neighbors(block, state, world, pos);
54        world.schedule_block_tick_default(pos, block, ACTIVATION_TICKS);
55        world.level_event(
56            level_events::PARTICLES_ELECTRIC_SPARK,
57            pos,
58            state.get_value(FACING).get_axis().ordinal(),
59            None,
60        );
61    }
62}
63
64impl BlockBehavior for LightningRodBlock {
65    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
66        Some(
67            self.block
68                .default_state()
69                .set_value(FACING, context.clicked_face())
70                .set_value(WATERLOGGED, context.is_water_source()),
71        )
72    }
73
74    fn update_shape(
75        &self,
76        state: BlockStateId,
77        world: &dyn ScheduledTickAccess,
78        pos: BlockPos,
79        _direction: Direction,
80        _neighbor_pos: BlockPos,
81        _neighbor_state: BlockStateId,
82    ) -> BlockStateId {
83        schedule_water_tick_if_waterlogged(state, world, pos);
84
85        state
86    }
87
88    fn get_own_signal(
89        &self,
90        state: BlockStateId,
91        _world: &dyn LevelReader,
92        _pos: BlockPos,
93        _context: SignalQueryContext,
94    ) -> i32 {
95        if state.get_value(POWERED) {
96            return MAX_REDSTONE_SIGNAL;
97        }
98        MIN_REDSTONE_SIGNAL
99    }
100
101    fn get_direct_signal(
102        &self,
103        state: BlockStateId,
104        _world: &dyn LevelReader,
105        _pos: BlockPos,
106        direction: Direction,
107        _context: SignalQueryContext,
108    ) -> i32 {
109        if state.get_value(POWERED) && state.get_value(FACING) == direction {
110            return MAX_REDSTONE_SIGNAL;
111        }
112        MIN_REDSTONE_SIGNAL
113    }
114
115    fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
116        world.set_block(
117            pos,
118            state.set_value(POWERED, false),
119            UpdateFlags::UPDATE_ALL,
120        );
121        Self::update_neighbors(self.block, state, world, pos);
122    }
123
124    fn affect_neighbors_after_removal(
125        &self,
126        state: BlockStateId,
127        world: &Arc<World>,
128        pos: BlockPos,
129        _moved_by_piston: bool,
130    ) {
131        if state.get_value(POWERED) {
132            Self::update_neighbors(self.block, state, world, pos);
133        }
134    }
135
136    fn on_place(
137        &self,
138        state: BlockStateId,
139        world: &Arc<World>,
140        pos: BlockPos,
141        old_state: BlockStateId,
142        _moved_by_piston: bool,
143    ) {
144        if state.get_block() != old_state.get_block()
145            && state.get_value(POWERED)
146            && !world.has_scheduled_block_tick(pos, self.block)
147        {
148            world.schedule_block_tick_default(pos, self.block, ACTIVATION_TICKS);
149        }
150    }
151
152    fn is_signal_source(&self, _state: BlockStateId, _context: SignalQueryContext) -> bool {
153        true
154    }
155
156    fn is_pathfindable(
157        &self,
158        _state: BlockStateId,
159        _computation_type: PathComputationType,
160    ) -> bool {
161        false
162    }
163
164    // `animateTick` emits client-local particles only.
165}