> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rpg-leveling.zuxaw.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Config migrations

# Config migrations

The plugin runs **config migrations** at mod start, before any manual config (ZoneLevelConfig, InstanceLevelConfig, LevelRewardsConfig) is loaded. Two kinds of migrations are supported:

* **JSON content migrations** (default) - read a config file, apply step ops to its JSON, write it back when the file's `Version` is **strictly less than** `MigrateVersionInferiorTo`. A backup of the **pre-migration** content is written to `<filename>.pre-migration` so it is not overwritten by Hytale's Config save (which uses `.bak`).
* **File-system migrations** (`Type: "file"`) - rename / move config files inside the plugin data directory. Idempotent on source presence: if the source path no longer exists, the step is a no-op and re-runs on future startups are safe. No JSON backup is written; the move itself preserves the file.

File migrations always run **before** JSON content migrations, so a JSON migration can target a file at its post-rename path.

## 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)` or relative paths under the plugin data dir. Config file names must match exactly (e.g. `RPGLevelingConfig.json`, `InstanceLevelConfig.json`, `ZoneLevelConfig.json`, `LevelRewardsConfig.json`). File-system migrations may use subfolder paths (e.g. `languages/MessagesLanguageMapping_english.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

```json theme={null}
{
  "0.2.9": ["InstanceLevelConfigMigration.json", "RPGLevelingConfigMigration.json"],
  "0.3.1": ["PassivesConfigMigration.json", "MessagesLanguageMappingMigration.json"],
  "0.3.0": ["PassivesConfigMigration.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:

| Field                      | Description                                                                                                                                                                                                                                                                   |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Type`                     | Optional. `"json"` (default when absent) for JSON content migrations, or `"file"` for file-system migrations.                                                                                                                                                                 |
| `ConfigFileName`           | Required for `json` migrations. Exact config file name (e.g. `InstanceLevelConfig.json`). Not used by `file` migrations.                                                                                                                                                      |
| `MigrateVersionInferiorTo` | Semantic version (e.g. `0.2.9`). For `json` migrations, runs when the config file's `Version` is **strictly less than** this. For `file` migrations, this field is informational (idempotency comes from "source missing → no-op"). Should match the folder name for clarity. |
| `Steps`                    | Array of step objects (see below). Applied in order.                                                                                                                                                                                                                          |

Example:

```json theme={null}
{
  "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
* `"whenCurrentEquals"` (optional) - if present and non-empty, the step runs only when the **current** string at `path` equals this value exactly. Use this to refresh a default message without overwriting customized text (e.g. translations).

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" }`

### `renameKeyInArray`

Renames a key in every **object** in the array at `path`. Skips non-object elements. No-op when `from` is missing on an element.

* `"op": "renameKeyInArray"`
* `"path": "Overrides"` - array of objects
* `"from": "Multiplier"` - old key name
* `"to": "XpRateMultiplier"` - new key name

Example (XPCurveOverridesConfig 0.3.7):

```json theme={null}
{ "op": "renameKeyInArray", "path": "Overrides", "from": "Multiplier", "to": "XpRateMultiplier" }
```

### `move` (file migrations only)

Moves a file inside the plugin data directory. Covers both rename (same parent) and move-into-subfolder (different parent) in a single step. Parent directories of `to` are created on demand. Idempotent: when `from` does not exist the step is a no-op; when `to` already exists the step is skipped with a warning so existing files are never overwritten.

* `"op": "move"`
* `"from": "MessagesLanguageMapping.json"` - source path relative to plugin data dir
* `"to": "languages/MessagesLanguageMapping_english.json"` - target path relative to plugin data dir

Example file migration (rename + move into a folder):

```json theme={null}
{
  "Type": "file",
  "MigrateVersionInferiorTo": "0.3.5",
  "Steps": [
    {
      "op": "move",
      "from": "MessagesLanguageMapping.json",
      "to": "languages/MessagesLanguageMapping_english.json"
    }
  ]
}
```

### `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"`:

```json theme={null}
{
  "op": "removeArrayElements",
  "path": "Instances",
  "arrayMatch": { "Id": "Default" }
}
```

After applying, the config’s `Version` is set to `0.2.9`.
