From d3ae574a90279638c5b0298d0badefb9af26543e Mon Sep 17 00:00:00 2001 From: anothersimulacrum Date: Sun, 14 Mar 2021 13:48:23 -0700 Subject: [PATCH] Allow CBMs to specify mutations that prevent installation. (#47822) * Move CBM installation checks to Character Reduce code duplication, encapsulate things better, and make this available outside of the inventory menus. * Allow mutations to prevent installing CBMs Bionics can currently cancel mutations when installed, but can't have mutations prevent installation. --- doc/JSON_INFO.md | 2 ++ src/bionics.cpp | 50 +++++++++++++++++++++++++++++++++ src/bionics.h | 4 +++ src/character.h | 3 ++ src/game_inventory.cpp | 64 ++++-------------------------------------- src/mutation.cpp | 12 ++++++++ 6 files changed, 77 insertions(+), 58 deletions(-) diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index d695eaab30f72..20dbbaac92b1e 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -655,6 +655,7 @@ For information about tools with option to export ASCII art in format ready to b | weight_capacity_bonus | (_optional_) Bonus to weight carrying capacity in grams, can be negative. Strings can be used - "5000 g" or "5 kg" (default: `0`) | weight_capacity_modifier | (_optional_) Factor modifying base weight carrying capacity. (default: `1`) | canceled_mutations | (_optional_) A list of mutations/traits that are removed when this bionic is installed (e.g. because it replaces the fault biological part). +| mutation_conflicts | (_optional_) A list of mutations that prevent this bionic from being installed. | included_bionics | (_optional_) Additional bionics that are installed automatically when this bionic is installed. This can be used to install several bionics from one CBM item, which is useful as each of those can be activated independently. | included | (_optional_) Whether this bionic is included with another. If true this bionic does not require a CBM item to be defined. (default: `false`) | env_protec | (_optional_) How much environmental protection does this bionic provide on the specified body parts. @@ -692,6 +693,7 @@ For information about tools with option to export ASCII art in format ready to b "encumbrance" : [ [ "torso", 10 ], [ "arm_l", 10 ], [ "arm_r", 10 ], [ "leg_l", 10 ], [ "leg_r", 10 ], [ "foot_l", 10 ], [ "foot_r", 10 ] ], "description" : "You have a battery draining attachment, and thus can make use of the energy contained in normal, everyday batteries. Use 'E' to consume batteries.", "canceled_mutations": ["HYPEROPIC"], + "mutation_conflicts": [ "HUGE" ], "installation_requirement": "sewing_standard", "included_bionics": ["bio_blindfold"] }, diff --git a/src/bionics.cpp b/src/bionics.cpp index 4a03e6c76d4c0..6262f30c28416 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -192,6 +192,7 @@ static const trait_id trait_PROF_MED( "PROF_MED" ); static const trait_id trait_THRESH_MEDICAL( "THRESH_MEDICAL" ); static const json_character_flag json_flag_BIONIC_GUN( "BIONIC_GUN" ); +static const json_character_flag json_flag_BIONIC_NPC_USABLE( "BIONIC_NPC_USABLE" ); static const json_character_flag json_flag_BIONIC_WEAPON( "BIONIC_WEAPON" ); static const json_character_flag json_flag_BIONIC_TOGGLED( "BIONIC_TOGGLED" ); @@ -317,6 +318,7 @@ void bionic_data::load( const JsonObject &jsobj, const std::string & ) optional( jsobj, was_loaded, "learned_spells", learned_spells ); optional( jsobj, was_loaded, "learned_proficiencies", proficiencies ); optional( jsobj, was_loaded, "canceled_mutations", canceled_mutations ); + optional( jsobj, was_loaded, "mutation_conflicts", mutation_conflicts ); optional( jsobj, was_loaded, "included_bionics", included_bionics ); optional( jsobj, was_loaded, "included", included ); optional( jsobj, was_loaded, "upgraded_bionic", upgraded_bionic ); @@ -2317,6 +2319,54 @@ bool Character::uninstall_bionic( const bionic &target_cbm, monster &installer, return false; } +ret_val Character::is_installable( const item_location &loc, const bool by_autodoc ) const +{ + const item *it = loc.get_item(); + const itype *itemtype = it->type; + const bionic_id &bid = itemtype->bionic->id; + + const auto has_trait_lambda = [this]( const trait_id & candidate ) { + return has_trait( candidate ); + }; + + if( it->has_flag( flag_FILTHY ) ) { + // NOLINTNEXTLINE(cata-text-style): single space after the period for symmetry + const std::string msg = by_autodoc ? _( "/!\\ CBM is highly contaminated. /!\\" ) : + _( "CBM is filthy." ); + return ret_val::make_failure( msg ); + } else if( it->has_flag( flag_NO_STERILE ) ) { + const std::string msg = by_autodoc ? + // NOLINTNEXTLINE(cata-text-style): single space after the period for symmetry + _( "/!\\ CBM is not sterile. /!\\ Please use autoclave to sterilize." ) : + _( "CBM is not sterile." ); + return ret_val::make_failure( msg ); + } else if( it->has_fault( fault_id( "fault_bionic_salvaged" ) ) ) { + return ret_val::make_failure( _( "CBM already deployed. Please reset to factory state." ) ); + } else if( has_bionic( bid ) ) { + return ret_val::make_failure( _( "CBM is already installed." ) ); + } else if( !can_install_cbm_on_bp( get_occupied_bodyparts( bid ) ) ) { + return ret_val::make_failure( _( "CBM not compatible with patient's body." ) ); + } else if( std::any_of( bid->mutation_conflicts.begin(), bid->mutation_conflicts.end(), + has_trait_lambda ) ) { + return ret_val::make_failure( _( "CBM not compatible with patient's body." ) ); + } else if( bid->upgraded_bionic && + !has_bionic( bid->upgraded_bionic ) && + it->is_upgrade() ) { + return ret_val::make_failure( _( "No base version installed." ) ); + } else if( std::any_of( bid->available_upgrades.begin(), + bid->available_upgrades.end(), + std::bind( &Character::has_bionic, this, + std::placeholders::_1 ) ) ) { + return ret_val::make_failure( _( "Superior version installed." ) ); + } else if( is_npc() && !bid->has_flag( json_flag_BIONIC_NPC_USABLE ) ) { + return ret_val::make_failure( _( "CBM not compatible with patient." ) ); + } else if( units::energy_max - get_max_power_level() < bid->capacity ) { + return ret_val::make_failure( _( "Max power capacity already reached." ) ); + } + + return ret_val::make_success( std::string() ); +} + bool Character::can_install_bionics( const itype &type, Character &installer, bool autodoc, int skill_level ) { diff --git a/src/bionics.h b/src/bionics.h index 8f18e22307110..0dfee95903409 100644 --- a/src/bionics.h +++ b/src/bionics.h @@ -113,6 +113,10 @@ struct bionic_data { * E.g. enhanced optic bionic may cancel HYPEROPIC trait. */ std::vector canceled_mutations; + /** + * Mutations/traits that prevent installing this CBM + */ + std::set mutation_conflicts; /** * The spells you learn when you install this bionic, and what level you learn them at. diff --git a/src/character.h b/src/character.h index d8e009c79a5d5..b9f8fee34cd8c 100644 --- a/src/character.h +++ b/src/character.h @@ -1323,6 +1323,9 @@ class Character : public Creature, public visitable /**Is the installation possible*/ bool can_install_bionics( const itype &type, Character &installer, bool autodoc = false, int skill_level = -1 ); + /** Is this bionic elligible to be installed in the player? */ + // Should be ret_val, but ret_val.h doesn't like it + ret_val is_installable( const item_location &loc, bool by_autodoc ) const; std::map bionic_installation_issues( const bionic_id &bioid ); /** Initialize all the values needed to start the operation player_activity */ bool install_bionics( const itype &type, player &installer, bool autodoc = false, diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index 32f08207802cb..0198fd600e926 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -74,8 +74,6 @@ static const trait_id trait_NOPAIN( "NOPAIN" ); static const trait_id trait_SAPROPHAGE( "SAPROPHAGE" ); static const trait_id trait_SAPROVORE( "SAPROVORE" ); -static const json_character_flag json_flag_BIONIC_NPC_USABLE( "BIONIC_NPC_USABLE" ); - using item_filter = std::function; using item_location_filter = std::function; @@ -1816,41 +1814,15 @@ class bionic_install_preset: public inventory_selector_preset } std::string get_denial( const item_location &loc ) const override { - const item *it = loc.get_item(); - const itype *itemtype = it->type; - const bionic_id &bid = itemtype->bionic->id; - - if( it->has_flag( flag_FILTHY ) ) { - // NOLINTNEXTLINE(cata-text-style): single space after the period for symmetry - return _( "/!\\ CBM is highly contaminated. /!\\" ); - } else if( it->has_flag( flag_NO_STERILE ) ) { - // NOLINTNEXTLINE(cata-text-style): single space after the period for symmetry - return _( "/!\\ CBM is not sterile. /!\\ Please use autoclave to sterilize." ); - } else if( it->has_fault( fault_id( "fault_bionic_salvaged" ) ) ) { - return _( "CBM already deployed. Please reset to factory state." ); - } else if( pa.has_bionic( bid ) ) { - return _( "CBM already installed." ); - } else if( !pa.can_install_cbm_on_bp( get_occupied_bodyparts( bid ) ) ) { - return _( "CBM not compatible with patient's body." ); - } else if( bid->upgraded_bionic && - !pa.has_bionic( bid->upgraded_bionic ) && - it->is_upgrade() ) { - return _( "No base version installed." ); - } else if( std::any_of( bid->available_upgrades.begin(), - bid->available_upgrades.end(), - std::bind( &player::has_bionic, &pa, - std::placeholders::_1 ) ) ) { - return _( "Superior version installed." ); - } else if( pa.is_npc() && !bid->has_flag( json_flag_BIONIC_NPC_USABLE ) ) { - return _( "CBM not compatible with patient." ); - } else if( units::energy_max - pa.get_max_power_level() < bid->capacity ) { - return _( "Max power capacity already reached." ); - } else if( !p.has_enough_anesth( *itemtype, pa ) ) { + const ret_val installable = pa.is_installable( loc, true ); + if( installable.success() && !p.has_enough_anesth( *loc.get_item()->type, pa ) ) { const int weight = units::to_kilogram( pa.bodyweight() ) / 10; const int duration = loc.get_item()->type->bionic->difficulty * 2; const requirement_data req_anesth = *requirement_id( "anesthetic" ) * duration * weight; return string_format( _( "%i mL" ), req_anesth.get_tools().front().front().count ); + } else if( !installable.success() ) { + return installable.str(); } return std::string(); @@ -1929,32 +1901,8 @@ class bionic_install_surgeon_preset : public inventory_selector_preset } std::string get_denial( const item_location &loc ) const override { - const item *it = loc.get_item(); - const itype *itemtype = it->type; - const bionic_id &bid = itemtype->bionic->id; - - if( it->has_flag( flag_FILTHY ) ) { - return _( "CBM is filthy." ); - } else if( it->has_flag( flag_NO_STERILE ) ) { - return _( "CBM is not sterile." ); - } else if( it->has_fault( fault_bionic_salvaged ) ) { - return _( "CBM is already deployed." ); - } else if( pa.has_bionic( bid ) ) { - return _( "CBM is already installed." ); - } else if( bid->upgraded_bionic && - !pa.has_bionic( bid->upgraded_bionic ) && - it->is_upgrade() ) { - return _( "No base version installed." ); - } else if( std::any_of( bid->available_upgrades.begin(), - bid->available_upgrades.end(), - std::bind( &player::has_bionic, &pa, - std::placeholders::_1 ) ) ) { - return _( "Superior version installed." ); - } else if( pa.is_npc() && !bid->has_flag( json_flag_BIONIC_NPC_USABLE ) ) { - return _( "CBM is not compatible with patient." ); - } - - return std::string(); + const ret_val installable = pa.is_installable( loc, false ); + return installable.str(); } protected: diff --git a/src/mutation.cpp b/src/mutation.cpp index c8229f05af92d..3824b264a35ac 100644 --- a/src/mutation.cpp +++ b/src/mutation.cpp @@ -854,6 +854,10 @@ bool Character::mutation_ok( const trait_id &mutation, bool force_good, bool for return false; } } + + if( bid->mutation_conflicts.count( mutation ) != 0 ) { + return false; + } } const mutation_branch &mdata = mutation.obj(); @@ -1182,6 +1186,14 @@ bool Character::mutate_towards( const trait_id &mut ) return false; } + // Just prevent it when it conflicts with a CBM, for now + // TODO: Consequences? + for( const bionic_id &bid : get_bionics() ) { + if( bid->mutation_conflicts.count( mut ) != 0 ) { + return false; + } + } + for( size_t i = 0; !has_threshreq && i < threshreq.size(); i++ ) { if( has_trait( threshreq[i] ) ) { has_threshreq = true;