Skip to content

Commit

Permalink
Allow CBMs to specify mutations that prevent installation. (#47822)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
anothersimulacrum authored and ZhilkinSerg committed Apr 7, 2021
1 parent 93bff2f commit c9b575c
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 58 deletions.
2 changes: 2 additions & 0 deletions doc/JSON_INFO.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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"]
},
Expand Down
50 changes: 50 additions & 0 deletions src/bionics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" );

Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -2316,6 +2318,54 @@ bool Character::uninstall_bionic( const bionic &target_cbm, monster &installer,
return false;
}

ret_val<bool> 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<bool>::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<bool>::make_failure( msg );
} else if( it->has_fault( fault_id( "fault_bionic_salvaged" ) ) ) {
return ret_val<bool>::make_failure( _( "CBM already deployed. Please reset to factory state." ) );
} else if( has_bionic( bid ) ) {
return ret_val<bool>::make_failure( _( "CBM is already installed." ) );
} else if( !can_install_cbm_on_bp( get_occupied_bodyparts( bid ) ) ) {
return ret_val<bool>::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<bool>::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<bool>::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<bool>::make_failure( _( "Superior version installed." ) );
} else if( is_npc() && !bid->has_flag( json_flag_BIONIC_NPC_USABLE ) ) {
return ret_val<bool>::make_failure( _( "CBM not compatible with patient." ) );
} else if( units::energy_max - get_max_power_level() < bid->capacity ) {
return ret_val<bool>::make_failure( _( "Max power capacity already reached." ) );
}

return ret_val<bool>::make_success( std::string() );
}

bool Character::can_install_bionics( const itype &type, Character &installer, bool autodoc,
int skill_level )
{
Expand Down
4 changes: 4 additions & 0 deletions src/bionics.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ struct bionic_data {
* E.g. enhanced optic bionic may cancel HYPEROPIC trait.
*/
std::vector<trait_id> canceled_mutations;
/**
* Mutations/traits that prevent installing this CBM
*/
std::set<trait_id> mutation_conflicts;

/**
* The spells you learn when you install this bionic, and what level you learn them at.
Expand Down
3 changes: 3 additions & 0 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,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<void>, but ret_val.h doesn't like it
ret_val<bool> is_installable( const item_location &loc, bool by_autodoc ) const;
std::map<bodypart_id, int> 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,
Expand Down
64 changes: 6 additions & 58 deletions src/game_inventory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool ( const item & )>;
using item_location_filter = std::function<bool ( const item_location & )>;

Expand Down Expand Up @@ -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<bool> 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();
Expand Down Expand Up @@ -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<bool> installable = pa.is_installable( loc, false );
return installable.str();
}

protected:
Expand Down
12 changes: 12 additions & 0 deletions src/mutation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit c9b575c

Please sign in to comment.