Skip to main content

steel_core/behavior/blocks/decoration/
torch_block.rs

1//! Torch block implementations.
2//!
3//! Torches come in two forms:
4//! - Standing torches (TorchBlock): placed on top of blocks
5//! - Wall torches (WallTorchBlock): placed on the side of blocks
6//!
7//! Both break when their supporting block is removed.
8
9use steel_macros::block_behavior;
10use steel_registry::REGISTRY;
11use steel_registry::blocks::BlockRef;
12use steel_registry::blocks::block_state_ext::BlockStateExt;
13use steel_registry::blocks::properties::{BlockStateProperties, Direction, EnumProperty};
14use steel_registry::blocks::shapes::SupportType;
15use steel_registry::vanilla_blocks;
16use steel_utils::{BlockPos, BlockStateId};
17
18use crate::behavior::block::BlockBehavior;
19use crate::behavior::context::BlockPlaceContext;
20use crate::world::{LevelReader, ScheduledTickAccess};
21
22/// Behavior for standing torch blocks (torch, `soul_torch`, `copper_torch`).
23///
24/// Standing torches are placed on top of blocks and require center support
25/// from the block below to survive.
26#[block_behavior]
27pub struct TorchBlock {
28    block: BlockRef,
29}
30
31const HORIZONTAL_FACING: &EnumProperty<Direction> = &BlockStateProperties::HORIZONTAL_FACING;
32
33impl TorchBlock {
34    /// Creates a new standing torch block behavior.
35    #[must_use]
36    pub const fn new(block: BlockRef) -> Self {
37        Self { block }
38    }
39}
40
41impl BlockBehavior for TorchBlock {
42    /// Checks if a torch can survive at the given position.
43    /// Requires the block below to provide center support on its top face.
44    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
45        let below_pos = pos.below();
46        let below_state = world.get_block_state(below_pos);
47        world.is_face_sturdy_for(below_state, below_pos, Direction::Up, SupportType::Center)
48    }
49
50    fn update_shape(
51        &self,
52        state: BlockStateId,
53        world: &dyn ScheduledTickAccess,
54        pos: BlockPos,
55        direction: Direction,
56        _neighbor_pos: BlockPos,
57        _neighbor_state: BlockStateId,
58    ) -> BlockStateId {
59        // Standing torches break when the block below is removed
60        if direction == Direction::Down && !self.can_survive(state, world, pos) {
61            return REGISTRY.blocks.get_default_state_id(&vanilla_blocks::AIR);
62        }
63        state
64    }
65
66    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
67        let default_state = self.block.default_state();
68        // Check if we can place on the block below
69        if !self.can_survive(default_state, context.world, context.place_pos()) {
70            return None;
71        }
72
73        Some(default_state)
74    }
75}
76
77/// Behavior for wall torch blocks (`wall_torch`, `soul_wall_torch`, `copper_wall_torch`).
78///
79/// Wall torches are placed on the side of blocks and require a sturdy face
80/// from the block they're attached to.
81#[block_behavior]
82pub struct WallTorchBlock {
83    block: BlockRef,
84}
85
86impl WallTorchBlock {
87    /// Creates a new wall torch block behavior.
88    #[must_use]
89    pub const fn new(block: BlockRef) -> Self {
90        Self { block }
91    }
92}
93
94impl BlockBehavior for WallTorchBlock {
95    /// Checks if a wall torch can survive at the given position.
96    /// Requires the block behind (opposite of facing) to provide a sturdy face.
97    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
98        let facing: Direction = state.get_value(HORIZONTAL_FACING);
99        let attach_direction = facing.opposite();
100        let attach_pos = attach_direction.relative(pos);
101        let attach_state = world.get_block_state(attach_pos);
102        world.is_face_sturdy(attach_state, attach_pos, facing)
103    }
104
105    fn update_shape(
106        &self,
107        state: BlockStateId,
108        world: &dyn ScheduledTickAccess,
109        pos: BlockPos,
110        direction: Direction,
111        _neighbor_pos: BlockPos,
112        _neighbor_state: BlockStateId,
113    ) -> BlockStateId {
114        // Wall torches break when the block they're attached to is removed
115        let facing: Direction = state.get_value(HORIZONTAL_FACING);
116        let attach_direction = facing.opposite();
117
118        if direction == attach_direction && !self.can_survive(state, world, pos) {
119            return REGISTRY.blocks.get_default_state_id(&vanilla_blocks::AIR);
120        }
121        state
122    }
123
124    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
125        // Vanilla iterates through getNearestLookingDirections() and uses the opposite
126        // of each horizontal direction as the facing (torch points away from wall)
127        for direction in context.get_nearest_looking_directions() {
128            if direction.is_horizontal() {
129                let facing = direction.opposite();
130                let state = self
131                    .block
132                    .default_state()
133                    .set_value(HORIZONTAL_FACING, facing);
134                if self.can_survive(state, context.world, context.place_pos()) {
135                    return Some(state);
136                }
137            }
138        }
139
140        None
141    }
142}