This guide covers the basics of working with .yaml's when designing a custom world for Oxygen Not Included.
The information detailed here will hopefully improve the learning process for anyone interested in modding Oxygen Not Included asteroids, and will hopefully encourage more people to do so.
Since there does not seem to be any wiki dedicated to modding ONI, this guide will function in part as a repository for information related to worldgen. I can only record here the admittedly incomplete knowledge I have gained over the brief time I have been modding the game. I plan to continue updating this guide as I learn more about worldgen and have more time on my hands. At some point I will add an appendix.
To edit .yaml's, I recommend using Visual Studio Code, and exercising a great deal of patience as you learn to speak the game's language.
This section of the guide lists and describes the locations you will need to access as you create your asteroid.
This folder contains the id of any element used in the game, e.g., liquid hydrogen (LiquidHydrogen).
Other id's can be found in the "\worldgen" directory. The most relevant of those are "borders.yaml", which lists the subworld border types, and "mobs.yaml", which lists the majority of critters (base morph only) and plants, as well as a few geysers.
This folder contains templates of bases and points of interest (POI). Templates may be created in-game using sandbox and/or debug mode and saved to a .yaml, or constructed manually in a .yaml.
POI are the ruins of past colonies randomly generated in the game world. The starting base, found in "\bases", which consists of your most immediate surroundings (the printing pod, the clump of oxylite, the floor, etc.) at the start of any new map, is the other main example of a template.
You could, if you wanted, create your entire world from templates, and have no randomly generated elements on the map. Usually, however, templates are a way to add flavor to your map, and to generate specific features in designated locations. Geysers and volcanoes are excellent examples of this.
This directory contains files and folders related to the random generation of worlds.
Worlds are the collection of subworlds that together combine to fill the map and create a complete world.
Subworlds are what is commonly known in game as biomes. They are usually separated from other subworlds by a border of abyssalite or granite.
Biomes determine the type of terrain elements generated for each subworld, and the parameters for their generation. This is where you have the most direct control over what your subworlds will look like.
Features are optional components of subworld generation. They are a very useful way to insert pockets of specific elements into a subworld, e.g., a body of water. Unlike templates and POI, their shape and size are randomly generated, according to parameters set in their .yaml. Where POI are usually geysers or remnants of human activity, features are more oriented towards natural formations of elements.
Subworld features should not be confused with global features, found in "\StreamingAssets\templates\features". The only default global features are generic geysers and volcanoes.
Noise determines the pattern in which biome data is generated. The same biome data will have different distribution and structure depending on what noise parameter is given.
This section of the guide explains each part of a world .yaml. The example used is "\worlds\Volcanic.yaml". Volcanea will be used as the example world for most of this guide.
Name and description cannot be edited without a compiler, which this guide will not cover.
Difficulty and tier determine the position of the world in the world selection menu. The default difficulty values that can be set are:
SpriteName designates the image used for your world in the game's world selection screen.
CoordinatePrefix is the abbreviated name that appears as the world's coordinates, before the seed number.
DisableWorldTraits disables world traits from being generated for your asteroid. It can be set to "true", and is "false" by default. World traits can be found in the "\worldgen\traits" directory. This parameter does not appear in Volcanea, or any default asteroid except Terra. Unless you are generating a world very similar in style to default worlds, world traits should be disabled.
Worldsize determines the dimensions of your world in width (X) and height (Y). Width must be less than height, or the game will crash.
SubworldFiles are all the subworld files loaded for use in your world during worldgen. In order to generate any subworld in your world, it must be listed here for the game to access it. The directory used is \worldgen, so any subworld files must be located within that directory. The default folder for subworlds is \worldgen\subworlds. Subworlds will be visited in detail in the Subworlds section of this guide.
StartSubworldName is the location of the starting subworld.
StartingBaseTemplate is the filename of the starting base, found in "\StreamingAssets\templates\bases".
StartingBasePositionHorizontal and startingBasePositionVertical determine the location of the starting base within the starting subworld, roughly at the center by default.
globalFeatureTemplates determines the type and number of templates generated in the world. By default, 12 geysers will be generated randomly throughout the world in subworlds that allow global templates. Global features are located in "\StreamingAssets\templates\features".
UnknownCellsAllowedSubworlds is where you will determine how subworlds are generated in the world.
Tagcommand determines the location parameter for subworld generation. These are:
Default, which generates the starting subworld.
DistanceFromTag, which will spawn the subworld at a set distance from the given tag. This type requires a distance range to be set.
AtTag, which will spawn the subworld at the location of the given tag.
Tag determines the reference point for location generation. These are:
- AtStart, which will use the world's start location as a reference point.
- AtDepths, which will use the bottom of the world as a reference point.
- AtSurface, which will use the top of the world as a reference point.
MinDistance and maxDistance set the minimum and maximum distance at which the subworld can be generated from the given reference point. These parameters can have the same value. If they have different values, e.g., min: 1 and max: 2, the subworld will be generated in that range.
Command determines the way the subworld will be generated in relation to other subworlds. These are:
- Replace, which generates the given subworld in whole at all instances of the given location. Any subworld previously generated at the location will be removed.
- UnionWith, which generates a random number of the given subworld in the given location without regard to subworld borders. This should result in a partial subworld border, where the subworld generated using UnionWith will merge with the subworlds adjacent to it. If the given location is a range, the random number of generated subworlds will be spread across the range.
- ExceptWith, which can only be used in conjunction with UnionWith, in order to limit its range. An example can be found in the next section, where each step of Volcanea's generation is explained and illustrated.
SubworldNames determines the subworlds generated by filename.
TemperatureRanges determines the subworlds generated by temperature. Multiple temperature values can be given. Any subworld given in subworldFiles that fits the given temperatures will be generated at random. A list of the game's temperature values can be found in "\worldgen".
This section of the guide explains Volcanea's worldgen.
In this section of the .yaml, Volcanea's subworlds are generated block by block, starting from the center and moving outwards. Worldgen reads these linearly, so subworlds generated later in the .yaml will always override subworlds generated before them.
SandstoneStart, SandstoneMiniMetal and SandstoneMiniWater compose the player's starting subworld, and correspond to the in-game temperate biome. See below for a note on balancing.
Immediately surrounding them are HotMarsh and Jungle subworlds, which correspond to in-game slime and caustic biomes, respectively.
Further out, and to the edges of the map, Ocean and Frozen subworlds also begin to be generated, which correspond to in-game salt and cold biomes, respectively.
All these subworlds, generated using the Replace command, should have clear borders, without overlap. Worldgen is rarely, if ever, perfect, however.
A standard 256x384 world allows for roughly 4 blocks horizontally and 5 blocks vertically from the start location, and it is not necessary to use a minDistance of more than 5. It is necessary to use a maxDistance of 999 for the 5th block, as seen here, or worldgen will break.
Note on balancing
The starting subworld usually consists of 7 "mini" subworlds, formed in the shape of an asterisk. In Volcanea, with two subworlds being generated at location 1-1, there will be an equal chance of either MiniMetal or MiniWater being generated in each of the blocks surrounding SandstoneStart. This means that at worst one in every 32 seeds will have only MiniMetal or MiniWater subworlds in the starting subworld. This is important to note in many situations, but perhaps most importantly when generating bodies of water in the starting subworld.
Mini subworlds in the starting subworld, numbered 1-7.
Here worldgen uses the bottom of the map as a reference point, and generates oil subworlds at a distance of 1-2 blocks from that point. At this point, any subworld previously generated in those locations will be overwritten. Note that AtDepths refers to the entire bottom of the map, not the center or any one point on the bottom of the map.
Here worldgen creates the surface subworld and immediately adds a layer of surface debris on the surface of the world.
Here worldgen creates the magma vents that are so recognizable on Volcanea. A location value of 1-4 from Depths puts the magma vents right at the border of the start location. This first UnionWith generates magma vents anywhere in the world within that range, while the second UnionWith generates magma vents at the edge of the map. Note that AtEdge refers to both edges of the map, although, given that asteroids are treated as round, they are technically considered to be one edge.
The second UnionWith is important, because without it the magma vents would be spread fairly thin across the bottom half of the map. Adding a second UnionWith with a fairly small range of 0-1 to generate vents at the edge of the world ensures every Volcanea seed has the distinctive magma rising up both sides of the map. The ExceptWith ensures that those vents will never be generated within 1-4 blocks of the surface. Without it, the magma vents at the edge would be generated all the way to the surface.
Notice the way the MagmaVent subworld both interrupts and is interrupted by other subworlds in the image below, also evident in the image illustrating OilPockets, above. This is a result of UnionWith generation.
Here the magma-filled bottom of the map is generated.
Here a somewhat peculiar aspect of Volcanea is generated. Surface crags are generated down into the body of the world, occasionally resulting in subsurface pockets of space. Because the SurfaceCrags subworld contains the Gravitas POI, you will occasionally find seeds where Gravitas has been generated below the surface.
Distances are always ambiguous in worldgen. There are always overlaps, and UnionWith obeys different spacing rules than Replace. To perfect the spacing of your subworlds, you will need to test the world and tweak it across many worldgens.
This section explains how subworlds are generated.
The default subworld groups, each of which corresponds to a folder in the "\subworlds" directory, are:
Looking at other Sandstone subworlds not used by Volcanea, we can find subworlds with similar topographies but different environments, as in the case of the "Cold" Sandstone variants. Note the only difference between the two subworlds below, "SandstoneMiniMetal" and "SandstoneMiniMetalCold", is the temperatureRange parameter.
More noticeable differences can be found elsewhere. "JungleFrozen", a subworld not used in Volcanea, contains no lifeforms indigenous to the "Jungle" subworld, except the morb. This is because the flora and fauna of the Jungle subworld require high temperatures to grow. See below.
This example should be followed as a rule. Generating critters or plants in a subworld that is too cold or hot for them to survive has very limited function and is generally bad design.
Subworld borders should be an important consideration in this regard. Avoid generating subworlds adjacent to others that vary drastically in temperature without an abyssalite border.
An exception to this can be seen in the Oasisse asteroid, where part of the world's difficulty is in the need to quickly insulate the borders of the starting subworld against the enroaching heat of the surrounding subworlds.
By default, starting subworlds have a granite ("rocky") border, which offers limited insulation. Abyssalite ("hardToDig") border should not be used for the starting subworld, primarily so that the player has the opportunity to explore without needing a Duplicant skilled in Superhard Digging.
More creative uses of worldgen will naturally deviate further from typical world types, and may bend or break the usual rules of world building. See below for an example I generated for the sake of this guide. Whatever type of world you build, the most important criterion to hold it to should be whether or not other players will find it fun to create a colony in it.
Below are all the subworld parameters whose functions I have tested and understand to some degree.
biomeNoise determines the subworld's terrain structure. An illustration of each noise type will be given in the Noise section of the guide.
temperatureRange determines the temperature of the subworld. Possible temperature ranges can be found in "\worldgen\temperatures.yaml".
pdWeight determines the size of the subworld. Smaller values generate smaller subworlds.
minChildCount seems to determine the population of the subworld, with higher values seeming to generate less critters and plants. More testing is needed.
borderOverride forces the given subworld border type. Possible border types can be found in "\worldgen\borders.yaml".
biomes references biome data ("name"), found in "\worldgen\biomes", determines the distribution of that data throughout the subworld when referencing multiple biomes ("weight"), and determines the flora and fauna of the subworld ("tags").
centralFeature and features determine which features can be generated in the subworld, but do not guarantee that any feature will be generated. I have not tested centralFeature enough to determine how it is distinguished from other features, but I would assume that it takes precedence during worldgen. Features can be found in the "\worldgen\features" directory.
pointsOfInterest determines which templates will be generated in the subworld. If a subworld lists a POI to be generated, exactly one instance of all instances of that subworld in the world will have the given POI generated in it. If multiple POI are listed, each POI will have equal chance of being generated.
tags assigns particular limits to the subworld. I have not tested the function of most of these. NoGlobalFeatureSpawning is the one that I have found to be most relevant. Global features by default are geysers, vents, and volcanoes. Global features can be found in the "\StreamingAssets\templates\features" directory.
This section of the guide explains how biomes work, and provides illustrations of different biome data.
Biomes are the elements that make up the terrain of subworlds. The distribution of solid elements, gases, and liquids are all determined by the subworld's biome. In default biomes, with the exception of magma and liquid hydrogen and oxygen, liquids are not generated. When generating bodies of water for the player, features are used instead. This is because, unless the liquid occupied the majority of space in the subworld, using biome data to generate water would create rivulets and scattered pockets of it throughout the subworld, which would be disruptive rather than functional.
The image below is of "\biomes\Magma.yaml".
MagmaPool, MagmaBed, CooledMagmaBed, and MagmaVent are the sub-biomes, which are specified in the subworld .yaml, below.
In the MagmaVent subworld given above (right), the sub-biome used is MagmaVent. Therefore, when generating a MagmaVent subworld, worldgen will assign it the content given in the MagmaVent sub-biome, namely obsidian and magma.
bandSize dictates how much of a given element will be generated. In theory, if two elements share a bandsize of 0.5 in a sub-biome with no other elements, they should have equal distribution in that biome. Bandsize does not always follow the expected logic, however.
In order to illustrate bandsize in worldgen, I have generated a sample world filled with columns of duplicated MagmaVent subworlds. From outermost (0-0) to innermost (4-4), the bandsize of obsidian/magma in each column is 2.0/0.1 (0-0), 1.5/0.1 (1-1), 1.0/0.1 (2-2), 0.5/0.1 (3-3), 0.01/0.1 (4-4).
As you can see, 3-3 by no means has 5 parts obsidian to 1 part magma. Since I do not have access to the game code and do not have a background in mathematics, I can only guess at how bandsize is generated through experience and testing in worldgen. At a certain point it becomes intuitive, and I recommend experimenting on your own until you reach that point.
Features and POI
This section of the guide explains what features and POI are and the difference between them.
This is "\features\jungle\BleachRoom.yaml", a default feature generated in Jungle subworlds.
Shape determines the shape of the feature. Known shapes are: ShortWide, TallThin, Blob, Splat, Circle, and Square.
borders seems to extend the elements listed under "RoomBorderChoices" beyond the area set by blobSize by the given number of cells. Needs further testing.
blobSize determines the size of the feature, measured in cells.
RoomCenterElements generates elements in the center of the feature.
RoomBorderChoices generates the feature's border. Multiple border types can be used to restrict a certain element to one section of the border.
Element generates the given element. Element id's can be found in the "\StreamingAssets\elements" directory.
Weight determines how much of the given element will be generated relative to other elements.
Points Of Interest
This is a custom POI, created in debug mode then edited in .yaml. The contents of the POI are shown by the selected cells in the picture below.
Default POI .yaml's tend to be exceptionally long. Because there are no randomly generated elements in POI, each .yaml contains a cell-by-cell list of all of its elements and their properties.
Cells lists every gas, liquid, solid, or special element in the POI. The properties of elements can be: mass, temperature, location_x, location_y, diseaseName, diseaseCount.
Buildings lists the structures in the POI with which the player can interact, e.g., heatsinks. These are usually destructible.
pickupables lists all the non-elemental objects in the POI that dupes can sweep, e.g., seeds.
ElementalOres lists the elemental objects in the POI that dupes can sweep, e.g., water bottles.
OtherEntities lists anything which does not fit in or is an exception to the above types, e.g., critters. These are usually indestructible.
Features and POI are distinct in two main ways.
First, features are randomly generated during worldgen. POI have no variation and are generated exactly as they appear in their template.
Second, when a subworld .yaml contains any number of POI, one of the given POI will always be generated in one instance of that subworld in the world. Further, only one of each POI can be generated in the same world. If "poi_magma_bigvolcano" exists in the world, no other instance of it can be generated. Features, however, may or may not be generated in a given subworld, and do not consider features generated in subworlds other than their own.
In the Frozen subworld, as with most other subworlds that generate POI, there are multiple POI groupings.
One POI in each of these groupings will be generated in a subworld somewhere in the world, provided there are enough subworlds to fit them.
Each POI has the same chance of being generated as every other POI in its grouping.
This section of the guide goes over the different types of worldgen errors and crashes, and common causes of each.
Before you start editing, create a backup of "\StreamingAssets", then create a subdirectory in "\worldgen". Keep all the worldgen files that you edit in that subdirectory, excepting POI, which must remain in the default POI directory in order to be generated.
Be sure to maintain the spacing rules shown in any default .yaml while editing. Failing to do so will usually cause gamecrash.
Because the game is so fragile to alterations in .yaml's, you will probably encounter a lot of crashes when you first start, and much of the time you will probably not feel sure what caused the crash.
The most useful piece of advice I can impart is to test worldgen after even the smallest changes. It can be a little tedious to exit the game only to make the slightest change to your .yaml before you test it again, over and over, but by doing so you will almost certainly save yourself from having to backtrack across several changes without knowing which caused the crash or error, and risk losing track of what you changed to the point that you have to reconstruct your entire worldgen.
There are two common points in worldgen where errors will occur, pictured below.
If the loading bar resets itself multiple times from "freezing ice formations", the asteroid will load as a block of either abyssalite or unobtainium. This usually means that your error is related to the blocking of subworlds, that is, dividing the world into blocks and assigning a subworld to each. Look for errors in your world .yaml first, or in how your subworld borders are generated.
If the loading bar freezes at "establishing personal boundaries", your error is mostly likely related to biome or subworld generation. If a subworld is attempting to access biome data that does not exist, for example, you will face this error. Look for errors in your subworld .yaml's first.
The black hole can be caused by the tiniest of things. A typo somewhere in one of your files, weighting a subworld outside the accepted parameters, generating an invalid POI, a line of bad code basically anywhere in any of the files used in worldgen... pretty much anything can cause a black hole. They are usually triggered when attempting to load the world selection screen.
The best way to deal with black holes is to revert to a stable version of worldgen, figure out what you did wrong in the process, and try again.
While most modded worlds will have world traits disabled, there is room for ones that follow the rules of default worlds closely enough that world traits could be enabled without disrupting worldgen. In these cases, custom world traits could be created to give the player an additional level of control over worldgen. Default world traits are found in the "\worldgen\traits" directory. Note that a decompiler would be needed to make custom world traits.