diff --git a/data/mods/TEST_DATA/items.json b/data/mods/TEST_DATA/items.json index c1900c6057073..d63d31793df7a 100644 --- a/data/mods/TEST_DATA/items.json +++ b/data/mods/TEST_DATA/items.json @@ -1589,6 +1589,31 @@ } ] }, + { + "id": "test_xl_waist_apron_long", + "type": "GENERIC", + "name": { "str": "long waist apron" }, + "copy-from": "test_waist_apron_long", + "extend": { "flags": [ "OVERSIZE", "PREFIX_XL" ] }, + "//": "the variants are not copy-from'd at the time of writing so let's make some", + "variants": [ + { + "id": "generic_apron_cotton", + "name": { "str": "long waist apron" }, + "description": "It's colored white, like the ones commonly used by chefs, professional or otherwise.", + "weight": 50, + "append": true + }, + { + "id": "pink_apron_cotton", + "name": { "str": "pink long waist apron" }, + "description": "It's colored neon pink, commonly used by women or men who like pink.", + "color": "pink", + "weight": 3, + "append": true + } + ] + }, { "id": "test_umbrella", "type": "GENERIC", diff --git a/data/mods/TEST_DATA/recipes.json b/data/mods/TEST_DATA/recipes.json index ec1aec7526be5..651f391018d05 100644 --- a/data/mods/TEST_DATA/recipes.json +++ b/data/mods/TEST_DATA/recipes.json @@ -280,6 +280,12 @@ "autolearn": true, "using": [ [ "tailoring_cotton_patchwork", 6 ] ] }, + { + "//": "Variant version with XL prefix", + "result": "test_xl_waist_apron_long", + "type": "recipe", + "copy-from": "test_waist_apron_long_pink_apron_cotton" + }, { "result": "test_200_kcal", "type": "recipe", diff --git a/src/item_tname.h b/src/item_tname.h index cdee759dfe120..3b8e405e92923 100644 --- a/src/item_tname.h +++ b/src/item_tname.h @@ -111,16 +111,26 @@ constexpr uint64_t tname_conditional_bits = // TODO: fine grain? 1ULL << static_cast( tname::segments::COMPONENTS ) | 1ULL << static_cast( tname::segments::TAGS ) | 1ULL << static_cast( tname::segments::VARS ); -constexpr uint64_t item_name_bits = // item prefix + item name + item suffix +constexpr uint64_t base_item_name_bits = 1ULL << static_cast( tname::segments::CUSTOM_ITEM_PREFIX ) | 1ULL << static_cast( tname::segments::TYPE ) | 1ULL << static_cast( tname::segments::CUSTOM_ITEM_SUFFIX ); +constexpr uint64_t variant_bits = + 1ULL << static_cast( tname::segments::VARIANT ); constexpr segment_bitset default_tname( default_tname_bits ); constexpr segment_bitset unprefixed_tname( default_tname_bits & ~tname_prefix_bits ); constexpr segment_bitset tname_sort_key( default_tname_bits & ~tname_unsortable_bits ); constexpr segment_bitset tname_contents( tname_contents_bits ); constexpr segment_bitset tname_conditional( tname_conditional_bits ); -constexpr segment_bitset item_name( item_name_bits ); +// Name for an abstract base item of a given class, not any specific one. +// Often used in crafting UI and similar. +// For example in the sentence "To make some 'XL socks' I will need to cut up a 'blanket'", +// when we don't care which color (read: variant) 'XL socks' we want or 'blanket' we have. +constexpr segment_bitset base_item_name( base_item_name_bits ); +// Name of a specific item in the game world, carries the item identity, and will not +// change in a normal playthrough except through exordinary means (e.g. via `iuse_transform`). +// E.g. "XL green socks" (notably, not "|. XL green socks (filthy)") +constexpr segment_bitset item_identity_name( base_item_name_bits | variant_bits ); } // namespace tname diff --git a/src/recipe.cpp b/src/recipe.cpp index 48c1a427e928c..b902993479435 100644 --- a/src/recipe.cpp +++ b/src/recipe.cpp @@ -1218,17 +1218,21 @@ std::string recipe::result_name( const bool decorated ) const { std::string name; if( !name_.empty() ) { + // if the recipe has an explicit name (such as for proficiency training) - use that name = name_.translated(); - } else if( !variant().empty() ) { - auto iter_var = std::find_if( result_->variants.begin(), result_->variants.end(), - [this]( const itype_variant_data & itvar ) { - return itvar.id == variant(); - } ); - if( iter_var != result_->variants.end() ) { - name = iter_var->alt_name.translated(); - } } else { - name = item::tname( result_, 1, tname::item_name ); + // Names are tricky, so we have to create a temporary fake result item to get one. + // As of 2025-01-01 there's no better way around this. + item temp_item( result_ ); + // Use generic item name by default. + tname::segment_bitset segs = tname::base_item_name; + if( !variant().empty() ) { + // ..but if the recipe calls for a specific varaint - then use that variant. + // Note that `temp_item` is likely to already have a random variant set at the time of creation. + temp_item.set_itype_variant( variant() ); + segs = tname::item_identity_name; + } + name = temp_item.tname( 1, segs ); } if( decorated && uistate.favorite_recipes.find( this->ident() ) != uistate.favorite_recipes.end() ) { diff --git a/src/requirements.cpp b/src/requirements.cpp index 43956bd07897f..8e289bfaf5868 100644 --- a/src/requirements.cpp +++ b/src/requirements.cpp @@ -158,9 +158,9 @@ std::string tool_comp::to_string( const int batch, const int ) const //~ %1$s: tool name, %2$d: charge requirement return string_format( npgettext( "requirement", "%1$s (%2$d charge)", "%1$s (%2$d charges)", charge_total ), - item::tname( type, 1, tname::item_name ), charge_total ); + item::tname( type, 1, tname::base_item_name ), charge_total ); } else { - return item::tname( type, std::abs( count ), tname::item_name ); + return item::tname( type, std::abs( count ), tname::base_item_name ); } } @@ -176,16 +176,16 @@ std::string item_comp::to_string( const int batch, const int avail ) const return string_format( npgettext( "requirement", "%2$d %1$s (have infinite)", "%2$d %1$s (have infinite)", c ), - item_temp.tname( 1, tname::item_name ), c ); + item_temp.tname( 1, tname::base_item_name ), c ); } else if( avail > 0 ) { //~ %1$s: item name, %2$d: charge requirement, %3%d: available charges return string_format( npgettext( "requirement", "%2$d %1$s (have %3$d)", "%2$d %1$s (have %3$d)", c ), - item_temp.tname( 1, tname::item_name ), c, avail ); + item_temp.tname( 1, tname::base_item_name ), c, avail ); } else { //~ %1$s: item name, %2$d: charge requirement return string_format( npgettext( "requirement", "%2$d %1$s", "%2$d %1$s", c ), - item_temp.tname( 1, tname::item_name ), c ); + item_temp.tname( 1, tname::base_item_name ), c ); } } else { if( avail == item::INFINITE_CHARGES ) { @@ -193,16 +193,16 @@ std::string item_comp::to_string( const int batch, const int avail ) const return string_format( npgettext( "requirement", "%2$d %1$s (have infinite)", "%2$d %1$s (have infinite)", c ), - item_temp.tname( c, tname::item_name ), c ); + item_temp.tname( c, tname::base_item_name ), c ); } else if( avail > 0 ) { //~ %1$s: item name, %2$d: required count, %3%d: available count return string_format( npgettext( "requirement", "%2$d %1$s (have %3$d)", "%2$d %1$s (have %3$d)", c ), - item_temp.tname( c, tname::item_name ), c, avail ); + item_temp.tname( c, tname::base_item_name ), c, avail ); } else { //~ %1$s: item name, %2$d: required count return string_format( npgettext( "requirement", "%2$d %1$s", "%2$d %1$s", c ), - item_temp.tname( c, tname::item_name ), c ); + item_temp.tname( c, tname::base_item_name ), c ); } } } diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index 373183b7aaeb2..de598b0f437fa 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -112,6 +112,8 @@ static const recipe_id recipe_test_tallow2( "test_tallow2" ); static const recipe_id recipe_test_waist_apron_long( "test_waist_apron_long" ); static const recipe_id recipe_test_waist_apron_long_pink_apron_cotton( "test_waist_apron_long_pink_apron_cotton" ); +static const recipe_id +recipe_test_xl_waist_apron_long_pink_apron_cotton( "test_xl_waist_apron_long_pink_apron_cotton" ); static const recipe_id recipe_vambrace_larmor( "vambrace_larmor" ); static const recipe_id recipe_water_clean( "water_clean" ); @@ -2247,11 +2249,12 @@ TEST_CASE( "variant_crafting_recipes", "[crafting][slow]" ) tools.emplace_back( "scissors" ); tools.insert( tools.end(), 10, item( "sheet_cotton" ) ); tools.insert( tools.end(), 10, item( "thread" ) ); - prep_craft( recipe_test_waist_apron_long, tools, true ); - actually_test_craft( recipe_test_waist_apron_long, INT_MAX, 10 ); + const recipe_id apron_recipe = recipe_test_waist_apron_long; + prep_craft( apron_recipe, tools, true ); + actually_test_craft( apron_recipe, INT_MAX, 10 ); item_location apron = player_character.get_wielded_item(); - REQUIRE( apron->type->get_id() == recipe_test_waist_apron_long->result() ); + REQUIRE( apron->type->get_id() == apron_recipe->result() ); REQUIRE( apron->has_itype_variant() ); if( variant_counts.count( apron->itype_variant().id ) == 0 ) { @@ -2275,11 +2278,12 @@ TEST_CASE( "variant_crafting_recipes", "[crafting][slow]" ) tools.emplace_back( "scissors" ); tools.insert( tools.end(), 10, item( "sheet_cotton" ) ); tools.insert( tools.end(), 10, item( "thread" ) ); - prep_craft( recipe_test_waist_apron_long_pink_apron_cotton, tools, true ); - actually_test_craft( recipe_test_waist_apron_long_pink_apron_cotton, INT_MAX, 10 ); + const recipe_id apron_recipe = recipe_test_xl_waist_apron_long_pink_apron_cotton; + prep_craft( apron_recipe, tools, true ); + actually_test_craft( apron_recipe, INT_MAX, 10 ); item_location apron = player_character.get_wielded_item(); - REQUIRE( apron->type->get_id() == recipe_test_waist_apron_long_pink_apron_cotton->result() ); + REQUIRE( apron->type->get_id() == apron_recipe->result() ); REQUIRE( apron->has_itype_variant() ); if( apron->itype_variant().id == "pink_apron_cotton" ) { @@ -2288,6 +2292,14 @@ TEST_CASE( "variant_crafting_recipes", "[crafting][slow]" ) } CHECK( specific_variant_count == max_iters ); } + SECTION( "recipe names" ) { + const recipe_id basic_recipe = recipe_test_waist_apron_long; + CHECK( basic_recipe.obj().result_name() == "long waist apron" ); + const recipe_id variant_recipe = recipe_test_waist_apron_long_pink_apron_cotton; + CHECK( variant_recipe.obj().result_name() == "pink long waist apron" ); + const recipe_id variant_prefix_recipe = recipe_test_xl_waist_apron_long_pink_apron_cotton; + CHECK( variant_prefix_recipe.obj().result_name() == "XL pink long waist apron" ); + } } TEST_CASE( "pseudo_tools_in_crafting_inventory", "[crafting][tools]" )