User Tools

Site Tools


tutorial:structures

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:structures [2019/05/26 16:34] – Syntax highlighting, more consistent formatting jamieswhiteshirttutorial:structures [2022/11/05 12:06] (current) jab125
Line 1: Line 1:
-====== Generating Structures ====== +:!: //**For versions 1.18 and beyond, the Fabric Structure API no longer exists and structures can be done entirely in datapacks. [[https://misode.github.io/guides/adding-custom-structures/|Please read this gist for more information.]] ** //
- +
-===== Features ===== +
- +
-All code used in this tutorial is available here: [[https://github.com/Draylar/fabric-structure-example-repo|fabric-structure-example-repo]] +
- +
-==== Introduction ====+
  
 +====== Adding Structure Features [1.16.3] ======
 We’re going to look at registering and placing structures in your world. We’re going to look at registering and placing structures in your world.
  
-To view examples of 1.14 vanilla structures in action, IglooGenerator & IglooFeature are a good place to start.+To view examples of vanilla structures in action, ''IglooFeature'' and ''IglooGenerator'' are a good place to start. However, the growing standard for making new structures is using Jigsaw Structures which can be easier to work with and lets you deal with less structure code. [[https://fabricmc.net/wiki/tutorial:jigsaw|Read more on Jigsaw Structures here and how to make them.]]
  
-You are going to need a Feature and Generator for the most basic structure. The feature handles the process of registering the structure and loading it in when the world is generating-- it answers questions such as ‘should I spawn here?’ and ‘what is my name?’ The generator handles the placement of blocks or loading in a structure file if you choose to do so.+You are going to need a feature and generator for the most basic structure. 
 +The feature handles the process of registering the structure and loading it in when the world is generating
 +The generator handles the placement of blocks or loading in a structure file if you choose to do so.
  
-==== Creating a Feature ====+Note that this tutorial depends on [[https://github.com/FabricMC/fabric/pull/1097|Biome Modification API in Fabric API]] which is marked as experimental. 
 +If the API doesn't work, consider using [[?rev=1599808070|the mixin version]].
  
-To create a basic feature, we recommend creating a class that extends AbstractTempleFeature<DefaultFeatureConfig>. Various vanilla structures, such as ShipwrecksIgloos, and Temples, use AbstractTempleFeature as a base. You will have to override the following methods:+===== Creating a Feature ===== 
 +To create a basic feature, we recommend creating a class that extends ''StructureFeature<DefaultFeatureConfig>''. 
 +Various vanilla structures, such as shipwrecksigloos, and temples, use ''StructureFeature<DefaultFeatureConfig>'' as a base.
  
-  * shouldStartAt: return true for testing purposes+You will have to override ''getStructureStartFactory'' method
-  * getName: name of your structure +For ''getStructureStartFactory''most vanilla structures make class that extends ''StructureStart'' inside their feature class.
-  * method_14021 [getRadius]: radius of your structureused for placement +
-  * method_13774 [getSeed]: seed to use for generation, put 0 for testing+
  
-You can pass DefaultFeatureConfig::deserialize into your constructor for testing.+<code java> 
 +public class MyFeature extends StructureFeature<DefaultFeatureConfig> { 
 +  public MyFeature(Codec<DefaultFeatureConfig> codec) { 
 +    super(codec); 
 +  }
  
-For getStructureStartFactory, most vanilla structures make a class that extends StructureStart inside their Feature class:+  @Override 
 +  public StructureFeature.StructureStartFactory<DefaultFeatureConfig> getStructureStartFactory() { 
 +    return Start::new; 
 +  }
  
-<code java [enable_line_numbers="true"]> +  public static class Start extends StructureStart<DefaultFeatureConfig> 
-public static class MyStructureStart extends StructureStart { +    public Start(StructureFeature<DefaultFeatureConfigfeature, int chunkX, int chunkZBlockBox box, int references, 
-    public MyStructureStart (StructureFeature<?structureFeature_1, int int_1, int int_2Biome biome_1, MutableIntBoundingBox mutableIntBoundingBox_1, int int_3, long long_1) { +        long seed) { 
-        super(structureFeature_1, int_1int_2biome_1mutableIntBoundingBox_1int_3long_1);+      super(featurechunkXchunkZboxreferencesseed);
     }     }
-    @Override + 
-    public void initialize(ChunkGenerator<?> chunkGenerator, StructureManager structureManager, int chunkX, int chunkZ, Biome biome) { +    // Called when the world attempts to spawn in a new structure, and is the gap between your feature and generator. 
-        DefaultFeatureConfig defaultFeatureConfig = chunkGenerator.getStructureConfig(biome, MyMainclass.myFeature); +    public void init(DynamicRegistryManager registryManager, ChunkGenerator chunkGenerator, StructureManager manager, int chunkX, 
-        int x = chunkX * 16; +        int chunkZ, Biome biome, DefaultFeatureConfig config) { 
-        int z = chunkZ * 16; +      int x = chunkX * 16; 
-        BlockPos startingPos = new BlockPos(x, 0, z); +      int z = chunkZ * 16; 
-        Rotation rotation = Rotation.values()[this.random.nextInt(Rotation.values().length)]+      int y = chunkGenerator.getHeight(x, z, Heightmap.Type.WORLD_SURFACE_WG); 
-        MyGenerator.addParts(structureManagerstartingPos, rotation, this.children, this.random, defaultFeatureConfig); +      BlockPos pos = new BlockPos(x, y, z); 
-        this.setBoundingBoxFromChildren();+      BlockRotation rotation = BlockRotation.random(this.random); 
 +      MyGenerator.addPieces(managerpos, rotation, this.children); 
 +      this.setBoundingBoxFromChildren();
     }     }
 +  }
 } }
 </code> </code>
-     
-This is called when the world attempts to spawn in a new structure, and is the gap between your Feature and Generator. The reference to the variable in your main class doesn't exist yet, but we'll create it at the end. You can also just set the config equal to a new DefaultFeatureConfig. You can return this in getStructureStartFactory with return MyStructureStart::new. 
  
-This is where structure files and generating straight from generate method part ways. There are two ways to go about this:+===== Creating Generator ===== 
 +As you have probably noticed, we need to create a generator.
  
-* If you want, you can simply override generate in your Feature class and use setBlockState to place blocks directly in the world. This is a valid option and was popular pre-1.13. +This is where structure files and generating straight from ''generate'' method part waysThere are two ways to go about this:
-* Use structure files and a GeneratorThese are rather powerful at this point and are highly recommended.+
  
-==== Creating Generator ====+  * If you want, you can simply override ''generate'' in your piece class and use ''addBlock'' to place blocks directly in the world. This is valid option and was popular pre-1.13. 
 +  * Use structure files. These are rather powerful at this point and are highly recommended.
  
-As you have probably noticed, we need to create a generator. We'll name it MyGenerator, and it's referenced in the initialize method of your StructureStart class. It doesn't need to override anything, but does require the following:+In this tutorial, we'll use a structure file. 
 +It doesn't need to override anything, but does require the following: 
 +  * An identifier that points to your structure file; use ''"igloo/top"'' if you need an example. 
 +  * Some sort of setup method - ''addPieces'' is a good name.
  
-  * An Identifier that points to your structure file; use "igloo/top" if you need an example. +<code java> 
-  * Some sort of setup method - addParts is a good name:+public class MyGenerator { 
 +  private static final Identifier IGLOO_TOP = new Identifier("igloo/top");
  
-<code java> +  public static void addPieces(StructureManager manager, BlockPos posBlockRotation rotation, List<StructurePiece> pieces) { 
-public static void addParts(StructureManager structureManager_1, BlockPos blockPos_1Rotation rotation_1 +    pieces.add(new MyPiece(manager, posIGLOO_TOProtation)); 
-    List<StructurePiece> list_1Random random_1DefaultFeatureConfig featureConfig+  }
-    +
 } }
 </code> </code>
-     + 
-In your addParts method, you can choose which structure pieces are added to your generation process. You can add a piece like this:+In your ''addPieces'' method, you can choose which structure pieces are added to your generation process. 
 + 
 +We're now going to create the piece we just referenced; make a class called ''MyPiece'' that extends ''SimpleStructurePiece'' //within your generator class//. 
 + 
 +Override required methods, and add a constructor that takes in a ''StructureManager'', ''BlockPos'', ''Identifier'' and ''Rotation''
 +**toNbt isn't required but is available if you need it**. 
 +We're also implementing ''initializeStructureData'', which is not an override. 
 +We also have 2 constructors: 1 for our own pieces, and one for registry. 
 +A basic template would be:
  
 <code java> <code java>
-list_1.add(new MyGenerator.Piece(structureManager_1, identifier, blockPos, rotation_1)); +public static class MyPiece extends SimpleStructurePiece { 
-</code> +  private final BlockRotation rotation; 
-   +  private final Identifier template;
-where the identifier is the path we created recently.+
  
-We're now going to create the Piece we just referencedmake a class called Piece that extends SimpleStructurePiece //within your generator class//.+  public MyPiece(StructureManager structureManager, CompoundTag compoundTag) { 
 +    super(ExampleMod.MY_PIECE, compoundTag); 
 +    this.template = new Identifier(compoundTag.getString("Template")); 
 +    this.rotation = BlockRotation.valueOf(compoundTag.getString("Rot")); 
 +    this.initializeStructureData(structureManager); 
 +  }
  
-Override required methods, and add a constructor that takes in a StructureManager, Identifier, BlockPos, and Rotation. **toNbt isn't required but is available if you need it**. We're also implementing our own setStructureData with different argumentsso it's not an overrideWe also have 2 constructors: 1 for our own piecesand one for registryA basic template would be:+  public MyPiece(StructureManager structureManager, BlockPos posIdentifier templateBlockRotation rotation) { 
 +    super(ExampleMod.MY_PIECE0); 
 +    this.pos = pos; 
 +    this.rotation = rotation; 
 +    this.template = template;
  
-<code java [enable_line_numbers="true"]> +    this.initializeStructureData(structureManager); 
-public static class Piece extends SimpleStructurePiece { +  
-    private Rotation rotation; + 
-    private Identifier template; +  private void initializeStructureData(StructureManager structureManager) { 
-     +    Structure structure = structureManager.getStructureOrBlank(this.template); 
-    public Piece(StructureManager structureManager_1, Identifier identifier_1, BlockPos blockPos_1, Rotation rotation_1) { +    StructurePlacementData placementData = (new StructurePlacementData()) 
-        super(MyModClass.myStructurePieceType, 0); +      .setRotation(this.rotation) 
-         +      .setMirror(BlockMirror.NONE) 
-        this.pos = blockPos_1; +      .addProcessor(BlockIgnoreStructureProcessor.IGNORE_STRUCTURE_BLOCKS); 
-        this.rotation = rotation_1; +    this.setStructureData(structure, this.pos, placementData); 
-        this.template = identifier_1; +  }
-         +
-        this.setStructureData(structureManager_1); +
-    +
-     +
-    public Piece(StructureManager structureManager_1, CompoundTag compoundTag_1) { +
-        super(MyModClass.myStructurePieceType, compoundTag_1); +
-        this.identifier = new Identifier(compoundTag_1.getString("Template")); +
-        this.rotation = Rotation.valueOf(compoundTag_1.getString("Rot")); +
-        this.setStructureData(structureManager_1); +
-    } +
-     +
-    @Override +
-    protected void toNbt(CompoundTag compoundTag_1) { +
-        super.toNbt(compoundTag_1); +
-        compoundTag_1.putString("Template", this.template.toString()); +
-        compoundTag_1.putString("Rot", this.rotation.name()); +
-    } +
-     +
-    public void setStructureData(StructureManager structureManager) { +
-        Structure structure_1 = structureManager.getStructureOrBlank(this.identifier); +
-        StructurePlacementData structurePlacementData_1 = (new StructurePlacementData()).setRotation(this.rotation).setMirrored(Mirror.NONE).setPosition(pos).addProcessor(BlockIgnoreStructureProcessor.IGNORE_STRUCTURE_BLOCKS); +
-        this.setStructureData(structure_1, this.pos, structurePlacementData_1); +
-    } +
-     +
-    @Override +
-    protected void handleMetadata(String string_1, BlockPos blockPos_1, IWorld iWorld_1, Random random_1, MutableIntBoundingBox mutableIntBoundingBox_1) { +
-         +
-    } +
-     +
-    @Override +
-    public boolean generate(IWorld iWorld_1, Random random_1, MutableIntBoundingBox mutableIntBoundingBox_1, ChunkPos chunkPos_1) { +
-       +
-    } +
-+
-</code> +
-     +
-handleMetadata is where you look at data blocks within your structure and do tasks based on what you find. In vanilla structures, data blocks are placed above chests so they can be filled with loot in this method.+
  
-We set the StructurePieceType to MyModClass.myStructurePiece type; this is the variable that holds your registered structure pieceWe'll handle that after we finish the generate functionwhich sets the position of your structure and generates it:+  protected void toNbt(CompoundTag tag) { 
 +    super.toNbt(tag); 
 +    tag.putString("Template", this.template.toString()); 
 +    tag.putString("Rot"this.rotation.name()); 
 +  }
  
-<code java [enable_line_numbers="true"]> +  @Override 
-@Override +  protected void handleMetadata(String metadataBlockPos posServerWorldAccess serverWorldAccessRandom random
-public boolean generate(IWorld iWorld_1Random random_1MutableIntBoundingBox mutableIntBoundingBox_1ChunkPos chunkPos_1) { +      BlockBox boundingBox{ 
-    int yHeight = iWorld_1.getTop(Heightmap.Type.WORLD_SURFACE_WGthis.pos.getX() + 8, this.pos.getZ() + 8); +  }
-    this.pos = this.pos.add(0, yHeight - 1, 0); +
-    return super.generate(iWorld_1, random_1, mutableIntBoundingBox_1, chunkPos_1);+
 } }
 </code> </code>
          
-In this case, we simply get the y position of the highest block in the middle of our chunk and generate the structure off that position+''handleMetadata'' is where you look at data blocks within your structure and can do tasks based on what you find. 
 +This can be good for dynamic stuff such as placing certain mobs based on what mod is on and so on.
  
-==== Registering Features ====+In vanilla structures, data blocks are placed above chests so they can be filled with loot in this method.  
 +HOWEVER, you do not need to use datablocks to place chests with loot. Instead, use this command to set a north facing chest with a loottable.  
 +Save this chest into your structure's nbt file and it will generate loot when opened for the first time. (Don't open the chest before saving to the nbt file!) 
 +<code>/setblock ~ ~ ~ minecraft:chest[facing=north]{LootTable:"modid:loottable"}</code>
  
-The last step is to register our features. We're going to need to register:+We set the ''StructurePieceType'' to ''ExampleMod.MY_PIECE''; this is the variable that holds your registered structure piece. 
  
-  * StructurePieceType +===== Registering Structures ===== 
-  * StructureFeature<DefaultFeatureConfig> +The last step is to register our structures. We're going to need to register:
-  * StructureFeature<?>+
  
-We're also going to need to add the structure to the STRUCTURES list and add it to each biome as a feature and generation step.+  * structure 
 +  * piece 
 +  * configured structure
  
-Registering piece type: 
 <code java> <code java>
-public static final StructurePieceType myStructurePieceType = Registry.register(Registry.STRUCTURE_PIECE, "my_piece", MyGenerator.Piece::new);+public class ExampleMod implements ModInitializer { 
 +  public static final StructurePieceType MY_PIECE MyGenerator.MyPiece::new; 
 +  private static final StructureFeature<DefaultFeatureConfig> MY_STRUCTURE = new MyFeature(DefaultFeatureConfig.CODEC); 
 +  private static final ConfiguredStructureFeature<?, ?> MY_CONFIGURED = MY_STRUCTURE.configure(DefaultFeatureConfig.DEFAULT); 
 + 
 +  @Override 
 +  public void onInitialize() { 
 +    Registry.register(Registry.STRUCTURE_PIECE, new Identifier("tutorial", "my_piece")MY_PIECE); 
 +    FabricStructureBuilder.create(new Identifier("tutorial", "my_structure"), MY_STRUCTURE) 
 +      .step(GenerationStep.Feature.SURFACE_STRUCTURES) 
 +      .defaultConfig(32, 8, 12345) 
 +      .adjustsSurface() 
 +      .register(); 
 + 
 +    RegistryKey<ConfiguredStructureFeature<?, ?>> myConfigured = RegistryKey.of(Registry.CONFIGURED_STRUCTURE_FEATURE_WORLDGEN, 
 +        new Identifier("tutorial", "my_structure")); 
 +    BuiltinRegistries.add(BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE, myConfigured.getValue(), MY_CONFIGURED); 
 +  } 
 +}
 </code> </code>
-   + 
-Registering feature:+===== Adding a configured feature to biomes ===== 
 +In this tutorial, we add our structure to all biomes. 
 <code java> <code java>
-public static final StructureFeature<DefaultFeatureConfig> myFeature = Registry.register(Registry.FEATURE, "my_feature", new MyFeature()); +public class ExampleMod implements ModInitializer { 
-</code> +  [...] 
-   +  
-Registering structure: +  @Override 
-<code java> +  public void onInitialize() { 
-public static final StructureFeature<?> myStructure = Registry.register(Registry.STRUCTURE_FEATURE, "my_structure", myFeature); +    [...]
-</code> +
-   +
-To put your feature in the features list, you can use: +
-<code java> +
-Feature.STRUCTURES.put("My Awesome Feature", myFeature); +
-</code> +
-   +
-For testing, it's a good idea to register your feature to every biome and set the spawn rate to 100% so you can be sure it's spawning and workingYou probably don't want your structure floating in the water, so we'll also filter that outAdd it to every biome by iterating over the biome list and adding it as a feature and generation step:+
  
-<code java [enable_line_numbers="true"]> +    BiomeModifications.addStructure(BiomeSelectors.all(), myConfigured); 
-for(Biome biome : Registry.BIOME) { +  }
-    if(biome.getCategory() != Biome.Category.OCEAN && biome.getCategory() != Biome.Category.RIVER) { +
-        biome.addStructureFeature(myFeaturenew DefaultFeatureConfig()); +
-        biome.addFeature(GenerationStep.Feature.SURFACE_STRUCTURES, Biome.configureFeature(myFeature, new DefaultFeatureConfig(), Decorator.CHANCE_PASSTHROUGH, new ChanceDecoratorConfig(0))); +
-    }+
 } }
 </code> </code>
  
-ChanceDecoratorConfig's argument is basically how many chunks it will skip over before generating0 is every chunk, 1 is every other, and 100 is every 100.+===== Result ===== 
 +You should be met with igloos. 
 +You can use below command to find your structure in the world. 
 + 
 +<code> 
 +/locate tutorial:my_structure 
 +</code>
  
-You need to add your structure as a feature so your biome knows it exists, and then as a generation step so it's actually generated.+{{tutorial:structures.png?800}}
  
-Load into your world, and if all went well, you should be met with a //lot// of Igloos. 
tutorial/structures.1558888446.txt.gz · Last modified: 2019/05/26 16:34 by jamieswhiteshirt