29
29
import net .minecraft .sounds .SoundSource ;
30
30
import net .minecraft .util .Mth ;
31
31
import net .minecraft .util .RandomSource ;
32
+ import net .minecraft .util .Tuple ;
32
33
import net .minecraft .world .entity .Entity ;
33
34
import net .minecraft .world .entity .LivingEntity ;
34
35
import net .minecraft .world .entity .animal .Bee ;
58
59
59
60
import javax .annotation .Nonnull ;
60
61
import 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 .*;
65
63
66
64
@ SuppressWarnings ("deprecation" )
67
65
public class DynamicLeavesBlock extends LeavesBlock implements TreePart , Ageable , RayTraceCollision {
@@ -143,7 +141,7 @@ public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGett
143
141
144
142
@ Override
145
143
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 );
147
145
}
148
146
149
147
@ Override
@@ -181,42 +179,66 @@ public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource
181
179
}
182
180
183
181
/**
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.
188
184
*/
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 ;
210
208
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 ));
212
217
}
213
218
}
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
+ }
214
231
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 );
215
236
}
216
237
}
217
238
return newHydro ;
218
239
}
219
240
241
+
220
242
protected boolean canCheckSurroundings (LevelAccessor accessor , BlockPos pos ) {
221
243
if (accessor instanceof Level level ){
222
244
// Check 2 blocks away for loaded chunks
@@ -229,32 +251,32 @@ protected boolean canCheckSurroundings(LevelAccessor accessor, BlockPos pos) {
229
251
return accessor .isAreaLoaded (pos , 2 );
230
252
}
231
253
232
- //TICK -> Destroy leaves if invalid
254
+ //RANDOM TICK -> Destroy leaves if invalid
233
255
//NEIGHBOR UPDATE -> recalculate hydro
234
256
//TREE PULSE -> recalculate hydro
235
257
// 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 ){
237
259
final LeavesProperties leavesProperties = getProperties ();
238
260
final int oldHydro = state .getValue (DISTANCE );
239
261
240
- if (!canCheckSurroundings (accesor , pos )) return oldHydro ;
262
+ if (!canCheckSurroundings (accessor , pos )) return oldHydro ;
241
263
242
264
// Check hydration level. Dry leaves are dead leaves.
243
- final int newHydro = getHydrationLevelFromNeighbors (accesor , pos , leavesProperties );
265
+ final int newHydro = getHydrationLevelFromNeighbors (accessor , pos , leavesProperties );
244
266
245
267
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 );
247
269
// 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
248
270
// 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.
249
271
// however if the new hydro is 0, it means the leaves were removed and we do need to update, so the flag is 3.
250
272
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 );
258
280
}
259
281
return newHydro ;
260
282
}
@@ -487,9 +509,9 @@ public GrowSignal branchOut(Level level, BlockPos pos, GrowSignal signal) {
487
509
}
488
510
489
511
//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 );
491
513
//if hydro was 0 then the leaves have been removed
492
- if (hydro == 0 ){
514
+ if (! survived ){
493
515
signal .success = false ;
494
516
return signal ;
495
517
}
0 commit comments