Skip to main content

steel_core/behavior/blocks/vegetation/
cocoa_block.rs

1use std::sync::Arc;
2
3use rand::Rng;
4use steel_macros::block_behavior;
5use steel_registry::{
6    blocks::{
7        BlockRef,
8        block_state_ext::BlockStateExt,
9        properties::{BlockStateProperties, Direction, EnumProperty, IntProperty},
10    },
11    item_stack::ItemStack,
12    vanilla_block_tags::BlockTag,
13    vanilla_blocks, vanilla_items,
14};
15use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
16
17use crate::{
18    behavior::{
19        BlockBehavior, blocks::vegetation::bonemealable::Bonemealable, context::BlockPlaceContext,
20    },
21    entity::ai::path::PathComputationType,
22    world::{LevelReader, ScheduledTickAccess, World},
23};
24
25const MAX_AGE: u8 = 2;
26const AGE_PROPERTY: IntProperty = BlockStateProperties::AGE_2;
27const FACING_PROPERTY: EnumProperty<Direction> = BlockStateProperties::HORIZONTAL_FACING;
28
29/// Cocoa Block behavior
30#[block_behavior]
31pub struct CocoaBlock {
32    block: BlockRef,
33}
34
35impl CocoaBlock {
36    /// Creates a new cocoa block
37    #[must_use]
38    pub const fn new(block: BlockRef) -> Self {
39        Self { block }
40    }
41
42    fn age(state: BlockStateId) -> u8 {
43        state.get_value(&AGE_PROPERTY)
44    }
45
46    fn with_age(state: BlockStateId, age: u8) -> BlockStateId {
47        state.set_value(&AGE_PROPERTY, age)
48    }
49}
50
51impl BlockBehavior for CocoaBlock {
52    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
53        let facing: Direction = state.get_value(&FACING_PROPERTY);
54        let support = world.get_block_state(pos.relative(facing));
55        support.get_block().has_tag(&BlockTag::SUPPORTS_COCOA)
56    }
57
58    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
59        for direction in context.get_nearest_looking_directions() {
60            if !direction.is_horizontal() {
61                continue;
62            }
63
64            let state = self
65                .block
66                .default_state()
67                .set_value(&FACING_PROPERTY, direction);
68            if self.can_survive(state, context.world, context.place_pos()) {
69                return Some(state);
70            }
71        }
72
73        None
74    }
75
76    fn update_shape(
77        &self,
78        state: BlockStateId,
79        world: &dyn ScheduledTickAccess,
80        pos: BlockPos,
81        direction: Direction,
82        _neighbor_pos: BlockPos,
83        _neighbor_state: BlockStateId,
84    ) -> BlockStateId {
85        let facing: Direction = state.get_value(&FACING_PROPERTY);
86        if direction == facing && !self.can_survive(state, world, pos) {
87            return vanilla_blocks::AIR.default_state();
88        }
89
90        state
91    }
92
93    fn is_pathfindable(
94        &self,
95        _state: BlockStateId,
96        _computation_type: PathComputationType,
97    ) -> bool {
98        false
99    }
100
101    fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
102        if rand::random_range(0..5) != 0 {
103            return;
104        }
105
106        let age = Self::age(state);
107        if age >= MAX_AGE {
108            return;
109        }
110
111        world.set_block(
112            pos,
113            Self::with_age(state, age + 1),
114            UpdateFlags::UPDATE_CLIENTS,
115        );
116    }
117
118    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
119        Some(self)
120    }
121
122    fn get_clone_item_stack(
123        &self,
124        _block: BlockRef,
125        _state: BlockStateId,
126        _include_data: bool,
127    ) -> Option<ItemStack> {
128        Some(ItemStack::new(&vanilla_items::COCOA_BEANS))
129    }
130}
131
132impl Bonemealable for CocoaBlock {
133    fn is_valid_bonemeal_target(
134        &self,
135        state: BlockStateId,
136        _world: &dyn LevelReader,
137        _pos: BlockPos,
138    ) -> bool {
139        Self::age(state) < MAX_AGE
140    }
141
142    fn perform_bonemeal(
143        &self,
144        state: BlockStateId,
145        world: &Arc<World>,
146        _rng: &mut dyn Rng,
147        pos: BlockPos,
148    ) {
149        world.set_block(
150            pos,
151            Self::with_age(state, Self::age(state) + 1),
152            UpdateFlags::UPDATE_CLIENTS,
153        );
154    }
155}