steel_core/behavior/blocks/vegetation/
cactus_block.rs1use std::sync::Arc;
7
8use steel_macros::block_behavior;
9use steel_registry::blocks::BlockRef;
10use steel_registry::blocks::block_state_ext::BlockStateExt;
11use steel_registry::blocks::properties::{BlockStateProperties, Direction};
12use steel_registry::vanilla_block_tags::BlockTag;
13use steel_registry::vanilla_blocks;
14use steel_registry::vanilla_damage_types;
15use steel_registry::vanilla_fluid_tags;
16use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
17
18use crate::behavior::block::BlockBehavior;
19use crate::behavior::context::BlockPlaceContext;
20use crate::entity::ai::path::PathComputationType;
21use crate::entity::damage::DamageSource;
22use crate::entity::{Entity, InsideBlockEffectCollector};
23use crate::world::{LevelReader, ScheduledTickAccess, World};
24
25const MAX_CACTUS_HEIGHT: u32 = 3;
27
28const CACTUS_FLOWER_AGE: u8 = 8;
30
31const FLOWER_CHANCE_SMALL: f64 = 0.1;
33
34const FLOWER_CHANCE_TALL: f64 = 0.25;
36
37#[block_behavior]
45pub struct CactusBlock {
46 block: BlockRef,
47}
48
49impl CactusBlock {
50 #[must_use]
52 pub const fn new(block: BlockRef) -> Self {
53 Self { block }
54 }
55}
56
57impl BlockBehavior for CactusBlock {
58 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
66 for dir in [
68 Direction::North,
69 Direction::South,
70 Direction::East,
71 Direction::West,
72 ] {
73 let neighbor_pos = dir.relative(pos);
74 let neighbor = world.get_block_state(neighbor_pos);
75 if neighbor.is_solid() {
76 return false;
77 }
78
79 let fluid = neighbor.get_fluid_state();
80 if fluid.fluid_id.has_tag(&vanilla_fluid_tags::FluidTag::LAVA) {
81 return false;
82 }
83 }
84
85 let below_pos = pos.below();
87 let below = world.get_block_state(below_pos);
88 let below_block = below.get_block();
89
90 let valid_below = below_block == &vanilla_blocks::CACTUS
91 || below_block.has_tag(&BlockTag::SUPPORTS_CACTUS);
92
93 if !valid_below {
94 return false;
95 }
96
97 let above = world.get_block_state(pos.above());
99
100 if above.has_fluid() {
101 return false;
102 }
103
104 true
105 }
106
107 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
108 let default_state = self.block.default_state();
109 if self.can_survive(default_state, context.world, context.place_pos()) {
110 Some(default_state)
111 } else {
112 None
113 }
114 }
115
116 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
117 if !self.can_survive(state, world, pos) {
118 world.destroy_block(pos, true);
119 }
120 }
121
122 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
123 let above_pos = pos.above();
124
125 if !world.get_block_state(above_pos).is_air() {
126 return;
127 }
128
129 let mut height = 1u32;
131 let age = state.get_value(&BlockStateProperties::AGE_15);
132
133 while world
134 .get_block_state(pos.offset(0, -(height as i32), 0))
135 .get_block()
136 == &vanilla_blocks::CACTUS
137 {
138 height += 1;
139 if height == MAX_CACTUS_HEIGHT && age == 15 {
140 return;
141 }
142 }
143
144 if age == CACTUS_FLOWER_AGE
146 && self.can_survive(self.block.default_state(), world, above_pos)
147 {
148 let chance = if height >= MAX_CACTUS_HEIGHT {
149 FLOWER_CHANCE_TALL
150 } else {
151 FLOWER_CHANCE_SMALL
152 };
153 if rand::random::<f64>() <= chance {
154 world.set_block(
155 above_pos,
156 vanilla_blocks::CACTUS_FLOWER.default_state(),
157 UpdateFlags::UPDATE_ALL,
158 );
159 }
160 } else if age == 15 && height < MAX_CACTUS_HEIGHT {
161 world.set_block(
162 above_pos,
163 vanilla_blocks::CACTUS.default_state(),
164 UpdateFlags::UPDATE_ALL,
165 );
166 let new_state = state.set_value(&BlockStateProperties::AGE_15, 0);
167 world.set_block(pos, new_state, UpdateFlags::UPDATE_NONE);
168 world.neighbor_changed(above_pos, &vanilla_blocks::CACTUS);
169 }
170
171 if age < 15 {
172 let new_state = state.set_value(&BlockStateProperties::AGE_15, age + 1);
173 world.set_block(pos, new_state, UpdateFlags::UPDATE_NONE);
174 }
175 }
176
177 fn update_shape(
178 &self,
179 state: BlockStateId,
180 world: &dyn ScheduledTickAccess,
181 pos: BlockPos,
182 _direction: Direction,
183 _neighbor_pos: BlockPos,
184 _neighbor_state: BlockStateId,
185 ) -> BlockStateId {
186 if !self.can_survive(state, world, pos) {
188 world.schedule_block_tick_default(pos, self.block, 1);
189 }
190 state
191 }
192
193 fn entity_inside(
194 &self,
195 _state: BlockStateId,
196 world: &Arc<World>,
197 _pos: BlockPos,
198 entity: &dyn Entity,
199 _effect_collector: &mut InsideBlockEffectCollector,
200 _is_precise: bool,
201 ) {
202 entity.hurt(
203 world,
204 &DamageSource::environment(&vanilla_damage_types::CACTUS),
205 1.0,
206 );
207 }
208
209 fn is_pathfindable(
210 &self,
211 _state: BlockStateId,
212 _computation_type: PathComputationType,
213 ) -> bool {
214 false
215 }
216}