Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic JSON ter/furn migration #72771

Merged
merged 15 commits into from
Apr 14, 2024
10 changes: 10 additions & 0 deletions data/mods/TEST_DATA/furniture.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@
"required_str": 8,
"hacksaw": { "duration": "80 minutes" }
},
{
"type": "furniture",
"id": "test_f_migration_new_id",
"name": "Furniture Test Migration",
"description": "Id to test migration from it's invalid counterparts test_t_migration_old_id and test_f_migration_old_id to.",
"symbol": "f",
"color": "blue",
"move_cost_mod": 1,
"required_str": 8
},
{
"type": "furniture",
"id": "test_f_oxytorch1",
Expand Down
13 changes: 13 additions & 0 deletions data/mods/TEST_DATA/ter_furn_migration.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"type": "ter_furn_migration",
"from_ter": "test_t_migration_old_id",
"to_ter": "test_t_migration_new_id",
"to_furn": "test_f_migration_new_id"
},
{
"type": "ter_furn_migration",
"from_furn": "test_f_migration_old_id",
"to_furn": "test_f_migration_new_id"
}
]
10 changes: 10 additions & 0 deletions data/mods/TEST_DATA/terrain.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@
},
"prying": { "result": "t_grass", "duration": "30 seconds", "prying_data": { "difficulty": 100, "breakable": true } }
},
{
"type": "terrain",
"id": "test_t_migration_new_id",
"name": "Terrain Test Migration",
"description": "Id to test migration from it's invalid counterpart test_t_migration_old_id to.",
"symbol": "t",
"color": "blue",
"move_cost": 1,
"bash": { "sound": "thump", "ter_set": "t_null", "str_min": 50, "str_max": 100, "str_min_supported": 100, "bash_below": true }
},
{
"type": "terrain",
"id": "test_t_bash_persist",
Expand Down
50 changes: 48 additions & 2 deletions doc/OBSOLETION_AND_MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Migration and obsoletion should happen in `\data\json\obsoletion_and_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](#Charge_and_temperature_removal)

```C++
```json
{
"type": "MIGRATION", // type, mandatory
"id": "arrowhead", // id of item, that you want to migrate, mandatory
Expand Down Expand Up @@ -78,7 +78,7 @@ For bionics, you should use `bionic_migration` type. The migration happens when

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.

```c++
```json
{
"type": "TRAIT_MIGRATION", // Mandatory. String. Must be "TRAIT_MIGRATION"
"id": "hair_red_mohawk", // Mandatory. String. Id of the trait that has been removed.
Expand Down Expand Up @@ -114,6 +114,52 @@ Monster can be removed without migration, the game replace all critters without

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`.

```json
{
"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

```json
{
"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

```json
{
"type": "ter_furn_migration",
"from_furn": "f_underbrush_harvested_spring",
"to_furn": "f_underbrush_harvested"
}
```
...
```json
{
"type": "ter_furn_migration",
"from_furn": "f_underbrush_harvested_winter",
"to_furn": "f_underbrush_harvested"
}
```

# 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`
Expand Down
3 changes: 3 additions & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ void DynamicDataLoader::initialize()
add( "mutation", &mutation_branch::load_trait );
add( "furniture", &load_furniture );
add( "terrain", &load_terrain );
add( "ter_furn_migration", &ter_furn_migrations::load );
add( "monstergroup", &MonsterGroupManager::LoadMonsterGroup );
add( "MONSTER_BLACKLIST", &MonsterGroupManager::LoadMonsterBlacklist );
add( "MONSTER_WHITELIST", &MonsterGroupManager::LoadMonsterWhitelist );
Expand Down Expand Up @@ -647,6 +648,7 @@ void DynamicDataLoader::unload_data()
SNIPPET.clear_snippets();
spell_type::reset_all();
start_locations::reset();
ter_furn_migrations::reset();
ter_furn_transform::reset();
trap::reset();
unload_talk_topics();
Expand Down Expand Up @@ -831,6 +833,7 @@ void DynamicDataLoader::check_consistency( loading_ui &ui )
{ _( "Mutations" ), &mutation_branch::check_consistency },
{ _( "Mutation categories" ), &mutation_category_trait::check_consistency },
{ _( "Region settings" ), check_region_settings },
{ _( "Terrain/Furniture migrations" ), &ter_furn_migrations::check },
{ _( "Overmap land use codes" ), &overmap_land_use_codes::check_consistency },
{ _( "Overmap connections" ), &overmap_connections::check_consistency },
{ _( "Overmap terrain" ), &overmap_terrains::check_consistency },
Expand Down
13 changes: 13 additions & 0 deletions src/mapdata.h
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,19 @@ void load_terrain( const JsonObject &jo, const std::string &src );
void verify_furniture();
void verify_terrain();

class ter_furn_migrations
{
public:
/** Handler for loading "ter_furn_migrations" type of json object */
static void load( const JsonObject &jo );

/** Clears migration list */
static void reset();

/** Checks migrations */
static void check();
};

/*
runtime index: ter_id
ter_id refers to a position in the terlist[] where the ter_t struct is stored. These global
Expand Down
107 changes: 92 additions & 15 deletions src/savegame_json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4618,6 +4618,62 @@ void _write_rle_terrain( JsonOut &jsout, const std::string_view ter, int num )

} // namespace

static std::unordered_map<ter_str_id, std::pair<ter_str_id, furn_str_id>> ter_migrations;
static std::unordered_map<furn_str_id, std::pair<ter_str_id, furn_str_id>> furn_migrations;

void ter_furn_migrations::load( const JsonObject &jo )
{
//TODO: Add support for migrating to items?
const bool is_ter_migration = jo.has_string( "from_ter" );
const bool is_furn_migration = jo.has_string( "from_furn" );
if( ( is_ter_migration && is_furn_migration ) || ( !is_ter_migration && !is_furn_migration ) ) {
debugmsg( "Should specify one of from_ter/from_furn" );
return;
}
ter_str_id to_ter = ter_str_id::NULL_ID();
furn_str_id to_furn = furn_str_id::NULL_ID();
if( is_ter_migration ) {
ter_str_id from_ter;
mandatory( jo, true, "from_ter", from_ter );
mandatory( jo, true, "to_ter", to_ter );
optional( jo, true, "to_furn", to_furn );
ter_migrations.insert( std::make_pair( from_ter, std::make_pair( to_ter, to_furn ) ) );
} else {
furn_str_id from_furn;
mandatory( jo, true, "from_furn", from_furn );
optional( jo, true, "to_ter", to_ter );
mandatory( jo, true, "to_furn", to_furn );
furn_migrations.insert( std::make_pair( from_furn, std::make_pair( to_ter, to_furn ) ) );
}
}

void ter_furn_migrations::reset()
{
ter_migrations.clear();
furn_migrations.clear();
}

void ter_furn_migrations::check()
{
auto check_to_ids_valid = []( const std::pair<ter_str_id, furn_str_id> &to_ids,
const std::string & context ) {
if( !to_ids.first.is_valid() ) {
debugmsg( "ter_furn_migration from '%s' specifies invalid to_ter id '%s'", context,
to_ids.first.c_str() );
}
if( !to_ids.second.is_valid() ) {
debugmsg( "ter_furn_migration from '%s' specifies invalid to_furn id '%s'", context,
to_ids.second.c_str() );
}
};
for( const auto &migration : ter_migrations ) {
check_to_ids_valid( migration.second, migration.first.c_str() );
}
for( const auto &migration : furn_migrations ) {
check_to_ids_valid( migration.second, migration.first.c_str() );
}
}

void submap::store( JsonOut &jsout ) const
{
jsout.member( "turn_last_touched", last_touched );
Expand Down Expand Up @@ -4881,29 +4937,32 @@ void submap::load( const JsonValue &jv, const std::string &member_name, int vers
} else {
// terrain is encoded using simple RLE
int remaining = 0;
int_id<ter_t> iid;
int_id<ter_t> iid_ter;
int_id<furn_t> iid_furn;
for( int j = 0; j < SEEY; j++ ) {
// NOLINTNEXTLINE(modernize-loop-convert)
for( int i = 0; i < SEEX; i++ ) {
if( !remaining ) {
JsonValue terrain_entry = terrain_json.next_value();
if( terrain_entry.test_string() ) {
const ter_str_id terstr( terrain_entry.get_string() );
auto migrate_terstr = [&]( ter_str_id terstr ) {
if( auto it = ter_migrations.find( terstr ); it != ter_migrations.end() ) {
terstr = it->second.first;
if( it->second.second != furn_str_id::NULL_ID() ) {
iid_furn = it->second.second.id();
}
}
if( terstr.is_valid() ) {
iid = terstr.id();
iid_ter = terstr.id();
} else {
debugmsg( "invalid ter_str_id '%s'", terstr.str() );
iid = ter_t_dirt;
debugmsg( "invalid ter_str_id '%s'", terstr.c_str() );
iid_ter = ter_t_dirt;
}
};
if( terrain_entry.test_string() ) {
migrate_terstr( ter_str_id( terrain_entry.get_string() ) );
} else if( terrain_entry.test_array() ) {
JsonArray terrain_rle = terrain_entry;
const ter_str_id terstr( terrain_rle.next_string() );
if( terstr.is_valid() ) {
iid = terstr.id();
} else {
debugmsg( "invalid ter_str_id '%s'", terstr.str() );
iid = ter_t_dirt;
}
migrate_terstr( ter_str_id( terrain_rle.next_string() ) );
remaining = terrain_rle.next_int() - 1;
if( terrain_rle.size() > 2 ) {
terrain_rle.throw_error( "Too many values for terrain RLE" );
Expand All @@ -4914,7 +4973,10 @@ void submap::load( const JsonValue &jv, const std::string &member_name, int vers
} else {
--remaining;
}
m->ter[i][j] = iid;
m->ter[i][j] = iid_ter;
if( iid_furn ) {
m->frn[i][j] = iid_furn;
}
}
}
if( remaining ) {
Expand All @@ -4935,11 +4997,26 @@ void submap::load( const JsonValue &jv, const std::string &member_name, int vers
}
}
} else if( member_name == "furniture" ) {
int_id<ter_t> iid_ter;
int_id<furn_t> iid_furn;
JsonArray furniture_json = jv;
for( JsonArray furniture_entry : furniture_json ) {
int i = furniture_entry.next_int();
int j = furniture_entry.next_int();
m->frn[i][j] = furn_id( furniture_entry.next_string() );
furn_str_id furnstr( furniture_entry.next_string() );
if( auto it = furn_migrations.find( furnstr ); it != furn_migrations.end() ) {
furnstr = it->second.second;
if( it->second.first != ter_str_id::NULL_ID() ) {
m->ter[i][j] = it->second.first.id();
}
}
if( furnstr.is_valid() ) {
iid_furn = furnstr.id();
} else {
debugmsg( "invalid furn_str_id '%s'", furnstr.c_str() );
iid_furn = furn_str_id::NULL_ID().id();
}
m->frn[i][j] = iid_furn;
if( furniture_entry.size() > 3 ) {
furniture_entry.throw_error( "Too many values for furniture entry." );
}
Expand Down
Loading
Loading