Skip to main content

Config migrations

The plugin runs JSON config migrations at mod start, before any manual config (ZoneLevelConfig, InstanceLevelConfig, LevelRewardsConfig) is loaded. If a config file’s Version is strictly less than a migration’s MigrateVersionInferiorTo, the migration steps are applied and the file is saved. A backup of the pre-migration content is written to <filename>.pre-migration (e.g. RPGLevelingConfig.json.pre-migration) so it is not overwritten by Hytale’s Config save (which uses .bak).

When migrations run

  • When: During plugin startup, after ConfigManager is created and before ZoneLevelConfig.initialize(), InstanceLevelConfig.initialize(), and LevelRewardsConfig.initialize().
  • Which files: Config files resolved via ConfigManager.getConfigFile(fileName). Config file names must match exactly (e.g. RPGLevelingConfig.json, InstanceLevelConfig.json, ZoneLevelConfig.json, LevelRewardsConfig.json).

Folder layout

  • Index: src/main/resources/migrations/index.json — lists version folders and their migration filenames.
  • Per-version: src/main/resources/migrations/<version>/ — e.g. migrations/0.2.9/. Inside each version folder, place one JSON file per config migration (e.g. InstanceLevelConfigMigration.json).
  • Discovery: The runner loads the index, then for each version and each filename loads migrations/<version>/<filename> from the classpath. You must add each new migration file to the index.

Index format

{
  "0.2.9": ["InstanceLevelConfigMigration.json"],
  "0.3.0": ["LevelRewardsConfigMigration.json", "ZoneLevelConfigMigration.json"]
}
Keys are version strings; values are arrays of migration file names in that folder.

Per-migration file format

Each file under migrations/<version>/<name>.json has:
FieldDescription
ConfigFileNameExact config file name (e.g. InstanceLevelConfig.json). Must match a name used with ConfigManager.getConfigFile(fileName).
MigrateVersionInferiorToSemantic version (e.g. 0.2.9). The migration runs when the config file’s Version is strictly less than this. Should match the folder name for clarity.
StepsArray of step objects (see below). Applied in order.
Example:
{
  "ConfigFileName": "InstanceLevelConfig.json",
  "MigrateVersionInferiorTo": "0.2.9",
  "Steps": [
    {
      "op": "removeArrayElements",
      "path": "Instances",
      "arrayMatch": { "Id": "Default" }
    }
  ]
}

Step operations

Paths use dot-separated segments. Each segment is either a key (object member) or a numeric index (array element). This allows arbitrary depth (e.g. Rewards.2.Items.0.ItemId).

set

Sets a value at the given path. Creates parent objects if missing; for arrays the index must already exist.
  • "op": "set"
  • "path": "Version" — top-level key
  • "value": "0.2.9" — string, number, or boolean
Examples:
  • "path": "Version", "value": "0.2.9"
  • "path": "Instances.0.LevelMin", "value": 5
  • "path": "Rewards.2.Items.0.Quantity", "value": 10

remove

Removes a key or an array element at the given path.
  • "op": "remove"
  • "path": "SomeKey" — remove top-level key
  • "path": "Instances.0" — remove first element of Instances
  • "path": "Rewards.1.Items.2" — remove nested array element

removeArrayElements

Removes all elements in the array at path that match every key-value in arrayMatch. The path can point to an array at any depth.
  • "op": "removeArrayElements"
  • "path": "Instances" — array to filter
  • "arrayMatch": { "Id": "Default" } — object; each array element that has all these keys with these values is removed
Example for a nested array:
  • "path": "Rewards.0.Items", "arrayMatch": { "ItemId": "OldItem" }

appendToCommaSeparated

Appends values to a comma-separated string at the given path without overwriting. Reads the current string, splits by comma, adds any values from value that are not already present, then writes back (joined with comma, no spaces). If the key is missing, treats current as empty and adds all values.
  • "op": "appendToCommaSeparated"
  • "path": "BlacklistedEntityRoles" — path to the string
  • "value": ["Pet_Follower", "Pet_Follower_Large", "Pet_Follower_Large_Combat"] — string or array of strings to add
Example: existing "Citizen_" becomes "Citizen_,Pet_Follower,Pet_Follower_Large,Pet_Follower_Large_Combat"; existing "Citizen_,MyRole" becomes "Citizen_,MyRole,Pet_Follower,Pet_Follower_Large,Pet_Follower_Large_Combat".

Version format

Versions are compared as semantic versions (e.g. 0.2.9, 1.0). Comparison is strictly less than: the migration runs only when config Version < MigrateVersionInferiorTo. After applying a migration, the config’s Version is set to that migration’s MigrateVersionInferiorTo so it is not applied again.

Config file names

Use the exact names from ConfigManager:
  • RPGLevelingConfig.json
  • InstanceLevelConfig.json
  • ZoneLevelConfig.json
  • LevelRewardsConfig.json
  • MessagesLanguageMapping.json

Example: remove Default instance

  • Config: InstanceLevelConfig.json
  • File: migrations/0.2.9/InstanceLevelConfigMigration.json
  • Index: "0.2.9": ["InstanceLevelConfigMigration.json"] in migrations/index.json
  • Condition: Migrate when Version < 0.2.9
  • Step: Remove from Instances any element with "Id": "Default":
{
  "op": "removeArrayElements",
  "path": "Instances",
  "arrayMatch": { "Id": "Default" }
}
After applying, the config’s Version is set to 0.2.9.