Skip to main content

steel_core/behavior/blocks/decoration/
cake_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::{
5    blocks::{
6        BlockRef,
7        block_state_ext::BlockStateExt,
8        properties::{BlockStateProperties, IntProperty},
9    },
10    items::item::BlockHitResult,
11    sound_events, vanilla_blocks, vanilla_custom_stats,
12    vanilla_item_tags::ItemTag,
13};
14use steel_utils::{
15    BlockPos, BlockStateId, Direction,
16    types::{InteractionHand, UpdateFlags},
17};
18
19use crate::{
20    behavior::{
21        BlockBehavior, BlockPlaceContext, InteractionResult, InventoryAccess, candle_cakes,
22    },
23    entity::Entity,
24    player::Player,
25    world::{LevelReader, ScheduledTickAccess, World},
26};
27
28/// Behavior for Cakes
29#[block_behavior]
30pub struct CakeBlock {
31    block: BlockRef,
32}
33
34const BITES: &IntProperty = &BlockStateProperties::BITES;
35
36impl CakeBlock {
37    /// Cakes a new Cake Block Behavior
38    #[must_use]
39    pub const fn new(block: BlockRef) -> Self {
40        Self { block }
41    }
42
43    /// Eats a slice of the cake for the player and updates the block
44    pub fn eat(
45        world: &Arc<World>,
46        pos: BlockPos,
47        state: BlockStateId,
48        player: &Player,
49    ) -> InteractionResult {
50        if player.can_eat(false) {
51            player.award_custom_stat(&vanilla_custom_stats::EAT_CAKE_SLICE);
52            let mut food_data = player.food_data.lock();
53            food_data.eat(2, 0.1);
54            let bites = state.get_value(BITES);
55            let new_state = if bites < 6 {
56                state.set_value(BITES, bites + 1)
57            } else {
58                vanilla_blocks::AIR.default_state()
59            };
60            world.set_block(pos, new_state, UpdateFlags::UPDATE_ALL);
61            return InteractionResult::Success;
62        }
63        InteractionResult::Pass
64    }
65
66    /// Analog Output Signal for the Amount of Bites
67    #[must_use]
68    pub const fn analog_output_signal(bites: i32) -> i32 {
69        (7 - bites) * 2
70    }
71}
72
73impl BlockBehavior for CakeBlock {
74    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
75        if context
76            .world
77            .get_block_state(context.place_pos().below())
78            .is_solid()
79        {
80            Some(self.block.default_state())
81        } else {
82            None
83        }
84    }
85
86    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
87        world.get_block_state(pos.below()).is_solid()
88    }
89
90    fn update_shape(
91        &self,
92        state: BlockStateId,
93        world: &dyn ScheduledTickAccess,
94        pos: BlockPos,
95        direction: Direction,
96        _neighbor_pos: BlockPos,
97        _neighbor_state: BlockStateId,
98    ) -> BlockStateId {
99        if direction == Direction::Down && !self.can_survive(state, world, pos) {
100            vanilla_blocks::AIR.default_state()
101        } else {
102            state
103        }
104    }
105
106    fn use_without_item(
107        &self,
108        state: BlockStateId,
109        world: &Arc<World>,
110        pos: BlockPos,
111        player: &Player,
112        _hit_result: &BlockHitResult,
113        _inv: &mut InventoryAccess,
114    ) -> InteractionResult {
115        if Self::eat(world, pos, state, player).consumes_action() {
116            return InteractionResult::Success;
117        }
118
119        InteractionResult::Pass
120    }
121
122    fn use_item_on(
123        &self,
124        state: BlockStateId,
125        world: &Arc<World>,
126        pos: BlockPos,
127        player: &Player,
128        _hand: InteractionHand,
129        _hit_result: &BlockHitResult,
130        inv: &mut InventoryAccess,
131    ) -> InteractionResult {
132        if state.get_value(BITES) == 0 {
133            let has_infinite_materials = player.has_infinite_materials();
134            let candle_cake = inv.with_item(|item_stack| {
135                let item = item_stack.item();
136                if !item.has_tag(&ItemTag::CANDLES) {
137                    return None;
138                }
139                let candle_cake = candle_cakes::candle_to_candle_cake(item)?;
140                item_stack.consume_one(has_infinite_materials);
141                Some(candle_cake)
142            });
143            let Some(candle_cake) = candle_cake else {
144                return InteractionResult::TryEmptyHandInteraction;
145            };
146            world.play_block_sound(
147                &sound_events::BLOCK_CAKE_ADD_CANDLE,
148                pos,
149                1.0,
150                1.0,
151                Some(player.id()),
152            );
153            world.set_block(pos, candle_cake.default_state(), UpdateFlags::UPDATE_ALL);
154            return InteractionResult::Success;
155        }
156        InteractionResult::TryEmptyHandInteraction
157    }
158
159    fn get_analog_output_signal(
160        &self,
161        state: BlockStateId,
162        _world: &dyn LevelReader,
163        _pos: BlockPos,
164        _direction: Direction,
165    ) -> i32 {
166        Self::analog_output_signal(i32::from(state.get_value(BITES)))
167    }
168
169    fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
170        true
171    }
172}