tutorial:structures
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
tutorial:structures [2019/02/16 20:58] – asie | tutorial:structures [2022/08/16 20:48] – FME2 mineblock11 | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Generating Structures ====== | + | DELETEME //**For versions 1.18 and beyond, the Fabric Structure API no longer exists and structures can be done entirely in datapacks. [[https:// |
- | ===== Features ===== | + | ====== Adding Structure |
+ | We’re going to look at registering and placing structures in your world. | ||
- | All code used in this tutorial | + | To view examples of vanilla structures |
- | ==== Introduction ==== | + | 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. | ||
- | We’re going to look at registering and placing structures | + | Note that this tutorial depends on [[https:// |
+ | If the API doesn' | ||
- | To view examples of 1.14 vanilla structures | + | ===== Creating a Feature ===== |
+ | To create a basic feature, we recommend creating a class that extends '' | ||
+ | Various | ||
- | You are going to need a Feature and Generator for the most basic structure. The feature | + | You will have to override '' |
+ | For '' | ||
- | ==== Creating a Feature ==== | + | <code java> |
+ | public class MyFeature extends StructureFeature< | ||
+ | public MyFeature(Codec< | ||
+ | super(codec); | ||
+ | } | ||
- | To create a basic feature, we recommend creating a class that extends AbstractTempleFeature< | + | @Override |
+ | public StructureFeature.StructureStartFactory< | ||
+ | return Start::new; | ||
+ | } | ||
- | | + | |
- | * getName: name of your structure | + | |
- | * method_14021 [getRadius]: | + | |
- | * method_13774 [getSeed]: a seed to use for generation, put 0 for testing | + | super(feature, chunkX, chunkZ, box, references, seed); |
+ | } | ||
- | You can pass DefaultFeatureConfig:: | + | // Called when the world attempts to spawn in a new structure, and is the gap between |
+ | public void init(DynamicRegistryManager registryManager, | ||
+ | int chunkZ, Biome biome, DefaultFeatureConfig config) { | ||
+ | int x = chunkX * 16; | ||
+ | int z = chunkZ * 16; | ||
+ | int y = chunkGenerator.getHeight(x, | ||
+ | BlockPos pos = new BlockPos(x, y, z); | ||
+ | BlockRotation rotation = BlockRotation.random(this.random); | ||
+ | MyGenerator.addPieces(manager, | ||
+ | this.setBoundingBoxFromChildren(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
- | For getStructureStartFactory, most vanilla structures make a class that extends StructureStart inside their Feature class: | + | ===== Creating a Generator ===== |
+ | As you have probably noticed, we need to create | ||
- | public static class MyStructureStart extends StructureStart | + | This is where structure |
- | { | + | |
- | public MyStructureStart (StructureFeature<?> | + | |
- | { | + | |
- | super(structureFeature_1, | + | |
- | } | + | |
- | @Override | + | |
- | public void initialize(ChunkGenerator<?> | + | |
- | { | + | |
- | DefaultFeatureConfig defaultFeatureConfig = chunkGenerator.getStructureConfig(biome, | + | |
- | int x = chunkX * 16; | + | |
- | int z = chunkZ * 16; | + | |
- | BlockPos startingPos = new BlockPos(x, 0, z); | + | |
- | Rotation rotation = Rotation.values()[this.random.nextInt(Rotation.values().length)]; | + | |
- | MyGenerator.addParts(structureManager, | + | |
- | this.setBoundingBoxFromChildren(); | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | 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 is where structure files and generating straight from a generate method part ways. There are two ways to go about this: | + | * If you want, you can simply override '' |
+ | * Use structure files. | ||
- | * 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. | + | In this tutorial, we' |
- | * Use structure | + | It doesn' |
+ | | ||
+ | * Some sort of setup method - '' | ||
- | ==== Creating a Generator ==== | + | <code java> |
+ | public class MyGenerator { | ||
+ | private static final Identifier IGLOO_TOP | ||
- | 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' | + | public static void addPieces(StructureManager manager, BlockPos pos, BlockRotation rotation, List< |
+ | pieces.add(new MyPiece(manager, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
- | * An Identifier that points to your structure file; use " | + | In your '' |
- | * Some sort of setup method - addParts is a good name: | + | |
- | public static void addParts(StructureManager structureManager_1, | + | We're now going to create the piece we just referenced; make a class called '' |
- | List< | + | |
- | + | ||
- | } | + | |
- | + | ||
- | In your addParts method, you can choose which structure pieces are added to your generation process. You can add a piece like this: | + | |
- | list_1.add(new MyGenerator.Piece(structureManager_1, identifier, blockPos, rotation_1)); | + | Override required methods, and add a constructor that takes in a '' |
- | + | **toNbt isn't required but is available if you need it**. | |
- | where the identifier | + | We're also implementing '' |
+ | We also have 2 constructors: | ||
+ | A basic template would be: | ||
- | We're now going to create the Piece we just referenced; make a class called Piece that extends SimpleStructurePiece | + | <code java> |
+ | public static | ||
+ | private final BlockRotation rotation; | ||
+ | private final Identifier template; | ||
- | Override required methods, and add a constructor that takes in a StructureManager, | + | public MyPiece(StructureManager |
+ | super(ExampleMod.MY_PIECE, compoundTag); | ||
+ | this.template = new Identifier(compoundTag.getString(" | ||
+ | this.rotation = BlockRotation.valueOf(compoundTag.getString(" | ||
+ | this.initializeStructureData(structureManager); | ||
+ | } | ||
- | public | + | public |
- | private Rotation rotation; | + | super(ExampleMod.MY_PIECE, 0); |
- | private Identifier template; | + | this.pos = pos; |
- | + | this.rotation = rotation; | |
- | public Piece(StructureManager | + | this.template = template; |
- | super(MyModClass.myStructurePieceType, 0); | + | |
- | + | ||
- | | + | |
- | this.rotation = rotation_1; | + | |
- | this.template = identifier_1; | + | |
- | + | ||
- | this.setStructureData(structureManager_1); | + | |
- | } | + | |
- | + | ||
- | public Piece(StructureManager structureManager_1, | + | |
- | super(MyModClass.myStructurePieceType, | + | |
- | this.identifier = new Identifier(compoundTag_1.getString(" | + | |
- | this.rotation = Rotation.valueOf(compoundTag_1.getString(" | + | |
- | this.setStructureData(structureManager_1); | + | |
- | } | + | |
- | + | ||
- | @Override | + | |
- | protected void toNbt(CompoundTag compoundTag_1) { | + | |
- | super.toNbt(compoundTag_1); | + | |
- | compoundTag_1.putString(" | + | |
- | compoundTag_1.putString(" | + | |
- | } | + | |
- | + | ||
- | 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, | + | |
- | } | + | |
- | + | ||
- | @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, | + | |
- | { | + | |
- | + | ||
- | } | + | |
- | } | + | |
- | + | ||
- | 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 | + | 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(" | ||
+ | tag.putString(" | ||
+ | } | ||
@Override | @Override | ||
- | public boolean generate(IWorld iWorld_1, Random random_1, MutableIntBoundingBox mutableIntBoundingBox_1, ChunkPos chunkPos_1) | + | protected void handleMetadata(String metadata, BlockPos pos, ServerWorldAccess serverWorldAccess, Random random, |
- | { | + | |
- | int yHeight = iWorld_1.getTop(Heightmap.Type.WORLD_SURFACE_WG, this.pos.getX() + 8, this.pos.getZ() + 8); | + | |
- | | + | } |
- | | + | </ |
- | } | + | |
| | ||
- | In this case, we simply get the y position of the highest block in the middle of our chunk and generate the structure | + | '' |
+ | 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' | ||
+ | < | ||
- | The last step is to register our features. | + | We set the '' |
- | * StructurePieceType | + | ===== Registering Structures ===== |
- | * StructureFeature< | + | 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 | + | * structure |
+ | * piece | ||
+ | * configured | ||
- | Registering piece type: | + | <code java> |
- | public static final StructurePieceType | + | public class ExampleMod implements ModInitializer { |
- | | + | public static final StructurePieceType |
- | Registering feature: | + | |
- | public | + | |
- | | + | |
- | Registering structure: | + | |
- | public | + | |
- | + | ||
- | To put your feature in the features list, you can use: | + | |
- | Feature.STRUCTURES.put(" | + | |
- | + | ||
- | 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 working. You probably don't want your structure floating in the water, so we'll also filter that out. Add it to every biome by iterating over the biome list and adding it as a feature and generation step: | + | |
- | | + | |
- | { | + | public void onInitialize() { |
- | if(biome.getCategory() != Biome.Category.OCEAN && biome.getCategory() != Biome.Category.RIVER) | + | |
- | { | + | |
- | biome.addStructureFeature(myFeature, | + | .step(GenerationStep.Feature.SURFACE_STRUCTURES) |
- | biome.addFeature(GenerationStep.Feature.SURFACE_STRUCTURES, Biome.configureFeature(myFeature, new DefaultFeatureConfig(), Decorator.CHANCE_PASSTHROUGH, | + | |
- | } | + | .adjustsSurface() |
- | } | + | |
- | ChanceDecoratorConfig' | + | RegistryKey< |
+ | new Identifier(" | ||
+ | BuiltinRegistries.add(BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
- | You need to add your structure as a feature | + | ===== Adding |
+ | In this tutorial, we add our structure to all biomes. | ||
+ | |||
+ | <code java> | ||
+ | public class ExampleMod implements ModInitializer { | ||
+ | [...] | ||
+ | |||
+ | @Override | ||
+ | public void onInitialize() { | ||
+ | [...] | ||
- | Load into your world, and if all went well, you should be met with a //lot// of Igloos. | + | 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. | ||
+ | < | ||
+ | /locate tutorial: | ||
+ | </ | ||
+ | {{tutorial: | ||
tutorial/structures.txt · Last modified: 2022/11/05 12:06 by jab125