Modding:JSON Patching: Difference between revisions

From Vintage Story Wiki
Normalizing all snippets to use shorthand property names, to avoid confusion; fixing JSON error in example
(Clarify that add can replace existing values)
(Normalizing all snippets to use shorthand property names, to avoid confusion; fixing JSON error in example)
 
(7 intermediate revisions by 3 users not shown)
Line 1: Line 1:
<languages/><translate>
<languages/><translate>
<!--T:1-->
<!--T:1-->
{{GameVersion|1.19.4-rc.1}}
{{GameVersion|1.19.4}}


<!--T:2-->
<!--T:2-->
Line 10: Line 10:


<!--T:4-->
<!--T:4-->
Below is a basic guide to help you understand how patches are working. A [https://tools.ietf.org/html/rfc6902 full reference doc] is also available however. To make your patching live easier in the long term you can also use the ModMaker 3000™, which is 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.
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 [https://tools.ietf.org/html/rfc6902 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 [https://github.com/anegostudios/vsessentialsmod/blob/master/Loading/JsonPatchLoader.cs JsonPatchLoader.cs] and the customized [https://github.com/anegostudios/Tavis.JsonPatch/tree/0fc44b73c8412c561506745b1944fc18e88c1d83 Tavis.JsonPatch].


=== The basics === <!--T:5-->
=== The basics === <!--T:5-->
Line 19: 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="json">
<syntaxhighlight lang="yaml">
[
[
{ file: "", op: "", path: "", value: }
{ file: "", op: "", path: "", value: }
Line 36: Line 38:
<!--T:10-->
<!--T:10-->
For the file put,  
For the file put,  
<syntaxhighlight lang="json">
<syntaxhighlight lang="yaml">
file: "game:entities/land/wolf-male"
file: "game:entities/land/wolf-male"
</syntaxhighlight>
</syntaxhighlight>
Line 49: Line 51:
<!--T:13-->
<!--T:13-->
Next put,
Next put,
<syntaxhighlight lang="json">
<syntaxhighlight lang="yaml">
op: "replace"
op: "replace"
</syntaxhighlight>
</syntaxhighlight>
Line 56: Line 58:
<!--T:14-->
<!--T:14-->
Now for the path,
Now for the path,
<syntaxhighlight lang="json">
<syntaxhighlight lang="yaml">
path: "/server/behaviors/5/aitasks/0/damage"
path: "/server/behaviors/5/aitasks/0/damage"
</syntaxhighlight>
</syntaxhighlight>
Line 87: 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="json">
<syntaxhighlight lang="yaml">
value: 6
value: 6
</syntaxhighlight>
</syntaxhighlight>
Line 96: 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="json">
<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 118: 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="json">
<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 127: 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="json">
<syntaxhighlight lang="yaml">
"side": "server"
side: "server"
</syntaxhighlight>
</syntaxhighlight>
so the client doesn't try to patch anything.
so the client doesn't try to patch anything.
Line 156: 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="json">
<syntaxhighlight lang="yaml">
# Bad patch example
[
[
   {
   {
     "op": "add",
     op: "add",
     "path": "/behaviors",
     path: "/behaviors",
     "value": [{ "name": "SealPlacedCrock" }],
     value: [{ name: "SealPlacedCrock" }],
     "file": "game:itemtypes/resource/fat.json"
     file: "game:itemtypes/resource/fat.json"
   }
   }
]
]
</syntaxhighlight>
</syntaxhighlight>


However, in version 1.19.4 the vanilla fat object was changed to include a behaviors array. 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. Whereas, if the patch used the "addmerge" operation (putting aside that the "addmerge" operation didn't exist in 1.19.3), then it would add instead of replace the behavior in 1.19.4.
However, in version 1.19.4 the vanilla fat object was changed to include a behaviors array.
<syntaxhighlight lang="json">
<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 } }
         ],
         ],
</syntaxhighlight>
</syntaxhighlight>
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="yaml">
# Result of bad patch on 1.19.4
        behaviors: [{ name: "SealPlacedCrock" }],
</syntaxhighlight>
Whereas, instead the patch could use the "addmerge" operation (putting aside that the "addmerge" operation didn't exist in 1.19.3).
<syntaxhighlight lang="yaml">
# Good patch example
[
  {
    op: "addmerge",
    path: "/behaviors",
    value: [{ name: "SealPlacedCrock" }],
    file: "game:itemtypes/resource/fat.json"
  }
]
</syntaxhighlight>
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="yaml">
# 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 } },
                { name: "SealPlacedCrock" }
        ],
</syntaxhighlight>
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.


<!--T:34-->
<!--T:34-->
'''Note''' : see the [[Modding:CompatibilityLib|Compatibility]] page for other ways to handle conflicts and dependencies between mods.
'''Note''' : see the [[Modding:CompatibilityLib|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:
<syntaxhighlight lang="yaml">
    # 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" }],
</syntaxhighlight>
The two new behaviors could be inserted using two "add" or "addmerge" patches.
<syntaxhighlight lang="yaml">
# 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"
    }
  },
]
</syntaxhighlight>
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="yaml">
# 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"
      }
    ]
  },
]
</syntaxhighlight>
"addeach" only works when the patch value is an array and the target is an array. Otherwise it will thrown an exception.


<!--T:26-->
<!--T:26-->
Line 181: Line 276:


<!--T:27-->
<!--T:27-->
{{Navbox/modding|Vintage Story}}
{{Navbox/contentmodding}}
</translate>
</translate>
Confirmedusers
7

edits