steel_core/behavior/blocks/vegetation/
sugar_cane.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_fluid_tags;
15use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
16
17use crate::behavior::BlockBehavior;
18use crate::behavior::context::BlockPlaceContext;
19use crate::world::{LevelReader, ScheduledTickAccess, World};
20
21const MAX_SUGAR_CANE_HEIGHT: i32 = 3;
23
24#[block_behavior]
26pub struct SugarCaneBlock {
27 block: BlockRef,
28}
29
30impl SugarCaneBlock {
31 #[must_use]
33 pub const fn new(block: BlockRef) -> Self {
34 Self { block }
35 }
36}
37
38impl BlockBehavior for SugarCaneBlock {
39 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
40 let pos = context.place_pos();
41 if self.can_survive(
42 vanilla_blocks::SUGAR_CANE.default_state(), context.world,
44 pos,
45 ) {
46 Some(self.block.default_state())
47 } else {
48 None
49 }
50 }
51
52 fn on_place(
54 &self,
55 state: BlockStateId,
56 world: &Arc<World>,
57 pos: BlockPos,
58 old_state: BlockStateId,
59 _moved_by_piston: bool,
60 ) {
61 if state.get_block() == old_state.get_block() {
62 return;
63 }
64
65 if !self.can_survive(state, world, pos) {
66 world.schedule_block_tick_default(pos, state.get_block(), 1);
67 }
68 }
69
70 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
71 if !self.can_survive(state, world, pos) {
72 world.destroy_block(pos, true);
73 }
74 }
75
76 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
77 let above_pos = pos.above();
78
79 if !world.get_block_state(above_pos).is_air() {
80 return;
81 }
82
83 let mut height = 1i32;
84 while world.get_block_state(pos.below_n(height)).get_block() == self.block {
85 height += 1;
86 }
87
88 if height >= MAX_SUGAR_CANE_HEIGHT {
89 return;
90 }
91
92 let age = state.get_value(&BlockStateProperties::AGE_15);
93
94 if age == 15 {
95 world.set_block(
96 above_pos,
97 self.block.default_state(),
98 UpdateFlags::UPDATE_ALL,
99 );
100 let new_state = state.set_value(&BlockStateProperties::AGE_15, 0);
101 world.set_block(pos, new_state, UpdateFlags::UPDATE_CLIENTS);
102 } else {
103 let new_state = state.set_value(&BlockStateProperties::AGE_15, age + 1);
104 world.set_block(pos, new_state, UpdateFlags::UPDATE_CLIENTS);
105 }
106 }
107
108 fn update_shape(
109 &self,
110 state: BlockStateId,
111 world: &dyn ScheduledTickAccess,
112 pos: BlockPos,
113 _direction: Direction,
114 _neighbor_pos: BlockPos,
115 _neighbor_state: BlockStateId,
116 ) -> BlockStateId {
117 if !self.can_survive(state, world, pos) {
118 world.schedule_block_tick_default(pos, self.block, 1);
119 }
120 state
121 }
122
123 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
124 let below_pos = pos.below();
125 let below_state = world.get_block_state(below_pos);
126 let below_block = below_state.get_block();
127
128 if below_block == self.block {
129 return true;
130 }
131
132 let is_valid_ground = below_block.has_tag(&BlockTag::SUPPORTS_SUGAR_CANE);
133
134 if !is_valid_ground {
135 return false;
136 }
137
138 for dir in [
139 Direction::North,
140 Direction::South,
141 Direction::East,
142 Direction::West,
143 ] {
144 let neighbor_pos = dir.relative(below_pos);
145 let neighbor_state = world.get_block_state(neighbor_pos);
146
147 if neighbor_state
148 .get_block()
149 .has_tag(&BlockTag::SUPPORTS_SUGAR_CANE_ADJACENTLY)
150 || neighbor_state
151 .get_fluid_state()
152 .fluid_id
153 .has_tag(&vanilla_fluid_tags::FluidTag::SUPPORTS_SUGAR_CANE_ADJACENTLY)
154 {
155 return true;
156 }
157 }
158
159 false
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use steel_registry::init_vanilla_registry;
166
167 use crate::test_support::TestLevel;
168
169 use super::*;
170
171 #[test]
172 fn sugar_cane_update_shape_schedules_break_tick_when_unsupported() {
173 init_vanilla_registry();
174 let behavior = SugarCaneBlock::new(&vanilla_blocks::SUGAR_CANE);
175 let level = TestLevel::default();
176 let state = vanilla_blocks::SUGAR_CANE.default_state();
177
178 let updated = behavior.update_shape(
179 state,
180 &level,
181 BlockPos::ZERO,
182 Direction::Down,
183 BlockPos::ZERO.below(),
184 vanilla_blocks::AIR.default_state(),
185 );
186
187 assert_eq!(updated, state);
188 assert!(
189 level
190 .scheduled_block_ticks
191 .borrow()
192 .iter()
193 .any(|tick| tick.block == &vanilla_blocks::SUGAR_CANE)
194 );
195 }
196}