From 90a48180e25132fe60c009f504fdaea872fcf2da Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sun, 7 Jun 2020 20:57:08 -0600 Subject: [PATCH 1/7] Detect tools with compatible ammo for battery mod Allow toolmod_attach to detect tools with compatible ammo based on type->tool->ammo_id (ammo the tool can *use*) instead of ammo_types() (ammo the tool can be directly loaded with). Refactor toolmod_attach for readability as well. --- src/iuse.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/iuse.cpp b/src/iuse.cpp index d52824d7370be..912045aa01edb 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -6230,11 +6230,15 @@ int iuse::toolmod_attach( player *p, item *it, bool, const tripoint & ) return false; } + // cannot mod non-tool, or a tool with existing mods, or a battery currently installed + if( !e.is_tool() || !e.toolmods().empty() || e.magazine_current() ) { + return false; + } + // can only attach to unmodified tools that use compatible ammo - return e.is_tool() && e.toolmods().empty() && !e.magazine_current() && - std::any_of( it->type->mod->acceptable_ammo.begin(), + return std::any_of( it->type->mod->acceptable_ammo.begin(), it->type->mod->acceptable_ammo.end(), [&]( const ammotype & at ) { - return e.ammo_types( false ).count( at ); + return e.type->tool->ammo_id.count( at ); } ); }; From 85ec6c50fd2c8058eef65defe452ef78897ef75d Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Thu, 11 Jun 2020 21:56:32 -0600 Subject: [PATCH 2/7] Allow tools with battery mods to accept batteries --- src/item.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/item.cpp b/src/item.cpp index a1b73f91bc8a2..f9f3f45d55eb1 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -7362,7 +7362,12 @@ std::set item::magazine_compatible( bool conversion ) const const std::vector &mods = is_gun() ? gunmods() : toolmods(); for( const item *m : mods ) { if( !m->type->mod->magazine_adaptor.empty() ) { - for( const ammotype &atype : ammo_types( conversion ) ) { + // Use item's ammo_types, unless it is a tool with ammo_id set + std::set ammos = ammo_types( conversion ); + if( is_tool() && !type->tool->ammo_id.empty() ) { + ammos = type->tool->ammo_id; + } + for( const ammotype &atype : ammos ) { if( m->type->mod->magazine_adaptor.count( atype ) ) { std::set magazines_for_atype = m->type->mod->magazine_adaptor.find( atype )->second; mags.insert( magazines_for_atype.begin(), magazines_for_atype.end() ); From 2820c6511ef896a6770e96b93243e594e1d57811 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Sat, 20 Jun 2020 17:15:41 -0600 Subject: [PATCH 3/7] Return more meaningful failures from insert_item --- src/item_contents.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/item_contents.cpp b/src/item_contents.cpp index 635fe585c1fe4..f465da15736cc 100644 --- a/src/item_contents.cpp +++ b/src/item_contents.cpp @@ -400,14 +400,14 @@ ret_val item_contents::insert_item( const item &it, item_pocket::pocket_ty ret_val pocket = find_pocket_for( it, pk_type ); if( pocket.value() == nullptr ) { - return ret_val::make_failure( "No pocket found: " + pocket.str() ); + return ret_val::make_failure( "Found no suitable pocket for item" ); } ret_val pocket_contain_code = pocket.value()->insert_item( it ); if( pocket_contain_code.success() ) { return ret_val::make_success(); } - return ret_val::make_failure( "No success" ); + return ret_val::make_failure( "Failed to insert into pocket" ); } void item_contents::force_insert_item( const item &it, item_pocket::pocket_type pk_type ) From 26d6741696de4d566fc42a9c1c3945fa2f34e698 Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Fri, 26 Jun 2020 20:56:55 -0600 Subject: [PATCH 4/7] Refactor reload_finish messaging and variables Restore the multiple messages that may be printed for RELOAD_ONE weapons with SPEEDLOADER ammo, which were nullified in commit 90ff817c63. Generate the messages inline instead of only printing a single message at the end. Indicate the ammo used when reloading. Add reloadable_name variable, eliminate single-use variables is_speedloader and ammo_is_filthy. --- src/activity_handlers.cpp | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 3cf7f80c77d86..50b27f43f9588 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -2041,35 +2041,32 @@ void activity_handlers::reload_finish( player_activity *act, player *p ) } item &reloadable = *act->targets[ 0 ]; - item &ammo = *act->targets[1]; + item &ammo = *act->targets[ 1 ]; + std::string reloadable_name = reloadable.tname(); std::string ammo_name = ammo.tname(); const int qty = act->index; - const bool is_speedloader = ammo.has_flag( flag_SPEEDLOADER ); - const bool ammo_is_filthy = ammo.is_filthy(); if( !reloadable.reload( *p, std::move( act->targets[ 1 ] ), qty ) ) { - add_msg( m_info, _( "Can't reload the %s." ), reloadable.tname() ); + add_msg( m_info, _( "Can't reload the %s." ), reloadable_name ); return; } - std::string msg = _( "You reload the %s." ); - - if( ammo_is_filthy ) { + if( ammo.is_filthy() ) { reloadable.set_flag( "FILTHY" ); } if( reloadable.get_var( "dirt", 0 ) > 7800 ) { - msg = - _( "You manage to loosen some debris and make your %s somewhat operational." ); + add_msg( m_neutral, _( "You manage to loosen some debris and make your %s somewhat operational." ), + reloadable_name ); reloadable.set_var( "dirt", ( reloadable.get_var( "dirt", 0 ) - rng( 790, 2750 ) ) ); } if( reloadable.is_gun() ) { p->recoil = MAX_RECOIL; - if( reloadable.has_flag( flag_RELOAD_ONE ) && !is_speedloader ) { + if( reloadable.has_flag( flag_RELOAD_ONE ) && !ammo.has_flag( flag_SPEEDLOADER ) ) { for( int i = 0; i != qty; ++i ) { - msg = _( "You insert one %2$s into the %1$s." ); + add_msg( m_neutral, _( "You insert one %2$s into the %1$s." ), reloadable_name, ammo_name ); } } if( reloadable.type->gun->reload_noise_volume > 0 ) { @@ -2079,9 +2076,10 @@ void activity_handlers::reload_finish( player_activity *act, player *p ) sounds::sound_t::activity, reloadable.type->gun->reload_noise ); } } else if( reloadable.is_watertight_container() ) { - msg = _( "You refill the %s." ); + add_msg( m_neutral, _( "You refill the %s." ), reloadable_name ); + } else { + add_msg( m_neutral, _( "You reload the %1$s with %2$s." ), reloadable_name, ammo_name ); } - add_msg( m_neutral, msg, reloadable.tname(), ammo_name ); } void activity_handlers::start_fire_finish( player_activity *act, player *p ) From 142f2a2b654059a6f0b8abd4a7b447dc2927cb0d Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Mon, 8 Jun 2020 15:55:13 -0600 Subject: [PATCH 5/7] Add test for batteries, tools, and battery mod TODO: Write a real commit message --- tests/battery_mod_test.cpp | 313 +++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 tests/battery_mod_test.cpp diff --git a/tests/battery_mod_test.cpp b/tests/battery_mod_test.cpp new file mode 100644 index 0000000000000..bd1cae0366760 --- /dev/null +++ b/tests/battery_mod_test.cpp @@ -0,0 +1,313 @@ +#include "item.h" +#include "itype.h" + +#include "catch/catch.hpp" + +// Includes functions: +// item::magazine_compatible +// item::magazine_default +// item::magazine_integral +// item::is_reloadable +// item::is_reloadable_with +// item::toolmods +// +// item::ammo_type +// item::ammo_types +// item::ammo_default +// item::ammo_remaining +// item::ammo_capacity +// +// Attributes: +// item.type->mod->acceptable_ammo +// item.type->mod->magazine_adaptor +// item.type->tool->ammo_id +// +// Related JSON fields: +// "ammo_type" +// "acceptable_ammo" +// "ammo_restriction" +// "magazine_adaptor" +// +TEST_CASE( "battery tool mod test", "[battery][mod]" ) +{ + item med_mod( "magazine_battery_medium_mod" ); + + SECTION( "battery mod properties" ) { + // Is a toolmod, and nothing else + CHECK( med_mod.is_toolmod() ); + CHECK_FALSE( med_mod.is_gun() ); + CHECK_FALSE( med_mod.is_tool() ); + CHECK_FALSE( med_mod.is_magazine() ); + + // Mod can be installed on items using battery ammotype + CHECK( med_mod.type->mod->acceptable_ammo.count( ammotype( "battery" ) ) == 1 ); + // The battery mod does not use ammo_modifier (since it gives explicit battery ids) + CHECK( med_mod.type->mod->ammo_modifier.empty() ); + + // And has some magazine adaptors + CHECK_FALSE( med_mod.type->mod->magazine_adaptor.empty() ); + // Mod itself has no ammo types + CHECK( med_mod.ammo_types().empty() ); + + // No tool mods + CHECK( med_mod.toolmods().empty() ); + + // Mods are not directly compatible with magazines, nor reloadable + CHECK( med_mod.magazine_compatible().empty() ); + CHECK_FALSE( med_mod.is_reloadable() ); + CHECK_FALSE( med_mod.is_reloadable_with( itype_id( "battery" ) ) ); + + // Mod magazine is not integral + CHECK_FALSE( med_mod.magazine_integral() ); + } + + GIVEN( "tool compatible with light batteries" ) { + item flashlight( "flashlight" ); + REQUIRE( flashlight.is_reloadable() ); + REQUIRE( flashlight.is_reloadable_with( itype_id( "light_battery_cell" ) ) ); + + // Flashlight must be free of battery or existing mods + REQUIRE_FALSE( flashlight.magazine_current() ); + REQUIRE( flashlight.toolmods().empty() ); + // Needs a MOD pocket to allow modding + REQUIRE( flashlight.contents.has_pocket_type( item_pocket::pocket_type::MOD ) ); + + WHEN( "medium battery mod is installed" ) { + med_mod.item_tags.insert( "IRREMOVABLE" ); + flashlight.put_in( med_mod, item_pocket::pocket_type::MOD ); + + THEN( "tool modification is successful" ) { + CHECK_FALSE( flashlight.toolmods().empty() ); + CHECK_FALSE( flashlight.magazine_compatible().empty() ); + + CHECK( flashlight.tname() == "flashlight (off)+1" ); + } + + + THEN( "medium batteries can be installed" ) { + CHECK( flashlight.is_reloadable() ); + CHECK( flashlight.is_reloadable_with( itype_id( "medium_battery_cell" ) ) ); + const std::set mag_compats = flashlight.magazine_compatible(); + CHECK( mag_compats.count( itype_id( "medium_battery_cell" ) ) == 1 ); + CHECK( mag_compats.count( itype_id( "medium_plus_battery_cell" ) ) == 1 ); + CHECK( mag_compats.count( itype_id( "medium_atomic_battery_cell" ) ) == 1 ); + CHECK( mag_compats.count( itype_id( "medium_disposable_cell" ) ) == 1 ); + CHECK( flashlight.contents.has_pocket_type( item_pocket::pocket_type::MAGAZINE_WELL ) == 1 ); + } + + THEN( "medium battery is now the default" ) { + // FIXME: Required to fix #40948 + itype_id mag_default = flashlight.magazine_default( false ); + CHECK_FALSE( mag_default.is_null() ); + CHECK( mag_default.str() == "medium_battery_cell" ); + } + + THEN( "light batteries no longer fit" ) { + CHECK_FALSE( flashlight.is_reloadable_with( itype_id( "light_battery_cell" ) ) ); + CHECK_FALSE( flashlight.magazine_compatible().count( itype_id( "light_battery_cell" ) ) ); + } + + AND_WHEN( "a medium battery is installed" ) { + item med_battery( "medium_battery_cell" ); + ret_val result = flashlight.put_in( med_battery, item_pocket::pocket_type::MAGAZINE_WELL ); + + THEN( "battery installation succeeds" ) { + // FIXME: Required to fix #40948 + CHECK( result.success() ); + } + + THEN( "the flashlight has a battery" ) { + // FIXME: Required to fix #40948 + CHECK( flashlight.magazine_current() ); + } + + AND_WHEN( "the battery is charged" ) { + const int bat_charges = med_battery.ammo_capacity( ammotype( "battery" ) ); + med_battery.ammo_set( med_battery.ammo_default(), bat_charges ); + REQUIRE( med_battery.ammo_remaining() == bat_charges ); + + THEN( "the flashlight has charges" ) { + // FIXME: Required to fix #40948 + CHECK( flashlight.ammo_remaining() == bat_charges ); + } + } + } + } + } +} + +// This test exercises several item functions related to battery-powered tools. +// +// Tool battery compartments and battery charge use terms derived from firearms, thus: +// +// - Batteries are "magazines" +// - Have "ammo types" compatible with them +// - Can be reloaded with "ammo" (battery charges) +// - Charge left in the battery is "ammo remaining" +// +// - Tools have a "magazine well" (battery compartment) +// - Can be reloaded with a compatible "magazine" (battery) +// - Charge left in the tool's battery is "ammo remaining" +// +TEST_CASE( "battery and tool properties", "[battery][tool][properties]" ) +{ + const item bat_cell( "light_battery_cell" ); + const item flashlight( "flashlight" ); + + // In JSON, "battery" is both an "ammunition_type" (ammo_types.json) and an "AMMO" (ammo.json) + const ammotype bat_ammotype( "battery" ); + const itype_id bat_ammo( "battery" ); + + SECTION( "battery cell" ) { + SECTION( "is a magazine" ) { + CHECK( bat_cell.is_magazine() ); + } + + SECTION( "is not a tool" ) { + CHECK_FALSE( bat_cell.is_tool() ); + } + + SECTION( "is not ammo" ) { + CHECK_FALSE( bat_cell.is_ammo() ); + // Non-ammo itself does not have an ammo type + CHECK( bat_cell.ammo_type() == ammotype::NULL_ID() ); + } + + SECTION( "has compatible ammo types" ) { + const std::set bat_ammos = bat_cell.ammo_types(); + CHECK_FALSE( bat_ammos.empty() ); + CHECK( bat_ammos.count( bat_ammotype) ); + } + + SECTION( "has capacity to hold battery ammo type" ) { + CHECK( bat_cell.ammo_capacity( bat_ammotype ) > 0 ); + } + + SECTION( "has battery ammo as default" ) { + CHECK( bat_cell.ammo_default() == bat_ammo ); + } + + SECTION( "is reloadable with battery ammo" ) { + CHECK( bat_cell.is_reloadable() ); + CHECK( bat_cell.is_reloadable_with( bat_ammo ) ); + } + + SECTION( "is not counted by charges" ) { + CHECK_FALSE( bat_cell.count_by_charges() ); + } + } + + SECTION( "flashlight" ) { + SECTION( "is a tool" ) { + CHECK( flashlight.is_tool() ); + } + + SECTION( "is not a magazine" ) { + CHECK_FALSE( flashlight.is_magazine() ); + CHECK_FALSE( flashlight.magazine_integral() ); + } + + SECTION( "is not ammo" ) { + CHECK_FALSE( flashlight.is_ammo() ); + // Non-ammo itself does not have an ammo type + CHECK( flashlight.ammo_type() == ammotype::NULL_ID() ); + } + + SECTION( "is reloadable with a magazine" ) { + CHECK( flashlight.is_reloadable() ); + CHECK( flashlight.is_reloadable_with( itype_id( "light_battery_cell" ) ) ); + CHECK( flashlight.is_reloadable_with( itype_id( "light_disposable_cell" ) ) ); + } + + SECTION( "has compatible magazines" ) { + const std::set mag_compats = flashlight.magazine_compatible(); + CHECK_FALSE( mag_compats.empty() ); + CHECK( mag_compats.count( itype_id( "light_battery_cell" ) ) == 1 ); + CHECK( mag_compats.count( itype_id( "light_disposable_cell" ) ) == 1 ); + CHECK( mag_compats.count( itype_id( "light_plus_battery_cell" ) ) == 1 ); + CHECK( mag_compats.count( itype_id( "light_atomic_battery_cell" ) ) == 1 ); + } + + SECTION( "has a default magazine" ) { + itype_id mag_default = flashlight.magazine_default( false ); + CHECK_FALSE( mag_default.is_null() ); + // FIXME: Required to fix #40800 + CHECK( mag_default.str() == "light_battery_cell" ); + } + + SECTION( "can use battery ammo" ) { + // Since flashlights get their "ammo" from a "magazine", their ammo_types is empty + CHECK( flashlight.ammo_types().empty() ); + CHECK( flashlight.ammo_default().is_null() ); + + // The ammo a flashlight can *use* is given by type->tool->ammo_id + CHECK_FALSE( flashlight.type->tool->ammo_id.empty() ); + CHECK( flashlight.type->tool->ammo_id.count( ammotype( "battery" ) ) == 1 ); + } + + SECTION( "requires some ammo (charge) to use" ) { + CHECK( flashlight.ammo_required() > 0 ); + } + + SECTION( "is not counted by charges" ) { + CHECK_FALSE( flashlight.count_by_charges() ); + } + } +} + +TEST_CASE( "installing battery in tool", "[battery][tool][install]" ) +{ + item bat_cell( "light_battery_cell" ); + item flashlight( "flashlight" ); + + const int bat_charges = bat_cell.ammo_capacity( ammotype( "battery" ) ); + REQUIRE( bat_charges > 0 ); + + SECTION( "flashlight with no battery installed" ) { + REQUIRE( !flashlight.magazine_current() ); + + CHECK( flashlight.ammo_remaining() == 0 ); + CHECK( flashlight.ammo_capacity( ammotype( "battery" ) ) == 0 ); + CHECK( flashlight.remaining_ammo_capacity() == 0 ); + } + + SECTION( "dead battery installed in flashlight" ) { + // Ensure battery is dead + bat_cell.ammo_set( bat_cell.ammo_default(), 0 ); + REQUIRE( bat_cell.ammo_remaining() == 0 ); + + // Put battery in flashlight + REQUIRE( flashlight.contents.has_pocket_type( item_pocket::pocket_type::MAGAZINE_WELL ) ); + ret_val result = flashlight.put_in( bat_cell, item_pocket::pocket_type::MAGAZINE_WELL ); + CHECK( result.success() ); + CHECK( flashlight.magazine_current() ); + + // No remaining ammo + CHECK( flashlight.ammo_remaining() == 0 ); + } + + SECTION( "charged battery installed in flashlight" ) { + // Charge the battery + bat_cell.ammo_set( bat_cell.ammo_default(), bat_charges ); + REQUIRE( bat_cell.ammo_remaining() == bat_charges ); + + // Put battery in flashlight + REQUIRE( flashlight.contents.has_pocket_type( item_pocket::pocket_type::MAGAZINE_WELL ) ); + ret_val result = flashlight.put_in( bat_cell, item_pocket::pocket_type::MAGAZINE_WELL ); + CHECK( result.success() ); + CHECK( flashlight.magazine_current() ); + + // Flashlight has a full charge + CHECK( flashlight.ammo_remaining() == bat_charges ); + } + + SECTION( "wrong size battery for flashlight" ) { + item med_bat_cell( "medium_battery_cell" ); + + // Should fail to install the magazine + REQUIRE( flashlight.contents.has_pocket_type( item_pocket::pocket_type::MAGAZINE_WELL ) ); + ret_val result = flashlight.put_in( med_bat_cell, item_pocket::pocket_type::MAGAZINE_WELL ); + CHECK_FALSE( result.success() ); + CHECK_FALSE( flashlight.magazine_current() ); + } +} From e22f0bdeee749bf6d060f9a6c897f017268ca8bd Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Thu, 11 Jun 2020 19:56:20 -0600 Subject: [PATCH 6/7] Add ammo test for ammo_types and ammo_default --- tests/ammo_test.cpp | 188 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 tests/ammo_test.cpp diff --git a/tests/ammo_test.cpp b/tests/ammo_test.cpp new file mode 100644 index 0000000000000..f0848cf3dc13a --- /dev/null +++ b/tests/ammo_test.cpp @@ -0,0 +1,188 @@ +#include "item.h" +#include "itype.h" + +#include "catch/catch.hpp" + +// Functions: +// - item::ammo_types +// +// TODO: +// - ammo_type +// - common_ammo_default +// - ammo_sort_name + +// item::ammo_types returns the ammo types an item may *contain*, which is distinct from the ammo +// types an item may *use*. +// +// Only magazines have ammo_types(). +// +// Tools, tool mods, and (all other item types?) have empty ammo_types() + + +// Return true if the given item has non-empty ammo_types +static bool has_ammo_types( const item &it ) +{ + return !it.ammo_types().empty(); +} + +TEST_CASE( "ammo types", "[ammo][ammo_types]" ) +{ + // Only a few kinds of item have ammo_types: + // - Items with type=MAGAZINE (including batteries as well as gun magazines) + // - Tools/weapons with magazine_integral (pocket_data has a MAGAZINE rather than MAGAZINE_WELL) + + SECTION( "items with MAGAZINE pockets have ammo_types" ) { + // Batteries are magazines + REQUIRE( item( "light_battery_cell" ).is_magazine() ); + REQUIRE( item( "battery_car" ).is_magazine() ); + + // Tool batteries + CHECK( has_ammo_types( item( "light_battery_cell" ) ) ); + CHECK( has_ammo_types( item( "medium_battery_cell" ) ) ); + CHECK( has_ammo_types( item( "heavy_battery_cell" ) ) ); + CHECK( has_ammo_types( item( "light_disposable_cell" ) ) ); + CHECK( has_ammo_types( item( "medium_disposable_cell" ) ) ); + CHECK( has_ammo_types( item( "heavy_disposable_cell" ) ) ); + // Vehicle batteries + CHECK( has_ammo_types( item( "battery_car" ) ) ); + CHECK( has_ammo_types( item( "battery_motorbike" ) ) ); + CHECK( has_ammo_types( item( "large_storage_battery" ) ) ); + + SECTION( "battery magazines include 'battery' ammo type" ) { + CHECK( item( "light_battery_cell" ).ammo_types().count( ammotype( "battery" ) ) == 1 ); + CHECK( item( "battery_car" ).ammo_types().count( ammotype( "battery" ) ) == 1 ); + } + + // Gun magazines + REQUIRE( item( "belt40mm" ).is_magazine() ); + REQUIRE( item( "akmag10" ).is_magazine() ); + + CHECK( has_ammo_types( item( "belt40mm" ) ) ); + CHECK( has_ammo_types( item( "belt308" ) ) ); + CHECK( has_ammo_types( item( "akmag10" ) ) ); + CHECK( has_ammo_types( item( "akdrum75" ) ) ); + CHECK( has_ammo_types( item( "glockmag" ) ) ); + + SECTION( "gun magazines include ammo type for that magazine" ) { + CHECK( item( "glockmag" ).ammo_types().count( ammotype( "9mm" ) ) == 1 ); + CHECK( item( "akmag10" ).ammo_types().count( ammotype( "762" ) ) == 1 ); + } + } + + SECTION( "GUN items with integral MAGAZINE pockets have ammo_types" ) { + REQUIRE( item( "nailgun" ).magazine_integral() ); + REQUIRE( item( "colt_army" ).magazine_integral() ); + + CHECK( has_ammo_types( item( "nailgun" ) ) ); + CHECK( has_ammo_types( item( "colt_army" ) ) ); + CHECK( has_ammo_types( item( "hand_crossbow" ) ) ); + CHECK( has_ammo_types( item( "compositebow" ) ) ); + CHECK( has_ammo_types( item( "sling" ) ) ); + CHECK( has_ammo_types( item( "slingshot" ) ) ); + } + + SECTION( "TOOL items with integral MAGAZINE pockets have ammo_types" ) { + REQUIRE( item( "sewing_kit" ).magazine_integral() ); + + CHECK( has_ammo_types( item( "needle_bone" ) ) ); + CHECK( has_ammo_types( item( "needle_wood" ) ) ); + CHECK( has_ammo_types( item( "sewing_kit" ) ) ); + CHECK( has_ammo_types( item( "tailors_kit" ) ) ); + } + + SECTION( "TOOL items with NO pockets have ammo_types" ) { + // NOTE: Fish trap is a TOOL with "ammo", but no "pocket_data", so an implicit MAGAZINE + // pocket is added by Item_factory::check_and_create_magazine_pockets on JSON load. + // This item would be considered needing data migration to an explicit MAGAZINE pocket. + REQUIRE( item( "fish_trap" ).magazine_integral() ); + + CHECK( has_ammo_types( item( "fish_trap" ) ) ); + } + + // These items have NO ammo_types: + + SECTION( "GUN items with MAGAZINE_WELL pockets do NOT have ammo_types" ) { + REQUIRE_FALSE( item( "m1911" ).magazine_integral() ); + + CHECK_FALSE( has_ammo_types( item( "m1911" ) ) ); + CHECK_FALSE( has_ammo_types( item( "usp_9mm" ) ) ); + CHECK_FALSE( has_ammo_types( item( "tommygun" ) ) ); + CHECK_FALSE( has_ammo_types( item( "ak74" ) ) ); + CHECK_FALSE( has_ammo_types( item( "ak47" ) ) ); + } + + SECTION( "TOOL items with MAGAZINE_WELL pockets do NOT have ammo_types" ) { + REQUIRE( item( "flashlight" ).is_tool() ); + + CHECK_FALSE( has_ammo_types( item( "flashlight" ) ) ); + CHECK_FALSE( has_ammo_types( item( "hotplate" ) ) ); + CHECK_FALSE( has_ammo_types( item( "vac_sealer" ) ) ); + CHECK_FALSE( has_ammo_types( item( "forge" ) ) ); + CHECK_FALSE( has_ammo_types( item( "cordless_drill" ) ) ); + } + + SECTION( "AMMO items themselves do NOT have ammo_types" ) { + REQUIRE( item( "38_special" ).is_ammo() ); + REQUIRE( item( "sinew" ).is_ammo() ); + + // Ammo for guns + CHECK_FALSE( has_ammo_types( item( "38_special" ) ) ); + CHECK_FALSE( has_ammo_types( item( "reloaded_308" ) ) ); + CHECK_FALSE( has_ammo_types( item( "bp_9mm" ) ) ); + CHECK_FALSE( has_ammo_types( item( "44magnum" ) ) ); + // Not for guns but classified as ammo + CHECK_FALSE( has_ammo_types( item( "sinew" ) ) ); + CHECK_FALSE( has_ammo_types( item( "nail" ) ) ); + CHECK_FALSE( has_ammo_types( item( "rock" ) ) ); + CHECK_FALSE( has_ammo_types( item( "solder_wire" ) ) ); + } + + SECTION( "TOOLMOD items do NOT have ammo_types" ) { + item med_mod( "magazine_battery_medium_mod" ); + REQUIRE( med_mod.is_toolmod() ); + + CHECK_FALSE( has_ammo_types( med_mod ) ); + } +} + +// The same items with no ammo_types, also have no ammo_default. +TEST_CASE( "ammo default", "[ammo][ammo_default]" ) +{ + // TOOLMOD type, and TOOL/GUN type items with MAGAZINE_WELL pockets have no ammo_default + SECTION( "items without ammo_default" ) { + item flashlight( "flashlight" ); + item med_mod( "magazine_battery_medium_mod" ); + item tommygun( "tommygun" ); + + CHECK( flashlight.ammo_default().is_null() ); + CHECK( med_mod.ammo_default().is_null() ); + CHECK( tommygun.ammo_default().is_null() ); + } + + // MAGAZINE type, and TOOL/GUN items with integral MAGAZINE pockets do have ammo_default + SECTION( "items with ammo_default" ) { + // MAGAZINE type items + item battery( "light_battery_cell" ); + item glockmag( "glockmag" ); + CHECK( battery.ammo_default() == itype_id( "battery" ) ); + CHECK( glockmag.ammo_default() == itype_id( "9mm" ) ); + + // TOOL type items with integral magazines + item sewing_kit( "sewing_kit" ); + item needle( "needle_bone" ); + item fishtrap( "fish_trap" ); + CHECK( sewing_kit.ammo_default() == itype_id( "thread" ) ); + CHECK( needle.ammo_default() == itype_id( "thread" ) ); + CHECK( fishtrap.ammo_default() == itype_id( "fish_bait" ) ); + + // GUN type items with integral magazine + item slingshot( "slingshot" ); + item colt( "colt_army" ); + item lemat( "lemat_revolver" ); + CHECK( slingshot.ammo_default() == itype_id( "pebble" ) ); + // Revolver ammo is "44paper" but default ammunition type is "44army" + CHECK( colt.ammo_default() == itype_id( "44army" ) ); + CHECK( lemat.ammo_default() == itype_id( "44army" ) ); + } +} + From 21c44c8e1aa585bcfd0ae933e47fa82fed685ddc Mon Sep 17 00:00:00 2001 From: Eric Pierce Date: Thu, 11 Jun 2020 19:55:26 -0600 Subject: [PATCH 7/7] Clarify docs for ammo_types and ammo_default Move ammo_types closer to ammo_default (as they are closely related) --- src/item.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/item.h b/src/item.h index 02073cd0dd66a..5fb68bfabf076 100644 --- a/src/item.h +++ b/src/item.h @@ -1757,19 +1757,17 @@ class item : public visitable const itype *ammo_data() const; /** Specific ammo type, returns "null" if item is neither ammo nor loaded with any */ itype_id ammo_current() const; - /** Set of ammo types (@ref ammunition_type) used by item - * @param conversion whether to include the effect of any flags or mods which convert the type - * @return empty set if item does not use a specific ammo type (and is consequently not reloadable) */ - std::set ammo_types( bool conversion = true ) const; - /** Ammo type of an ammo item * @return ammotype of ammo item or a null id if the item is not ammo */ ammotype ammo_type() const; - /** Get default ammo used by item or a null id if item does not have a default ammo type + /** Ammo types (@ref ammunition_type) the item magazine pocket can contain. + * @param conversion whether to include the effect of any flags or mods which convert the type + * @return empty set if item does not have a magazine for a specific ammo type */ + std::set ammo_types( bool conversion = true ) const; + /** Default ammo for the the item magazine pocket, if item has ammo_types(). * @param conversion whether to include the effect of any flags or mods which convert the type - * @return itype_id::NULL_ID() if item does not use a specific ammo type - * (and is consequently not reloadable) */ + * @return itype_id::NULL_ID() if item does have a magazine for a specific ammo type */ itype_id ammo_default( bool conversion = true ) const; /** Get default ammo for the first ammotype common to an item and its current magazine or "NULL" if none exists