Modding:Variants
Basic Understanding
Vintage Story has a very large number of items, blocks, and entities, and this is helped by having variants of many objects.
Take, for example, the hammer item. There exists 9 hammers in the game and each one has identical functionality, with some minor details changed. Without variants, each hammer would have to be created individually which would result in a lot of time, and a lot of different files.
By using variants, a single hammer item is created, and variant groups are added to it. Most properties in our hammer item file remain the same, however some are changed dependent on the variant of hammer. Specifically, each hammer has identical functionality, however each hammer has a different texture, tooltier, durability, and attack power.
The hammer is referenced throughout this page, so here is the file in its entirety:
itemtypes/tool/hammer.json |
{
code: "hammer",
class: "ItemHammer",
storageFlags: 257,
attributes: {
handbook: {
groupBy: ["hammer-*"]
},
toolrackTransform: {
rotation: { y: 1, z: -1 },
translation: { x: -0.2, y: 0.02 },
scale: 1.5,
},
groundStorageTransform: {
translation: { x: 0, y: -0.16, z: 0.33 },
rotation: { x: 26, y: 88, z: -87 },
scale: 1.01
}
},
behaviors: [{
name: "GroundStorable",
properties: {
layout: 'WallHalves',
wallOffY: 1,
sprintKey: true,
selectionBox: { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.1, z2: 1 },
collisionBox: { x1: 0, y1: 0, z1: 0, x2: 0, y2: 0, z2: 0 },
}
}],
variantgroups: [
{ code: "metal", states: ["copper", "tinbronze", "bismuthbronze", "blackbronze", "gold", "silver",
"iron", "meteoriciron", "steel"
] },
],
tool: "hammer",
damagedby: ["blockbreaking", "attacking"],
heldTpHitAnimation: "smithingwide",
shape: { base: "item/tool/hammer" },
texturesByType: {
"*": {
"metal": { base: "block/metal/ingot/{metal}" },
"wood": { base: "item/tool/material/wood" }
}
},
tooltierbytype: {
"*-copper": 2,
"*-gold": 2,
"*-silver": 2,
"*-bismuthbronze": 3,
"*-tinbronze": 3,
"*-blackbronze": 3,
"*-iron": 4,
"*-meteoriciron": 4,
"*-steel": 5
},
durabilitybytype: {
"hammer-stone": 60,
"hammer-gold": 250,
"hammer-silver": 250,
"hammer-copper": 500,
"hammer-tinbronze": 750,
"hammer-bismuthbronze": 900,
"hammer-blackbronze": 1100,
"hammer-iron": 1800,
"hammer-meteoriciron": 2100,
"hammer-steel": 4500
},
attackpowerbytype: {
"hammer-stone": 1,
"hammer-copper": 1.25,
"hammer-gold": 1.5,
"hammer-silver": 1.5,
"hammer-bismuthbronze": 1.5,
"hammer-tinbronze": 1.75,
"hammer-blackbronze": 2,
"hammer-iron": 2.25,
"hammer-meteoriciron": 2.35,
"hammer-steel": 2.5
},
creativeinventory: { "general": ["*"], "items": ["*"], "tools": ["*"] },
fpHandTransform: {
translation: { x: 0.0468, y: -0.2, z: 0 },
rotation: { x: -33, y: 7, z: 90 },
scale: 2.75
},
guiTransform: {
translation: { x: 0, y: 0, z: 0 },
rotation: { x: -77, y: 46, z: 8 },
origin: { x: 0.59, y: 0.5, z: 0.49 },
scale: 2.6
},
groundTransform: {
translation: { x: 0, y: 0, z: 0 },
rotation: { x: 0, y: 0, z: 0 },
origin: { x: 0.5, y: 0.45, z: 0.5 },
scale: 4.5
},
tpHandTransform: {
translation: { x: -0.65, y: -0.48, z: -0.52 },
rotation: { x: 90, y: 1, z: 0 },
scale: 1
}
}
|
Usage in JSON
Variant groups are extremely useful for content and code modders, so it is important to know exactly how to use them in your assets.
Defining Variant Groups
To create a set of variants, you will need to define at least one variant group, which includes the variant code, and all the states for that variant.
variantgroups: [
{ code: "metal", states: ["copper", "tinbronze", "bismuthbronze", "blackbronze", "gold", "silver", "iron", "meteoriciron", "steel"] }
]
The variant group for the hammer is shown above. The code is metal, and the states are a list of metals. Each state will result in a new object, with the state ID being added onto the end of the item's code. Hammers, for example, all have a code of "hammer-{metal}", where {metal} is replaced with the appropriate state. An object can have any number of variant groups. Take a look at the seashell.json variant groups:
variantgroups: [
{ code: "type", states: ["scallop", "sundial", "turritella", "clam", "conch", "seastar", "volute"] },
{ code: "color", states: ["latte", "plain", "seafoam", "darkpurple", "cinnamon", "turquoise" ] }
]
Seashells have two variant groups: type and color. For each state in type, there exists every state in color, giving a total of 42 different sea shells. Every sea shell in game has a code of "seashell-{type}-{color}". Variant states can also be created from any World Properties:
variantgroups: [
{ code: "rock", loadFromProperties: "block/rock" },
],
SkipVariants
On occasion, a combination of variant groups can cause the creation of unwanted objects. Say, for example, that turquoise clams were unrealistic and shouldn't be in the game. To remove variants, you can use the 'SkipVariants' property. The seashell variants would now look like this:
variantgroups: [
{ code: "type", states: ["scallop", "sundial", "turritella", "clam", "conch", "seastar", "volute"] },
{ code: "color", states: ["latte", "plain", "seafoam", "darkpurple", "cinnamon", "turquoise" ] }
],
skipVariants: [
"seashell-clam-turquoise"
]
Any code within skipVariants will not be created in game.
AllowVariants
Sometimes, you may want to specifically choose which variants should be created in game. For example, you may only want a specific selection of seashells to be created in the game for testing purposes. To only allow specific variants, you can use the 'AllowedVariants' property. The seashell variants would now look like this:
variantgroups: [
{ code: "type", states: ["scallop", "sundial", "turritella", "clam", "conch", "seastar", "volute"] },
{ code: "color", states: ["latte", "plain", "seafoam", "darkpurple", "cinnamon", "turquoise" ] }
],
allowedVariants: [
"seashell-scallop-seafoam",
"seashell-clam-darkpurple",
"seashell-sundial-latte",
"seashell-sundial-seafoam"
]
If allowedVariants exists, any code that is not within it will not be created in game.
It is important to remember that skipVariants takes presedence over allowedVariants - If an element exists in both the allowed and skip list, it will be skipped.
SkipVariants and AllowedVariants both accept wildcard parameters.
Wildcards
When referencing asset locations from a json file, wildcards can be used to reference any number of assets. By using the wildcard character (*) inside an asset location, any matching code with satisfy the wildcard. The use and purpose of a wildcard depends on where it is being used.
Some examples of wildcards are:
- hammer-* - Any hammer will satisfy this condition.
- seashell-scallop-* - Any seashell with the type scallop.
- seashell-*-latte - Any seashell with the color latte.
- *-copper - Any item, block, or entity who's last variant state is copper.
Wildcards are very versatile and are used commonly throughout json files. Wildcards can be used in, but not including:
- SkipVariants and AllowedVariants when creating an object.
- ByType variant groups. Any matching code will use the byType property.
- Recipe ingredients. Any matching code will work as a valid ingredient for the recipe.
{Variant Group} Substitutions
One way to specify slight differences between object variants is by using the variant substitution system. In an asset file that defines variants, any property that accepts a string can include a variant code surrounded by { }'s. This will then get substituted by the variant's state. For example, each seashell chooses its texture based on its color variant, and its shape based on its type variant.
textures: {
color: { base: "block/creature/seashell/{color}" }
}
shape: {
base: "block/seashell/{type}"
}
For example, a plain clam would look for a texture at textures/block/creature/seashell/plain, and would look for a shape at shapes/block/seashell/clam.
ByType Properties
Another way to add differences to variants is by adding 'byType' onto the end of any property in a file that has defined variants.
When using a key with byType, the value becomes a new dictionary, with the keys being asset locations and the values being whatever the original property was.
For example, the following code would set all hammers to have the same durability:
durability: 60,
However, hammers different durabilities based on their variant type. The following code sets different durabilities for each hammer type:
durabilitybytype: {
"hammer-stone": 60,
"hammer-gold": 250,
"hammer-silver": 250,
"hammer-copper": 500,
"hammer-tinbronze": 750,
"hammer-bismuthbronze": 900,
"hammer-blackbronze": 1100,
"hammer-iron": 1800,
"hammer-meteoriciron": 2100,
"hammer-steel": 4500
},
Our durability property has now become durabilitybytype, and accepts a dictionary of all the hammers to each have their own durability. By using a combination of ByType Properties, wildcards, and Variant Substitutions, objects can be created much more efficiently. The following code is an excerpt from the blade.json tool.
shapeByType: {
"blade-falx-*": { base: "item/tool/blade/falx/{metal}" },
"blade-longsword-admin": { base: "item/tool/blade/admin" },
"blade-*-ruined": { base: "item/tool/blade/ruined/{type}" },
"*": { base: "item/tool/blade/{type}" }
},
There's a few important things to note here:
- All JSON properties are case-insensitive in content mods, including the ByType suffix.
- The ByType keys can use wildcards to group variants together by their type.
- The values in ByType can still use variant substitutions.
- A wildcard of "*" will simply satisfy every object.
- ByType keys are analysed from top to bottom - If an object's code satisfies more than one key in byType, it will always use the topmost entry.
Detailed Understanding
Calculating Variant Asset Locations
In Vintage Story, all items, blocks, and entities are loaded as registry objects (GitHub). When a registry object is loaded into the game, before any other properties are read, both its code and variant properties are analysed. There exists 3 json properties to control the creation of variants, which are:
- VariantGroups - An array of all variant groups available for the object.
- SkipVariants - An array of wildcards - Any variants that match these wildcards will never be created.
- AllowedVariants - An array of wildcards - If this property exists, then only variants that match these wildcards will be created, unless they exist in the skip list.
When these arrays are loaded, the game begins to resolve the variant entries by creating unique asset locations for each variant entry. There can be numerous variant groups for any object, so every possible permutation for all the variant states is calculated. This is easy to see if we analyse the seashell.json file.
variantgroups: [
{ code: "type", states: ["scallop", "sundial", "turritella", "clam", "conch", "seastar", "volute"] },
{ code: "color", states: ["latte", "plain", "seafoam", "darkpurple", "cinnamon", "turquoise" ] }
]
Seashells have two variant groups: type and color, meaning that every variant of seashell will follow the code of "seashell-type-color". For every type of seashell, there exists every color of that type. In total, as there are 7 types and 6 colors, this resolves into a total of 42 unique sea shell asset locations.
All permutations are analysed against the skip and allowed arrays, creating a populated list of every allowed code.
Analysing ByType & {VariantCodes}
When all the available variant permutations exist, all json properties are cloned for each resolved variant.
Any property in a json file can be defined as 'byType', except from the code and variant properties. The ByType properties are analysed based on the provided wildcards, and properties that do not match the variant are removed.
After the byType is analysed, any variant groups referenced in curly braces ({ }) are substituted with their appropriate variant state.
When all the variants, byTypes, and variant groups are resolved, the cloned json properties are loaded as a unique object.
Conclusion
Variants are exceptionally useful when creating content for Vintage Story, and it is highly recommended to understand how and why variants are used. To read about more content modding concepts, go to the Content Mod Concepts page.
Content Modding | |||||||||
---|---|---|---|---|---|---|---|---|---|
Basics | Content Mods • Developing a Content Mod • Packaging & Release | ||||||||
Tutorials |
|
||||||||
Concepts | Modding Concepts • Modinfo • Variants • Domains • Patching • Remapping • World Properties | ||||||||
Moddable Assets |
|
||||||||
Uncategorized |
|
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 |