Wrangling resource autoplace / noise expressions?

Place to get help with not working mods / modding interface.
User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 2529
Joined: Fri Nov 06, 2015 7:41 pm

Wrangling resource autoplace / noise expressions?

Post by Deadlock989 »

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?
Last edited by Deadlock989 on Mon Jan 01, 2024 12:37 pm, edited 1 time in total.
Bilka
Factorio Staff
Factorio Staff
Posts: 3309
Joined: Sat Aug 13, 2016 9:20 am
Contact:

Re: Converting resource autoplace settings to tile properties / named expressions?

Post by Bilka »

No need for named noise expression shenanigans, this just works:

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)
Using noise expressions instead, it would look like this

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)
}
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:

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)
Or with more complicated conditions:

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)
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

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.
User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 2529
Joined: Fri Nov 06, 2015 7:41 pm

Re: Converting resource autoplace settings to tile properties / named expressions?

Post by Deadlock989 »

That's helpful, thanks.
User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 2529
Joined: Fri Nov 06, 2015 7:41 pm

Re: Converting resource autoplace settings to tile properties / named expressions?

Post by Deadlock989 »

So after some wrangling I've got to a state where I'm getting the desired results in the map generator preview:

Untitled.jpg
Untitled.jpg (49.44 KiB) Viewed 1003 times

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
}
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):

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
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.
Bilka
Factorio Staff
Factorio Staff
Posts: 3309
Joined: Sat Aug 13, 2016 9:20 am
Contact:

Re: Converting resource autoplace settings to tile properties / named expressions?

Post by Bilka »

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
The last line here overrides the autoplace so that only the probability_expression is set (no order or control or richness_expression or ...).

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.
User avatar
Deadlock989
Smart Inserter
Smart Inserter
Posts: 2529
Joined: Fri Nov 06, 2015 7:41 pm

Re: Converting resource autoplace settings to tile properties / named expressions?

Post by Deadlock989 »

Bilka wrote: Mon Jan 01, 2024 12:52 pmFor 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.
Ouch. That's ... a thing, apparently.

Thanks, I got there in the end.
Post Reply

Return to “Modding help”