Skip to main content

steel_core/behavior/items/
hanging_sign_item.rs

1//! Hanging sign item behavior implementation.
2//! Wall and standing signs are handled by `StandingAndWallBlockItem`, not here.
3
4use std::sync::Arc;
5use steel_macros::item_behavior;
6use steel_registry::REGISTRY;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt;
9use steel_registry::blocks::properties::{BlockStateProperties, Direction};
10use steel_registry::blocks::shapes::SupportType;
11use steel_registry::vanilla_block_tags::BlockTag;
12use steel_utils::{BlockPos, BlockStateId};
13
14use crate::behavior::context::{InteractionResult, UseOnContext};
15use crate::behavior::items::block_item::SurvivalCheck;
16use crate::behavior::{BlockItem, ItemBehavior, StandingAndWallBlockItem};
17use crate::world::{LevelReader as _, World};
18
19/// Behavior for hanging sign items that place hanging sign blocks.
20///
21/// Hanging signs can be placed as ceiling hanging signs or wall hanging signs.
22#[item_behavior]
23pub struct HangingSignItem {
24    /// The ceiling hanging sign block.
25    #[json_arg(vanilla_blocks, json = "block")]
26    pub ceiling_block: BlockRef,
27    /// The wall hanging sign block.
28    #[json_arg(vanilla_blocks, json = "wall_block")]
29    pub wall_block: BlockRef,
30
31    base: StandingAndWallBlockItem,
32}
33
34impl HangingSignItem {
35    /// Creates a new hanging sign item behavior.
36    #[must_use]
37    pub const fn new(ceiling_block: BlockRef, wall_block: BlockRef) -> Self {
38        Self {
39            ceiling_block,
40            wall_block,
41            base: StandingAndWallBlockItem::new(ceiling_block, wall_block, Direction::Up),
42        }
43    }
44}
45
46/// Checks if a wall hanging sign can attach to a neighboring block.
47///
48/// This matches vanilla's `WallHangingSignBlock.canAttachTo`.
49fn can_attach_to(
50    world: &Arc<World>,
51    sign_facing: Direction,
52    attach_pos: BlockPos,
53    attach_face: Direction,
54) -> bool {
55    let attach_state = world.get_block_state(attach_pos);
56    let attach_block = REGISTRY.blocks.by_state_id(attach_state);
57
58    if let Some(block) = attach_block
59        && block.has_tag(&BlockTag::WALL_HANGING_SIGNS)
60        && let Some(neighbor_facing) =
61            attach_state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING)
62    {
63        return neighbor_facing.axis() == sign_facing.axis();
64    }
65    world.is_face_sturdy_for(attach_state, attach_pos, attach_face, SupportType::Full)
66}
67
68/// Checks if a wall hanging sign can be placed at the given position.
69///
70/// This matches vanilla's `WallHangingSignBlock.canPlace` which is called
71/// from `HangingSignItem.canPlace` in addition to `canSurvive`.
72fn can_wall_hanging_sign_place(world: &Arc<World>, state: BlockStateId, pos: BlockPos) -> bool {
73    let Some(facing) = state.try_get_value(&BlockStateProperties::HORIZONTAL_FACING) else {
74        return false;
75    };
76
77    let clockwise = facing.rotate_y_clockwise();
78    let counter_clockwise = facing.rotate_y_counter_clockwise();
79
80    let can_attach_clockwise = {
81        let attach_pos = clockwise.relative(pos);
82        can_attach_to(world, facing, attach_pos, counter_clockwise)
83    };
84
85    let can_attach_counter = {
86        let attach_pos = counter_clockwise.relative(pos);
87        can_attach_to(world, facing, attach_pos, clockwise)
88    };
89
90    can_attach_clockwise || can_attach_counter
91}
92
93/// Checks if a wall hanging sign block state can be placed.
94///
95/// This matches vanilla's `HangingSignItem.canPlace` override which adds
96/// an additional check for `WallHangingSignBlock.canPlace`.
97///
98/// Survival is already checked by `get_state_for_placement`.
99fn can_place_hanging_sign(world: &Arc<World>, state: BlockStateId, pos: BlockPos) -> bool {
100    let is_wall_hanging_sign = REGISTRY
101        .blocks
102        .by_state_id(state)
103        .is_some_and(|block| block.has_tag(&BlockTag::WALL_HANGING_SIGNS));
104
105    !is_wall_hanging_sign || can_wall_hanging_sign_place(world, state, pos)
106}
107
108impl ItemBehavior for HangingSignItem {
109    // Survival check required here because it was a true function and this should be better
110    fn use_on(&self, context: &mut UseOnContext) -> InteractionResult {
111        self.base.base.place_with_policy(
112            context.build_place_context(),
113            Some,
114            SurvivalCheck::Skipped,
115            BlockItem::place_block,
116            self.ceiling_block.config.sound_type.place_sound,
117            |context| {
118                self.base
119                    .get_placement_state(context, can_place_hanging_sign)
120            },
121        )
122    }
123}