Skip to main content

steel_core/behavior/blocks/building/
straw_bed_block.rs

1use 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/// straw bed
33#[block_behavior]
34pub struct StrawBedBlock {
35    base: BedBlock,
36}
37
38impl StrawBedBlock {
39    /// straw bed behaviour
40    #[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}