I find noise expressions and their documentation very, very difficult to comprehend. At this point nothing you can say will convince me that they aren't an occult ritual.
I want to add a new resource that spawns "spotty" resources in exactly the same manner as vanilla's crude oil, just with tweaked frequencies and patch size. That's the easy part, done.
I also want to be able to tweak any spawned resources via script on chunk generation, replacing them conditionally with things based on tile properties such as elevation and temperature. That's also relatively straightforward thanks to surface.calculate_tile_properties().
What you can't do straight off the shelf is access the noise that generates ore or crude oil resources. Noise layers appear to be defined for them but as far as I can tell they aren't actually used anywhere, maybe they are a left-over from way back when resources still used peak expressions, I don't know. I would like to be able to access "crude oiliness" in the same way that I can access elevation, moistness or temperature.
So I guess what I want to be able to do is:
- Convert crude oil's autoplace settings to a named noise expression that precisely duplicates the existing spawn behaviour of crude oil
- Tell crude oil to use that expression
- Hope that this allows you to access the rough proximity to a crude oil patch via surface.calculate_tile_properties
How do I even get started with that?
Wrangling resource autoplace / noise expressions?
- Deadlock989
- Smart Inserter
- Posts: 2529
- Joined: Fri Nov 06, 2015 7:41 pm
Wrangling resource autoplace / noise expressions?
Last edited by Deadlock989 on Mon Jan 01, 2024 12:37 pm, edited 1 time in total.
Re: Converting resource autoplace settings to tile properties / named expressions?
No need for named noise expression shenanigans, this just works:
However, I want to illustrate for you or anyone else reading that changing a noise expression based on a few conditions can be quite simple.
For an easily illustrated example, let's spawn wooden chests on top of all iron patches at positions with x < 10.
In runtime code it would look like this
Using noise expressions instead, it would look like this
This uses that noise.less_than returns 1 or 0, so it can be used for boolean logic and as a multiplier for existing spawning probabilities.
To replace iron ore with wooden chests instead of just placing them on top, invert the condition and apply it to iron ore:
Or with more complicated conditions:
You can apply the same to the richness expression or even make things dependent on the richness itself. Wooden chests spawned instead of iron for richness > 1000:
Code: Select all
/c game.print(serpent.line(game.player.surface.calculate_tile_properties({"entity:crude-oil:probability", "entity:crude-oil:richness"}, {game.player.position})))
However, I want to illustrate for you or anyone else reading that changing a noise expression based on a few conditions can be quite simple.
For an easily illustrated example, let's spawn wooden chests on top of all iron patches at positions with x < 10.
In runtime code it would look like this
Code: Select all
script.on_event(defines.events.on_chunk_generated, function(e)
for _, iron in pairs(e.surface.find_entities_filtered{area=e.area, name="iron-ore"}) do
if iron.position.x < 10 then
iron.surface.create_entity{position = iron.position, name = "wooden-chest"}
end
end
end)
Code: Select all
local noise = require("noise")
local original_iron = util.copy(data.raw.resource["iron-ore"].autoplace.probability_expression)
data.raw.container["wooden-chest"].autoplace =
{
probability_expression = original_iron * noise.less_than(noise.var("x"), 10)
}
To replace iron ore with wooden chests instead of just placing them on top, invert the condition and apply it to iron ore:
Code: Select all
local noise = require("noise")
local original_iron = util.copy(data.raw.resource["iron-ore"].autoplace.probability_expression)
local condition = noise.less_than(noise.var("x"), 10)
data.raw.container["wooden-chest"].autoplace =
{
probability_expression = original_iron * condition
}
data.raw.resource["iron-ore"].autoplace.probability_expression = original_iron * (1 - condition)
Code: Select all
local noise = require("noise")
local original_iron = util.copy(data.raw.resource["iron-ore"].autoplace.probability_expression)
-- temp < 0.5 or aux < 0.6
-- boolean or temp < 0.5 aux < 0.6
local boolean = noise.max(noise.less_than(noise.var("temperature"), 0.5), noise.less_than(noise.var("aux"), 0.6))
data.raw.container["wooden-chest"].autoplace =
{
probability_expression = original_iron * boolean
}
data.raw.resource["iron-ore"].autoplace.probability_expression = original_iron * (1 - boolean)
Code: Select all
local noise = require("noise")
local original_iron = util.copy(data.raw.resource["iron-ore"].autoplace.probability_expression)
-- aux < 0.5 and elevation > 0.6
local foo = noise.less_than(noise.var("aux"), 0.5) * noise.less_or_equal(0.6, noise.var("elevation"))
data.raw.container["wooden-chest"].autoplace =
{
probability_expression = original_iron * foo
}
data.raw.resource["iron-ore"].autoplace.probability_expression = original_iron * (1 - foo)
Code: Select all
local noise = require("noise")
local original_iron = util.copy(data.raw.resource["iron-ore"].autoplace.probability_expression)
local high_richness = noise.less_or_equal(1000, data.raw.resource["iron-ore"].autoplace.richness_expression)
data.raw.container["wooden-chest"].autoplace =
{
probability_expression = original_iron * high_richness
}
data.raw.resource["iron-ore"].autoplace.probability_expression = original_iron * (1 - high_richness)
I'm an admin over at https://wiki.factorio.com. Feel free to contact me if there's anything wrong (or right) with it.
- Deadlock989
- Smart Inserter
- Posts: 2529
- Joined: Fri Nov 06, 2015 7:41 pm
Re: Converting resource autoplace settings to tile properties / named expressions?
That's helpful, thanks.
- Deadlock989
- Smart Inserter
- Posts: 2529
- Joined: Fri Nov 06, 2015 7:41 pm
Re: Converting resource autoplace settings to tile properties / named expressions?
So after some wrangling I've got to a state where I'm getting the desired results in the map generator preview:
But when I start the game, the resources do not spawn, ever.
To get this far I am creating an autoplace control and generating an orphaned autoplace using resource_autoplace:
This would generate something similar to sparse crude oil if assigned directly to a resource. I then have four different resource prototypes; I'm giving them a probability expression multiplied by banded noise expressions as suggested above (properties.low_band and properties.high_band are slices of the range 0-1):
It's frustrating to see them in the preview and then get nothing on chunk generation. Is it some sort of order thing? I'm baffled.
But when I start the game, the resources do not spawn, ever.
To get this far I am creating an autoplace control and generating an orphaned autoplace using resource_autoplace:
Code: Select all
resource_autoplace.initialize_patch_set("fissures", false)
local autoplace_template = resource_autoplace.resource_autoplace_settings {
name = "fissures",
order = "c",
base_density = 8,
base_spots_per_km2 = 12,
random_probability = 1/100,
random_spot_size_minimum = 1,
random_spot_size_maximum = 1,
additional_richness = 0,
has_starting_area_placement = false,
regular_rq_factor_multiplier = 1
}
Code: Select all
local initial_expression = util.copy(autoplace_template.probability_expression)
local v = "moisture"
local condition = noise.less_than(noise.var(v), properties.high_band) * noise.less_or_equal(properties.low_band, noise.var(v))
fissure_prototype.autoplace = properties.autoplace and {
probability_expression = initial_expression * condition
} or nil
Re: Converting resource autoplace settings to tile properties / named expressions?
The last line here overrides the autoplace so that only the probability_expression is set (no order or control or richness_expression or ...).Deadlock989 wrote: ↑Mon Jan 01, 2024 12:16 pm
Code: Select all
local initial_expression = util.copy(autoplace_template.probability_expression) local v = "moisture" local condition = noise.less_than(noise.var(v), properties.high_band) * noise.less_or_equal(properties.low_band, noise.var(v)) fissure_prototype.autoplace = properties.autoplace and { probability_expression = initial_expression * condition } or nil
This means that the richness defaults to the probability, so in this case this is likely a maximum of 1. For some reason, resource with exactly 1 richness aren't spawned in-game, it has to be at least 2. This didn't affect my examples because I used containers instead of resources.
I'm an admin over at https://wiki.factorio.com. Feel free to contact me if there's anything wrong (or right) with it.
- Deadlock989
- Smart Inserter
- Posts: 2529
- Joined: Fri Nov 06, 2015 7:41 pm