From ea4db65daa1afeaf508af1226a4beb218390afae Mon Sep 17 00:00:00 2001 From: Vollch Date: Sun, 3 Dec 2023 02:59:37 +0300 Subject: [PATCH] refactor(port): Make mutation_category ids be string_ids (#3810) * Make mutation_category ids be string_ids The mutation_category_trait objects have ids, but they were just std::strings. Make them be a string_id instead. This is a general move towards type safety, although the specific goal I wanted to achieve was better validity checking of cata_variants. * Removed mutation_category_id exclusion from mapgen type check --------- Co-authored-by: John Bytheway --- src/cata_variant.h | 3 +- src/character.cpp | 10 +++--- src/character.h | 10 +++--- src/consumption.cpp | 8 +++-- src/iuse.cpp | 9 +++--- src/iuse_actor.cpp | 10 +++--- src/iuse_actor.h | 4 +-- src/magic_spell_effect.cpp | 2 +- src/map_field.cpp | 5 +-- src/mapgen.cpp | 5 +-- src/memorial_logger.cpp | 2 +- src/mutation.cpp | 52 +++++++++++++++++--------------- src/mutation.h | 15 ++++----- src/mutation_data.cpp | 28 ++++++++++------- src/mutation_data.h | 5 +-- src/npc_class.cpp | 9 +++--- src/npc_class.h | 4 +-- src/player_hardcoded_effects.cpp | 8 ++--- src/type_id.h | 3 ++ src/wish.cpp | 4 +-- tests/memorial_test.cpp | 2 +- tests/modify_morale_test.cpp | 2 +- tests/mutation_test.cpp | 19 ++++++------ 23 files changed, 117 insertions(+), 102 deletions(-) diff --git a/src/cata_variant.h b/src/cata_variant.h index 2b6ad61d5ff0..2910b59132ff 100644 --- a/src/cata_variant.h +++ b/src/cata_variant.h @@ -287,7 +287,8 @@ template<> struct convert : convert_enum {}; template<> -struct convert : convert_string {}; +struct convert : + convert_string_id {}; template<> struct convert : convert_string_id {}; diff --git a/src/character.cpp b/src/character.cpp index 707cce4f12df..3836d2a02ef0 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -8300,7 +8300,7 @@ void Character::set_highest_cat_level() for( const std::pair &i : dependency_map ) { const mutation_branch &mdata = i.first.obj(); if( !mdata.flags.count( flag_NON_THRESH ) ) { - for( const std::string &cat : mdata.category ) { + for( const mutation_category_id &cat : mdata.category ) { // Decay category strength based on how far it is from the current mutation mutation_category_level[cat] += 8 / static_cast( std::pow( 2, i.second ) ); } @@ -8333,17 +8333,17 @@ void Character::drench_mut_calc() } /// Returns the mutation category with the highest strength -std::string Character::get_highest_category() const +mutation_category_id Character::get_highest_category() const { int iLevel = 0; - std::string sMaxCat; + mutation_category_id sMaxCat; - for( const std::pair &elem : mutation_category_level ) { + for( const std::pair &elem : mutation_category_level ) { if( elem.second > iLevel ) { sMaxCat = elem.first; iLevel = elem.second; } else if( elem.second == iLevel ) { - sMaxCat.clear(); // no category on ties + sMaxCat = mutation_category_id(); // no category on ties } } return sMaxCat; diff --git a/src/character.h b/src/character.h index f8a25d6d4b13..53856cc79220 100644 --- a/src/character.h +++ b/src/character.h @@ -923,7 +923,7 @@ class Character : public Creature, public location_visitable /** Returns true if the player doesn't have the mutation or a conflicting one and it complies with the force typing */ bool mutation_ok( const trait_id &mutation, bool force_good, bool force_bad ) const; /** Picks a random valid mutation in a category and mutate_towards() it */ - void mutate_category( const std::string &mut_cat ); + void mutate_category( const mutation_category_id &mut_cat ); /** Mutates toward one of the given mutations, upgrading or removing conflicts if necessary */ bool mutate_towards( std::vector muts, int num_tries = INT_MAX ); /** Mutates toward the entered mutation, upgrading or removing conflicts if necessary */ @@ -939,7 +939,7 @@ class Character : public Creature, public location_visitable /** Recalculates mutation_category_level[] values for the player */ void set_highest_cat_level(); /** Returns the highest mutation category */ - std::string get_highest_category() const; + mutation_category_id get_highest_category() const; /** Recalculates mutation drench protection for all bodyparts (ignored/good/neutral stats) */ void drench_mut_calc(); /** Recursively traverses the mutation's prerequisites and replacements, building up a map */ @@ -949,8 +949,8 @@ class Character : public Creature, public location_visitable /** * Returns true if this category of mutation is allowed. */ - bool is_category_allowed( const std::vector &category ) const; - bool is_category_allowed( const std::string &category ) const; + bool is_category_allowed( const std::vector &category ) const; + bool is_category_allowed( const mutation_category_id &category ) const; bool is_weak_to_water() const; @@ -1847,7 +1847,7 @@ class Character : public Creature, public location_visitable // the amount healed per bodypart per day std::array healed_total; - std::map mutation_category_level; + std::map mutation_category_level; int adjust_for_focus( int amount ) const; void update_type_of_scent( bool init = false ); diff --git a/src/consumption.cpp b/src/consumption.cpp index 8f9b2de398c3..72a35c9127e7 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -75,6 +75,8 @@ static const efftype_id effect_visuals( "visuals" ); static const itype_id itype_syringe( "syringe" ); +static const mutation_category_id mutation_category_URSINE( "URSINE" ); + static const trait_id trait_ACIDBLOOD( "ACIDBLOOD" ); static const trait_id trait_AMORPHOUS( "AMORPHOUS" ); static const trait_id trait_ANTIFRUIT( "ANTIFRUIT" ); @@ -1151,11 +1153,11 @@ void Character::modify_morale( item &food, int nutr ) } if( food.has_flag( flag_URSINE_HONEY ) && ( !crossed_threshold() || has_trait( trait_THRESH_URSINE ) ) && - mutation_category_level["URSINE"] > 40 ) { + mutation_category_level[mutation_category_URSINE] > 40 ) { // Need at least 5 bear mutations for effect to show, to filter out mutations in common with other categories int honey_fun = has_trait( trait_THRESH_URSINE ) ? - std::min( mutation_category_level["URSINE"] / 8, 20 ) : - mutation_category_level["URSINE"] / 12; + std::min( mutation_category_level[mutation_category_URSINE] / 8, 20 ) : + mutation_category_level[mutation_category_URSINE] / 12; if( honey_fun < 10 ) { add_msg_if_player( m_good, _( "You find the sweet taste of honey surprisingly palatable." ) ); } else { diff --git a/src/iuse.cpp b/src/iuse.cpp index 63d14804f2c5..10254d490202 100644 --- a/src/iuse.cpp +++ b/src/iuse.cpp @@ -1505,7 +1505,7 @@ int iuse::mycus( player *p, item *it, bool t, const tripoint &pos ) } else if( p->has_trait( trait_THRESH_MYCUS ) && !p->has_trait( trait_M_DEPENDENT ) ) { // OK, now set the hook. if( !one_in( 3 ) ) { - p->mutate_category( "MYCUS" ); + p->mutate_category( mutation_category_id( "MYCUS" ) ); p->mod_stored_nutr( 10 ); p->mod_thirst( 10 ); p->mod_fatigue( 5 ); @@ -6000,10 +6000,11 @@ int iuse::bell( player *p, item *it, bool, const tripoint & ) sounds::sound( p->pos(), 12, sounds::sound_t::music, _( "Clank! Clank!" ), true, "misc", "cow_bell" ); if( !p->is_deaf() ) { - const int cow_factor = 1 + ( p->mutation_category_level.find( "CATTLE" ) == - p->mutation_category_level.end() ? + auto cattle_level = + p->mutation_category_level.find( mutation_category_id( "CATTLE" ) ); + const int cow_factor = 1 + ( cattle_level == p->mutation_category_level.end() ? 0 : - ( p->mutation_category_level.find( "CATTLE" )->second ) / 8 + ( cattle_level->second ) / 8 ); if( x_in_y( cow_factor, 1 + cow_factor ) ) { p->add_morale( MORALE_MUSIC, 1, 15 * ( cow_factor > 10 ? 10 : cow_factor ) ); diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 019c8a81906d..382258f8133a 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -4448,7 +4448,7 @@ std::unique_ptr mutagen_actor::clone() const void mutagen_actor::load( const JsonObject &obj ) { - mutation_category = obj.get_string( "mutation_category", "ANY" ); + mutation_category = mutation_category_id( obj.get_string( "mutation_category", "ANY" ) ); is_weak = obj.get_bool( "is_weak", false ); is_strong = obj.get_bool( "is_strong", false ); } @@ -4462,7 +4462,7 @@ int mutagen_actor::use( player &p, item &it, bool, const tripoint & ) const return checks.charges_used; } - bool no_category = mutation_category == "ANY"; + bool no_category = mutation_category == mutation_category_id( "ANY" ); bool balanced = get_option( "BALANCED_MUTATIONS" ); int accumulated_mutagen = p.get_effect_int( effect_accumulated_mutagen ); if( balanced && !is_strong && is_weak && accumulated_mutagen < 2 && no_category && !p.query_yn( @@ -4518,7 +4518,7 @@ std::unique_ptr mutagen_iv_actor::clone() const void mutagen_iv_actor::load( const JsonObject &obj ) { - mutation_category = obj.get_string( "mutation_category", "ANY" ); + mutation_category = mutation_category_id( obj.get_string( "mutation_category", "ANY" ) ); } int mutagen_iv_actor::use( player &p, item &it, bool, const tripoint & ) const @@ -4566,9 +4566,9 @@ int mutagen_iv_actor::use( player &p, item &it, bool, const tripoint & ) const p.mod_thirst( m_category.iv_thirst * mut_count ); p.mod_fatigue( m_category.iv_fatigue * mut_count ); - if( m_category.id == "CHIMERA" ) { + if( m_category.id == mutation_category_id( "CHIMERA" ) ) { p.add_morale( MORALE_MUTAGEN_CHIMERA, m_category.iv_morale, m_category.iv_morale_max ); - } else if( m_category.id == "ELFA" ) { + } else if( m_category.id == mutation_category_id( "ELFA" ) ) { p.add_morale( MORALE_MUTAGEN_ELF, m_category.iv_morale, m_category.iv_morale_max ); } else if( m_category.iv_morale > 0 ) { p.add_morale( MORALE_MUTAGEN_MUTATION, m_category.iv_morale, m_category.iv_morale_max ); diff --git a/src/iuse_actor.h b/src/iuse_actor.h index 587988fa927c..9194740eb760 100644 --- a/src/iuse_actor.h +++ b/src/iuse_actor.h @@ -1162,7 +1162,7 @@ class detach_gunmods_actor : public iuse_actor class mutagen_actor : public iuse_actor { public: - std::string mutation_category; + mutation_category_id mutation_category; bool is_weak = false; bool is_strong = false; @@ -1177,7 +1177,7 @@ class mutagen_actor : public iuse_actor class mutagen_iv_actor : public iuse_actor { public: - std::string mutation_category; + mutation_category_id mutation_category; mutagen_iv_actor() : iuse_actor( "mutagen_iv" ) {} diff --git a/src/magic_spell_effect.cpp b/src/magic_spell_effect.cpp index 3f6199b1ff75..42ebae4b15d9 100644 --- a/src/magic_spell_effect.cpp +++ b/src/magic_spell_effect.cpp @@ -1069,7 +1069,7 @@ void spell_effect::mutate( const spell &sp, Creature &caster, const tripoint &ta if( sp.has_flag( spell_flag::MUTATE_TRAIT ) ) { guy->mutate_towards( trait_id( sp.effect_data() ) ); } else { - guy->mutate_category( sp.effect_data() ); + guy->mutate_category( mutation_category_id( sp.effect_data() ) ); } } sp.make_sound( potential_target ); diff --git a/src/map_field.cpp b/src/map_field.cpp index 423ffd33fe79..c1052facea1f 100644 --- a/src/map_field.cpp +++ b/src/map_field.cpp @@ -1541,8 +1541,9 @@ void map::player_in_field( player &u ) const int intensity = cur.get_field_intensity(); bool inhaled = u.add_env_effect( effect_poison, bp_mouth, 5, intensity * 1_minutes ); if( u.has_trait( trait_THRESH_MYCUS ) || u.has_trait( trait_THRESH_MARLOSS ) || - ( ft == fd_insecticidal_gas && ( u.get_highest_category() == "INSECT" || - u.get_highest_category() == "SPIDER" ) ) ) { + ( ft == fd_insecticidal_gas && + ( u.get_highest_category() == mutation_category_id( "INSECT" ) || + u.get_highest_category() == mutation_category_id( "SPIDER" ) ) ) ) { inhaled |= u.add_env_effect( effect_badpoison, bp_mouth, 5, intensity * 1_minutes ); u.hurtall( rng( intensity, intensity * 2 ), nullptr ); u.add_msg_if_player( m_bad, _( "The %s burns your skin." ), cur.name() ); diff --git a/src/mapgen.cpp b/src/mapgen.cpp index d41924308ad2..fd0d068c83b8 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -1083,10 +1083,7 @@ class mapgen_value const mapgen_parameter ¶m = param_it->second; constexpr cata_variant_type req_type = cata_variant_type_for(); cata_variant_type param_type = param.type(); - // TODO: mutation_category_id also uses std::string as underlaying type, - // migrate to type safe string_id to get rid of this hack - if( param_type != req_type && req_type != cata_variant_type::string - && req_type != cata_variant_type::mutation_category_id ) { + if( param_type != req_type && req_type != cata_variant_type::string ) { debugmsg( "mapgen '%s' uses parameter '%s' of type '%s' in a context " "expecting type '%s'", context, param_name, io::enum_to_string( param_type ), diff --git a/src/memorial_logger.cpp b/src/memorial_logger.cpp index e07b93549142..3afd358bca7a 100644 --- a/src/memorial_logger.cpp +++ b/src/memorial_logger.cpp @@ -656,7 +656,7 @@ void memorial_logger::notify( const cata::event &e ) case event_type::crosses_mutation_threshold: { character_id ch = e.get( "character" ); if( ch == g->u.getID() ) { - std::string category_id = + mutation_category_id category_id = e.get( "category" ); const mutation_category_trait &category = mutation_category_trait::get_category( category_id ); diff --git a/src/mutation.cpp b/src/mutation.cpp index 99e7747dbb8a..8f86628774a0 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -363,7 +363,7 @@ bool Character::has_active_mutation( const trait_id &b ) const return iter != my_mutations.end() && iter->second.powered; } -bool Character::is_category_allowed( const std::vector &category ) const +bool Character::is_category_allowed( const std::vector &category ) const { bool allowed = false; bool restricted = false; @@ -371,7 +371,7 @@ bool Character::is_category_allowed( const std::vector &category ) if( !mut.obj().allowed_category.empty() ) { restricted = true; } - for( const std::string &Mu_cat : category ) { + for( const mutation_category_id &Mu_cat : category ) { if( mut.obj().allowed_category.count( Mu_cat ) ) { allowed = true; break; @@ -386,12 +386,12 @@ bool Character::is_category_allowed( const std::vector &category ) } -bool Character::is_category_allowed( const std::string &category ) const +bool Character::is_category_allowed( const mutation_category_id &category ) const { bool allowed = false; bool restricted = false; for( const trait_id &mut : get_mutations() ) { - for( const std::string &Ch_cat : mut.obj().allowed_category ) { + for( const mutation_category_id &Ch_cat : mut.obj().allowed_category ) { restricted = true; if( Ch_cat == category ) { allowed = true; @@ -686,10 +686,10 @@ static T normalized_map( const T &ctn ) // For removals it's: // 5-4*cat_lvl/max(hi_lvl, 20) // /2 if highest lvl -static std::map calc_category_weights( const std::map &mcl, - bool addition ) +static std::map calc_category_weights( + const std::map &mcl, bool addition ) { - std::map category_weights; + std::map category_weights; auto max_lvl_iter = std::max_element( mcl.begin(), mcl.end(), []( const auto & best, const auto & current ) { return current.second > best.second; @@ -770,17 +770,17 @@ std::map Character::mutation_chances() const } // We need all mutation categories in here - std::map padded_mut_cat_lvl = mutation_category_level; + std::map padded_mut_cat_lvl = mutation_category_level; for( const mutation_branch &traits_iter : mutation_branch::get_all() ) { - for( const std::string &cat : traits_iter.category ) { + for( const mutation_category_id &cat : traits_iter.category ) { // Will do nothing if it exists already padded_mut_cat_lvl.insert( std::make_pair( cat, 0 ) ); } } - const std::map add_weighs = + const std::map add_weighs = calc_category_weights( padded_mut_cat_lvl, true ); - const std::map rem_weighs = + const std::map rem_weighs = calc_category_weights( padded_mut_cat_lvl, false ); // Not normalized @@ -794,14 +794,14 @@ std::map Character::mutation_chances() const if( pm.to.is_valid() ) { float cat_mod = std::accumulate( pm.to->category.begin(), pm.to->category.end(), 0.0f, - [&add_weighs]( float m, const std::string & cat ) { + [&add_weighs]( float m, const mutation_category_id & cat ) { return std::max( m, add_weighs.at( cat ) ); } ); float c = score_difference_to_chance( direction + score_diff ); chances[pm.to] += c * cat_mod; } else if( pm.from.is_valid() ) { float cat_mod = std::accumulate( pm.from->category.begin(), pm.from->category.end(), 0.0f, - [&rem_weighs]( float m, const std::string & cat ) { + [&rem_weighs]( float m, const mutation_category_id & cat ) { return std::min( m, rem_weighs.at( cat ) ); } ); float c = score_difference_to_chance( direction - score_diff ); @@ -870,10 +870,10 @@ void Character::old_mutate() } // Determine the highest mutation category - std::string cat = get_highest_category(); + mutation_category_id cat = get_highest_category(); if( !is_category_allowed( cat ) ) { - cat.clear(); + cat = mutation_category_id(); } // See if we should upgrade/extend an existing mutation... @@ -950,7 +950,7 @@ void Character::old_mutate() } } else { // Remove existing mutations that don't fit into our category - if( !downgrades.empty() && !cat.empty() ) { + if( !downgrades.empty() && !cat.str().empty() ) { size_t roll = rng( 0, downgrades.size() + 4 ); if( roll < downgrades.size() ) { remove_mutation( downgrades[roll] ); @@ -967,10 +967,10 @@ void Character::old_mutate() // there, try again with empty category // CHAOTIC_BAD lets the game pull from any category by default if( !first_pass || has_trait( trait_CHAOTIC_BAD ) ) { - cat.clear(); + cat = mutation_category_id(); } - if( cat.empty() ) { + if( cat.str().empty() ) { // Pull the full list for( const mutation_branch &traits_iter : mutation_branch::get_all() ) { if( traits_iter.valid && is_category_allowed( traits_iter.category ) ) { @@ -996,7 +996,7 @@ void Character::old_mutate() // So we won't repeat endlessly first_pass = false; } - } while( valid.empty() && !cat.empty() ); + } while( valid.empty() && !cat.str().empty() ); if( valid.empty() ) { // Couldn't find anything at all! @@ -1011,11 +1011,11 @@ void Character::old_mutate() } } -void Character::mutate_category( const std::string &cat ) +void Character::mutate_category( const mutation_category_id &cat ) { // Hacky ID comparison is better than separate hardcoded branch used before // TODO: Turn it into the null id - if( cat == "ANY" ) { + if( cat == mutation_category_id( "ANY" ) ) { mutate(); return; } @@ -1585,13 +1585,13 @@ void test_crossing_threshold( Character &guy, const mutation_category_trait &m_c return; } - std::string mutation_category = m_category.id; + mutation_category_id mutation_category = m_category.id; int total = 0; for( const auto &iter : mutation_category_trait::get_all() ) { total += guy.mutation_category_level[ iter.first ]; } // Threshold-breaching - const std::string &primary = guy.get_highest_category(); + const mutation_category_id &primary = guy.get_highest_category(); int breach_power = guy.mutation_category_level[primary]; // Only if you were pushing for more in your primary category. // You wanted to be more like it and less human. @@ -1602,7 +1602,8 @@ void test_crossing_threshold( Character &guy, const mutation_category_trait &m_c // Alpha is similarly eclipsed by other mutation categories. // Will add others if there's serious/demonstrable need. int booster = 0; - if( mutation_category == "URSINE" || mutation_category == "ALPHA" ) { + if( mutation_category == mutation_category_id( "URSINE" ) || + mutation_category == mutation_category_id( "ALPHA" ) ) { booster = 50; } int breacher = breach_power + booster; @@ -1614,7 +1615,8 @@ void test_crossing_threshold( Character &guy, const mutation_category_trait &m_c // Manually removing Carnivore, since it tends to creep in // This is because carnivore is a prerequisite for the // predator-style post-threshold mutations. - if( mutation_category == "URSINE" && guy.has_trait( trait_CARNIVORE ) ) { + if( mutation_category == mutation_category_id( "URSINE" ) && + guy.has_trait( trait_CARNIVORE ) ) { guy.unset_mutation( trait_CARNIVORE ); guy.add_msg_if_player( _( "Your appetite for blood fades." ) ); } diff --git a/src/mutation.h b/src/mutation.h index d25deea6ba26..3b6a9dc2a16d 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -32,7 +32,7 @@ template struct enum_traits; template class string_id; class JsonArray; -extern std::map > mutations_category; +extern std::map > mutations_category; struct mut_attack { /** Text printed when the attack is proced by you */ @@ -220,7 +220,7 @@ struct mutation_branch { std::set can_heal_with; /**List of allowed mutatrion category*/ - std::set allowed_category; + std::set allowed_category; /**List of body parts locked out of bionics*/ std::set no_cbm_on_bp; @@ -264,7 +264,7 @@ struct mutation_branch { std::vector cancels; // Mutations that conflict with this one std::vector replacements; // Mutations that replace this one std::vector additions; // Mutations that add to this one - std::vector category; // Mutation Categories + std::vector category; // Mutation Categories std::set flags; // Mutation flags std::map protection; // Mutation wet effects std::map encumbrance_always; // Mutation encumbrance that always applies @@ -429,7 +429,7 @@ struct mutation_category_trait { std::string memorial_message_female() const; // Mutation category i.e "BIRD", "CHIMERA" - std::string id; + mutation_category_id id; // The trait that you gain when you break the threshold for this category trait_id threshold_mut; @@ -458,8 +458,9 @@ struct mutation_category_trait { bool iv_sleep = false; int iv_sleep_dur = 0; - static const std::map &get_all(); - static const mutation_category_trait &get_category( const std::string &category_id ); + static const std::map &get_all(); + static const mutation_category_trait &get_category( + const mutation_category_id &category_id ); static void reset(); static void check_consistency(); @@ -468,7 +469,7 @@ struct mutation_category_trait { void load_mutation_type( const JsonObject &jsobj ); void reset_mutation_types(); -bool mutation_category_is_valid( const std::string &cat ); +bool mutation_category_is_valid( const mutation_category_id &cat ); bool mutation_type_exists( const std::string &id ); std::vector get_mutations_in_types( const std::set &ids ); std::vector get_mutations_in_type( const std::string &id ); diff --git a/src/mutation_data.cpp b/src/mutation_data.cpp index 952ad9802ce9..c760a34023ac 100644 --- a/src/mutation_data.cpp +++ b/src/mutation_data.cpp @@ -61,8 +61,8 @@ generic_factory trait_factory( "trait" ); } // namespace static std::vector all_dreams; -std::map > mutations_category; -std::map mutation_category_traits; +std::map > mutations_category; +std::map mutation_category_traits; template<> const mutation_branch &string_id::obj() const @@ -113,7 +113,7 @@ static void load_mutation_mods( void mutation_category_trait::load( const JsonObject &jsobj ) { mutation_category_trait new_category; - new_category.id = jsobj.get_string( "id" ); + jsobj.read( "id", new_category.id, true ); new_category.raw_name = jsobj.get_string( "name" ); new_category.threshold_mut = trait_id( jsobj.get_string( "threshold_mut" ) ); @@ -203,13 +203,13 @@ std::string mutation_category_trait::memorial_message_female() const return pgettext( "memorial_female", raw_memorial_message.c_str() ); } -const std::map &mutation_category_trait::get_all() +const std::map &mutation_category_trait::get_all() { return mutation_category_traits; } -const mutation_category_trait &mutation_category_trait::get_category( const std::string - &category_id ) +const mutation_category_trait &mutation_category_trait::get_category( + const mutation_category_id &category_id ) { return mutation_category_traits.find( category_id )->second; } @@ -465,7 +465,7 @@ void mutation_branch::load( const JsonObject &jo, const std::string & ) optional( jo, was_loaded, "body_size", body_size ); - optional( jo, was_loaded, "category", category, string_reader{} ); + optional( jo, was_loaded, "category", category, string_id_reader {} ); for( JsonArray ja : jo.get_array( "spells_learned" ) ) { const spell_id sp( ja.next_string() ); @@ -665,7 +665,7 @@ void dreams::load( const JsonObject &jo ) dream newdream; jo.read( "strength", newdream.strength ); - jo.read( "category", newdream.category ); + jo.read( "category", newdream.category, true ); jo.read( "messages", newdream.messages ); all_dreams.push_back( newdream ); @@ -676,7 +676,7 @@ void dreams::clear() all_dreams.clear(); } -std::string dreams::get_random_for_category( const std::string &cat, int strength ) +std::string dreams::get_random_for_category( const mutation_category_id &cat, int strength ) { std::vector valid_dreams; for( const dream &d : all_dreams ) { @@ -723,7 +723,7 @@ bool mutation_branch::trait_is_blacklisted( const trait_id &tid ) void mutation_branch::finalize() { for( const mutation_branch &branch : get_all() ) { - for( const std::string &cat : branch.category ) { + for( const mutation_category_id &cat : branch.category ) { mutations_category[cat].emplace_back( branch.id ); } } @@ -896,7 +896,13 @@ std::vector mutation_branch::get_all_group_names() return rval; } -bool mutation_category_is_valid( const std::string &cat ) +bool mutation_category_is_valid( const mutation_category_id &cat ) { return mutation_category_traits.count( cat ); } + +template<> +bool string_id::is_valid() const +{ + return mutation_category_traits.count( *this ); +} diff --git a/src/mutation_data.h b/src/mutation_data.h index 4b7430750834..ab48522ca9a5 100644 --- a/src/mutation_data.h +++ b/src/mutation_data.h @@ -6,12 +6,13 @@ #include #include "translations.h" +#include "type_id.h" class JsonObject; struct dream { std::vector messages; // The messages that the dream will give - std::string category; // The category that will trigger the dream + mutation_category_id category; // The category that will trigger the dream int strength = 0; // The category strength required for the dream }; @@ -21,7 +22,7 @@ void load( const JsonObject &jo ); void clear(); /** Returns a random dream description that matches given category and strength. */ -std::string get_random_for_category( const std::string &cat, int strength ); +std::string get_random_for_category( const mutation_category_id &cat, int strength ); } // namespace dreams diff --git a/src/npc_class.cpp b/src/npc_class.cpp index 15c3c54fc004..c31422d0aea5 100644 --- a/src/npc_class.cpp +++ b/src/npc_class.cpp @@ -266,17 +266,18 @@ void npc_class::load( const JsonObject &jo, const std::string & ) * } */ if( jo.has_object( "mutation_rounds" ) ) { - const std::map &mutation_categories = + const std::map &mutation_categories = mutation_category_trait::get_all(); for( const JsonMember member : jo.get_object( "mutation_rounds" ) ) { - const std::string &mutation = member.name(); - const auto category_match = [&mutation]( const std::pair + const mutation_category_id mutation( member.name() ); + const auto category_match = [&mutation]( const + std::pair &p ) { return p.second.id == mutation; }; if( std::find_if( mutation_categories.begin(), mutation_categories.end(), category_match ) == mutation_categories.end() ) { - debugmsg( "Unrecognized mutation category %s", mutation ); + debugmsg( "Unrecognized mutation category %s", mutation.str() ); continue; } auto distrib = member.get_object(); diff --git a/src/npc_class.h b/src/npc_class.h index 1392cf68ecd2..99a8b1d09c03 100644 --- a/src/npc_class.h +++ b/src/npc_class.h @@ -13,8 +13,6 @@ class JsonObject; -using Mutation_category_tag = std::string; - class Trait_group; namespace trait_group @@ -74,7 +72,7 @@ class npc_class item_group_id carry_override; item_group_id weapon_override; - std::map mutation_rounds; + std::map mutation_rounds; trait_group::Trait_group_tag traits = trait_group::Trait_group_tag( "EMPTY_GROUP" ); // the int is what level the spell starts at std::map _starting_spells; diff --git a/src/player_hardcoded_effects.cpp b/src/player_hardcoded_effects.cpp index d0be36b545cd..025ce9dc977d 100644 --- a/src/player_hardcoded_effects.cpp +++ b/src/player_hardcoded_effects.cpp @@ -188,10 +188,10 @@ static void eff_fun_rat( player &u, effect &it ) it.set_intensity( dur / 10 ); if( rng( 0, 100 ) < dur / 10 ) { if( !one_in( 5 ) ) { - u.mutate_category( "RAT" ); + u.mutate_category( mutation_category_id( "RAT" ) ); it.mult_duration( .2 ); } else { - u.mutate_category( "TROGLOBITE" ); + u.mutate_category( mutation_category_id( "TROGLOBITE" ) ); it.mult_duration( .33 ); } } else if( rng( 0, 100 ) < dur / 8 ) { @@ -1110,7 +1110,7 @@ void Character::hardcoded_effects( effect &it ) } // Check mutation category strengths to see if we're mutated enough to get a dream - std::string highcat = get_highest_category(); + mutation_category_id highcat = get_highest_category(); int highest = mutation_category_level[highcat]; // Determine the strength of effects or dreams based upon category strength @@ -1137,7 +1137,7 @@ void Character::hardcoded_effects( effect &it ) // Mycus folks upgrade in their sleep. if( has_trait( trait_THRESH_MYCUS ) ) { if( one_in( 8 ) ) { - mutate_category( "MYCUS" ); + mutate_category( mutation_category_id( "MYCUS" ) ); mod_stored_nutr( 10 ); mod_thirst( 10 ); mod_fatigue( 5 ); diff --git a/src/type_id.h b/src/type_id.h index b05ca6a309da..41c69deb0c4b 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -166,6 +166,9 @@ using spell_id = string_id; class start_location; using start_location_id = string_id; +struct mutation_category_trait; +using mutation_category_id = string_id; + struct ter_t; using ter_id = int_id; using ter_str_id = string_id; diff --git a/src/wish.cpp b/src/wish.cpp index d6c45b0af7f5..dc6add08b7fa 100644 --- a/src/wish.cpp +++ b/src/wish.cpp @@ -194,8 +194,8 @@ class wish_mutate_callback: public uilist_callback if( !mdata.category.empty() ) { line2++; mvwprintz( menu->window, point( startx, line2 ), c_light_gray, _( "Category:" ) ); - for( auto &j : mdata.category ) { - mvwprintw( menu->window, point( startx + 11, line2 ), j ); + for( const mutation_category_id &j : mdata.category ) { + mvwprintw( menu->window, point( startx + 11, line2 ), j.str() ); line2++; } } diff --git a/tests/memorial_test.cpp b/tests/memorial_test.cpp index 7515e784cf4f..0700036c611c 100644 --- a/tests/memorial_test.cpp +++ b/tests/memorial_test.cpp @@ -118,7 +118,7 @@ TEST_CASE( "memorials" ) m, b, "Opened the Marloss Gateway.", ch ); check_memorial( - m, b, "Became one with the bears.", ch, "URSINE" ); + m, b, "Became one with the bears.", ch, mutation_category_id( "URSINE" ) ); check_memorial( m, b, "Became one with the Mycus.", ch ); diff --git a/tests/modify_morale_test.cpp b/tests/modify_morale_test.cpp index c7f8f4df3e0d..2458b42710f2 100644 --- a/tests/modify_morale_test.cpp +++ b/tests/modify_morale_test.cpp @@ -443,7 +443,7 @@ TEST_CASE( "ursine honey", "[food][modify_morale][ursine][honey]" ) dummy.mutate_towards( trait_URSINE_FUR ); dummy.mutate_towards( trait_URSINE_EYE ); dummy.mutate_towards( trait_PADDED_FEET ); - REQUIRE( dummy.mutation_category_level["URSINE"] > 40 ); + REQUIRE( dummy.mutation_category_level[mutation_category_id( "URSINE" )] > 40 ); THEN( "they get an extra honey morale bonus for eating it" ) { dummy.modify_morale( honeycomb ); diff --git a/tests/mutation_test.cpp b/tests/mutation_test.cpp index 64b68d0ff9bc..c74b0ee1853a 100644 --- a/tests/mutation_test.cpp +++ b/tests/mutation_test.cpp @@ -73,25 +73,25 @@ TEST_CASE( "Having all mutations give correct highest category", "[mutations]" ) for( auto &cat : mutation_category_trait::get_all() ) { const auto &cur_cat = cat.second; const auto &cat_id = cur_cat.id; - if( cat_id == "ANY" ) { + if( cat_id == mutation_category_id( "ANY" ) ) { continue; } - GIVEN( "The player has all pre-threshold mutations for " + cat_id ) { + GIVEN( "The player has all pre-threshold mutations for " + cat_id.str() ) { npc dummy; give_all_mutations( dummy, cur_cat, false ); - THEN( cat_id + " is the strongest category" ) { + THEN( cat_id.str() + " is the strongest category" ) { INFO( "MUTATIONS: " << get_mutations_as_string( dummy ) ); CHECK( dummy.get_highest_category() == cat_id ); } } - GIVEN( "The player has all mutations for " + cat_id ) { + GIVEN( "The player has all mutations for " + cat_id.str() ) { npc dummy; give_all_mutations( dummy, cur_cat, true ); - THEN( cat_id + " is the strongest category" ) { + THEN( cat_id.str() + " is the strongest category" ) { INFO( "MUTATIONS: " << get_mutations_as_string( dummy ) ); CHECK( dummy.get_highest_category() == cat_id ); } @@ -108,11 +108,11 @@ TEST_CASE( "Having all pre-threshold mutations gives a sensible threshold breach for( auto &cat : mutation_category_trait::get_all() ) { const auto &cur_cat = cat.second; const auto &cat_id = cur_cat.id; - if( cat_id == "ANY" ) { + if( cat_id == mutation_category_id( "ANY" ) ) { continue; } - GIVEN( "The player has all pre-threshold mutations for " + cat_id ) { + GIVEN( "The player has all pre-threshold mutations for " + cat_id.str() ) { npc dummy; give_all_mutations( dummy, cur_cat, false ); @@ -132,7 +132,8 @@ TEST_CASE( "Having all pre-threshold mutations gives a sensible threshold breach } } -static float sum_without_category( const std::map &chances, std::string cat ) +static float sum_without_category( const std::map &chances, + const mutation_category_id &cat ) { float sum = 0.0f; for( const auto &c : chances ) { @@ -151,7 +152,7 @@ TEST_CASE( "Gaining a mutation in category makes mutations from other categories for( auto &cat : mutation_category_trait::get_all() ) { const auto &cur_cat = cat.second; const auto &cat_id = cur_cat.id; - if( cat_id == "ANY" ) { + if( cat_id == mutation_category_id( "ANY" ) ) { continue; }