Modding: Making a custom environment

From Noita Wiki
Jump to navigation Jump to search
Modding Navigation
BasicsData.wakGetting startedLua ScriptingUseful Tools
AudioEnemiesEnvironments (Fog of War) • Image EmittersMaterialsPerksSpellsSteam WorkshopSpritesheetsUsing CMake
Component DocumentationEnumsList of all tagsSpecial Tags
Lua Scripting
Lua APIUtility Scripts
Other Information
Enemy Information TableMagic NumbersSound EventsSpell IDsPerk IDsMaterial IDs

A Guide to the basics of Noita world generation & manipulation.

How biome loading works

The first thing the game needs to know is which biome map to load. This is defined inside the magic number BIOME_MAP, which by default is set to data/biome_impl/biome_map.png.

The biome map can be changed in one of three ways:

  • Overwrite that file with your own by placing your new version inside the same folder structure in your mod, e.g.: mods/yourmod/data/biome_impl/biome_map.png
  • Telling the game to load a different map statically, by changing the magic number using ModMagicNumbersFileAdd in the init.lua with the following file
<MagicNumbers BIOME_MAP="mods/yourmod/yourmap.png"></MagicNumbers>
  • Doing the same thing dynamically, by pointing to a lua script instead of a png:
<MagicNumbers BIOME_MAP="mods/yourmod/yourmap.lua"></MagicNumbers>

2 of those are static, meaning you cannot load them conditionally, the third one however is and must be used if we want biome mods to be compatible. How we do that we'll get to later. For now let's go over what the game does with the biome map. Once a biome map been loaded, it goes through all biomes defined in data/biome/_biomes_all.xml. For every entry, regardless if it's present on the biome map image or not, it parses the file defined in biome_filename. For instance:


will go look at data/biome/coalmine.xml, which in turn has two more definitions, wang_template_file="data/wang_tiles/coalmine.png" which defines which wang tiles will be used to generate the biome and lua_script="data/scripts/biomes/coalmine.lua", pointing to a script that will run once every time at game startup (meaning it will run again on quit & reload) and stay loaded throughout the game session. These 2 files defined in the biome's xml will always be accessed by the game, so they must exist, otherwise it will crash.

Documentation: Biome lists all fields available in the biome xml files.

After parsing the _biomes_all.xml, which also sets the biome_offset_y, it will load the biome map image and spawn the player in the world. The worlds origin (0, 0) position is defined by:

  • horizontal, x: Center of the biome map
  • vertical, y: biome_offset_y defined in _biomes_all.xml (biome_offset_x does not exist)

Biome generation

Now the actual biome generation happens, at whatever position the camera is currently pointing at, meaning what is currently visible and needs to be generated. Everything that's outside the camera will only be generated once it is needed, once it comes close to the viewport.

The start position will at first be empty space, so the game looks at the biome map to determine which biome needs to be loaded, by taking the camera coordinates, let's say x=1098, y=824.

One pixel on the biome map image is one chunk (512x512 ingame pixels/units), so to get which pixel this corresponds to on the biome map image, we:

  • divide x by 512 = 2.144, round down to 2
  • divide y by 512 = 1.609, round down to 1
  • which gives us x=2, y=1

which means the corresponding pixel on the biome map is 2 pixels to the right from the center, and 1 pixel down from biome_offset_y. So at a size of 70x35 pixels:

  • x = 70/2 = 35 + 2 = 37
  • y = biome_offset_y=14 + 1 = 15

Which gives us x=37, y=15 on the biome_map.png. That pixel has the color ffd57917 (hex ARGB), so the game looks inside the _biomes_all.xml, for a <Biome> with color="ffd57917". There is one declared with that color, which defines biome_filename="data/biome/coalmine.xml". In that file in turn wang_template_file tells the game how to generate the biome.

Shades of grey will get filled with the materials defined in coalmine.xml's <Materials>, while other colors will place the corresponding materials directly. Which color corresponds to which material is defined in data/materials.xml in the wang_colors attributes.

Biome scripts

But what about enemies and items? How do they get placed? By looking at the pixel colors of the wang tiles when placing them, each time it places a wang tile, which is the first time it comes into the camera viewport, it will try to call a predefined function in the biomes lua_script. Some are hardcoded and can be found in data/scripts/wang_scripts.csv. For instance: ffffd171,spawn_orb,-1,-1,-1,-1 If it places a wang tile that has a pixel with color ffffd171 inside it, it will call spawn_orb(x, y) where x and y are the coordinates where that pixel is placed in the world. If that function is not present in the biomes lua_script, it will produce an error when running noita_dev.exe without crashing. Some of these scripts have a default version defined in data/scripts/biome_scripts.lua, which can simply be imported like:


But you still need to define all the rest or you get spammed with errors like:

Couldn't find function: spawn_props

We can also define our own pixel colors and corresponding scripts using:

RegisterSpawnFunction(0xffabcdef, "my_function_name")
function my_function_name(x, y)
-- Gets called for every pixel of color ffabcdef found either in a wang tile, or a pixel scene

There is also a special "color" that can be registered:

RegisterSpawnFunction(0xffffeedd, "init")

whose pixel doesn't need to be placed anywhere, this is a special case. This will run the defined function every time a biome chunk is loaded.

Where does the tree to the left of the starting position and the skull in the desert come from?

That is defined in data/biome/_pixel_scenes.xml, where global pixel scenes are defined with absolute world coordinates. This path seems to be hardcoded and cannot be changed. So if you want to get rid of those pixel scenes, you will have to overwrite that file.

There is also this function, which is used by new game+:

BiomeMapLoad_KeepPlayer(filename, pixel_scenes='data/biome/_pixel_scenes.xml')

But that seems to be only callable once the game is already running and the map had been loaded. filename can be a biome map image or a map loading script (it works the same as MagicNumbers:BIOME_MAP). This is how you can re-generate the world and load a different pixel scenes file. As of today (12th April 2020) I found that this function is not reliable since it can sometimes crash the game.

How to make biome mods compatible


Now that we know how biomes get defined and loaded, let's look at what makes compatibility difficult.

New biomes must be added to data/biome/_biomes_all.xml, but this cannot be done programmatically. The only way is to overwrite it with our own _biomes_all.xml which we add our biome definitions into. If multiple mods do this however, the last mod that gets loaded overwrites all of the previous versions, nullifying them. So there needs to be a _biomes_all.xml which has all of every mods biomes defined inside of it and it needs to be the one that gets loaded. Furthermore, since all biomes will be declared, the files pointed to must always exist, even if that mod is not installed or active, otherwise the game crashes. So:

  • In _biomes_all.xml the files defined in biome_filename="data/biome/coalmine.xml"
  • and inside that file: wang_template_file="data/wang_tiles/coalmine.png"
  • and lua_script="data/scripts/biomes/coalmine.lua"

Then, depending on which mods are active, a different biome map image needs to be loaded or modified, which has all the biomes in their desired place.

The current solution:

All mods need to:

  • Have the same, up-to-date data/biome/_biomes_all.xml
  • Have all other mods up-to-date biome.xmls and wang_template_files
  • Have the same up-to-date biome map loader script, defined in MagicNumbers:BIOME_MAP
  • Have the same biome map height, since the biome_offset_y is defined in _biomes_all.xml, so a taller map means that offset has to change, but all mods share the same file, so... if one mod decides to make a taller map, everyone needs to have a tall map.

All of those need to be kept up-to-date. Every time a mod wants to change any of those files, meaning either the map layout, adding new biomes, changing the biome files like materials, or modifying wang templates, these files need to be updated for everyone. There are two solutions:

  • A) Everyone deploys a duplicate of those files in their mod. (This is the current method)
  • B) It all gets seperated out into another mod, which will be a dependency of all biome mods.

One upside of (A) is that there is no need to download a dependency mod. A big downside of solution (A) is that if an outdated mod gets loaded last, it will overwrite all the data files with outdated versions, potentially breaking other mods should they have released an update since then. If it gets loaded first, it will have it's outdated MagicNumbers:BIOME_MAP script run.

Solution (B) is much better for mod developers since only one mod needs to be updated when there is a change. The problem however is, that steam workshop does not allow multiple mod developers to work together on one mod, which would be necessary for (B). It's also not possible to transfer ownership. So one person would have to be responsible for providing and maintaining the mastermod.

For both methods, mod loading order does not matter.

In order to keep everything organized and easier to update, I propose that all files that need to be shared should go into their own subfolder in data: data/shared/ModName. This way that folder can simply be deleted and replaced with an updated version instead of finding all individual files. As a mod developer you can also quickly see which files need to be shared. Entities spawned from your biome scripts, pixel scenes etc are independent and can be put wherever you like.

So to reiterate, all files that need to be shared and up-to-date for everyone are:

  • _biomes_all.xml
  • The files defined in there, like biome_filename="data/biome/coalmine.xml"
  • The wang template files like wang_template_file="data/wang_tiles/coalmine.png"
  • Biome map loading script defined in MagicNumbers:BIOME_MAP

One solution that I found for making the lua scripts independent is this: The shared files will simply point to a dummy_script.lua, in which the actual file will get loaded. Like this, when biome.xml points to biome_dummy.lua, which is this file:

if ModIsEnabled("MyCoolMod") then

If the mod is not active, it's biome will not be present on the map, and won't really have it's functions executed. It will get loaded, but none of it's spawner functions called, so having an empty file with just this if statement works. In case the mod is enabled, it will load the actual real file and everything works normally. This allows mod authors to change their Biome:lua_script without having to update the shared files, only the dummy scripts need to be shared once. Finally, we need to to actually put the new biomes on the biome map. There are two solutions. First we need to handle map loading inside a script, to do that we point to a lua script instead of a png:

<MagicNumbers BIOME_MAP="mods/yourmod/yourmap.lua"></MagicNumbers>

Which will allow us to use the BiomeMap* functions inside that file to for instance load a pre-generated static map depending on which mods are active:

BiomeMapSetSize(70, 45)
if ModIsEnabled("SomeMod") and ModIsEnabled("OtherMod") then
  -- Has both SomeMod's pixels placed and OtherMod's.
  BiomeMapLoadImage(0, 0, "data/biome_impl/biome_map_somemod_othermod.png")
elseif not ModIsEnabled("SomeMod") and ModIsEnabled("OtherMod") then
  -- Only has OtherMod's pixels placed.
  BiomeMapLoadImage(0, 0, "data/biome_impl/biome_map_othermod.png")
-- etc

Yes, this will get horribly complicated with more mods, another solution would be to use BiomeMapSetPixel to dynamically paint onto the map:

BiomeMapSetSize(70, 45)
BiomeMapLoadImage(0, 0, "data/biome_impl/biome_map.png")
if ModIsEnabled("SomeMod") then
  BiomeMapSetPixel(35, 14, 0xffabcdef)
if ModIsEnabled("OtherMod") then
  BiomeMapSetPixel(40, 14, 0xfffedcba)

Helper functions to fill rectangle areas would be easy to implement and actually already exist in the nightmare mode mod.

Things to note: The last mod in the list will overwrite data files, but the first mod that gets loaded and defines MagicNumbers:BIOME_MAP will have it's file loaded, all others that define it again after that will be ignored.

How to make your mod compatible with nightmare mode and new game+

Nightmare mode

Nightmare mode will try to use it's own map generation script by setting MagicNumbers:BIOME_MAP. If the mod is at the top of the mod loading order, it will succeed. But luckily it seems to be possible to append to it's map loading script like any other:

ModLuaFileAppend("mods/nightmare/files/biome_map.lua", "your_map_loading_script.lua")

In addition to that, any BiomeMapSetSize and BiomeMapLoadImage calls can simply be overwritten and undone, by calling them again. What this means is that you can simply append your usual map loading script and everything will work fine, regardless of mod loading order.

New game+

This one is a little trickier, new game+ will get initiated in data/entities/animals/boss_centipede/ending/sampo_start_ending_sequence.lua line 41. The part that is important is inside data/scripts/newgame_plus.lua line 71:

BiomeMapLoad_KeepPlayer("data/biome_impl/biome_map_newgame_plus.lua", "data/biome/_pixel_scenes_newgame_plus.xml")

We need to somehow hijack this to again point to our own map loading script. And we might also want to prevent the usage of the NG+ pixel scenes. This can be done by appending this to biome_map_newgame_plus.lua:

setfenv(do_newgame_plus, setmetatable({
  BiomeMapLoad_KeepPlayer = function(map_file, pixel_scenes_file)
}, {
  __index = _G

This will simply redirect the call to BiomeMapLoad_KeepPlayer inside the function do_newgame_plus to our own version.

Compatibility example

Here are 2 sample mods that demonstrate how compatibility can be done. One of them has 2 versions, the only difference is simply the folder structure. Not sure yet what would be best.

Material reference for mapping

Open the thumbnail on right and save the file locally. You can then open it in your favorite image editor while editing wang tiles or pixel scenes for easy color-picker selection of any material or biome color.

Pixel Scenes

Pixel Scenes are images, randomly chosen by the generator or defined manually in data/biome/_pixel_scenes.xml

To draw a pixel scene, use the reference colors from the thumbnail image. The color FFFFFF uses materials from the biome it loads in, so you dont have to draw it yourself.

Special colors can be defined in your biome's lua script using RegisterSpawnFunction, this is useful for spawning entities at specified locations in your scene.

Example of a Pixel Scene image:


You can spawn your scene with T in devmode.

Splicing Large Pixel Scenes

Pixel scenes are limited to 512x512 pixels. For larger scenes, you can create spliced pixel scenes using noita exe in a command prompt

cd C:path/to/your/Noita/mods/yourmod
C:path/to/your/Noita/noita.exe -splice_pixel_scene files/largescene.png -x 500 -y 1000

This should generate the file yourmod/data/biome_impl/spliced/largescene.xml and a folder containing plz files for your scene. You can add this xml file to the PixelSceneFiles section in data/biome/_pixel_scenes.xml, which will cause it to spawn at the coordinates specified in the command (ie. x = 500, y = 1000).

Adding pixel scenes to biome generation

First draw your pixel scene (or just copy the one above). To use it in generated biomes you then need to make a .lua file, to append it into the scene pool:

table.insert(g_pixel_scene_02, {
  prob = 1000,
  material_file = "mods/yourmod/files/scene.png",
  visual_file = "",
  background_file = "",
  is_unique = 0
  • Use g_pixel_scene_01 for vertical scenes, g_pixel_scene_02 for horizontal ones.
  • prob should be from 0-1000, 1000 being most common.
  • material_file spawns the materials,(see example above)
  • visual_file does visuals of those materials, like the appearance of wand pedestals. Note: this is applied only on top of materials. Eg. visuals have no effect on air.
  • background_file is the background, which can any sort of image you wish.

Lastly, append your new file to the biome you want to add your scene to, by putting this in your init.lua:

ModLuaFileAppend("data/scripts/biomes/coalmine.lua", "mods/somemod/files/examplescene.lua")

First argument being the path to the biomes script, second to your scenes script.

Wang Tiles

Making your own wang tiles? Check how they would look like in-game with Horscht's awesome web-based wang tiler

Examples of the wang tiles from the Coal Pits (full, and zoomed section) can be seen below.