Modding:Registry Object JSON Parsing
Registry objects support defining variants in json files. A variant is a copy of the base object with different values substituted into placeholders. Registry objects include items, blocks, and entities.
Enumerating variants
The variantgroups property allows you to define multiple variants of this item. All of them will have their unique pattern, which will be added to the item code.
An easy example would be a bowl, which can either be raw or burned:
code: "bowl",
variantgroups: [
{ code:"type", states: ["raw", "burned"] },
],
Meaning there will be two variants bowl-raw
and bowl-burned
.
It's also possible to define multiple groups.
code: "barrel",
variantgroups: [
{ code:"state", states: ["closed", "opened"] },
{ code:"contents", states: ["empty", "cabbage"] },
],
As a result you will have 2x2 groups, which will be added one after each other: barrel-closed-empty
, barrel-closed-cabbage
, barrel-opened-empty
and barrel-opened-cabbage
.
World properties can be used to define the variant states, instead of listing them directly. For example, block/rock
contains all states of all rock types. This used for gravel
, sand
and rock
. It's a good way to keep everything organized:
variantgroups: [
{ loadFromProperties: "block/rock" },
],
Here is a full list of all world properties and their values.
Furthermore there are other ways of combining groups together. So far we covered the default combination mode, which is Multiply
(the total count of variants is the product of all states). There are two other methods, Add and SelectiveMultiply.
Let's take a look at the Add
combination mode used in this example of a flowerpot block:
code: "flowerpot",
variantgroups: [
{ code: "type", states: ["raw"] },
{ code: "empty", states: ["empty"], combine: "Add" },
{ code: "flower", loadFromProperties: "block/flower", combine: "Add" },
{ code: "mushroom", loadFromProperties: "block/mushroom", combine: "Add" },
{ code: "sapling", loadFromProperties: "block/wood", combine: "Add" },
],
The variants are flowerpot-raw
, flowerpot-empty
, flowerpot-{all flowers}
, flowerpot-{all mushrooms}
and flowerpot-{all saplings}
.
Add
mode could also be called separate, since it defines a variant separate from all the other groups:
code: "thingy",
variantgroups: [
{ code: "something", states: ["same", "different"] },
{ code: "type", states: ["raw", "baked"] },
{ code: "empty", states: ["red", "green"], "combine": "Add" },
],
In this case, the result would be thingy-same-raw
, thingy-same-baked
, thingy-different-raw
, thingy-different-baked
, thingy-red
and thingy-green
The third combination mode "SelectiveMultiply"
which allows you to specify which variant groups you want to combine multiplicatively with. There are many examples of this in the clothing item JSONS, as shown below with this example of a lowerbody asset:
code: "clothes",
variantgroups: [
{ code: "category", states: ["lowerbody"] },
{ code: "lowerbody", combine: "SelectiveMultiply", onVariant: "category", states: [
"aristocrat-leggings", "dirty-linen-trousers", "fine-trousers", "jailor-pants", "lackey-breeches", "merchant-pants", "messenger-trousers", "minstrel-pants", "noble-pants", "prince-breeches", "raindeer-trousers", "raw-hide-trousers", "shepherd-pants", "squire-pants", "steppe-shepherds-trousers", "tattered-peasent-gown", "torn-riding-pants", "warm-woolen-pants", "woolen-leggings", "workmans-gown"
] },
],
The parameter onVariant
specifies which variant group (by using that groups code
) to target and the code
specifies which of the target variant groups states
(only one) to selectively combine with, ignoring all other variants without that specific state.
Using this will result items called clothes-lowerbody-aristocrat-leggings
, clothes-lowerbody-dirty-linen-trousers
, etc.
Filtering out variants
After a list of possible variants is enumerated, the list may be filtered down to exclude some combinations. The skipVariants
property acts like a denylist: variants matching the denylist are filtered out. The default skipVariants
does not filter out any variants. The allowedVariants
acts like an allowlist. Any variant that does not match the allowlist is filtered out. The default allowVariants
allows all variants (acts like *
). skipVariants
is processed before allowedVariants
. So if a resolved variant matches both lists, skipVariants
takes precedence.
Both lists support wildcards. With standard patterns, the wildcard char, "*", matches 0 or more of any character, including "-". Alternatively a regex pattern can be denoted by starting the pattern with "@". The .NET regex flavor is used. Both regex and standard patterns must match the entire resolved collectible code (not just a substring in the middle). Standard patterns act the same as a regex pattern where every "*" is replaced with ".*".
The following example uses allowedVariants
, skipVariants
, and wildcards:
code: "armor",
variantgroups: [
{ code: "bodypart", states: ["head", "body", "legs"] },
{ code: "construction", states: [
"improvised",
"jerkin",
"lamellar",
"sewn",
"brigandine",
"chain",
"scale",
"plate"
]},
{ code: "material", states: [
"wood",
"leather",
"linen",
"copper",
"tinbronze",
"bismuthbronze",
"blackbronze",
"iron",
"steel",
"gold",
"silver"
]},
],
skipVariants: [
"armor-*-brigandine-leather",
"armor-*-brigandine-linen",
"armor-*-brigandine-wood",
"armor-*-brigandine-gold",
"armor-*-brigandine-silver",
"armor-*-chain-leather",
"armor-*-chain-linen",
"armor-*-chain-wood",
"armor-*-scale-leather",
"armor-*-scale-linen",
"armor-*-scale-wood",
"armor-*-scale-gold",
"armor-*-scale-silver",
"armor-*-plate-leather",
"armor-*-plate-linen",
"armor-*-plate-wood",
"armor-*-sewn-wood",
],
allowedVariants: [
"armor-body-improvised-wood",
"armor-body-jerkin-leather",
"armor-legs-jerkin-leather",
"armor-*-lamellar-wood",
"armor-*-lamellar-copper",
"armor-*-lamellar-tinbronze",
"armor-*-lamellar-bismuthbronze",
"armor-*-lamellar-blackbronze",
"armor-*-sewn-linen",
"armor-*-sewn-leather",
"armor-*-scale-*",
"armor-*-chain-*",
"armor-*-plate-*",
"armor-*-brigandine-*",
],
None of the vanilla collectibles use regex patterns for allowedVariants
or skipVariants
. However, the following example uses one for a "ByType" field.
shapeByType: {
"@anvil-(iron|meteoriciron|steel)": {
"base": "block/metal/anvil/iron"
},
"*": {
"base": "block/metal/anvil/normal"
}
}
ByType properties
In order to define properties for specific variants you can add byType to the property name. This allows you to define it depending on the type and always follows the same syntax:
(property)ByType: {
"selector": property,
"selector2": property2,
...
}
If the selector matches the name of the variant the given property will be used. Keep in mind that only the first matching one will be used (everything below will be ignored).
A slab for example has two variants (up, down), which have different collision boxes:
collisionboxByType: {
"*-down": { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.5, z2: 1 },
"*-up": { x1: 0, y1: 0.5, z1: 0, x2: 1, y2: 1, z2: 1 }
},
The char *
stands for anything. In this case it ignores the code of the item.
Furthermore this opens up even more possbilities for more advanced selectors like this one for doors:
*-north-*-opened-left
. This will ignore the second variantgroup. Additionally ByType can also be used for child properties:
collisionboxnbox: {
x1: 0, y1: 0, z1: 0.875, x2: 1, y2: 1, z2: 1,
rotateYByType: {
"*-north-*-opened-left": 90,
"*-north-*-closed-left": 0,
"*-west-*-opened-left": 180,
"*-west-*-closed-left": 90,
"*-east-*-opened-left": 0,
"*-east-*-closed-left": 270,
"*-south-*-opened-left": 270,
"*-south-*-closed-left": 180,
"*-north-*-opened-right": 270,
"*-north-*-closed-right": 0,
"*-west-*-opened-right": 0,
"*-west-*-closed-right": 90,
"*-east-*-opened-right": 180,
"*-east-*-closed-right": 270,
"*-south-*-opened-right": 90,
"*-south-*-closed-right": 180
}
},
ByType properties are handled by RegistryObjectType.solveByType.
Placeholder substitution
Placeholders allow variant values to be substituted inside of strings. They often involve less repetition as compared to using ByType properties.
The primary syntax is "before{variant_group_code}after", where variant_group_code is the name of a variant group. For every resolved variant, variant_group_code is replaced by the value of that variant code within the resolved variant object.
The following json example uses a placeholder to define the texture for multiple variants. In it, the resolved variant axe-copper
will use block/metal/ingot/copper
as its metal
texture.
code: "axe",
variantgroups: [
{ code: "metal", states: ["copper", "tinbronze", "bismuthbronze", "blackbronze", "gold", "silver", "iron" ] },
],
textures: {
"metal": { base: "block/metal/ingot/{metal}" },
"wood": { base: "item/tool/material/wood" }
},
Alternate placeholder names may be given in addition to the main placeholder name. All of the placeholder names are separated by '|' characters in the search expression. The search expression will be replaced by the first placeholder that matches. For example, in "before{version1|version2}after", the variant group name can be either "version1" or "version2".
Alternate placeholder names are a way for a json patch to support multiple versions of the game, even if the variant name changes between versions. For example, let's say a json patch has the string "block/special-{version1}". Then in a new version of the name the variant is renamed from version1 to version2. Now the json patch needs to be updated. It could be updated to "block/special-{version2}", but then the patch would only work with the new version. Instead it could be updated to "block/special-{version1|version2}" so that it works with either version of the game.
Placeholders only work in JSON string values. They do not work in the dictionary keys. For example, substitution DOES NOT WORK in the following snippet, because "{face}" occurs in a dictionary key.
texture: {
"{face}": { base: "block/selected" }
},
Placeholders are handled by RegistryObject.FillPlaceHolder.
Capitalization
As a coding style, the C# properties start with upper case letters, but the json objects that are deserialized into them start with lower case letters. This works becasue Newtonsoft is used for the json deserialization, and it is case insensitive.
Json5
Newtonsoft supports JSON5. The main features are:
- '//' comments out the rest of the line.
- '/*' and '*/' comments out everything in the middle.
- Quotes around property names are optional.
Example:
attributes: {
inContainerTextureByType: {
//"*-raw": { base: "block/wood/barrel/{type}-fish" },
//"*-cured": { base: "block/wood/barrel/{type}-fish" }
},
/* the quotes around shelvable are not required. */
"shelvable": true,
},
Wondering where some links have gone?
The modding navbox is going through some changes! Check out Navigation Box Updates for more info and help finding specific pages.
Modding | |
---|---|
Modding Introduction | Getting Started • Theme Pack |
Content Modding | Content Mods • Developing a Content Mod • Basic Tutorials • Intermediate Tutorials • Advanced Tutorials • Content Mod Concepts |
Code Modding | Code Mods • Setting up your Development Environment |
Property Overview | Item • Entity • Entity Behaviors • Block • Block Behaviors • Block Classes • Block Entities • Block Entity Behaviors • Collectible Behaviors • World properties |
Workflows & Infrastructure | Modding Efficiency Tips • Mod-engine compatibility • Mod Extensibility • VS Engine |
Additional Resources | Community Resources • Modding API Updates • Programming Languages • List of server commands • List of client commands • Client startup parameters • Server startup parameters Example Mods • API Docs • GitHub Repository |