Modding:Registry Object JSON Parsing

From Vintage Story Wiki

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,
    },


Icon Sign.png

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 ItemEntityEntity BehaviorsBlockBlock BehaviorsBlock ClassesBlock EntitiesBlock Entity BehaviorsCollectible BehaviorsWorld properties
Workflows & Infrastructure Modding Efficiency TipsMod-engine compatibilityMod ExtensibilityVS Engine
Additional Resources Community Resources Modding API Updates Programming Languages List of server commandsList of client commandsClient startup parametersServer startup parameters
Example ModsAPI DocsGitHub Repository