diff --git a/data/json/game_balance.json b/data/json/game_balance.json index 82d4a813f9cdf..e9d2365c4774c 100644 --- a/data/json/game_balance.json +++ b/data/json/game_balance.json @@ -76,13 +76,6 @@ "stype": "bool", "value": false }, - { - "type": "EXTERNAL_OPTION", - "name": "MANUAL_BIONIC_INSTALLATION", - "info": "Permits manual self-installation of bionics.", - "stype": "bool", - "value": false - }, { "type": "EXTERNAL_OPTION", "name": "NO_NPC_FOOD", diff --git a/data/mods/ManualBionicInstall/game_balance.json b/data/mods/ManualBionicInstall/game_balance.json deleted file mode 100644 index 5c9953ab44adc..0000000000000 --- a/data/mods/ManualBionicInstall/game_balance.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "type": "EXTERNAL_OPTION", - "name": "MANUAL_BIONIC_INSTALLATION", - "stype": "bool", - "value": true - } -] diff --git a/data/mods/ManualBionicInstall/modinfo.json b/data/mods/ManualBionicInstall/modinfo.json deleted file mode 100644 index ca9127cca7b8d..0000000000000 --- a/data/mods/ManualBionicInstall/modinfo.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "type": "MOD_INFO", - "ident": "manualbionicinstall", - "name": "Manual Bionic Installation", - "authors": [ "Xhuis" ], - "maintainers": [ "Xhuis" ], - "description": "Allows CBMs to be installed by hand. Pairs well with Safe Autodoc.", - "category": "rebalance", - "dependencies": [ "dda" ], - "obsolete": true - } -] diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index f48e448aacd12..64dfb055f9603 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -499,6 +499,7 @@ This section describes each json file and their contents. Each json has their ow | stat_bonus | (_optional_) List of passive stat bonus. Stat are designated as follow: "DEX", "INT", "STR", "PER". | enchantments | (_optional_) List of enchantments applied by this CBM (see MAGIC.md for instructions on enchantment. NB: enchantments are not necessarily magic.) | learned_spells | (_optional_) Map of {spell:level} you gain when installing this CBM, and lose when you uninstall this CBM. Spell classes are automatically gained. +| installation_requirement | (_optional_) Requirment id pointing to a requirment defining the tools and componentsnt necessary to install this CBM. ```C++ { @@ -514,6 +515,7 @@ This section describes each json file and their contents. Each json has their ow "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"], + "installation_requirement": "sewing_standard", "included_bionics": ["bio_blindfold"] }, { diff --git a/src/bionics.cpp b/src/bionics.cpp index 8b83eb70d4288..e03e3ddfb5e23 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -280,6 +280,8 @@ void bionic_data::load( const JsonObject &jsobj, const std::string ) optional( jsobj, was_loaded, "available_upgrades", available_upgrades ); + optional( jsobj, was_loaded, "installation_requirement", installation_requirement ); + if( jsobj.has_array( "stat_bonus" ) ) { // clear data first so that copy-from can override it stat_bonus.clear(); @@ -359,6 +361,10 @@ void bionic_data::check_bionic_consistency() { for( const bionic_data &bio : get_all() ) { + if( !bio.installation_requirement.is_empty() && !bio.installation_requirement.is_valid() ) { + debugmsg( "Bionic %s uses undefined requirement_id %s", bio.id.c_str(), + bio.installation_requirement.c_str() ); + } if( bio.has_flag( flag_BIO_GUN ) && bio.has_flag( flag_BIO_WEAPON ) ) { debugmsg( "Bionic %s specified as both gun and weapon bionic", bio.id.c_str() ); } @@ -1868,25 +1874,88 @@ void Character::bionics_uninstall_failure( monster &installer, player &patient, } } -bool Character::has_enough_anesth( const itype *cbm, player &patient ) +bool Character::has_enough_anesth( const itype &cbm, player &patient ) { - if( !cbm->bionic ) { - debugmsg( "has_enough_anesth( const itype *cbm ): %s is not a bionic", cbm->get_id() ); + if( !cbm.bionic ) { + debugmsg( "has_enough_anesth( const itype *cbm ): %s is not a bionic", cbm.get_id() ); return false; } - if( has_bionic( bio_painkiller ) || has_trait( trait_NOPAIN ) || + if( patient.has_bionic( bio_painkiller ) || patient.has_trait( trait_NOPAIN ) || has_trait( trait_DEBUG_BIONICS ) ) { return true; } const int weight = units::to_kilogram( patient.bodyweight() ) / 10; const requirement_data req_anesth = *requirement_id( "anesthetic" ) * - cbm->bionic->difficulty * 2 * weight; + cbm.bionic->difficulty * 2 * weight; return req_anesth.can_make_with_inventory( crafting_inventory(), is_crafting_component ); } +bool Character::has_enough_anesth( const itype &cbm ) +{ + if( has_bionic( bio_painkiller ) || has_trait( trait_NOPAIN ) || + has_trait( trait_DEBUG_BIONICS ) ) { + return true; + } + const int weight = units::to_kilogram( bodyweight() ) / 10; + const requirement_data req_anesth = *requirement_id( "anesthetic" ) * + cbm.bionic->difficulty * 2 * weight; + if( !req_anesth.can_make_with_inventory( crafting_inventory(), + is_crafting_component ) ) { + std::string buffer = _( "You don't have enough anesthetic to perform the installation." ); + buffer += "\n"; + buffer += req_anesth.list_missing(); + popup( buffer, PF_NONE ); + return false; + } + return true; +} + +void Character::consume_anesth_requirment( const itype &cbm, player &patient ) +{ + const int weight = units::to_kilogram( patient.bodyweight() ) / 10; + const requirement_data req_anesth = *requirement_id( "anesthetic" ) * + cbm.bionic->difficulty * 2 * weight; + for( const auto &e : req_anesth.get_components() ) { + as_player()->consume_items( e, 1, is_crafting_component ); + } + for( const auto &e : req_anesth.get_tools() ) { + as_player()->consume_tools( e ); + } + invalidate_crafting_inventory(); +} + +bool Character::has_installation_requirment( bionic_id bid ) +{ + if( bid->installation_requirement.is_empty() ) { + return false; + } + + if( !bid->installation_requirement->can_make_with_inventory( crafting_inventory(), + is_crafting_component ) ) { + std::string buffer = _( "You don't have the required components to perform the installation." ); + buffer += "\n"; + buffer += bid->installation_requirement->list_missing(); + popup( buffer, PF_NONE ); + return false; + } + + return true; +} + +void Character::consume_installation_requirment( bionic_id bid ) +{ + for( const auto &e : bid->installation_requirement->get_components() ) { + as_player()->consume_items( e, 1, is_crafting_component ); + } + for( const auto &e : bid->installation_requirement->get_tools() ) { + as_player()->consume_tools( e ); + } + invalidate_crafting_inventory(); +} + // bionic manipulation adjusted skill float Character::bionics_adjusted_skill( const skill_id &most_important_skill, const skill_id &important_skill, @@ -2216,7 +2285,7 @@ bool Character::uninstall_bionic( const bionic &target_cbm, monster &installer, return false; } -bool Character::can_install_bionics( const itype &type, player &installer, bool autodoc, +bool Character::can_install_bionics( const itype &type, Character &installer, bool autodoc, int skill_level ) { if( !type.bionic ) { @@ -2231,6 +2300,12 @@ bool Character::can_install_bionics( const itype &type, player &installer, bool const int difficult = type.bionic->difficulty; float adjusted_skill; + // if we're doing self install + if( !autodoc && installer.is_avatar() ) { + return installer.has_enough_anesth( type ) && + installer.has_installation_requirment( bioid ); + } + if( autodoc ) { adjusted_skill = installer.bionics_adjusted_skill( skill_firstaid, skill_computer, diff --git a/src/bionics.h b/src/bionics.h index 5ef1a76fc7ebd..2524ea870eaf5 100644 --- a/src/bionics.h +++ b/src/bionics.h @@ -126,6 +126,9 @@ struct bionic_data { */ std::set available_upgrades; + /**Requirement to bionic installation*/ + requirement_id installation_requirement; + cata::flat_set flags; bool has_flag( const std::string &flag ) const; diff --git a/src/character.h b/src/character.h index 040ef7dc54a3b..8055254224f47 100644 --- a/src/character.h +++ b/src/character.h @@ -1047,7 +1047,12 @@ class Character : public Creature, public visitable int get_free_bionics_slots( body_part bp ) const; /**Has enough anesthetic for surgery*/ - bool has_enough_anesth( const itype *cbm, player &patient ); + bool has_enough_anesth( const itype &cbm, player &patient ); + bool has_enough_anesth( const itype &cbm ); + void consume_anesth_requirment( const itype &cbm, player &patient ); + /**Has the required equipement for manual installation*/ + bool has_installation_requirment( bionic_id bid ); + void consume_installation_requirment( bionic_id bid ); /** Handles process of introducing patient into anesthesia during Autodoc operations. Requires anesthesia kits or NOPAIN mutation */ void introduce_into_anesthesia( const time_duration &duration, player &installer, bool needs_anesthesia ); @@ -1068,7 +1073,7 @@ class Character : public Creature, public visitable const skill_id &least_important_skill, int skill_level = -1 ); /**Is the installation possible*/ - bool can_install_bionics( const itype &type, player &installer, bool autodoc = false, + bool can_install_bionics( const itype &type, Character &installer, bool autodoc = false, int skill_level = -1 ); std::map bionic_installation_issues( const bionic_id &bioid ); /** Initialize all the values needed to start the operation player_activity */ diff --git a/src/game_inventory.cpp b/src/game_inventory.cpp index 03e7ac3243773..aff828099ac6f 100644 --- a/src/game_inventory.cpp +++ b/src/game_inventory.cpp @@ -1683,7 +1683,7 @@ class bionic_install_preset: public inventory_selector_preset 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 ) ) { + } else if( !p.has_enough_anesth( *itemtype, 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" ) * @@ -1864,7 +1864,7 @@ class bionic_uninstall_preset : public inventory_selector_preset std::string get_denial( const item_location &loc ) const override { const itype *itemtype = loc.get_item()->type; - if( !p.has_enough_anesth( itemtype, pa ) ) { + if( !p.has_enough_anesth( *itemtype, 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" ) * diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index c12456db2b930..755c95e8b5948 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -3967,6 +3967,8 @@ std::unique_ptr saw_barrel_actor::clone() const int install_bionic_actor::use( player &p, item &it, bool, const tripoint & ) const { if( p.can_install_bionics( *it.type, p, false ) ) { + p.consume_installation_requirment( it.type->bionic->id ); + p.consume_anesth_requirment( *it.type, p ); return p.install_bionics( *it.type, p, false ) ? it.type->charges_to_use() : 0; } else { return 0; @@ -3983,11 +3985,10 @@ ret_val install_bionic_actor::can_use( const Character &p, const item &it, if( p.is_mounted() ) { return ret_val::make_failure( _( "You can't install bionics while mounted." ) ); } - if( !get_option( "MANUAL_BIONIC_INSTALLATION" ) && - !p.has_trait( trait_DEBUG_BIONICS ) ) { - return ret_val::make_failure( _( "You can't self-install bionics." ) ); - } else if( !p.has_trait( trait_DEBUG_BIONICS ) ) { - if( it.has_flag( "FILTHY" ) ) { + if( !p.has_trait( trait_DEBUG_BIONICS ) ) { + if( bid->installation_requirement.is_empty() ) { + return ret_val::make_failure( _( "You can't self-install this CBM." ) ); + } else if( it.has_flag( "FILTHY" ) ) { return ret_val::make_failure( _( "You can't install a filthy CBM!" ) ); } else if( it.has_flag( "NO_STERILE" ) ) { return ret_val::make_failure( _( "This CBM is not sterile, you can't install it." ) );