Mod: 制作一个自定义材料

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

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

模组页面

## Getting started

To make a custom material and even reactions between materials, like acid eating through ground, create an XML file with the same structure as the original materials file `data/materials.xml`, though it does not have to be named materials.xml, for instance:

```
<Materials>

 <CellData
 name="my\_custom\_material"
 ui\_name="$mat\_my\_custom\_material"
 wang\_color="ff223344"
 >
 </CellData>

 <CellDataChild
 \_parent="my\_custom\_material"
 name="my\_custom\_material2"
 ui\_name="$mat\_my\_custom\_material2"
 wang\_color="ff223345"
 >
 </CellDataChild>

 <Reaction probability="1"
 input\_cell1="my\_custom\_material" input\_cell2="air"
 output\_cell1="my\_custom\_material2" output\_cell2="air"
 >
 </Reaction>

</Materials>

```

Then, in your mod's `init.lua` you need to tell the game to add your custom material(s) like so:

```
ModMaterialsFileAdd("mods/your\_mod/path\_to\_your\_materials\_file.xml")

```

This will add two new materials and a reaction that turns the first material into the second when it comes in contact with air.
Because we did not specify any properties it will use the defaults instead, which in this case means they are liquids with the same color as specified in `wang_color`.
That's the minimum amount of work that's required to add a new material. It's boring but it gives you a starting point from which you can start experimenting.

Add translation information as well in init.lua. Otherwise it will not show the name when hovering the cursor over the new material, though you can still spawn it, and when put in flasks will display "empty flask" instead of the specified name.

```
local content = ModTextFileGetContent("data/translations/common.csv")
ModTextFileSetContent("data/translations/common.csv", content .. [[
mat\_my\_custom\_material, Custom Name,
mat\_my\_custom\_material2, Custom Name 2,
]])

```

## Basics

Now that we know how to get our custom materials into the game, let's first learn about a few important things. First of all, the `name` of each material needs to be unique, if you define a new material with an already existing name it will overwrite the previously defined material with the same name. The name is not shown ingame so go ahead and prefix it with something, for instance `coolmaterialmod_superpoison` instead of just `superpoison`, make sure the prefix has a low chance of ever being used by anyone else.

Next up, there are two ways to define a material, either as a completely new material in which case you would use `<CellData>`, or by basing it off an existing material by using `<CellDataChild>`, when you use the latter, you need to specify a material to inherit from using the property `_parent`, if you don't, the game will crash. The same goes for specifying a `_parent` on `<CellData>`, only `<CellDataChild>` can **and must** have a `_parent`. This allows you to basically make a copy of an existing material with just slightly different properties.

The next important thing is the `wang_color`, which also needs to be unique and is how the game knows what materials to place in the world when loading wang tiles or pixel scenes. So when there is a big blob of the color `ffff6000` (the format being hexadecimal ARGB, alpha, red, green, blue), it will know to place lava there, because lava has `wang_color="ffff6000"`. When you define a new material with the same wang color as an existing one, that new material will get placed instead wherever that wang color is used. So make sure to use something unique. But... how? The only way is to pick one randomly and then simply use your text editor to search for that value to check if it already exists or not. The chances of picking an already existing one randomly are relatively small though, because with 255 different values for red, green and blue, there are a total of 16.581.375 different combinations! And that's even without taking alpha into account.

Making a new material based on an already existing one is a fun and easy way to experiment. You have two options, copying an existing material entirely, or simply extending it by inheriting from it.
As an example, let's make a liquid version of sand:

```
<CellDataChild
 \_parent="sand"
 name="mymod\_liquid\_sand"
 ui\_name="Liquid sand"
 wang\_color="ff0b4115"
 liquid\_sand="0"
 >
</CellDataChild>

```

Here we inherited from the material `sand` by using `<CellDataChild>` and specifying `_parent="sand"`, gave it a custom `name`, `wang_color` and then the important part, we override the parents property `liquid_sand="1"` by specifying our own: `liquid_sand="0"`.

At this point you should be able to make any material you like by looking at similar existing materials and creating a modified version of them.

## Adding graphics and texture

When no graphics are specified, the `wang_color` with full opacity will be used as a graphic instead. That's not good so let's define our own by adding it as a child element of our material:

```
<CellDataChild
 \_parent="sand"
 name="mymod\_liquid\_sand"
 ui\_name="Liquid sand"
 wang\_color="ff0b4115"
 liquid\_sand="0"
 >
 <Graphics color="8000ff00">
 </Graphics>
</CellDataChild>

```

This will make the material translucent green, if we want to use a texture instead we would do this:

```
<Graphics texture\_file="data/materials\_gfx/lavarock.png">
</Graphics>

```

Now our material would use a different texture. It's also possible to fine tune the graphics by adding textures for the edges for all kinds of orientations, but that is beyond the scope of this article.

## Material types

The type of a material is determined by multiple properties, the first and most defining one being `cell_type`, which can be one of 4 different values: `solid, liquid, fire, gas`. They are confusing because you might think that ground is solid, right? WRONG! It's static liquid sand!... What? So what exactly is solid then? Solids are mostly physics objects like boxes, ice, glass, etc and will be simulated using the Box2D physics system. If you make your entire world out of a solid material, it's going to lag like hell because it will constantly collide with itself. So instead you would use static sand, that is sand that floats in the air and can be stood upon, that's basically what every "ground" in noita is.

Sand itself however is not it's own type, but simply a subtype of liquid. A combination of `cell_type="liquid"` and `liquid_sand="1"`.
Most types of materials are a combination of different properties, just check out a similar material as to what you want to make in the default materials in order to find out how to make it.

As for properties, usually all properties starting with `liquid_` only apply when `cell_type="liquid"`. All properties starting with `solid_` to `cell_type="solid"` and so on. If you specify a inapplicable one it will simply have no effect.
Explanations for most properties can be found at the bottom of this article.

## Stain and ingestion effects

Materials can apply different effects like wet, slimed, oiled etc when entities get stained with that material, or apply ingestion effects when eaten. Only liquids can apply stains, although sand can too, just not very reliably since the material needs to be on top of the to-be-stained entity.
To define those effects, add them as a child element, just like `<Graphics>`:

```
<CellDataChild
 \_parent="sand"
 name="mymod\_liquid\_sand"
 ui\_name="Liquid sand"
 wang\_color="ff0b4115"
 liquid\_sand="0"
 liquid\_stains="2"
 >
 <StatusEffects>
 <Stains>
 <StatusEffect type="SLIMY" />
 </Stains>
 <Ingestion>
 <StatusEffect type="FOOD\_POISONING" amount="0.2" />
 </Ingestion>
 </StatusEffects>
</CellDataChild>

```

To make your material stain, you also need to set `liquid_stains="2"`.

## Reactions

Reactions define how materials interact with each other, for example lava melting metals, acid eating through ground, steam condensating into water, or draught of midas turning everything to gold.
This is how you define a reaction:

```
<Reaction probability="1"
 input\_cell1="my\_custom\_material" input\_cell2="air"
 output\_cell1="my\_custom\_material2" output\_cell2="air"
 >
</Reaction>

```

The probability determines how fast the reaction happens, input cell 1 and 2 are the materials required for the reaction to happen and output cell 1 and 2 what they turn into respectively. This one would slowly turn `my_custom_material` into `my_custom_material2` when it comes in contact with air.
It's also possible to define a reaction that happens when a reaction does **not** meet the requirement, for that you can use `<ReqReaction>`.

Have you noticed that you can add tags to materials? This makes the reaction system very flexible. Instead of specifying reactions for every material, you can also specify a tag inside a reaction, which means it will react with **any** material using that tag. For instance:

```
<Reaction probability="20"
 input\_cell1="[meltable]" input\_cell2="[lava]"
 output\_cell1="[meltable]\_molten" output\_cell2="[lava]"
 >
</Reaction>

```

This is a reaction that applies a reaction to every material with a `[meltable]` tag, that on contact with a material that has a `[lava]` tag turns it into a material with the same name but `_molten` appended. For instance,`wax` has the `[meltable]` tag, so the reaction would turn it into `wax_molten`. Don't forget that for this to work, a material with `name="wax_molten"` needs to exist.

There is a whole lot more to reactions that is best to be explored by studying the `data/materials.xml` as it would go well beyond the scope of this article.

## Making materials deal damage

How much damage a materials deals is not a property of the material, but rather a property of the `DamageModelComponent` of the to-be-damaged entity. That means if you want a material to deal contact damage to the player, you would have to get the player entity, get it's `DamageModelComponent` and change the `materials_that_damage` and `materials_how_much_damage` properties to include your new material. The properties are both a comma seperated string. Example:

`materials_that_damage="acid,lava,magic_gas_hp_regeneration"`

`materials_how_much_damage="0.005,0.003,-0.005"`

This would cause acid to deal 0.005 damage, lava 0.003 and magic\_gas\_hp\_regeneration would heal 0.005 because it's a negative number.
The numbers are damage per particle, per tick, divided by 25. So if you want something to deal 0.1 damage per particle, you have to set the damage to 0.004.
However, because modifying a comma seperated string is clunky and changes to `materials_that_damage` and `materials_how_much_damage` don't take immediate effect, you can use these helper functions:

```
local function stringsplit(inputstr, sep)
  sep = sep or "%s"
  local t = {}
  for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
    table.insert(t, str)
  end
  return t
end
-- Returns a key-value table, where they keys are the material name and the values the damage.
function get\_materials\_that\_damage(entity\_id)
  local out = {}
  local damage\_model\_component = EntityGetFirstComponentIncludingDisabled(entity\_id, "DamageModelComponent")
  if damage\_model\_component then
    local materials\_that\_damage = ComponentGetValue2(damage\_model\_component, "materials\_that\_damage")
    materials\_that\_damage = stringsplit(materials\_that\_damage, ",")
    local materials\_how\_much\_damage = ComponentGetValue2(damage\_model\_component, "materials\_how\_much\_damage")
    materials\_how\_much\_damage = stringsplit(materials\_how\_much\_damage, ",")
    for i, v in ipairs(materials\_that\_damage) do
      out[v] = materials\_how\_much\_damage[i]
    end
    return out
  end
end
-- <materials> should be a key-value table with the keys being the name of the material to change and the value the new damage.
-- For instance: change\_materials\_that\_damage(entity\_id, { lava = 0, new\_material = 0.004 }) would make the entity immune to lava
-- and the material with name="new\_material" would deal 0.004 damage.
function change\_materials\_that\_damage(entity\_id, materials)
  -- At the time of writing (1st of September 2020) changes to DamageModelComponent:materials\_that\_damage
  -- do not take effect. One of the ways to work around that is to remove and re-add the component with
  -- the changes applied and the same old values for everything else
  local damage\_model\_component = EntityGetFirstComponentIncludingDisabled(entity\_id, "DamageModelComponent")
  if damage\_model\_component then
    -- Retrieve and store all old values of the DamageModelComponent
    local old\_values = {}
    local old\_damage\_multipliers = {}
    for k,v in pairs(ComponentGetMembers(damage\_model\_component)) do
      -- ComponentGetMembers does not return the value for ragdoll\_fx\_forced, ComponentGetValue2 is necessary
      if k == "ragdoll\_fx\_forced" then
        v = ComponentGetValue2(damage\_model\_component, k)
      end
      old\_values[k] = v
    end
    for k,\_ in pairs(ComponentObjectGetMembers(damage\_model\_component, "damage\_multipliers")) do
      old\_damage\_multipliers[k] = ComponentObjectGetValue(damage\_model\_component, "damage\_multipliers", k)
    end
    -- Build comma separated string
    old\_values.materials\_that\_damage = ""
    old\_values.materials\_how\_much\_damage = ""
    local old\_materials\_that\_damage = get\_materials\_that\_damage(entity\_id)
    for material, damage in pairs(materials) do
      old\_materials\_that\_damage[material] = damage
    end
    for material, damage in pairs(old\_materials\_that\_damage) do
      local comma = old\_values.materials\_that\_damage == "" and "" or ","
      old\_values.materials\_that\_damage = old\_values.materials\_that\_damage .. comma .. material
      old\_values.materials\_how\_much\_damage = old\_values.materials\_how\_much\_damage .. comma .. damage
    end
    EntityRemoveComponent(entity\_id, damage\_model\_component)
    damage\_model\_component = EntityAddComponent(entity\_id, "DamageModelComponent", old\_values)
    ComponentSetValue2(damage\_model\_component, "ragdoll\_fx\_forced", old\_values.ragdoll\_fx\_forced)
    for k, v in pairs(old\_damage\_multipliers) do
      ComponentObjectSetValue(damage\_model\_component, "damage\_multipliers", k, v)
    end
  end
end

```

Then use them like this in your `init.lua` right after your player spawns:

```
function OnPlayerSpawned(player\_entity)
  change\_materials\_that\_damage(player\_entity, { your\_new\_material = 0.004 })
end

```

Making enemies take damage from a certain material is much harder since it requires modification of every enemies `DamageModelComponent` individually and therefore not explained in this article at this time.

## List of all known material properties

Lastly here is a list that was compiled through experimentation and analyzing the `data/materials.xml` file.

### Vegetation

 supports\_collapsible\_structures | [0, 1] | ???