package net.minecraft.world.level.block.grower; import com.mojang.serialization.Codec; import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import java.util.Map; import java.util.Optional; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.registries.Registries; import net.minecraft.data.worldgen.features.TreeFeatures; import net.minecraft.resources.ResourceKey; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.BlockTags; import net.minecraft.util.RandomSource; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; import org.jetbrains.annotations.Nullable; public final class TreeGrower { private static final Map GROWERS = new Object2ObjectArrayMap<>(); public static final Codec CODEC = Codec.stringResolver(treeGrower -> treeGrower.name, GROWERS::get); public static final TreeGrower OAK = new TreeGrower( "oak", 0.1F, Optional.empty(), Optional.empty(), Optional.of(TreeFeatures.OAK), Optional.of(TreeFeatures.FANCY_OAK), Optional.of(TreeFeatures.OAK_BEES_005), Optional.of(TreeFeatures.FANCY_OAK_BEES_005) ); public static final TreeGrower SPRUCE = new TreeGrower( "spruce", 0.5F, Optional.of(TreeFeatures.MEGA_SPRUCE), Optional.of(TreeFeatures.MEGA_PINE), Optional.of(TreeFeatures.SPRUCE), Optional.empty(), Optional.empty(), Optional.empty() ); public static final TreeGrower MANGROVE = new TreeGrower( "mangrove", 0.85F, Optional.empty(), Optional.empty(), Optional.of(TreeFeatures.MANGROVE), Optional.of(TreeFeatures.TALL_MANGROVE), Optional.empty(), Optional.empty() ); public static final TreeGrower AZALEA = new TreeGrower("azalea", Optional.empty(), Optional.of(TreeFeatures.AZALEA_TREE), Optional.empty()); public static final TreeGrower BIRCH = new TreeGrower("birch", Optional.empty(), Optional.of(TreeFeatures.BIRCH), Optional.of(TreeFeatures.BIRCH_BEES_005)); public static final TreeGrower JUNGLE = new TreeGrower( "jungle", Optional.of(TreeFeatures.MEGA_JUNGLE_TREE), Optional.of(TreeFeatures.JUNGLE_TREE_NO_VINE), Optional.empty() ); public static final TreeGrower ACACIA = new TreeGrower("acacia", Optional.empty(), Optional.of(TreeFeatures.ACACIA), Optional.empty()); public static final TreeGrower CHERRY = new TreeGrower("cherry", Optional.empty(), Optional.of(TreeFeatures.CHERRY), Optional.of(TreeFeatures.CHERRY_BEES_005)); public static final TreeGrower DARK_OAK = new TreeGrower("dark_oak", Optional.of(TreeFeatures.DARK_OAK), Optional.empty(), Optional.empty()); public static final TreeGrower PALE_OAK = new TreeGrower("pale_oak", Optional.of(TreeFeatures.PALE_OAK_BONEMEAL), Optional.empty(), Optional.empty()); private final String name; private final float secondaryChance; private final Optional>> megaTree; private final Optional>> secondaryMegaTree; private final Optional>> tree; private final Optional>> secondaryTree; private final Optional>> flowers; private final Optional>> secondaryFlowers; public TreeGrower( String name, Optional>> megaTree, Optional>> tree, Optional>> flowers ) { this(name, 0.0F, megaTree, Optional.empty(), tree, Optional.empty(), flowers, Optional.empty()); } public TreeGrower( String name, float secondaryChance, Optional>> megaTree, Optional>> secondaryMegaTree, Optional>> tree, Optional>> secondaryTree, Optional>> flowers, Optional>> secondaryFlowers ) { this.name = name; this.secondaryChance = secondaryChance; this.megaTree = megaTree; this.secondaryMegaTree = secondaryMegaTree; this.tree = tree; this.secondaryTree = secondaryTree; this.flowers = flowers; this.secondaryFlowers = secondaryFlowers; GROWERS.put(name, this); } @Nullable private ResourceKey> getConfiguredFeature(RandomSource random, boolean flowers) { if (random.nextFloat() < this.secondaryChance) { if (flowers && this.secondaryFlowers.isPresent()) { return (ResourceKey>)this.secondaryFlowers.get(); } if (this.secondaryTree.isPresent()) { return (ResourceKey>)this.secondaryTree.get(); } } return flowers && this.flowers.isPresent() ? (ResourceKey)this.flowers.get() : (ResourceKey)this.tree.orElse(null); } @Nullable private ResourceKey> getConfiguredMegaFeature(RandomSource random) { return this.secondaryMegaTree.isPresent() && random.nextFloat() < this.secondaryChance ? (ResourceKey)this.secondaryMegaTree.get() : (ResourceKey)this.megaTree.orElse(null); } public boolean growTree(ServerLevel level, ChunkGenerator chunkGenerator, BlockPos pos, BlockState state, RandomSource random) { ResourceKey> resourceKey = this.getConfiguredMegaFeature(random); if (resourceKey != null) { Holder> holder = (Holder>)level.registryAccess() .lookupOrThrow(Registries.CONFIGURED_FEATURE) .get(resourceKey) .orElse(null); if (holder != null) { for (int i = 0; i >= -1; i--) { for (int j = 0; j >= -1; j--) { if (isTwoByTwoSapling(state, level, pos, i, j)) { ConfiguredFeature configuredFeature = holder.value(); BlockState blockState = Blocks.AIR.defaultBlockState(); level.setBlock(pos.offset(i, 0, j), blockState, 260); level.setBlock(pos.offset(i + 1, 0, j), blockState, 260); level.setBlock(pos.offset(i, 0, j + 1), blockState, 260); level.setBlock(pos.offset(i + 1, 0, j + 1), blockState, 260); if (configuredFeature.place(level, chunkGenerator, random, pos.offset(i, 0, j))) { return true; } level.setBlock(pos.offset(i, 0, j), state, 260); level.setBlock(pos.offset(i + 1, 0, j), state, 260); level.setBlock(pos.offset(i, 0, j + 1), state, 260); level.setBlock(pos.offset(i + 1, 0, j + 1), state, 260); return false; } } } } } ResourceKey> resourceKey2 = this.getConfiguredFeature(random, this.hasFlowers(level, pos)); if (resourceKey2 == null) { return false; } else { Holder> holder2 = (Holder>)level.registryAccess() .lookupOrThrow(Registries.CONFIGURED_FEATURE) .get(resourceKey2) .orElse(null); if (holder2 == null) { return false; } else { ConfiguredFeature configuredFeature2 = holder2.value(); BlockState blockState2 = level.getFluidState(pos).createLegacyBlock(); level.setBlock(pos, blockState2, 260); if (configuredFeature2.place(level, chunkGenerator, random, pos)) { if (level.getBlockState(pos) == blockState2) { level.sendBlockUpdated(pos, state, blockState2, 2); } return true; } else { level.setBlock(pos, state, 260); return false; } } } } private static boolean isTwoByTwoSapling(BlockState state, BlockGetter level, BlockPos pos, int xOffset, int yOffset) { Block block = state.getBlock(); return level.getBlockState(pos.offset(xOffset, 0, yOffset)).is(block) && level.getBlockState(pos.offset(xOffset + 1, 0, yOffset)).is(block) && level.getBlockState(pos.offset(xOffset, 0, yOffset + 1)).is(block) && level.getBlockState(pos.offset(xOffset + 1, 0, yOffset + 1)).is(block); } private boolean hasFlowers(LevelAccessor level, BlockPos pos) { for (BlockPos blockPos : BlockPos.MutableBlockPos.betweenClosed(pos.below().north(2).west(2), pos.above().south(2).east(2))) { if (level.getBlockState(blockPos).is(BlockTags.FLOWERS)) { return true; } } return false; } }