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, IntProperty};
12use steel_registry::vanilla_block_tags::BlockTag;
13use steel_registry::vanilla_blocks;
14use steel_registry::vanilla_damage_types;
15use steel_registry::vanilla_fluid_tags::FluidTag;
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
49const AGE: &IntProperty = &BlockStateProperties::AGE_15;
50
51impl CactusBlock {
52 #[must_use]
54 pub const fn new(block: BlockRef) -> Self {
55 Self { block }
56 }
57}
58
59impl BlockBehavior for CactusBlock {
60 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
68 for dir in [
70 Direction::North,
71 Direction::South,
72 Direction::East,
73 Direction::West,
74 ] {
75 let neighbor_pos = dir.relative(pos);
76 let neighbor = world.get_block_state(neighbor_pos);
77 if neighbor.is_solid() {
78 return false;
79 }
80
81 let fluid = neighbor.get_fluid_state();
82 if fluid.fluid_id.has_tag(&FluidTag::LAVA) {
83 return false;
84 }
85 }
86
87 let below_pos = pos.below();
89 let below = world.get_block_state(below_pos);
90 let below_block = below.get_block();
91
92 let valid_below = below_block == &vanilla_blocks::CACTUS
93 || below_block.has_tag(&BlockTag::SUPPORTS_CACTUS);
94
95 if !valid_below {
96 return false;
97 }
98
99 let above = world.get_block_state(pos.above());
101
102 if above.has_fluid() {
103 return false;
104 }
105
106 true
107 }
108
109 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
110 let default_state = self.block.default_state();
111 if self.can_survive(default_state, context.world, context.place_pos()) {
112 Some(default_state)
113 } else {
114 None
115 }
116 }
117
118 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
119 if !self.can_survive(state, world, pos) {
120 world.destroy_block(pos, true);
121 }
122 }
123
124 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
125 let above_pos = pos.above();
126
127 if !world.get_block_state(above_pos).is_air() {
128 return;
129 }
130
131 let mut height = 1u32;
133 let age = state.get_value(AGE);
134
135 while world
136 .get_block_state(pos.offset(0, -(height as i32), 0))
137 .get_block()
138 == &vanilla_blocks::CACTUS
139 {
140 height += 1;
141 if height == MAX_CACTUS_HEIGHT && age == AGE.max {
142 return;
143 }
144 }
145
146 if age == CACTUS_FLOWER_AGE
148 && self.can_survive(self.block.default_state(), world, above_pos)
149 {
150 let chance = if height >= MAX_CACTUS_HEIGHT {
151 FLOWER_CHANCE_TALL
152 } else {
153 FLOWER_CHANCE_SMALL
154 };
155 if rand::random::<f64>() <= chance {
156 world.set_block(
157 above_pos,
158 vanilla_blocks::CACTUS_FLOWER.default_state(),
159 UpdateFlags::UPDATE_ALL,
160 );
161 }
162 } else if age == AGE.max && height < MAX_CACTUS_HEIGHT {
163 world.set_block(
164 above_pos,
165 vanilla_blocks::CACTUS.default_state(),
166 UpdateFlags::UPDATE_ALL,
167 );
168 let new_state = state.set_value(AGE, 0);
169 world.set_block(pos, new_state, UpdateFlags::UPDATE_NONE);
170 world.neighbor_changed(above_pos, &vanilla_blocks::CACTUS);
171 }
172
173 if age < AGE.max {
174 let new_state = state.set_value(AGE, age + 1);
175 world.set_block(pos, new_state, UpdateFlags::UPDATE_NONE);
176 }
177 }
178
179 fn update_shape(
180 &self,
181 state: BlockStateId,
182 world: &dyn ScheduledTickAccess,
183 pos: BlockPos,
184 _direction: Direction,
185 _neighbor_pos: BlockPos,
186 _neighbor_state: BlockStateId,
187 ) -> BlockStateId {
188 if !self.can_survive(state, world, pos) {
190 world.schedule_block_tick_default(pos, self.block, 1);
191 }
192 state
193 }
194
195 fn entity_inside(
196 &self,
197 _state: BlockStateId,
198 world: &Arc<World>,
199 _pos: BlockPos,
200 entity: &dyn Entity,
201 _effect_collector: &mut InsideBlockEffectCollector,
202 _is_precise: bool,
203 ) {
204 entity.hurt(
205 world,
206 &DamageSource::environment(&vanilla_damage_types::CACTUS),
207 1.0,
208 );
209 }
210
211 fn is_pathfindable(
212 &self,
213 _state: BlockStateId,
214 _computation_type: PathComputationType,
215 ) -> bool {
216 false
217 }
218}