Contents

If you want to remove some item, it is rarely as straightforward as “remove the item json and call it a day”. Everything that is stored in save file (like furniture, monsters or items, but not item or monstergroups) may cause harmless, but annoying errors when thing is removed. This document describe how to properly obsolete most types of json we have in the game.

Migration is used, when we want to remove one item by replacing it with another item, that do exist in the game, or to maintain a consistent list of item type ids. After migration, the item can be safely removed - remember to remove all mentions of entity elsewhere

Migration and obsoletion should happen in \data\json\obsoletion_and_migration_<current_stable_version> folder except for mod migration which is handled in \data\core\

Item migration

Any of item types (AMMO, GUN, ARMOR, PET_ARMOR, TOOL, TOOLMOD, TOOL_ARMOR, BOOK, COMESTIBLE, ENGINE, WHEEL, GUNMOD, MAGAZINE, GENERIC, BIONIC_ITEM) should be migrated to another item, then it can be removed with no issues AMMO and COMESTIBLE types require additional handling, explained in Charge and temperature removal

{
  "type": "MIGRATION", // type, mandatory
  "id": "arrowhead",  // id of item, that you want to migrate, mandatory
  "from_variant": "scar_l", // if used, only specific variant of this item would be removed instead of an item
  "replace": "steel_chunk", // item, that replace the removed item, mandatory
  "variant": "m1014", // variant of an item, that would be used to replace the item. only for items, that do use variants
  "flags": [ "DIAMOND" ], // additional flag, that item would have when replaced
  "charges": 1930, // amount of charges, that replaced item would have
  "contents": [ { "id": "dogfood", "count": 1 } ], // if `replace` is container, describes what would be inside of it
  "sealed": false, // if `replace` is container, will it be sealed or not
  "reset_item_vars": true // if original item has variables stored inside, and this field is true, this variables won't be migrated to the next item
}

Charge and temperature removal

If an item that used to have charges (e.g. AMMO or COMESTIBLE types) is changed to another type that does not use charges, migration is needed to ensure correct behavior when loading from existing save files, and prevent spurious error messages from being shown to the player. Migration lists for this are found in charge_removal.json.

Such items may be added to one of the following:

blacklist_charge_migration.json a charge_migration_blacklist list: Items in existing save files with n charges will be converted to n items with no charges. This will preserve item count.

blacklist_charge_removal.json a charge_removal_blacklist list

  • charge_removal_blacklist: items will simply have charges removed.

Additionally, COMESTIBLE items have temperature and rot processing, and are thus set as always activated. When an item is changed from COMESTIBLE to a different type, migration is needed to check and unset this if applicable:

blacklist_temperature_removal.json a temperature_removal_blacklist list:

  • In most cases, the item has no other features that require it to remain activated, in which case it can be simply added to temperature_removal_blacklist. Items in this list will be deactivated and have temperature-related data cleared without any further checks performed.
  • In case of an item that may be active for additional reasons other than temperature/rot tracking, an instance of the item loaded from existing save file cannot be blindly deactivated – additional checks are required to see if it should remain active. Instead of adding to the above list, a separate special case should be added in src/savegame_json.cpp to implement the necessary item-specific deactivation logic.

Vehicle migration

Vehicles do not need any migration, simple deletion is enough

Vehicle part migration

Migrating vehicle parts is done using vehicle_part_migration type, in the example below - when loading the vehicle any part with id from will have it’s id switched to to. For VEH_TOOLS parts only - add_veh_tools is a list of itype_ids to add to the vehicle tools after migrating the part. Vehicle part can be removed safely afterwards

  {
    "type": "vehicle_part_migration",
    "//": "migration to VEH_TOOLS, remove after 0.H release",
    "from": "afs_metal_rig",
    "to": "veh_tools_workshop",
    "add_veh_tools": [ "welder", "soldering_iron", "forge", "kiln" ]
  }

Effect migration

For effects, effect_migration type should be used.

  {
    "type": "effect_migration",
    "from": "yrax_overcharged",   // effect that needs to be migrated
    "to": "drunk"                 // old effect will be replaced with this one. Can be omitted, in which case effect will be deleted
  }
  {
    "type": "effect_migration",
    "from": "foobar_effect",
  }

Bionic migration

For bionics, you should use bionic_migration type. The migration happens when character is loaded; if to is null or is not defined, the bionic is removed, if to is not null the id will be changed to the provided value.

  {
    "type": "bionic_migration",
    "from": "bio_tools_extend",
    "to": "bio_pitch_perfect"
  },
  {
    "type": "bionic_migration",
    "from": "bio_tools_extend"
  }

Trait migration

A mutation migration can be used to migrate a mutation that formerly existed gracefully into a proficiency, another mutation (potentially a specific variant), or to simply remove it without any fuss.

  {
    "type": "TRAIT_MIGRATION",      // Mandatory. String. Must be "TRAIT_MIGRATION"
    "id": "hair_red_mohawk",        // Mandatory. String. Id of the trait that has been removed.
    "trait": "hair_mohawk",         // Optional*. String. Id of the trait this trait is being migrated to.
    "variant": "red",               // Optional. String. Can only be specified if trait is specified. Id of a variant of trait that this mutation will be set to.
    "proficiency": "prof_aaa",      // Optional*. String. Id of proficiency that will replace this trait.
    "remove": true                  // Optional*. Boolean. If neither trait or variant are specified, this must be true. 
                                    // *One of these three must be specified.
  },
  {
    "type": "TRAIT_MIGRATION",
    "id": "dead_trait1",
    "trait": "new_trait",
    "variant": "correct_variant"
  },
  {
    "type": "TRAIT_MIGRATION",
    "id": "trait_now_prof",
    "proficiency": "prof_old_trait"
  },
  {
    "type": "TRAIT_MIGRATION",
    "id": "deleted_trait",
    "remove": true
  }

Proficiency migration

Prof migration can be migrated to new prof (with progress being transferred) or to null

  {
    "type": "proficiency_migration",
    "from": "prof_wp_slime_basic",  // Mandatory. Id of the prof that has been removed.
    "to": "prof_wp_mi-go_basic"     // Optional. Id of the new prof that will be set instead. can be omitted to remove the prof
  },
  {
    "type": "proficiency_migration",
    "from": "prof_wp_slime_advanced"
  }

Spell migration

The only spells that require migration are one that are actual spells that character/player learned, so this section is more applicable to magic mods, and not applicable to, for example, a monster spells (monsters do not store it anywhere, just picking it up fron definition when needed)

  {
    "type": "spell_migration",
    "from": "spell_foo",  // Mandatory. Id of the spell that has been removed.
    "to": "spell_bar"     // Optional. Id of the new spell that will be set instead. Can be omitted to remove the spell completely. If character already has this spell learned, it will not be modified
  },
  {
    "type": "spell_migration",
    "from": "spell_bar",
  },

Monster migration

Monster can be removed without migration, the game replace all critters without matched monster id with mon_breather when loaded

Recipe migration

Recipes can be removed without migration, the game will simply delete the recipe when loaded

Terrain and furniture migration

Terrain and furniture migration replace the provided id as submaps are loaded. You can use f_null with to_furn to remove furniture entirely without creating errors, however from_ter must specify a non null to_ter.

{
    "type": "ter_furn_migration",   // Mandatory. String. Must be "ter_furn_migration"
    "from_ter": "t_old_ter",        // Optional*. String. Id of the terrain to replace.
    "from_furn": "f_old_furn",      // Optional*. String. Id of the furniture to replace.
    "to_ter": "t_new_ter",          // Mandatory if from_ter specified, optional otherwise. String. Id of new terrain to place. Overwrites existing ter when used with from_furn.
    "to_furn": "f_new_furn",        // Mandatory if from_furn specified, optional otherwise. String. Id of new furniture to place.
                                    // *Exactly one of these two must be specified.
},

Examples

Replace a fence that bashes into t_dirt into a furniture that can be used seamlessly over all terrains

  {
    "type": "ter_furn_migration",
    "from_ter": "t_fence_dirt",
    "to_ter": "t_dirt",
    "to_furn": "f_fence"
  }

Move multiple ids that don’t need to be unique any more to a single id

  {
    "type": "ter_furn_migration",
    "from_furn": "f_underbrush_harvested_spring",
    "to_furn": "f_underbrush_harvested"
  }

  {
    "type": "ter_furn_migration",
    "from_furn": "f_underbrush_harvested_winter",
    "to_furn": "f_underbrush_harvested"
  }

Trap migration

Trap migration replaces the provided id as submaps are loaded. You can use tr_null with to_trap to remove the trap entirely without creating errors.

{
    "type": "trap_migration",   // Mandatory. String. Must be "trap_migration"
    "from_trap": "tr_old_trap",        // Mandatory. String. Id of the trap to replace.
    "to_trap": "tr_new_trap",      // Mandatory. String. Id of the new trap to place.
},

Examples

Migrate an id

  {
    "type": "trap_migration",
    "from_trap": "tr_being_migrated_id",
    "to_trap": "tr_new_id"
  }

Errorlessly obsolete an id

  {
    "type": "trap_migration",
    "from_trap": "tr_being_obsoleted_id",
    "to_trap": "tr_null"
  }

Field migration

Field migration replaces the provided id as submaps are loaded. You can use fd_null with to_field to remove the field entirely without creating errors.

{
    "type": "field_type_migration",   // Mandatory. String. Must be "field_type_migration"
    "from_field": "fd_old_field",        // Mandatory. String. Id of the field to replace.
    "to_field": "fd_new_field",      // Mandatory. String. Id of the new field to place.
},

Examples

Migrate an id

  {
    "type": "field_type_migration",
    "from_field": "fd_being_migrated_id",
    "to_field": "fd_new_id"
  }

Errorlessly obsolete an id

  {
    "type": "field_type_migration",
    "from_field": "fd_being_obsoleted_id",
    "to_field": "fd_null"
  }

Overmap terrain migration

Overmap terrain migration replaces the location, if it’s not generated, and replaces the entry shown on your map even if it’s already generated. If you need the map to be removed without alternative, use omt_obsolete. Mods can override replacement ids by specifying different new ids or cancel them entirely by making the new id the same as the old one.

  {
    "type": "oter_id_migration",
    "oter_ids": {
      "old_overmap_terrain_id": "new_overmap_terrain_id",
      "hive": "omt_obsolete",
      "hive2": "omt_obsolete"
    }
  }

Overmap specials migration

Overmap special can be either migrated to another overmap special, or just removed, using overmap_special_migration

  {
    "type": "overmap_special_migration",
    "id": "Farm with silo",
  },
  {
    "type": "overmap_special_migration",
    "id": "FakeSpecial_cs_open_sewer",
    "new_id": "cs_open_sewer",
  },

Dialogue / EoC variable migration

For EOC/dialogue variables you can use var_migration. This currently only migrates Character, and Global vars. If “to” isn’t provided the variable will be migrated to a key of “NULL_VALUE”.

{
    "type": "var_migration",
    "from": "temp_var",
    "to": "new_temp_var"
},
{
    "type": "var_migration",
    "from": "temp_var"
}

Activity Migration

See if it is mentioned in src/savegame_legacy.cpp.

In src/savegame_json.cpp in player_activity::deserialize( const JsonObject &data ) add to std::set<std::string> obs_activities the activity ID with comment to remove it after the next version (after 0.B when in 0.A experimental). There should always be at least one example left.

Ammo types

Ammo types don’t need an infrastructure to be obsoleted, but it is required to remove all items that use this ammo type

Spells

Spells have no infrastructure to migrate them, just copy the entire json of obsoleted spell to the obsoletion folder. Additionally one can make EoC, that makes the level of the spell to be -1, and make the spell run this EoC, removing it in such way

city_building

city_building have no infrastructure to migrate them, just copy the entire json of obsoleted spell to the obsoletion folder (not sure one is needed?)

Item groups

Don’t need any migration

Monster groups

Don’t need any migration

body_part migration

body_part have no infrastructure to migrate them, just copy the entire json of body_part to the obsoletion folder

Mods

Obsoletion

To obsolete a mod, you need to add an "obsolete": true, boolean into MOD_INFO, which prevents the mod from showing in the mod list when creating a new world but won’t affect existing worlds.

When declaring a mod obsolete, consider also adding its directory to the lang/update_pot.sh file via the -D argument to extract_json_strings.py so it’s no longer exposed to translation.

echo "> Extracting strings from JSON"
if ! lang/extract_json_strings.py \
        -i data \
        ...
+       -D data/mods/YOUR_DEPRECATED_MOD \
        -n "$package $version" \
        -r lang/po/gui.pot \
        -o lang/po/json.pot

Example

  {
    "type": "MOD_INFO",
    "id": "military_professions",
    "name": "Military Profession Pack",
    "description": "Numerous military themed professions",
    "category": "rebalance",
    "dependencies": [ "dda" ],
    "obsolete": true
  }

Migration

Mod migrations should be located in the \data\core\ folder and enable a no longer existing id to be silently migrated to a new id when loading a world where the old mod is present.

  {
    "type": "mod_migration",
    "//": "Migrated in 0.X experimental meaning this can be removed after 0.X stable",
    "id": "old_mod_id",
    "new_id": "new_mod_id"
  }

Removal

Mod removals should be located in the \data\core\ folder and must include a message that will appear when querying the player what to do when loading a world where the old mod is present.

Example

  {
    "type": "mod_migration",
    "//": "Removed in 0.X experimental meaning this can be removed after 0.X stable",
    "id": "old_mod_id",
    "removal_message": "Removed because it hasn't been maintained in a decade."
  }

The above would lead to this (translated) query:

Mod old_mod_id has been removed with reason: Removed because it hasn't been maintained in a decade.
Remove it from this world's modlist?