Skip to main content

steel_core/behavior/blocks/container/
ender_chest_block.rs

1//! Ender chest block behavior implementation.
2
3use std::sync::{Arc, Weak};
4
5use steel_macros::block_behavior;
6use steel_registry::blocks::BlockRef;
7use steel_registry::blocks::block_state_ext::BlockStateExt;
8use steel_registry::blocks::properties::BlockStateProperties;
9use steel_registry::{vanilla_block_entity_types, vanilla_custom_stats};
10use steel_utils::{BlockPos, BlockStateId, translations};
11use text_components::TextComponent;
12
13use crate::behavior::InventoryAccess;
14use crate::behavior::block::{BlockBehavior, BlockEntityCreation};
15use crate::behavior::context::{BlockHitResult, BlockPlaceContext, InteractionResult};
16use crate::block_entity::BLOCK_ENTITIES;
17use crate::inventory::menu::kinds::ender_chest;
18use crate::player::Player;
19use crate::world::{World, is_redstone_conductor};
20
21/// The ender chest block behavior.
22#[block_behavior]
23pub struct EnderChestBlock {
24    block: BlockRef,
25}
26
27impl EnderChestBlock {
28    /// Creates a new ender chest block.
29    #[must_use]
30    pub const fn new(block: BlockRef) -> Self {
31        Self { block }
32    }
33}
34
35impl BlockBehavior for EnderChestBlock {
36    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
37        let facing = context.horizontal_direction().opposite();
38
39        Some(
40            self.block
41                .default_state()
42                .set_value(&BlockStateProperties::FACING, facing)
43                .set_value(
44                    &BlockStateProperties::WATERLOGGED,
45                    context.is_water_source(),
46                ),
47        )
48    }
49
50    fn use_without_item(
51        &self,
52        _state: BlockStateId,
53        world: &Arc<World>,
54        pos: BlockPos,
55        player: &Player,
56        _hit_result: &BlockHitResult,
57        _inv: &mut InventoryAccess,
58    ) -> InteractionResult {
59        let Some(block_entity) = world.get_block_entity(pos) else {
60            return InteractionResult::Success;
61        };
62        if block_entity.get_type() != &vanilla_block_entity_types::ENDER_CHEST {
63            return InteractionResult::Success;
64        }
65
66        let above_pos = pos.above();
67        if is_redstone_conductor(world.as_ref(), world.get_block_state(above_pos), above_pos) {
68            return InteractionResult::Success;
69        }
70
71        let inventory = player.inventory.clone();
72        let container = player.ender_chest_inventory.clone();
73        let chest = Arc::downgrade(&block_entity);
74        player.open_menu(
75            TextComponent::translated(translations::CONTAINER_ENDERCHEST.msg()),
76            move |context| {
77                // open only after active chest has closed, since
78                // closing an ender chest menu clears the active chest.
79                container.lock().set_active_chest(chest);
80                ender_chest(inventory, context.container_id, container)
81            },
82        );
83
84        player.award_custom_stat(&vanilla_custom_stats::OPEN_ENDERCHEST);
85        // TODO: Anger nearby piglins
86
87        InteractionResult::Success
88    }
89
90    fn new_block_entity(
91        &self,
92        level: Weak<World>,
93        pos: BlockPos,
94        state: BlockStateId,
95    ) -> BlockEntityCreation {
96        BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
97            &vanilla_block_entity_types::ENDER_CHEST,
98            level,
99            pos,
100            state,
101        ))
102    }
103}