Skip to main content

steel_core/behavior/blocks/decoration/
candle_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    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/// Behavior for Candle Cakes
26/// TODO:
27/// - [ ] animation ticks
28/// - [ ] onExplosion
29#[block_behavior]
30pub struct CandleCakeBlock {
31    block: BlockRef,
32}
33
34impl CandleCakeBlock {
35    /// Creates a new Candle Cake Block Behavior
36    #[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; // lighting of candles and candle cakes is handled by the flint and steel/fire charge implementation
74        } 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            // TODO: particles!
84            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}