diff --git a/data/json/flags.json b/data/json/flags.json index 57748ac86ab26..1ec23aa7db0fa 100644 --- a/data/json/flags.json +++ b/data/json/flags.json @@ -2387,5 +2387,10 @@ "id": "BLEEDSLOW2", "type": "json_flag", "info": "The character bleeds even slower than normal, losing blood at 1/3rd the normal rate." + }, + { + "id": "NOT_MAGAZINE", + "type": "json_flag", + "//": "Skips a check in Character::list_ammo that would otherwise prevent items here from being used to reload a gun." } ] diff --git a/data/json/items/gunmod/accessories.json b/data/json/items/gunmod/accessories.json index 457c9ba622084..2407ce8fc45da 100644 --- a/data/json/items/gunmod/accessories.json +++ b/data/json/items/gunmod/accessories.json @@ -132,5 +132,127 @@ "location": "belt clip", "mod_targets": [ "rugerlcp", "kp32", "kp3at", "kpf9", "cop_38", "moss_brownie" ], "flags": [ "IS_ARMOR", "SKINTIGHT", "WATER_FRIENDLY" ] + }, + { + "id": "12ga_shell_holder_stock", + "type": "GUNMOD", + "name": { "str": "stock-mounted shell holder" }, + "description": "Designed to be strapped to the side of a shotgun's stock, this polymer shotshell holder can store five 12 gauge shells for easy reloading.", + "weight": "50 g", + "volume": "350 ml", + "price": 2000, + "price_postapoc": 50, + "install_time": "30 s", + "material": [ "plastic" ], + "flags": [ "NOT_MAGAZINE" ], + "symbol": ":", + "color": "black", + "location": "stock accessory", + "mod_targets": [ "shotgun" ], + "pocket_data": [ + { + "pocket_type": "CONTAINER", + "rigid": true, + "max_contains_volume": "100 ml", + "max_contains_weight": "250 g", + "ammo_restriction": { "shot": 5 }, + "moves": 25 + } + ], + "pocket_mods": [ + { + "pocket_type": "CONTAINER", + "rigid": true, + "max_contains_volume": "100 ml", + "max_contains_weight": "250 g", + "ammo_restriction": { "shot": 5 }, + "moves": 25 + } + ], + "min_skills": [ [ "weapon", 1 ] ] + }, + { + "id": "12ga_shell_holder_stock_leather", + "type": "GUNMOD", + "name": { "str": "stock-mounted leather shell holder" }, + "description": "Designed to be strapped to the side of a shotgun's stock, this leather shotshell holder can store five 12 gauge shells for easy reloading.", + "weight": "16 g", + "volume": "60 ml", + "price": 2000, + "price_postapoc": 50, + "install_time": "30 s", + "material": [ "leather" ], + "flags": [ "NOT_MAGAZINE" ], + "symbol": ":", + "color": "brown", + "location": "stock accessory", + "mod_targets": [ "shotgun" ], + "pocket_mods": [ + { + "pocket_type": "CONTAINER", + "rigid": true, + "max_contains_volume": "100 ml", + "max_contains_weight": "250 g", + "ammo_restriction": { "shot": 5 }, + "moves": 25 + } + ], + "min_skills": [ [ "weapon", 1 ] ] + }, + { + "id": "rifle_cart_holder_stock", + "type": "GUNMOD", + "name": { "str": "stock-mounted cartridge holder" }, + "description": "Designed to be strapped to the side of a rifle's stock, this polymer cartridge holder can store seven rounds for easy reloading. Fits most larger calibers.", + "weight": "16 g", + "volume": "60 ml", + "price": 2000, + "price_postapoc": 50, + "install_time": "30 s", + "material": [ "plastic" ], + "flags": [ "NOT_MAGAZINE" ], + "symbol": ":", + "color": "black", + "location": "stock accessory", + "mod_targets": [ "rifle" ], + "pocket_mods": [ + { + "pocket_type": "CONTAINER", + "rigid": true, + "max_contains_volume": "100 ml", + "max_contains_weight": "250 g", + "ammo_restriction": { "338lapua": 7, "3007": 7, "300": 7, "308": 7, "762R": 7, "77mm_arisaka": 7, "762": 7, "50": 7, "30carbine": 7, "223": 7, "123ln": 7, "303": 7 }, + "moves": 25 + } + ], + "min_skills": [ [ "weapon", 1 ] ] + }, + { + "id": "rifle_cart_holder_stock_leather", + "type": "GUNMOD", + "name": { "str": "stock-mounted leather cartridge holder" }, + "description": "Designed to be strapped to the side of a rifle's stock, this leather cartridge holder can store seven rounds for easy reloading. Fits most larger calibers.", + "weight": "16 g", + "volume": "60 ml", + "price": 2000, + "price_postapoc": 50, + "install_time": "30 s", + "material": [ "leather" ], + "flags": [ "NOT_MAGAZINE" ], + "symbol": ":", + "color": "brown", + "location": "stock accessory", + "mod_targets": [ "rifle" ], + "pocket_mods": [ + { + "pocket_type": "CONTAINER", + "rigid": true, + "max_contains_volume": "100 ml", + "max_contains_weight": "250 g", + "ammo_restriction": { "338lapua": 7, "3007": 7, "300": 7, "308": 7, "762R": 7, "77mm_arisaka": 7, "762": 7, "50": 7, "30carbine": 7, "223": 7, "123ln": 7, "303": 7 }, + "moves": 25 + } + ], + "min_skills": [ [ "weapon", 1 ] ] } ] diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index f32046cf283df..ea5ed2a7ffe11 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -636,7 +636,7 @@ void gunmod_remove_activity_actor::finish( player_activity &act, Character &who bool gunmod_remove_activity_actor::gunmod_unload( Character &who, item &gunmod ) { - if( gunmod.has_flag( flag_BRASS_CATCHER ) ) { + if( gunmod.has_flag( flag_BRASS_CATCHER ) || gunmod.has_flag( flag_NOT_MAGAZINE ) ) { // Exclude brass catchers so that removing them wouldn't spill the casings return true; } @@ -674,19 +674,21 @@ void gunmod_remove_activity_actor::serialize( JsonOut &jsout ) const jsout.member( "moves_total", moves_total ); jsout.member( "gun", gun ); jsout.member( "gunmod", gunmod_idx ); + jsout.member( "mod_contents", mod_contents ); jsout.end_object(); } std::unique_ptr gunmod_remove_activity_actor::deserialize( JsonValue &jsin ) { - gunmod_remove_activity_actor actor( 0, item_location(), -1 ); + gunmod_remove_activity_actor actor( 0, item_location(), -1, {} ); JsonObject data = jsin.get_object(); data.read( "moves_total", actor.moves_total ); data.read( "gun", actor.gun ); data.read( "gunmod", actor.gunmod_idx ); + data.read( "mod_contents", actor.mod_contents ); return actor.clone(); } diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 23e6d04ce4288..d147c67c2f7c6 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -144,13 +144,15 @@ class gunmod_remove_activity_actor : public activity_actor int moves_total; item_location gun; int gunmod_idx; + std::list mod_contents; public: gunmod_remove_activity_actor( int moves_total, const item_location &gun, - int gunmod_idx - ) : moves_total( moves_total ), gun( gun ), gunmod_idx( gunmod_idx ) {} + int gunmod_idx, + std::list mod_contents + ) : moves_total( moves_total ), gun( gun ), gunmod_idx( gunmod_idx ), mod_contents {} activity_id get_type() const override { return activity_id( "ACT_GUNMOD_REMOVE" ); diff --git a/src/character_ammo.cpp b/src/character_ammo.cpp index 295505c3e4e48..d521cca5cd968 100644 --- a/src/character_ammo.cpp +++ b/src/character_ammo.cpp @@ -352,6 +352,7 @@ item::reload_option Character::select_ammo( const item_location &base, item::reload_option Character::select_ammo( const item_location &base, bool prompt, bool empty ) const { + if( !base ) { return item::reload_option(); } diff --git a/src/character_guns.cpp b/src/character_guns.cpp index 9afcf33f10f67..5c5032a006dd1 100644 --- a/src/character_guns.cpp +++ b/src/character_guns.cpp @@ -17,7 +17,7 @@ void find_ammo_helper( T &src, const item &obj, bool empty, Output out, bool nes src.visit_items( [&src, &nested, &out, &obj, empty]( item * node, item * parent ) { // This stops containers and magazines counting *themselves* as ammo sources - if( node == &obj ) { + if( node == &obj && !node->has_flag( flag_NOT_MAGAZINE ) ) { return VisitResponse::SKIP; } @@ -32,7 +32,7 @@ void find_ammo_helper( T &src, const item &obj, bool empty, Output out, bool nes } // Do not steal ammo from magazines - if( parent != nullptr && parent->is_magazine() ) { + if( parent != nullptr && parent->is_magazine() && !parent->is_magazine() ) { return VisitResponse::SKIP; } @@ -252,6 +252,7 @@ bool Character::gunmod_remove( item &gun, item &mod ) break; } } + if( gunmod_idx == mods.size() ) { debugmsg( "Cannot remove non-existent gunmod" ); return false; @@ -264,7 +265,23 @@ bool Character::gunmod_remove( item &gun, item &mod ) // Removing gunmod takes only half as much time as installing it const int moves = has_trait( trait_DEBUG_HS ) ? 0 : mod.type->gunmod->install_time / 2; item_location gun_loc = item_location( *this, &gun ); - assign_activity( gunmod_remove_activity_actor( moves, gun_loc, static_cast( gunmod_idx ) ) ); + if( mod.has_flag( flag_NOT_MAGAZINE ) ) { + std::list mod_contents; + for( const item *it : gun.all_items_ptr( pocket_type::CONTAINER ) ) { + mod_contents.push_back( *it ); + } + if( !mod_contents.empty() ) { + assign_activity( gunmod_remove_activity_actor( moves, gun_loc, static_cast( gunmod_idx ), mod_contents ) ); + // make the activity do this stuff + // map &here = get_map(); + // for( item &content : mod_contents ) { + // here.add_item_or_charges( this->pos(), content ); + // } + return true; + } + } else { + assign_activity( gunmod_remove_activity_actor( moves, gun_loc, static_cast( gunmod_idx ), {} ) ); + } return true; } diff --git a/src/flag.cpp b/src/flag.cpp index cf887e7190956..f2a7a9ca6f0db 100644 --- a/src/flag.cpp +++ b/src/flag.cpp @@ -217,6 +217,7 @@ const flag_id flag_NO_TURRET( "NO_TURRET" ); const flag_id flag_NO_UNLOAD( "NO_UNLOAD" ); const flag_id flag_NO_UNWIELD( "NO_UNWIELD" ); const flag_id flag_NO_WEAR_EFFECT( "NO_WEAR_EFFECT" ); +const flag_id flag_NOT_MAGAZINE( "NOT_MAGAZINE" ); const flag_id flag_NPC_ACTIVATE( "NPC_ACTIVATE" ); const flag_id flag_NPC_ALT_ATTACK( "NPC_ALT_ATTACK" ); const flag_id flag_NPC_SAFE( "NPC_SAFE" ); diff --git a/src/flag.h b/src/flag.h index 5dedc6dc1b04f..ecdf4e154b1ab 100644 --- a/src/flag.h +++ b/src/flag.h @@ -225,6 +225,7 @@ extern const flag_id flag_NO_TURRET; extern const flag_id flag_NO_UNLOAD; extern const flag_id flag_NO_UNWIELD; extern const flag_id flag_NO_WEAR_EFFECT; +extern const flag_id flag_NOT_MAGAZINE; extern const flag_id flag_NPC_ACTIVATE; extern const flag_id flag_NPC_ALT_ATTACK; extern const flag_id flag_NPC_SAFE; diff --git a/src/item_pocket.cpp b/src/item_pocket.cpp index f6e9120ff7a71..1179161a8ee54 100644 --- a/src/item_pocket.cpp +++ b/src/item_pocket.cpp @@ -1715,7 +1715,6 @@ bool item_pocket::can_reload_with( const item &ammo, const bool now ) const } return true; - } else if( is_type( pocket_type::MAGAZINE_WELL ) ) { // Reloading is refused if there already is full magazine here // Reloading with another identical mag with identical contents is also pointless so it is not allowed @@ -1725,10 +1724,12 @@ bool item_pocket::can_reload_with( const item &ammo, const bool now ) const front().same_contents( ammo ) ); } else if( is_type( pocket_type::CONTAINER ) ) { // Reloading is possible if liquid combines with old liquid - if( front().can_combine( ammo ) ) { return true; } + if( !ammo.made_of( phase_id::LIQUID ) ) { + return true; + } } } return false;