This is an old revision of the document!
Table of Contents
Adding a Biome
NOTE: There is a ongoing pull request on Fabric API that adds biome API for 1.16.2. Read this tutorial as a workaround until it gets merged.
Introduction
There are 3 steps that are required to add a biome to the world.
- Creating a biome
- Registering a biome
- Adding a biome to a climate zone in the world
In this tutorial, we will add new biome called obsiland biome, whose surface is covered with obsidians.
Creating a Biome
To create a biome, use Biome.Builder
and configure properties.
Missing one property will likely cause the game to crash.
It is recommended to look at vanilla biomes (created in DefaultBiomeCreator
) as examples.
public class ExampleMod implements ModInitializer { // SurfaceBuilder defines how the surface of your biome looks. // We use custom surface builder for our biome to cover surface with obsidians. private static final ConfiguredSurfaceBuilder<TernarySurfaceConfig> OBSIDIAN_SURFACE_BUILDER = SurfaceBuilder.DEFAULT .method_30478(new TernarySurfaceConfig( Blocks.OBSIDIAN.getDefaultState(), Blocks.DIRT.getDefaultState(), Blocks.GRAVEL.getDefaultState())); private static final Biome OBSILAND = createObsiland(); private static Biome createObsiland() { // We specify what entities spawn and what features generate in the biome. // Aside from some structures, trees, rocks, plants and // custom entities, these are mostly the same for each biome. // Vanilla configured features for biomes are defined in DefaultBiomeFeatures. SpawnSettings.Builder spawnSettings = new SpawnSettings.Builder(); DefaultBiomeFeatures.addFarmAnimals(spawnSettings); DefaultBiomeFeatures.addMonsters(spawnSettings, 95, 5, 100); GenerationSettings.Builder generationSettings = new GenerationSettings.Builder(); generationSettings.surfaceBuilder(OBSIDIAN_SURFACE_BUILDER); DefaultBiomeFeatures.addDefaultUndergroundStructures(generationSettings); DefaultBiomeFeatures.addLandCarvers(generationSettings); DefaultBiomeFeatures.addDefaultLakes(generationSettings); DefaultBiomeFeatures.addDungeons(generationSettings); DefaultBiomeFeatures.addMineables(generationSettings); DefaultBiomeFeatures.addDefaultOres(generationSettings); DefaultBiomeFeatures.addDefaultDisks(generationSettings); DefaultBiomeFeatures.addSprings(generationSettings); DefaultBiomeFeatures.addFrozenTopLayer(generationSettings); return (new Biome.Builder()) .precipitation(Biome.Precipitation.RAIN) .category(Biome.Category.NONE) .depth(0.125F) .scale(0.05F) .temperature(0.8F) .downfall(0.4F) .effects((new BiomeEffects.Builder()) .waterColor(0x3f76e4) .waterFogColor(0x050533) .fogColor(0xc0d8ff) .skyColor(0x77adff) .build()) .spawnSettings(spawnSettings.build()) .generationSettings(generationSettings.build()) .build(); } }
Registering Biomes
We register our biome at the entrypoint onInitialize
.
If you use your own surface builder, you will also have to register it.
public class ExampleMod implements ModInitializer { public static final RegistryKey<Biome> OBSILAND_KEY = RegistryKey.of(Registry.BIOME_KEY, new Identifier("tutorial", "obsiland")); @Override public void onInitialize() { Registry.register(BuiltinRegistries.CONFIGURED_SURFACE_BUILDER, new Identifier("tutorial", "obsidian"), OBSIDIAN_SURFACE_BUILDER); Registry.register(BuiltinRegistries.BIOME, OBSILAND_KEY.getValue(), OBSILAND); BuiltinBiomesAccessor.getRawIdMap().put(BuiltinRegistries.BIOME.getRawId(OBSILAND), OBSILAND_KEY); } }
@Mixin(BuiltinBiomes.class) public interface BuiltinBiomesAccessor { @Accessor("BY_RAW_ID") public static Int2ObjectMap<RegistryKey<Biome>> getRawIdMap() { throw new AssertionError(); } }
You should also give your biome a language entry in your en_us.json
file:
- src/main/resources/assets/modid/lang/en_us.json
{ "biome.tutorial.obsiland": "Obsiland" }
Adding a biome to a climate zone in the world
The vanilla biomes used in the overworld is defined VanillaLayeredBiomeSource
.
We have to add our biome to VanillaLayeredBiomeSource
first.
@Mixin(VanillaLayeredBiomeSource.class) public class VanillaLayeredBiomeSourceMixin { @ModifyArgs(method = "<init>(JZZLnet/minecraft/util/registry/Registry;)V", at = @At(value = "INVOKE", target = "net/minecraft/world/biome/source/BiomeSource.<init>(Ljava/util/stream/Stream;)V")) private static void addOverworldBiomes(Args args, long seed, boolean legacyBiomeInitLayer, boolean largeBiomes, Registry<Biome> biomeRegistry) { Stream<Supplier<Biome>> biomes = args.get(0); biomes = Stream.concat(biomes, Stream.of(() -> biomeRegistry.get(ExampleMod.OBSILAND_KEY))); args.set(0, biomes); } }
We need to specify the climate to which the biome is added.
In this tutorial, we will add the custom biome to the temperate climate as an example.
We modify SetBaseBiomesLayer.TEMPERATE_BIOMES
to accomplish this.
public class ExampleMod implements ModInitializer { @Override public void onInitialize() { [...] // SetBaseBiomesLayer.TEMPERATE_BIOMES is an array of raw biome IDs. // We have to get the raw id of our biome and append it to TEMPERATE_BIOMES. SetBaseBiomesLayer.TEMPERATE_BIOMES = ArrayUtils.add(SetBaseBiomesLayer.TEMPERATE_BIOMES, BuiltinRegistries.BIOME.getRawId(OBSILAND)); } }
We have to use Access Wideners to modify SetBaseBiomesLayer.TEMPERATE_BIOMES
because it is a privte static field.
Your modid.accesswidener
should look like:
accessWidener v1 named accessible field net/minecraft/world/biome/layer/SetBaseBiomesLayer TEMPERATE_BIOMES [I mutable field net/minecraft/world/biome/layer/SetBaseBiomesLayer TEMPERATE_BIOMES [I