steel_core/behavior/blocks/container/
shulker_box_block.rs1use std::sync::{Arc, Weak};
2
3use steel_macros::block_behavior;
4use steel_registry::{
5 block_entity_type::BlockEntityTypeRef,
6 blocks::{
7 BlockRef,
8 block_state_ext::BlockStateExt,
9 properties::{BlockStateProperties, EnumProperty},
10 shapes::VoxelShape,
11 },
12 item_stack::ItemStack,
13 items::item::BlockHitResult,
14 vanilla_block_entity_types, vanilla_custom_stats,
15};
16use steel_utils::{BlockLocalAabb, BlockPos, BlockStateId, Direction, Downcast, translations};
17use text_components::TextComponent;
18
19use crate::{
20 behavior::{
21 BlockBehavior, BlockCollisionBoxes, BlockCollisionContext, BlockEntityCreation,
22 BlockLootContext, BlockPlaceContext, InteractionResult, InventoryAccess,
23 },
24 block_entity::{
25 BLOCK_ENTITIES, BlockEntity, BlockEntityTicker,
26 entities::{AnimationStatus, ShulkerBoxBlockEntity},
27 },
28 inventory::{
29 container::calculate_redstone_signal_from_container,
30 lock::{ContainerLockGuard, ContainerRef},
31 menu::kinds::shulker_box,
32 },
33 player::Player,
34 world::{LevelReader, World},
35};
36
37#[block_behavior]
39pub struct ShulkerBoxBlock {
40 block: BlockRef,
41}
42
43impl ShulkerBoxBlock {
44 pub const FACING: &EnumProperty<Direction> = &BlockStateProperties::FACING;
46
47 #[must_use]
49 pub const fn new(block: BlockRef) -> Self {
50 Self { block }
51 }
52}
53
54const OPEN_SUPPORT_DOWN_BOXES: &[BlockLocalAabb] =
58 &[BlockLocalAabb::new(0.0, 0.0, 0.0, 1.0, 0.0625, 1.0)];
59const OPEN_SUPPORT_UP_BOXES: &[BlockLocalAabb] =
60 &[BlockLocalAabb::new(0.0, 0.9375, 0.0, 1.0, 1.0, 1.0)];
61const OPEN_SUPPORT_NORTH_BOXES: &[BlockLocalAabb] =
62 &[BlockLocalAabb::new(0.0, 0.0, 0.0, 1.0, 1.0, 0.0625)];
63const OPEN_SUPPORT_SOUTH_BOXES: &[BlockLocalAabb] =
64 &[BlockLocalAabb::new(0.0, 0.0, 0.9375, 1.0, 1.0, 1.0)];
65const OPEN_SUPPORT_WEST_BOXES: &[BlockLocalAabb] =
66 &[BlockLocalAabb::new(0.0, 0.0, 0.0, 0.0625, 1.0, 1.0)];
67const OPEN_SUPPORT_EAST_BOXES: &[BlockLocalAabb] =
68 &[BlockLocalAabb::new(0.9375, 0.0, 0.0, 1.0, 1.0, 1.0)];
69
70const fn open_support_shape(direction: Direction) -> VoxelShape {
73 VoxelShape::from_boxes(match direction {
74 Direction::Down => OPEN_SUPPORT_DOWN_BOXES,
75 Direction::Up => OPEN_SUPPORT_UP_BOXES,
76 Direction::North => OPEN_SUPPORT_NORTH_BOXES,
77 Direction::South => OPEN_SUPPORT_SOUTH_BOXES,
78 Direction::West => OPEN_SUPPORT_WEST_BOXES,
79 Direction::East => OPEN_SUPPORT_EAST_BOXES,
80 })
81}
82
83impl BlockBehavior for ShulkerBoxBlock {
84 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
85 let facing = context.clicked_face();
86 Some(
87 self.block
88 .default_state()
89 .set_value(&BlockStateProperties::FACING, facing),
90 )
91 }
92
93 fn use_without_item(
94 &self,
95 state: BlockStateId,
96 world: &Arc<World>,
97 pos: BlockPos,
98 player: &Player,
99 _hit_result: &BlockHitResult,
100 _inv: &mut InventoryAccess,
101 ) -> InteractionResult {
102 if let Some(block_entity) = world.get_block_entity(pos)
103 && let Some(block_entity) = block_entity.downcast_ref::<ShulkerBoxBlockEntity>()
104 && let Some(container_ref) = block_entity.container_ref()
105 && block_entity.can_open(state, world, pos)
106 {
107 let inventory = player.inventory.clone();
108 player.open_menu(
109 TextComponent::translated(translations::CONTAINER_SHULKER_BOX.msg()),
110 move |context| shulker_box(inventory, context.container_id, container_ref),
111 );
112
113 player.award_custom_stat(&vanilla_custom_stats::OPEN_SHULKER_BOX);
114 }
116 InteractionResult::Success
117 }
118
119 fn player_will_destroy(
120 &self,
121 state: BlockStateId,
122 world: &Arc<World>,
123 pos: BlockPos,
124 player: &Player,
125 ) -> BlockStateId {
126 let Some(block_entity) = world.get_block_entity(pos) else {
127 return state;
128 };
129
130 let Some(shulker_box_block_entity) = block_entity.downcast_ref::<ShulkerBoxBlockEntity>()
131 else {
132 return state;
133 };
134
135 if player.prevents_block_drops() && !shulker_box_block_entity.is_empty() {
136 let item = shulker_box_block_entity.shulker_box_as_item(state);
137 world.pop_resource(pos, item);
138 } else {
139 }
141
142 state
143 }
144
145 fn get_drops(
146 &self,
147 state: BlockStateId,
148 context: &BlockLootContext<'_>,
149 ) -> Option<Vec<ItemStack>> {
150 let block_entity = context.block_entity()?;
151 let shulker_box_block_entity = block_entity.downcast_ref::<ShulkerBoxBlockEntity>()?;
152
153 let item = shulker_box_block_entity.shulker_box_as_item(state);
154 Some(vec![item])
155 }
156
157 fn new_block_entity(
158 &self,
159 level: Weak<World>,
160 pos: BlockPos,
161 state: BlockStateId,
162 ) -> BlockEntityCreation {
163 BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
164 &vanilla_block_entity_types::SHULKER_BOX,
165 level,
166 pos,
167 state,
168 ))
169 }
170
171 fn get_block_entity_ticker(
172 &self,
173 _world: &Arc<World>,
174 _state: BlockStateId,
175 block_entity_type: BlockEntityTypeRef,
176 ) -> Option<BlockEntityTicker> {
177 BlockEntityTicker::for_matching_entity_tick(
178 block_entity_type,
179 &vanilla_block_entity_types::SHULKER_BOX,
180 )
181 }
182
183 fn trigger_event(
184 &self,
185 _state: BlockStateId,
186 world: &Arc<World>,
187 pos: BlockPos,
188 event: i32,
189 data: i32,
190 ) -> bool {
191 let Some(block_entity) = world.get_block_entity(pos) else {
192 return false;
193 };
194 block_entity.trigger_event(event, data)
195 }
196
197 fn get_collision_boxes(
202 &self,
203 state: BlockStateId,
204 world: &dyn LevelReader,
205 pos: BlockPos,
206 _context: BlockCollisionContext,
207 ) -> BlockCollisionBoxes {
208 if let Some(block_entity) = world.get_block_entity(pos)
209 && let Some(shulker_box_block_entity) =
210 block_entity.downcast_ref::<ShulkerBoxBlockEntity>()
211 {
212 return BlockCollisionBoxes::from_slice(&[
213 shulker_box_block_entity.get_bounding_box(state)
214 ]);
215 }
216
217 BlockCollisionBoxes::from_slice(VoxelShape::FULL_BLOCK.boxes())
218 }
219
220 fn get_block_support_boxes(
221 &self,
222 state: BlockStateId,
223 world: &dyn LevelReader,
224 pos: BlockPos,
225 ) -> BlockCollisionBoxes {
226 let shape = if let Some(block_entity) = world.get_block_entity(pos)
227 && let Some(shulker_box_block_entity) =
228 block_entity.downcast_ref::<ShulkerBoxBlockEntity>()
229 && !matches!(
230 shulker_box_block_entity.animation_status(),
231 AnimationStatus::Closed
232 ) {
233 open_support_shape(state.get_value(Self::FACING).opposite())
234 } else {
235 VoxelShape::FULL_BLOCK
236 };
237
238 BlockCollisionBoxes::from_slice(shape.boxes())
239 }
240
241 fn affect_neighbors_after_removal(
242 &self,
243 _state: BlockStateId,
244 world: &Arc<World>,
245 pos: BlockPos,
246 _moved_by_piston: bool,
247 ) {
248 world.update_neighbor_for_output_signal(pos, self.block);
249 }
250
251 fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
252 true
253 }
254
255 fn get_analog_output_signal(
256 &self,
257 _state: BlockStateId,
258 world: &dyn LevelReader,
259 pos: BlockPos,
260 _direction: Direction,
261 ) -> i32 {
262 let Some(container_ref) = world
263 .get_block_entity(pos)
264 .and_then(ContainerRef::from_block_entity)
265 else {
266 return 0;
267 };
268 let guard = ContainerLockGuard::lock_all(&[&container_ref]);
269 guard
270 .get(container_ref.container_id())
271 .map_or(0, |container| {
272 calculate_redstone_signal_from_container(container)
273 })
274 }
275
276 fn fits_inside_container_items(&self) -> bool {
277 false
278 }
279}