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