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