steel_core/behavior/blocks/decoration/
candle_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, BoolProperty},
9 },
10 item_stack::ItemStack,
11 items::item::BlockHitResult,
12 vanilla_blocks, vanilla_items,
13};
14use steel_utils::{
15 BlockPos, BlockStateId, Direction,
16 types::{InteractionHand, UpdateFlags},
17};
18
19use crate::{
20 behavior::{
21 BlockBehavior, BlockPlaceContext, InteractionResult, InventoryAccess,
22 blocks::{CakeBlock, CandleBlock},
23 },
24 block_entity::SharedBlockEntity,
25 entity::projectile::Projectile,
26 player::Player,
27 world::{ClipHitResult, LevelReader, ScheduledTickAccess, World},
28};
29
30#[block_behavior]
34pub struct CandleCakeBlock {
35 block: BlockRef,
36}
37
38const LIT: &BoolProperty = &BlockStateProperties::LIT;
39
40impl CandleCakeBlock {
41 #[must_use]
43 pub const fn new(block: BlockRef) -> Self {
44 Self { block }
45 }
46}
47
48impl BlockBehavior for CandleCakeBlock {
49 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
50 if context
51 .world
52 .get_block_state(context.place_pos().below())
53 .is_solid()
54 {
55 Some(self.block.default_state())
56 } else {
57 None
58 }
59 }
60
61 fn use_item_on(
62 &self,
63 state: BlockStateId,
64 world: &Arc<World>,
65 pos: BlockPos,
66 player: &Player,
67 _hand: InteractionHand,
68 hit_result: &BlockHitResult,
69 inv: &mut InventoryAccess,
70 ) -> InteractionResult {
71 let (is_fire_charge, is_flint_and_steel, is_empty) = inv.with_item(|item_stack| {
72 (
73 item_stack.is(&vanilla_items::FIRE_CHARGE),
74 item_stack.is(&vanilla_items::FLINT_AND_STEEL),
75 item_stack.is_empty(),
76 )
77 });
78 if is_fire_charge || is_flint_and_steel {
79 return InteractionResult::Pass; } else if (hit_result.location.y - f64::from(hit_result.block_pos.y())) > 0.5
81 && is_empty
82 && state.get_value(LIT)
83 {
84 CandleBlock::extinguish(Some(player), state, world, pos);
85 return InteractionResult::Success;
86 }
87 InteractionResult::TryEmptyHandInteraction
88 }
89
90 fn use_without_item(
91 &self,
92 state: BlockStateId,
93 world: &Arc<World>,
94 pos: BlockPos,
95 player: &Player,
96 _hit_result: &BlockHitResult,
97 _inv: &mut InventoryAccess,
98 ) -> InteractionResult {
99 let result = CakeBlock::eat(world, pos, vanilla_blocks::CAKE.default_state(), player);
100 if result.consumes_action() {
101 world.drop_resources(state, pos);
102 }
103 result
104 }
105
106 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
107 world.get_block_state(pos.below()).is_solid()
108 }
109
110 fn update_shape(
111 &self,
112 state: BlockStateId,
113 world: &dyn ScheduledTickAccess,
114 pos: BlockPos,
115 direction: steel_utils::Direction,
116 _neighbor_pos: BlockPos,
117 _neighbor_state: BlockStateId,
118 ) -> BlockStateId {
119 if direction == Direction::Down && !self.can_survive(state, world, pos) {
120 vanilla_blocks::AIR.default_state()
121 } else {
122 state
123 }
124 }
125
126 fn on_projectile_hit(
127 &self,
128 state: BlockStateId,
129 world: &Arc<World>,
130 hit: &ClipHitResult,
131 projectile: &dyn Projectile,
132 ) {
133 let Some(lit_state) = CandleBlock::projectile_lit_state(state, projectile.is_on_fire())
134 else {
135 return;
136 };
137 world.set_block(hit.block_pos, lit_state, UpdateFlags::UPDATE_ALL_IMMEDIATE);
138 }
139
140 fn get_clone_item_stack(
141 &self,
142 _block: BlockRef,
143 _state: BlockStateId,
144 _block_entity: Option<SharedBlockEntity>,
145 _include_data: bool,
146 ) -> Option<ItemStack> {
147 Some(ItemStack::new(&vanilla_items::CAKE))
148 }
149
150 fn get_analog_output_signal(
151 &self,
152 _state: BlockStateId,
153 _world: &World,
154 _pos: BlockPos,
155 _direction: Direction,
156 ) -> i32 {
157 CakeBlock::analog_output_signal(0)
158 }
159
160 fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
161 true
162 }
163}