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