steel_core/behavior/blocks/decoration/
cake_block.rs1use 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#[block_behavior]
30pub struct CakeBlock {
31 block: BlockRef,
32}
33
34const BITES: &IntProperty = &BlockStateProperties::BITES;
35
36impl CakeBlock {
37 #[must_use]
39 pub const fn new(block: BlockRef) -> Self {
40 Self { block }
41 }
42
43 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 #[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}