From f98c67d3c3dd5b19196938a4d8516f5faf069a28 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Wed, 19 Feb 2020 21:10:15 +0100 Subject: [PATCH] Migrate ammotypes of guns, magazines, and mods (#37746) * Migrate ammotypes of guns, magazines, and mods * Migrate magazine compatibility * Fix seemingly erroneous json entries --- data/json/items/gun/9mm.json | 2 +- .../Generic_Guns/firearms/pistol_tiny.json | 3 +- data/mods/Generic_Guns/firearms/rifle.json | 3 +- .../magazines/gg_magazines_migration.json | 7 +- src/item_factory.cpp | 129 +++++++++++++++++- src/item_factory.h | 3 + 6 files changed, 136 insertions(+), 11 deletions(-) diff --git a/data/json/items/gun/9mm.json b/data/json/items/gun/9mm.json index d9e10e17017c7..38156a7e2fd0c 100644 --- a/data/json/items/gun/9mm.json +++ b/data/json/items/gun/9mm.json @@ -318,7 +318,7 @@ "material": [ "steel", "plastic" ], "symbol": "(", "color": "dark_gray", - "ammo": "9mm", + "ammo": [ "9mm", "460" ], "range": 1, "dispersion": 340, "durability": 9, diff --git a/data/mods/Generic_Guns/firearms/pistol_tiny.json b/data/mods/Generic_Guns/firearms/pistol_tiny.json index f2cfac80e35c6..1549c746b816c 100644 --- a/data/mods/Generic_Guns/firearms/pistol_tiny.json +++ b/data/mods/Generic_Guns/firearms/pistol_tiny.json @@ -6,7 +6,8 @@ "name": "plinker carbine", "ammo": "ammo_pistol_tiny", "description": "With near non-existent recoil and inexpensive ammunition, rifles like this one are popular introductory firearms. It has a built in magazine, capable of holding an impressive amount of its small cartridges. You could take small game with this, but anything bigger might not even notice.", - "clip_size": 19 + "clip_size": 19, + "magazines": [ ] }, { "id": "pistol_tiny_target", diff --git a/data/mods/Generic_Guns/firearms/rifle.json b/data/mods/Generic_Guns/firearms/rifle.json index a3671cef612b5..d47044117eb43 100644 --- a/data/mods/Generic_Guns/firearms/rifle.json +++ b/data/mods/Generic_Guns/firearms/rifle.json @@ -24,7 +24,8 @@ "name": "pipe rifle", "ammo": "ammo_rifle", "description": "A crude longarm chambered in standard rifle ammunition, reinforced near the chamber. It holds a single a round and has a crude assembly to fire it. There's no extractor, so it might be slow to reload, and its construction makes for poor reliability and longevity.", - "clip_size": 1 + "clip_size": 1, + "magazines": [ ] }, { "id": "rifle_pipe_carbine", diff --git a/data/mods/Generic_Guns/magazines/gg_magazines_migration.json b/data/mods/Generic_Guns/magazines/gg_magazines_migration.json index 54e7845c029b4..0625cba48c116 100644 --- a/data/mods/Generic_Guns/magazines/gg_magazines_migration.json +++ b/data/mods/Generic_Guns/magazines/gg_magazines_migration.json @@ -19,6 +19,8 @@ "hptcf380mag_10rd", "taurus_spectrum_mag", "m1911mag_10rd_38super", + "m9mag", + "makarovmag", "glock40mag", "sig40mag", "bhp40mag", @@ -57,11 +59,6 @@ "type": "MIGRATION", "replace": "pistol_magnum_mag" }, - { - "id": [ "m9mag", "makarovmag" ], - "type": "MIGRATION", - "replace": "pistol_medium" - }, { "id": [ "20x66_40_mag", diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 00c0515c8e1e3..50e31e7fa2be5 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -247,11 +247,107 @@ void Item_factory::finalize_pre( itype &obj ) obj.ammo->special_cookoff = false; } } - // for magazines ensure default_ammo is set - if( obj.magazine && obj.magazine->default_ammo == "NULL" ) { - obj.magazine->default_ammo = ammotype( *obj.magazine->type.begin() )->default_ammotype(); + + // Helper for ammo migration in following sections + auto migrate_ammo_set = [&]( std::set &ammoset ) { + for( auto ammo_type_it = ammoset.begin(); ammo_type_it != ammoset.end(); ) { + auto maybe_migrated = migrated_ammo.find( ammo_type_it->obj().default_ammotype() ); + if( maybe_migrated != migrated_ammo.end() ) { + ammo_type_it = ammoset.erase( ammo_type_it ); + ammoset.insert( ammoset.begin(), maybe_migrated->second ); + } else { + ++ammo_type_it; + } + } + }; + + if( obj.magazine ) { + // ensure default_ammo is set + if( obj.magazine->default_ammo == "NULL" ) { + obj.magazine->default_ammo = ammotype( *obj.magazine->type.begin() )->default_ammotype(); + } + + // If the magazine has ammo types for which the default ammo has been migrated, we need to + // replace those ammo types with that of the migrated ammo + migrate_ammo_set( obj.magazine->type ); + + // ensure default_ammo is migrated if need be + auto maybe_migrated = migrated_ammo.find( obj.magazine->default_ammo ); + if( maybe_migrated != migrated_ammo.end() ) { + obj.magazine->default_ammo = maybe_migrated->second.obj().default_ammotype(); + } + } + + // Migrate compataible magazines + for( auto kv : obj.magazines ) { + for( auto mag_it = kv.second.begin(); mag_it != kv.second.end(); ) { + auto maybe_migrated = migrated_magazines.find( *mag_it ); + if( maybe_migrated != migrated_magazines.end() ) { + mag_it = kv.second.erase( mag_it ); + kv.second.insert( kv.second.begin(), maybe_migrated->second ); + } else { + ++mag_it; + } + } + } + + // Migrate default magazines + for( auto kv : obj.magazine_default ) { + auto maybe_migrated = migrated_magazines.find( kv.second ); + if( maybe_migrated != migrated_magazines.end() ) { + kv.second = maybe_migrated->second; + } + } + + if( obj.mod ) { + // Migrate acceptable ammo and ammo modifiers + migrate_ammo_set( obj.mod->acceptable_ammo ); + migrate_ammo_set( obj.mod->ammo_modifier ); + + for( auto kv = obj.mod->magazine_adaptor.begin(); kv != obj.mod->magazine_adaptor.end(); ) { + auto maybe_migrated = migrated_ammo.find( kv->first.obj().default_ammotype() ); + if( maybe_migrated != migrated_ammo.end() ) { + for( const itype_id &compatible_mag : kv->second ) { + obj.mod->magazine_adaptor[maybe_migrated->second].insert( compatible_mag ); + } + kv = obj.mod->magazine_adaptor.erase( kv ); + } else { + ++kv; + } + } } + if( obj.gun ) { + // If the gun has ammo types for which the default ammo has been migrated, we need to + // replace those ammo types with that of the migrated ammo + for( auto ammo_type_it = obj.gun->ammo.begin(); ammo_type_it != obj.gun->ammo.end(); ) { + auto maybe_migrated = migrated_ammo.find( ammo_type_it->obj().default_ammotype() ); + if( maybe_migrated != migrated_ammo.end() ) { + const ammotype old_ammo = *ammo_type_it; + // Remove the old ammotype add the migrated version + ammo_type_it = obj.gun->ammo.erase( ammo_type_it ); + const ammotype &new_ammo = maybe_migrated->second; + obj.gun->ammo.insert( obj.gun->ammo.begin(), new_ammo ); + // Migrate the compatible magazines + auto old_mag_it = obj.magazines.find( old_ammo ); + if( old_mag_it != obj.magazines.end() ) { + for( const itype_id &old_mag : old_mag_it->second ) { + obj.magazines[new_ammo].insert( old_mag ); + } + obj.magazines.erase( old_ammo ); + } + // And the default magazines for each magazine type + auto old_default_mag_it = obj.magazine_default.find( old_ammo ); + if( old_default_mag_it != obj.magazine_default.end() ) { + const itype_id &old_default_mag = old_default_mag_it->second; + obj.magazine_default[new_ammo] = old_default_mag; + obj.magazine_default.erase( old_ammo ); + } + } else { + ++ammo_type_it; + } + } + handle_legacy_ranged( *obj.gun ); // TODO: add explicit action field to gun definitions const auto defmode_name = [&]() { @@ -519,6 +615,33 @@ void Item_factory::finalize_item_blacklist() recipe_dictionary::delete_if( [&migrate]( const recipe & r ) { return r.result() == migrate.first; } ); + + // If the default ammo of an ammo_type gets migrated, we migrate all guns using that ammo + // type to the ammo type of whatever that default ammo was migrated to. + // To do that we need to store a map of ammo to the migration replacement thereof. + auto maybe_ammo = m_templates.find( migrate.first ); + // If the itype_id is valid and the itype has ammo data + if( maybe_ammo != m_templates.end() && maybe_ammo->second.ammo ) { + auto replacement = m_templates.find( migrate.second.replace ); + if( replacement->second.ammo ) { + migrated_ammo.emplace( std::make_pair( migrate.first, replacement->second.ammo->type ) ); + } else { + debugmsg( "Replacement item %s for migrated ammo %s is not ammo.", migrate.second.replace, + migrate.first ); + } + } + + // migrate magazines as well + auto maybe_mag = m_templates.find( migrate.first ); + if( maybe_mag != m_templates.end() && maybe_mag->second.magazine ) { + auto replacement = m_templates.find( migrate.second.replace ); + if( replacement->second.magazine ) { + migrated_magazines.emplace( std::make_pair( migrate.first, migrate.second.replace ) ); + } else { + debugmsg( "Replacement item %s for migrated magazine %s is not a magazine.", migrate.second.replace, + migrate.first ); + } + } } for( vproto_id &vid : vehicle_prototype::get_all() ) { vehicle_prototype &prototype = const_cast( vid.obj() ); diff --git a/src/item_factory.h b/src/item_factory.h index 8209532d301e0..f936b7fc48f82 100644 --- a/src/item_factory.h +++ b/src/item_factory.h @@ -248,6 +248,9 @@ class Item_factory using GroupMap = std::map>; GroupMap m_template_groups; + std::unordered_map migrated_ammo; + std::unordered_map migrated_magazines; + /** Checks that ammo is listed in ammunition_type::name(). * At least one instance of this ammo type should be defined. * If any of checks fails, prints a message to the msg stream.