User Tools

Site Tools


tutorial:jigsaw

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:jigsaw [2020/08/14 18:26] – created emmanuelmesstutorial:jigsaw [2021/12/30 05:47] (current) – Fix link directing at Forge version, not Fabric redgrapefruit
Line 2: Line 2:
 Jigsaws are good for advanced structures such as dungeons & villages, and allow you to spend more time on actually building content vs. messing with procedural generation code.  Jigsaws are good for advanced structures such as dungeons & villages, and allow you to spend more time on actually building content vs. messing with procedural generation code. 
  
-A repository with finished code can be found [[https://github.com/Draylar/jigsaw-example-mod/tree/1.16|here for 1.16]] +A repository with the finished code from this tutorial can be found [[https://github.com/Draylar/jigsaw-example-mod|here for 1.14 - 1.16]]. 
 +A full example with structure files and more can be found [[https://github.com/TelepathicGrunt/StructureTutorialMod/tree/1.18.x-Fabric-Jigsaw|here for 1.15 - 1.18]].
  
 ===== Creating a StructureFeature ===== ===== Creating a StructureFeature =====
Line 27: Line 28:
 </code> </code>
  
-//getName// is the name of your structureIt's used for few things, including: +Finally, //getStructureStartFactory// is the generation portion of your StructureFeatureYou'll have to return factory method to create new StructureStarts-- we can simply method reference the constructorOur implementation will look like thiswith ''ExampleStructure.Start'' being the next step in this tutorial:
-  * Serializing and Deserializing your structure from a chunk +
-  * Locating your structure +
-  * Labeling your structure +
-Vanilla convention is a properly capitalized title"Igloo," "Village," and "Fortress," are all valid.((Do not use a ''TranslatableText'' for this: while the intent is valiant, things will break.))+
 <code java [enable_line_numbers="false"]> <code java [enable_line_numbers="false"]>
 @Override @Override
-public String getName() { +public StructureStartFactory<StructurePoolFeatureConfig> getStructureStartFactory() { 
-    return "Example Feature";+    return Start::new;
 } }
 </code> </code>
  
-The exact usage for //getRadius// is still under discussion, but my guess (as the writer) is that it's related to translation within chunkA structure with a radius of 4 may be moved around within the chunk bounds, while a structure with a radius of 8 will always be centered. This may be wrong. For now, the radius should be half the size of your structure; if your structure will take up more than single chunk (or fill it entirely), return 8. +==== Creating ExampleFeature.Start class ==== 
-<code java [enable_line_numbers="false"]> + 
-@Override +''Start'' is like the initialization stage of generating our structure in the world. For now, create simple child class with a constructor that also overrides initialize: 
-public int getRadius() { +<code java [enable_line_numbers="true"]> 
-    return 2;+public static class Start extends StructureStart<StructurePoolFeatureConfig>
 + 
 +    Start(StructureFeature<StructurePoolFeatureConfig> feature, int x, int z, BlockBox box, int int_3, long seed) { 
 +        super(feature, x, z, box, int_3, seed); 
 +    } 
 + 
 +    @Override 
 +    public void init(ChunkGenerator chunkGenerator, StructureManager structureManager, int x, int z, Biome biome, 
 +                     StructurePoolFeatureConfig config) { 
 +  
 +    }
 } }
 </code> </code>
  
-Finally, //getStructureStartFactory// is the generation portion of your StructureFeature. You'll have to return a factory method to create new StructureStarts-- we can simply method reference the constructor. Our implementation will look like this, with ''ExampleStructureStart'' being the next step in this tutorial:+Now all we have to do is add our starting piece in our ''init'' method:
 <code java [enable_line_numbers="false"]> <code java [enable_line_numbers="false"]>
 @Override @Override
-public StructureStartFactory getStructureStartFactory() { +public void init(ChunkGenerator chunkGenerator, StructureManager structureManager, int x, int z, Biome biome, 
-    return ExampleStructureStart::new;+                 StructurePoolFeatureConfig config) { 
 +    BlockPos pos = new BlockPos(x * 16, 80, z * 16); 
 + 
 +    boolean randomYPos = false; 
 +    boolean calculateMaxYFromPiecePositions = false; 
 + 
 +    ExamplePiece.init(); 
 + 
 +    StructurePoolBasedGenerator.method_30419(config.getStartPool(), config.getSize(), ExamplePiece::new, chunkGenerator, structureManager, 
 +            pos, children, random, calculateMaxYFromPiecePositions, randomYPos); 
 + 
 +    setBoundingBoxFromChildren();
 } }
 </code> </code>
 +
 +The Identifier is the starting pool to select from, the int is the size of the entire structure (with 7 being "7 squares out"), and the 3rd argument is a factory for the piece we'll register in a second.
  
 Our finalized ''ExampleFeature'' class: Our finalized ''ExampleFeature'' class:
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-import com.mojang.datafixers.Dynamic; +public class ExampleFeature extends StructureFeature<StructurePoolFeatureConfig> {
-import net.minecraft.world.gen.chunk.ChunkGenerator; +
-import net.minecraft.world.gen.feature.DefaultFeatureConfig; +
-import net.minecraft.world.gen.feature.StructureFeature; +
- +
-import java.util.Random; +
-import java.util.function.Function; +
- +
-public class ExampleFeature extends StructureFeature<DefaultFeatureConfig> {+
  
-    public ExampleFeature(Function<Dynamic<?>, ? extends DefaultFeatureConfigconfig) { +    public ExampleFeature(Codec<StructurePoolFeatureConfigcodec) { 
-        super(config);+        super(codec);
     }     }
  
     @Override     @Override
-    public boolean shouldStartAt(ChunkGenerator<?chunkGenerator, Random random, int x, int z) { +    public StructureStartFactory<StructurePoolFeatureConfiggetStructureStartFactory() { 
-        return true;+        return Start::new;
     }     }
  
     @Override     @Override
-    public StructureStartFactory getStructureStartFactory() { +    protected boolean shouldStartAt(ChunkGenerator chunkGenerator, BiomeSource biomeSource, long l, ChunkRandom chunkRandom, int i, int j, Biome biome, ChunkPos chunkPos, StructurePoolFeatureConfig featureConfig) { 
-        return ExampleStructureStart::new;+        return chunkRandom.nextInt(150) == 0// 1 in 150
     }     }
  
-    @Override +    public static class Start extends StructureStart<StructurePoolFeatureConfig> {
-    public String getName() { +
-        return "Example Feature"; +
-    }+
  
-    @Override +        Start(StructureFeature<StructurePoolFeatureConfig> feature, int x, int z, BlockBox box, int int_3, long seed) { 
-    public int getRadius() { +            super(feature, x, z, box, int_3, seed); 
-        return 2;+        } 
 + 
 +        @Override 
 +        public void init(ChunkGenerator chunkGenerator, StructureManager structureManager, int x, int z, Biome biome, 
 +                         StructurePoolFeatureConfig config) { 
 +            BlockPos pos = new BlockPos(x * 16, 80, z * 16); 
 + 
 +            boolean randomYPos = false; 
 +            boolean calculateMaxYFromPiecePositions = false; 
 + 
 +            ExamplePiece.init(); 
 + 
 +            StructurePoolBasedGenerator.addPieces(config.startPool, config.size, ExamplePiece::new, chunkGenerator, structureManager, 
 +                    pos, children, random, calculateMaxYFromPiecePositions, randomYPos); 
 + 
 +            setBoundingBoxFromChildren(); 
 +        }
     }     }
 } }
 </code> </code>
  
-==== Creating a StructureStart class ===== +===== Creating a Piece ===== 
-''StructureStart'' is like the initialization stage of generating our structure in the worldFor now, create a simple child class with a constructor that also overrides initialize+This portion is very simple. A piece represents one section or element in your full structure. You'll need to create a basic piece class, and we'll register it later
-<code java [enable_line_numbers="true"]> +<code java [enable_line_numbers="false"]> 
-public class ExampleStructureStart extends StructureStart {+public class ExamplePiece extends PoolStructurePiece {
  
-    ExampleStructureStart(StructureFeature<?> featureint x, int zBiome biome, MutableIntBoundingBox box, int int_3long seed) { +    ExamplePiece(StructureManager structureManager_1StructurePoolElement structurePoolElement_1, BlockPos blockPos_1, int int_1BlockRotation blockRotation_1BlockBox mutableIntBoundingBox_1) { 
-        super(featurexzbiomeboxint_3seed);+        super(TutorialJigsaws.EXAMPLE_PIECEstructureManager_1structurePoolElement_1blockPos_1int_1blockRotation_1mutableIntBoundingBox_1);
     }     }
  
-    @Override +    public ExamplePiece(StructureManager managerCompoundTag tag) { 
-    public void initialize(ChunkGenerator<?> chunkGenerator, StructureManager structureManagerint x, int z, Biome biome) { +        super(manager, tag, TutorialJigsaws.EXAMPLE_PIECE);
-    +
     }     }
 } }
 </code> </code>
 +
 +Where ''ExampleMod.EXAMPLE_PIECE'' is a reference to our registered piece.
 +
 +
 + In a static block at the top of our class, we're going to register our structure pools using ''StructurePoolBasedGenerator.REGISTRY'':
 +<code java [enable_line_numbers="true"]>
 +public static void init() { }
 +
 +static {
 +    StructurePoolBasedGenerator.REGISTRY.add(
 +            new StructurePool(
 +                    TutorialJigsaws.BASE_POOL,
 +                    new Identifier("empty"),
 +                    ImmutableList.of(
 +                            Pair.of(new LegacySinglePoolElement("tutorial:black_square"), 1),
 +                            Pair.of(new LegacySinglePoolElement("tutorial:white_square"), 1)
 +                    ),
 +                    StructurePool.Projection.RIGID
 +            )
 +    );
 +
 +    StructurePoolBasedGenerator.REGISTRY.add(
 +            new StructurePool(
 +                    TutorialJigsaws.COLOR_POOL,
 +                    new Identifier("empty"),
 +                    ImmutableList.of(
 +                            Pair.of(new LegacySinglePoolElement("tutorial:lime_square"), 1),
 +                            Pair.of(new LegacySinglePoolElement("tutorial:magenta_square"), 1),
 +                            Pair.of(new LegacySinglePoolElement("tutorial:orange_square"), 1),
 +                            Pair.of(new LegacySinglePoolElement("tutorial:light_blue_square"), 1)
 +                    ),
 +                    StructurePool.Projection.RIGID
 +            )
 +    );
 +}
 +</code>
 +
 +Here, we're registering 2 pools (base & color) and then adding their respective children to them. The StructurePool constructor is as follows:
 +  * registry name of the pool, same as target pool at top of a jigsaw
 +  * @Draylar if you know what this one does
 +  * a list of pool elements
 +  * the projection type of the pool
 +
 +For the list of elements, we add Pairs((com.mojang.datafixers.util)) of pool elements and integers. The string passed into the element is the location of the structure in the data directory, and the int is the weight of the element within the entire target pool. Using 1 for each element ensures each one will be picked evenly.
 +
 +The projection is how the pool is placed in the world. Rigid means it will be placed directly as is, and terrain matching means it will be bent to sit on top of the terrain. The latter may be good for a wheat field structure that moves with the terrain shape, whereas the first would be better for houses with solid floors.
 +
 +
 +==== Jigsaws and pieces ====
  
 To understand what happens here, we'll have to dive into jigsaws (https://minecraft.gamepedia.com/Jigsaw_Block) and structure blocks (https://minecraft.gamepedia.com/Structure_Block). To understand what happens here, we'll have to dive into jigsaws (https://minecraft.gamepedia.com/Jigsaw_Block) and structure blocks (https://minecraft.gamepedia.com/Structure_Block).
Line 114: Line 187:
  
 The jigsaw menu consists of 3 fields: The jigsaw menu consists of 3 fields:
- * target pool + 
- * attachment type +  * target pool 
- * turns into+  * attachment type 
 +  * turns into
  
 {{ https://i.imgur.com/owaJ0k2.png?nolink&600 |Blank Jigsaw}} {{ https://i.imgur.com/owaJ0k2.png?nolink&600 |Blank Jigsaw}}
Line 135: Line 209:
  
 This jigsaw will ask for any other jigsaw that: This jigsaw will ask for any other jigsaw that:
- * is in the //tutorial:color_pool// target pool +  * is in the //tutorial:color_pool// target pool 
- * has an attachment type of //tutorial:square_edge//+  * has an attachment type of //tutorial:square_edge//
 It then turns into white concrete to match the rest of the platform. It then turns into white concrete to match the rest of the platform.
  
Line 156: Line 230:
  
 The setup is complete! We now have 6 total squares. Let's briefly recap the goal: The setup is complete! We now have 6 total squares. Let's briefly recap the goal:
- * have a white or black square selected as the center for our structure +  * have a white or black square selected as the center for our structure 
- * have a pool of the 4 other colors +  * have a pool of the 4 other colors 
- * branch off from the center square with our 4 extra colors+  * branch off from the center square with our 4 extra colors
  
-Let's head back to our ''ExampleStructureStart'' class. First, we'll need 2 Identifiers to label our 2 pools (black&white, 4 colors):+Let's head back to our ''TutorialJigsaws'' class. We'll need 2 Identifiers to label our 2 pools (black&white, 4 colors):
 <code java [enable_line_numbers="false"]> <code java [enable_line_numbers="false"]>
 private static final Identifier BASE_POOL = new Identifier("tutorial:base_pool"); private static final Identifier BASE_POOL = new Identifier("tutorial:base_pool");
Line 168: Line 242:
 Remember: every jigsaw ends up searching through the color pool, but we still have a base pool! This is to keep our black & white squares out of the outside generated squares. It's also going to be our origin pool, where we randomly select 1 structure from to begin our generation. Remember: every jigsaw ends up searching through the color pool, but we still have a base pool! This is to keep our black & white squares out of the outside generated squares. It's also going to be our origin pool, where we randomly select 1 structure from to begin our generation.
  
- In a static block at the bottom of our class, we're going to register our structure pools using ''StructurePoolBasedGenerator.REGISTRY'':+===== Registering Everything ===== 
 +We'll need to register our structure as a structure feature, and also register our piece.
 <code java [enable_line_numbers="true"]> <code java [enable_line_numbers="true"]>
-static +public static final StructureFeature<StructurePoolFeatureConfig> FEATURE = StructureFeature.register
-        StructurePoolBasedGenerator.REGISTRY.add( +        "tutorial:example_feature", 
-                new StructurePool( +        new ExampleFeature(StructurePoolFeatureConfig.CODEC), 
-                        BASE_POOL, +        GenerationStep.Feature.SURFACE_STRUCTURES
-                        new Identifier("empty"), +
-                        ImmutableList.of( +
-                                Pair.of(new SinglePoolElement("tutorial:black_square"), 1), +
-                                Pair.of(new SinglePoolElement("tutorial:white_square"), 1) +
-                        ), +
-                        StructurePool.Projection.RIGID +
-                ) +
-        ); +
- +
-        StructurePoolBasedGenerator.REGISTRY.add( +
-                new StructurePool( +
-                        COLOR_POOL, +
-                        new Identifier("empty"), +
-                        ImmutableList.of( +
-                                Pair.of(new SinglePoolElement("tutorial:lime_square"), 1), +
-                                Pair.of(new SinglePoolElement("tutorial:magenta_square"), 1), +
-                                Pair.of(new SinglePoolElement("tutorial:orange_square"), 1), +
-                                Pair.of(new SinglePoolElement("tutorial:light_blue_square"), 1) +
-                        ), +
-                        StructurePool.Projection.RIGID +
-                ) +
-        ); +
-    } +
-</code> +
- +
-Here, we're registering 2 pools (base & color) and then adding their respective children to them. The StructurePool constructor is as follows: +
- * registry name of the pool, same as target pool at top of a jigsaw +
- * @Draylar if you know what this one does +
- * a list of pool elements +
- * the projection type of the pool +
- +
-For the list of elements, we add Pairs((com.mojang.datafixers.util)) of pool elements and integers. The string passed into the element is the location of the structure in the data directory, and the int is the weight of the element within the entire target pool. Using 1 for each element ensures each one will be picked evenly. +
- +
-The projection is how the pool is placed in the world. Rigid means it will be placed directly as is, and terrain matching means it will be bent to sit on top of the terrain. The latter may be good for a wheat field structure that moves with the terrain shape, whereas the first would be better for houses with solid floors. +
-  +
-Now all we have to do is add our starting piece in our ''initialize'' method: +
-<code java [enable_line_numbers="false"]> +
-@Override +
-public void initialize(ChunkGenerator<?> chunkGenerator, StructureManager structureManager, int x, int z, Biome biome) { +
-    StructurePoolBasedGenerator.addPieces(BASE_POOL, 7, ExamplePiece::new, chunkGenerator, structureManager, new BlockPos(x * 16, 150, z * 16), children, random); +
-    setBoundingBoxFromChildren(); +
-+
-</code> +
-The Identifier is the starting pool to select from, the int is the size of the entire structure (with 7 being "7 squares out"), and the 3rd argument is a factory for the piece we'll register in a second. +
- +
-==== Creating a Piece ==== +
-This portion is very simple. A piece represents one section or element in your full structure. You'll need to create a basic piece class, and we'll register it later: +
-<code java [enable_line_numbers="false"]> +
-public class ExamplePiece extends PoolStructurePiece { +
- +
-    ExamplePiece(StructureManager structureManager_1, StructurePoolElement structurePoolElement_1, BlockPos blockPos_1, int int_1, BlockRotation blockRotation_1, MutableIntBoundingBox mutableIntBoundingBox_1+
-        super(ExampleMod.EXAMPLE_PIECEstructureManager_1, structurePoolElement_1, blockPos_1, int_1, blockRotation_1, mutableIntBoundingBox_1); +
-    } +
- +
-    public ExamplePiece(StructureManager manager, CompoundTag tag) { +
-        super(manager, tag, ExampleMod.EXAMPLE_PIECE); +
-    } +
-+
-</code> +
- +
-Where ''ExampleMod.EXAMPLE_PIECE'' is a reference to our registered piece. +
- +
-==== Registering Everything ==== +
-We'll need to register our structure as both a feature //and// a structure feature, and also register our piece. Registering your structure as a StructureFeature is optional, and is used for saving it to the chunk. If the world is stopped half-way through your structure loading, having this registered will allow it to continue after the world is re-opened. If it is not registered to a structure feature and this happens, the structure will stop half-way through (which would mostly only occur in larger, multiple chunk wide structures). +
-<code java [enable_line_numbers="true"]> +
-public static final StructureFeature<DefaultFeatureConfig> EXAMPLE_FEATURE = Registry.register( +
- Registry.FEATURE, +
- new Identifier("tutorial", "example_feature"), +
- new ExampleFeature(DefaultFeatureConfig::deserialize)+
 ); );
  
-public static final StructureFeature<DefaultFeatureConfig> EXAMPLE_STRUCTURE_FEATURE = Registry.register( +public static final StructurePieceType EXAMPLE_PIECE = StructurePieceType.register( 
- Registry.STRUCTURE_FEATURE, +        ExamplePiece::new
- new Identifier("tutorial", "example_structure_feature"), +        "tutorial:example_piece"
- EXAMPLE_FEATURE +
-); +
- +
-public static final StructurePieceType EXAMPLE_PIECE = Registry.register( +
- Registry.STRUCTURE_PIECE+
- new Identifier("tutorial", "example_piece"), +
- ExamplePiece::new+
 ); );
 </code> </code>
  
-==== Spawning Our Structure ====+===== Spawning Our Structure =====
 Finally, we'll have to spawn our structure. A basic example which adds it to every biome is: Finally, we'll have to spawn our structure. A basic example which adds it to every biome is:
 <code java [enable_line_numbers="false"]> <code java [enable_line_numbers="false"]>
-Registry.BIOME.forEach(biome -> +public static final ConfiguredStructureFeature<StructurePoolFeatureConfig, ? extends StructureFeature<StructurePoolFeatureConfig>> FEATURE_CONFIGURED 
- biome.addFeature(GenerationStep.Feature.RAW_GENERATIONEXAMPLE_FEATURE.configure(new DefaultFeatureConfig()).createDecoratedFeature(Decorator.NOPE.configure(DecoratorConfig.DEFAULT))); +        = FEATURE.configure(new StructurePoolFeatureConfig(BASE_POOL, 7)); 
- biome.addStructureFeature(EXAMPLE_FEATURE.configure(new DefaultFeatureConfig())); + 
-});+static 
 +    StructuresConfigAccessor.setDefaultStructures( 
 +            new ImmutableMap.Builder<StructureFeature<?>StructureConfig>() 
 +                    .putAll(StructuresConfig.DEFAULT_STRUCTURES) 
 +                    .put(TutorialJigsaws.FEATURE, new StructureConfig(32, 8, 10387312)) 
 +                    .build() 
 +    ); 
 +
 + 
 +@Override 
 +public void onInitialize() { 
 +    Registry.BIOME.forEach(biome -> { 
 +        biome.addStructureFeature(FEATURE_CONFIGURED); 
 +    }); 
 +}
 </code> </code>
- +===== Finished! =====
-=== Finished! ===+
 As you can see, we have a single white square in the center, with boxes going off the edges. Note that the radius in this screenshot was increased to 14 instead of the 7 used in the tutorial. As you can see, we have a single white square in the center, with boxes going off the edges. Note that the radius in this screenshot was increased to 14 instead of the 7 used in the tutorial.
  
 {{ https://i.imgur.com/qndZzZu.png?nolink&600 |Finalized}} {{ https://i.imgur.com/qndZzZu.png?nolink&600 |Finalized}}
-=== Jigsaw Tips ===+===== Jigsaw Tips =====
 Ideally, you do not want structure pieces to be bigger than 32x32x32, so breaking them into chunk-sized pieces is the best option.  Ideally, you do not want structure pieces to be bigger than 32x32x32, so breaking them into chunk-sized pieces is the best option. 
 You cannot generate other structure pieces of the same pool through jigsaws. So, if you have a piece in pool A and you try to generate another piece, you will have to have another pool.  You cannot generate other structure pieces of the same pool through jigsaws. So, if you have a piece in pool A and you try to generate another piece, you will have to have another pool. 
tutorial/jigsaw.1597429584.txt.gz · Last modified: 2020/08/14 18:26 by emmanuelmess