Skip to main content

steel_core/behavior/blocks/container/
barrel_block.rs

1//! Barrel block behavior implementation.
2//!
3//! Opens a 27-slot container menu when right-clicked.
4
5use std::sync::{Arc, Weak};
6
7use steel_macros::block_behavior;
8use steel_registry::blocks::BlockRef;
9use steel_registry::blocks::block_state_ext::BlockStateExt;
10use steel_registry::blocks::properties::{BlockStateProperties, Direction, EnumProperty};
11use steel_registry::{vanilla_block_entity_types, vanilla_custom_stats};
12use steel_utils::{BlockPos, BlockStateId, translations};
13use text_components::TextComponent;
14
15use crate::behavior::InventoryAccess;
16use crate::behavior::block::{BlockBehavior, BlockEntityCreation};
17use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult};
18use crate::block_entity::BLOCK_ENTITIES;
19use crate::inventory::container::calculate_redstone_signal_from_container;
20use crate::inventory::lock::{ContainerLockGuard, ContainerRef};
21use crate::inventory::menu::kinds::chest;
22use crate::player::Player;
23use crate::world::{LevelReader, World};
24
25/// Behavior for barrel blocks.
26///
27/// Barrels are container block entities with 27 slots (3x9 grid).
28/// They use the same menu as chests but cannot form double containers.
29#[block_behavior]
30pub struct BarrelBlock {
31    block: BlockRef,
32}
33
34const FACING: &EnumProperty<Direction> = &BlockStateProperties::FACING;
35
36impl BarrelBlock {
37    /// Creates a new barrel block behavior.
38    #[must_use]
39    pub const fn new(block: BlockRef) -> Self {
40        Self { block }
41    }
42}
43
44impl BlockBehavior for BarrelBlock {
45    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
46        // Barrel faces opposite to the player's look direction (all 6 directions).
47        let facing = context.get_nearest_looking_direction().opposite();
48
49        Some(self.block.default_state().set_value(FACING, facing))
50    }
51
52    fn use_without_item(
53        &self,
54        _state: BlockStateId,
55        world: &Arc<World>,
56        pos: BlockPos,
57        player: &Player,
58        _hit_result: &BlockHitResult,
59        _inv: &mut InventoryAccess,
60    ) -> InteractionResult {
61        // Get the block entity
62        let Some(block_entity) = world.get_block_entity(pos) else {
63            return InteractionResult::Pass;
64        };
65
66        // Create a container reference from the block entity
67        let Some(container_ref) = ContainerRef::from_block_entity(block_entity) else {
68            return InteractionResult::Pass;
69        };
70
71        // Open the chest menu (3 rows for barrel)
72        let inventory = player.inventory.clone();
73        player.open_menu(
74            TextComponent::translated(translations::CONTAINER_BARREL.msg()),
75            move |context| chest(inventory, context.container_id, container_ref, 3),
76        );
77
78        player.award_custom_stat(&vanilla_custom_stats::OPEN_BARREL);
79        // TODO: Anger nearby piglins (PiglinAi.angerNearbyPiglins)
80        // TODO: Implement ContainerOpenersCounter to track open state, play sounds,
81        //       and update OPEN block property. Requires scheduled block ticks (scheduleTick)
82        //       for recheck functionality. See vanilla BarrelBlockEntity and ContainerOpenersCounter.
83
84        InteractionResult::Success
85    }
86
87    fn new_block_entity(
88        &self,
89        level: Weak<World>,
90        pos: BlockPos,
91        state: BlockStateId,
92    ) -> BlockEntityCreation {
93        BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
94            &vanilla_block_entity_types::BARREL,
95            level,
96            pos,
97            state,
98        ))
99    }
100
101    fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
102        true
103    }
104
105    fn get_analog_output_signal(
106        &self,
107        _state: BlockStateId,
108        world: &dyn LevelReader,
109        pos: BlockPos,
110        _direction: Direction,
111    ) -> i32 {
112        // Get the block entity and calculate signal from container contents
113        let Some(container_ref) = world
114            .get_block_entity(pos)
115            .and_then(ContainerRef::from_block_entity)
116        else {
117            return 0;
118        };
119        let guard = ContainerLockGuard::lock_all(&[&container_ref]);
120        guard
121            .get(container_ref.container_id())
122            .map_or(0, |container| {
123                calculate_redstone_signal_from_container(container)
124            })
125    }
126}