Modding:JSON Patching: Difference between revisions
m (→AddEach operation: Fixing incorrect markup syntax) |
m (Changing all "json" syntax highlighting to "yaml") |
||
Line 21: | Line 21: | ||
<!--T:7--> | <!--T:7--> | ||
Now that the mod files and folders are set up, it is time to make the first patch. Start by making a .json and name it however you'd like. Open the file with your favorite text editor and fill it in with the following. | Now that the mod files and folders are set up, it is time to make the first patch. Start by making a .json and name it however you'd like. Open the file with your favorite text editor and fill it in with the following. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
[ | [ | ||
{ file: "", op: "", path: "", value: } | { file: "", op: "", path: "", value: } | ||
Line 38: | Line 38: | ||
<!--T:10--> | <!--T:10--> | ||
For the file put, | For the file put, | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
file: "game:entities/land/wolf-male" | file: "game:entities/land/wolf-male" | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 51: | Line 51: | ||
<!--T:13--> | <!--T:13--> | ||
Next put, | Next put, | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
op: "replace" | op: "replace" | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 58: | Line 58: | ||
<!--T:14--> | <!--T:14--> | ||
Now for the path, | Now for the path, | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
path: "/server/behaviors/5/aitasks/0/damage" | path: "/server/behaviors/5/aitasks/0/damage" | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 89: | Line 89: | ||
<!--T:19--> | <!--T:19--> | ||
We can now move on to the last part, value | We can now move on to the last part, value | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
value: 6 | value: 6 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 98: | Line 98: | ||
<!--T:21--> | <!--T:21--> | ||
We will now move on to a more complex example using the same file. This time we'll add a drop to the male wolf. For this, you can make a new file or put a comma after the first patch and put the new patch on the next line. | We will now move on to a more complex example using the same file. This time we'll add a drop to the male wolf. For this, you can make a new file or put a comma after the first patch and put the new patch on the next line. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
[ | [ | ||
{ file: "game:entities/land/wolf-male", op: "replace", path: "/server/behaviors/9/aitasks/0/damage", value: 6}, | { file: "game:entities/land/wolf-male", op: "replace", path: "/server/behaviors/9/aitasks/0/damage", value: 6}, | ||
Line 120: | Line 120: | ||
<!--T:25--> | <!--T:25--> | ||
Sometimes you'll want to disable a json entirely. This might be to disable a vanilla recipe for example. For this example I'll be disabling wolves because I don't want to upload another file and hopefully you are quite familiar with the file by this time. All jsons have various labels that the game looks for even if they are not there. In this case the file doesn't have the enabled label so we'll be adding it and setting it to false like so. | Sometimes you'll want to disable a json entirely. This might be to disable a vanilla recipe for example. For this example I'll be disabling wolves because I don't want to upload another file and hopefully you are quite familiar with the file by this time. All jsons have various labels that the game looks for even if they are not there. In this case the file doesn't have the enabled label so we'll be adding it and setting it to false like so. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
[ | [ | ||
{ file: "game:entities/land/wolf-male", op: "add", path: "/enabled", value: "false"} | { file: "game:entities/land/wolf-male", op: "add", path: "/enabled", value: "false"} | ||
Line 129: | Line 129: | ||
=== Targeting server or client assets === | === Targeting server or client assets === | ||
If you know that a target JSON file is only applied on the server or client, you can use the attribute "side" with the appropriate value to avoid unnecessary processing and the accompanying warnings in log files such as "''Hint: This asset is usually only loaded Server side''". For example, a cooking recipe will only be loaded by the server and you could therefore add the attribute : | If you know that a target JSON file is only applied on the server or client, you can use the attribute "side" with the appropriate value to avoid unnecessary processing and the accompanying warnings in log files such as "''Hint: This asset is usually only loaded Server side''". For example, a cooking recipe will only be loaded by the server and you could therefore add the attribute : | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
"side": "server" | "side": "server" | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Line 158: | Line 158: | ||
For example, prior to version 1.19.4, the fat item did not have a behaviors array. Let's say a mod added the behaviors through a patch (notice the path does not end in '''dash -'''): | For example, prior to version 1.19.4, the fat item did not have a behaviors array. Let's say a mod added the behaviors through a patch (notice the path does not end in '''dash -'''): | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Bad patch example | |||
[ | [ | ||
{ | { | ||
Line 171: | Line 171: | ||
However, in version 1.19.4 the vanilla fat object was changed to include a behaviors array. | However, in version 1.19.4 the vanilla fat object was changed to include a behaviors array. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Section of fat.json from 1.19.4 | |||
behaviors: [ | behaviors: [ | ||
{ name: "GroundStorable", properties: { layout: 'Quadrants', collisionBox: { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.125, z2: 1 }, scale: 0.3 } } | { name: "GroundStorable", properties: { layout: 'Quadrants', collisionBox: { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.125, z2: 1 }, scale: 0.3 } } | ||
Line 179: | Line 179: | ||
So if the old patch was applied on the new game version, then it would replace the vanilla behavior instead of adding a new behavior. | So if the old patch was applied on the new game version, then it would replace the vanilla behavior instead of adding a new behavior. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Result of bad patch on 1.19.4 | |||
behaviors: [{ "name": "SealPlacedCrock" }], | behaviors: [{ "name": "SealPlacedCrock" }], | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Whereas, instead the patch could use the "addmerge" operation (putting aside that the "addmerge" operation didn't exist in 1.19.3). | Whereas, instead the patch could use the "addmerge" operation (putting aside that the "addmerge" operation didn't exist in 1.19.3). | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Good patch example | |||
[ | [ | ||
{ | { | ||
Line 198: | Line 198: | ||
If the target array exists (which it does in this example), then addmerge will append the patch array to the target array instead of replacing it. | If the target array exists (which it does in this example), then addmerge will append the patch array to the target array instead of replacing it. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Result of good patch on 1.19.4 | |||
behaviors: [ | behaviors: [ | ||
{ name: "GroundStorable", properties: { layout: 'Quadrants', collisionBox: { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.125, z2: 1 }, scale: 0.3 } } | { name: "GroundStorable", properties: { layout: 'Quadrants', collisionBox: { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.125, z2: 1 }, scale: 0.3 } } | ||
Line 214: | Line 214: | ||
"addeach" is used to insert multiple entries at some index in an array. As example, let's say a mod wanted to insert two new behaviors before the AnimationAuthoritative behavior in the hammer tool. This is the initial value before the patch: | "addeach" is used to insert multiple entries at some index in an array. As example, let's say a mod wanted to insert two new behaviors before the AnimationAuthoritative behavior in the hammer tool. This is the initial value before the patch: | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Section of hammer.json | |||
behaviors: [{ | behaviors: [{ | ||
name: "GroundStorable", | name: "GroundStorable", | ||
Line 229: | Line 229: | ||
The two new behaviors could be inserted using two "add" or "addmerge" patches. | The two new behaviors could be inserted using two "add" or "addmerge" patches. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Example patch using "add" | |||
[ | [ | ||
# Insert new behaviors in the hammer item before the existing AnimationAuthoritative behavior. | |||
{ | { | ||
side: "server", | side: "server", | ||
Line 251: | Line 251: | ||
Or to be more concise, "addeach" could be used to insert both entries in the array with one operation. Note that when using "addeach", the entries to insert are in standard (non-reversed) order. | Or to be more concise, "addeach" could be used to insert both entries in the array with one operation. Note that when using "addeach", the entries to insert are in standard (non-reversed) order. | ||
<syntaxhighlight lang=" | <syntaxhighlight lang="yaml"> | ||
# Example patch using "add" | |||
[ | [ | ||
# Insert new behaviors in the hammer item before the existing AnimationAuthoritative behavior. | |||
{ | { | ||
side: "server", | side: "server", |
Revision as of 19:36, 9 May 2024
This page was last verified for Vintage Story version 1.19.4.
Vintage Story comes with a powerful patching system that lets you perform pinpoint modifications on Assets in JSON format. This significantly improves mod interoperability as you don't have to fully replace these files and overwrite other mod changes in the process. Furthermore you can change multiple files at once.
Below is a basic guide to help you understand how patches are working. You can use the ModMaker 3000™ to start writing a patch. It is a a command line tool that ships with the game itself. If you modify vanilla assets, you can run ModMaker 3000™ to create a mod consisting of a set of patches based on the changes you made to the assets. You can then undo or reinstall the game to get your original files back. Note that before packaging the output of the ModMaker 3000™ in a mod, some edits should be made as described later in this document.
The patch syntax is based on rfc6902. However, in addition to what's described in the RFC, it also has "addmerge" and "addeach" operations. The source code for the json patcher is JsonPatchLoader.cs and the customized Tavis.JsonPatch.
The basics
The first thing you need to do is to set up a generic mod as outlined on the wiki. That information can be found on the mod packaging page. In that example the domain is the name of your mod. Patching is considered a content mod but if you have a .dll or .cs in your mod it is a code mod. Patches go in assets/domain/patches.
Now that the mod files and folders are set up, it is time to make the first patch. Start by making a .json and name it however you'd like. Open the file with your favorite text editor and fill it in with the following.
[
{ file: "", op: "", path: "", value: }
]
- File is where the file you want to change is located.
- Op stands for operation and it's what you want to do. Valid operations are add, addeach, remove, replace, move and copy.
- Path is where the value you want to change in the file is located.
- Value is what you want to put in the file.
Now let's give a few examples, using the wolf-male.json file. Note that specific line numbers etc. below might not match your own wolf-male.json file if you're using a different version of the game. In this first example, we'll modify wolf damage.
For the file put,
file: "game:entities/land/wolf-male"
The game: is the domain. In this case, it's the domain for vanilla VS. Note that this is not the same thing as the mod folder; all three of the vanilla folders (survival, game, and creative) all use the domain "game".
You can change the domain to modify other people's mods, as well.
If you look in the vanilla game folders, under assets you'll find the file we are modifying under entities/land/wolf-male. That is why that part comes after the domain.
Next put,
op: "replace"
Since wolves already deal damage, we simply want to replace the damage value.
Now for the path,
path: "/server/behaviors/5/aitasks/0/damage"
This is the truly tricky part. In these JSON files, you'll find labels such as server: and behaviors:. You'll know it's a label because it's followed by the colon. Colon :
Next, we have arrays. These start and end in square brackets. Square brackets [ ]
Within these square brackets, we have sections. These start and end in curly braces. Curly braces { }
These sections don't have a label so they must be referenced by number. To figure out the number you must count starting with 0.
Now to break down the example. We see the value we want to modify (damage) on line 119. Trace it back to the first label, which is server. From there the next label is behaviors. Here we notice the square brackets, so we must count -- in this case to 9.
{ code: "repulseagents"
... is section 0{ code: "controlledphysics"
... is section 1{ code: "despawn"
... is section 2{ code: "health"
... is section 3{ code: "deaddecay"
... is section 4{ code: "floatupwhenstuck"
... is section 5{ code: "harvestable"
... is section 6{ code: "breathe"
... is section 7{ code: "emotionstates"
... is section 8{ code: "taskai"
... is section 9
Next is the label aitasks, then we hit more square brackets. The count for this is 0 because that's the number you start on. Finally we get to the label damage.
We can now move on to the last part, value
value: 6
Since the value is a number we just put a number. If it were text, the value would be "6".
Advanced patching
We will now move on to a more complex example using the same file. This time we'll add a drop to the male wolf. For this, you can make a new file or put a comma after the first patch and put the new patch on the next line.
[
{ file: "game:entities/land/wolf-male", op: "replace", path: "/server/behaviors/9/aitasks/0/damage", value: 6},
{ file: "game:entities/land/wolf-male", op: "add", path: "/drops/-", value: {
type: "item",
code: "stick",
quantity: { avg: 2, var: 1 }
} }
]
In this second example, we'll skip straight to the path. You'll notice the label drops and then a dash. Dash -
This dash means append to the end of the array. This is useful because it prevents us from overwriting another drop and we may not know the number of drops if other mods added drops as well.
For the value, there is an entire section showing that the value doesn't just have to be a number or text. In this case, we added the stick to the drops but by specifying a domain in front of stick like we do for the file it could be a stick from any mod!
Disabling Assets
Sometimes you'll want to disable a json entirely. This might be to disable a vanilla recipe for example. For this example I'll be disabling wolves because I don't want to upload another file and hopefully you are quite familiar with the file by this time. All jsons have various labels that the game looks for even if they are not there. In this case the file doesn't have the enabled label so we'll be adding it and setting it to false like so.
[
{ file: "game:entities/land/wolf-male", op: "add", path: "/enabled", value: "false"}
]
That's all you have to do. If there's already an enabled label in the file, then the "add" operation will replace it.
Targeting server or client assets
If you know that a target JSON file is only applied on the server or client, you can use the attribute "side" with the appropriate value to avoid unnecessary processing and the accompanying warnings in log files such as "Hint: This asset is usually only loaded Server side". For example, a cooking recipe will only be loaded by the server and you could therefore add the attribute :
"side": "server"
so the client doesn't try to patch anything.
File Path and Name Considerations
By default, when you generate a mod using ModMaker 3000™, the folder structure will use game as the domain, and use the same file name as the vanilla JSON file. This can lead to mod conflicts.
Let's use the wolf examples from above. If you run ModMaker after making these changes to wolf-male.json, the resulting ZIP will contain the file assets\game\patches\survival-entities-land-wolf-male.json. This seems to suggest two conventions that VS expects; that you should put JSON patches under the game folder, and that you should match the source file name using a folder-hyphen notation. Neither are true, however.
Say you split up the two different wolf changes into two different mods. The first is WolvesAreWimps and the second is WolvesDropSticks. ModMaker would generate identical folder structures and files for these two different mods; that is, both would contain the file assets\game\patches\survival-entities-land-wolf-male.json. These two mods will not work together because they have a filename conflict. (The one that is alphabetically last will take precedence: WolvesDropSticks.)
To avoid the possibility of mod conflicts, do not use the game folder. Instead, use a folder with the same name as your modid; that is, your mod's domain. So in this example, WolvesAreWimps.zip should instead have the folders assets\wolvesarewimps\patches and WolvesDropSticks.zip should instead have the folders assets\wolvesdropsticks\patches. File names are also arbitrary, you do not need to use survival-entities-land-wolf-male.json and can name the file anything you want, since its contents direct the game on what and how to patch, not the file's name.
AddMerge operation
In addition to the "add" operation, the "addmerge" operation was added in version 1.19.4. "addmerge" is safer, and should be generally be used instead of "add.
They behave the same in many conditions:
- For targets that do not exist, both create the target
- For paths that end with dash -, both will append a new entry to the target array
- For paths that end with a number, both will insert the value into the target array at that location
- For paths that point to an existing primitive value (string, bool, or int), both will replace the value
However, they differ when the target is an existing array or object. "add" will replace the target with the patch value. "addmerge" will append the patch value if the target is an array. "addmerge" will merge the value if the target is an object.
For example, prior to version 1.19.4, the fat item did not have a behaviors array. Let's say a mod added the behaviors through a patch (notice the path does not end in dash -):
# Bad patch example
[
{
"op": "add",
"path": "/behaviors",
"value": [{ "name": "SealPlacedCrock" }],
"file": "game:itemtypes/resource/fat.json"
}
]
However, in version 1.19.4 the vanilla fat object was changed to include a behaviors array.
# Section of fat.json from 1.19.4
behaviors: [
{ name: "GroundStorable", properties: { layout: 'Quadrants', collisionBox: { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.125, z2: 1 }, scale: 0.3 } }
],
So if the old patch was applied on the new game version, then it would replace the vanilla behavior instead of adding a new behavior.
# Result of bad patch on 1.19.4
behaviors: [{ "name": "SealPlacedCrock" }],
Whereas, instead the patch could use the "addmerge" operation (putting aside that the "addmerge" operation didn't exist in 1.19.3).
# Good patch example
[
{
"op": "addmerge",
"path": "/behaviors",
"value": [{ "name": "SealPlacedCrock" }],
"file": "game:itemtypes/resource/fat.json"
}
]
If the target array exists (which it does in this example), then addmerge will append the patch array to the target array instead of replacing it.
# Result of good patch on 1.19.4
behaviors: [
{ name: "GroundStorable", properties: { layout: 'Quadrants', collisionBox: { x1: 0, y1: 0, z1: 0, x2: 1, y2: 0.125, z2: 1 }, scale: 0.3 } }
],
behaviors: [{ "name": "SealPlacedCrock" }],
Note that when the path ends with a number, "addmerge" will not merge the patch value into the existing array entry at that index. It will instead insert the patch value as a new entry at that index.
Note : see the Compatibility page for other ways to handle conflicts and dependencies between mods.
AddEach operation
"addeach" is used to insert multiple entries at some index in an array. As example, let's say a mod wanted to insert two new behaviors before the AnimationAuthoritative behavior in the hammer tool. This is the initial value before the patch:
# Section of hammer.json
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 },
}
}, { name: "AnimationAuthoritative" }],
The two new behaviors could be inserted using two "add" or "addmerge" patches.
# Example patch using "add"
[
# Insert new behaviors in the hammer item before the existing AnimationAuthoritative behavior.
{
side: "server",
file: "game:itemtypes/tool/hammer", op: "add", path: "/behaviors/1",
value: {
name: "NewBehavior2"
}
},
{
side: "server",
file: "game:itemtypes/tool/hammer", op: "add", path: "/behaviors/1",
value: {
name: "NewBehavior1"
}
},
]
Or to be more concise, "addeach" could be used to insert both entries in the array with one operation. Note that when using "addeach", the entries to insert are in standard (non-reversed) order.
# Example patch using "add"
[
# Insert new behaviors in the hammer item before the existing AnimationAuthoritative behavior.
{
side: "server",
file: "game:itemtypes/tool/hammer", op: "addeach", path: "/behaviors/1",
value: [
{
name: "NewBehavior1"
},
{
name: "NewBehavior2"
}
]
},
]
"addeach" only works when the patch value is an array and the target is an array. Otherwise it will thrown an exception.
At this point, you have all the tools for modifying assets using the patching system. This is an incredibly powerful tool that is only limited by your imagination. Have fun patching!
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 |