Mod: 制作一个自定义生物群系

此页面的内容需要被翻译。

你可以帮助我们来翻译此页面。

模组页面

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:

```
<Biome
 biome\_filename="data/biome/coalmine.xml"
 height\_index="1"
 color="ffd57917"
></Biome>

```
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.

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:

```
dofile\_once("data/scripts/biome\_scripts.lua")

```
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
end

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

### Problems

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.xml`s and `wang_template_file`s
* 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
  dofile\_once("mods/MyCoolMod/biome.lua")
end

```
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")
end
-- 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)
end
if ModIsEnabled("OtherMod") then
  BiomeMapSetPixel(40, 14, 0xfffedcba)
end

```
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)
    BiomeMapLoad\_KeepPlayer("your\_map\_loading\_script.lua")
  end
}, {
  \_\_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.

https://drive.google.com/open?id=1exlxZ\_9WFXGm53i4d1Arx4dD4oxtOerH

# 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.

Example of a **Pixel Scene** image:

*You can spawn your scene with T in devmode.*

### 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.