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