steel_core/behavior/blocks/building/
straw_bed_block.rs1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::{
5 blocks::{
6 BlockRef,
7 block_state_ext::BlockStateExt as _,
8 properties::{BedPart, BlockStateProperties, BoolProperty, EnumProperty},
9 },
10 sound_events, vanilla_blocks, vanilla_custom_stats,
11};
12use steel_utils::{BlockPos, BlockStateId, Direction, axis::Axis, types::UpdateFlags};
13use text_components::TextComponent;
14use text_components::translation::TranslatedMessage;
15
16use crate::{
17 behavior::{
18 BlockBehavior, BlockHitResult, BlockPlaceContext, InteractionResult, InventoryAccess,
19 PlacementSource,
20 },
21 entity::ai::path::PathComputationType,
22 player::Player,
23 world::{LevelReader, ScheduledTickAccess, World},
24};
25
26use super::BedBlock;
27
28const BED_PART: &EnumProperty<BedPart> = &BlockStateProperties::BED_PART;
29const FACING: &EnumProperty<Direction> = &BlockStateProperties::HORIZONTAL_FACING;
30const OCCUPIED: &BoolProperty = &BlockStateProperties::OCCUPIED;
31
32#[block_behavior]
34pub struct StrawBedBlock {
35 base: BedBlock,
36}
37
38impl StrawBedBlock {
39 #[must_use]
41 pub const fn new(block: BlockRef) -> Self {
42 Self {
43 base: BedBlock::new(block),
44 }
45 }
46
47 fn destroy_bed(world: &Arc<World>, pos: BlockPos) {
48 world.play_block_sound(
49 &sound_events::BLOCK_STRAW_BED_BREAK_LEAVE,
50 pos,
51 1.0,
52 1.0,
53 None,
54 );
55 world.set_block(
56 pos,
57 vanilla_blocks::AIR.default_state(),
58 UpdateFlags::UPDATE_ALL,
59 );
60 }
61}
62
63impl BlockBehavior for StrawBedBlock {
64 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
65 self.base.get_state_for_placement(context)
66 }
67
68 fn is_pathfindable(&self, state: BlockStateId, computation_type: PathComputationType) -> bool {
69 self.base.is_pathfindable(state, computation_type)
70 }
71
72 fn is_bed(&self) -> bool {
73 true
74 }
75
76 fn get_sleep_height(
77 &self,
78 state: BlockStateId,
79 world: &dyn LevelReader,
80 pos: BlockPos,
81 ) -> Option<f64> {
82 let foot_state = if state.get_value(BED_PART) == BedPart::Head {
83 let foot_pos = state.get_value(FACING).opposite().relative(pos);
84
85 let foot_state = world.get_block_state(foot_pos);
86 (foot_state.get_block() == self.base.block
87 && foot_state.get_value(BED_PART) == BedPart::Foot)
88 .then_some(foot_state)?
89 } else {
90 state
91 };
92
93 let shape = foot_state.get_static_outline_shape();
94 (!shape.is_empty()).then(|| shape.max(Axis::Y))
95 }
96
97 fn on_stop_sleeping(&self, _state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
98 if world.dimension_type.straw_bed_rule.destroy_on_leave {
99 Self::destroy_bed(world, pos);
100 }
101 }
102
103 fn player_will_destroy(
104 &self,
105 state: BlockStateId,
106 world: &Arc<World>,
107 pos: BlockPos,
108 player: &Player,
109 ) -> BlockStateId {
110 self.base.player_will_destroy(state, world, pos, player)
111 }
112
113 fn update_shape(
114 &self,
115 state: BlockStateId,
116 world: &dyn ScheduledTickAccess,
117 pos: BlockPos,
118 direction: Direction,
119 neighbor_pos: BlockPos,
120 neighbor_state: BlockStateId,
121 ) -> BlockStateId {
122 self.base
123 .update_shape(state, world, pos, direction, neighbor_pos, neighbor_state)
124 }
125
126 fn set_placed_by(
127 &self,
128 state: BlockStateId,
129 world: &Arc<World>,
130 pos: BlockPos,
131 source: &PlacementSource<'_>,
132 ) {
133 self.base.set_placed_by(state, world, pos, source);
134 }
135
136 fn use_without_item(
137 &self,
138 state: BlockStateId,
139 world: &Arc<World>,
140 pos: BlockPos,
141 player: &Player,
142 _hit_result: &BlockHitResult,
143 _inv: &mut InventoryAccess,
144 ) -> InteractionResult {
145 let Some((head_state, head_pos)) = self.base.head_state_and_pos(world, state, pos) else {
146 return InteractionResult::Consume;
147 };
148
149 let rule = &world.dimension_type.straw_bed_rule;
150 if rule.destroy_on_use {
151 Self::destroy_bed(world, head_pos);
152 return InteractionResult::SuccessServer;
153 }
154
155 if head_state.get_value(OCCUPIED) {
156 player.send_overlay_message(&TextComponent::translated(TranslatedMessage {
157 key: "block.minecraft.bed.occupied".into(),
158 fallback: None,
159 args: None,
160 }));
161 return InteractionResult::SuccessServer;
162 }
163
164 if let Err(problem) =
165 player.start_sleep_in_bed(head_pos, rule, &vanilla_custom_stats::SLEEP_IN_STRAW_BED)
166 && let Some(message) = problem.message()
167 {
168 player.send_overlay_message(message);
169 }
170 InteractionResult::SuccessServer
171 }
172}