From 5ec4c0b459e2b6ae58a0f5512292bee88ca9907b Mon Sep 17 00:00:00 2001 From: anothersimulacrum Date: Thu, 13 Jun 2024 23:49:11 +0000 Subject: [PATCH] Move recipe categories to generic_factory To enable using copy-from to extend recipe subcategories, so mods that touch the same ones don't overwrite. --- data/json/recipes/recipes.json | 3 + .../recipes/recipes_categories.json | 31 +--- data/mods/DinoMod/recipes/recipes.json | 12 +- .../recipes/psionics_practice.json | 20 +-- src/activity_actor.cpp | 3 +- src/crafting.cpp | 2 +- src/crafting_gui.cpp | 155 +++++++++++------- src/crafting_gui.h | 15 +- src/item_factory.cpp | 4 +- src/itype.h | 2 +- src/iuse.cpp | 5 +- src/output.h | 4 +- src/recipe.cpp | 3 +- src/recipe.h | 2 +- src/recipe_dictionary.cpp | 22 +-- src/recipe_dictionary.h | 7 +- src/type_id.h | 3 + tests/crafting_test.cpp | 4 +- 18 files changed, 158 insertions(+), 139 deletions(-) diff --git a/data/json/recipes/recipes.json b/data/json/recipes/recipes.json index 38e1e1eeb3f74..06248f451ac65 100644 --- a/data/json/recipes/recipes.json +++ b/data/json/recipes/recipes.json @@ -3,6 +3,7 @@ "type": "recipe_category", "id": "CC_*", "//": "This is just a dummy category. No recipes should be added in here.", + "is_wildcard": true, "recipe_subcategories": [ "CSC_*_FAVORITE", "CSC_*_RECENT", "CSC_*_HIDDEN", "CSC_*_NESTED" ] }, { @@ -125,11 +126,13 @@ "type": "recipe_category", "id": "CC_BUILDING", "recipe_subcategories": [ "CSC_ALL", "CSC_BUILDING_BASES", "CSC_BUILDING_EXPANSIONS" ], + "is_building": true, "is_hidden": true }, { "type": "recipe_category", "id": "CC_PRACTICE", + "is_practice": true, "recipe_subcategories": [ "CSC_ALL", "CSC_PRACTICE_SCIENCE", diff --git a/data/mods/Aftershock/recipes/recipes_categories.json b/data/mods/Aftershock/recipes/recipes_categories.json index 2b9bac376a21a..5831b9362d6c8 100644 --- a/data/mods/Aftershock/recipes/recipes_categories.json +++ b/data/mods/Aftershock/recipes/recipes_categories.json @@ -2,36 +2,13 @@ { "type": "recipe_category", "id": "CC_ELECTRONIC", - "recipe_subcategories": [ - "CSC_ALL", - "CSC_ELECTRONIC_CBMS", - "CSC_ELECTRONIC_TOOLS", - "CSC_ELECTRONIC_PARTS", - "CSC_ELECTRONIC_LIGHTING", - "CSC_ELECTRONIC_COMPONENTS", - "CSC_ELECTRONIC_OTHER" - ] + "copy-from": "CC_ELECTRONIC", + "extend": { "recipe_subcategories": [ "CSC_ELECTRONIC_CBMS" ] } }, { "type": "recipe_category", "id": "CC_PRACTICE", - "recipe_subcategories": [ - "CSC_ALL", - "CSC_PRACTICE_SCIENCE", - "CSC_PRACTICE_ELECTRONICS", - "CSC_PRACTICE_FABRICATION", - "CSC_PRACTICE_FOOD", - "CSC_PRACTICE_SURVIVAL", - "CSC_PRACTICE_TAILORING", - "CSC_PRACTICE_ATHLETICS", - "CSC_PRACTICE_COMPUTERS", - "CSC_PRACTICE_DEVICES", - "CSC_PRACTICE_HEALTH", - "CSC_PRACTICE_MECHANICS", - "CSC_PRACTICE_SOCIAL", - "CSC_PRACTICE_MELEE", - "CSC_PRACTICE_RANGED", - "CSC_PRACTICE_PSI" - ] + "copy-from": "CC_PRACTICE", + "extend": { "recipe_subcategories": [ "CSC_PRACTICE_PSI" ] } } ] diff --git a/data/mods/DinoMod/recipes/recipes.json b/data/mods/DinoMod/recipes/recipes.json index adb8642836f1c..bab8f4537615f 100644 --- a/data/mods/DinoMod/recipes/recipes.json +++ b/data/mods/DinoMod/recipes/recipes.json @@ -2,15 +2,7 @@ { "type": "recipe_category", "id": "CC_ANIMALS", - "recipe_subcategories": [ - "CSC_ALL", - "CSC_ANIMALS_BOVINE ARMOR", - "CSC_ANIMALS_CANINE ARMOR", - "CSC_ANIMALS_EQUINE ARMOR", - "CSC_ANIMALS_ELEPHANT ARMOR", - "CSC_ANIMALS_OSTRICH ARMOR", - "CSC_ANIMALS_BEAR ARMOR", - "CSC_ANIMALS_EQUINE STORAGE" - ] + "copy-from": "CC_ANIMALS", + "extend": { "recipe_subcategories": [ "CSC_ANIMALS_ELEPHANT ARMOR", "CSC_ANIMALS_OSTRICH ARMOR", "CSC_ANIMALS_BEAR ARMOR" ] } } ] diff --git a/data/mods/MindOverMatter/recipes/psionics_practice.json b/data/mods/MindOverMatter/recipes/psionics_practice.json index bce81eebdbef9..c3be72f6f6fdb 100644 --- a/data/mods/MindOverMatter/recipes/psionics_practice.json +++ b/data/mods/MindOverMatter/recipes/psionics_practice.json @@ -2,24 +2,8 @@ { "type": "recipe_category", "id": "CC_PRACTICE", - "recipe_subcategories": [ - "CSC_ALL", - "CSC_PRACTICE_SCIENCE", - "CSC_PRACTICE_ELECTRONICS", - "CSC_PRACTICE_FABRICATION", - "CSC_PRACTICE_FOOD", - "CSC_PRACTICE_SURVIVAL", - "CSC_PRACTICE_TAILORING", - "CSC_PRACTICE_ATHLETICS", - "CSC_PRACTICE_COMPUTERS", - "CSC_PRACTICE_DEVICES", - "CSC_PRACTICE_HEALTH", - "CSC_PRACTICE_MECHANICS", - "CSC_PRACTICE_SOCIAL", - "CSC_PRACTICE_MELEE", - "CSC_PRACTICE_RANGED", - "CSC_PRACTICE_METAPHYSICS" - ] + "copy-from": "CC_PRACTICE", + "extend": { "recipe_subcategories": [ "CSC_PRACTICE_METAPHYSICS" ] } }, { "id": "prac_psionics_begin", diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 96cbfc71b9c64..c0aa3bcb2612f 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -36,6 +36,7 @@ #include "contents_change_handler.h" #include "coordinates.h" #include "craft_command.h" +#include "crafting_gui.h" #include "creature.h" #include "creature_tracker.h" #include "debug.h" @@ -3768,7 +3769,7 @@ void craft_activity_actor::canceled( player_activity &/*act*/, Character &/*who* } const recipe item_recipe = craft->get_making(); // practice recipe items with no components can be safely removed - if( item_recipe.category == "CC_PRACTICE" && craft->components.empty() ) { + if( item_recipe.category->is_practice && craft->components.empty() ) { craft_item.remove_item(); } } diff --git a/src/crafting.cpp b/src/crafting.cpp index 7a689c9d91b5f..99534f674c6be 100644 --- a/src/crafting.cpp +++ b/src/crafting.cpp @@ -157,7 +157,7 @@ static bool crafting_allowed( const Character &p, const recipe &rec ) return false; } - if( rec.category == "CC_BUILDING" ) { + if( rec.category->is_building ) { add_msg( m_info, _( "Overmap terrain building recipes are not implemented yet!" ) ); return false; } diff --git a/src/crafting_gui.cpp b/src/crafting_gui.cpp index e86a6b14e720e..06a15c5d356e9 100644 --- a/src/crafting_gui.cpp +++ b/src/crafting_gui.cpp @@ -91,9 +91,24 @@ static const std::map craft_speed_reaso {NORMAL_CRAFTING, to_translation( "craftable" )} }; -// TODO: Convert these globals to handling categories via generic_factory? -static std::vector craft_cat_list; -static std::map > craft_subcat_list; +namespace +{ + +generic_factory craft_cat_list( "recipe_category" ); + +} // namespace + +template<> +const crafting_category &string_id::obj() const +{ + return craft_cat_list.obj( *this ); +} + +template<> +bool string_id::is_valid() const +{ + return craft_cat_list.is_valid( *this ); +} static bool query_is_yes( std::string_view query ); static void draw_hidden_amount( const catacurses::window &w, int amount, int num_recipe ); @@ -127,32 +142,39 @@ static std::string get_cat_unprefixed( const std::string_view prefixed_name ) return std::string( prefixed_name.substr( 3, prefixed_name.size() - 3 ) ); } -void load_recipe_category( const JsonObject &jsobj ) +void load_recipe_category( const JsonObject &jsobj, const std::string &src ) { - const std::string category = jsobj.get_string( "id" ); - const bool is_hidden = jsobj.get_bool( "is_hidden", false ); - - if( category.find( "CC_" ) != 0 ) { - jsobj.throw_error( "Crafting category id has to be prefixed with 'CC_'" ); - } - - if( !is_hidden - && std::find( craft_cat_list.begin(), craft_cat_list.end(), category ) == craft_cat_list.end() - ) { - craft_cat_list.push_back( category ); - } - - const std::string cat_name = get_cat_unprefixed( category ); + craft_cat_list.load( jsobj, src ); +} - craft_subcat_list[category].clear(); - for( const std::string subcat_id : jsobj.get_array( "recipe_subcategories" ) ) { +void crafting_category::load( const JsonObject &jo, const std::string_view ) +{ + // Ensure id is correct + if( id.str().find( "CC_" ) != 0 ) { + jo.throw_error( "Crafting category id has to be prefixed with 'CC_'" ); + } + + optional( jo, was_loaded, "is_hidden", is_hidden, false ); + optional( jo, was_loaded, "is_practice", is_practice, false ); + optional( jo, was_loaded, "is_building", is_building, false ); + optional( jo, was_loaded, "is_wildcard", is_wildcard, false ); + mandatory( jo, was_loaded, "recipe_subcategories", subcategories, + auto_flags_reader<> {} ); + + // Ensure subcategory ids are correct and remove dupes + std::string cat_name = get_cat_unprefixed( id.str() ); + std::unordered_set known; + for( auto it = subcategories.begin(); it != subcategories.end(); ) { + const std::string &subcat_id = *it; if( subcat_id.find( "CSC_" + cat_name + "_" ) != 0 && subcat_id != "CSC_ALL" ) { - jsobj.throw_error( "Crafting sub-category id has to be prefixed with CSC__" ); + jo.throw_error( "Crafting sub-category id has to be prefixed with CSC__" ); } - if( find( craft_subcat_list[category].begin(), craft_subcat_list[category].end(), - subcat_id ) == craft_subcat_list[category].end() ) { - craft_subcat_list[category].push_back( subcat_id ); + if( known.find( subcat_id ) != known.end() ) { + it = subcategories.erase( it ); + continue; } + known.emplace( subcat_id ); + ++it; } } @@ -170,8 +192,7 @@ static std::string get_subcat_unprefixed( const std::string_view cat, void reset_recipe_categories() { - craft_cat_list.clear(); - craft_subcat_list.clear(); + craft_cat_list.reset(); } static bool cannot_gain_skill_or_prof( const Character &crafter, const recipe &recp ) @@ -860,7 +881,7 @@ void recipe_result_info_cache::insert_iteminfo_block_separator( std::vector, bool> -recipes_from_cat( const recipe_subset &available_recipes, const std::string &cat, +recipes_from_cat( const recipe_subset &available_recipes, const crafting_category_id &cat, const std::string &subcat ) { if( subcat == "CSC_*_FAVORITE" ) { @@ -1282,8 +1303,16 @@ std::pair select_crafter_and_crafting_recipe( int & bool is_filtered_unread = false; std::map is_cat_unread; std::map> is_subcat_unread; - tab_list tab( craft_cat_list ); - tab_list subtab( craft_subcat_list[tab.cur()] ); + std::vector crafting_categories; + crafting_categories.reserve( craft_cat_list.size() ); + for( const crafting_category &cat : craft_cat_list.get_all() ) { + if( cat.is_hidden ) { + continue; + } + crafting_categories.emplace_back( cat.id.str() ); + } + tab_list tab( crafting_categories ); + tab_list subtab( crafting_category_id( tab.cur() )->subcategories ); std::map> translated_tab_map; std::map> translated_subtab_map; std::map> list_map; @@ -1330,13 +1359,11 @@ std::pair select_crafter_and_crafting_recipe( int & gotocat.end(), [&goto_recipe]( const recipe * r ) { return r && r->ident() == goto_recipe; } ); - if( gotorec != gotocat.end() && - std::find( craft_cat_list.begin(), craft_cat_list.end(), - goto_recipe->category ) != craft_cat_list.end() ) { - while( tab.cur() != goto_recipe->category ) { + if( gotorec != gotocat.end() && ( *gotorec )->category.is_valid() ) { + while( tab.cur() != goto_recipe->category.str() ) { tab.next(); } - subtab = tab_list( craft_subcat_list[tab.cur()] ); + subtab = tab_list( crafting_category_id( tab.cur() )->subcategories ); chosen = *gotorec; show_hidden = true; keepline = true; @@ -1349,12 +1376,12 @@ std::pair select_crafter_and_crafting_recipe( int & ui.on_redraw( [&]( ui_adaptor & ui ) { if( highlight_unread_recipes && recalc_unread ) { if( filterstring.empty() ) { - for( const std::string &cat : craft_cat_list ) { + for( const std::string &cat : crafting_categories ) { is_cat_unread[cat] = false; - for( const std::string &subcat : craft_subcat_list[cat] ) { + for( const std::string &subcat : crafting_category_id( cat )->subcategories ) { is_subcat_unread[cat][subcat] = false; const std::pair, bool> result = recipes_from_cat( available_recipes, - cat, subcat ); + crafting_category_id( cat ), subcat ); const std::vector &recipes = result.first; const bool include_hidden = result.second; for( const recipe *const rcp : recipes ) { @@ -1583,7 +1610,7 @@ std::pair select_crafter_and_crafting_recipe( int & picking.insert( picking.end(), filtered_recipes.begin(), filtered_recipes.end() ); } else { const std::pair, bool> result = recipes_from_cat( available_recipes, - tab.cur(), subtab.cur() ); + crafting_category_id( tab.cur() ), subtab.cur() ); show_hidden = result.second; if( show_hidden ) { current = result.first; @@ -1722,7 +1749,7 @@ std::pair select_crafter_and_crafting_recipe( int & if( entry.second.contains( local_coord ) ) { tab.set_index( entry.first ); recalc = true; - subtab = tab_list( craft_subcat_list[tab.cur()] ); + subtab = tab_list( crafting_category_id( tab.cur() )->subcategories ); handled = true; } } @@ -1751,8 +1778,9 @@ std::pair select_crafter_and_crafting_recipe( int & std::string start = subtab.cur(); do { subtab.prev(); - } while( subtab.cur() != start && available_recipes.empty_category( tab.cur(), - subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); + } while( subtab.cur() != start && + available_recipes.empty_category( crafting_category_id( tab.cur() ), + subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); recalc = true; } else if( action == "SCROLL_ITEM_INFO_UP" ) { line_item_info -= scroll_item_info_lines; @@ -1766,7 +1794,7 @@ std::pair select_crafter_and_crafting_recipe( int & mouse_in_window( coord, w_head_tabs ) ) ) { tab.prev(); // Default ALL - subtab = tab_list( craft_subcat_list[tab.cur()] ); + subtab = tab_list( crafting_category_id( tab.cur() )->subcategories ); recalc = true; } else if( action == "RIGHT" || ( action == "SCROLL_DOWN" && mouse_in_window( coord, w_subhead ) ) ) { @@ -1776,14 +1804,15 @@ std::pair select_crafter_and_crafting_recipe( int & std::string start = subtab.cur(); do { subtab.next(); - } while( subtab.cur() != start && available_recipes.empty_category( tab.cur(), - subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); + } while( subtab.cur() != start && + available_recipes.empty_category( crafting_category_id( tab.cur() ), + subtab.cur() != "CSC_ALL" ? subtab.cur() : "" ) ); recalc = true; } else if( action == "NEXT_TAB" || ( action == "SCROLL_DOWN" && mouse_in_window( coord, w_head_tabs ) ) ) { tab.next(); // Default ALL - subtab = tab_list( craft_subcat_list[tab.cur()] ); + subtab = tab_list( crafting_category_id( tab.cur() )->subcategories ); recalc = true; } else if( action == "DOWN" ) { if( !previously_toggled_unread ) { @@ -2333,12 +2362,15 @@ static std::map> draw_recipe_tabs( const cata case NORMAL: { std::vector translated_cats; translated_cats.reserve( craft_cat_list.size() ); - for( const std::string &cat : craft_cat_list ) { - if( unread[ cat ] ) { + for( const crafting_category &cat : craft_cat_list.get_all() ) { + if( cat.is_hidden ) { + continue; + } + if( unread[ cat.id.str() ] ) { translated_cats.emplace_back( _( get_cat_unprefixed( - cat ) ).append( "⁺" ) ); + cat.id.str() ) ).append( "⁺" ) ); } else { - translated_cats.emplace_back( _( get_cat_unprefixed( cat ) ) ); + translated_cats.emplace_back( _( get_cat_unprefixed( cat.id.str() ) ) ); } } std::pair, size_t> fitted_tabs = fit_tabs_to_width( getmaxx( w ), @@ -2390,19 +2422,22 @@ static std::map> draw_recipe_subtabs( std::vector translated_subcats; std::vector empty_subcats; std::vector unread_subcats; - translated_subcats.reserve( craft_subcat_list[tab].size() ); - empty_subcats.reserve( craft_subcat_list[tab].size() ); - unread_subcats.reserve( craft_subcat_list[tab].size() ); - for( const std::string &subcat : craft_subcat_list[tab] ) { + crafting_category_id current_cat = crafting_category_id( tab ); + size_t subcats_count = current_cat->subcategories.size(); + translated_subcats.reserve( subcats_count ); + empty_subcats.reserve( subcats_count ); + unread_subcats.reserve( subcats_count ); + for( const std::string &subcat : current_cat->subcategories ) { translated_subcats.emplace_back( _( get_subcat_unprefixed( tab, subcat ) ) ); - empty_subcats.emplace_back( available_recipes.empty_category( tab, - subcat != "CSC_ALL" ? subcat : "" ) ); + empty_subcats.emplace_back( available_recipes.empty_category( + crafting_category_id( tab ), + subcat != "CSC_ALL" ? subcat : "" ) ); unread_subcats.emplace_back( unread[subcat] ); } std::pair, size_t> fitted_subcat_list = fit_tabs_to_width( getmaxx( w ), subtab, translated_subcats ); size_t offset = fitted_subcat_list.second; - if( fitted_subcat_list.first.size() + offset > craft_subcat_list[tab].size() ) { + if( fitted_subcat_list.first.size() + offset > subcats_count ) { break; } // Draw the tabs on each other @@ -2440,9 +2475,9 @@ static std::map> draw_recipe_subtabs( const std::vector *subcategories_for_category( const std::string &category ) { - auto it = craft_subcat_list.find( category ); - if( it != craft_subcat_list.end() ) { - return &it->second; + crafting_category_id cat( category ); + if( !cat.is_valid() ) { + return nullptr; } - return nullptr; + return &cat->subcategories; } diff --git a/src/crafting_gui.h b/src/crafting_gui.h index 0e2039bbe1d85..149cc934dbeec 100644 --- a/src/crafting_gui.h +++ b/src/crafting_gui.h @@ -23,11 +23,24 @@ class recipe; std::pair select_crafter_and_crafting_recipe( int &batch_size_out, const recipe_id &goto_recipe, Character *crafter, std::string filterstring = "" ); -void load_recipe_category( const JsonObject &jsobj ); +void load_recipe_category( const JsonObject &jsobj, const std::string &src ); void reset_recipe_categories(); // Returns nullptr if the category does not exist, or a pointer to its vector // of subcategories it the category does exist const std::vector *subcategories_for_category( const std::string &category ); +struct crafting_category { + crafting_category_id id; + bool was_loaded = false; + + bool is_hidden; + bool is_practice; + bool is_building; + bool is_wildcard; + std::vector subcategories; + + void load( const JsonObject &jo, std::string_view src ); +}; + #endif // CATA_SRC_CRAFTING_GUI_H diff --git a/src/item_factory.cpp b/src/item_factory.cpp index d7987d7c55c9f..b5af8bfff8fc6 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -66,6 +66,8 @@ static const ammo_effect_str_id ammo_effect_SPECIAL_COOKOFF( "SPECIAL_COOKOFF" ) static const ammotype ammo_NULL( "NULL" ); +static const crafting_category_id crafting_category_CC_FOOD( "CC_FOOD" ); + static const damage_type_id damage_bash( "bash" ); static const damage_type_id damage_bullet( "bullet" ); @@ -2666,7 +2668,7 @@ static void load_memory_card_data( memory_card_info &mcd, const JsonObject &jo ) mcd.recipes_categories.emplace( cat ); } } else { - mcd.recipes_categories = { "CC_FOOD" }; + mcd.recipes_categories = { crafting_category_CC_FOOD }; } if( jo.has_member( "secret_recipes" ) ) { mcd.secret_recipes = jo.get_bool( "secret_recipes" ); diff --git a/src/itype.h b/src/itype.h index 5a8dcff50cfee..68afdb3d90008 100644 --- a/src/itype.h +++ b/src/itype.h @@ -1182,7 +1182,7 @@ struct memory_card_info { int recipes_amount; int recipes_level_min; int recipes_level_max; - std::set recipes_categories; + std::set recipes_categories; bool secret_recipes; }; diff --git a/src/iuse.cpp b/src/iuse.cpp index 8a4e5679dee52..32a72b0ada2a8 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -146,6 +146,8 @@ static const construction_str_id construction_constr_pit( "constr_pit" ); static const construction_str_id construction_constr_pit_shallow( "constr_pit_shallow" ); static const construction_str_id construction_constr_water_channel( "constr_water_channel" ); +static const crafting_category_id crafting_category_CC_FOOD( "CC_FOOD" ); + static const efftype_id effect_adrenaline( "adrenaline" ); static const efftype_id effect_antibiotic( "antibiotic" ); static const efftype_id effect_antibiotic_visible( "antibiotic_visible" ); @@ -7569,7 +7571,8 @@ std::optional iuse::multicooker( Character *p, item *it, const tripoint &po int counter = 0; static const std::set multicooked_subcats = { "CSC_FOOD_MEAT", "CSC_FOOD_VEGGI", "CSC_FOOD_PASTA" }; - for( const recipe * const &r : get_avatar().get_learned_recipes().in_category( "CC_FOOD" ) ) { + for( const recipe * const &r : get_avatar().get_learned_recipes().in_category( + crafting_category_CC_FOOD ) ) { if( multicooked_subcats.count( r->subcategory ) > 0 ) { dishes.push_back( r ); const bool can_make = r->deduped_requirements().can_make_with_inventory( diff --git a/src/output.h b/src/output.h index eddd46c7553a3..124874c520099 100644 --- a/src/output.h +++ b/src/output.h @@ -1221,9 +1221,9 @@ class tab_list { private: size_t _index = 0; - std::vector *_list; + const std::vector *_list; public: - explicit tab_list( std::vector &_list ) : _list( &_list ) { + explicit tab_list( const std::vector &_list ) : _list( &_list ) { } void last() { diff --git a/src/recipe.cpp b/src/recipe.cpp index a2515db29da86..06a6e97b290f6 100644 --- a/src/recipe.cpp +++ b/src/recipe.cpp @@ -15,6 +15,7 @@ #include "cata_utility.h" #include "character.h" #include "color.h" +#include "crafting_gui.h" #include "debug.h" #include "enum_traits.h" #include "effect_on_condition.h" @@ -662,7 +663,7 @@ void recipe::add_requirements( const std::vector> std::string recipe::get_consistency_error() const { - if( category == "CC_BUILDING" ) { + if( category.is_valid() && category->is_building ) { if( is_blueprint() || oter_str_id( result_.c_str() ).is_valid() ) { return std::string(); } diff --git a/src/recipe.h b/src/recipe.h index 0a8a7e63f895a..9e9532bf75787 100644 --- a/src/recipe.h +++ b/src/recipe.h @@ -123,7 +123,7 @@ class recipe bool was_loaded = false; bool obsolete = false; - std::string category; + crafting_category_id category; std::string subcategory; translation description; diff --git a/src/recipe_dictionary.cpp b/src/recipe_dictionary.cpp index 0566f6832257c..1aa1417a547c4 100644 --- a/src/recipe_dictionary.cpp +++ b/src/recipe_dictionary.cpp @@ -341,7 +341,8 @@ std::vector recipe_subset::recipes_that_produce( const itype_id return res; } -bool recipe_subset::empty_category( const std::string &cat, const std::string &subcat ) const +bool recipe_subset::empty_category( const crafting_category_id &cat, + const std::string &subcat ) const { if( subcat == "CSC_*_FAVORITE" ) { return uistate.favorite_recipes.empty(); @@ -349,7 +350,7 @@ bool recipe_subset::empty_category( const std::string &cat, const std::string &s return uistate.recent_recipes.empty(); } else if( subcat == "CSC_*_HIDDEN" ) { return uistate.hidden_recipes.empty(); - } else if( cat == "CC_*" ) { + } else if( cat->is_wildcard ) { //any other category in CC_* is populated return false; } @@ -369,7 +370,7 @@ bool recipe_subset::empty_category( const std::string &cat, const std::string &s return true; } -std::vector recipe_subset::in_category( const std::string &cat, +std::vector recipe_subset::in_category( const crafting_category_id &cat, const std::string &subcat ) const { std::vector res; @@ -676,7 +677,7 @@ void recipe_dictionary::check_consistency() for( const auto &e : recipe_dict.recipes ) { const recipe &r = e.second; - if( r.category.empty() ) { + if( r.category.str().empty() ) { if( !r.subcategory.empty() ) { debugmsg( "recipe %s has subcategory but no category", r.ident().str() ); } @@ -684,18 +685,19 @@ void recipe_dictionary::check_consistency() continue; } - const std::vector *subcategories = subcategories_for_category( r.category ); - if( !subcategories ) { - debugmsg( "recipe %s has invalid category %s", r.ident().str(), r.category ); + if( !r.category.is_valid() ) { + debugmsg( "recipe %s has invalid category %s", r.ident().str(), r.category.str() ); continue; } + const std::vector &subcategories = r.category->subcategories; + if( !r.subcategory.empty() ) { - auto it = std::find( subcategories->begin(), subcategories->end(), r.subcategory ); - if( it == subcategories->end() ) { + auto it = std::find( subcategories.begin(), subcategories.end(), r.subcategory ); + if( it == subcategories.end() ) { debugmsg( "recipe %s has subcategory %s which is invalid or doesn't match category %s", - r.ident().str(), r.subcategory, r.category ); + r.ident().str(), r.subcategory, r.category.str() ); } } } diff --git a/src/recipe_dictionary.h b/src/recipe_dictionary.h index 56375fed242d0..ffeef002336f1 100644 --- a/src/recipe_dictionary.h +++ b/src/recipe_dictionary.h @@ -128,11 +128,12 @@ class recipe_subset int get_custom_difficulty( const recipe *r ) const; /** Check if there is any recipes in given category (optionally restricted to subcategory), index which is for nested categories */ - bool empty_category( const std::string &cat, const std::string &subcat = std::string() ) const; + bool empty_category( const crafting_category_id &cat, + const std::string &subcat = std::string() ) const; /** Get all recipes in given category (optionally restricted to subcategory) */ std::vector in_category( - const std::string &cat, + const crafting_category_id &cat, const std::string &subcat = std::string() ) const; /** Returns all recipes which could use component */ @@ -202,7 +203,7 @@ class recipe_subset private: std::set recipes; std::map difficulties; - std::map> category; + std::map> category; std::map> component; }; diff --git a/src/type_id.h b/src/type_id.h index 13685d8e419b9..bfbe45db19c4c 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -58,6 +58,9 @@ using construction_group_str_id = string_id; struct clothing_mod; using clothing_mod_id = string_id; +struct crafting_category; +using crafting_category_id = string_id; + struct effect_on_condition; using effect_on_condition_id = string_id; diff --git a/tests/crafting_test.cpp b/tests/crafting_test.cpp index ea93639d0200a..a1ce14c96d21a 100644 --- a/tests/crafting_test.cpp +++ b/tests/crafting_test.cpp @@ -44,6 +44,8 @@ static const activity_id ACT_CRAFT( "ACT_CRAFT" ); +static const crafting_category_id crafting_category_CC_FOOD( "CC_FOOD" ); + static const flag_id json_flag_ITEM_BROKEN( "ITEM_BROKEN" ); static const flag_id json_flag_USE_UPS( "USE_UPS" ); @@ -142,7 +144,7 @@ TEST_CASE( "recipe_subset" ) CHECK( subset.get_custom_difficulty( r ) == r->difficulty ); } THEN( "it's in the right category" ) { - const auto cat_recipes( subset.in_category( "CC_FOOD" ) ); + const auto cat_recipes( subset.in_category( crafting_category_CC_FOOD ) ); CHECK( cat_recipes.size() == 1 ); CHECK( std::find( cat_recipes.begin(), cat_recipes.end(), r ) != cat_recipes.end() );