Skip to content

Instantly share code, notes, and snippets.

@Deamon5550
Last active July 7, 2022 07:55
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Deamon5550/5649289f2c5dff5954eb to your computer and use it in GitHub Desktop.
Save Deamon5550/5649289f2c5dff5954eb to your computer and use it in GitHub Desktop.
Custom World Generation

What this document contains:

  • Overview of the World Generation Process
  • The process of creating a new World Generator
  • A list of not yet implemented features

The Generation Process

As you are probably aware each World is split into a number of Chunks (technically chunk columns) each one of which defines an area of the world which is 16x256x16 blocks (by default see below) and these chunks may be loaded in any order. It is therefore one of the primary requirements of any WorldGenerator that it must generate the world on a chunk-by-chunk basis without any requirement for a specific order.

The assumption as made above that each Chunk has a size of 16x256x16 however this is not always the case as other implementations could theoretically modify the structure and dimensions of chunks. At runtime the size of a chunk may be determined with Chunk.getBlockSize().

The generation process is thus split into two distinct phases. First there is the generation phase during which the more intensive aspects of generation are performed into a BlockBuffer. A buffer is used so that many of the World functions that happen when a block is changed such as updating neighbouring blocks and calculating lighting are not performed. Some examples of processes that occur during this phase is the generation of caves, ravines, and many structures such as villages and strongholds. Secondly, and this can happen immediately after or some time in the future, the chunk is populated. It is a requirement that before a chunk is populated every chunk immediately surrounding it will at least have passed the generation phase (so the base terrain will at least exist, if not the additional features), this is to allow objects placed by populators to overlap the chunk boundaries and extend into other surrounding chunks. In this phase the smaller and/or biome-based features such as flowers, trees, and ores. The ground cover (such as dirt and grass) is also placed in at this time.

The structure of the WorldGenerator is set up in oder to reflect this two phase approach. All operations performed by world generators are performed via populators, of which there are two types. GeneratorPopulators are for the generation phase and operate upon a BlockBuffer. Populators are the corresponding populaotr but act instead upon a Chunk object. Within the WorldGenerator there is also a special generator populator which is what creates the base terrain shape at the start of the generation phase. Biomes also have their own set of generator populators and populators which are applied during the appropriate phases depending on what biomes are within the chunk. Additionally biomes also define an ordered list of GroundCoverLayers these are the biome-dependant layers of blocks that are applied to the top of the generated world, things like the layer of grass and the 4ish layers of dirt under them.

As a current implementation limitation the generator will apply only the biome specific generator populators and populators for the biome defined at Chunk.getBlockMin().add(16, 0, 16). However this may be solved in the future.

The final order of operations for generation is this: The generation phase:

  1. Create the chunk buffer
  2. Refer to the BiomeGenerator in order to get what biomes exist for each block in the chunk's 2d plane
  3. Call the base GeneratorPopulator to create the base terrain shape
  4. for each column in the chunk run through the list of GroundCoverLayers in the column's biome and place them starting at the top most block
  5. Call each of the GeneratorPopulators registered to the biome at (x+16, 0, z+16)
  6. Call each of the GeneratorPopulators registered to the generator
  7. Build the chunk object from the chunk buffer

The population phase:

  1. Validate surrounding chunks
  2. Pass the chunk to each of the populators registered to the biome at (x+16, 0, z+16)
  3. Pass the chunk to each of the populators registered to the generator

The process of creating a new World Generator

So now that we have a handle on what process the server takes at generating these chunks lets look at how we can modify it. All modifications to world generators are done by implementing and registering a WorldGeneratorModifier. When a world is loaded it fetches from its configuration which modifiers should be applied to its generator and runs through them.

At this time the config for these modifiers is not yet implemented, as a temporary solution while testing you can listen for the WorldLoadEvent of your target world and call your modifier yourself.

From a WorldGeneratorModifier you are able to modify or replace all aspects of the world generation process. by iterating through the lists of generator populators and populators you should see the vanilla populators which may be modified through their interfaces within org.spongepowered.api.world.gen.populators or removed.

In addition of you are creating an entirely new generator and wish to add new instances of the vanilla populators you can build the vanilla populators by getting a copy of the PopulatorFactory from the game registry and getting the appropriate builder.

Things not implemented yet

On the topic of populators and generator populators you can refer to the follwoing which is a copy of my current expansion plan which I am in the process of implementing currently.

World Generation API expansion plan: phase 1: Populators

  • Create better utilities for VariableAmounts and WeightedRandomObjects
  • Update populator interfaces to make better use of PopulatorObjects, VariableAmounts, and WeightedRandomObjects.
  • Allow overwriting biome populators for particular world generators
  • Implement populator interfaces onto WorldGen* NMS classes
  • Overwrite vanilla populators in ChunkProvider* and BiomeDecorator to use our populator lists

phase 2: GenPop

  • Create GenPop interfaces
  • expose Noise generators? (flow noise?)
  • Implement GenPop interfaces onto MapGen* NMS classes
  • Overwrite vanilla to use our genpop lists

phase 3: Structures

  • Create interfaces for structures
  • Create some sort of StructureBuilder (?) for creating new structures

phase 4: Custom everything

  • Allow creation and registration of custom biomes
  • Allow control of animals/monsters that spawn in certain biomes

Another currently unimplemented piece is the creation of config in order to define, from a server admin's perspective, which WorldGeneratorModifiers will be applied to a world.

There is the requirement for proper control over a world's generation that you are able to modify the populators and other options of a biome within a world generator independantly of the rest of the world. It is therefore planned (in phase 1 above) to allow the overridng of this information from the context of a world generator.

You may have noticed that not once have I mentioned GeneratorType. GeneratorType is from an earlier stage of development and I need to take a bit of time to review its form and either modify it or remove it. It is possibly that I may define a generator type to be a collection of WorldGeneratorModifiers and in the earlier mentioned configuration for server admins they would be defining a generator type per world rather than a set of modifiers per world (or maybe both).

Finally, it should go without saying that world generation is a large and somewhat complicated system and any input on this system is always welcome.

TL;DR write a world gen api they said...it'll be simple and easy they said...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment