Modding:Registry Object JSON Parsing

From Vintage Story Wiki
Revision as of 06:49, 1 November 2023 by Bluelightning32 (talk | contribs) (Created page with "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: <syntaxhighl...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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 *). Both lists support the wildcard char *. It can be used to target many variants with one string instead of having to write them all down.

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-*",
    ],

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