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