User Tools

Site Tools


tutorial:structures

Differences

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

Link to this comparison view

Next revision
Previous revision
features [2019/02/16 19:15] – added start of structure tutorial draylartutorial:structures [2022/11/05 12:06] (current) jab125
Line 1: Line 1:
 +:!: //**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.]] ** //
 +
 +====== 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.
  
-To create a basic feature, we recommend creating a class that extends AbstractTempleFeature<DefaultFeatureConfig>Various vanilla structures, such as ShipwrecksIgloos, and Temples, use AbstracTempleFeature as a baseYou will have to override the following methods:+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 workconsider using [[?rev=1599808070|the mixin version]].
  
-  * shouldStartAt: return true for testing purposes. +===== Creating a Feature ===== 
-  * getName: name of your structure +To create a basic featurewe recommend creating class that extends ''StructureFeature<DefaultFeatureConfig>''
-  * method_14021 [getRadius]: radius of your structureused for placement +Various vanilla structuressuch as shipwrecks, igloos, and temples, use ''StructureFeature<DefaultFeatureConfig>'' as a base.
-  * method_13774 [getSeed]: seed to use for generationput 0 for testing+
  
-You can pass DefaultFeatureConfig::deserialize into your constructor for testing.+You will have to override ''getStructureStartFactory'' method. 
 +For ''getStructureStartFactory'', most vanilla structures make a class that extends ''StructureStart'' inside their feature class.
  
-For getStructureStartFactory, most vanilla structures make a class that extends StructureStart inside their Feature class:+<code java> 
 +public class MyFeature extends StructureFeature<DefaultFeatureConfig>
 +  public MyFeature(Codec<DefaultFeatureConfig> codec) { 
 +    super(codec); 
 +  }
  
-  public static class MyStructureStart extends StructureStart +  @Override 
-    { +  public StructureFeature.StructureStartFactory<DefaultFeatureConfiggetStructureStartFactory() { 
-        public MyStructureStart (StructureFeature<?structureFeature_1, int int_1, int int_2, Biome biome_1, MutableIntBoundingBox mutableIntBoundingBox_1, int int_3, long long_1) +    return Start::new
-        +  
-            super(structureFeature_1, int_1, int_2, biome_1, mutableIntBoundingBox_1, int_3, long_1)+ 
-        +  public static class Start extends StructureStart<DefaultFeatureConfig>
-        @Override +    public Start(StructureFeature<DefaultFeatureConfigfeature, int chunkX, int chunkZ, BlockBox box, int references
-        public void initialize(ChunkGenerator<?chunkGenerator, StructureManager structureManager, int chunkX, int chunkZ, Biome biome) +        long seed{ 
-        { +      super(featurechunkXchunkZboxreferencesseed);
-            DefaultFeatureConfig defaultFeatureConfig = chunkGenerator.getStructureConfig(biomeBattleTowers.battleTowerFeature); +
-            int x = chunkX * 16; +
-            int z = chunkZ * 16; +
-            BlockPos startingPos = new BlockPos(x0, z); +
-            Rotation rotation = Rotation.values()[this.random.nextInt(Rotation.values().length)]; +
-            MyGenerator.addParts(structureManagerstartingPosrotationthis.childrenthis.randomdefaultFeatureConfig); +
-            this.setBoundingBoxFromChildren(); +
-        }+
     }     }
-     
-This is called when the world attempts to spawn in a new structure, and is the gap between your Feature and Generator. You can return this in getStructureStartFactory with return MyStructureStart::new. 
  
-This is where structure files and generating straight from generate method part ways. If you wantyou 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.13but structure files are simply more powerful at this point and are highly recommended.+    // Called when the world attempts to spawn in new structure, and is the gap between your feature and generator. 
 +    public void init(DynamicRegistryManager registryManagerChunkGenerator chunkGenerator, StructureManager manager, int chunkX, 
 +        int chunkZ, Biome biome, DefaultFeatureConfig config) { 
 +      int x = chunkX * 16; 
 +      int z = chunkZ * 16; 
 +      int y = chunkGenerator.getHeight(x, z, Heightmap.Type.WORLD_SURFACE_WG); 
 +      BlockPos pos = new BlockPos(x, y, z); 
 +      BlockRotation rotation = BlockRotation.random(this.random); 
 +      MyGenerator.addPieces(manager, pos, rotation, this.children); 
 +      this.setBoundingBoxFromChildren(); 
 +    } 
 +  } 
 +
 +</code>
  
-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:+===== Creating a Generator ===== 
 +As you have probably noticed, we need to create a generator.
  
-  * An Identifier that points to your structure file; use "igloo/top" if you need an example. +This is where structure files and generating straight from ''generate'' method part ways. There are two ways to go about this:
-  * Some sort of setup method-- addParts is a good name:+
  
-  public static void addParts(StructureManager structureManager_1, BlockPos blockPos_1, Rotation rotation_1,  +  * If you want, you can simply override ''generate'' in your piece class and use ''addBlock'' to place blocks directly in the worldThis is valid option and was popular pre-1.13. 
-  List<StructurePiece> list_1, Random random_1, DefaultFeatureConfig featureConfig) +  * Use structure files. These are rather powerful at this point and are highly recommended.
-     +
-    } +
-     +
-In your addParts method, you can choose which structure pieces are added to your generation processYou can add piece like this:+
  
-  list_1.add(new MyGenerator.Piece(structureManager_1, identifier, blockPos, rotation_1));  +In this tutorial, we'll use a structure file. 
-   +It doesn't need to override anythingbut does require the following: 
-where the identifier is the path we created recently.+  * An identifier that points to your structure fileuse ''"igloo/top"'' if you need an example. 
 +  * Some sort of setup method - ''addPieces'' is a good name.
  
-We're now going to create the Piece we just referenced; make a class called Piece that extends SimpleStructurePiece //within your generator class//.+<code java> 
 +public class MyGenerator { 
 +  private static final Identifier IGLOO_TOP = new Identifier("igloo/top");
  
-Override required methodsA basic template would be:+  public static void addPieces(StructureManager manager, BlockPos pos, BlockRotation rotation, List<StructurePiece> pieces) { 
 +    pieces.add(new MyPiece(manager, pos, IGLOO_TOP, rotation)); 
 +  } 
 +
 +</code>
  
-  public static class Piece extends SimpleStructurePiece { +In your ''addPieces'' methodyou can choose which structure pieces are added to your generation process.
-        private final Rotation rotation; +
-        private final Identifier template; +
-        public Piece(StructureManager structureManager_1Identifier identifier_1, BlockPos blockPos_1, Rotation rotation_1) { +
-            super(StructurePieceType.VILLAGE, 0); +
-            this.pos = blockPos_1; +
-            this.rotation = rotation_1; +
-            this.template = identifier_1; +
-            this.method_14837(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()); +
-        } +
-        private void method_14837(StructureManager structureManager_1) { +
-            Structure structure_1 = structureManager_1.getStructureOrBlank(this.template); +
-            StructurePlacementData structurePlacementData_1 = (new StructurePlacementData()).setRotation(this.rotation).setMirrored(Mirror.NONE).setPosition(MyGenerator.startingPos).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) {+
  
-        } +We're now going to create the piece we just referenced; make a class called ''MyPiece'' that extends ''SimpleStructurePiece'' //within your generator class//. 
-        @Override + 
-        public boolean generate(IWorld iWorld_1Random random_1MutableIntBoundingBox mutableIntBoundingBox_1ChunkPos chunkPos_1+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> 
 +public static class MyPiece extends SimpleStructurePiece { 
 +  private final BlockRotation rotation; 
 +  private final Identifier template; 
 + 
 +  public MyPiece(StructureManager structureManagerCompoundTag compoundTag) { 
 +    super(ExampleMod.MY_PIECEcompoundTag); 
 +    this.template = new Identifier(compoundTag.getString("Template")); 
 +    this.rotation = BlockRotation.valueOf(compoundTag.getString("Rot")); 
 +    this.initializeStructureData(structureManager); 
 +  } 
 + 
 +  public MyPiece(StructureManager structureManagerBlockPos pos, Identifier template, BlockRotation rotation{ 
 +    super(ExampleMod.MY_PIECE, 0); 
 +    this.pos = pos; 
 +    this.rotation = rotation; 
 +    this.template = template; 
 + 
 +    this.initializeStructureData(structureManager); 
 +  } 
 + 
 +  private void initializeStructureData(StructureManager structureManager) 
 +    Structure structure = structureManager.getStructureOrBlank(this.template); 
 +    StructurePlacementData placementData = (new StructurePlacementData()) 
 +      .setRotation(this.rotation) 
 +      .setMirror(BlockMirror.NONE) 
 +      .addProcessor(BlockIgnoreStructureProcessor.IGNORE_STRUCTURE_BLOCKS); 
 +    this.setStructureData(structure, this.pos, placementData); 
 +  } 
 + 
 +  protected void toNbt(CompoundTag tag) { 
 +    super.toNbt(tag); 
 +    tag.putString("Template", this.template.toString()); 
 +    tag.putString("Rot", this.rotation.name()); 
 +  } 
 + 
 +  @Override 
 +  protected void handleMetadata(String metadata, BlockPos pos, ServerWorldAccess serverWorldAccess, Random random, 
 +      BlockBox boundingBox) { 
 +  } 
 +
 +</code>
          
 +''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.
 +
 +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>
 +
 +We set the ''StructurePieceType'' to ''ExampleMod.MY_PIECE''; this is the variable that holds your registered structure piece. 
 +
 +===== Registering Structures =====
 +The last step is to register our structures. We're going to need to register:
 +
 +  * structure
 +  * piece
 +  * configured structure
 +
 +<code java>
 +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>
 +
 +===== Adding a configured feature to biomes =====
 +In this tutorial, we add our structure to all biomes.
 +
 +<code java>
 +public class ExampleMod implements ModInitializer {
 +  [...]
 + 
 +  @Override
 +  public void onInitialize() {
 +    [...]
 +
 +    BiomeModifications.addStructure(BiomeSelectors.all(), myConfigured);
 +  }
 +}
 +</code>
 +
 +===== 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>
  
 +{{tutorial:structures.png?800}}
  
tutorial/structures.1550344526.txt.gz · Last modified: 2019/02/16 19:15 by draylar