Welcome to the factorio noise expression tutorial! This assumes that you're authoring a mod and putting most of the code directly into data.lua.

The source code for this page is itself the data.lua file from the TOGoS Example Noise Programs mod processed to generate this HTML.

You might like to open the noise expression wiki page for reference while you work through this.

I'm going to define some custom elevation generators, because elevation has the most dramatic effect and is probably the most useful thing to customize: water is placed anywhere elevation is less than zero, and cliffs are placed along elevation contours.

For starters, let's define the simplest possible expression: one that just returns a constant value.

We'll add a 'named noise expression' prototype (`type = "noise-expression"`

),
which wraps a raw noise expression to give it a name and some other metadata.

The expression itself has a type and other fields depending on the type.
To make an expression that always returns a constant number which we provide
directly `type = "literal-number"`

, and `literal_value`

= the numeric value we want to return.

data:extend{ { type = "noise-expression", name = "constant-one-hundred", intended_property = "elevation", expression = { type = "literal-number", literal_value = 100 } } }

Setting intended_property = "elevation" makes this available in the 'Map type' / 'Elevation generator' drop-down on the map generator options screen when you start a new game.

Without that, you'd still be able to create a game using this generator,
but you'd have to do it using a custom mapgensettings JSON file and
`--create`

a save from the command-line.

We also need to add text to our words.cfg so that the dropdown shows nicely:

[noise-expression] constant-one-hundred=One Hundred

Noise expressions used by the terrain generator are evaluated at every point on the map.
Therefore `x`

, `y`

, and `distance`

variables are provided, which resolve to the x and y coordinate
and distance from the closest starting point.
(A few other variables are also provided, but we'll get into those later.)

Let's make a new elevation generator that just returns the value of `x`

:

data:extend{ { type = "noise-expression", name = "just-x", intended_property = "elevation", expression = { type = "variable", variable_name = "x" } } }

Again, we'll add the name "just-x" to our words.cfg:

[noise-expression] constant-one-hundred=One Hundred just-x=Just X

Hopefully we've got down that we need to add things to words.cfg, so I'm not going to mention it again. At least not for noise expression name translations.

Restart factorio to reload the mod, select it from the list, and...

Pretty boring, huh?
But the water to the west (where `x < 0`

) and lines of cliffs to the east
clearly show how `x`

, `elevation`

, water, and cliffs are related.

We can apply functions to literal value and variable expressions
(as well as other function application expressions) to make more complex stuff happen.
A function-application expression has `type = "function-application"`

,
a `function_name`

, and a table (keyed by number or name, depending on the function)
of arguments. Let's use the "add" function to add `x`

, `y`

, and a constant `100`

:

data:extend{ { type = "noise-expression", name = "x-plus-y", intended_property = "elevation", expression = { type = "function-application", function_name = "add", arguments = { { type = "variable", variable_name = "x" }, { type = "variable", variable_name = "y" }, { type = "literal-number", literal_value = 100 } } } } }

As you can see, writing out noise expressions this way is already becoming cumbersome. Fortunately there's a built-in Lua library to make things easier.

Let's import it:

local noise = require("noise")

There's a bunch of stuff in `noise`

, but probably the most all-around useful function is
`define_noise_function`

.
It lets us define noise expressions using mostly Lua syntax as if we're defining a Lua function.
As its argument, it takes a function of `x`

, `y`

, tile properties, and map properties.

If we were to define that x-plus-y expression this way, it would look like this:

data:extend{ { type = "noise-expression", name = "x-plus-y", intended_property = "elevation", expression = noise.define_noise_function(function(x, y, tile, map) return x + y + 100 end) } }

The `x`

and `y`

variables passed into our Lua function by `define_noise_function`

are `variable`

noise expression tables like we used above, but with metatables associated with them
so that arithmetic operations work in a helpful way.
Using the `+`

operator between two noise expressions results in an `add`

function-application expression,
so you can, at least as far as basic arithmetic goes, pretend that you're just writing Lua code.

The other arithmetic operators that work on noise expressions are `+`

, `-`

, `*`

, `/`

, and `^`

(exponentiate).

If you want to build your own noise expressions manually and be able to use those operators on them,
use the `to_noise_expression`

function, usually aliased as `tne`

locally:

local tne = noise.to_noise_expression local the_variable_x = tne{ type = "variable", variable_name = "x" } local the_variable_y = tne{ type = "variable", variable_name = "y" } -- Yet another way to write our x-plus-y expression data:extend{ { type = "noise-expression", name = "x-plus-y", intended_property = "elevation", expression = the_variable_x + the_variable_y + tne(100) } }

Past basic arithmetic, maybe the second most fundamental functions
used in noise expressions are `clamp`

and `ridge`

.
They each take 3 arguments: a value to be modified, a lower limit, and an upper limit.

`clamp`

returns the lower limit whenever the input value is lower than the lower limit,
and the upper limit whenever the input value is higher than the upper limit.

`ridge`

also always returns a value between the lower and upper limits, but does so
by 'folding' the input back between them. e.g. `ridge(6, 1, 5)`

would return 4
(because the input, 6, is one greater than 5, so folding it back under 5 produces 4).
`ridge(-1, 1, 5)`

would return 2, since the input is 2 below the lower limit.

We can make a map that's rings of land and water by just taking
the `distance`

variable (which is made handy as `tile.distance`

when using `define_noise_function`

)
and ridging it between a negative and positive value:

data:extend{ { type = "noise-expression", name = "rings", intended_property = "elevation", expression = noise.define_noise_function(function(x, y, tile, map) return noise.ridge(tile.distance, -20, 20) end) } }

Giving us this beautiful thing:

If you mess with the "water scale" slider you'll notice that nothing changes. Let's fix that!

For historical reasons, water scale inversely controls a variable called "segmentation multiplier".
When you set water scale to 600%, `segmentation_multiplier = 1/6`

.

To have it make a nice effect on our rings map, we can just multiply `distance`

by `segmentation_multiplier`

(which is provided to our `define_noise_function`

callback as `map.segmentation_multiplier`

)
and divide our function's output by it
(because if ridges are 6 times as wide, you'd expect them to be 6 times as tall, too, right?)

In general, you'll want to multiply your `x`

, `y`

, and `distance`

inputs by `segmentation_multiplier`

,
and divide your outputs by it.

data:extend{ { type = "noise-expression", name = "rings", intended_property = "elevation", expression = noise.define_noise_function(function(x, y, tile, map) return noise.ridge(tile.distance * map.segmentation_multiplier, -20, 20) / map.segmentation_multiplier end) } }

Yay, now scale has an effect!

But water level still does nothing!

Water level doesn't actually change the level of the water. Usually the way it is handled is by being subtracted from elevation.

There are a few map variables that are affected by the water level control:
`map.wlc_elevation_offset`

('water level correction offset') is a value that should
be added to our elevation to account for water level.
To accomodate the 'no water' case without dealing with infinities,
`map.wlc_elevation_minimum`

provides a minimum value that our elevation should be clamped above.

We can define a function that takes our pre-corrected elevation and the `map`

object to help us handle these:

local function water_level_correct(to_be_corrected, map) return noise.max( map.wlc_elevation_minimum, to_be_corrected + map.wlc_elevation_offset ) end

And use it for our rings generator:

data:extend{ { type = "noise-expression", name = "rings", intended_property = "elevation", expression = noise.define_noise_function(function(x, y, tile, map) local raw_elevation = noise.ridge(tile.distance * map.segmentation_multiplier, -20, 20) / map.segmentation_multiplier return water_level_correct(raw_elevation, map) end) } }

Notice that 'only in starting area' does not actually make a starting area lake! That's because our generator hasn't ensured that there is one.

We can use the `distance-from-nearest-point`

function
(to which calls can be constructed using `noise.distance_from(x, y, point_list)`

),
combined with the `starting_lake_positions`

variable
(which evaluates to a list of starting lake positions automatically generated from MapGenSettings)
to get the distance to the nearest starting area lake.
We can then subtract our desired lake depth from that distance to get starting area lakes,
and take the minimum of that and our other function to ensure that there is always
a starting area lake, regardless of water level.

data:extend{ { type = "noise-expression", name = "rings", intended_property = "elevation", expression = noise.define_noise_function(function(x, y, tile, map) local starting_lake_distance = noise.distance_from(x, y, noise.var("starting_lake_positions")) local starting_lake_bottom = starting_lake_distance - 10 local raw_elevation = noise.ridge(tile.distance * map.segmentation_multiplier, -20, 20) / map.segmentation_multiplier local corrected = water_level_correct(raw_elevation, map) return noise.min(corrected, starting_lake_bottom) end) } }

It's not the most interesting lake, but there's guaranteed to be some water, now, so players can definitely build some working steam boilers.

To generate maps with unpredictable features, we'll use `factorio-basis-noise`

, which is a
coherent noise function
similar to Perlin or Simplex noise.

This kind of function takes an `x`

/`y`

coordinate as input and produces a value that varies continuously
in a way that is omewhat random but with predictable characteristics.

Anyway, let's try to use it by creating an expression that applies the `factorio-basis-noise`

function:

data:extend{ { type = "noise-expression", name = "straight-basis-noise", intended_property = "elevation", expression = { type = "function-application", function_name = "factorio-basis-noise", arguments = { x = noise.var("x"), y = noise.var("y"), seed0 = tne(noise.var("map_seed")), -- i.e. map.seed seed1 = tne(123), -- Some random number input_scale = tne(1), output_scale = tne(20) } } } }

Notice that even though we set `output_scale`

to 20,
which should result in the function's output range being about -20 to +20,
this didn't seem to do anything.
That's because `factorio-basis-noise`

returns 0 at integer coordinates,
which is what `x`

and `y`

happen to be during map generation.
(As of 0.17, the map generator passes the coordinate of each tile's upper-left corner to the noise program.
It would be reasonable to get the coordinates of the center of the tile, instead,
but that wouldn't change the story very much. Either way we need to divide the inputs.)

To solve this, we could pass in smaller `x`

and `y`

values:

data:extend{ { type = "noise-expression", name = "straight-basis-noise", intended_property = "elevation", expression = { type = "function-application", function_name = "factorio-basis-noise", arguments = { x = noise.var("x") / 20, y = noise.var("y") / 20, seed0 = noise.var("map_seed"), -- i.e. map.seed seed1 = tne(123), -- Some random number input_scale = tne(1), output_scale = tne(20) } } } }

Or we could tell the basis noise to do that division internally, by setting its input_scale.

And actually, as long as we're at it, let's handle `segmentation_multiplier`

properly:
multiply the inputs and divide the outputs.

data:extend{ { type = "noise-expression", name = "straight-basis-noise", intended_property = "elevation", expression = { type = "function-application", function_name = "factorio-basis-noise", arguments = { x = noise.var("x"), y = noise.var("y"), seed0 = noise.var("map_seed"), -- i.e. map.seed seed1 = tne(123), -- Some random number input_scale = noise.var("segmentation_multiplier")/20, output_scale = 20/noise.var("segmentation_multiplier") } } } }

Note that noise expressions returned by noise.var already have the metatable added
to allow us to do arithmetic on them, so we don't need to `tne`

them before
doing arithmetic on them or using them as sub-expressions.

Let's write a function to correct water level and add in the starting lake, since that's going to be a shared feature of every elevation function we write.

local minimal_starting_lake_elevation_expression = noise.define_noise_function( function(x,y,tile,map) local starting_lake_distance = noise.distance_from(x, y, noise.var("starting_lake_positions"), 1024) local minimal_starting_lake_depth = 4 local lake_noise = tne{ type = "function-application", function_name = "factorio-basis-noise", arguments = { x = x, y = y, seed0 = tne(map.seed), seed1 = tne(123), input_scale = noise.fraction(1,8), output_scale = tne(1.5) } } local minimal_starting_lake_bottom = starting_lake_distance / 4 - minimal_starting_lake_depth + lake_noise return minimal_starting_lake_bottom end) -- Water level correct, multiply elevation by map scale, -- and add minimal starting area lake local function finish_elevation(elevation, map) local elevation = water_level_correct(elevation, map) elevation = elevation / map.segmentation_multiplier elevation = noise.min(elevation, minimal_starting_lake_elevation_expression) return elevation end

Let's apply that to our basis noise elevation generator.

Note that `finish_elevation`

divides by segmentation_multiplier **after**
correcting water level; this way bodies of water keep their overall shape.
It also means that we should not divide our output by `segmentation_multiplier`

ourselves.

data:extend{ { type = "noise-expression", name = "straight-basis-noise", intended_property = "elevation", expression = noise.define_noise_function(function(x,y,tile,map) local basis_noise = { type = "function-application", function_name = "factorio-basis-noise", arguments = { x = noise.var("x"), y = noise.var("y"), seed0 = tne(noise.var("map_seed")), -- i.e. map.seed seed1 = tne(123), -- Some random number input_scale = tne(noise.var("segmentation_multiplier")/20), output_scale = tne(20) } } return finish_elevation(basis_noise, map) end) } }

If you slide the water coverage and scale sliders to 600%, you'll see that this produces a lot of tiny islands.

This is **almost** useful. Wouldn't it be better if we could make reasonably-sized islands, though?
Let's decrease the input scale by a factor of 6 so that it's possible to make larger ones.
The minimum scale setting was a bit too small, anyway:

data:extend{ { type = "noise-expression", name = "straight-basis-noise", intended_property = "elevation", expression = noise.define_noise_function(function(x,y,tile,map) local basis_noise = { type = "function-application", function_name = "factorio-basis-noise", arguments = { x = noise.var("x"), y = noise.var("y"), seed0 = tne(noise.var("map_seed")), -- i.e. map.seed seed1 = tne(123), -- Some random number input_scale = tne(noise.var("segmentation_multiplier")/120), output_scale = tne(20) } } return finish_elevation(basis_noise, map) end) } }

Note that this doesn't guarantee there will positive elevation at or near the starting point, especially on high-scale settings. I will leave that as an exercise for the reader.

Other stuff that would be good to have examples of in here:

- The factorio-multioctave-noise function
- Combine expressions in different cool ways
- Correlate multiple tile properties, e.g. biters at high elevation, or trails
- Map gen presets