2929import net .minecraft .sounds .SoundSource ;
3030import net .minecraft .util .Mth ;
3131import net .minecraft .util .RandomSource ;
32+ import net .minecraft .util .Tuple ;
3233import net .minecraft .world .entity .Entity ;
3334import net .minecraft .world .entity .LivingEntity ;
3435import net .minecraft .world .entity .animal .Bee ;
5859
5960import javax .annotation .Nonnull ;
6061import javax .annotation .Nullable ;
61- import java .util .ArrayList ;
62- import java .util .Collections ;
63- import java .util .HashSet ;
64- import java .util .List ;
62+ import java .util .*;
6563
6664@ SuppressWarnings ("deprecation" )
6765public class DynamicLeavesBlock extends LeavesBlock implements TreePart , Ageable , RayTraceCollision {
@@ -143,7 +141,7 @@ public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGett
143141
144142 @ Override
145143 public int age (LevelAccessor level , BlockPos pos , BlockState state , RandomSource rand , SafeChunkBounds safeBounds ) {
146- return updateLeaves (level , pos , state , rand ,safeBounds == SafeChunkBounds .ANY_WG , null , 0 );
144+ return updateLeaves (level , pos , state , rand ,safeBounds == SafeChunkBounds .ANY_WG );
147145 }
148146
149147 @ Override
@@ -181,42 +179,66 @@ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource
181179 }
182180
183181 /**
184- * Pulses recursively through the canopy, updating hydro and growing around if possible.
185- * If visitedPositions is null, it is not recursive and only the first block will be updated.
186- * @param visitedPositions keeps track of visited blocks for recursive calls. Set to null to make it pulse this block only.
187- * @return the new hydro value
182+ * Pulses recursively through the canopy using BFS, updating hydro and growing around if possible.
183+ * @return False if the leaves decayed. True if they survived.
188184 */
189- public int updateLeaves (LevelAccessor level , BlockPos pos , BlockState state , RandomSource rand , boolean worldGen , @ Nullable HashSet <BlockPos > visitedPositions , int fromHydro ){
190- boolean recursive = visitedPositions != null ;
191- final LeavesProperties leavesProperties = getProperties ();
192- if (recursive ){
193- if (visitedPositions .contains (pos ) || visitedPositions .size () > leavesProperties .maxLeavesRecursion ()) return 0 ;
194- visitedPositions .add (pos );
195- }
196-
197- int newHydro = updateHydro (level , pos , state , worldGen );
198- if (recursive && newHydro > fromHydro ) return newHydro ; //We do not recurse back through bigger hydro values
199- //Grows new leaves around
200- // We should do this even if the hydro is only 1. Since there could be adjacent branch blocks that could use a leaves block
201- for (Direction dir : Direction .values ()) { // Go on all 6 sides of this block
202- if (newHydro > 1 || rand .nextInt (4 ) == 0 ) { // we'll give it a 1 in 4 chance to grow leaves if hydro is low to help performance
203- BlockPos offpos = pos .relative (dir );
204- if (recursive && visitedPositions .contains (offpos )) continue ;
205- //attempt to grow new leaves, a null hydro will be calculated from neighbors.
206- growLeavesIfLocationIsSuitable (level , leavesProperties , offpos , null );
207- if (recursive ){
208- //We recursively visit nearby leaves telling them to update too
209- BlockState sideState = level .getBlockState (offpos );
185+ public boolean updateAllLeaves (LevelAccessor level , BlockPos startPos , BlockState startState , RandomSource rand , boolean worldGen ){
186+ //We store the position and hydro of the next block.
187+ Queue <Tuple <BlockPos , Integer >> toProcess = new ArrayDeque <>();
188+ Set <BlockPos > processedPositions = new HashSet <>();
189+ int firstHydro = updateHydro (level , startPos , startState , worldGen );
190+ toProcess .add (new Tuple <>(startPos , firstHydro ));
191+ if (firstHydro == 0 ) return false ;
192+ while (!toProcess .isEmpty () && processedPositions .size () <= getProperties ().maxLeavesRecursion ()){
193+ Tuple <BlockPos , Integer > tup = toProcess .remove ();
194+ BlockPos pos = tup .getA ();
195+ int hydro = tup .getB ();
196+ processedPositions .add (pos );
197+ for (Direction dir : Direction .values ()) { // Go on all 6 sides of this block
198+ if (hydro > 1 || rand .nextInt (4 ) == 0 ) { // we'll give it a 1 in 4 chance to grow leaves if hydro is low to help performance
199+ BlockPos sidePos = pos .relative (dir );
200+ if (processedPositions .contains (sidePos )) continue ;
201+ BlockState sideState = level .getBlockState (sidePos );
202+ //Check for surrounding leaves. Grow them if there aren't any.
203+
204+ if (!TreeHelper .isLeaves (sideState )) { //There were no leaves, attempt to grow some
205+ growLeavesIfLocationIsSuitable (level , getProperties (), sidePos , null );
206+ }
207+ int sideHydro ;
210208 if (TreeHelper .isLeaves (sideState )){
211- updateLeaves (level , offpos , sideState , rand , worldGen , visitedPositions , newHydro );
209+ sideHydro = updateHydro (level , sidePos , sideState , worldGen );
210+ } else {
211+ sideHydro = 0 ;
212+ }
213+ //Do not iterate back through bigger hydro values
214+ //or if the leaves failed to grow
215+ if (sideHydro == 0 || sideHydro <= hydro ){
216+ toProcess .add (new Tuple <>(sidePos , sideHydro ));
212217 }
213218 }
219+ }
220+ }
221+ return true ;
222+ }
223+
224+ public int updateLeaves (LevelAccessor level , BlockPos pos , BlockState state , RandomSource rand , boolean worldGen ){
225+ int newHydro = updateHydro (level , pos , state , worldGen );
226+ if (newHydro == 0 ) return 0 ; //If the leaves died don't bother
227+
228+ if (!worldGen && removeIfLightIsInadequate (state , level , pos , rand )) {
229+ return 0 ;
230+ }
214231
232+ for (Direction dir : Direction .values ()) {
233+ if (newHydro > 1 || rand .nextInt (4 ) == 0 ) {
234+ BlockPos sidePos = pos .relative (dir );
235+ growLeavesIfLocationIsSuitable (level , getProperties (), sidePos , null );
215236 }
216237 }
217238 return newHydro ;
218239 }
219240
241+
220242 protected boolean canCheckSurroundings (LevelAccessor accessor , BlockPos pos ) {
221243 if (accessor instanceof Level level ){
222244 // Check 2 blocks away for loaded chunks
@@ -229,32 +251,32 @@ protected boolean canCheckSurroundings(LevelAccessor accessor, BlockPos pos) {
229251 return accessor .isAreaLoaded (pos , 2 );
230252 }
231253
232- //TICK -> Destroy leaves if invalid
254+ //RANDOM TICK -> Destroy leaves if invalid
233255 //NEIGHBOR UPDATE -> recalculate hydro
234256 //TREE PULSE -> recalculate hydro
235257 // grows new leaves around (recursive)
236- public int updateHydro (LevelAccessor accesor , BlockPos pos , BlockState state , boolean worldGen ){
258+ public int updateHydro (LevelAccessor accessor , BlockPos pos , BlockState state , boolean worldGen ){
237259 final LeavesProperties leavesProperties = getProperties ();
238260 final int oldHydro = state .getValue (DISTANCE );
239261
240- if (!canCheckSurroundings (accesor , pos )) return oldHydro ;
262+ if (!canCheckSurroundings (accessor , pos )) return oldHydro ;
241263
242264 // Check hydration level. Dry leaves are dead leaves.
243- final int newHydro = getHydrationLevelFromNeighbors (accesor , pos , leavesProperties );
265+ final int newHydro = getHydrationLevelFromNeighbors (accessor , pos , leavesProperties );
244266
245267 if (oldHydro != newHydro ) { // Only update if the hydro has changed. A little performance gain.
246- BlockState placeState = getLeavesBlockStateForPlacement (accesor , pos , leavesProperties .getDynamicLeavesState (newHydro ), oldHydro , worldGen );
268+ BlockState placeState = getLeavesBlockStateForPlacement (accessor , pos , leavesProperties .getDynamicLeavesState (newHydro ), oldHydro , worldGen );
247269 // We do not use the 0x02 flag(update client) for performance reasons. The clients do not need to know the hydration level of the leaves blocks as it
248270 // does not affect appearance or behavior, unless appearanceChangesWithHydro. For the same reason we use the 0x04 flag to prevent the block from being re-rendered.
249271 // however if the new hydro is 0, it means the leaves were removed and we do need to update, so the flag is 3.
250272 int flag = newHydro == 0 ? 3 : (appearanceChangesWithHydro (oldHydro , newHydro ) ? 2 : 4 );
251- if (newHydro == 0 && !worldGen
252- && (accesor instanceof Level level )
253- //if the old hydro is the default then its most likely a block that was just placed and failed
254- && oldHydro != getProperties ().getCellKit ().getDefaultHydration ()) {
255- dropResources (state , level , pos );
256- }
257- accesor .setBlock (pos , placeState , flag );
273+ // if (newHydro == 0 && !worldGen
274+ // && (accessor instanceof Level level)
275+ // //if the old hydro is the default then its most likely a block that was just placed and failed
276+ // && oldHydro != getProperties().getCellKit().getDefaultHydration()) {
277+ // dropResources(state, level, pos);
278+ // }
279+ accessor .setBlock (pos , placeState , flag );
258280 }
259281 return newHydro ;
260282 }
@@ -487,9 +509,9 @@ public GrowSignal branchOut(Level level, BlockPos pos, GrowSignal signal) {
487509 }
488510
489511 //Pulse through the leaves to update the canopy shape and their hydro values
490- int hydro = updateLeaves (level , pos , level .getBlockState (pos ), signal .rand , false , new HashSet <>(), Integer . MAX_VALUE );
512+ boolean survived = updateAllLeaves (level , pos , level .getBlockState (pos ), signal .rand , false );
491513 //if hydro was 0 then the leaves have been removed
492- if (hydro == 0 ){
514+ if (! survived ){
493515 signal .success = false ;
494516 return signal ;
495517 }
0 commit comments