User Tools

Site Tools


tutorial:trees

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:trees [2021/06/20 17:08] – Add a note about the PR into Fabric API redgrapefruittutorial:trees [2022/12/21 01:40] (current) – 1.19.3 haykam
Line 1: Line 1:
-===== Adding Trees [1.17] (Advanced) ===== +===== 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.\\
-It is recommended that you learn how to create a ''Feature'' in Minecraft first before reading this tutorial.\\ +
-See [[https://fabricmc.net/wiki/tutorial:features]]+
  
 Trees are a great way to expand Minecraft's world generation in your mod.\\ 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. Beware that this topic is advanced and preferably you should have decent experience with modding world generation in Minecraft before starting.
  
-==== API news ==== +Firstlyyou 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 featurein our case ''Feature.TREE'', we configure it with ''TreeFeatureConfig'', make a placed feature, make a configured feature and finally placed feature (This is the placed feature that gets placed in the world)
-Currently, ''TrunkPlacerType''s, ''FoliagePlacerType''s, ''TreeDecoratorType''s''BlockStateProviderType''and ''IntProviderType''sand you have to use mixins. +
- +
-I'm working on [[https://github.com/FabricMC/fabric/pull/1507|pull request]] to the [[https://github.com/FabricMC/fabric/|Fabric API]] that adds these mixins into\\ the Object Builders API (v1for you to use in a nice way.\\ +
-I hope everything works out and the pull request will be merged into Fabric API for all of us to use!+
  
 ===== Creating a Simple Tree ===== ===== Creating a Simple Tree =====
  
 ==== Architecture ==== ==== Architecture ====
- +Minecraft's tree configuration architecture is split into different classes to allow for very complex and beautiful trees.\\
-Minecraft's tree architecture is split into different classes to allow for very complex and beautiful trees.\\+
 Here's an overview: 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.   - ''SaplingGenerator'' - creates your tree's ''ConfiguredFeature'' from a sapling depending on the context.
-  - ''TrunkPlacer'' - using it you generate the trunk of your tree. +  - ''TreeDecorator'' - you can generate additional elements on your tree with this, for example, beehives or vines(//optional//) 
-  - ''FoliagePlacer'' - using it you generate the foliage of your tree. +  - ''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.
-  - ''TreeDecorator'' (//optional//using it you can generate additional elements on your tree, for example, beehives or apples+
-  - ''BlockStateProvider''it allows you to programmatically return a block depending on the context. This can be useful when you want a part of your tree to be block A, and the other one block B+
-  - ''FeatureSize'' - defines multiple ''Feature''s combined into one. Most of the time with trees, you'll only need the vanilla ''TwoLayersFeatureSize'' implementation. +
-  - ''IntProvider'' - provides an integer value depending on the context.+
  
-The creation of these will be documented later, for now we'll use the vanilla implementations to simplify the process.+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 ==== ==== 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:
  
-We won't need to create a new ''Feature'', instead we'll reuse the vanilla ''TreeFeature'' with our own settings.\\ +<code java> 
-Add this into your ''ModInitializer'''s body: +public static final RegistryEntry<ConfiguredFeature<TreeFeatureConfig, ?>> TREE_RICH ConfiguredFeatures.register("tutorial:tree_rich", Feature.TREE 
- +  // Configure the feature using the builder 
-<code java [enable_line_numbers="true"]> +  new TreeFeatureConfig.Builder( 
- +    BlockStateProvider.of(Blocks.NETHERITE_BLOCK), // Trunk block provider 
-public static final ConfiguredFeature<?, ?> RICH_TREE = Feature.TREE +    new StraightTrunkPlacer(8, 3, 0), // places a straight trunk 
-    // Reconfigure the feature using the builder +    BlockStateProvider.of(Blocks.DIAMOND_BLOCK), // Foliage block provider 
-    .configure(new TreeFeatureConfig.Builder( +    new BlobFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), 3), // places leaves as blob (radiusoffset from trunk, height) 
-         // The SimpleBlockStateProvider just returns what you passed in itThis provider contains the trunk of your tree +    new TwoLayersFeatureSize(1, 0, 1) // The width of the tree at different layersused to see how tall the tree can be without clipping into blocks 
-         new SimpleBlockStateProvider(Blocks.NETHERITE_BLOCK.getDefaultState()), +  ).build()));
-         // The StraightTrunkPlacer places a straight trunk up in the air. +
-         new StraightTrunkPlacer(8, 3, 0), +
-         // This provider contains the foliage of your tree +
-         new SimpleBlockStateProvider(Blocks.DIAMOND_BLOCK.getDefaultState()), +
-         // This provider contains the sapling of your tree +
-         new SimpleBlockStateProvider(RICH_SAPLING.getDefaultState()), +
-         // The BlobFoliagePlacer places a big blob of your foliage. +
-         // The ConstantIntProvider simply returns the integer that you gave it. +
-         // The first provider determines the radius of the blob +
-         // The second provider determines the offset of the foliage from the trunk +
-         // The third integer determines the height of your foliage +
-         new BlobFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), 3), +
-         // The TwoLayersFeatureSize manages two-layer Feature like a tree +
-         // The first integer is the layer limit +
-         // The second integer is the lower layer size +
-         // The third integer is the higher layer size +
-         // Most of the timethese values should not be edited +
-         new TwoLayersFeatureSize(1, 0, 1) +
-    ).build()) +
-    // Now decorate the feature to make it spawn +
-    // Here we use the chance Decorator with a 30% chance of the tree spawning +
-    .decorate(Decorator.CHANCE.configure(new ChanceDecoratorConfig(30))); +
- +
-</code> +
- +
-Now we just register the ''ConfiguredFeature'' to the game like normal and make the biome modification using Fabric's API: +
- +
-<code java [enable_line_numbers="true"]> +
- +
-static { +
-    RegistryKey<ConfiguredFeature<?, ?>> richTreeKey = RegistryKey.of(Registry.CONFIGURED_FEATURE_KEY, new Identifier("tutorial", "rich_tree")); +
-     +
-    Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, richTreeKey.getValue(), RICH_TREE)+
-     +
-    // You should use VEGETAL_DECORATION step for trees +
-    BiomeModifications.addFeature(BiomeSelectors.all(), GenerationStep.Feature.VEGETAL_DECORATION, richTreeKey); +
-+
 </code> </code>
  
 ==== Creating the sapling ==== ==== Creating the sapling ====
- 
 A sapling is a special kind of block to grow trees that requires a ''SaplingGenerator''.\\ A sapling is a special kind of block to grow trees that requires a ''SaplingGenerator''.\\
  
 === Creating the SaplingGenerator === === Creating the SaplingGenerator ===
- 
 A simple generator that takes your tree's ''ConfiguredFeature'' and returns it would look like this: A simple generator that takes your tree's ''ConfiguredFeature'' and returns it would look like this:
  
-<code java [enable_line_numbers="true"]> +<code java>
 public class RichSaplingGenerator extends SaplingGenerator { public class RichSaplingGenerator extends SaplingGenerator {
-    private final ConfiguredFeature<TreeFeatureConfig, ?> feature; +  @Nullable 
- +  @Override 
-    public RichSaplingGenerator(ConfiguredFeature<?, ?> feature) { +  protected RegistryEntry<ConfiguredFeature<TreeFeatureConfig, ?>> getTreeFeature(Random random, boolean bees) { 
-        this.feature = (ConfiguredFeature<TreeFeatureConfig, ?>) feature; +    return Tutorial.TREE_RICH
-    } +  }
- +
-    @Nullable +
-    @Override +
-    protected ConfiguredFeature<TreeFeatureConfig, ?> createTreeFeature(Random random, boolean bees) { +
-        return feature+
-    }+
 } }
- 
 </code> </code>
  
Line 112: Line 57:
  
 === Creating the SaplingBlock === === Creating the SaplingBlock ===
 +Creating the block itself requires you to extend ''SaplingBlock'' instead of just instantiating it, because its constructor has protected access.
  
-Creating the block itself requires you to extend from ''SaplingBlock'' because it's constructor has protected access. +<code java>
- +
-<code java [enable_line_numbers="true"]> +
 public class RichSaplingBlock extends SaplingBlock { public class RichSaplingBlock extends SaplingBlock {
-    public RichSaplingBlock(SaplingGenerator generator, Settings settings) { +  public RichSaplingBlock(SaplingGenerator generator, Settings settings) { 
-        super(generator, settings); +    super(generator, settings); 
-    }+  }
 } }
- 
 </code> </code>
  
 === Registering the SaplingBlock === === 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''.
  
-To register your sapling, follow the normal steps for registering a block (see [[https://fabricmc.net/wiki/tutorial:blocks]]), +Put this in the class you use for your blocks:
-but pass in the instance of your generator with the ''ConfiguredFeature''.+
  
-Put this after your ''ConfiguredFeature'' declaration: +<code java> 
- +public static final RichSaplingBlock RICH_SAPLING = new RichSaplingBlock(new RichSaplingGenerator(TREE_RICH), FabricBlockSettings.copyOf(Blocks.OAK_SAPLING));
-<code java [enable_line_numbers="true"]> +
- +
-public static final RICH_SAPLING = new RichSaplingBlock(new RichSaplingGenerator(RICH_TREE), FabricBlockSettings.copyOf(Blocks.BIRCH_SAPLING.getDefaultState()));+
  
-static { +public static void register() 
-     Registry.register(Registry.BLOCK, new Identifier("tutorial", "rich_sapling"), RICH_SAPLING); +  Registry.register(Registries.BLOCK, new Identifier("tutorial", "rich_sapling"), RICH_SAPLING); 
-     Registry.register(Registry.ITEM, new Identifier("tutorial", "rich_sapling"), new BlockItem(RICH_SAPLING, ItemGroup.MISC));+  Registry.register(Registries.ITEM, new Identifier("tutorial", "rich_sapling"), new BlockItem(RICH_SAPLING, new FabricItemSettings()));
 } }
  
Line 144: Line 83:
  
 ===== Creating a TrunkPlacer ===== ===== Creating a TrunkPlacer =====
- 
 A ''TrunkPlacer'' creates the tree's trunk out of the block given by the ''BlockStateProvider''. A ''TrunkPlacer'' creates the tree's trunk out of the block given by the ''BlockStateProvider''.
  
 ==== Vanilla TrunkPlacers ==== ==== Vanilla TrunkPlacers ====
- 
 Before creating one, look at the reusable vanilla ''TrunkPlacer''s available and try not to reinvent the wheel: Before creating one, look at the reusable vanilla ''TrunkPlacer''s available and try not to reinvent the wheel:
  
-  * ''BendingTrunkPlacer''+  * ''StraightTrunkPlacer''
   * ''ForkingTrunkPlacer''   * ''ForkingTrunkPlacer''
   * ''GiantTrunkPlacer''   * ''GiantTrunkPlacer''
-  * ''StraightTrunkPlacer''+  * ''BendingTrunkPlacer''
  
 ==== Creating a TrunkPlacerType ==== ==== Creating a TrunkPlacerType ====
- 
 A ''TrunkPlacerType'' is necessary to register your ''TrunkPlacer'' into the game. A ''TrunkPlacerType'' is necessary to register your ''TrunkPlacer'' into the game.
  
Line 168: Line 104:
 Here's our mixin, and don't forget to add it to your mixin config: Here's our mixin, and don't forget to add it to your mixin config:
  
-<code java [enable_line_numbers="true"]>+<code java>
  
 @Mixin(TrunkPlacerType.class) @Mixin(TrunkPlacerType.class)
 public interface TrunkPlacerTypeInvoker { public interface TrunkPlacerTypeInvoker {
-    @Invoker +    @Invoker("register") 
-    static <TTrunkPlacer extends TrunkPlacer> TrunkPlacerType<TTrunkPlacer> callRegister(String id, Codec<TTrunkPlacer> codec) { +    static <extends TrunkPlacer> TrunkPlacerType<P> callRegister(String id, Codec<P> codec) { 
-        throw new UnsupportedOperationException();+        throw new IllegalStateException();
     }     }
 } }
- 
 </code> </code>
  
 ==== Creating the TrunkPlacer ==== ==== Creating the TrunkPlacer ====
- 
 A ''TrunkPlacer'' contains multiple things in it: 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 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''   * A getter where you return your ''TrunkPlacerType''
-  * The ''generate'' method where you create your trunk and return a list of ''TreeNode''you've created.+  * 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: Our ''TrunkPlacer'' is going to create two trunks placed diagonally in the world:
  
-<code java [enable_line_numbers="true"]> +<code java>
 public class RichTrunkPlacer extends TrunkPlacer { public class RichTrunkPlacer extends TrunkPlacer {
     // Use the fillTrunkPlacerFields to create our codec     // Use the fillTrunkPlacerFields to create our codec
-    public static final Codec<RichTrunkPlacer> CODEC = RecordCodecBuilder.create+    public static final Codec<RichTrunkPlacer> CODEC = RecordCodecBuilder.create(instance ->  
-            (instance-> fillTrunkPlacerFields(instance).apply(instance, RichTrunkPlacer::new));+        fillTrunkPlacerFields(instance).apply(instance, RichTrunkPlacer::new));
  
     public RichTrunkPlacer(int baseHeight, int firstRandomHeight, int secondRandomHeight) {     public RichTrunkPlacer(int baseHeight, int firstRandomHeight, int secondRandomHeight) {
Line 209: Line 142:
     public List<FoliagePlacer.TreeNode> generate(TestableWorld world, BiConsumer<BlockPos, BlockState> replacer, Random random, int height, BlockPos startPos, TreeFeatureConfig config) {     public List<FoliagePlacer.TreeNode> generate(TestableWorld world, BiConsumer<BlockPos, BlockState> replacer, Random random, int height, BlockPos startPos, TreeFeatureConfig config) {
         // Set the ground beneath the trunk to dirt         // Set the ground beneath the trunk to dirt
-        setToDirt(world, replacer, random, startPos.down(), config);+       setToDirt(world, replacer, random, startPos.down(), config);
                  
-        // Iterate until the trunk height limit and place two blocks using the built-in getAndSetState method+        // Iterate until the trunk height limit and place two blocks using the getAndSetState method from TrunkPlacer
         for (int i = 0; i < height; i++) {         for (int i = 0; i < height; i++) {
-            getAndSetState(world, replacer, random, startPos.up(i), config); +            this.getAndSetState(world, replacer, random, startPos.up(i), config); 
-            getAndSetState(world, replacer, random, startPos.up(i).east().north(), config);+            this.getAndSetState(world, replacer, random, startPos.up(i).east().north(), config);
         }         }
  
Line 223: Line 156:
     }     }
 } }
- 
 </code> </code>
  
 ==== Registering and using your TrunkPlacer ==== ==== Registering and using your TrunkPlacer ====
- +Using your invoker, create and register an instance of a ''TrunkPlacerType'' for your ''TrunkPlacer''.
-Using your invoker, create and register an instance of a ''TrunkPlacerType'' for your ''TrunkPlacer''.\\+
 Put this into your ''ModInitializer''s body: Put this into your ''ModInitializer''s body:
  
-<code java [enable_line_numbers="true"]+<code java> 
-public static final TrunkPlacerType<RichTrunkPlacer> RICH_TRUNK_PLACER = TrunkPlacerTypeInvoker.callRegister("rich_trunk_placer", RichTrunkPlacer.CODEC);+public static final TrunkPlacerType<RichTrunkPlacer> RICH_TRUNK_PLACER = TrunkPlacerTypeInvoker.callRegister("tutorial:rich_trunk_placer", RichTrunkPlacer.CODEC);
 </code> </code>
  
 Now just replace your ''StraightTrunkPlacer'' with your newly created ''RichTrunkPlacer'' and you're done: Now just replace your ''StraightTrunkPlacer'' with your newly created ''RichTrunkPlacer'' and you're done:
-<code java [enable_line_numbers="true"]>+<code java>
 [...] [...]
 new RichTrunkPlacer(8, 3, 0), new RichTrunkPlacer(8, 3, 0),
Line 243: Line 174:
  
 ===== Creating a FoliagePlacer ===== ===== Creating a FoliagePlacer =====
- 
 A ''FoliagePlacer'' creates the tree's foliage out of the block given by the ''BlockStateProvider''. A ''FoliagePlacer'' creates the tree's foliage out of the block given by the ''BlockStateProvider''.
  
 ==== Vanilla FoliagePlacers ==== ==== Vanilla FoliagePlacers ====
- 
 Before creating a ''FoliagePlacer'', look at the reusable vanilla ''FoliagePlacer''s to not reinvent the wheel: Before creating a ''FoliagePlacer'', look at the reusable vanilla ''FoliagePlacer''s to not reinvent the wheel:
  
Line 255: Line 184:
  
 ==== Creating a FoliagePlacerType ==== ==== Creating a FoliagePlacerType ====
- 
 A ''FoliagePlacerType'' is necessary to register a ''FoliagePlacer'' into the game. 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''.\\+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! Our mixin will look almost exactly the same. Don't forget to add it to your mixin config!
  
-<code java [enable_line_numbers="true"]> +<code java>
 @Mixin(FoliagePlacerType.class) @Mixin(FoliagePlacerType.class)
 public interface FoliagePlacerTypeInvoker { public interface FoliagePlacerTypeInvoker {
     @Invoker     @Invoker
-    static <TFoliagePlacer extends FoliagePlacer> FoliagePlacerType<TFoliagePlacer> callRegister(String id, Codec<TFoliagePlacer> codec) { +    static <extends FoliagePlacer> FoliagePlacerType<P> callRegister(String id, Codec<P> codec) { 
-        throw new UnsupportedOperationException();+        throw new IllegalStateException();
     }     }
 } }
- 
 </code> </code>
  
 ==== Creating the FoliagePlacer ==== ==== Creating the FoliagePlacer ====
- 
 A ''FoliagePlacer'' is a bit more complicated to create than a ''TrunkPlacer''. It contains: A ''FoliagePlacer'' is a bit more complicated to create than a ''TrunkPlacer''. It contains:
  
Line 285: Line 210:
 Our ''FoliagePlacer'' will create 4 lines of our foliage block in all directions (north, south, east, west): Our ''FoliagePlacer'' will create 4 lines of our foliage block in all directions (north, south, east, west):
  
-<code java [enable_line_numbers="true"]>+<code java>
  
 public class RichFoliagePlacer extends FoliagePlacer { public class RichFoliagePlacer extends FoliagePlacer {
-    // Here we use the built-in fillFoliagePlacerFields for basic fields 
-    // 
     // For the foliageHeight we use a codec generated by IntProvider.createValidatingCodec     // 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 +    // As the method's argumentswe pass in the minimum and maximum value of the IntProvider
-    // In fieldOf we put the name of the codec entry +
-    // In forGetter we return the value of the entry through a lambda expression +
-    //+
     // To add more fields into your TrunkPlacer/FoliagePlacer/TreeDecorator etc., use multiple .and calls     // 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     // For an example of creating your own type of codec, see the IntProvider.createValidatingCodec method's source
-    public static final Codec<RichFoliagePlacer> CODEC = RecordCodecBuilder.create+    public static final Codec<RichFoliagePlacer> CODEC = RecordCodecBuilder.create(instance -> 
-            (instance-> +        fillFoliagePlacerFields(instance) 
-                    fillFoliagePlacerFields(instance) +        .and(IntProvider.createValidatingCodec(1, 512).fieldOf("foliage_height").forGetter(RichFoliagePlacer::getFoliageHeight)) 
-                    .and(IntProvider +        .apply(instance, RichFoliagePlacer::new));
-                            .createValidatingCodec(1, 512) +
-                            .fieldOf("foliage_height") +
-                            .forGetter((placer) -> placer.foliageHeight)) +
-                    .apply(instance, RichFoliagePlacer::new));+
  
     private final IntProvider foliageHeight;     private final IntProvider foliageHeight;
Line 313: Line 229:
  
         this.foliageHeight = foliageHeight;         this.foliageHeight = foliageHeight;
 +    }
 +
 +    public IntProvider getFoliageHeight() {
 +        return this.foliageHeight;
     }     }
  
Line 325: Line 245:
  
         for (         for (
-                // Start from X: center-radius +            // Start from X: center - radius 
-                Vec3i vec = center.subtract(new Vec3i(radius, 0, 0)); +            Vec3i vec = center.subtract(new Vec3i(radius, 0, 0)); 
-                // End in X: center+radius +            // End in X: center + radius 
-                vec.compareTo(center.add(new Vec3i(radius, 0, 0))) == 0; +            vec.compareTo(center.add(new Vec3i(radius, 0, 0))) == 0; 
-                // Move by 1 each time +            // Move by 1 each time 
-                vec.add(1, 0, 0)) { +            vec.add(1, 0, 0)) { 
-            placeFoliageBlock(world, replacer, random, config, new BlockPos(vec));+            this.placeFoliageBlock(world, replacer, random, config, new BlockPos(vec));
         }         }
  
-        for ( +        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)) { 
-                // Start from Y: center-radius +            this.placeFoliageBlock(world, replacer, random, config, new BlockPos(vec));
-                Vec3i vec = center.subtract(new Vec3i(0, radius, 0)); +
-                // End in Y: center+radius +
-                vec.compareTo(center.add(new Vec3i(0, radius, 0))) == 0; +
-                // Move by 1 each time +
-                vec.add(0, 1, 0) +
-        ) { +
-            placeFoliageBlock(world, replacer, random, config, new BlockPos(vec));+
         }         }
     }     }
Line 360: Line 273:
  
 </code> </code>
- 
 ==== Registering and using your FoliagePlacer ==== ==== Registering and using your FoliagePlacer ====
- 
 This process is almost exactly the same, just use your invoker to create and register the ''FoliagePlacerType'' This process is almost exactly the same, just use your invoker to create and register the ''FoliagePlacerType''
  
-<code java [enable_line_numbers="true"]+<code java> 
-public static final FoliagePlacerType<RichFoliagePlacer> RICH_FOLIAGE_PLACER = FoliagePlacerTypeInvoker.callRegister("rich_foliage_placer", RichFoliagePlacer.CODEC);+public static final FoliagePlacerType<RichFoliagePlacer> RICH_FOLIAGE_PLACER = FoliagePlacerTypeInvoker.callRegister("tutorial:rich_foliage_placer", RichFoliagePlacer.CODEC);
 </code> </code>
  
 and replace the old ''FoliagePlacer'' with your new one: and replace the old ''FoliagePlacer'' with your new one:
  
-<code java [enable_line_numbers="true"]>+<code java>
 [...] [...]
 new RichFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), ConstantIntProvider.create(3)), new RichFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), ConstantIntProvider.create(3)),
Line 378: Line 289:
  
 ===== Creating a TreeDecorator ===== ===== 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''.
-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. If you have a game development background, it's essentially a post-processor, but for trees.
  
 ==== Vanilla TreeDecorators ==== ==== Vanilla TreeDecorators ====
- +Almost none vanilla ''TreeDecorator''s are reusable, except for ''LeavesVineTreeDecorator''
-Almost none vanilla ''TreeDecorator''s are reusable, except for ''LeavesVineTreeDecorator''\\+
 and ''TrunkVineTreeDecorator''. and ''TrunkVineTreeDecorator''.
  
Line 390: Line 299:
  
 ==== Creating a TreeDecoratorType ==== ==== Creating a TreeDecoratorType ====
- 
 A ''TreeDecoratorType'' is required to register your ''TreeDecorator''. A ''TreeDecoratorType'' is required to register your ''TreeDecorator''.
  
Line 397: Line 305:
 Our mixin will look almost exactly the same, don't forget to add it to your mixin config: Our mixin will look almost exactly the same, don't forget to add it to your mixin config:
  
-<code java [enable_line_numbers="true"]> +<code java>
 @Mixin(TreeDecoratorType.class) @Mixin(TreeDecoratorType.class)
 public interface TreeDecoratorTypeInvoker { public interface TreeDecoratorTypeInvoker {
     @Invoker     @Invoker
-    static <TTreeDecorator extends TreeDecorator> TreeDecoratorType<TTreeDecorator> callRegister(String id, Codec<TTreeDecorator> codec) { +    static <extends TreeDecorator> TreeDecoratorType<P> callRegister(String id, Codec<P> codec) { 
-        throw new UnsupportedOperationException();+        throw new IllegalStateException();
     }     }
 } }
- 
 </code> </code>
  
 ==== Creating the TreeDecorator ==== ==== Creating the TreeDecorator ====
- 
 A ''TreeDecorator'' has an extremely simple structure: A ''TreeDecorator'' has an extremely simple structure:
  
Line 419: Line 324:
 Our ''TreeDecorator'' will spawn gold blocks around the trunk of our tree with a 25% chance on a random side of the trunk: Our ''TreeDecorator'' will spawn gold blocks around the trunk of our tree with a 25% chance on a random side of the trunk:
  
-<code java [enable_line_numbers="true"]> +<code java>
 public class RichTreeDecorator extends TreeDecorator { public class RichTreeDecorator extends TreeDecorator {
     public static final RichTreeDecorator INSTANCE = new RichTreeDecorator();     public static final RichTreeDecorator INSTANCE = new RichTreeDecorator();
-    // Our constructor doesn't have any arguments, so we create an empty codec that returns the singleton instance+    // Our constructor doesn't have any arguments, so we create a unit codec that returns the singleton instance
     public static final Codec<RichTreeDecorator> CODEC = Codec.unit(() -> INSTANCE);     public static final Codec<RichTreeDecorator> CODEC = Codec.unit(() -> INSTANCE);
 +    
 +    private RichTreeDecorator() {}
  
     @Override     @Override
Line 432: Line 338:
  
     @Override     @Override
-    public void generate(TestableWorld world, BiConsumer<BlockPos, BlockState> replacer, Random random, List<BlockPos> logPositions, List<BlockPos> leavesPositions) {+    public void generate(TreeDecorator.Generator generator) {
         // Iterate through block positions         // Iterate through block positions
-        for (BlockPos logPosition : logPositions) { +        generator.getLogPositions().forEach(pos -> { 
-            // Pick a value from 0 to 100 and if it'in the to 25 range, continue+            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             // This is the chance for spawning the gold block
-            if (random.nextInt(100<25) { +            if (random.nextInt(4) == 0) { 
-                // Pick a random value from 0 to and determine the side where the gold block will be placed using it+                // Pick a random value from 0 to and determine the side where the gold block will be placed using it
                 int sideRaw = random.nextInt(4);                 int sideRaw = random.nextInt(4);
                 Direction side = switch (sideRaw) {                 Direction side = switch (sideRaw) {
Line 445: Line 352:
                     case 2 -> Direction.EAST;                     case 2 -> Direction.EAST;
                     case 3 -> Direction.WEST;                     case 3 -> Direction.WEST;
-                    default -> throw new ArithmeticException("The picked sideRaw value doesn't fit in the 0 to bounds");+                    default -> throw new ArithmeticException("The picked side value doesn't fit in the 0 to bounds");
                 };                 };
  
Line 455: Line 362:
                 replacer.accept(targetPosition, Blocks.GOLD_BLOCK.getDefaultState());                 replacer.accept(targetPosition, Blocks.GOLD_BLOCK.getDefaultState());
             }             }
-        }+        });
     }     }
 } }
- 
 </code> </code>
  
 ==== Registering and using your TreeDecorator ==== ==== Registering and using your TreeDecorator ====
- 
 First, create your ''TreeDecoratorType'' using the invoker: First, create your ''TreeDecoratorType'' using the invoker:
  
-<code java [enable_line_numbers="true"]+<code java> 
-public static final TreeDecoratorType<RichTreeDecorator> RICH_TREE_DECORATOR = TreeDecoratorTypeInvoker.callRegister("rich_tree_decorator", RichTreeDecorator.CODEC);+public static final TreeDecoratorType<RichTreeDecorator> RICH_TREE_DECORATOR = TreeDecoratorTypeInvoker.callRegister("tutorial:rich_tree_decorator", RichTreeDecorator.CODEC);
 </code> </code>
  
 Then, between the creation of your ''TreeFeatureConfig.Builder'' and the ''build'' method call, put this: Then, between the creation of your ''TreeFeatureConfig.Builder'' and the ''build'' method call, put this:
-<code java [enable_line_numbers="true"]>+ 
 +<code java>
 [...] [...]
 .decorators(Collections.singletonList(RichTreeDecorator.INSTANCE)) .decorators(Collections.singletonList(RichTreeDecorator.INSTANCE))
Line 477: Line 383:
  
 ===== Creating an advanced SaplingGenerator ===== ===== Creating an advanced SaplingGenerator =====
- +So, remember how I told you that ''SaplingGenerator''s can actually contain more complex logic?
-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: Here's an example of that - we create several vanilla trees instead of the actual trees depending on the chance:
  
-<code java [enable_line_numbers="true"]> +<code java>
 public class RichSaplingGenerator extends SaplingGenerator { public class RichSaplingGenerator extends SaplingGenerator {
-    private final ConfiguredFeature<TreeFeatureConfig, ?> feature; 
- 
-    public RichSaplingGenerator(ConfiguredFeature<?, ?> feature) { 
-        this.feature = (ConfiguredFeature<TreeFeatureConfig, ?>) feature; 
-    } 
- 
     @Nullable     @Nullable
     @Override     @Override
-    protected ConfiguredFeature<TreeFeatureConfig, ?> createTreeFeature(Random random, boolean bees) {+    protected RegistryEntry<ConfiguredFeature<TreeFeatureConfig, ?>> getTreeFeature(Random random, boolean bees) {
         int chance = random.nextInt(100);         int chance = random.nextInt(100);
                  
-        // With a 10% chance, an oak tree will be created +        // Each tree has a 10% chance 
-        if (chance <= 10) { +        return switch (chance) { 
-            return ConfiguredFeatures.OAK;+          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
         }         }
-        // With a 20% chance, a birch tree will be created +   }
-        if (chance <= 20) { +
-            return ConfiguredFeatures.BIRCH; +
-        } +
-        // With a 30% chance, a spruce tree will be created +
-        if (chance <= 30) { +
-            return ConfiguredFeatures.SPRUCE; +
-        } +
-        // With a 40% chance, a mega spruce tree will be created +
-        if (chance <= 40) { +
-            return ConfiguredFeatures.MEGA_SPRUCE; +
-        } +
-        // With a 50% chance, a pine tree will be created +
-        if (chance <= 50) { +
-            return ConfiguredFeatures.PINE; +
-        } +
-        // With a 60% chance, a mega pine tree will be created +
-        if (chance <= 60) { +
-            return ConfiguredFeatures.MEGA_PINE; +
-        } +
-        // With a 70% chance, a jungle tree will be created +
-        if (chance <= 70) { +
-            return ConfiguredFeatures.MEGA_JUNGLE_TREE; +
-        } +
-         +
-        // If none of that happened (the chance is between 71 and 99 percents), create the actual tree +
-        return feature; +
-    }+
 } }
- 
 </code> </code>
  
-This isn't a very practical, but it shows what you can achieve using ''SaplingGenerator''s.+This isn't a very practical example, but it shows what you can achieve using ''SaplingGenerator''s.
  
 ===== Extra settings for your tree ===== ===== Extra settings for your tree =====
- 
 Using the extra ''TreeFeatureConfig.Builder'' methods, you can add more settings to your tree: Using the extra ''TreeFeatureConfig.Builder'' methods, you can add more settings to your tree:
  
 ==== dirtProvider ==== ==== dirtProvider ====
- +Sets the ''BlockStateProvider'' for the block of dirt generated under the tree.
-Sets the ''BlockStateProvider'' for the dirt generated beneath the tree.+
  
 Example: Example:
- 
 <code java> <code java>
 [...] [...]
-.dirtProvider(new SimpleBlockStateProvider(Blocks.IRON_BLOCK.getDefaultState()))+.dirtProvider(BlockStateProvider.of(Blocks.IRON_BLOCK))
 [...] [...]
 </code> </code>
  
 ==== decorators ==== ==== decorators ====
- +Used to add ''TreeDecorator''s to your tree. 
-Used to add ''TreeDecorator''s to your tree.\\ +This was briefly showcased in the ''TreeDecorator'' section of this tutorial. 
-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''.
-If you want, you can add **multiple ''TreeDecorator''s** to the same tree using a convenience method like ''Arrays.asList''.+
  
 Example: Example:
Line 568: Line 440:
  
 ==== ignoreVines ==== ==== ignoreVines ====
- 
 Makes the tree generation ignore vines stuck in the way. Makes the tree generation ignore vines stuck in the way.
  
Line 580: Line 451:
  
 ==== forceDirt ==== ==== forceDirt ====
- +Forces the ''TreeFeature'' to generate the dirt underneath the tree.
-Forces the ''TreeFeature'' to generate the dirt underneath the tree automatically no matter if there are blocks in the way.+
  
 Example: Example:
Line 592: Line 462:
  
 ===== Creating a BlockStateProvider ===== ===== Creating a BlockStateProvider =====
- 
-Coming soon. 
- 
-===== Creating an IntProvider ===== 
- 
 Coming soon. Coming soon.
  
tutorial/trees.1624208916.txt.gz · Last modified: 2021/06/20 17:08 by redgrapefruit