Skip to content

JSON Conventions

Tokens work everywhere

Because these are ordinary Content Patcher patches, tokens work in every entry key and value:

{
  "Id": "{{ModId}}_RiverClearing",
  "Description": "{{i18n: riverClearing.description}}",
  "TravelTimeInHours": "{{TravelTime}}"
}

{{i18n:}} lets you translate DisplayName and Description from your pack's i18n folder. Setting this up early is recommended.

{{Random: {{Range: 20, 40}} }} produces a random value at load (the example pack uses it for a visitor's DaysRequiredBetweenVisits and for shop stock and prices).

Positions: Vector2

Properties typed Vector2 (PlayerSpawnTile, TileOffset, PreviewOffset, ShadowOffset, EntranceTile, Tile) accept an object (see tiles vs pixels for which unit each uses):

{ "PlayerSpawnTile": { "X": 12, "Y": 18 } }

A string shorthand also parses:

{ "PlayerSpawnTile": "12, 18" }

Both work. The object form is more verbose but harder to get wrong and it's what the examples on this site use throughout.

Areas: Rectangle

Properties typed Rectangle (DisplayRectangle, BoundaryRectangle, ShadowRectangle, SourceRectangle, FromArea, ToArea) take four values: the top-left corner, then the size.

{ "DisplayRectangle": { "X": 0, "Y": 0, "Width": 32, "Height": 48 } }

Width/Height are a size, not a second corner

A 32×48 sprite starting at (0, 0) is Width: 32, Height: 48 and not X: 32, Y: 48.

Tiles versus pixels

This is the distinction that causes the most confusion, so here it is explicitly:

Kind of property Unit Examples
Tile positions Tiles (1 tile = 16 px) PlayerSpawnTile, GuestSpawnTile, Tile
Sprite areas Pixels on your texture DisplayRectangle, BoundaryRectangle, ShadowRectangle, SourceRectangle
Pixel offsets Pixels EntranceTile, TileOffset, ShadowOffset, PreviewOffset
Sizes Tiles MaxTentTileSize

A tent whose DisplayRectangle is 48×32 pixels occupies 3×2 tiles. The framework derives tile footprint by dividing the display rectangle by 16. See GetTileSize.

Optional properties

Any property marked optional in these docs can simply be left out of your JSON.

Please note:

  • Omitted is not the same as zero. SpecificYear omitted means "any year". SpecificYear: 0 is an invalid year and will fail validation.
  • Omitted is not the same as empty. For VisitorData, omitting PreferredDays means "any day". Providing [] means the list exists but matches nothing.
  • Defaults are applied by the model, if not by you. PreviewTextureScale is 4 if you don't set it.

Enums

Enum-typed properties take the member name as a string and are case-sensitive unless the framework says otherwise:

{
  "Direction": "North",
  "RequiredSpot": "SW",
  "PatchMode": "Overlay"
}

See the Enums reference for every valid value.

Days of the week

VisitorData.PreferredDays is a list of DayOfWeek values, so the week starts on Sunday:

{ "PreferredDays": [ "Saturday", "Sunday" ] }

Valid names: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday.

Seasons

DateModel.Season takes a vanilla Season value: Spring, Summer, Fall, Winter.

Asset paths are asset names

TexturePath, PreviewTexturePath, GrayscaleTexturePath and MapPath take game asset names, not file paths into your pack:

{ "TexturePath": "Mods/{{ModId}}/Tents" }     // correct
{ "TexturePath": "assets/tents.png" }         // wrong

Load your file into an asset name first. See Textures and maps.

Game State Queries

UnlockCondition and RequirementsCondition are vanilla Game State Queries (the same syntax Content Patcher and the game's own data files use).

{ "UnlockCondition": "PLAYER_HAS_MAIL Current guildMember" }
{ "UnlockCondition": "SEASON summer, PLAYER_FRIENDSHIP_POINTS Current Abigail 1250" }

A comma-separated list is an AND statement (every condition must pass). Leaving the property empty or omitting it entirely means "always true".

Buffs

CampfireFoodData.Buffs and CampingTentData.RestingBuffs are lists of BuffData, each naming a vanilla or custom buff Id (matching a key in the Data/Buffs asset) with an optional Duration:

{ "Buffs": [ { "Id": "food_2" }, { "Id": "drink_1", "Duration": 300000 } ] }

Duration is in milliseconds. Omit it (or use 0) to inherit the tent's default duration, or use -2 for a buff that lasts the whole day. An Id the game doesn't recognise won't be caught by validation. It fails the moment the buff is applied, so test your meals in-game.