diff --git a/data/json/flags.json b/data/json/flags.json index 99b5ffda108e..f76a71b5b3b3 100644 --- a/data/json/flags.json +++ b/data/json/flags.json @@ -1047,12 +1047,6 @@ "context": [ "SPELL" ], "//": "pain altering spells can't be resisted (like with the deadened trait)" }, - { - "id": "NON_THRESH", - "type": "json_flag", - "context": [ "mutation" ], - "//": "This mutation does not count toward thresholds at all." - }, { "id": "EFFECT_FEATHER_FALL", "context": [ ], diff --git a/data/json/flags_mutation.json b/data/json/flags_mutation.json new file mode 100644 index 000000000000..b2ec7e307c83 --- /dev/null +++ b/data/json/flags_mutation.json @@ -0,0 +1,132 @@ +[ + { + "id": "BG_SURVIVAL_STORY", + "//": "This trait flag is used in dialogue JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "CANNIBAL", + "type": "mutation_flag" + }, + { + "id": "hair_black", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_blond", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_brown", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_crewcut", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_fro", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_gray", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_long", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_medium", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_mohawk", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_red", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_short", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "hair_white", + "//": "This trait flag is used by cosmetic trait JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "mycus", + "//": "This trait flag is used in dialogue JSON, with no hardcode usage at present.", + "type": "mutation_flag" + }, + { + "id": "MUTATION_THRESHOLD", + "type": "mutation_flag" + }, + { + "id": "NEED_ACTIVE_TO_MELEE", + "type": "mutation_flag" + }, + { + "id": "NO_THIRST", + "type": "mutation_flag" + }, + { + "id": "NO_RADIATION", + "type": "mutation_flag" + }, + { + "id": "NON_THRESH", + "type": "mutation_flag" + }, + { + "id": "PSYCHOPATH", + "type": "mutation_flag" + }, + { + "id": "PRED1", + "type": "mutation_flag" + }, + { + "id": "PRED2", + "type": "mutation_flag" + }, + { + "id": "PRED3", + "type": "mutation_flag" + }, + { + "id": "PRED4", + "type": "mutation_flag" + }, + { + "id": "SAPIOVORE", + "type": "mutation_flag" + }, + { + "id": "SILENT_SPELL", + "type": "mutation_flag" + }, + { + "id": "SUBTLE_SPELL", + "type": "mutation_flag" + }, + { + "id": "UNARMED_BONUS", + "type": "mutation_flag" + } +] diff --git a/data/json/obsoletion/mutations.json b/data/json/obsoletion/mutations.json index 94c1dbf5ae74..12c5621cc777 100644 --- a/data/json/obsoletion/mutations.json +++ b/data/json/obsoletion/mutations.json @@ -130,7 +130,6 @@ "points": 2, "description": "Your body is simply immune to diseases. You will never catch an ambient disease.", "prereqs": [ "DISRESISTANT" ], - "flags": [ "NO_DISEASE" ], "valid": false }, { diff --git a/doc/src/content/docs/en/mod/json/reference/json_flags.md b/doc/src/content/docs/en/mod/json/reference/json_flags.md index 1e0febf26281..4bfc8647e892 100644 --- a/doc/src/content/docs/en/mod/json/reference/json_flags.md +++ b/doc/src/content/docs/en/mod/json/reference/json_flags.md @@ -1178,10 +1178,41 @@ example, impale and scratch. ## Mutations -#### Flags +#### Mutation Flags + +Mutation flags use a different JSON type from other flags, see json/flags_mutation.json. Primary +difference is that `conflicts` and `requires` are the only additional properties that can be added +to them. + +The following show all trait flags that are currently used by the game's code. Trait flags must also +be defined in JSON if they are to be used in NPC dialogue conditions. +- `CANNIBAL` No morale penalty from butchery human corpses, skips warning you about human meat. + NOTE: this only skips the warning, the actual morale effects of eating human flesh still require + one of the relevant traits. Custom traits with this flag will skip the warning and suffer the + morale penalty. +- `NEED_ACTIVE_TO_MELEE` A mutation with this flag will only provide unarmed bonuses if it's been + toggled on. - `NO_RADIATION` This mutation grants immunity to radiations. - `NO_THIRST` Your thirst is not modified by food or drinks. +- `NON_THRESH` Mutations with this flag will not count towards the mutation strength (and thus + ability to breach a mutation threshold) of any categories it counts as belonging to. +- `PRED1` Reduces morale impact of enzlaving zombie corpses, reduces morale impact of killing + monsters with the `GUILT` flag. +- `PRED2` Increases EXP gain from combat, negates skill rust of combat skills (if skill rust is + enabled), reduces morale impact of enzlaving zombie corpses, reduces morale impact of killing + monsters with the `GUILT` flag. +- `PRED3` Increases EXP gain from combat, negates skill rust of combat skills (if skill rust is + enabled), increases tolerance for enzlaving zombies while already depressed, negates morale impact + of killing monsters with the `GUILT` flag. +- `PRED4` Increases EXP gain from combat, prevents EXP gain from combat from affecting focus, + negates skill rust of combat skills (if skill rust is enabled), negates morale impact of enzlaving + zombie corpses, increases tolerance for enzlaving zombies while already depressed, negates morale + impact of killing monsters with the `GUILT` flag. +- `PSYCHOPATH` No morale penalty from butchering human corpses. +- `SAPIVORE` No morale penalty from butcheing human corpses. +- `SILENT_SPELL` Negates the negative impact of mouth encumbrance on spells with the `VERBAL` flag. +- `SUBTLE_SPELL` Negates the negative impact of arm encumbrance on spells with the `SOMATIC` flag. - `UNARMED_BONUS` You get a bonus to unarmed bash and cut damage equal to unarmed_skill/2 up to 4. ### Categories diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e1f15113d78d..843e9fef3317 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -33,7 +33,6 @@ add_custom_command( ${CMAKE_SOURCE_DIR}/src/version.cmake WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - # Build tiles version if requested if (TILES) setup_library(cataclysm-tiles-common) diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 79223276899c..97298f707999 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -247,9 +247,9 @@ static const quality_id qual_LOCKPICK( "LOCKPICK" ); static const species_id HUMAN( "HUMAN" ); static const species_id ZOMBIE( "ZOMBIE" ); -static const std::string trait_flag_CANNIBAL( "CANNIBAL" ); -static const std::string trait_flag_PSYCHOPATH( "PSYCHOPATH" ); -static const std::string trait_flag_SAPIOVORE( "SAPIOVORE" ); +static const trait_flag_str_id trait_flag_CANNIBAL( "CANNIBAL" ); +static const trait_flag_str_id trait_flag_PSYCHOPATH( "PSYCHOPATH" ); +static const trait_flag_str_id trait_flag_SAPIOVORE( "SAPIOVORE" ); static const bionic_id bio_ears( "bio_ears" ); static const bionic_id bio_painkiller( "bio_painkiller" ); diff --git a/src/character.cpp b/src/character.cpp index 761e0785f444..9d6b3c5debd7 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -378,6 +378,15 @@ static const flag_str_id flag_BIONIC_ARMOR_INTERFACE( "BIONIC_ARMOR_INTERFACE" ) static const mtype_id mon_player_blob( "mon_player_blob" ); static const mtype_id mon_shadow_snake( "mon_shadow_snake" ); +static const trait_flag_str_id trait_flag_PRED1( "PRED1" ); +static const trait_flag_str_id trait_flag_PRED2( "PRED2" ); +static const trait_flag_str_id trait_flag_PRED3( "PRED3" ); +static const trait_flag_str_id trait_flag_PRED4( "PRED4" ); + +static const trait_flag_str_id flag_NO_THIRST( "NO_THIRST" ); +static const trait_flag_str_id flag_NO_RADIATION( "NO_RADIATION" ); +static const trait_flag_str_id flag_NON_THRESH( "NON_THRESH" ); + namespace io { @@ -3446,16 +3455,16 @@ void Character::practice( const skill_id &id, int amount, int cap, bool suppress amount = 0; } } - if( has_trait_flag( "PRED2" ) && skill.is_combat_skill() ) { + if( has_trait_flag( trait_flag_PRED2 ) && skill.is_combat_skill() ) { if( one_in( 3 ) ) { amount *= 2; } } - if( has_trait_flag( "PRED3" ) && skill.is_combat_skill() ) { + if( has_trait_flag( trait_flag_PRED3 ) && skill.is_combat_skill() ) { amount *= 2; } - if( has_trait_flag( "PRED4" ) && skill.is_combat_skill() ) { + if( has_trait_flag( trait_flag_PRED4 ) && skill.is_combat_skill() ) { amount *= 3; } @@ -3489,8 +3498,9 @@ void Character::practice( const skill_id &id, int amount, int cap, bool suppress focus_pool -= chance_to_drop / 100; // Apex Predators don't think about much other than killing. // They don't lose Focus when practicing combat skills. - if( ( rng( 1, 100 ) <= ( chance_to_drop % 100 ) ) && ( !( has_trait_flag( "PRED4" ) && - skill.is_combat_skill() ) ) ) { + if( ( rng( 1, 100 ) <= ( chance_to_drop % 100 ) ) && + ( !( has_trait_flag( trait_flag_PRED4 ) && + skill.is_combat_skill() ) ) ) { focus_pool--; } } @@ -3600,17 +3610,14 @@ void Character::apply_skill_boost() void Character::do_skill_rust() { const int rust_rate_tmp = rust_rate(); - static const std::string PRED2( "PRED2" ); - static const std::string PRED3( "PRED3" ); - static const std::string PRED4( "PRED4" ); for( std::pair &pair : *_skills ) { const Skill &aSkill = *pair.first; SkillLevel &skill_level_obj = pair.second; if( aSkill.is_combat_skill() && - ( ( has_trait_flag( PRED2 ) && calendar::once_every( 8_hours ) ) || - ( has_trait_flag( PRED3 ) && calendar::once_every( 4_hours ) ) || - ( has_trait_flag( PRED4 ) && calendar::once_every( 3_hours ) ) ) ) { + ( ( has_trait_flag( trait_flag_PRED2 ) && calendar::once_every( 8_hours ) ) || + ( has_trait_flag( trait_flag_PRED3 ) && calendar::once_every( 4_hours ) ) || + ( has_trait_flag( trait_flag_PRED4 ) && calendar::once_every( 3_hours ) ) ) ) { // Their brain is optimized to remember this if( one_in( 13 ) ) { // They've already passed the roll to avoid rust at @@ -4438,7 +4445,7 @@ std::pair Character::get_fatigue_description() const void Character::mod_thirst( int nthirst ) { - if( has_trait_flag( "NO_THIRST" ) ) { + if( has_trait_flag( flag_NO_THIRST ) ) { return; } set_thirst( std::max( -100, thirst + nthirst ) ); @@ -7084,7 +7091,7 @@ void Character::set_rad( int new_rad ) void Character::mod_rad( int mod ) { - if( has_trait_flag( "NO_RADIATION" ) ) { + if( has_trait_flag( flag_NO_RADIATION ) ) { return; } set_rad( std::max( 0, get_rad() + mod ) ); @@ -7810,7 +7817,7 @@ void Character::set_highest_cat_level() // Then use the map to set the category levels for( const std::pair &i : dependency_map ) { const mutation_branch &mdata = i.first.obj(); - if( !mdata.flags.count( "NON_THRESH" ) ) { + if( !mdata.flags.count( flag_NON_THRESH ) ) { for( const std::string &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 ) ); diff --git a/src/character.h b/src/character.h index a84bf824865c..07214353d534 100644 --- a/src/character.h +++ b/src/character.h @@ -703,7 +703,7 @@ class Character : public Creature, public visitable /** Returns true if the player has the entered starting trait */ bool has_base_trait( const trait_id &b ) const; /** Returns true if player has a trait with a flag */ - bool has_trait_flag( const std::string &b ) const; + bool has_trait_flag( const trait_flag_str_id &b ) const; /** Returns true if character has a trait which cancels the entered trait. */ bool has_opposite_trait( const trait_id &flag ) const; diff --git a/src/condition.cpp b/src/condition.cpp index acac62bf6005..dc0b64503bb7 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -46,7 +46,7 @@ class basecamp; class recipe; static const efftype_id effect_currently_busy( "currently_busy" ); - +static const trait_flag_str_id flag_MUTATION_THRESHOLD( "MUTATION_THRESHOLD" ); // throws an error on failure, so no need to return std::string get_talk_varname( const JsonObject &jo, const std::string &member, bool check_value ) { @@ -126,13 +126,18 @@ template void conditional_t::set_has_trait_flag( const JsonObject &jo, const std::string &member, bool is_npc ) { - const std::string &trait_flag_to_check = jo.get_string( member ); - condition = [trait_flag_to_check, is_npc]( const T & d ) { + const std::string &raw = jo.get_string( member ); + const trait_flag_str_id trait_flag_to_check( raw ); + if( !trait_flag_to_check.is_valid() ) { + jo.show_warning( string_format( "Invalid trait flag %s", raw ), member ); + } + const bool check_threshold = trait_flag_to_check == flag_MUTATION_THRESHOLD; + condition = [trait_flag_to_check, check_threshold, is_npc]( const T & d ) { player *actor = d.alpha; if( is_npc ) { actor = dynamic_cast( d.beta ); } - if( trait_flag_to_check == "MUTATION_THRESHOLD" ) { + if( check_threshold ) { return actor->crossed_threshold(); } return actor->has_trait_flag( trait_flag_to_check ); diff --git a/src/consumption.cpp b/src/consumption.cpp index 344bdcda4f13..285a7ba6d40b 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -118,6 +118,8 @@ static const trait_id trait_THRESH_URSINE( "THRESH_URSINE" ); static const trait_id trait_VEGETARIAN( "VEGETARIAN" ); static const trait_id trait_WATERSLEEP( "WATERSLEEP" ); +static const trait_flag_str_id trait_flag_CANNIBAL( "CANNIBAL" ); + static const std::string flag_HIDDEN_HALLU( "HIDDEN_HALLU" ); static const std::string flag_ALLERGEN_EGG( "ALLERGEN_EGG" ); static const std::string flag_ALLERGEN_FRUIT( "ALLERGEN_FRUIT" ); @@ -755,7 +757,7 @@ ret_val Character::will_eat( const item &food, bool interactive ) } const bool carnivore = has_trait( trait_CARNIVORE ); - if( food.has_flag( flag_CANNIBALISM ) && !has_trait_flag( "CANNIBAL" ) ) { + if( food.has_flag( flag_CANNIBALISM ) && !has_trait_flag( trait_flag_CANNIBAL ) ) { add_consequence( _( "The thought of eating human flesh makes you feel sick." ), edible_rating::cannibalism ); } diff --git a/src/flag_trait.cpp b/src/flag_trait.cpp new file mode 100644 index 000000000000..9824feab956b --- /dev/null +++ b/src/flag_trait.cpp @@ -0,0 +1,116 @@ +#include + +#include "debug.h" +#include "flag_trait.h" +#include "json.h" +#include "type_id.h" +#include "generic_factory.h" + +namespace +{ +generic_factory json_trait_flags_all( "json_trait_flags" ); +} // namespace + +/** @relates int_id */ +template<> +bool trait_flag_id ::is_valid() const +{ + return json_trait_flags_all.is_valid( *this ); +} + +/** @relates int_id */ +template<> +const json_trait_flag &trait_flag_id::obj() const +{ + return json_trait_flags_all.obj( *this ); +} + +/** @relates int_id */ +template<> +const trait_flag_str_id &trait_flag_id::id() const +{ + return json_trait_flags_all.convert( *this ); +} + +/** @relates string_id */ +template<> +bool trait_flag_str_id ::is_valid() const +{ + return json_trait_flags_all.is_valid( *this ); +} + +/** @relates string_id */ +template<> +const json_trait_flag &trait_flag_str_id::obj() const +{ + return json_trait_flags_all.obj( *this ); +} + +/** @relates string_id */ +template<> +trait_flag_id trait_flag_str_id::id() const +{ + return json_trait_flags_all.convert( *this, trait_flag_id( -1 ) ); +} + +/** @relates int_id */ +template<> +trait_flag_id::int_id( const trait_flag_str_id &id ) : _id( id.id() ) +{ +} + +json_trait_flag::operator bool() const +{ + return id.is_valid(); +} + +const json_trait_flag &json_trait_flag::get( const std::string &id ) +{ + static const json_trait_flag null_value = json_trait_flag(); + const trait_flag_str_id f_id( id ); + return f_id.is_valid() ? *f_id : null_value; +} + +void json_trait_flag::load( const JsonObject &, const std::string & ) +{ +} + +void json_trait_flag::check_consistency() +{ + json_trait_flags_all.check(); +} + +void json_trait_flag::reset() +{ + json_trait_flags_all.reset(); +} + +void json_trait_flag::load_all( const JsonObject &jo, const std::string &src ) +{ + json_trait_flags_all.load( jo, src ); +} + +void json_trait_flag::check() const +{ + for( const auto &conflicting : conflicts_ ) { + if( !trait_flag_str_id( conflicting ).is_valid() ) { + debugmsg( "trait flag definition %s specifies unknown conflicting field %s", id.str(), + conflicting ); + } + } +} + +void json_trait_flag::finalize_all() +{ + json_trait_flags_all.finalize(); +} + +bool json_trait_flag::is_ready() +{ + return !json_trait_flags_all.empty(); +} + +const std::vector &json_trait_flag::get_all() +{ + return json_trait_flags_all.get_all(); +} diff --git a/src/flag_trait.h b/src/flag_trait.h new file mode 100644 index 000000000000..68cb479f6ef1 --- /dev/null +++ b/src/flag_trait.h @@ -0,0 +1,56 @@ +#pragma once +#ifndef CATA_SRC_FLAG_TRAIT_H +#define CATA_SRC_FLAG_TRAIT_H + +#include +#include + +#include "type_id.h" + +class JsonObject; + +class json_trait_flag +{ + friend class DynamicDataLoader; + friend class generic_factory; + + public: + // used by generic_factory + trait_flag_str_id id = trait_flag_str_id::NULL_ID(); + bool was_loaded = false; + + json_trait_flag() = default; + + /** Fetches flag definition (or null flag if not found) */ + static const json_trait_flag &get( const std::string &id ); + + /** Is this a valid (non-null) flag */ + operator bool() const; + + void check() const; + + /** true, if flags were loaded */ + static bool is_ready(); + + static const std::vector &get_all(); + + private: + std::set conflicts_; + + /** Load flag definition from JSON (NO-OP) */ + void load( const JsonObject &jo, const std::string &src ); + + /** Load all flags from JSON */ + static void load_all( const JsonObject &jo, const std::string &src ); + + /** finalize */ + static void finalize_all( ); + + /** Check consistency of all loaded flags */ + static void check_consistency(); + + /** Clear all loaded flags (invalidating any pointers) */ + static void reset(); +}; + +#endif // CATA_SRC_FLAG_TRAIT_H diff --git a/src/init.cpp b/src/init.cpp index c6832eb379cb..a62f6320c4ec 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -45,6 +45,7 @@ #include "filesystem.h" #include "fstream_utils.h" #include "flag.h" +#include "flag_trait.h" #include "gates.h" #include "harvest.h" #include "item_action.h" @@ -249,6 +250,7 @@ void DynamicDataLoader::initialize() add( "WORLD_OPTION", &load_world_option ); add( "EXTERNAL_OPTION", &load_external_option ); add( "json_flag", &json_flag::load_all ); + add( "mutation_flag", &json_trait_flag::load_all ); add( "fault", &fault::load_fault ); add( "field_type", &field_types::load ); add( "weather_type", &weather_types::load ); @@ -570,6 +572,7 @@ void DynamicDataLoader::unload_data() item_action_generator::generator().reset(); item_controller->reset(); json_flag::reset(); + json_trait_flag::reset(); MapExtras::reset(); mapgen_palette::reset(); materials::reset(); @@ -652,6 +655,7 @@ void DynamicDataLoader::finalize_loaded_data( loading_ui &ui ) using named_entry = std::pair>; const std::vector entries = {{ { _( "Flags" ), &json_flag::finalize_all }, + { _( "Mutation Flags" ), &json_trait_flag::finalize_all }, { _( "Body parts" ), &body_part_type::finalize_all }, { _( "Bionics" ), &bionic_data::finalize_all }, { _( "Weather types" ), &weather_types::finalize_all }, @@ -729,6 +733,7 @@ void DynamicDataLoader::check_consistency( loading_ui &ui ) using named_entry = std::pair>; const std::vector entries = {{ { _( "Flags" ), &json_flag::check_consistency }, + { _( "Mutation Flags" ), &json_trait_flag::check_consistency }, { _( "Crafting requirements" ), []() { diff --git a/src/item.cpp b/src/item.cpp index 89618c6a05f7..effb3c1d323b 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -158,7 +158,7 @@ static const quality_id qual_JACK( "JACK" ); static const quality_id qual_LIFT( "LIFT" ); static const species_id ROBOT( "ROBOT" ); -static const std::string trait_flag_CANNIBAL( "CANNIBAL" ); +static const trait_flag_str_id trait_flag_CANNIBAL( "CANNIBAL" ); static const bionic_id bio_digestion( "bio_digestion" ); diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 4e38999390c5..e7410146cdc3 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -152,6 +152,11 @@ static const std::string flag_UNDERSIZE( "UNDERSIZE" ); static const std::string flag_VARSIZE( "VARSIZE" ); static const std::string flag_POWERARMOR_MOD( "POWERARMOR_MOD" ); +static const trait_flag_str_id trait_flag_PRED1( "PRED1" ); +static const trait_flag_str_id trait_flag_PRED2( "PRED2" ); +static const trait_flag_str_id trait_flag_PRED3( "PRED3" ); +static const trait_flag_str_id trait_flag_PRED4( "PRED4" ); + class npc; std::unique_ptr iuse_transform::clone() const @@ -2091,9 +2096,9 @@ int enzlave_actor::use( player &p, item &it, bool t, const tripoint & ) const int tolerance_level = 9; if( p.has_trait( trait_PSYCHOPATH ) || p.has_trait( trait_SAPIOVORE ) ) { tolerance_level = 0; - } else if( p.has_trait_flag( "PRED4" ) ) { + } else if( p.has_trait_flag( trait_flag_PRED4 ) ) { tolerance_level = 5; - } else if( p.has_trait_flag( "PRED3" ) ) { + } else if( p.has_trait_flag( trait_flag_PRED3 ) ) { tolerance_level = 7; } @@ -2137,9 +2142,9 @@ int enzlave_actor::use( player &p, item &it, bool t, const tripoint & ) const if( p.has_trait( trait_PACIFIST ) ) { moraleMalus *= 5; maxMalus *= 3; - } else if( p.has_trait_flag( "PRED1" ) ) { + } else if( p.has_trait_flag( trait_flag_PRED1 ) ) { moraleMalus /= 4; - } else if( p.has_trait_flag( "PRED2" ) ) { + } else if( p.has_trait_flag( trait_flag_PRED2 ) ) { moraleMalus /= 5; } diff --git a/src/magic.cpp b/src/magic.cpp index 7815169c505f..172b9480cdb8 100644 --- a/src/magic.cpp +++ b/src/magic.cpp @@ -48,6 +48,8 @@ #include "units.h" static const trait_id trait_NONE( "NONE" ); +static const trait_flag_str_id trait_flag_SUBTLE_SPELL( "SUBTLE_SPELL" ); +static const trait_flag_str_id trait_flag_SILENT_SPELL( "SILENT_SPELL" ); namespace io { @@ -758,13 +760,14 @@ float spell::spell_fail( const Character &guy ) const return 1.0f; } float fail_chance = std::pow( ( effective_skill - 30.0f ) / 30.0f, 2 ); - if( has_flag( spell_flag::SOMATIC ) && !guy.has_trait_flag( "SUBTLE_SPELL" ) ) { + if( has_flag( spell_flag::SOMATIC ) && + !guy.has_trait_flag( trait_flag_SUBTLE_SPELL ) ) { // the first 20 points of encumbrance combined is ignored const int arms_encumb = std::max( 0, guy.encumb( bp_arm_l ) + guy.encumb( bp_arm_r ) - 20 ); // each encumbrance point beyond the "gray" color counts as half an additional fail % fail_chance += arms_encumb / 200.0f; } - if( has_flag( spell_flag::VERBAL ) && !guy.has_trait_flag( "SILENT_SPELL" ) ) { + if( has_flag( spell_flag::VERBAL ) && !guy.has_trait_flag( trait_flag_SILENT_SPELL ) ) { // a little bit of mouth encumbrance is allowed, but not much const int mouth_encumb = std::max( 0, guy.encumb( bp_mouth ) - 5 ); fail_chance += mouth_encumb / 100.0f; diff --git a/src/melee.cpp b/src/melee.cpp index 68e22c7c3d8f..d4aab29e3949 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -113,6 +113,9 @@ static const trait_id trait_PROF_SKATER( "PROF_SKATER" ); static const trait_id trait_VINES2( "VINES2" ); static const trait_id trait_VINES3( "VINES3" ); +static const trait_flag_str_id trait_flag_NEED_ACTIVE_TO_MELEE( "NEED_ACTIVE_TO_MELEE" ); +static const trait_flag_str_id trait_flag_UNARMED_BONUS( "UNARMED_BONUS" ); + static const efftype_id effect_amigara( "amigara" ); static const species_id HUMAN( "HUMAN" ); @@ -966,12 +969,13 @@ void Character::roll_bash_damage( bool crit, damage_instance &di, bool average, if( left_empty || right_empty ) { float per_hand = 0.0f; for( const trait_id &mut : get_mutations() ) { - if( mut->flags.count( "NEED_ACTIVE_TO_MELEE" ) > 0 && !has_active_mutation( mut ) ) { + if( mut->flags.count( trait_flag_NEED_ACTIVE_TO_MELEE ) > 0 && + !has_active_mutation( mut ) ) { continue; } float unarmed_bonus = 0.0f; const int bash_bonus = mut->bash_dmg_bonus; - if( mut->flags.count( "UNARMED_BONUS" ) > 0 && bash_bonus > 0 ) { + if( mut->flags.count( trait_flag_UNARMED_BONUS ) > 0 && bash_bonus > 0 ) { unarmed_bonus += std::min( get_skill_level( skill_unarmed ) / 2, 4 ); } per_hand += bash_bonus + unarmed_bonus; @@ -1054,12 +1058,13 @@ void Character::roll_cut_damage( bool crit, damage_instance &di, bool average, } for( const trait_id &mut : get_mutations() ) { - if( mut->flags.count( "NEED_ACTIVE_TO_MELEE" ) > 0 && !has_active_mutation( mut ) ) { + if( mut->flags.count( trait_flag_NEED_ACTIVE_TO_MELEE ) > 0 && + !has_active_mutation( mut ) ) { continue; } float unarmed_bonus = 0.0f; const int cut_bonus = mut->cut_dmg_bonus; - if( mut->flags.count( "UNARMED_BONUS" ) > 0 && cut_bonus > 0 ) { + if( mut->flags.count( trait_flag_UNARMED_BONUS ) > 0 && cut_bonus > 0 ) { unarmed_bonus += std::min( get_skill_level( skill_unarmed ) / 2, 4 ); } per_hand += cut_bonus + unarmed_bonus; @@ -1126,7 +1131,7 @@ void Character::roll_stab_damage( bool crit, damage_instance &di, bool /*average for( const trait_id &mut : get_mutations() ) { int stab_bonus = mut->pierce_dmg_bonus; int unarmed_bonus = 0; - if( mut->flags.count( "UNARMED_BONUS" ) > 0 && stab_bonus > 0 ) { + if( mut->flags.count( trait_flag_UNARMED_BONUS ) > 0 && stab_bonus > 0 ) { unarmed_bonus = std::min( unarmed_skill / 2, 4 ); } diff --git a/src/mondeath.cpp b/src/mondeath.cpp index 4b4ebc740618..218602eb43ac 100644 --- a/src/mondeath.cpp +++ b/src/mondeath.cpp @@ -85,6 +85,11 @@ static const trait_id trait_KILLER( "KILLER" ); static const trait_id trait_PACIFIST( "PACIFIST" ); static const trait_id trait_PSYCHOPATH( "PSYCHOPATH" ); +static const trait_flag_str_id trait_flag_PRED1( "PRED1" ); +static const trait_flag_str_id trait_flag_PRED2( "PRED2" ); +static const trait_flag_str_id trait_flag_PRED3( "PRED3" ); +static const trait_flag_str_id trait_flag_PRED4( "PRED4" ); + void mdeath::normal( monster &z ) { if( z.no_corpse_quiet ) { @@ -438,8 +443,8 @@ void mdeath::guilt( monster &z ) guilt_tresholds[50] = _( "You regret killing %s." ); guilt_tresholds[25] = _( "You feel remorse for killing %s." ); - if( g->u.has_trait( trait_PSYCHOPATH ) || g->u.has_trait_flag( "PRED3" ) || - g->u.has_trait_flag( "PRED4" ) || g->u.has_trait( trait_KILLER ) ) { + if( g->u.has_trait( trait_PSYCHOPATH ) || g->u.has_trait_flag( trait_flag_PRED3 ) || + g->u.has_trait_flag( trait_flag_PRED4 ) || g->u.has_trait( trait_KILLER ) ) { return; } if( rl_dist( z.pos(), g->u.pos() ) > MAX_GUILT_DISTANCE ) { @@ -458,7 +463,9 @@ void mdeath::guilt( monster &z ) "about their deaths anymore." ), z.name( maxKills ) ); } return; - } else if( ( g->u.has_trait_flag( "PRED1" ) ) || ( g->u.has_trait_flag( "PRED2" ) ) ) { + + } else if( ( g->u.has_trait_flag( trait_flag_PRED1 ) ) || + ( g->u.has_trait_flag( trait_flag_PRED1 ) ) ) { msg = ( _( "Culling the weak is distasteful, but necessary." ) ); msgtype = m_neutral; } else { @@ -481,9 +488,9 @@ void mdeath::guilt( monster &z ) moraleMalus /= 10; if( g->u.has_trait( trait_PACIFIST ) ) { moraleMalus *= 5; - } else if( g->u.has_trait_flag( "PRED1" ) ) { + } else if( g->u.has_trait_flag( trait_flag_PRED1 ) ) { moraleMalus /= 4; - } else if( g->u.has_trait_flag( "PRED2" ) ) { + } else if( g->u.has_trait_flag( trait_flag_PRED2 ) ) { moraleMalus /= 5; } } diff --git a/src/mutation.cpp b/src/mutation.cpp index 5173ce56e98c..a9d044dd5943 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -103,12 +103,10 @@ bool Character::has_trait( const trait_id &b ) const return my_mutations.count( b ) || enchantment_cache->get_mutations().count( b ); } -bool Character::has_trait_flag( const std::string &b ) const +bool Character::has_trait_flag( const trait_flag_str_id &b ) const { - // UGLY, SLOW, should be cached as my_mutation_flags or something - for( const trait_id &mut : get_mutations() ) { - const mutation_branch &mut_data = mut.obj(); - if( mut_data.flags.count( b ) > 0 ) { + for( const mutation_branch *mut : cached_mutations ) { + if( mut->flags.count( b ) > 0 ) { return true; } } diff --git a/src/mutation.h b/src/mutation.h index 8fe226d47b5e..d25deea6ba26 100644 --- a/src/mutation.h +++ b/src/mutation.h @@ -265,7 +265,7 @@ struct mutation_branch { std::vector replacements; // Mutations that replace this one std::vector additions; // Mutations that add to this one std::vector category; // Mutation Categories - std::set flags; // Mutation flags + std::set flags; // Mutation flags std::map protection; // Mutation wet effects std::map encumbrance_always; // Mutation encumbrance that always applies // Mutation encumbrance that applies when covered with unfitting item diff --git a/src/mutation_data.cpp b/src/mutation_data.cpp index a070de723d85..994ec211643a 100644 --- a/src/mutation_data.cpp +++ b/src/mutation_data.cpp @@ -455,7 +455,7 @@ void mutation_branch::load( const JsonObject &jo, const std::string & ) optional( jo, was_loaded, "cancels", cancels, trait_reader{} ); optional( jo, was_loaded, "changes_to", replacements, trait_reader{} ); optional( jo, was_loaded, "leads_to", additions, trait_reader{} ); - optional( jo, was_loaded, "flags", flags, string_reader{} ); + optional( jo, was_loaded, "flags", flags, auto_flags_reader {} ); optional( jo, was_loaded, "types", types, string_reader{} ); optional( jo, was_loaded, "enchantments", enchantments ); @@ -609,6 +609,11 @@ void mutation_branch::check_consistency() for( const enchantment_id &ench : mdata.enchantments ) { ench->check(); } + for( const auto &flag : mdata.flags ) { + if( !flag.is_valid() ) { + debugmsg( "mutation %s refers to undefined mutation flag %s", mid, flag ); + } + } ::check_consistency( mdata.prereqs, mid, "prereq" ); ::check_consistency( mdata.prereqs2, mid, "prereqs2" ); ::check_consistency( mdata.threshreq, mid, "threshreq" ); diff --git a/src/string_id_null_ids.cpp b/src/string_id_null_ids.cpp index f58922822d49..f272078e8bd4 100644 --- a/src/string_id_null_ids.cpp +++ b/src/string_id_null_ids.cpp @@ -32,6 +32,7 @@ MAKE_NULL_ID( recipe, "null" ) MAKE_NULL_ID( translation, "null" ) MAKE_NULL_ID( Item_group, "" ) MAKE_NULL_ID( morale_type_data, "" ) +MAKE_NULL_ID( json_trait_flag, "null" ) #define MAKE_NULL_ID2( type, ... ) \ struct type; \ diff --git a/src/type_id.h b/src/type_id.h index fed78f471067..fae447247343 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -212,4 +212,8 @@ class json_flag; using flag_id = int_id; using flag_str_id = string_id; +class json_trait_flag; +using trait_flag_id = int_id; +using trait_flag_str_id = string_id; + #endif // CATA_SRC_TYPE_ID_H