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};
11use steel_registry::vanilla_block_entity_types;
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
34impl BarrelBlock {
35    /// Creates a new barrel block behavior.
36    #[must_use]
37    pub const fn new(block: BlockRef) -> Self {
38        Self { block }
39    }
40}
41
42impl BlockBehavior for BarrelBlock {
43    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
44        // Barrel faces opposite to the player's look direction (all 6 directions).
45        let facing = context.get_nearest_looking_direction().opposite();
46
47        Some(
48            self.block
49                .default_state()
50                .set_value(&BlockStateProperties::FACING, facing),
51        )
52    }
53
54    fn use_without_item(
55        &self,
56        _state: BlockStateId,
57        world: &Arc<World>,
58        pos: BlockPos,
59        player: &Player,
60        _hit_result: &BlockHitResult,
61        _inv: &mut InventoryAccess,
62    ) -> InteractionResult {
63        // Get the block entity
64        let Some(block_entity) = world.get_block_entity(pos) else {
65            return InteractionResult::Pass;
66        };
67
68        // Create a container reference from the block entity
69        let Some(container_ref) = ContainerRef::from_block_entity(block_entity) else {
70            return InteractionResult::Pass;
71        };
72
73        // Open the chest menu (3 rows for barrel)
74        let inventory = player.inventory.clone();
75        player.open_menu(
76            TextComponent::translated(translations::CONTAINER_BARREL.msg()),
77            move |context| chest(inventory, context.container_id, container_ref, 3),
78        );
79
80        // TODO: Award stat OPEN_BARREL
81        // TODO: Anger nearby piglins (PiglinAi.angerNearbyPiglins)
82        // TODO: Implement ContainerOpenersCounter to track open state, play sounds,
83        //       and update OPEN block property. Requires scheduled block ticks (scheduleTick)
84        //       for recheck functionality. See vanilla BarrelBlockEntity and ContainerOpenersCounter.
85
86        InteractionResult::Success
87    }
88
89    fn new_block_entity(
90        &self,
91        level: Weak<World>,
92        pos: BlockPos,
93        state: BlockStateId,
94    ) -> BlockEntityCreation {
95        BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
96            &vanilla_block_entity_types::BARREL,
97            level,
98            pos,
99            state,
100        ))
101    }
102
103    fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
104        true
105    }
106
107    fn get_analog_output_signal(
108        &self,
109        _state: BlockStateId,
110        world: &dyn LevelReader,
111        pos: BlockPos,
112        _direction: Direction,
113    ) -> i32 {
114        // Get the block entity and calculate signal from container contents
115        let Some(container_ref) = world
116            .get_block_entity(pos)
117            .and_then(ContainerRef::from_block_entity)
118        else {
119            return 0;
120        };
121        let guard = ContainerLockGuard::lock_all(&[&container_ref]);
122        guard
123            .get(container_ref.container_id())
124            .map_or(0, |container| {
125                calculate_redstone_signal_from_container(container)
126            })
127    }
128}