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 !above.has_fluid()
103 }
104
105 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
106 let default_state = self.block.default_state();
107 if self.can_survive(default_state, context.world, context.place_pos()) {
108 Some(default_state)
109 } else {
110 None
111 }
112 }
113
114 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
115 if !self.can_survive(state, world, pos) {
116 world.destroy_block(pos, true);
117 }
118 }
119
120 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
121 let above_pos = pos.above();
122
123 if !world.get_block_state(above_pos).is_air() {
124 return;
125 }
126
127 let mut height = 1u32;
129 let age = state.get_value(AGE);
130
131 while world
132 .get_block_state(pos.offset(0, -(height as i32), 0))
133 .get_block()
134 == &vanilla_blocks::CACTUS
135 {
136 height += 1;
137 if height == MAX_CACTUS_HEIGHT && age == AGE.max {
138 return;
139 }
140 }
141
142 if age == CACTUS_FLOWER_AGE
144 && self.can_survive(self.block.default_state(), world, above_pos)
145 {
146 let chance = if height >= MAX_CACTUS_HEIGHT {
147 FLOWER_CHANCE_TALL
148 } else {
149 FLOWER_CHANCE_SMALL
150 };
151 if rand::random::<f64>() <= chance {
152 world.set_block(
153 above_pos,
154 vanilla_blocks::CACTUS_FLOWER.default_state(),
155 UpdateFlags::UPDATE_ALL,
156 );
157 }
158 } else if age == AGE.max && height < MAX_CACTUS_HEIGHT {
159 world.set_block(
160 above_pos,
161 vanilla_blocks::CACTUS.default_state(),
162 UpdateFlags::UPDATE_ALL,
163 );
164 let new_state = state.set_value(AGE, 0);
165 world.set_block(pos, new_state, UpdateFlags::UPDATE_NONE);
166 world.neighbor_changed(above_pos, &vanilla_blocks::CACTUS);
167 }
168
169 if age < AGE.max {
170 let new_state = state.set_value(AGE, age + 1);
171 world.set_block(pos, new_state, UpdateFlags::UPDATE_NONE);
172 }
173 }
174
175 fn update_shape(
176 &self,
177 state: BlockStateId,
178 world: &dyn ScheduledTickAccess,
179 pos: BlockPos,
180 _direction: Direction,
181 _neighbor_pos: BlockPos,
182 _neighbor_state: BlockStateId,
183 ) -> BlockStateId {
184 if !self.can_survive(state, world, pos) {
186 world.schedule_block_tick_default(pos, self.block, 1);
187 }
188 state
189 }
190
191 fn entity_inside(
192 &self,
193 _state: BlockStateId,
194 world: &Arc<World>,
195 _pos: BlockPos,
196 entity: &dyn Entity,
197 _effect_collector: &mut InsideBlockEffectCollector,
198 _is_precise: bool,
199 ) {
200 entity.hurt(
201 world,
202 &DamageSource::environment(&vanilla_damage_types::CACTUS),
203 1.0,
204 );
205 }
206
207 fn is_pathfindable(
208 &self,
209 _state: BlockStateId,
210 _computation_type: PathComputationType,
211 ) -> bool {
212 false
213 }
214}