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::{
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/// Behavior for Candle Cakes
31/// TODO:
32/// - [ ] onExplosion
33#[block_behavior]
34pub struct CandleCakeBlock {
35    block: BlockRef,
36}
37
38const LIT: &BoolProperty = &BlockStateProperties::LIT;
39
40impl CandleCakeBlock {
41    /// Creates a new Candle Cake Block Behavior
42    #[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; // lighting of candles and candle cakes is handled by the flint and steel/fire charge implementation
80        } 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}