===== Adding Trees [1.19.2] (Advanced) ===== It is recommended that you learn how to create a [[tutorial:features|Feature]] in Minecraft first before reading this tutorial.\\ Trees are a great way to expand Minecraft's world generation in your mod.\\ Beware that this topic is advanced and preferably you should have decent experience with modding world generation in Minecraft before starting. Firstly, you need to understand that a ''PlacedFeature'' is what gets placed in the world. There are a few steps to retrieve a tree placed feature: You need a feature, in our case ''Feature.TREE'', we configure it with ''TreeFeatureConfig'', make a placed feature, make a configured feature and finally a placed feature (This is the placed feature that gets placed in the world) ===== Creating a Simple Tree ===== ==== Architecture ==== Minecraft's tree configuration architecture is split into different classes to allow for very complex and beautiful trees.\\ Here's an overview: - ''TrunkPlacer'' - this generates the trunk of the tree. - ''FoliagePlacer'' - this generates the leaves of the tree. - ''SaplingGenerator'' - creates your tree's ''ConfiguredFeature'' from a sapling depending on the context. - ''TreeDecorator'' - you can generate additional elements on your tree with this, for example, beehives or vines. (//optional//) - ''BlockStateProvider'' - Can return blocks depending on context. This is useful if you want a part of your tree to be block A, and the other one block B. You can create custom implementations of these if you want a tree that does not look like vanilla's. However, the vanilla implementations are usually enough. ==== Creating the ConfiguredFeature ==== We don't need to create a new ''Feature'', as the vanilla ''TreeFeature'' is configurable. Add this into your ''ModInitializer'''s (This could be your content initializer if you prefer) body: public static final RegistryEntry> TREE_RICH = ConfiguredFeatures.register("tutorial:tree_rich", Feature.TREE // Configure the feature using the builder new TreeFeatureConfig.Builder( BlockStateProvider.of(Blocks.NETHERITE_BLOCK), // Trunk block provider new StraightTrunkPlacer(8, 3, 0), // places a straight trunk BlockStateProvider.of(Blocks.DIAMOND_BLOCK), // Foliage block provider new BlobFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), 3), // places leaves as a blob (radius, offset from trunk, height) new TwoLayersFeatureSize(1, 0, 1) // The width of the tree at different layers; used to see how tall the tree can be without clipping into blocks ).build())); ==== Creating the sapling ==== A sapling is a special kind of block to grow trees that requires a ''SaplingGenerator''.\\ === Creating the SaplingGenerator === A simple generator that takes your tree's ''ConfiguredFeature'' and returns it would look like this: public class RichSaplingGenerator extends SaplingGenerator { @Nullable @Override protected RegistryEntry> getTreeFeature(Random random, boolean bees) { return Tutorial.TREE_RICH; } } An example of an advanced ''SaplingGenerator'' will be shown in a later section. === Creating the SaplingBlock === Creating the block itself requires you to extend ''SaplingBlock'' instead of just instantiating it, because its constructor has protected access. public class RichSaplingBlock extends SaplingBlock { public RichSaplingBlock(SaplingGenerator generator, Settings settings) { super(generator, settings); } } === Registering the SaplingBlock === To register your sapling, follow the normal steps for registering a block (see [[tutorial:blocks]]), but pass in the instance of your generator with the ''ConfiguredFeature''. Put this in the class you use for your blocks: public static final RichSaplingBlock RICH_SAPLING = new RichSaplingBlock(new RichSaplingGenerator(TREE_RICH), FabricBlockSettings.copyOf(Blocks.OAK_SAPLING)); public static void register() { Registry.register(Registries.BLOCK, new Identifier("tutorial", "rich_sapling"), RICH_SAPLING); Registry.register(Registries.ITEM, new Identifier("tutorial", "rich_sapling"), new BlockItem(RICH_SAPLING, new FabricItemSettings())); } ===== Creating a TrunkPlacer ===== A ''TrunkPlacer'' creates the tree's trunk out of the block given by the ''BlockStateProvider''. ==== Vanilla TrunkPlacers ==== Before creating one, look at the reusable vanilla ''TrunkPlacer''s available and try not to reinvent the wheel: * ''StraightTrunkPlacer'' * ''ForkingTrunkPlacer'' * ''GiantTrunkPlacer'' * ''BendingTrunkPlacer'' ==== Creating a TrunkPlacerType ==== A ''TrunkPlacerType'' is necessary to register your ''TrunkPlacer'' into the game. Unfortunately, Fabric API currently doesn't have an API for creating and registering ''TrunkPlacer''s,\\ so we have to use mixins. We're going to create an invoker (see [[https://github.com/2xsaiko/mixin-cheatsheet/blob/master/invoker.md]]) to\\ invoke the private static ''TrunkPlacerType.register'' method. Here's our mixin, and don't forget to add it to your mixin config: @Mixin(TrunkPlacerType.class) public interface TrunkPlacerTypeInvoker { @Invoker("register") static

TrunkPlacerType

callRegister(String id, Codec

codec) { throw new IllegalStateException(); } } ==== Creating the TrunkPlacer ==== A ''TrunkPlacer'' contains multiple things in it: * A codec for serialization. Codecs are a topic of their own, here we'll just use the ''fillTrunkPlacerFields'' method to generate it. * A getter where you return your ''TrunkPlacerType'' * The ''generate'' method where you place the trunk and return a list of ''TreeNode''s, which are used by the foliage placer for where to place the leaves. Our ''TrunkPlacer'' is going to create two trunks placed diagonally in the world: public class RichTrunkPlacer extends TrunkPlacer { // Use the fillTrunkPlacerFields to create our codec public static final Codec CODEC = RecordCodecBuilder.create(instance -> fillTrunkPlacerFields(instance).apply(instance, RichTrunkPlacer::new)); public RichTrunkPlacer(int baseHeight, int firstRandomHeight, int secondRandomHeight) { super(baseHeight, firstRandomHeight, secondRandomHeight); } @Override protected TrunkPlacerType getType() { return Tutorial.RICH_TRUNK_PLACER; } @Override public List generate(TestableWorld world, BiConsumer replacer, Random random, int height, BlockPos startPos, TreeFeatureConfig config) { // Set the ground beneath the trunk to dirt setToDirt(world, replacer, random, startPos.down(), config); // Iterate until the trunk height limit and place two blocks using the getAndSetState method from TrunkPlacer for (int i = 0; i < height; i++) { this.getAndSetState(world, replacer, random, startPos.up(i), config); this.getAndSetState(world, replacer, random, startPos.up(i).east().north(), config); } // We create two TreeNodes - one for the first trunk, and the other for the second // Put the highest block in the trunk as the center position for the FoliagePlacer to use return ImmutableList.of(new FoliagePlacer.TreeNode(startPos.up(height), 0, false), new FoliagePlacer.TreeNode(startPos.east().north().up(height), 0, false)); } } ==== Registering and using your TrunkPlacer ==== Using your invoker, create and register an instance of a ''TrunkPlacerType'' for your ''TrunkPlacer''. Put this into your ''ModInitializer''s body: public static final TrunkPlacerType RICH_TRUNK_PLACER = TrunkPlacerTypeInvoker.callRegister("tutorial:rich_trunk_placer", RichTrunkPlacer.CODEC); Now just replace your ''StraightTrunkPlacer'' with your newly created ''RichTrunkPlacer'' and you're done: [...] new RichTrunkPlacer(8, 3, 0), [...] ===== Creating a FoliagePlacer ===== A ''FoliagePlacer'' creates the tree's foliage out of the block given by the ''BlockStateProvider''. ==== Vanilla FoliagePlacers ==== Before creating a ''FoliagePlacer'', look at the reusable vanilla ''FoliagePlacer''s to not reinvent the wheel: * ''BlobFoliagePlacer'' * ''BushFoliagePlacer'' * ''RandomSpreadFoliagePlacer'' ==== Creating a FoliagePlacerType ==== A ''FoliagePlacerType'' is necessary to register a ''FoliagePlacer'' into the game. Similarly to the ''TrunkPlacerType'', Fabric API doesn't provide utilities for creating a ''FoliagePlacerType''. Our mixin will look almost exactly the same. Don't forget to add it to your mixin config! @Mixin(FoliagePlacerType.class) public interface FoliagePlacerTypeInvoker { @Invoker static

FoliagePlacerType

callRegister(String id, Codec

codec) { throw new IllegalStateException(); } } ==== Creating the FoliagePlacer ==== A ''FoliagePlacer'' is a bit more complicated to create than a ''TrunkPlacer''. It contains: * A codec for serialization. In this example we show how to add an extra IntProvider to the codec. * A getter for your ''FoliagePlacerType''. * The ''generate'' method where you create the foliage. * The ''getRandomHeight'' method. Despite the name, you normally should just return the maximum height of your foliage. * The ''isInvalidForLeaves'' method where you can set restrictions on where to put the leaves. Our ''FoliagePlacer'' will create 4 lines of our foliage block in all directions (north, south, east, west): public class RichFoliagePlacer extends FoliagePlacer { // For the foliageHeight we use a codec generated by IntProvider.createValidatingCodec // As the method's arguments, we pass in the minimum and maximum value of the IntProvider // To add more fields into your TrunkPlacer/FoliagePlacer/TreeDecorator etc., use multiple .and calls // // For an example of creating your own type of codec, see the IntProvider.createValidatingCodec method's source public static final Codec CODEC = RecordCodecBuilder.create(instance -> fillFoliagePlacerFields(instance) .and(IntProvider.createValidatingCodec(1, 512).fieldOf("foliage_height").forGetter(RichFoliagePlacer::getFoliageHeight)) .apply(instance, RichFoliagePlacer::new)); private final IntProvider foliageHeight; public RichFoliagePlacer(IntProvider radius, IntProvider offset, IntProvider foliageHeight) { super(radius, offset); this.foliageHeight = foliageHeight; } public IntProvider getFoliageHeight() { return this.foliageHeight; } @Override protected FoliagePlacerType getType() { return Tutorial.RICH_FOLIAGE_PLACER; } @Override protected void generate(TestableWorld world, BiConsumer replacer, Random random, TreeFeatureConfig config, int trunkHeight, TreeNode treeNode, int foliageHeight, int radius, int offset) { BlockPos.Mutable center = treeNode.getCenter().mutableCopy(); for ( // Start from X: center - radius Vec3i vec = center.subtract(new Vec3i(radius, 0, 0)); // End in X: center + radius vec.compareTo(center.add(new Vec3i(radius, 0, 0))) == 0; // Move by 1 each time vec.add(1, 0, 0)) { this.placeFoliageBlock(world, replacer, random, config, new BlockPos(vec)); } for (Vec3i vec = center.subtract(new Vec3i(0, radius, 0)); vec.compareTo(center.add(new Vec3i(0, radius, 0))) == 0; vec.add(0, 1, 0)) { this.placeFoliageBlock(world, replacer, random, config, new BlockPos(vec)); } } @Override public int getRandomHeight(Random random, int trunkHeight, TreeFeatureConfig config) { // Just pick the random height using the IntProvider return foliageHeight.get(random); } @Override protected boolean isInvalidForLeaves(Random random, int dx, int y, int dz, int radius, boolean giantTrunk) { // Our FoliagePlacer doesn't set any restrictions on leaves return false; } } ==== Registering and using your FoliagePlacer ==== This process is almost exactly the same, just use your invoker to create and register the ''FoliagePlacerType'' public static final FoliagePlacerType RICH_FOLIAGE_PLACER = FoliagePlacerTypeInvoker.callRegister("tutorial:rich_foliage_placer", RichFoliagePlacer.CODEC); and replace the old ''FoliagePlacer'' with your new one: [...] new RichFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), ConstantIntProvider.create(3)), [...] ===== Creating a TreeDecorator ===== A ''TreeDecorator'' allows you to add extra elements to your tree (apples, beehives etc.) //after// the execution of your ''TrunkPlacer'' and ''FoliagePlacer''. If you have a game development background, it's essentially a post-processor, but for trees. ==== Vanilla TreeDecorators ==== Almost none vanilla ''TreeDecorator''s are reusable, except for ''LeavesVineTreeDecorator'' and ''TrunkVineTreeDecorator''. For anything non-trivial, you have to create your own ''TreeDecorator''s. ==== Creating a TreeDecoratorType ==== A ''TreeDecoratorType'' is required to register your ''TreeDecorator''. Fabric API doesn't provide utilities for creating ''TreeDecoratorType''s, so we have to use mixins again. Our mixin will look almost exactly the same, don't forget to add it to your mixin config: @Mixin(TreeDecoratorType.class) public interface TreeDecoratorTypeInvoker { @Invoker static

TreeDecoratorType

callRegister(String id, Codec

codec) { throw new IllegalStateException(); } } ==== Creating the TreeDecorator ==== A ''TreeDecorator'' has an extremely simple structure: * A codec for serialization, but it's empty by default because the constructor has no arguments. You can always expand it if you want * A getter for your ''TreeDecoratorType'' * The ''generate'' method to decorate the tree Our ''TreeDecorator'' will spawn gold blocks around the trunk of our tree with a 25% chance on a random side of the trunk: public class RichTreeDecorator extends TreeDecorator { public static final RichTreeDecorator INSTANCE = new RichTreeDecorator(); // Our constructor doesn't have any arguments, so we create a unit codec that returns the singleton instance public static final Codec CODEC = Codec.unit(() -> INSTANCE); private RichTreeDecorator() {} @Override protected TreeDecoratorType getType() { return Tutorial.RICH_TREE_DECORATOR; } @Override public void generate(TreeDecorator.Generator generator) { // Iterate through block positions generator.getLogPositions().forEach(pos -> { Random random = generator.getRandom(); // Pick a value from 0 (inclusive) to 4 (exclusive) and if it's 0, continue // This is the chance for spawning the gold block if (random.nextInt(4) == 0) { // Pick a random value from 0 to 4 and determine the side where the gold block will be placed using it int sideRaw = random.nextInt(4); Direction side = switch (sideRaw) { case 0 -> Direction.NORTH; case 1 -> Direction.SOUTH; case 2 -> Direction.EAST; case 3 -> Direction.WEST; default -> throw new ArithmeticException("The picked side value doesn't fit in the 0 to 4 bounds"); }; // Offset the log position by the resulting side BlockPos targetPosition = logPosition.offset(side, 1); // Place the gold block using the replacer BiConsumer // This is the standard way of placing blocks in TrunkPlacers, FoliagePlacers and TreeDecorators replacer.accept(targetPosition, Blocks.GOLD_BLOCK.getDefaultState()); } }); } } ==== Registering and using your TreeDecorator ==== First, create your ''TreeDecoratorType'' using the invoker: public static final TreeDecoratorType RICH_TREE_DECORATOR = TreeDecoratorTypeInvoker.callRegister("tutorial:rich_tree_decorator", RichTreeDecorator.CODEC); Then, between the creation of your ''TreeFeatureConfig.Builder'' and the ''build'' method call, put this: [...] .decorators(Collections.singletonList(RichTreeDecorator.INSTANCE)) [...] ===== Creating an advanced SaplingGenerator ===== So, remember how I told you that ''SaplingGenerator''s can actually contain more complex logic? Here's an example of that - we create several vanilla trees instead of the actual trees depending on the chance: public class RichSaplingGenerator extends SaplingGenerator { @Nullable @Override protected RegistryEntry> getTreeFeature(Random random, boolean bees) { int chance = random.nextInt(100); // Each tree has a 10% chance return switch (chance) { case 10 -> TreeConfiguredFeatures.OAK; case 20 -> TreeConfiguredFeatures.BIRCH; case 30 -> TreeConfiguredFeatures.MEGA_SPRUCE; case 40 -> TreeConfiguredFeatures.PINE; case 50 -> TreeConfiguredFeatures.MEGA_PINE; case 60 -> TreeConfiguredFeatures.MEGA_JUNGLE_TREE; default -> Tutorial.RICH } } } This isn't a very practical example, but it shows what you can achieve using ''SaplingGenerator''s. ===== Extra settings for your tree ===== Using the extra ''TreeFeatureConfig.Builder'' methods, you can add more settings to your tree: ==== dirtProvider ==== Sets the ''BlockStateProvider'' for the block of dirt generated under the tree. Example: [...] .dirtProvider(BlockStateProvider.of(Blocks.IRON_BLOCK)) [...] ==== decorators ==== Used to add ''TreeDecorator''s to your tree. This was briefly showcased in the ''TreeDecorator'' section of this tutorial. If you want, you can add multiple ''TreeDecorator''s to the same tree using a convenience method like ''Arrays.asList''. Example: [...] .decorators(Arrays.asList( FirstTreeDecorator.INSTANCE, SecondTreeDecorator.INSTANCE, ThirdTreeDecorator.INSTANCE )) [...] ==== ignoreVines ==== Makes the tree generation ignore vines stuck in the way. Example: [...] .ignoreVines() [...] ==== forceDirt ==== Forces the ''TreeFeature'' to generate the dirt underneath the tree. Example: [...] .forceDirt() [...] ===== Creating a BlockStateProvider ===== Coming soon.