Skip to content

Instantly share code, notes, and snippets.

@Barteks2x
Last active September 4, 2022 14:06
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Barteks2x/41122efc766afdd47aeb457a3c19b275 to your computer and use it in GitHub Desktop.
Save Barteks2x/41122efc766afdd47aeb457a3c19b275 to your computer and use it in GitHub Desktop.
MC 1.13 Worldgen

Overview of changes

  • net.minecraftforge.fml.common.IWorldGenerator -> net.minecraft.world.gen.feature.Feature

    • No longer needed. I think it should be removed by forge, as it has been superseded by vanilla functionality. See below.
  • net.minecraft.world.gen.feature.WorldGenerator -> net.minecraft.world.gen.feature.Feature

    • This would also be the most common replacement of Forge's IWorldGenerator. This should be the solution for anything smaller than a chunk.
    • Except the 8 blocks offset. This is not a thing anymore. Population now works just like any normal person would expect.
    • Position of features is controlled by instances of net.minecraft.world.gen.placement.BasePlacement instead of by the feature itself.
  • net.minecraft.world.gen.MapGenBase -> net.minecraft.world.gen.carver.IWorldCarver

    • This is now finally exposed to mods in a useful way. As it was mostly hidden from modders before, not eveyone may know what it is, so it will be explained later. Generates caves and canyons.
  • net.minecraft.world.gen.structure.MapGenStructure -> net.minecraft.world.gen.feature.structure.Structure

    • Structure extends Feature. Works in a way similar to IWorldCarver, but diring population stage, and usually generates structures like villages and strongholds
  • net.minecraft.world.gen.feature.IFeatureConfig:

    Each feature has it's own configuration object. Features are all registered to individual biomes, instead of globally, wrapped in a net.minecraft.world.gen.feature.CompositeFeature, which contains the BasePlacement, IFeatureConfig and IPlacementConfig

    A custom IChunkGenerator may be able to access the feature directly and change the way it's used. Mojang's obfuscator probably stripped some useful getters from there, so I think it would be a good idea for forge to add them.

  • net.minecraft.world.gen.surfacebuilders.ISurfaceBuilder:

    • Each biome has a SurfaceBuilder that generates this biome's specific surface blocks on top, usually by replacing some of the existing stone blocks. It gets a randomized, small (exact range probably around -1 to 4, to be determined, because Notch's noise generators are still impossible to understand) float value that usually determines the depth of the surface.

Custom world generators and custom dimensions - IChunkGenerator and AbstractChunkGenerator

The idea of IChunkGenerator stays mostly the same in 1.13: Coordinate the action of all the other parts, and generate the terrain shape. One notable change is that it's now split into more clearly defined generation passes, and there is an abstract class that implements the core functionality that a generator is expected to do. Note: AbstractChunkGenerator's methods that call individual features, carvers and structures may be a good place for forge to add events, if it wouldn't cause too big performance hit.

A basic implementation of AbstractChunkGenerator has control over the terrain shape by being able to generate "noise density" values in generateNoiseRegion. An IChunkGenerator also always has a configuration object assigned to it.

In order to override how features, structures, and carvers are generated, mods can override the following methods:

  • public void makeBase(IChunk chunkIn)
    • Fills the actual chunk with blocks to produce basic terrain shape. // TODO explain how this actually is done
  • public void carve(WorldGenRegion region, GenerationStage.Carving carvingStage)
    • Runs all the chunk carvers. Due to the improved design, carvers can also be per-biome now.
  • protected void makeBedrock(IChunk chunkIn, Random random)
    • Intended to be used from the implementation as necessary
  • public void decorate(WorldGenRegion region)
    • Generates all the Features
  • public void buildSurface(IChunk chunkIn, Biome[] biomesIn, SharedSeedRandom random, int seaLevel)
    • Invokes biome-specific surface generation method. This is where blocks like dirt, grass, sand, sandstone and mesa clay blocks are placed. Currently there appears to be very little control over this from IChunkGenerator side.
  • public abstract double[] generateNoiseRegion(int x, int z);
    • Allows to control the properties of biome-specific surface, mostly dirt depth.

Registering parts of the generators

  1. For a custom generator

    Currently, I don't see any obvious way to do that.

    A way that appears to fit the 1.13 system would be to register them normally for all biomes, with default configuration being "disabled", and change the configuration in your own generator. And for that, see #2

  2. For normal worlds

    In your setup (? needs to be verified), for all the biomes you are interested in do one of these:

    biome.addFeature(generationStage, Biome.createCompositeFeature(feature, defaultConfig, defaultPlacement, defaultPlacementConfig));
    biome.addCarver(generationStage, Biome.createWorldCarverWrapper(carver, defaultConfig));
    biome.addStructure(structure, defaultConfig);

    Replacing or modifying the biome's surface builder is currently not possible.

Registering a new generator

  1. As a world type:

    This is mostly unchanged since MC 1.12.2.

    As always, WorldTypes are "registered" automatically in constructor, so all you need to do is create a class that extends WorldType and make an instance of it in your mod's setup.

    To create a custom chunk generator, override the createChunkGenerator method to return your generator. This method also creates the BiomeProvider, there is no separate method to create a biome provider anymore. If you don't want to override a dimension, you can call the createChunkGenerator method from Dimension which creates the dimension-specific world generator.

  2. As a dimension: Adding dimensions works basically the same way as in old MC versions. There is really different. You jsut don't use EnumHelper, and instead there is a runtime-patched create method in DimensionType class.

@BlackJar72
Copy link

Very informative, though I do not agree with your assessment of IWorldGenerator; it has plenty of functionality that Features and Structures lack, or at very least exposes it more simply and directly. Vanilla analogs do not supersede this, they fall short of it, partly because its generate() method takes an actual World object, thus exposing information not readily available to a Feature or Structure.

@Barteks2x
Copy link
Author

Very informative, though I do not agree with your assessment of IWorldGenerator; it has plenty of functionality that Features and Structures lack, or at very least exposes it more simply and directly. Vanilla analogs do not supersede this, they fall short of it, partly because its generate() method takes an actual World object, thus exposing information not readily available to a Feature or Structure.

IWorldGenerator is really not going to work well with 1.13 and 1.14 system. World object not being passed directly is intentional as most of it's uses aren't going to work with async worldgen. But if you really need it, you can access it through IWorld.getWorld() (and still, what do you need it for?)

And IWorldGenerator was actually the more limited system as it didn't have a good way of implementing structures. It is also possible to use Feature the way IWorldGenerator was used before, but would really be trying to get around the whole world generation system.

The only thing that is a bit annoying to do with the 1.13+ vanilla system is custom world types and dimensions with their own special terrain generation.

I still need to update this for 1.14 but I have a lot of other things to do and didn't get around to it yet.

@Draco18s
Copy link

Draco18s commented Aug 4, 2019

How does one go about cancelling / removing features? i.e. replacing one ore generator for a different one (some of us don't like vanilla blobs)
How does one get notified that a feature has been generated (in order to make changes or perform some other task)? i.e. being able to do something like count how much ore is in a chunk post decoration or altering the trap at the bottom of a pyramid, etc

@Barteks2x
Copy link
Author

Barteks2x commented Aug 4, 2019

While it's possible to do it is a bit of a hack currently, and IMO there should be some forge patches to make it easier. But it also may just be the right way to so it. There are 2 ways to go about it, one without reflection/accesstransformer, and one with it.

  • getFeatures for the biome you are interested in, and remove the ore features you want to modify, and put your own modified versions instead. The data to figure out which is what is public, but final
  • De-finalize some of the fields in ore feature config using access transformer and modify them directly

@yungnickyoung
Copy link

yungnickyoung commented Sep 19, 2019

Great post! This has been very helpful for me with 1.14.

Given your apparent expertise with this subject matter, I was hoping you could help me out a bit.

How would one go about overriding cave gen? It was very straightforward in 1.12 (just override MapGenCaves#generate or whatever it's called), but now I've been having problems trying to port to 1.14.

Specifically, I have done the following:

  • In FMLCommonSetupEvent, use reflection to access the carvers field of every registered biome, replacing it with a list consisting of my own carver (two references to the same static instance; one for AIR, one for LIQUID). This is hacky, but works.
  • In WorldEvent.CreateSpawnPosition event, get the world and pass its seed to my static carver instance for noise generator initialization (I'm using my own noise generation functions). I couldn't find another way to obtain the world seed before chunk generation begins.

The above setup works when run via the runClient gradle task. It also works when I run it as a dedicated server and join that. However, it fails for regular minecraft single player worlds, which is rather perplexing to me, considering it works via runClient.

I was hoping you could help me answer a few questions:

  • How might I change things up so that I'm not having these weird side-related issues? I'm pretty new to MC modding, so while I understand the concept of physical/logical sides, I don't know much in practice.
  • What do you think of my current setup? Any ideas on better ways to override vanilla cave gen for default world types?

Thanks in advance!

@Barteks2x
Copy link
Author

It's hard to guess what may be wrong it actually seeing the code.
In either case this looks like something forge could provide API for, so you should try discussing it on forge discord or make github issue for it if there really is no other way currently. Then make a forge pull request. Or hope someone else wants to make one.

That said if it's a custom dimension, you can create your own chunk generator which is in complete control over features, structures and carvers. You can simply override the right method and exclude the default cave carver instance and use your own instead.

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