diff --git a/build.gradle.kts b/build.gradle.kts index bec7261e8..963633737 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -99,6 +99,9 @@ dependencies { runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-core:${property("ccVersion")}")) runtimeOnly(fg.deobf("cc.tweaked:cc-tweaked-$mcVersion-forge:${property("ccVersion")}")) + //Spark profiler + runtimeOnly(fg.deobf("curse.maven:spark-361579:4738952")) + // runtimeOnly(fg.deobf("com.harleyoconnor.suggestionproviderfix:SuggestionProviderFix-1.19:${property("suggestionProviderFixVersion")}")) } diff --git a/src/main/java/com/ferreusveritas/dynamictrees/block/leaves/DynamicLeavesBlock.java b/src/main/java/com/ferreusveritas/dynamictrees/block/leaves/DynamicLeavesBlock.java index 40222c082..b1488a74f 100644 --- a/src/main/java/com/ferreusveritas/dynamictrees/block/leaves/DynamicLeavesBlock.java +++ b/src/main/java/com/ferreusveritas/dynamictrees/block/leaves/DynamicLeavesBlock.java @@ -18,8 +18,12 @@ import com.ferreusveritas.dynamictrees.util.LevelContext; import com.ferreusveritas.dynamictrees.util.RayTraceCollision; import com.ferreusveritas.dynamictrees.util.SafeChunkBounds; +import net.minecraft.client.Minecraft; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.network.chat.ChatType; +import net.minecraft.network.chat.OutgoingChatMessage; +import net.minecraft.network.chat.PlayerChatMessage; import net.minecraft.server.level.ServerLevel; import net.minecraft.sounds.SoundSource; import net.minecraft.util.Mth; @@ -55,6 +59,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; @SuppressWarnings("deprecation") @@ -73,20 +78,15 @@ public DynamicLeavesBlock(Properties properties) { this.registerDefaultState(this.stateDefinition.any().setValue(DISTANCE, LeavesProperties.maxHydro).setValue(PERSISTENT, false).setValue(WATERLOGGED, false)); } + /////////////////////////////////////////// + // PROPERTIES + /////////////////////////////////////////// + @Override public boolean isRandomlyTicking(BlockState state) { return !state.getValue(PERSISTENT); } - @Override - public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { - if (properties.hasTickParticles && properties.getPrimitiveLeavesBlock().isPresent()) { - properties.getPrimitiveLeavesBlock().ifPresent((b)->b.animateTick(state,level,pos,random)); - } else { - super.animateTick(state, level, pos, random); - } - } - public void setProperties(LeavesProperties properties) { this.properties = properties; } @@ -117,15 +117,31 @@ public boolean isFlammable(BlockState state, BlockGetter level, BlockPos pos, Di return this.getFlammability(state, level, pos, face) > 0 || face == Direction.UP; } - @Nonnull - public BlockState updateShape(@Nonnull BlockState stateIn, Direction facing, BlockState facingState, @Nonnull LevelAccessor level, @Nonnull BlockPos currentPos, BlockPos facingPos) { - return stateIn; - } - + @Override public BlockState getStateForPlacement(BlockPlaceContext context) { return this.defaultBlockState(); } + @Override + public float getDestroyProgress(BlockState state, Player player, BlockGetter level, BlockPos pos) { + return getProperties(state).getPrimitiveLeaves().getDestroyProgress(player, level, pos); + } + + @Override + public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGetter level, BlockPos pos, Player player) { + return getProperties(state).getPrimitiveLeavesItemStack(); + } + + /////////////////////////////////////////// + // GROWTH + /////////////////////////////////////////// + + @Override + public int age(LevelAccessor level, BlockPos pos, BlockState state, RandomSource rand, SafeChunkBounds safeBounds) { + updateLeaves(level, pos, state, rand,safeBounds == SafeChunkBounds.ANY_WG,null); + return state.getValue(DISTANCE); + } + @Override public void randomTick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) { if (rand.nextInt(DTConfigs.TREE_GROWTH_FOLDING.get()) != 0) { @@ -135,238 +151,121 @@ public void randomTick(BlockState state, ServerLevel level, BlockPos pos, Random double attempts = DTConfigs.TREE_GROWTH_FOLDING.get() * DTConfigs.TREE_GROWTH_MULTIPLIER.get(); if (attempts >= 1.0f || rand.nextFloat() < attempts) { - doTick(level, pos, state, rand); - } - - int start = rand.nextInt(26); - - while (--attempts > 0) { - if (attempts >= 1.0f || rand.nextFloat() < attempts) { - int r = (start++ % 26) + 14; // 14 - 39 - r = r > 26 ? r - 13 : r - 14; // 0 - 26 but Skip 13 - final BlockPos dPos = pos.offset((r % 3) - 1, ((r / 3) % 3) - 1, ((r / 9) % 3) - 1);// (-1, -1, -1) to (1, 1, 1) skipping (0, 0, 0) - final BlockState dState = level.getBlockState(dPos); - - if (dState.getBlock() instanceof DynamicLeavesBlock) { - ((DynamicLeavesBlock) dState.getBlock()).doTick(level, dPos, dState, rand); + if (getProperties(state).updateTick(level, pos, state, rand)) { + if (!hasAdequateLight(state, level, getProperties(state), pos)) { + level.removeBlock(pos, false); // No water, no light ... no leaves. + return; } } } - } - - @Override - public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) { - } - - protected void doTick(Level level, BlockPos pos, BlockState state, RandomSource rand) { - if (canTickAt(level, pos) && getProperties(state).updateTick(level, pos, state, rand)) { + //Every once in a blue moon, age the leaves + if (rand.nextFloat() < 0.05){ age(level, pos, state, rand, SafeChunkBounds.ANY); } } - protected boolean canTickAt(Level level, BlockPos pos) { - // Check 2 blocks away for loaded chunks - int xm = pos.getX() - ((pos.getX() >> 4) << 4); - int zm = pos.getZ() - ((pos.getZ() >> 4) << 4); - if (xm > 1 && xm < 14 && zm > 1 && zm < 14) { - return level.isLoaded(pos); + @Nonnull + public BlockState updateShape(@Nonnull BlockState stateIn, Direction facing, BlockState facingState, @Nonnull LevelAccessor level, @Nonnull BlockPos currentPos, BlockPos facingPos) { + boolean sideIsLeaves = TreeHelper.isLeaves(facingState); + int sideHydro = sideIsLeaves ? facingState.getValue(DISTANCE) : 0; + if (!sideIsLeaves || sideHydro < stateIn.getValue(DISTANCE)){ + level.scheduleTick(currentPos, this, 1); } - return level.isAreaLoaded(pos, 2); - } - public boolean appearanceChangesWithHydro(int oldHydro, int newHydro) { - return false; + return stateIn; } - @Override - public int age(LevelAccessor level, BlockPos pos, BlockState state, RandomSource rand, SafeChunkBounds safeBounds) { - final LeavesProperties leavesProperties = getProperties(state); - final int oldHydro = state.getValue(DynamicLeavesBlock.DISTANCE); - - final boolean worldGen = safeBounds != SafeChunkBounds.ANY; - - if (!getProperties(state).shouldAge(worldGen, state)) { - return oldHydro; - } - - // Check hydration level. Dry leaves are dead leaves. - final int newHydro = getHydrationLevelFromNeighbors(level, pos, leavesProperties); + public void tick(BlockState state, ServerLevel level, BlockPos pos, RandomSource rand) { + updateHydro(level, pos, state, false); + } - if (newHydro == 0 || (!worldGen && !hasAdequateLight(state, level, leavesProperties, pos))) { // Light doesn't work right during worldgen so we'll just disable it during worldgen for now. - level.removeBlock(pos, false); // No water, no light .. no leaves. - return -1; // Leaves were destroyed. - } else { - if (oldHydro != newHydro) { // Only update if the hydro has changed. A little performance gain. - // 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 - // 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. - level.setBlock(pos, getLeavesBlockStateForPlacement(level, pos, leavesProperties.getDynamicLeavesState(newHydro), oldHydro, worldGen), appearanceChangesWithHydro(oldHydro, newHydro) ? 2 : 4); - } + public int updateLeaves(LevelAccessor level, BlockPos pos, BlockState state, RandomSource rand, boolean worldGen, @Nullable HashSet visitedPositions){ + boolean recursive = visitedPositions != null; + if (recursive){ + if (visitedPositions.contains(pos)) return 0; + visitedPositions.add(pos); } - + final LeavesProperties leavesProperties = getProperties(state); + int newHydro = updateHydro(level, pos, state, worldGen); + //Grows new leaves around // We should do this even if the hydro is only 1. Since there could be adjacent branch blocks that could use a leaves block for (Direction dir : Direction.values()) { // Go on all 6 sides of this block 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 BlockPos offpos = pos.relative(dir); - if (safeBounds.inBounds(offpos, true) && isLocationSuitableForNewLeaves(level, leavesProperties, offpos)) { // Attempt to grow new leaves - int hydro = getHydrationLevelFromNeighbors(level, offpos, leavesProperties); - if (hydro > 0) { - level.setBlock(offpos, getLeavesBlockStateForPlacement(level, offpos, leavesProperties.getDynamicLeavesState(hydro), 0, worldGen), 2); // Removed Notify Neighbors Flag for performance + if (recursive && visitedPositions.contains(offpos)) continue; + //attempt to grow new leaves, a null hydro will be calculated from neighbors. + growLeavesIfLocationIsSuitable(level, leavesProperties, offpos, null); + if (recursive){ + //We recursively visit nearby leaves telling them to update too + BlockState sideState = level.getBlockState(offpos); + if (TreeHelper.isLeaves(sideState)){ + updateLeaves(level, offpos, sideState, rand, worldGen, visitedPositions); } } + } } - - return newHydro; // Leaves were not destroyed - } - - /** - * Provides a method to add custom leaves properties besides the normal hydro. - * - * @param level The level - * @param pos Position of the new leaves blck - * @param leavesStateWithHydro The state of the leaves with the hydro applied - * @param oldHydro the hydro value of the leaves before the palcement. Will be 0 if its a new leaf - * @param worldGen true if this is happening during worldgen - * @return A provider for adding more blockstate properties - */ - public BlockState getLeavesBlockStateForPlacement(LevelAccessor level, BlockPos pos, BlockState leavesStateWithHydro, int oldHydro, boolean worldGen) { - return leavesStateWithHydro; //by default just pass the blockstate along + return newHydro; } - @Override - public float getDestroyProgress(BlockState state, Player player, BlockGetter level, BlockPos pos) { - return getProperties(state).getPrimitiveLeaves().getDestroyProgress(player, level, pos); - } + //TICK -> Destroy leaves if invalid - @Override - public ItemStack getCloneItemStack(BlockState state, HitResult target, BlockGetter level, BlockPos pos, Player player) { - return getProperties(state).getPrimitiveLeavesItemStack(); - } + //NEIGHBOR UPDATE -> recalculate hydro - /** - * We will disable landing effects because we crush the blocks on landing and create our own particles in - * crushBlock() - */ - @Override - public boolean addLandingEffects(BlockState state1, ServerLevel level, BlockPos pos, BlockState state2, LivingEntity entity, int numberOfParticles) { - return true; - } + //TREE PULSE -> recalculate hydro + // grows new leaves around (recursive) - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { - if (isLeavesPassable() || this.isEntityPassable(context)) { - return Shapes.empty(); - } else if (DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.VANILLA_LEAVES_COLLISION.get()) { - return Shapes.block(); - } else { - return Shapes.create(new AABB(0.125, 0, 0.125, 0.875, 0.50, 0.875)); + protected boolean canCheckSurroundings(LevelAccessor accessor, BlockPos pos) { + if (accessor instanceof Level level){ + // Check 2 blocks away for loaded chunks + int xm = pos.getX() - ((pos.getX() >> 4) << 4); + int zm = pos.getZ() - ((pos.getZ() >> 4) << 4); + if (xm > 1 && xm < 14 && zm > 1 && zm < 14) { + return level.isLoaded(pos); + } } + return accessor.isAreaLoaded(pos, 2); } - /** - * This support shape allows for placement on the sides (vines) but not on top - */ - protected static final VoxelShape SUPPORT_SHAPE = Shapes.join(Shapes.block(), box(2.0D, 14.0D, 2.0D, 14.0D, 16.0D, 14.0D), BooleanOp.ONLY_FIRST); - @Override - public VoxelShape getBlockSupportShape(BlockState pState, BlockGetter pReader, BlockPos pPos) { - return SUPPORT_SHAPE; - } + public int updateHydro(LevelAccessor level, BlockPos pos, BlockState state, boolean worldGen){ + final LeavesProperties leavesProperties = getProperties(state); + final int oldHydro = state.getValue(DISTANCE); - protected boolean isLeavesPassable() { - return (DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.IS_LEAVES_PASSABLE.get()) || ModList.get().isLoaded(DynamicTrees.PASSABLE_FOLIAGE); - } + if (!canCheckSurroundings(level, pos)) return oldHydro; - public boolean isEntityPassable(CollisionContext context) { - if (context instanceof EntityCollisionContext entityCollisionContext) { - return isEntityPassable(entityCollisionContext.getEntity()); - } - return false; - } + // Check hydration level. Dry leaves are dead leaves. + final int newHydro = getHydrationLevelFromNeighbors(level, pos, leavesProperties); - public boolean isEntityPassable(@Nullable Entity entity) { - if (entity instanceof Projectile) //Projectiles such as arrows fly through leaves - { - return true; + if (oldHydro != newHydro) { // Only update if the hydro has changed. A little performance gain. + BlockState placeState = getLeavesBlockStateForPlacement(level, pos, leavesProperties.getDynamicLeavesState(newHydro), oldHydro, worldGen); + // 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 + // 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. + // however if the new hydro is 0, it means the leaves were removed and we do need to update. + int flag = newHydro == 0 ? 3 : appearanceChangesWithHydro(oldHydro, newHydro) ? 2 : 4; + level.setBlock(pos, placeState, flag); } - if (entity instanceof ItemEntity) //Seed items fall through leaves - { - return ((ItemEntity) entity).getItem().getItem() instanceof Seed; - } - if (entity instanceof LivingEntity) //Bees fly through leaves, otherwise they get stuck :( - { - return entity instanceof Bee; - } - return false; + return newHydro; } /** - * Only here so that subclasses can override {@link #fallOn(Level, BlockState, BlockPos, Entity, float)} - * and return it to the default behavior in {@link Block#fallOn(Level, BlockState, BlockPos, Entity, float)}. + * Whether the leaves have different looks */ - protected void superFallOn(Level level, BlockState blockState, BlockPos pos, Entity entity, float fallDistance) { - super.fallOn(level, blockState, pos, entity, fallDistance); - } - - @Override - public void fallOn(Level level, BlockState blockState, BlockPos pos, Entity entity, float fallDistance) { - // We are only interested in Living things crashing through the canopy. - if (!DTConfigs.CANOPY_CRASH.get() || !(entity instanceof LivingEntity)) { - return; - } - - entity.fallDistance--; - - final AABB aabb = entity.getBoundingBox(); - - final int minX = Mth.floor(aabb.minX + 0.001D); - final int minZ = Mth.floor(aabb.minZ + 0.001D); - final int maxX = Mth.floor(aabb.maxX - 0.001D); - final int maxZ = Mth.floor(aabb.maxZ - 0.001D); - - boolean crushing = true; - boolean hasLeaves = true; - - final SoundType stepSound = this.getSoundType(level.getBlockState(pos), level, pos, entity); - final float volume = Mth.clamp(stepSound.getVolume() / 16.0f * fallDistance, 0, 3.0f); - level.playLocalSound(entity.getX(), entity.getY(), entity.getZ(), stepSound.getBreakSound(), SoundSource.BLOCKS, volume, stepSound.getPitch(), false); - - for (int iy = 0; (entity.fallDistance > 3.0f) && crushing && ((pos.getY() - iy) > 0); iy++) { - if (hasLeaves) { // This layer has leaves that can help break our fall - entity.fallDistance *= 0.66f; // For each layer we are crushing break the momentum - hasLeaves = false; - } - - for (int ix = minX; ix <= maxX; ix++) { - for (int iz = minZ; iz <= maxZ; iz++) { - BlockPos iPos = new BlockPos(ix, pos.getY() - iy, iz); - BlockState state = level.getBlockState(iPos); - if (TreeHelper.isLeaves(state)) { - hasLeaves = true; // This layer has leaves - DTClient.crushLeavesBlock(level, iPos, state, entity); - level.removeBlock(iPos, false); - } else if (!level.isEmptyBlock(iPos)) { - crushing = false; // We hit something solid thus no longer crushing leaves layers - } - } - } - } + public boolean appearanceChangesWithHydro(int oldHydro, int newHydro) { + return false; } - @Override - public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { - if (isLeavesPassable() || isEntityPassable(entity)) { - super.entityInside(state, level, pos, entity); - } else { - if (entity.getDeltaMovement().y < 0.0D && entity.fallDistance < 2.0f) { - entity.fallDistance = 0.0f; - entity.setDeltaMovement(entity.getDeltaMovement().x, entity.getDeltaMovement().y * 0.5D, entity.getDeltaMovement().z); // Slowly sink into the block - } else if (entity.getDeltaMovement().y > 0 && entity.getDeltaMovement().y < 0.25D) { - entity.setDeltaMovement(entity.getDeltaMovement().x, entity.getDeltaMovement().y + 0.025, entity.getDeltaMovement().z); // Allow a little climbing - } - - entity.setSprinting(false); // One cannot sprint upon tree tops - entity.setDeltaMovement(entity.getDeltaMovement().x * 0.25D, entity.getDeltaMovement().y, entity.getDeltaMovement().z * 0.25D); // Make travel slow and laborious - } + /** + * Provides a method to add custom leaves properties besides the normal hydro. + * + * @param level The level + * @param pos Position of the new leaves blck + * @param leavesStateWithHydro The state of the leaves with the hydro applied + * @param oldHydro the hydro value of the leaves before the palcement. Will be 0 if its a new leaf + * @param worldGen true if this is happening during worldgen + * @return A provider for adding more blockstate properties + */ + public BlockState getLeavesBlockStateForPlacement(LevelAccessor level, BlockPos pos, BlockState leavesStateWithHydro, int oldHydro, boolean worldGen) { + return leavesStateWithHydro; //by default just pass the blockstate along } /** @@ -378,9 +277,10 @@ public void entityInside(BlockState state, Level level, BlockPos pos, Entity ent * @param hydro The hydration value for the resulting cell * @return {@code true} if the location was suitable (and so leaves were placed); {@code false} otherwise. */ - public boolean growLeavesIfLocationIsSuitable(LevelAccessor level, LeavesProperties leavesProp, BlockPos pos, int hydro) { - hydro = hydro == 0 ? leavesProp.getCellKit().getDefaultHydration() : hydro; + public boolean growLeavesIfLocationIsSuitable(LevelAccessor level, LeavesProperties leavesProp, BlockPos pos, @Nullable Integer hydro) { if (isLocationSuitableForNewLeaves(level, leavesProp, pos)) { + if (hydro == null) hydro = getHydrationLevelFromNeighbors(level, pos, leavesProp); + else hydro = hydro == 0 ? leavesProp.getCellKit().getDefaultHydration() : hydro; level.setBlock(pos, getLeavesBlockStateForPlacement(level, pos, leavesProp.getDynamicLeavesState(hydro), 0, false), 2); // Removed Notify Neighbors Flag for performance. return true; } @@ -464,7 +364,6 @@ public boolean hasAdequateLight(BlockState state, LevelAccessor level, LeavesPro If there's already leaves here then don't kill them if it's a little dark. If it's empty space then don't create leaves unless it's sufficiently bright. The range allows for adaptation to the hysteric effect that could cause blocks to rapidly appear and disappear. */ - return level.getBrightness(LightLayer.SKY, pos) >= (TreeHelper.isLeaves(state) ? leavesProperties.getLightRequirement() - 2 : leavesProperties.getLightRequirement()); } @@ -532,14 +431,13 @@ public GrowSignal growSignal(Level level, BlockPos pos, GrowSignal signal) { * @param leavesProperties The {@link LeavesProperties} required. * @return {@code true} if the leaves are at the given {@link BlockPos}; {@code false} otherwise. */ - public boolean needLeaves(Level level, BlockPos pos, LeavesProperties leavesProperties, Species species) { + public boolean needLeaves(Level level, BlockPos pos, LeavesProperties leavesProperties, Species species, Boolean pulseLeavesIfFound) { if (level.isEmptyBlock(pos)) { // Place leaves if air. return this.growLeavesIfLocationIsSuitable(level, leavesProperties, pos, leavesProperties.getCellKit().getDefaultHydration()); } else { // Otherwise check if there's already this type of leaves there. - final TreePart treePart = TreeHelper.getTreePart(level.getBlockState(pos)); - - - return treePart instanceof DynamicLeavesBlock && species.isValidLeafBlock((DynamicLeavesBlock) treePart); // Check if this leaves are valid for the species + BlockState state = level.getBlockState(pos); + final TreePart treePart = TreeHelper.getTreePart(state); + return treePart instanceof DynamicLeavesBlock && species.isValidLeafBlock((DynamicLeavesBlock) treePart); } } @@ -549,7 +447,7 @@ public GrowSignal branchOut(Level level, BlockPos pos, GrowSignal signal) { LeavesProperties leavesProperties = species.getLeavesProperties(); //Check to be sure the placement for a branch is valid by testing to see if it would first support a leaves block - if (!needLeaves(level, pos, leavesProperties, species)) { + if (!needLeaves(level, pos, leavesProperties, species, true)) { signal.success = false; return signal; } @@ -561,14 +459,22 @@ public GrowSignal branchOut(Level level, BlockPos pos, GrowSignal signal) { } boolean hasLeaves = false; - + //Check leaves conditions before expanding the canopy, otherwise it will always be true for (Direction dir : Direction.values()) { - if (needLeaves(level, pos.relative(dir), leavesProperties, species)) { + if (needLeaves(level, pos.relative(dir), leavesProperties, species, false)) { hasLeaves = true; break; } } + //Pulse through the leaves to update the canopy shape and their hydro values + int hydro = updateLeaves(level, pos, level.getBlockState(pos), signal.rand, false, new HashSet<>()); + //if hydro was 0 then the leaves have been removed + if (hydro == 0){ + signal.success = false; + return signal; + } + if (hasLeaves) { //Finally set the leaves block to a branch Family family = species.getFamily(); @@ -588,6 +494,136 @@ public int probabilityForBlock(BlockState state, BlockGetter level, BlockPos pos return from.getFamily().isCompatibleDynamicLeaves(from.getFamily().getCommonSpecies(), state, level, pos) ? 2 : 0; } + /////////////////////////////////////////// + // ENTITY INTERACTIONS + /////////////////////////////////////////// + + /** + * We will disable landing effects because we crush the blocks on landing and create our own particles in + * crushBlock() + */ + @Override + public boolean addLandingEffects(BlockState state1, ServerLevel level, BlockPos pos, BlockState state2, LivingEntity entity, int numberOfParticles) { + return true; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { + if (isLeavesPassable() || this.isEntityPassable(context)) { + return Shapes.empty(); + } else if (DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.VANILLA_LEAVES_COLLISION.get()) { + return Shapes.block(); + } else { + return Shapes.create(new AABB(0.125, 0, 0.125, 0.875, 0.50, 0.875)); + } + } + + /** + * This support shape allows for placement on the sides (vines) but not on top + */ + protected static final VoxelShape SUPPORT_SHAPE = Shapes.join(Shapes.block(), box(2.0D, 14.0D, 2.0D, 14.0D, 16.0D, 14.0D), BooleanOp.ONLY_FIRST); + @Override + public VoxelShape getBlockSupportShape(BlockState pState, BlockGetter pReader, BlockPos pPos) { + return SUPPORT_SHAPE; + } + + protected boolean isLeavesPassable() { + return (DTConfigs.SERVER_CONFIG.isLoaded() && DTConfigs.IS_LEAVES_PASSABLE.get()) || ModList.get().isLoaded(DynamicTrees.PASSABLE_FOLIAGE); + } + + public boolean isEntityPassable(CollisionContext context) { + if (context instanceof EntityCollisionContext entityCollisionContext) { + return isEntityPassable(entityCollisionContext.getEntity()); + } + return false; + } + + public boolean isEntityPassable(@Nullable Entity entity) { + if (entity instanceof Projectile) //Projectiles such as arrows fly through leaves + { + return true; + } + if (entity instanceof ItemEntity) //Seed items fall through leaves + { + return ((ItemEntity) entity).getItem().getItem() instanceof Seed; + } + if (entity instanceof LivingEntity) //Bees fly through leaves, otherwise they get stuck :( + { + return entity instanceof Bee; + } + return false; + } + + /** + * Only here so that subclasses can override {@link #fallOn(Level, BlockState, BlockPos, Entity, float)} + * and return it to the default behavior in {@link Block#fallOn(Level, BlockState, BlockPos, Entity, float)}. + */ + protected void superFallOn(Level level, BlockState blockState, BlockPos pos, Entity entity, float fallDistance) { + super.fallOn(level, blockState, pos, entity, fallDistance); + } + + @Override + public void fallOn(Level level, BlockState blockState, BlockPos pos, Entity entity, float fallDistance) { + // We are only interested in Living things crashing through the canopy. + if (!DTConfigs.CANOPY_CRASH.get() || !(entity instanceof LivingEntity)) { + return; + } + + entity.fallDistance--; + + final AABB aabb = entity.getBoundingBox(); + + final int minX = Mth.floor(aabb.minX + 0.001D); + final int minZ = Mth.floor(aabb.minZ + 0.001D); + final int maxX = Mth.floor(aabb.maxX - 0.001D); + final int maxZ = Mth.floor(aabb.maxZ - 0.001D); + + boolean crushing = true; + boolean hasLeaves = true; + + final SoundType stepSound = this.getSoundType(level.getBlockState(pos), level, pos, entity); + final float volume = Mth.clamp(stepSound.getVolume() / 16.0f * fallDistance, 0, 3.0f); + level.playLocalSound(entity.getX(), entity.getY(), entity.getZ(), stepSound.getBreakSound(), SoundSource.BLOCKS, volume, stepSound.getPitch(), false); + + for (int iy = 0; (entity.fallDistance > 3.0f) && crushing && ((pos.getY() - iy) >= level.getMinBuildHeight()); iy++) { + if (hasLeaves) { // This layer has leaves that can help break our fall + entity.fallDistance *= 0.66f; // For each layer we are crushing break the momentum + hasLeaves = false; + } + + for (int ix = minX; ix <= maxX; ix++) { + for (int iz = minZ; iz <= maxZ; iz++) { + BlockPos iPos = new BlockPos(ix, pos.getY() - iy, iz); + BlockState state = level.getBlockState(iPos); + if (TreeHelper.isLeaves(state)) { + hasLeaves = true; // This layer has leaves + DTClient.crushLeavesBlock(level, iPos, state, entity); + level.removeBlock(iPos, false); + } else if (!level.isEmptyBlock(iPos)) { + crushing = false; // We hit something solid thus no longer crushing leaves layers + } + } + } + } + } + + @Override + public void entityInside(BlockState state, Level level, BlockPos pos, Entity entity) { + if (isLeavesPassable() || isEntityPassable(entity)) { + super.entityInside(state, level, pos, entity); + } else { + if (entity.getDeltaMovement().y < 0.0D && entity.fallDistance < 2.0f) { + entity.fallDistance = 0.0f; + entity.setDeltaMovement(entity.getDeltaMovement().x, entity.getDeltaMovement().y * 0.5D, entity.getDeltaMovement().z); // Slowly sink into the block + } else if (entity.getDeltaMovement().y > 0 && entity.getDeltaMovement().y < 0.25D) { + entity.setDeltaMovement(entity.getDeltaMovement().x, entity.getDeltaMovement().y + 0.025, entity.getDeltaMovement().z); // Allow a little climbing + } + + entity.setSprinting(false); // One cannot sprint upon tree tops + entity.setDeltaMovement(entity.getDeltaMovement().x * 0.25D, entity.getDeltaMovement().y, entity.getDeltaMovement().z * 0.25D); // Make travel slow and laborious + } + } + ////////////////////////////// // DROPS ////////////////////////////// @@ -752,4 +788,13 @@ public float getShadeBrightness(BlockState state, BlockGetter level, BlockPos po return 0.2F; } + @Override + public void animateTick(BlockState state, Level level, BlockPos pos, RandomSource random) { + if (properties.hasTickParticles && properties.getPrimitiveLeavesBlock().isPresent()) { + properties.getPrimitiveLeavesBlock().ifPresent((b)->b.animateTick(state,level,pos,random)); + } else { + super.animateTick(state, level, pos, random); + } + } + } \ No newline at end of file