1 #include "bionics.h"
2 
3 #include <algorithm> //std::min
4 #include <climits>
5 #include <cmath>
6 #include <cstdlib>
7 #include <forward_list>
8 #include <functional>
9 #include <iterator>
10 #include <list>
11 #include <memory>
12 #include <string>
13 #include <utility>
14 
15 #include "action.h"
16 #include "activity_actor_definitions.h"
17 #include "activity_type.h"
18 #include "assign.h"
19 #include "avatar.h"
20 #include "avatar_action.h"
21 #include "ballistics.h"
22 #include "bodypart.h"
23 #include "calendar.h"
24 #include "cata_utility.h"
25 #include "character.h"
26 #include "character_martial_arts.h"
27 #include "colony.h"
28 #include "color.h"
29 #include "damage.h"
30 #include "debug.h"
31 #include "dispersion.h"
32 #include "effect.h"
33 #include "enum_conversions.h"
34 #include "enums.h"
35 #include "event.h"
36 #include "event_bus.h"
37 #include "explosion.h"
38 #include "field_type.h"
39 #include "flag.h"
40 #include "game.h"
41 #include "generic_factory.h"
42 #include "handle_liquid.h"
43 #include "inventory.h"
44 #include "item.h"
45 #include "item_location.h"
46 #include "itype.h"
47 #include "json.h"
48 #include "line.h"
49 #include "make_static.h"
50 #include "map.h"
51 #include "map_iterator.h"
52 #include "mapdata.h"
53 #include "material.h"
54 #include "memorial_logger.h"
55 #include "messages.h"
56 #include "monster.h"
57 #include "morale_types.h"
58 #include "mutation.h"
59 #include "npc.h"
60 #include "optional.h"
61 #include "options.h"
62 #include "output.h"
63 #include "overmapbuffer.h"
64 #include "pimpl.h"
65 #include "player.h"
66 #include "player_activity.h"
67 #include "point.h"
68 #include "projectile.h"
69 #include "requirements.h"
70 #include "ret_val.h"
71 #include "rng.h"
72 #include "sounds.h"
73 #include "string_formatter.h"
74 #include "teleport.h"
75 #include "translations.h"
76 #include "ui.h"
77 #include "units.h"
78 #include "value_ptr.h"
79 #include "vehicle.h"
80 #include "viewer.h"
81 #include "vpart_position.h"
82 #include "weather.h"
83 #include "weather_gen.h"
84 
85 static const activity_id ACT_OPERATION( "ACT_OPERATION" );
86 
87 static const efftype_id effect_adrenaline( "adrenaline" );
88 static const efftype_id effect_antifungal( "antifungal" );
89 static const efftype_id effect_assisted( "assisted" );
90 static const efftype_id effect_asthma( "asthma" );
91 static const efftype_id effect_badpoison( "badpoison" );
92 static const efftype_id effect_bleed( "bleed" );
93 static const efftype_id effect_bloodworms( "bloodworms" );
94 static const efftype_id effect_cig( "cig" );
95 static const efftype_id effect_datura( "datura" );
96 static const efftype_id effect_dermatik( "dermatik" );
97 static const efftype_id effect_drunk( "drunk" );
98 static const efftype_id effect_fungus( "fungus" );
99 static const efftype_id effect_hallu( "hallu" );
100 static const efftype_id effect_heating_bionic( "heating_bionic" );
101 static const efftype_id effect_high( "high" );
102 static const efftype_id effect_iodine( "iodine" );
103 static const efftype_id effect_meth( "meth" );
104 static const efftype_id effect_narcosis( "narcosis" );
105 static const efftype_id effect_operating( "operating" );
106 static const efftype_id effect_paralysepoison( "paralysepoison" );
107 static const efftype_id effect_pblue( "pblue" );
108 static const efftype_id effect_pkill_l( "pkill_l" );
109 static const efftype_id effect_pkill1( "pkill1" );
110 static const efftype_id effect_pkill2( "pkill2" );
111 static const efftype_id effect_pkill3( "pkill3" );
112 static const efftype_id effect_poison( "poison" );
113 static const efftype_id effect_sleep( "sleep" );
114 static const efftype_id effect_stung( "stung" );
115 static const efftype_id effect_teleglow( "teleglow" );
116 static const efftype_id effect_tetanus( "tetanus" );
117 static const efftype_id effect_took_flumed( "took_flumed" );
118 static const efftype_id effect_took_prozac( "took_prozac" );
119 static const efftype_id effect_took_prozac_bad( "took_prozac_bad" );
120 static const efftype_id effect_took_xanax( "took_xanax" );
121 static const efftype_id effect_under_operation( "under_operation" );
122 static const efftype_id effect_venom_dmg( "venom_dmg" );
123 static const efftype_id effect_venom_weaken( "venom_weaken" );
124 static const efftype_id effect_visuals( "visuals" );
125 
126 static const material_id fuel_type_battery( "battery" );
127 static const material_id fuel_type_metabolism( "metabolism" );
128 static const material_id fuel_type_muscle( "muscle" );
129 static const material_id fuel_type_sun_light( "sunlight" );
130 static const material_id fuel_type_wind( "wind" );
131 
132 static const itype_id itype_adv_UPS_off( "adv_UPS_off" );
133 static const itype_id itype_anesthetic( "anesthetic" );
134 static const itype_id itype_radiocontrol( "radiocontrol" );
135 static const itype_id itype_remotevehcontrol( "remotevehcontrol" );
136 static const itype_id itype_UPS( "UPS" );
137 static const itype_id itype_UPS_off( "UPS_off" );
138 static const itype_id itype_water_clean( "water_clean" );
139 
140 static const fault_id fault_bionic_salvaged( "fault_bionic_salvaged" );
141 
142 static const skill_id skill_computer( "computer" );
143 static const skill_id skill_electronics( "electronics" );
144 static const skill_id skill_firstaid( "firstaid" );
145 static const skill_id skill_mechanics( "mechanics" );
146 
147 static const bionic_id bio_adrenaline( "bio_adrenaline" );
148 static const bionic_id bio_blade_weapon( "bio_blade_weapon" );
149 static const bionic_id bio_blaster( "bio_blaster" );
150 static const bionic_id bio_blood_anal( "bio_blood_anal" );
151 static const bionic_id bio_blood_filter( "bio_blood_filter" );
152 static const bionic_id bio_claws_weapon( "bio_claws_weapon" );
153 static const bionic_id bio_cqb( "bio_cqb" );
154 static const bionic_id bio_earplugs( "bio_earplugs" );
155 static const bionic_id bio_ears( "bio_ears" );
156 static const bionic_id bio_emp( "bio_emp" );
157 static const bionic_id bio_evap( "bio_evap" );
158 static const bionic_id bio_eye_optic( "bio_eye_optic" );
159 static const bionic_id bio_flashbang( "bio_flashbang" );
160 static const bionic_id bio_geiger( "bio_geiger" );
161 static const bionic_id bio_gills( "bio_gills" );
162 static const bionic_id bio_hydraulics( "bio_hydraulics" );
163 static const bionic_id bio_jointservo( "bio_jointservo" );
164 static const bionic_id bio_lighter( "bio_lighter" );
165 static const bionic_id bio_lockpick( "bio_lockpick" );
166 static const bionic_id bio_magnet( "bio_magnet" );
167 static const bionic_id bio_meteorologist( "bio_meteorologist" );
168 static const bionic_id bio_nanobots( "bio_nanobots" );
169 static const bionic_id bio_painkiller( "bio_painkiller" );
170 static const bionic_id bio_power_storage( "bio_power_storage" );
171 static const bionic_id bio_power_storage_mkII( "bio_power_storage_mkII" );
172 static const bionic_id bio_radscrubber( "bio_radscrubber" );
173 static const bionic_id bio_remote( "bio_remote" );
174 static const bionic_id bio_resonator( "bio_resonator" );
175 static const bionic_id bio_shockwave( "bio_shockwave" );
176 static const bionic_id bio_teleport( "bio_teleport" );
177 static const bionic_id bio_time_freeze( "bio_time_freeze" );
178 static const bionic_id bio_tools( "bio_tools" );
179 static const bionic_id bio_torsionratchet( "bio_torsionratchet" );
180 static const bionic_id bio_water_extractor( "bio_water_extractor" );
181 static const bionic_id bio_tools_extend( "bio_tools_extend" );
182 // Aftershock stuff!
183 static const bionic_id afs_bio_dopamine_stimulators( "afs_bio_dopamine_stimulators" );
184 
185 static const trait_id trait_CENOBITE( "CENOBITE" );
186 static const trait_id trait_DEBUG_BIONICS( "DEBUG_BIONICS" );
187 static const trait_id trait_MASOCHIST( "MASOCHIST" );
188 static const trait_id trait_MASOCHIST_MED( "MASOCHIST_MED" );
189 static const trait_id trait_NOPAIN( "NOPAIN" );
190 static const trait_id trait_PROF_AUTODOC( "PROF_AUTODOC" );
191 static const trait_id trait_PROF_MED( "PROF_MED" );
192 static const trait_id trait_THRESH_MEDICAL( "THRESH_MEDICAL" );
193 
194 static const json_character_flag json_flag_BIONIC_GUN( "BIONIC_GUN" );
195 static const json_character_flag json_flag_BIONIC_WEAPON( "BIONIC_WEAPON" );
196 static const json_character_flag json_flag_BIONIC_TOGGLED( "BIONIC_TOGGLED" );
197 
198 static const std::string flag_SEALED( "SEALED" );
199 
200 struct Character::auto_toggle_bionic_result {
201     bool can_burn_fuel = false;
202     bool has_burnable_fuel = false;
203     material_id burnable_fuel_id;
204     enum class fuel_type_t {
205         metabolism,
206         perpetual,
207         remote,
208         other
209     };
210     fuel_type_t fuel_type = fuel_type_t::other;
211     int fuel_energy = 0;
212     float effective_efficiency = 0.0f;
213     int current_fuel_stock = 0;
214 };
215 
216 namespace
217 {
218 generic_factory<bionic_data> bionic_factory( "bionic" );
219 std::vector<bionic_id> faulty_bionics;
220 } //namespace
221 
222 /** @relates string_id */
223 template<>
obj() const224 const bionic_data &string_id<bionic_data>::obj() const
225 {
226     return bionic_factory.obj( *this );
227 }
228 
229 /** @relates string_id */
230 template<>
is_valid() const231 bool string_id<bionic_data>::is_valid() const
232 {
233     return bionic_factory.is_valid( *this );
234 }
235 
get_occupied_bodyparts(const bionic_id & bid)236 std::vector<bodypart_id> get_occupied_bodyparts( const bionic_id &bid )
237 {
238     std::vector<bodypart_id> parts;
239     for( const std::pair<const string_id<body_part_type>, size_t> &element : bid->occupied_bodyparts ) {
240         if( element.second > 0 ) {
241             parts.push_back( element.first.id() );
242         }
243     }
244     return parts;
245 }
246 
has_flag(const json_character_flag & flag) const247 bool bionic_data::has_flag( const json_character_flag &flag ) const
248 {
249     return flags.count( flag ) > 0;
250 }
251 
has_active_flag(const json_character_flag & flag) const252 bool bionic_data::has_active_flag( const json_character_flag &flag ) const
253 {
254     return active_flags.count( flag ) > 0;
255 }
256 
has_inactive_flag(const json_character_flag & flag) const257 bool bionic_data::has_inactive_flag( const json_character_flag &flag ) const
258 {
259     return inactive_flags.count( flag ) > 0;
260 }
261 
itype() const262 itype_id bionic_data::itype() const
263 {
264     // For now we just assume that the bionic id matches the corresponding item
265     // id (as strings).
266     return itype_id( id.str() );
267 }
268 
is_included(const bionic_id & id) const269 bool bionic_data::is_included( const bionic_id &id ) const
270 {
271     return std::find( included_bionics.begin(), included_bionics.end(), id ) != included_bionics.end();
272 }
273 
load_bionic_social_mods(const JsonObject & jo)274 static social_modifiers load_bionic_social_mods( const JsonObject &jo )
275 {
276     social_modifiers ret;
277     jo.read( "lie", ret.lie );
278     jo.read( "persuade", ret.persuade );
279     jo.read( "intimidate", ret.intimidate );
280     return ret;
281 }
282 
load(const JsonObject & jsobj,const std::string &)283 void bionic_data::load( const JsonObject &jsobj, const std::string & )
284 {
285 
286     mandatory( jsobj, was_loaded, "id", id );
287     mandatory( jsobj, was_loaded, "name", name );
288     mandatory( jsobj, was_loaded, "description", description );
289 
290     // uses assign because optional doesn't handle loading units as strings
291     assign( jsobj, "react_cost", power_over_time, false, 0_kJ );
292     assign( jsobj, "capacity", capacity, false, 0_kJ );
293     assign( jsobj, "weight_capacity_bonus", weight_capacity_bonus, false );
294     assign( jsobj, "act_cost", power_activate, false, 0_kJ );
295     assign( jsobj, "deact_cost", power_deactivate, false, 0_kJ );
296 
297     optional( jsobj, was_loaded, "time", charge_time, 0 );
298 
299     optional( jsobj, was_loaded, "flags", flags );
300     optional( jsobj, was_loaded, "active_flags", active_flags );
301     optional( jsobj, was_loaded, "inactive_flags", inactive_flags );
302 
303     optional( jsobj, was_loaded, "fuel_efficiency", fuel_efficiency, 0 );
304     optional( jsobj, was_loaded, "passive_fuel_efficiency", passive_fuel_efficiency, 0 );
305 
306     optional( jsobj, was_loaded, "fake_item", fake_item, itype_id() );
307 
308     optional( jsobj, was_loaded, "enchantments", enchantments );
309     optional( jsobj, was_loaded, "spell_on_activation", spell_on_activate );
310 
311     optional( jsobj, was_loaded, "weight_capacity_modifier", weight_capacity_modifier, 1.0f );
312     optional( jsobj, was_loaded, "exothermic_power_gen", exothermic_power_gen );
313     optional( jsobj, was_loaded, "power_gen_emission", power_gen_emission );
314     optional( jsobj, was_loaded, "coverage_power_gen_penalty", coverage_power_gen_penalty );
315     optional( jsobj, was_loaded, "is_remote_fueled", is_remote_fueled );
316 
317     optional( jsobj, was_loaded, "learned_spells", learned_spells );
318     optional( jsobj, was_loaded, "learned_proficiencies", proficiencies );
319     optional( jsobj, was_loaded, "canceled_mutations", canceled_mutations );
320     optional( jsobj, was_loaded, "included_bionics", included_bionics );
321     optional( jsobj, was_loaded, "included", included );
322     optional( jsobj, was_loaded, "upgraded_bionic", upgraded_bionic );
323     optional( jsobj, was_loaded, "fuel_options", fuel_opts );
324     optional( jsobj, was_loaded, "fuel_capacity", fuel_capacity );
325 
326     optional( jsobj, was_loaded, "available_upgrades", available_upgrades );
327 
328     optional( jsobj, was_loaded, "installation_requirement", installation_requirement );
329 
330     optional( jsobj, was_loaded, "vitamin_absorb_mod", vitamin_absorb_mod, 1.0f );
331 
332     if( jsobj.has_array( "stat_bonus" ) ) {
333         // clear data first so that copy-from can override it
334         stat_bonus.clear();
335         for( JsonArray ja : jsobj.get_array( "stat_bonus" ) ) {
336             stat_bonus.emplace( io::string_to_enum<character_stat>( ja.get_string( 0 ) ),
337                                 ja.get_int( 1 ) );
338         }
339     }
340     if( jsobj.has_array( "encumbrance" ) ) {
341         // clear data first so that copy-from can override it
342         encumbrance.clear();
343         for( JsonArray ja : jsobj.get_array( "encumbrance" ) ) {
344             encumbrance.emplace( bodypart_str_id( ja.get_string( 0 ) ),
345                                  ja.get_int( 1 ) );
346         }
347     }
348     if( jsobj.has_array( "occupied_bodyparts" ) ) {
349         // clear data first so that copy-from can override it
350         occupied_bodyparts.clear();
351         for( JsonArray ja : jsobj.get_array( "occupied_bodyparts" ) ) {
352             occupied_bodyparts.emplace( bodypart_str_id( ja.get_string( 0 ) ),
353                                         ja.get_int( 1 ) );
354         }
355     }
356     if( jsobj.has_array( "env_protec" ) ) {
357         // clear data first so that copy-from can override it
358         env_protec.clear();
359         for( JsonArray ja : jsobj.get_array( "env_protec" ) ) {
360             env_protec.emplace( bodypart_str_id( ja.get_string( 0 ) ), ja.get_int( 1 ) );
361         }
362     }
363     if( jsobj.has_array( "bash_protec" ) ) {
364         // clear data first so that copy-from can override it
365         bash_protec.clear();
366         for( JsonArray ja : jsobj.get_array( "bash_protec" ) ) {
367             bash_protec.emplace( bodypart_str_id( ja.get_string( 0 ) ),
368                                  ja.get_int( 1 ) );
369         }
370     }
371     if( jsobj.has_array( "cut_protec" ) ) {
372         // clear data first so that copy-from can override it
373         cut_protec.clear();
374         for( JsonArray ja : jsobj.get_array( "cut_protec" ) ) {
375             cut_protec.emplace( bodypart_str_id( ja.get_string( 0 ) ),
376                                 ja.get_int( 1 ) );
377         }
378     }
379     if( jsobj.has_array( "bullet_protec" ) ) {
380         // clear data first so that copy-from can override it
381         bullet_protec.clear();
382         for( JsonArray ja : jsobj.get_array( "bullet_protec" ) ) {
383             bullet_protec.emplace( bodypart_str_id( ja.get_string( 0 ) ),
384                                    ja.get_int( 1 ) );
385         }
386     }
387     if( jsobj.has_object( "social_modifiers" ) ) {
388         JsonObject sm = jsobj.get_object( "social_modifiers" );
389         social_mods = load_bionic_social_mods( sm );
390     }
391 
392     activated = has_flag( STATIC( json_character_flag( json_flag_BIONIC_TOGGLED ) ) ) ||
393                 power_activate > 0_kJ ||
394                 charge_time > 0;
395 
396     if( has_flag( STATIC( json_character_flag( "BIONIC_FAULTY" ) ) ) ) {
397         faulty_bionics.push_back( id );
398     }
399 }
400 
load_bionic(const JsonObject & jo,const std::string & src)401 void bionic_data::load_bionic( const JsonObject &jo, const std::string &src )
402 {
403     bionic_factory.load( jo, src );
404 }
405 
get_all()406 const std::vector<bionic_data> &bionic_data::get_all()
407 {
408     return bionic_factory.get_all();
409 }
410 
check_bionic_consistency()411 void bionic_data::check_bionic_consistency()
412 {
413     for( const bionic_data &bio : get_all() ) {
414 
415         if( !bio.installation_requirement.is_empty() && !bio.installation_requirement.is_valid() ) {
416             debugmsg( "Bionic %s uses undefined requirement_id %s", bio.id.c_str(),
417                       bio.installation_requirement.c_str() );
418         }
419         if( bio.has_flag( json_flag_BIONIC_GUN ) && bio.has_flag( json_flag_BIONIC_WEAPON ) ) {
420             debugmsg( "Bionic %s specified as both gun and weapon bionic", bio.id.c_str() );
421         }
422         if( !bio.fake_item.is_empty() &&
423             !item::type_is_defined( bio.fake_item ) ) {
424             debugmsg( "Bionic %s has unknown fake_item %s",
425                       bio.id.c_str(), bio.fake_item.c_str() );
426         }
427         for( const trait_id &mid : bio.canceled_mutations ) {
428             if( !mid.is_valid() ) {
429                 debugmsg( "Bionic %s cancels undefined mutation %s",
430                           bio.id.c_str(), mid.c_str() );
431             }
432         }
433         for( const enchantment_id &eid : bio.id->enchantments ) {
434             if( !eid.is_valid() ) {
435                 debugmsg( "Bionic %s uses undefined enchantment %s", bio.id.c_str(), eid.c_str() );
436             }
437         }
438         for( const bionic_id &bid : bio.included_bionics ) {
439             if( !bid.is_valid() ) {
440                 debugmsg( "Bionic %s includes undefined bionic %s",
441                           bio.id.c_str(), bid.c_str() );
442             }
443             if( !bid->occupied_bodyparts.empty() ) {
444                 debugmsg( "Bionic %s (included by %s) consumes slots, those should be part of the containing bionic instead.",
445                           bid.c_str(), bio.id.c_str() );
446             }
447         }
448         if( bio.upgraded_bionic ) {
449             if( bio.upgraded_bionic == bio.id ) {
450                 debugmsg( "Bionic %s is upgraded with itself", bio.id.c_str() );
451             } else if( !bio.upgraded_bionic.is_valid() ) {
452                 debugmsg( "Bionic %s upgrades undefined bionic %s",
453                           bio.id.c_str(), bio.upgraded_bionic.c_str() );
454             }
455         }
456         if( !item::type_is_defined( bio.itype() ) && !bio.included ) {
457             debugmsg( "Bionic %s has no defined item version", bio.id.c_str() );
458         }
459     }
460 }
461 
bionic_data()462 bionic_data::bionic_data() : name( no_translation( "bad bionic" ) ),
463     description( no_translation( "This bionic was not set up correctly, this is a bug" ) )
464 {
465 }
466 
force_comedown(effect & eff)467 static void force_comedown( effect &eff )
468 {
469     if( eff.is_null() || eff.get_effect_type() == nullptr || eff.get_duration() <= 1_turns ) {
470         return;
471     }
472 
473     eff.set_duration( std::min( eff.get_duration(), eff.get_int_dur_factor() ) );
474 }
475 
discharge_cbm_weapon()476 void npc::discharge_cbm_weapon()
477 {
478     if( cbm_weapon_index < 0 ) {
479         return;
480     }
481     const bionic &bio = ( *my_bionics )[cbm_weapon_index];
482     mod_power_level( -bio.info().power_activate );
483     weapon = real_weapon;
484     cbm_weapon_index = -1;
485 }
486 
check_or_use_weapon_cbm(const bionic_id & cbm_id)487 void npc::check_or_use_weapon_cbm( const bionic_id &cbm_id )
488 {
489     // if we're already using a bio_weapon, keep using it
490     if( cbm_weapon_index >= 0 ) {
491         return;
492     }
493     const float allowed_ratio = static_cast<int>( rules.cbm_reserve ) / 100.0f;
494     const units::energy free_power = get_power_level() - get_max_power_level() * allowed_ratio;
495     if( free_power <= 0_mJ ) {
496         return;
497     }
498 
499     int index = 0;
500     bool found = false;
501     for( bionic &i : *my_bionics ) {
502         if( i.id == cbm_id && !i.powered ) {
503             found = true;
504             break;
505         }
506         index += 1;
507     }
508     if( !found ) {
509         return;
510     }
511     bionic &bio = ( *my_bionics )[index];
512 
513     if( bio.info().has_flag( json_flag_BIONIC_GUN ) ) {
514         const item cbm_weapon = item( bio.info().fake_item );
515         bool not_allowed = !rules.has_flag( ally_rule::use_guns ) ||
516                            ( rules.has_flag( ally_rule::use_silent ) && !cbm_weapon.is_silent() );
517         if( is_player_ally() && not_allowed ) {
518             return;
519         }
520 
521         const int ups_charges = charges_of( itype_UPS );
522         int ammo_count = weapon.ammo_remaining();
523         const int ups_drain = weapon.get_gun_ups_drain();
524         if( ups_drain > 0 ) {
525             ammo_count = std::min( ammo_count, ups_charges / ups_drain );
526         }
527         const int cbm_ammo = free_power /  bio.info().power_activate;
528 
529         if( weapon_value( weapon, ammo_count ) < weapon_value( cbm_weapon, cbm_ammo ) ) {
530             real_weapon = weapon;
531             weapon = cbm_weapon;
532             cbm_weapon_index = index;
533         }
534     } else if( bio.info().has_flag( json_flag_BIONIC_WEAPON ) && !weapon.has_flag( flag_NO_UNWIELD ) &&
535                free_power > bio.info().power_activate ) {
536         if( is_armed() ) {
537             stow_item( weapon );
538         }
539         add_msg_if_player_sees( pos(), m_info, _( "%s activates their %s." ),
540                                 disp_name(), bio.info().name );
541 
542         weapon = item( bio.info().fake_item );
543         mod_power_level( -bio.info().power_activate );
544         bio.powered = true;
545         cbm_weapon_index = index;
546     }
547 }
548 
549 // Why put this in a Big Switch?  Why not let bionics have pointers to
550 // functions, much like monsters and items?
551 //
552 // Well, because like diseases, which are also in a Big Switch, bionics don't
553 // share functions....
activate_bionic(int b,bool eff_only,bool * close_bionics_ui)554 bool Character::activate_bionic( int b, bool eff_only, bool *close_bionics_ui )
555 {
556     bionic &bio = ( *my_bionics )[b];
557     const bool mounted = is_mounted();
558     if( bio.incapacitated_time > 0_turns ) {
559         add_msg( m_info, _( "Your %s is shorting out and can't be activated." ),
560                  bio.info().name );
561         return false;
562     }
563 
564     // Special compatibility code for people who updated saves with their claws out
565     if( ( weapon.typeId().str() == bio_claws_weapon.str() &&
566           bio.id == bio_claws_weapon ) ||
567         ( weapon.typeId().str() == bio_blade_weapon.str() &&
568           bio.id == bio_blade_weapon ) ) {
569         return deactivate_bionic( b );
570     }
571 
572     // eff_only means only do the effect without messing with stats or displaying messages
573     if( !eff_only ) {
574         if( bio.powered ) {
575             // It's already on!
576             return false;
577         }
578         if( !enough_power_for( bio.id ) ) {
579             add_msg_if_player( m_info, _( "You don't have the power to activate your %s." ),
580                                bio.info().name );
581             return false;
582         }
583 
584         const auto_toggle_bionic_result result = auto_toggle_bionic( b, true );
585         if( result.can_burn_fuel && !result.has_burnable_fuel ) {
586             return false;
587         }
588 
589         if( !bio.activate_spell( *this ) ) {
590             // the spell this bionic uses was unable to be cast
591             return false;
592         }
593 
594         // We can actually activate now, do activation-y things
595         mod_power_level( -bio.info().power_activate );
596 
597         bio.powered = bio.info().has_flag( json_flag_BIONIC_TOGGLED ) || bio.info().charge_time > 0;
598 
599         if( bio.info().charge_time > 0 ) {
600             bio.charge_timer = bio.info().charge_time;
601         }
602         if( !bio.id->enchantments.empty() ) {
603             recalculate_enchantment_cache();
604         }
605     }
606 
607     auto add_msg_activate = [&]() {
608         if( !eff_only ) {
609             add_msg_if_player( m_info, _( "You activate your %s." ), bio.info().name );
610         }
611     };
612     auto refund_power = [&]() {
613         if( !eff_only ) {
614             mod_power_level( bio.info().power_activate );
615         }
616     };
617 
618     item tmp_item;
619     avatar &player_character = get_avatar();
620     map &here = get_map();
621     // On activation effects go here
622     if( bio.info().has_flag( json_flag_BIONIC_GUN ) ) {
623         add_msg_activate();
624         refund_power(); // Power usage calculated later, in avatar_action::fire
625         if( close_bionics_ui ) {
626             *close_bionics_ui = true;
627         }
628         avatar_action::fire_ranged_bionic( player_character, item( bio.info().fake_item ),
629                                            bio.info().power_activate );
630     } else if( bio.info().has_flag( json_flag_BIONIC_WEAPON ) ) {
631         if( weapon.has_flag( flag_NO_UNWIELD ) ) {
632             cata::optional<int> active_bio_weapon_index = active_bionic_weapon_index();
633             if( active_bio_weapon_index && deactivate_bionic( *active_bio_weapon_index, eff_only ) ) {
634                 // restore state and try again
635                 refund_power();
636                 bio.powered = false;
637                 // note: deep recursion is not possible, as `deactivate_bionic` won't return true second time
638                 return activate_bionic( b, eff_only, close_bionics_ui );
639             }
640 
641             add_msg_if_player( m_info, _( "Deactivate your %s first!" ), weapon.tname() );
642             refund_power();
643             bio.powered = false;
644             return false;
645         }
646 
647         if( !weapon.is_null() ) {
648             const std::string query = string_format( _( "Stop wielding %s?" ), weapon.tname() );
649             if( !dispose_item( item_location( *this, &weapon ), query ) ) {
650                 refund_power();
651                 bio.powered = false;
652                 return false;
653             }
654         }
655 
656         add_msg_activate();
657         weapon = item( bio.info().fake_item );
658         weapon.invlet = '#';
659         if( bio.ammo_count > 0 ) {
660             weapon.ammo_set( bio.ammo_loaded, bio.ammo_count );
661             avatar_action::fire_wielded_weapon( player_character );
662         }
663     } else if( bio.id == bio_ears && has_active_bionic( bio_earplugs ) ) {
664         add_msg_activate();
665         for( bionic &bio : *my_bionics ) {
666             if( bio.id == bio_earplugs ) {
667                 bio.powered = false;
668                 add_msg_if_player( m_info, _( "Your %s automatically turn off." ),
669                                    bio.info().name );
670             }
671         }
672     } else if( bio.id == bio_earplugs && has_active_bionic( bio_ears ) ) {
673         add_msg_activate();
674         for( bionic &bio : *my_bionics ) {
675             if( bio.id == bio_ears ) {
676                 bio.powered = false;
677                 add_msg_if_player( m_info, _( "Your %s automatically turns off." ),
678                                    bio.info().name );
679             }
680         }
681     } else if( bio.id == bio_evap ) {
682         add_msg_activate();
683         const w_point weatherPoint = *get_weather().weather_precise;
684         int humidity = get_local_humidity( weatherPoint.humidity, get_weather().weather_id,
685                                            g->is_sheltered( player_character.pos() ) );
686         // thirst units = 5 mL
687         int water_available = std::lround( humidity * 3.0 / 100.0 );
688         if( water_available == 0 ) {
689             bio.powered = false;
690             add_msg_if_player( m_bad, _( "There is not enough humidity in the air for your %s to function." ),
691                                bio.info().name );
692             return false;
693         } else if( water_available == 1 ) {
694             add_msg_if_player( m_mixed,
695                                _( "Your %s issues a low humidity warning.  Efficiency will be reduced." ),
696                                bio.info().name );
697         }
698     } else if( bio.id == bio_tools ) {
699         add_msg_activate();
700         invalidate_crafting_inventory();
701     } else if( bio.id == bio_cqb ) {
702         add_msg_activate();
703         const avatar *you = as_avatar();
704         if( you && !martial_arts_data->pick_style( *you ) ) {
705             bio.powered = false;
706             add_msg_if_player( m_info, _( "You change your mind and turn it off." ) );
707             return false;
708         }
709     } else if( bio.id == bio_resonator ) {
710         add_msg_activate();
711         //~Sound of a bionic sonic-resonator shaking the area
712         sounds::sound( pos(), 30, sounds::sound_t::combat, _( "VRRRRMP!" ), false, "bionic",
713                        static_cast<std::string>( bio_resonator ) );
714         for( const tripoint &bashpoint : here.points_in_radius( pos(), 1 ) ) {
715             here.bash( bashpoint, 110 );
716             // Multibash effect, so that doors &c will fall
717             here.bash( bashpoint, 110 );
718             here.bash( bashpoint, 110 );
719         }
720 
721         mod_moves( -100 );
722     } else if( bio.id == bio_time_freeze ) {
723         if( mounted ) {
724             refund_power();
725             add_msg_if_player( m_info, _( "You cannot activate %s while mounted." ), bio.info().name );
726             return false;
727         }
728         add_msg_activate();
729 
730         mod_moves( units::to_kilojoule( get_power_level() ) );
731         set_power_level( 0_kJ );
732         add_msg_if_player( m_good, _( "Your speed suddenly increases!" ) );
733         if( one_in( 3 ) ) {
734             add_msg_if_player( m_bad, _( "Your muscles tear with the strain." ) );
735             apply_damage( nullptr, bodypart_id( "arm_l" ), rng( 5, 10 ) );
736             apply_damage( nullptr, bodypart_id( "arm_r" ), rng( 5, 10 ) );
737             apply_damage( nullptr, bodypart_id( "leg_l" ), rng( 7, 12 ) );
738             apply_damage( nullptr, bodypart_id( "leg_r" ), rng( 7, 12 ) );
739             apply_damage( nullptr, bodypart_id( "torso" ), rng( 5, 15 ) );
740         }
741         if( one_in( 5 ) ) {
742             add_effect( effect_teleglow, rng( 5_minutes, 40_minutes ) );
743         }
744     } else if( bio.id == bio_teleport ) {
745         if( mounted ) {
746             refund_power();
747             add_msg_if_player( m_info, _( "You cannot activate %s while mounted." ), bio.info().name );
748             return false;
749         }
750         add_msg_activate();
751 
752         teleport::teleport( *this );
753         mod_moves( -100 );
754     } else if( bio.id == bio_blood_anal ) {
755         add_msg_activate();
756         conduct_blood_analysis();
757     } else if( bio.id == bio_blood_filter ) {
758         add_msg_activate();
759         static const std::vector<efftype_id> removable = {{
760                 effect_fungus, effect_dermatik, effect_bloodworms,
761                 effect_tetanus, effect_poison, effect_badpoison, effect_stung,
762                 effect_pkill1, effect_pkill2, effect_pkill3, effect_pkill_l,
763                 effect_drunk, effect_cig, effect_high, effect_hallu, effect_visuals,
764                 effect_pblue, effect_iodine, effect_datura,
765                 effect_took_xanax, effect_took_prozac, effect_took_prozac_bad,
766                 effect_took_flumed, effect_antifungal, effect_venom_weaken,
767                 effect_venom_dmg, effect_paralysepoison
768             }
769         };
770 
771         for( const string_id<effect_type> &eff : removable ) {
772             remove_effect( eff );
773         }
774         // Purging the substance won't remove the fatigue it caused
775         force_comedown( get_effect( effect_adrenaline ) );
776         force_comedown( get_effect( effect_meth ) );
777         set_painkiller( 0 );
778         set_stim( 0 );
779         mod_moves( -100 );
780     } else if( bio.id == bio_torsionratchet ) {
781         add_msg_activate();
782         add_msg_if_player( m_info, _( "Your torsion ratchet locks onto your joints." ) );
783     } else if( bio.id == bio_jointservo ) {
784         add_msg_activate();
785         add_msg_if_player( m_info, _( "You can now run faster, assisted by joint servomotors." ) );
786     } else if( bio.id == bio_lighter ) {
787         const cata::optional<tripoint> pnt = choose_adjacent( _( "Start a fire where?" ) );
788         if( pnt && here.is_flammable( *pnt ) ) {
789             add_msg_activate();
790             here.add_field( *pnt, fd_fire, 1 );
791             mod_moves( -100 );
792         } else {
793             refund_power();
794             add_msg_if_player( m_info, _( "There's nothing to light there." ) );
795             return false;
796         }
797     } else if( bio.id == bio_geiger ) {
798         add_msg_activate();
799         add_msg_if_player( m_info, _( "Your radiation level: %d" ), get_rad() );
800     } else if( bio.id == bio_radscrubber ) {
801         add_msg_activate();
802         if( get_rad() > 4 ) {
803             mod_rad( -5 );
804         } else {
805             set_rad( 0 );
806         }
807     } else if( bio.id == bio_adrenaline ) {
808         add_msg_activate();
809         if( has_effect( effect_adrenaline ) ) {
810             add_msg_if_player( m_bad, _( "Safeguards kick in, and the bionic refuses to activate!" ) );
811             refund_power();
812             return false;
813         } else {
814             add_msg_activate();
815             add_effect( effect_adrenaline, 20_minutes );
816         }
817     } else if( bio.id == bio_emp ) {
818         if( const cata::optional<tripoint> pnt = choose_adjacent( _( "Create an EMP where?" ) ) ) {
819             add_msg_activate();
820             explosion_handler::emp_blast( *pnt );
821             mod_moves( -100 );
822         } else {
823             refund_power();
824             return false;
825         }
826     } else if( bio.id == bio_hydraulics ) {
827         add_msg_activate();
828         add_msg_if_player( m_good, _( "Your muscles hiss as hydraulic strength fills them!" ) );
829         //~ Sound of hissing hydraulic muscle! (not quite as loud as a car horn)
830         sounds::sound( pos(), 19, sounds::sound_t::activity, _( "HISISSS!" ), false, "bionic",
831                        static_cast<std::string>( bio_hydraulics ) );
832     } else if( bio.id == bio_water_extractor ) {
833         bool no_target = true;
834         bool extracted = false;
835         for( item &it : here.i_at( pos() ) ) {
836             static const units::volume volume_per_water_charge = 500_ml;
837             if( it.is_corpse() ) {
838                 const int avail = it.get_var( "remaining_water", it.volume() / volume_per_water_charge );
839                 if( avail > 0 ) {
840                     no_target = false;
841                     if( query_yn( _( "Extract water from the %s" ),
842                                   colorize( it.tname(), it.color_in_inventory() ) ) ) {
843                         item water( itype_water_clean, calendar::turn, avail );
844                         water.set_item_temperature( 0.00001 * it.temperature );
845                         if( liquid_handler::consume_liquid( water ) ) {
846                             add_msg_activate();
847                             extracted = true;
848                             it.set_var( "remaining_water", static_cast<int>( water.charges ) );
849                         }
850                         break;
851                     }
852                 }
853             }
854         }
855         if( no_target ) {
856             add_msg_if_player( m_bad, _( "There is no suitable corpse on this tile." ) );
857         }
858         if( !extracted ) {
859             refund_power();
860             return false;
861         }
862     } else if( bio.id == bio_magnet ) {
863         add_msg_activate();
864         static const std::set<material_id> affected_materials =
865         { material_id( "iron" ), material_id( "steel" ), material_id( "hardsteel" ), material_id( "budget_steel" ) };
866         // Remember all items that will be affected, then affect them
867         // Don't "snowball" by affecting some items multiple times
868         std::vector<std::pair<item, tripoint>> affected;
869         const units::mass weight_cap = weight_capacity();
870         for( const tripoint &p : here.points_in_radius( pos(), 10 ) ) {
871             if( p == pos() || !here.has_items( p ) || here.has_flag( flag_SEALED, p ) ) {
872                 continue;
873             }
874 
875             map_stack stack = here.i_at( p );
876             for( auto it = stack.begin(); it != stack.end(); it++ ) {
877                 if( it->weight() < weight_cap &&
878                     it->made_of_any( affected_materials ) ) {
879                     affected.emplace_back( std::make_pair( *it, p ) );
880                     stack.erase( it );
881                     break;
882                 }
883             }
884         }
885 
886         for( const std::pair<item, tripoint> &pr : affected ) {
887             projectile proj;
888             proj.speed  = 50;
889             proj.impact = damage_instance::physical( pr.first.weight() / 250_gram, 0, 0, 0 );
890             // make the projectile stop one tile short to prevent hitting the player
891             proj.range = rl_dist( pr.second, pos() ) - 1;
892             proj.proj_effects = {{ "NO_ITEM_DAMAGE", "DRAW_AS_LINE", "NO_DAMAGE_SCALING", "JET" }};
893 
894             dealt_projectile_attack dealt = projectile_attack(
895                                                 proj, pr.second, pos(), dispersion_sources{ 0 }, this );
896             here.add_item_or_charges( dealt.end_point, pr.first );
897         }
898 
899         mod_moves( -100 );
900     } else if( bio.id == bio_lockpick ) {
901         if( !is_avatar() ) {
902             return false;
903         }
904         cata::optional<tripoint> target = lockpick_activity_actor::select_location( player_character );
905         if( target.has_value() ) {
906             add_msg_activate();
907             assign_activity(
908                 player_activity( lockpick_activity_actor::use_bionic( here.getabs( *target ) ) ) );
909             if( close_bionics_ui ) {
910                 *close_bionics_ui = true;
911             }
912         } else {
913             refund_power();
914             return false;
915         }
916     } else if( bio.id == bio_flashbang ) {
917         add_msg_activate();
918         explosion_handler::flashbang( pos(), true );
919         mod_moves( -100 );
920     } else if( bio.id == bio_shockwave ) {
921         add_msg_activate();
922         explosion_handler::shockwave( pos(), 3, 4, 2, 8, true );
923         add_msg_if_player( m_neutral, _( "You unleash a powerful shockwave!" ) );
924         mod_moves( -100 );
925     } else if( bio.id == bio_meteorologist ) {
926         add_msg_activate();
927         // Calculate local wind power
928         int vehwindspeed = 0;
929         if( optional_vpart_position vp = here.veh_at( pos() ) ) {
930             // vehicle velocity in mph
931             vehwindspeed = std::abs( vp->vehicle().velocity / 100 );
932         }
933         const oter_id &cur_om_ter = overmap_buffer.ter( global_omt_location() );
934         /* cache g->get_temperature( player location ) since it is used twice. No reason to recalc */
935         const int player_local_temp = g->weather.get_temperature( player_character.pos() );
936         /* windpower defined in internal velocity units (=.01 mph) */
937         double windpower = 100.0f * get_local_windpower( g->weather.windspeed + vehwindspeed,
938                            cur_om_ter, pos(), g->weather.winddirection, g->is_sheltered( pos() ) );
939         add_msg_if_player( m_info, _( "Temperature: %s." ), print_temperature( player_local_temp ) );
940         const w_point weatherPoint = *g->weather.weather_precise;
941         add_msg_if_player( m_info, _( "Relative Humidity: %s." ),
942                            print_humidity(
943                                get_local_humidity( weatherPoint.humidity, get_weather().weather_id,
944                                        g->is_sheltered( player_character.pos() ) ) ) );
945         add_msg_if_player( m_info, _( "Pressure: %s." ),
946                            print_pressure( static_cast<int>( weatherPoint.pressure ) ) );
947         add_msg_if_player( m_info, _( "Wind Speed: %.1f %s." ),
948                            convert_velocity( static_cast<int>( windpower ), VU_WIND ),
949                            velocity_units( VU_WIND ) );
950         add_msg_if_player( m_info, _( "Feels Like: %s." ),
951                            print_temperature(
952                                get_local_windchill( weatherPoint.temperature, weatherPoint.humidity,
953                                        windpower / 100 ) + player_local_temp ) );
954         std::string dirstring = get_dirstring( g->weather.winddirection );
955         add_msg_if_player( m_info, _( "Wind Direction: From the %s." ), dirstring );
956     } else if( bio.id == bio_remote ) {
957         add_msg_activate();
958         int choice = uilist( _( "Perform which function:" ), {
959             _( "Control vehicle" ), _( "RC radio" )
960         } );
961         if( choice >= 0 && choice <= 1 ) {
962             item ctr;
963             if( choice == 0 ) {
964                 ctr = item( "remotevehcontrol", calendar::turn_zero );
965             } else {
966                 ctr = item( "radiocontrol", calendar::turn_zero );
967             }
968             ctr.charges = units::to_kilojoule( get_power_level() );
969             int power_use = invoke_item( &ctr );
970             mod_power_level( units::from_kilojoule( -power_use ) );
971             bio.powered = ctr.active;
972         } else {
973             bio.powered = g->remoteveh() != nullptr || !get_value( "remote_controlling" ).empty();
974         }
975     } else if( bio.info().is_remote_fueled ) {
976         std::vector<item *> cables = items_with( []( const item & it ) {
977             return it.has_flag( flag_CABLE_SPOOL );
978         } );
979         bool has_cable = !cables.empty();
980         bool free_cable = false;
981         bool success = false;
982         if( !has_cable ) {
983             add_msg_if_player( m_info,
984                                _( "You need a jumper cable connected to a power source to drain power from it." ) );
985         } else {
986             for( item *cable : cables ) {
987                 const std::string state = cable->get_var( "state" );
988                 if( state == "cable_charger" ) {
989                     add_msg_if_player( m_info,
990                                        _( "Cable is plugged-in to the CBM but it has to be also connected to the power source." ) );
991                 }
992                 if( state == "cable_charger_link" ) {
993                     add_msg_activate();
994                     success = true;
995                     add_msg_if_player( m_info,
996                                        _( "You are plugged to the vehicle.  It will charge you if it has some juice in it." ) );
997                 }
998                 if( state == "solar_pack_link" ) {
999                     add_msg_activate();
1000                     success = true;
1001                     add_msg_if_player( m_info,
1002                                        _( "You are plugged to a solar pack.  It will charge you if it's unfolded and in sunlight." ) );
1003                 }
1004                 if( state == "UPS_link" ) {
1005                     add_msg_activate();
1006                     success = true;
1007                     add_msg_if_player( m_info,
1008                                        _( "You are plugged to a UPS.  It will charge you if it has some juice in it." ) );
1009                 }
1010                 if( state == "solar_pack" || state == "UPS" ) {
1011                     add_msg_if_player( m_info,
1012                                        _( "You have a cable plugged to a portable power source, but you need to plug it in to the CBM." ) );
1013                 }
1014                 if( state == "pay_out_cable" ) {
1015                     add_msg_if_player( m_info,
1016                                        _( "You have a cable plugged to a vehicle, but you need to plug it in to the CBM." ) );
1017                 }
1018                 if( state == "attach_first" ) {
1019                     free_cable = true;
1020                 }
1021             }
1022 
1023             if( free_cable ) {
1024                 add_msg_if_player( m_info,
1025                                    _( "You have at least one free cable in your inventory that you could use to plug yourself in." ) );
1026             }
1027         }
1028         if( !success ) {
1029             refund_power();
1030             bio.powered = false;
1031             return false;
1032         }
1033     } else {
1034         add_msg_activate();
1035     }
1036 
1037     // Recalculate stats (strength, mods from pain etc.) that could have been affected
1038     calc_encumbrance();
1039     reset();
1040 
1041     // Also reset crafting inventory cache if this bionic spawned a fake item
1042     if( !bio.info().fake_item.is_empty() ) {
1043         invalidate_crafting_inventory();
1044     }
1045 
1046     return true;
1047 }
1048 
active_bionic_weapon_index() const1049 cata::optional<int> Character::active_bionic_weapon_index() const
1050 {
1051     if( weapon.is_null() ) {
1052         return cata::nullopt;
1053     }
1054 
1055     for( int i = 0; i < static_cast<int>( my_bionics->size() ); i++ ) {
1056         const bionic &bio = ( *my_bionics )[ i ];
1057         if( bio.powered && bio.info().has_flag( json_flag_BIONIC_WEAPON ) &&
1058             weapon.typeId() == bio.info().fake_item ) {
1059             return i;
1060         }
1061     }
1062 
1063     return cata::nullopt;
1064 }
1065 
can_deactivate_bionic(int b,bool eff_only) const1066 ret_val<bool> Character::can_deactivate_bionic( int b, bool eff_only ) const
1067 {
1068     bionic &bio = ( *my_bionics )[b];
1069 
1070     if( bio.incapacitated_time > 0_turns ) {
1071         return ret_val<bool>::make_failure( _( "Your %s is shorting out and can't be deactivated." ),
1072                                             bio.info().name );
1073     }
1074 
1075     if( !eff_only ) {
1076         if( !bio.powered ) {
1077             // It's already off!
1078             return ret_val<bool>::make_failure();
1079         }
1080         if( !bio.info().has_flag( json_flag_BIONIC_TOGGLED ) ) {
1081             // It's a fire-and-forget bionic, we can't turn it off but have to wait for
1082             //it to run out of charge
1083             return ret_val<bool>::make_failure( _( "You can't deactivate your %s manually!" ),
1084                                                 bio.info().name );
1085         }
1086         if( get_power_level() < bio.info().power_deactivate ) {
1087             return ret_val<bool>::make_failure( _( "You don't have the power to deactivate your %s." ),
1088                                                 bio.info().name );
1089         }
1090     }
1091 
1092     return ret_val<bool>::make_success();
1093 }
1094 
deactivate_bionic(int b,bool eff_only)1095 bool Character::deactivate_bionic( int b, bool eff_only )
1096 {
1097     const auto can_deactivate = can_deactivate_bionic( b, eff_only );
1098 
1099     if( !can_deactivate.success() ) {
1100         if( !can_deactivate.str().empty() ) {
1101             add_msg( m_info,  can_deactivate.str() );
1102         }
1103         return false;
1104     }
1105 
1106     bionic &bio = ( *my_bionics )[b];
1107 
1108     if( bio.info().is_remote_fueled ) {
1109         reset_remote_fuel();
1110     }
1111 
1112     // Just do the effect, no stat changing or messages
1113     if( !eff_only ) {
1114         //We can actually deactivate now, do deactivation-y things
1115         mod_power_level( -bio.info().power_deactivate );
1116         bio.powered = false;
1117         add_msg_if_player( m_neutral, _( "You deactivate your %s." ), bio.info().name );
1118     }
1119 
1120     // Deactivation effects go here
1121     if( bio.info().has_flag( json_flag_BIONIC_WEAPON ) ) {
1122         if( weapon.typeId() == bio.info().fake_item ) {
1123             add_msg_if_player( _( "You withdraw your %s." ), weapon.tname() );
1124             if( get_player_view().sees( pos() ) ) {
1125                 if( male ) {
1126                     add_msg_if_npc( m_info, _( "<npcname> withdraws his %s." ), weapon.tname() );
1127                 } else {
1128                     add_msg_if_npc( m_info, _( "<npcname> withdraws her %s." ), weapon.tname() );
1129                 }
1130             }
1131             bio.ammo_loaded =
1132                 weapon.ammo_data() != nullptr ? weapon.ammo_data()->get_id() : itype_id::NULL_ID();
1133             bio.ammo_count = static_cast<unsigned int>( weapon.ammo_remaining() );
1134             weapon = item();
1135             invalidate_crafting_inventory();
1136         }
1137     } else if( bio.id == bio_cqb ) {
1138         martial_arts_data->selected_style_check();
1139     } else if( bio.id == bio_remote ) {
1140         if( g->remoteveh() != nullptr && !has_active_item( itype_remotevehcontrol ) ) {
1141             g->setremoteveh( nullptr );
1142         } else if( !get_value( "remote_controlling" ).empty() &&
1143                    !has_active_item( itype_radiocontrol ) ) {
1144             set_value( "remote_controlling", "" );
1145         }
1146     } else if( bio.id == bio_tools ) {
1147         invalidate_crafting_inventory();
1148     }
1149 
1150     // Recalculate stats (strength, mods from pain etc.) that could have been affected
1151     calc_encumbrance();
1152     reset();
1153     if( !bio.id->enchantments.empty() ) {
1154         recalculate_enchantment_cache();
1155     }
1156 
1157     // Also reset crafting inventory cache if this bionic spawned a fake item
1158     if( !bio.info().fake_item.is_empty() ) {
1159         invalidate_crafting_inventory();
1160     }
1161 
1162     // Compatibility with old saves without the toolset hammerspace
1163     if( !eff_only && bio.id == bio_tools && !has_bionic( bio_tools_extend ) ) {
1164         // E X T E N D    T O O L S
1165         add_bionic( bio_tools_extend );
1166     }
1167 
1168     return true;
1169 }
1170 
auto_toggle_bionic(const int b,const bool start)1171 Character::auto_toggle_bionic_result Character::auto_toggle_bionic( const int b, const bool start )
1172 {
1173     auto_toggle_bionic_result result;
1174     bionic &bio = ( *my_bionics )[b];
1175     if( bio.info().fuel_opts.empty() && !bio.info().is_remote_fueled ) {
1176         return result;
1177     }
1178     result.can_burn_fuel = true;
1179     std::vector<material_id> fuel_available = get_fuel_available( bio.id );
1180 
1181     const bool is_remote_fueled = bio.info().is_remote_fueled;
1182     material_id remote_fuel;
1183     if( is_remote_fueled ) {
1184         remote_fuel = find_remote_fuel();
1185         if( !remote_fuel.is_empty() ) {
1186             fuel_available.emplace_back( remote_fuel );
1187         }
1188     }
1189 
1190     bool toggle_off = false;
1191     bool keep_off = false;
1192     if( fuel_available.empty() ) {
1193         if( bio.powered || start ) {
1194             if( start ) {
1195                 add_msg_player_or_npc( m_bad, _( "Your %s does not have enough fuel to start." ),
1196                                        _( "<npcname>'s %s does not have enough fuel to start." ),
1197                                        bio.info().name );
1198                 if( bio.powered ) {
1199                     deactivate_bionic( b );
1200                 }
1201             } else {
1202                 add_msg_player_or_npc( m_info,
1203                                        _( "Your %s runs out of fuel and turns off." ),
1204                                        _( "<npcname>'s %s runs out of fuel and turns off." ),
1205                                        bio.info().name );
1206                 if( bio.powered ) {
1207                     bio.powered = false;
1208                     deactivate_bionic( b, true );
1209                 }
1210             }
1211             toggle_off = true;
1212         } else {
1213             keep_off = true;
1214         }
1215     } else {
1216         std::string msg_player;
1217         std::string msg_npc;
1218         for( const material_id &fuel : fuel_available ) {
1219             const int fuel_energy = fuel->get_fuel_data().energy;
1220             const bool is_metabolism_powered = fuel == fuel_type_metabolism;
1221             const bool is_perpetual_fuel = fuel->get_fuel_data().is_perpetual_fuel;
1222             const bool is_remote_fuel = is_remote_fueled && fuel == remote_fuel;
1223             float effective_efficiency = get_effective_efficiency( b, bio.info().fuel_efficiency );
1224             if( is_remote_fuel && fuel == fuel_type_sun_light ) {
1225                 effective_efficiency *= item_worn_with_flag( flag_SOLARPACK_ON ).type->solar_efficiency;
1226             }
1227             int current_fuel_stock = 0;
1228             if( is_metabolism_powered ) {
1229                 current_fuel_stock = std::max( 0.0f, get_stored_kcal() - 0.8f *
1230                                                get_healthy_kcal() );
1231             } else if( is_perpetual_fuel ) {
1232                 current_fuel_stock = 1;
1233             } else if( is_remote_fuel ) {
1234                 current_fuel_stock = std::stoi( get_value( "rem_" + fuel.str() ) );
1235                 if( current_fuel_stock <= 0 ) {
1236                     remove_value( "rem_" + fuel.str() );
1237                 }
1238             } else {
1239                 current_fuel_stock = std::stoi( get_value( fuel.str() ) );
1240                 if( current_fuel_stock <= 0 ) {
1241                     remove_value( fuel.str() );
1242                 }
1243             }
1244 
1245             if( result.has_burnable_fuel ) {
1246                 // if we already found a burnable fuel we can skip the following
1247                 // code which only generates fuel-not-found messages and assigns
1248                 // found fuel to the result. code before this has side-effects
1249                 // so cannot be skipped.
1250                 continue;
1251             }
1252 
1253             if( bio.get_safe_fuel_thresh() > 0
1254                 && get_power_level() + units::from_kilojoule( fuel_energy ) * effective_efficiency >
1255                 get_max_power_level() * std::min( 1.0f, bio.get_safe_fuel_thresh() ) ) {
1256                 if( bio.powered || start ) {
1257                     if( !start ) {
1258                         if( is_metabolism_powered ) {
1259                             msg_player = _( "Your %s turns off to not waste calories." );
1260                             msg_npc = _( "<npcname>'s %s turns off to not waste calories." );
1261                         } else if( is_perpetual_fuel ) {
1262                             msg_player = _( "Your %s turns off after filling your power banks." );
1263                             msg_npc = _( "<npcname>'s %s turns off after filling their power banks." );
1264                         } else {
1265                             msg_player = _( "Your %s turns off to not waste fuel." );
1266                             msg_npc = _( "<npcname>'s %s turns off to not waste fuel." );
1267                         }
1268                     } else if( get_max_power_level() == 0_mJ ) {
1269                         msg_player = _( "Your %s cannot be started because you don't have any bionic power storage." );
1270                         msg_npc = _( "<npcname>'s %s cannot be started because they don't have any bionic power storage." );
1271                     } else if( get_power_level() != get_max_power_level() ) {
1272                         msg_player = _( "Your %s cannot be started due to fuel saving." );
1273                         msg_npc = _( "<npcname>'s %s cannot be started due to fuel saving." );
1274                     } else {
1275                         msg_player = _( "Your %s cannot be started because your power banks are full." );
1276                         msg_npc = _( "<npcname>'s %s cannot be started because their power banks are full." );
1277                     }
1278                 }
1279             } else if( current_fuel_stock <= 0 ) {
1280                 if( bio.powered || start ) {
1281                     if( !start ) {
1282                         if( is_metabolism_powered ) {
1283                             msg_player =
1284                                 _( "Stored calories are below the safe threshold, your %s shuts down to preserve your health." );
1285                             msg_npc = _( "Stored calories are below the safe threshold, <npcname>'s %s shuts down to preserve their health." );
1286                         } else {
1287                             msg_player = _( "Your %s runs out of fuel and turns off." );
1288                             msg_npc = _( "<npcname>'s %s runs out of fuel and turns off." );
1289                         }
1290                     } else {
1291                         if( is_metabolism_powered ) {
1292                             msg_player = _( "Your %s cannot be started because your calories are below safe levels." );
1293                             msg_npc = _( "<npcname>'s %s cannot be started because their calories are below safe levels." );
1294                         } else {
1295                             msg_player = _( "Your %s doesn't have enough fuel to start." );
1296                             msg_npc = _( "<npcname>'s %s doesn't have enough fuel to start." );
1297                         }
1298                     }
1299                 }
1300             } else {
1301                 result.has_burnable_fuel = true;
1302                 result.burnable_fuel_id = fuel;
1303                 if( is_metabolism_powered ) {
1304                     result.fuel_type = auto_toggle_bionic_result::fuel_type_t::metabolism;
1305                 } else if( is_perpetual_fuel ) {
1306                     result.fuel_type = auto_toggle_bionic_result::fuel_type_t::perpetual;
1307                 } else if( is_remote_fuel ) {
1308                     result.fuel_type = auto_toggle_bionic_result::fuel_type_t::remote;
1309                 } else {
1310                     result.fuel_type = auto_toggle_bionic_result::fuel_type_t::other;
1311                 }
1312                 result.fuel_energy = fuel_energy;
1313                 result.effective_efficiency = effective_efficiency;
1314                 result.current_fuel_stock = current_fuel_stock;
1315             }
1316         }
1317         if( !result.has_burnable_fuel ) {
1318             if( bio.powered || start ) {
1319                 add_msg_player_or_npc( m_info, msg_player, msg_npc, bio.info().name );
1320                 if( bio.powered ) {
1321                     bio.powered = false;
1322                     deactivate_bionic( b, true );
1323                 }
1324                 toggle_off = true;
1325             } else {
1326                 keep_off = true;
1327             }
1328         }
1329     }
1330 
1331     if( !toggle_off && !bio.powered && !start && bio.is_auto_start_on() ) {
1332         const float start_threshold = bio.get_auto_start_thresh();
1333         if( get_power_level() <= start_threshold * get_max_power_level() ) {
1334             if( !keep_off ) {
1335                 activate_bionic( b );
1336             } else if( calendar::once_every( 1_hours ) ) {
1337                 add_msg_player_or_npc( m_bad, _( "Your %s does not have enough fuel to use Auto Start." ),
1338                                        _( "<npcname>'s %s does not have enough fuel to use Auto Start." ),
1339                                        bio.info().name );
1340             }
1341         }
1342     }
1343 
1344     return result;
1345 }
1346 
burn_fuel(const int b,const auto_toggle_bionic_result & result)1347 void Character::burn_fuel( const int b, const auto_toggle_bionic_result &result )
1348 {
1349     bionic &bio = ( *my_bionics )[b];
1350     if( !bio.powered || !result.can_burn_fuel || !result.has_burnable_fuel ) {
1351         return;
1352     }
1353 
1354     map &here = get_map();
1355     switch( result.fuel_type ) {
1356         case auto_toggle_bionic_result::fuel_type_t::metabolism: {
1357             const int kcal_consumed = result.fuel_energy;
1358             // 1kcal = 4184 J
1359             const units::energy power_gain = kcal_consumed * 4184_J * result.effective_efficiency;
1360             mod_stored_kcal( -kcal_consumed, true );
1361             mod_power_level( power_gain );
1362             break;
1363         }
1364         case auto_toggle_bionic_result::fuel_type_t::perpetual:
1365             if( result.burnable_fuel_id == fuel_type_sun_light && g->is_in_sunlight( pos() ) ) {
1366                 const weather_type_id &wtype = current_weather( pos() );
1367                 const float tick_sunlight = incident_sunlight( wtype, calendar::turn );
1368                 const double intensity = tick_sunlight / default_daylight_level();
1369                 mod_power_level( units::from_kilojoule( result.fuel_energy ) * intensity *
1370                                  result.effective_efficiency );
1371             } else if( result.burnable_fuel_id == fuel_type_wind ) {
1372                 int vehwindspeed = 0;
1373                 const optional_vpart_position vp = here.veh_at( pos() );
1374                 if( vp ) {
1375                     // vehicle velocity in mph
1376                     vehwindspeed = std::abs( vp->vehicle().velocity / 100 );
1377                 }
1378                 const double windpower = get_local_windpower( g->weather.windspeed + vehwindspeed,
1379                                          overmap_buffer.ter( global_omt_location() ), pos(), g->weather.winddirection,
1380                                          g->is_sheltered( pos() ) );
1381                 mod_power_level( units::from_kilojoule( result.fuel_energy ) * windpower *
1382                                  result.effective_efficiency );
1383             } else if( result.burnable_fuel_id == fuel_type_muscle ) {
1384                 // simply return
1385             }
1386             break;
1387         case auto_toggle_bionic_result::fuel_type_t::remote: {
1388             const int unconsumed = consume_remote_fuel( 1 );
1389             int current_fuel_stock = result.current_fuel_stock;
1390             if( unconsumed == 0 ) {
1391                 mod_power_level( units::from_kilojoule( result.fuel_energy ) * result.effective_efficiency );
1392                 current_fuel_stock -= 1;
1393             } else {
1394                 current_fuel_stock = 0;
1395             }
1396             set_value( "rem_" + result.burnable_fuel_id.str(), std::to_string( current_fuel_stock ) );
1397             break;
1398         }
1399         case auto_toggle_bionic_result::fuel_type_t::other:
1400             set_value( result.burnable_fuel_id.str(), std::to_string( result.current_fuel_stock - 1 ) );
1401             update_fuel_storage( result.burnable_fuel_id );
1402             mod_power_level( units::from_kilojoule( result.fuel_energy ) * result.effective_efficiency );
1403             break;
1404     }
1405 
1406     heat_emission( b, result.fuel_energy );
1407     here.emit_field( pos(), bio.info().power_gen_emission );
1408 }
1409 
passive_power_gen(int b)1410 void Character::passive_power_gen( int b )
1411 {
1412     const bionic &bio = ( *my_bionics )[b];
1413     const float passive_fuel_efficiency = bio.info().passive_fuel_efficiency;
1414     if( bio.info().fuel_opts.empty() || bio.is_this_fuel_powered( fuel_type_muscle ) ||
1415         passive_fuel_efficiency == 0.0 ) {
1416         return;
1417     }
1418     const float effective_passive_efficiency = get_effective_efficiency( b, passive_fuel_efficiency );
1419     const std::vector<material_id> &fuel_available = get_fuel_available( bio.id );
1420     map &here = get_map();
1421 
1422     for( const material_id &fuel : fuel_available ) {
1423         const int fuel_energy = fuel->get_fuel_data().energy;
1424         if( !fuel->get_fuel_data().is_perpetual_fuel ) {
1425             continue;
1426         }
1427 
1428         if( fuel == fuel_type_sun_light ) {
1429             const double modifier = g->natural_light_level( pos().z ) / default_daylight_level();
1430             mod_power_level( units::from_kilojoule( fuel_energy ) * modifier * effective_passive_efficiency );
1431         } else if( fuel == fuel_type_wind ) {
1432             int vehwindspeed = 0;
1433             const optional_vpart_position vp = here.veh_at( pos() );
1434             if( vp ) {
1435                 // vehicle velocity in mph
1436                 vehwindspeed = std::abs( vp->vehicle().velocity / 100 );
1437             }
1438             const double windpower = get_local_windpower( g->weather.windspeed + vehwindspeed,
1439                                      overmap_buffer.ter( global_omt_location() ), pos(), g->weather.winddirection,
1440                                      g->is_sheltered( pos() ) );
1441             mod_power_level( units::from_kilojoule( fuel_energy ) * windpower * effective_passive_efficiency );
1442         } else {
1443             mod_power_level( units::from_kilojoule( fuel_energy ) * effective_passive_efficiency );
1444         }
1445 
1446         heat_emission( b, fuel_energy );
1447         here.emit_field( pos(), bio.info().power_gen_emission );
1448 
1449     }
1450 }
1451 
find_remote_fuel(bool look_only)1452 material_id Character::find_remote_fuel( bool look_only )
1453 {
1454     material_id remote_fuel;
1455     map &here = get_map();
1456 
1457     const std::vector<item *> cables = items_with( []( const item & it ) {
1458         return it.active && it.has_flag( flag_CABLE_SPOOL );
1459     } );
1460 
1461     for( const item *cable : cables ) {
1462 
1463         const cata::optional<tripoint> target = cable->get_cable_target( this, pos() );
1464         if( !target ) {
1465             if( here.is_outside( pos() ) && !is_night( calendar::turn ) &&
1466                 cable->get_var( "state" ) == "solar_pack_link" ) {
1467                 if( !look_only ) {
1468                     set_value( "sunlight", "1" );
1469                 }
1470                 remote_fuel = fuel_type_sun_light;
1471             }
1472 
1473             if( cable->get_var( "state" ) == "UPS_link" ) {
1474                 static const item_filter used_ups = [&]( const item & itm ) {
1475                     return itm.get_var( "cable" ) == "plugged_in";
1476                 };
1477                 if( !look_only ) {
1478                     if( has_charges( itype_UPS_off, 1, used_ups ) ) {
1479                         set_value( "rem_battery", std::to_string( charges_of( itype_UPS_off,
1480                                    units::to_kilojoule( max_power_level ), used_ups ) ) );
1481                     } else if( has_charges( itype_adv_UPS_off, 1, used_ups ) ) {
1482                         set_value( "rem_battery", std::to_string( charges_of( itype_adv_UPS_off,
1483                                    units::to_kilojoule( max_power_level ), used_ups ) ) );
1484                     } else {
1485                         set_value( "rem_battery", std::to_string( 0 ) );
1486                     }
1487                 }
1488                 remote_fuel = fuel_type_battery;
1489             }
1490             continue;
1491         }
1492         const optional_vpart_position vp = here.veh_at( *target );
1493         if( !vp ) {
1494             continue;
1495         }
1496         if( !look_only ) {
1497             set_value( "rem_battery", std::to_string( vp->vehicle().fuel_left( itype_id( "battery" ),
1498                        true ) ) );
1499         }
1500         remote_fuel = fuel_type_battery;
1501     }
1502 
1503     return remote_fuel;
1504 }
1505 
consume_remote_fuel(int amount)1506 int Character::consume_remote_fuel( int amount )
1507 {
1508     int unconsumed_amount = amount;
1509     const std::vector<item *> cables = items_with( []( const item & it ) {
1510         return it.active && it.has_flag( flag_CABLE_SPOOL );
1511     } );
1512 
1513     map &here = get_map();
1514     for( const item *cable : cables ) {
1515         const cata::optional<tripoint> target = cable->get_cable_target( this, pos() );
1516         if( target ) {
1517             const optional_vpart_position vp = here.veh_at( *target );
1518             if( !vp ) {
1519                 continue;
1520             }
1521             unconsumed_amount = vp->vehicle().discharge_battery( amount );
1522         }
1523     }
1524 
1525     if( unconsumed_amount > 0 ) {
1526         static const item_filter used_ups = [&]( const item & itm ) {
1527             return itm.get_var( "cable" ) == "plugged_in";
1528         };
1529         if( has_charges( itype_UPS_off, unconsumed_amount, used_ups ) ) {
1530             use_charges( itype_UPS_off, unconsumed_amount, used_ups );
1531             unconsumed_amount -= 1;
1532         } else if( has_charges( itype_adv_UPS_off, unconsumed_amount, used_ups ) ) {
1533             use_charges( itype_adv_UPS_off, roll_remainder( unconsumed_amount * 0.6 ), used_ups );
1534             unconsumed_amount -= 1;
1535         }
1536     }
1537 
1538     return unconsumed_amount;
1539 }
1540 
reset_remote_fuel()1541 void Character::reset_remote_fuel()
1542 {
1543     if( get_bionic_fueled_with( fuel_type_sun_light ).empty() ) {
1544         remove_value( "sunlight" );
1545     }
1546     remove_value( "rem_battery" );
1547 }
1548 
heat_emission(int b,int fuel_energy)1549 void Character::heat_emission( int b, int fuel_energy )
1550 {
1551     const bionic &bio = ( *my_bionics )[b];
1552     if( !bio.info().exothermic_power_gen ) {
1553         return;
1554     }
1555     const float efficiency = bio.info().fuel_efficiency;
1556 
1557     const int heat_prod = fuel_energy * ( 1.0f - efficiency );
1558     const int heat_level = std::min( heat_prod / 10, 4 );
1559     const emit_id hotness = emit_id( "emit_hot_air" + std::to_string( heat_level ) + "_cbm" );
1560     map &here = get_map();
1561     if( hotness.is_valid() ) {
1562         const int heat_spread = std::max( heat_prod / 10 - heat_level, 1 );
1563         here.emit_field( pos(), hotness, heat_spread );
1564     }
1565     for( const std::pair<const bodypart_str_id, size_t> &bp : bio.info().occupied_bodyparts ) {
1566         add_effect( effect_heating_bionic, 2_seconds, bp.first.id(), false, heat_prod );
1567     }
1568 }
1569 
get_effective_efficiency(int b,float fuel_efficiency)1570 float Character::get_effective_efficiency( int b, float fuel_efficiency )
1571 {
1572     const bionic &bio = ( *my_bionics )[b];
1573     const cata::optional<float> &coverage_penalty = bio.info().coverage_power_gen_penalty;
1574     float effective_efficiency = fuel_efficiency;
1575     if( coverage_penalty ) {
1576         int coverage = 0;
1577         const std::map< bodypart_str_id, size_t > &occupied_bodyparts = bio.info().occupied_bodyparts;
1578         for( const std::pair< const bodypart_str_id, size_t > &elem : occupied_bodyparts ) {
1579             for( const item &i : worn ) {
1580                 if( i.covers( elem.first ) && !i.has_flag( flag_ALLOWS_NATURAL_ATTACKS ) &&
1581                     !i.has_flag( flag_SEMITANGIBLE ) &&
1582                     !i.has_flag( flag_PERSONAL ) && !i.has_flag( flag_AURA ) ) {
1583                     coverage += i.get_coverage( elem.first.id() );
1584                 }
1585             }
1586         }
1587         effective_efficiency = fuel_efficiency * ( 1.0 - ( coverage / ( 100.0 *
1588                                occupied_bodyparts.size() ) )
1589                                * coverage_penalty.value() );
1590     }
1591     return effective_efficiency;
1592 }
1593 
1594 /**
1595  * @param p the player
1596  * @param bio the bionic that is meant to be recharged.
1597  * @param amount the amount of power that is to be spent recharging the bionic.
1598  * @return indicates whether we successfully charged the bionic.
1599  */
attempt_recharge(Character & p,bionic & bio,units::energy & amount)1600 static bool attempt_recharge( Character &p, bionic &bio, units::energy &amount )
1601 {
1602     const bionic_data &info = bio.info();
1603     units::energy power_cost = info.power_over_time;
1604     bool recharged = false;
1605 
1606     if( power_cost > 0_kJ ) {
1607         if( info.has_flag( STATIC( json_character_flag( "BIONIC_ARMOR_INTERFACE" ) ) ) ) {
1608             // Don't spend any power on armor interfacing unless we're wearing active powered armor.
1609             bool powered_armor = std::any_of( p.worn.begin(), p.worn.end(),
1610             []( const item & w ) {
1611                 return w.active && w.is_power_armor();
1612             } );
1613             if( !powered_armor ) {
1614                 const units::energy armor_power_cost = 1_kJ;
1615                 power_cost -= armor_power_cost;
1616             }
1617         }
1618         if( p.get_power_level() >= power_cost ) {
1619             // Set the recharging cost and charge the bionic.
1620             amount = power_cost;
1621             bio.charge_timer = info.charge_time;
1622             recharged = true;
1623         }
1624     }
1625 
1626     return recharged;
1627 }
1628 
process_bionic(const int b)1629 void Character::process_bionic( const int b )
1630 {
1631     bionic &bio = ( *my_bionics )[b];
1632     const auto_toggle_bionic_result result = auto_toggle_bionic( b, false );
1633 
1634     // Only powered bionics should be processed
1635     if( !bio.powered ) {
1636         passive_power_gen( b );
1637         return;
1638     }
1639 
1640     // These might be affected by environmental conditions, status effects, faulty bionics, etc.
1641     int discharge_rate = 1;
1642 
1643     units::energy cost = 0_mJ;
1644 
1645     bio.charge_timer = std::max( 0, bio.charge_timer - discharge_rate );
1646     if( bio.charge_timer <= 0 ) {
1647         if( bio.info().charge_time > 0 ) {
1648             if( bio.info().has_flag( STATIC( json_character_flag( "BIONIC_POWER_SOURCE" ) ) ) ) {
1649                 // Convert fuel to bionic power
1650                 burn_fuel( b, result );
1651                 // Reset timer
1652                 bio.charge_timer = bio.info().charge_time;
1653             } else {
1654                 // Try to recharge our bionic if it is made for it
1655                 bool recharged = attempt_recharge( *this, bio, cost );
1656                 if( !recharged ) {
1657                     // No power to recharge, so deactivate
1658                     bio.powered = false;
1659                     add_msg_if_player( m_neutral, _( "Your %s powers down." ), bio.info().name );
1660                     // This purposely bypasses the deactivation cost
1661                     deactivate_bionic( b, true );
1662                     return;
1663                 }
1664                 if( cost > 0_mJ ) {
1665                     mod_power_level( -cost );
1666                 }
1667             }
1668         }
1669     }
1670 
1671     // Bionic effects on every turn they are active go here.
1672     if( bio.id == bio_remote ) {
1673         if( g->remoteveh() == nullptr && get_value( "remote_controlling" ).empty() ) {
1674             bio.powered = false;
1675             add_msg_if_player( m_warning, _( "Your %s has lost connection and is turning off." ),
1676                                bio.info().name );
1677         }
1678     } else if( bio.id == bio_hydraulics ) {
1679         // Sound of hissing hydraulic muscle! (not quite as loud as a car horn)
1680         sounds::sound( pos(), 19, sounds::sound_t::activity, _( "HISISSS!" ), false, "bionic",
1681                        static_cast<std::string>( bio_hydraulics ) );
1682     } else if( bio.id == bio_nanobots ) {
1683         std::forward_list<bodypart_id> bleeding_bp_parts;
1684         for( const bodypart_id &bp : get_all_body_parts() ) {
1685             if( has_effect( effect_bleed, bp.id() ) ) {
1686                 bleeding_bp_parts.push_front( bp );
1687             }
1688         }
1689         std::vector<bodypart_id> damaged_hp_parts;
1690         for( const std::pair<const bodypart_str_id, bodypart> &part : get_body() ) {
1691             const int hp_cur = part.second.get_hp_cur();
1692             if( hp_cur > 0 && hp_cur < part.second.get_hp_max() ) {
1693                 damaged_hp_parts.push_back( part.first.id() );
1694             }
1695         }
1696         if( damaged_hp_parts.empty() && bleeding_bp_parts.empty() ) {
1697             // Nothing to heal. Return the consumed power and exit early
1698             mod_power_level( cost );
1699             return;
1700         }
1701         for( const bodypart_id &i : bleeding_bp_parts ) {
1702             // effectively reduces by 1 intensity level
1703             if( get_stored_kcal() >= 15 ) {
1704                 get_effect( effect_bleed, i ).mod_duration( -get_effect( effect_bleed, i ).get_int_dur_factor() );
1705                 mod_stored_kcal( -15 );
1706             } else {
1707                 bleeding_bp_parts.clear();
1708                 break;
1709             }
1710         }
1711         if( calendar::once_every( 60_turns ) ) {
1712             if( get_stored_kcal() >= 5 && !damaged_hp_parts.empty() ) {
1713                 const bodypart_id part_to_heal = damaged_hp_parts[ rng( 0, damaged_hp_parts.size() - 1 ) ];
1714                 heal( part_to_heal, 1 );
1715                 mod_stored_kcal( -5 );
1716             }
1717         }
1718     } else if( bio.id == bio_painkiller ) {
1719         const int pkill = get_painkiller();
1720         const int pain = get_pain();
1721         int max_pkill = std::min( 150, pain );
1722         if( pkill < max_pkill ) {
1723             mod_painkiller( 1 );
1724             mod_power_level( -2_kJ );
1725         }
1726 
1727         // Only dull pain so extreme that we can't pkill it safely
1728         if( pkill >= 150 && pain > pkill && get_stim() > -150 ) {
1729             mod_pain( -1 );
1730             // Negative side effect: negative stim
1731             mod_stim( -1 );
1732             mod_power_level( -2_kJ );
1733         }
1734     } else if( bio.id == bio_gills ) {
1735         if( has_effect( effect_asthma ) ) {
1736             add_msg_if_player( m_good,
1737                                _( "You feel your throat open up and air filling your lungs!" ) );
1738             remove_effect( effect_asthma );
1739         }
1740     } else if( bio.id == bio_evap ) {
1741         // Aero-Evaporator provides water at 60 watts with 2 L / kWh efficiency
1742         // which is 10 mL per 5 minutes.  Humidity can modify the amount gained.
1743         if( calendar::once_every( 5_minutes ) ) {
1744             const w_point weatherPoint = *get_weather().weather_precise;
1745             int humidity = get_local_humidity( weatherPoint.humidity, get_weather().weather_id,
1746                                                g->is_sheltered( pos() ) );
1747             // in thirst units = 5 mL water
1748             int water_available = std::lround( humidity * 3.0 / 100.0 );
1749             // At 50% relative humidity or more, the player will draw 10 mL
1750             // At 16% relative humidity or less, the bionic will give up
1751             if( water_available == 0 ) {
1752                 add_msg_if_player( m_bad,
1753                                    _( "There is not enough humidity in the air for your %s to function." ),
1754                                    bio.info().name );
1755                 deactivate_bionic( b );
1756             } else if( water_available == 1 ) {
1757                 add_msg_if_player( m_mixed,
1758                                    _( "Your %s issues a low humidity warning.  Efficiency is reduced." ),
1759                                    bio.info().name );
1760             }
1761 
1762             mod_thirst( -water_available );
1763         }
1764 
1765         if( get_thirst() < -40 ) {
1766             add_msg_if_player( m_good,
1767                                _( "You are properly hydrated.  Your %s chirps happily." ),
1768                                bio.info().name );
1769             deactivate_bionic( b );
1770         }
1771     } else if( bio.id == afs_bio_dopamine_stimulators ) {
1772         // Aftershock
1773         add_morale( MORALE_FEELING_GOOD, 20, 20, 30_minutes, 20_minutes, true );
1774     }
1775 }
1776 
roll_critical_bionics_failure(const bodypart_id & bp)1777 void Character::roll_critical_bionics_failure( const bodypart_id &bp )
1778 {
1779     if( one_in( get_part_hp_cur( bp ) / 4 ) ) {
1780         set_part_hp_cur( bp, 0 );
1781     }
1782 }
1783 
bionics_uninstall_failure(int difficulty,int success,float adjusted_skill)1784 void Character::bionics_uninstall_failure( int difficulty, int success, float adjusted_skill )
1785 {
1786     // "success" should be passed in as a negative integer representing how far off we
1787     // were for a successful removal.  We use this to determine consequences for failing.
1788     success = std::abs( success );
1789 
1790     // failure level is decided by how far off the character was from a successful removal, and
1791     // this is scaled up or down by the ratio of difficulty/skill.  At high skill levels (or low
1792     // difficulties), only minor consequences occur.  At low skill levels, severe consequences
1793     // are more likely.
1794     const int failure_level = static_cast<int>( std::sqrt( success * 4.0 * difficulty /
1795                               adjusted_skill ) );
1796     const int fail_type = std::min( 5, failure_level );
1797 
1798     if( fail_type <= 0 ) {
1799         add_msg( m_neutral, _( "The removal fails without incident." ) );
1800         return;
1801     }
1802 
1803     add_msg( m_neutral, _( "The removal is a failure." ) );
1804     std::set<bodypart_id> bp_hurt;
1805     switch( fail_type ) {
1806         case 1:
1807             if( !has_trait( trait_NOPAIN ) ) {
1808                 add_msg_if_player( m_bad, _( "It really hurts!" ) );
1809                 mod_pain( rng( 10, 30 ) );
1810             }
1811             break;
1812 
1813         case 2:
1814         case 3:
1815             for( const bodypart_id &bp : get_all_body_parts() ) {
1816                 if( has_effect( effect_under_operation, bp.id() ) ) {
1817                     if( bp_hurt.count( bp->main_part ) > 0 ) {
1818                         continue;
1819                     }
1820                     bp_hurt.emplace( bp->main_part );
1821                     apply_damage( this, bp, rng( 5, 10 ), true );
1822                     add_msg_player_or_npc( m_bad, _( "Your %s is damaged." ), _( "<npcname>'s %s is damaged." ),
1823                                            body_part_name_accusative( bp ) );
1824                 }
1825             }
1826             break;
1827 
1828         case 4:
1829         case 5:
1830             for( const bodypart_id &bp : get_all_body_parts() ) {
1831                 if( has_effect( effect_under_operation, bp.id() ) ) {
1832                     if( bp_hurt.count( bp->main_part ) > 0 ) {
1833                         continue;
1834                     }
1835                     bp_hurt.emplace( bp->main_part );
1836 
1837                     apply_damage( this, bp, rng( 25, 50 ), true );
1838                     roll_critical_bionics_failure( bp );
1839 
1840                     add_msg_player_or_npc( m_bad, _( "Your %s is severely damaged." ),
1841                                            _( "<npcname>'s %s is severely damaged." ),
1842                                            body_part_name_accusative( bp ) );
1843                 }
1844             }
1845             break;
1846     }
1847 
1848 }
1849 
bionics_uninstall_failure(monster & installer,player & patient,int difficulty,int success,float adjusted_skill)1850 void Character::bionics_uninstall_failure( monster &installer, player &patient, int difficulty,
1851         int success, float adjusted_skill )
1852 {
1853 
1854     // "success" should be passed in as a negative integer representing how far off we
1855     // were for a successful removal.  We use this to determine consequences for failing.
1856     success = std::abs( success );
1857 
1858     // failure level is decided by how far off the monster was from a successful removal, and
1859     // this is scaled up or down by the ratio of difficulty/skill.  At high skill levels (or low
1860     // difficulties), only minor consequences occur.  At low skill levels, severe consequences
1861     // are more likely.
1862     const int failure_level = static_cast<int>( std::sqrt( success * 4.0 * difficulty /
1863                               adjusted_skill ) );
1864     const int fail_type = std::min( 5, failure_level );
1865 
1866     bool u_see = sees( patient );
1867 
1868     if( u_see || patient.is_player() ) {
1869         if( fail_type <= 0 ) {
1870             add_msg( m_neutral, _( "The removal fails without incident." ) );
1871             return;
1872         }
1873         switch( rng( 1, 5 ) ) {
1874             case 1:
1875                 add_msg( m_mixed, _( "The %s flub the operation." ), installer.name() );
1876                 break;
1877             case 2:
1878                 add_msg( m_mixed, _( "The %s messes up the operation." ), installer.name() );
1879                 break;
1880             case 3:
1881                 add_msg( m_mixed, _( "The operation fails." ) );
1882                 break;
1883             case 4:
1884                 add_msg( m_mixed, _( "The operation is a failure." ) );
1885                 break;
1886             case 5:
1887                 add_msg( m_mixed, _( "The %s screws up the operation." ), installer.name() );
1888                 break;
1889         }
1890     }
1891     std::set<bodypart_id> bp_hurt;
1892     switch( fail_type ) {
1893         case 1:
1894             if( !has_trait( trait_NOPAIN ) ) {
1895                 patient.add_msg_if_player( m_bad, _( "It really hurts!" ) );
1896                 patient.mod_pain( rng( 10, 30 ) );
1897             }
1898             break;
1899 
1900         case 2:
1901         case 3:
1902             for( const bodypart_id &bp : get_all_body_parts() ) {
1903                 if( has_effect( effect_under_operation, bp.id() ) ) {
1904                     if( bp_hurt.count( bp->main_part ) > 0 ) {
1905                         continue;
1906                     }
1907                     bp_hurt.emplace( bp->main_part );
1908                     patient.apply_damage( this, bp, rng( failure_level, failure_level * 2 ), true );
1909                     if( u_see ) {
1910                         patient.add_msg_player_or_npc( m_bad, _( "Your %s is damaged." ), _( "<npcname>'s %s is damaged." ),
1911                                                        body_part_name_accusative( bp ) );
1912                     }
1913                 }
1914             }
1915             break;
1916 
1917         case 4:
1918         case 5:
1919             for( const bodypart_id &bp : get_all_body_parts() ) {
1920                 if( has_effect( effect_under_operation, bp.id() ) ) {
1921                     if( bp_hurt.count( bp->main_part ) > 0 ) {
1922                         continue;
1923                     }
1924                     bp_hurt.emplace( bp->main_part );
1925 
1926                     patient.apply_damage( this, bp, rng( 25, 50 ), true );
1927                     roll_critical_bionics_failure( bp );
1928 
1929                     if( u_see ) {
1930                         patient.add_msg_player_or_npc( m_bad, _( "Your %s is severely damaged." ),
1931                                                        _( "<npcname>'s %s is severely damaged." ),
1932                                                        body_part_name_accusative( bp ) );
1933                     }
1934                 }
1935             }
1936             break;
1937     }
1938 }
1939 
has_enough_anesth(const itype & cbm,player & patient)1940 bool Character::has_enough_anesth( const itype &cbm, player &patient )
1941 {
1942     if( !cbm.bionic ) {
1943         debugmsg( "has_enough_anesth( const itype *cbm ): %s is not a bionic", cbm.get_id().str() );
1944         return false;
1945     }
1946 
1947     if( patient.has_bionic( bio_painkiller ) || patient.has_trait( trait_NOPAIN ) ||
1948         has_trait( trait_DEBUG_BIONICS ) ) {
1949         return true;
1950     }
1951 
1952     const int weight = units::to_kilogram( patient.bodyweight() ) / 10;
1953     const requirement_data req_anesth = *requirement_id( "anesthetic" ) *
1954                                         cbm.bionic->difficulty * 2 * weight;
1955 
1956     return req_anesth.can_make_with_inventory( crafting_inventory(), is_crafting_component );
1957 }
1958 
has_enough_anesth(const itype & cbm)1959 bool Character::has_enough_anesth( const itype &cbm )
1960 {
1961     if( has_bionic( bio_painkiller ) || has_trait( trait_NOPAIN ) ||
1962         has_trait( trait_DEBUG_BIONICS ) ) {
1963         return true;
1964     }
1965     const int weight = units::to_kilogram( bodyweight() ) / 10;
1966     const requirement_data req_anesth = *requirement_id( "anesthetic" ) *
1967                                         cbm.bionic->difficulty * 2 * weight;
1968     if( !req_anesth.can_make_with_inventory( crafting_inventory(),
1969             is_crafting_component ) ) {
1970         std::string buffer = _( "You don't have enough anesthetic to perform the installation." );
1971         buffer += "\n";
1972         buffer += req_anesth.list_missing();
1973         popup( buffer, PF_NONE );
1974         return false;
1975     }
1976     return true;
1977 }
1978 
consume_anesth_requirement(const itype & cbm,player & patient)1979 void Character::consume_anesth_requirement( const itype &cbm, player &patient )
1980 {
1981     const int weight = units::to_kilogram( patient.bodyweight() ) / 10;
1982     const requirement_data req_anesth = *requirement_id( "anesthetic" ) *
1983                                         cbm.bionic->difficulty * 2 * weight;
1984     for( const auto &e : req_anesth.get_components() ) {
1985         as_player()->consume_items( e, 1, is_crafting_component );
1986     }
1987     for( const auto &e : req_anesth.get_tools() ) {
1988         as_player()->consume_tools( e );
1989     }
1990     invalidate_crafting_inventory();
1991 }
1992 
has_installation_requirement(const bionic_id & bid)1993 bool Character::has_installation_requirement( const bionic_id &bid )
1994 {
1995     if( bid->installation_requirement.is_empty() ) {
1996         return false;
1997     }
1998 
1999     if( !bid->installation_requirement->can_make_with_inventory( crafting_inventory(),
2000             is_crafting_component ) ) {
2001         std::string buffer = _( "You don't have the required components to perform the installation." );
2002         buffer += "\n";
2003         buffer += bid->installation_requirement->list_missing();
2004         popup( buffer, PF_NONE );
2005         return false;
2006     }
2007 
2008     return true;
2009 }
2010 
consume_installation_requirement(const bionic_id & bid)2011 void Character::consume_installation_requirement( const bionic_id &bid )
2012 {
2013     for( const auto &e : bid->installation_requirement->get_components() ) {
2014         as_player()->consume_items( e, 1, is_crafting_component );
2015     }
2016     for( const auto &e : bid->installation_requirement->get_tools() ) {
2017         as_player()->consume_tools( e );
2018     }
2019     invalidate_crafting_inventory();
2020 }
2021 
2022 // bionic manipulation adjusted skill
bionics_adjusted_skill(bool autodoc,int skill_level) const2023 float Character::bionics_adjusted_skill( bool autodoc, int skill_level ) const
2024 {
2025     int pl_skill = bionics_pl_skill( autodoc, skill_level );
2026 
2027     // for chance_of_success calculation, shift skill down to a float between ~0.4 - 30
2028     float adjusted_skill = static_cast<float>( pl_skill ) - std::min( static_cast<float>( 40 ),
2029                            static_cast<float>( pl_skill ) - static_cast<float>( pl_skill ) / static_cast<float>( 10.0 ) );
2030     adjusted_skill *= env_surgery_bonus( 1 ) + get_effect_int( effect_assisted );
2031     return adjusted_skill;
2032 }
2033 
bionics_pl_skill(bool autodoc,int skill_level) const2034 int Character::bionics_pl_skill( bool autodoc, int skill_level ) const
2035 {
2036     skill_id most_important_skill;
2037     skill_id important_skill;
2038     skill_id least_important_skill;
2039 
2040     if( autodoc ) {
2041         most_important_skill = skill_firstaid;
2042         important_skill = skill_computer;
2043         least_important_skill = skill_electronics;
2044     } else {
2045         most_important_skill = skill_electronics;
2046         important_skill = skill_firstaid;
2047         least_important_skill = skill_mechanics;
2048     }
2049 
2050     int pl_skill;
2051     if( skill_level == -1 ) {
2052         pl_skill = int_cur                                  * 4 +
2053                    get_skill_level( most_important_skill )  * 4 +
2054                    get_skill_level( important_skill )       * 3 +
2055                    get_skill_level( least_important_skill ) * 1;
2056     } else {
2057         // override chance as though all values were skill_level if it is provided
2058         pl_skill = 12 * skill_level;
2059     }
2060 
2061     // Medical residents have some idea what they're doing
2062     if( has_trait( trait_PROF_MED ) ) {
2063         pl_skill += 3;
2064     }
2065 
2066     // People trained in bionics gain an additional advantage towards using it
2067     if( has_trait( trait_PROF_AUTODOC ) ) {
2068         pl_skill += 7;
2069     }
2070     return pl_skill;
2071 }
2072 
bionic_success_chance(bool autodoc,int skill_level,int difficulty,const Character & target)2073 int bionic_success_chance( bool autodoc, int skill_level, int difficulty, const Character &target )
2074 {
2075     return bionic_manip_cos( target.bionics_adjusted_skill( autodoc, skill_level ), difficulty );
2076 }
2077 
2078 // bionic manipulation chance of success
bionic_manip_cos(float adjusted_skill,int bionic_difficulty)2079 int bionic_manip_cos( float adjusted_skill, int bionic_difficulty )
2080 {
2081     if( get_player_character().has_trait( trait_DEBUG_BIONICS ) ) {
2082         return 100;
2083     }
2084 
2085     int chance_of_success = 0;
2086     // we will base chance_of_success on a ratio of skill and difficulty
2087     // when skill=difficulty, this gives us 1.  skill < difficulty gives a fraction.
2088     float skill_difficulty_parameter = static_cast<float>( adjusted_skill /
2089                                        ( 4.0 * bionic_difficulty ) );
2090 
2091     // when skill == difficulty, chance_of_success is 50%. Chance of success drops quickly below that
2092     // to reserve bionics for characters with the appropriate skill.  For more difficult bionics, the
2093     // curve flattens out just above 80%
2094     chance_of_success = static_cast<int>( ( 100 * skill_difficulty_parameter ) /
2095                                           ( skill_difficulty_parameter + std::sqrt( 1 / skill_difficulty_parameter ) ) );
2096 
2097     return chance_of_success;
2098 }
2099 
can_uninstall_bionic(const bionic_id & b_id,player & installer,bool autodoc,int skill_level)2100 bool Character::can_uninstall_bionic( const bionic_id &b_id, player &installer, bool autodoc,
2101                                       int skill_level )
2102 {
2103     // if malfunctioning bionics doesn't have associated item it gets a difficulty of 12
2104     int difficulty = 12;
2105     if( item::type_is_defined( b_id->itype() ) ) {
2106         const itype *type = item::find_type( b_id->itype() );
2107         if( type->bionic ) {
2108             difficulty = type->bionic->difficulty;
2109         }
2110     }
2111 
2112     if( !has_bionic( b_id ) ) {
2113         popup( _( "%s don't have this bionic installed." ), disp_name() );
2114         return false;
2115     }
2116 
2117     if( b_id == bio_blaster ) {
2118         popup( _( "Removing %s Fusion Blaster Arm would leave %s with a useless stump." ),
2119                disp_name( true ), disp_name() );
2120         return false;
2121     }
2122 
2123     Character &player_character = get_player_character();
2124 
2125     for( const bionic_id &bid : get_bionics() ) {
2126         if( bid->is_included( b_id ) ) {
2127             popup( _( "%s must remove the %s bionic to remove the %s." ), installer.disp_name(),
2128                    bid->name, b_id->name );
2129             return false;
2130         }
2131     }
2132 
2133     if( b_id == bio_eye_optic ) {
2134         popup( _( "The Telescopic Lenses are part of %s eyes now.  Removing them would leave %s blind." ),
2135                disp_name( true ), disp_name() );
2136         return false;
2137     }
2138 
2139     // removal of bionics adds +2 difficulty over installation
2140     int chance_of_success = bionic_success_chance( autodoc, skill_level, difficulty + 2,
2141                             installer );
2142 
2143     if( chance_of_success >= 100 ) {
2144         if( !player_character.query_yn(
2145                 _( "Are you sure you wish to uninstall the selected bionic?" ),
2146                 100 - chance_of_success ) ) {
2147             return false;
2148         }
2149     } else {
2150         if( !player_character.query_yn(
2151                 _( "WARNING: %i percent chance of SEVERE damage to all body parts!  Continue anyway?" ),
2152                 ( 100 - static_cast<int>( chance_of_success ) ) ) ) {
2153             return false;
2154         }
2155     }
2156 
2157     return true;
2158 }
2159 
uninstall_bionic(const bionic_id & b_id,player & installer,bool autodoc,int skill_level)2160 bool Character::uninstall_bionic( const bionic_id &b_id, player &installer, bool autodoc,
2161                                   int skill_level )
2162 {
2163     // if malfunctioning bionics doesn't have associated item it gets a difficulty of 12
2164     int difficulty = 12;
2165     if( item::type_is_defined( b_id->itype() ) ) {
2166         const itype *type = item::find_type( b_id->itype() );
2167         if( type->bionic ) {
2168             difficulty = type->bionic->difficulty;
2169         }
2170     }
2171 
2172     // removal of bionics adds +2 difficulty over installation
2173     int pl_skill = bionics_pl_skill( autodoc, skill_level );
2174     int chance_of_success = bionic_success_chance( autodoc, skill_level, difficulty + 2, installer );
2175 
2176     // Surgery is imminent, retract claws or blade if active
2177     for( size_t i = 0; i < installer.my_bionics->size(); i++ ) {
2178         const bionic &bio = ( *installer.my_bionics )[ i ];
2179         if( bio.powered && bio.info().has_flag( json_flag_BIONIC_WEAPON ) ) {
2180             installer.deactivate_bionic( i );
2181         }
2182     }
2183 
2184     int success = chance_of_success - rng( 1, 100 );
2185     if( installer.has_trait( trait_DEBUG_BIONICS ) ) {
2186         perform_uninstall( b_id, difficulty, success, b_id->capacity, pl_skill );
2187         return true;
2188     }
2189     assign_activity( ACT_OPERATION, to_moves<int>( difficulty * 20_minutes ) );
2190 
2191     activity.values.push_back( difficulty );
2192     activity.values.push_back( success );
2193     activity.values.push_back( units::to_kilojoule( b_id->capacity ) );
2194     activity.values.push_back( pl_skill );
2195     activity.str_values.push_back( "uninstall" );
2196     activity.str_values.push_back( b_id.str() );
2197     activity.str_values.push_back( "" ); // installer_name is unused for uninstall
2198     if( autodoc ) {
2199         activity.str_values.push_back( "true" );
2200     } else {
2201         activity.str_values.push_back( "false" );
2202     }
2203     for( const std::pair<const bodypart_str_id, size_t> &elem : b_id->occupied_bodyparts ) {
2204         add_effect( effect_under_operation, difficulty * 20_minutes, elem.first.id(), true, difficulty );
2205     }
2206 
2207     return true;
2208 }
2209 
perform_uninstall(const bionic_id & bid,int difficulty,int success,const units::energy & power_lvl,int pl_skill)2210 void Character::perform_uninstall( const bionic_id &bid, int difficulty, int success,
2211                                    const units::energy &power_lvl, int pl_skill )
2212 {
2213     map &here = get_map();
2214     if( success > 0 ) {
2215         get_event_bus().send<event_type::removes_cbm>( getID(), bid );
2216 
2217         // until bionics can be flagged as non-removable
2218         add_msg_player_or_npc( m_neutral, _( "Your parts are jiggled back into their familiar places." ),
2219                                _( "<npcname>'s parts are jiggled back into their familiar places." ) );
2220         add_msg( m_good, _( "Successfully removed %s." ), bid.obj().name );
2221         remove_bionic( bid );
2222 
2223         // remove power bank provided by bionic
2224         mod_max_power_level( -power_lvl );
2225 
2226         item cbm( "burnt_out_bionic" );
2227         if( item::type_is_defined( bid->itype() ) ) {
2228             cbm = item( bid.c_str() );
2229         }
2230         cbm.set_flag( flag_FILTHY );
2231         cbm.set_flag( flag_NO_STERILE );
2232         cbm.set_flag( flag_NO_PACKED );
2233         cbm.faults.emplace( fault_bionic_salvaged );
2234         here.add_item( pos(), cbm );
2235     } else {
2236         get_event_bus().send<event_type::fails_to_remove_cbm>( getID(), bid );
2237         // for chance_of_success calculation, shift skill down to a float between ~0.4 - 30
2238         float adjusted_skill = static_cast<float>( pl_skill ) - std::min( static_cast<float>( 40 ),
2239                                static_cast<float>( pl_skill ) - static_cast<float>( pl_skill ) / static_cast<float>
2240                                ( 10.0 ) );
2241         bionics_uninstall_failure( difficulty, success, adjusted_skill );
2242 
2243     }
2244     here.invalidate_map_cache( here.get_abs_sub().z );
2245 }
2246 
uninstall_bionic(const bionic & target_cbm,monster & installer,player & patient,float adjusted_skill)2247 bool Character::uninstall_bionic( const bionic &target_cbm, monster &installer, player &patient,
2248                                   float adjusted_skill )
2249 {
2250     viewer &player_view = get_player_view();
2251     if( installer.ammo[itype_anesthetic] <= 0 ) {
2252         add_msg_if_player_sees( installer, _( "The %s's anesthesia kit looks empty." ), installer.name() );
2253         return false;
2254     }
2255 
2256     item bionic_to_uninstall = item( target_cbm.id.str(), calendar::turn_zero );
2257     const itype *itemtype = bionic_to_uninstall.type;
2258     int difficulty = itemtype->bionic->difficulty;
2259     int chance_of_success = bionic_manip_cos( adjusted_skill, difficulty + 2 );
2260     int success = chance_of_success - rng( 1, 100 );
2261 
2262     const time_duration duration = difficulty * 20_minutes;
2263     // don't stack up the effect
2264     if( !installer.has_effect( effect_operating ) ) {
2265         installer.add_effect( effect_operating, duration + 5_turns );
2266     }
2267 
2268     if( patient.is_player() ) {
2269         add_msg( m_bad,
2270                  _( "You feel a tiny pricking sensation in your right arm, and lose all sensation before abruptly blacking out." ) );
2271     } else {
2272         add_msg_if_player_sees( installer, m_bad,
2273                                 _( "The %1$s gently inserts a syringe into %2$s's arm and starts injecting something while holding them down." ),
2274                                 installer.name(), patient.disp_name() );
2275     }
2276 
2277     installer.ammo[itype_anesthetic] -= 1;
2278 
2279     patient.add_effect( effect_narcosis, duration );
2280     patient.add_effect( effect_sleep, duration );
2281 
2282     if( patient.is_player() ) {
2283         add_msg( _( "You fall asleep and %1$s starts operating." ), installer.disp_name() );
2284     } else {
2285         add_msg_if_player_sees( patient, _( "%1$s falls asleep and %2$s starts operating." ),
2286                                 patient.disp_name(), installer.disp_name() );
2287     }
2288 
2289     if( success > 0 ) {
2290 
2291         if( patient.is_player() ) {
2292             add_msg( m_neutral, _( "Your parts are jiggled back into their familiar places." ) );
2293             add_msg( m_mixed, _( "Successfully removed %s." ), target_cbm.info().name );
2294         } else if( patient.is_npc() && player_view.sees( patient ) ) {
2295             add_msg( m_neutral, _( "%s's parts are jiggled back into their familiar places." ),
2296                      patient.disp_name() );
2297             add_msg( m_mixed, _( "Successfully removed %s." ), target_cbm.info().name );
2298         }
2299 
2300         // remove power bank provided by bionic
2301         patient.mod_max_power_level( -target_cbm.info().capacity );
2302         patient.remove_bionic( target_cbm.id );
2303         item cbm( "burnt_out_bionic" );
2304         if( item::type_is_defined( target_cbm.info().itype() ) ) {
2305             cbm = bionic_to_uninstall;
2306         }
2307         cbm.set_flag( flag_FILTHY );
2308         cbm.set_flag( flag_NO_STERILE );
2309         cbm.set_flag( flag_NO_PACKED );
2310         cbm.faults.emplace( fault_bionic_salvaged );
2311         get_map().add_item( patient.pos(), cbm );
2312     } else {
2313         bionics_uninstall_failure( installer, patient, difficulty, success, adjusted_skill );
2314     }
2315 
2316     return false;
2317 }
2318 
can_install_bionics(const itype & type,Character & installer,bool autodoc,int skill_level)2319 bool Character::can_install_bionics( const itype &type, Character &installer, bool autodoc,
2320                                      int skill_level )
2321 {
2322     if( !type.bionic ) {
2323         debugmsg( "Tried to install NULL bionic" );
2324         return false;
2325     }
2326     if( has_trait( trait_DEBUG_BIONICS ) ) {
2327         return true;
2328     }
2329     if( is_mounted() ) {
2330         return false;
2331     }
2332 
2333     const bionic_id &bioid = type.bionic->id;
2334     const int difficult = type.bionic->difficulty;
2335 
2336     // if we're doing self install
2337     if( !autodoc && installer.is_avatar() ) {
2338         return installer.has_enough_anesth( type ) &&
2339                installer.has_installation_requirement( bioid );
2340     }
2341 
2342     int chance_of_success = bionic_success_chance( autodoc, skill_level, difficult, installer );
2343 
2344     std::vector<std::string> conflicting_muts;
2345     for( const trait_id &mid : bioid->canceled_mutations ) {
2346         if( has_trait( mid ) ) {
2347             conflicting_muts.push_back( mid->name() );
2348         }
2349     }
2350 
2351     if( !conflicting_muts.empty() &&
2352         !query_yn(
2353             _( "Installing this bionic will remove the conflicting traits: %s.  Continue anyway?" ),
2354             enumerate_as_string( conflicting_muts ) ) ) {
2355         return false;
2356     }
2357 
2358     const std::map<bodypart_id, int> &issues = bionic_installation_issues( bioid );
2359     // show all requirements which are not satisfied
2360     if( !issues.empty() ) {
2361         std::string detailed_info;
2362         for( const std::pair<const bodypart_id, int> &elem : issues ) {
2363             //~ <Body part name>: <number of slots> more slot(s) needed.
2364             detailed_info += string_format( _( "\n%s: %i more slot(s) needed." ),
2365                                             body_part_name_as_heading( elem.first, 1 ),
2366                                             elem.second );
2367         }
2368         popup( _( "Not enough space for bionic installation!%s" ), detailed_info );
2369         return false;
2370     }
2371 
2372     Character &player_character = get_player_character();
2373     if( chance_of_success >= 100 ) {
2374         if( !player_character.query_yn(
2375                 _( "Are you sure you wish to install the selected bionic?" ),
2376                 100 - chance_of_success ) ) {
2377             return false;
2378         }
2379     } else {
2380         if( !player_character.query_yn(
2381                 _( "WARNING: %i percent chance of failure that may result in damage, pain, or a faulty installation!  Continue anyway?" ),
2382                 ( 100 - chance_of_success ) ) ) {
2383             return false;
2384         }
2385     }
2386 
2387     return true;
2388 }
2389 
env_surgery_bonus(int radius) const2390 float Character::env_surgery_bonus( int radius ) const
2391 {
2392     float bonus = 1.0f;
2393     map &here = get_map();
2394     for( const tripoint &cell : here.points_in_radius( pos(), radius ) ) {
2395         if( here.furn( cell )->surgery_skill_multiplier ) {
2396             bonus = std::max( bonus, *here.furn( cell )->surgery_skill_multiplier );
2397         }
2398     }
2399     return bonus;
2400 }
2401 
install_bionics(const itype & type,player & installer,bool autodoc,int skill_level)2402 bool Character::install_bionics( const itype &type, player &installer, bool autodoc,
2403                                  int skill_level )
2404 {
2405     if( !type.bionic ) {
2406         debugmsg( "Tried to install NULL bionic" );
2407         return false;
2408     }
2409 
2410     const bionic_id &bioid = type.bionic->id;
2411     const bionic_id &upbioid = bioid->upgraded_bionic;
2412     const int difficulty = type.bionic->difficulty;
2413     int pl_skill = installer.bionics_pl_skill( autodoc, skill_level );
2414     int chance_of_success = bionic_success_chance( autodoc, skill_level, difficulty, installer );
2415 
2416     // Practice skills only if conducting manual installation
2417     if( !autodoc ) {
2418         installer.practice( skill_electronics, static_cast<int>( ( 100 - chance_of_success ) * 1.5 ) );
2419         installer.practice( skill_firstaid, static_cast<int>( ( 100 - chance_of_success ) * 1.0 ) );
2420         installer.practice( skill_mechanics, static_cast<int>( ( 100 - chance_of_success ) * 0.5 ) );
2421     }
2422 
2423     int success = chance_of_success - rng( 0, 99 );
2424     if( installer.has_trait( trait_DEBUG_BIONICS ) ) {
2425         perform_install( bioid, upbioid, difficulty, success, pl_skill, "NOT_MED",
2426                          bioid->canceled_mutations, pos() );
2427         return true;
2428     }
2429     assign_activity( ACT_OPERATION, to_moves<int>( difficulty * 20_minutes ) );
2430     activity.values.push_back( difficulty );
2431     activity.values.push_back( success );
2432     activity.values.push_back( units::to_millijoule( bioid->capacity ) );
2433     activity.values.push_back( pl_skill );
2434     activity.str_values.push_back( "install" );
2435     activity.str_values.push_back( bioid.str() );
2436 
2437     if( installer.has_trait( trait_PROF_MED ) || installer.has_trait( trait_PROF_AUTODOC ) ) {
2438         activity.str_values.push_back( installer.disp_name( true ) );
2439     } else {
2440         activity.str_values.push_back( "NOT_MED" );
2441     }
2442     if( autodoc ) {
2443         activity.str_values.push_back( "true" );
2444     } else {
2445         activity.str_values.push_back( "false" );
2446     }
2447     for( const std::pair<const bodypart_str_id, size_t> &elem : bioid->occupied_bodyparts ) {
2448         add_effect( effect_under_operation, difficulty * 20_minutes, elem.first.id(), true, difficulty );
2449     }
2450 
2451     return true;
2452 }
2453 
perform_install(const bionic_id & bid,const bionic_id & upbid,int difficulty,int success,int pl_skill,const std::string & installer_name,const std::vector<trait_id> & trait_to_rem,const tripoint & patient_pos)2454 void Character::perform_install( const bionic_id &bid, const bionic_id &upbid, int difficulty,
2455                                  int success, int pl_skill, const std::string &installer_name,
2456                                  const std::vector<trait_id> &trait_to_rem, const tripoint &patient_pos )
2457 {
2458     if( success > 0 ) {
2459         get_event_bus().send<event_type::installs_cbm>( getID(), bid );
2460         if( upbid != bionic_id( "" ) ) {
2461             remove_bionic( upbid );
2462             //~ %1$s - name of the bionic to be upgraded (inferior), %2$s - name of the upgraded bionic (superior).
2463             add_msg( m_good, _( "Successfully upgraded %1$s to %2$s." ),
2464                      upbid.obj().name, bid.obj().name );
2465         } else {
2466             //~ %s - name of the bionic.
2467             add_msg( m_good, _( "Successfully installed %s." ), bid.obj().name );
2468         }
2469 
2470         add_bionic( bid );
2471 
2472         if( !trait_to_rem.empty() ) {
2473             for( const trait_id &tid : trait_to_rem ) {
2474                 if( has_trait( tid ) ) {
2475                     remove_mutation( tid );
2476                 }
2477             }
2478         }
2479 
2480     } else {
2481         get_event_bus().send<event_type::fails_to_install_cbm>( getID(), bid );
2482 
2483         // for chance_of_success calculation, shift skill down to a float between ~0.4 - 30
2484         float adjusted_skill = static_cast<float>( pl_skill ) - std::min( static_cast<float>( 40 ),
2485                                static_cast<float>( pl_skill ) - static_cast<float>( pl_skill ) / static_cast<float>
2486                                ( 10.0 ) );
2487         bionics_install_failure( bid, installer_name, difficulty, success, adjusted_skill, patient_pos );
2488     }
2489     map &here = get_map();
2490     here.invalidate_map_cache( here.get_abs_sub().z );
2491 }
2492 
bionics_install_failure(const bionic_id & bid,const std::string & installer,int difficulty,int success,float adjusted_skill,const tripoint & patient_pos)2493 void Character::bionics_install_failure( const bionic_id &bid, const std::string &installer,
2494         int difficulty, int success, float adjusted_skill, const tripoint &patient_pos )
2495 {
2496     // "success" should be passed in as a negative integer representing how far off we
2497     // were for a successful install.  We use this to determine consequences for failing.
2498     success = std::abs( success );
2499 
2500     // failure level is decided by how far off the character was from a successful install, and
2501     // this is scaled up or down by the ratio of difficulty/skill.  At high skill levels (or low
2502     // difficulties), only minor consequences occur.  At low skill levels, severe consequences
2503     // are more likely.
2504     int failure_level = static_cast<int>( std::sqrt( success * 4.0 * difficulty / adjusted_skill ) );
2505     int fail_type = ( failure_level > 5 ? 5 : failure_level );
2506     bool drop_cbm = false;
2507     add_msg( m_neutral, _( "The installation is a failure." ) );
2508 
2509     if( installer != "NOT_MED" ) {
2510         //~"Complications" is USian medical-speak for "unintended damage from a medical procedure".
2511         add_msg( m_neutral, _( "%s training helps to minimize the complications." ),
2512                  installer );
2513         // In addition to the bonus, medical residents know enough OR protocol to avoid botching.
2514         // Take MD and be immune to faulty bionics.
2515         if( fail_type > 3 ) {
2516             fail_type = rng( 1, 3 );
2517         }
2518     }
2519     if( fail_type <= 0 ) {
2520         add_msg( m_neutral, _( "The installation fails without incident." ) );
2521         drop_cbm = true;
2522     } else {
2523         std::set<bodypart_id> bp_hurt;
2524         switch( fail_type ) {
2525 
2526             case 1:
2527                 if( !( has_trait( trait_NOPAIN ) ) ) {
2528                     add_msg_if_player( m_bad, _( "It really hurts!" ) );
2529                     mod_pain( rng( 10, 30 ) );
2530                 }
2531                 drop_cbm = true;
2532                 break;
2533 
2534             case 2:
2535             case 3: {
2536                 add_msg( m_bad, _( "The installation is faulty!" ) );
2537                 std::vector<bionic_id> valid;
2538                 std::copy_if( begin( faulty_bionics ), end( faulty_bionics ), std::back_inserter( valid ),
2539                 [&]( const bionic_id & id ) {
2540                     return !has_bionic( id );
2541                 } );
2542 
2543                 // We've got all the bad bionics!
2544                 if( valid.empty() ) {
2545                     if( has_max_power() ) {
2546                         units::energy old_power = get_max_power_level();
2547                         add_msg( m_bad, _( "%s lose power capacity!" ), disp_name() );
2548                         set_max_power_level( units::from_kilojoule( rng( 0,
2549                                              units::to_kilojoule( get_max_power_level() ) - 25 ) ) );
2550                         if( is_player() ) {
2551                             get_memorial().add(
2552                                 pgettext( "memorial_male", "Lost %d units of power capacity." ),
2553                                 pgettext( "memorial_female", "Lost %d units of power capacity." ),
2554                                 units::to_kilojoule( old_power - get_max_power_level() ) );
2555                         }
2556                     }
2557                     // TODO: What if we can't lose power capacity?  No penalty?
2558                 } else {
2559                     const bionic_id &id = random_entry( valid );
2560                     add_bionic( id );
2561                     get_event_bus().send<event_type::installs_faulty_cbm>( getID(), id );
2562                 }
2563 
2564                 break;
2565             }
2566             case 4:
2567             case 5: {
2568                 for( const bodypart_id &bp : get_all_body_parts() ) {
2569                     if( has_effect( effect_under_operation, bp.id() ) ) {
2570                         if( bp_hurt.count( bp->main_part ) > 0 ) {
2571                             continue;
2572                         }
2573                         bp_hurt.emplace( bp->main_part );
2574 
2575                         apply_damage( this, bp, rng( 25, 50 ), true );
2576                         roll_critical_bionics_failure( bp );
2577 
2578                         add_msg_player_or_npc( m_bad, _( "Your %s is damaged." ), _( "<npcname>'s %s is damaged." ),
2579                                                body_part_name_accusative( bp ) );
2580                     }
2581                 }
2582                 drop_cbm = true;
2583                 break;
2584             }
2585         }
2586     }
2587     if( drop_cbm ) {
2588         item cbm( bid.c_str() );
2589         cbm.set_flag( flag_NO_STERILE );
2590         cbm.set_flag( flag_NO_PACKED );
2591         cbm.faults.emplace( fault_bionic_salvaged );
2592         get_map().add_item( patient_pos, cbm );
2593     }
2594 }
2595 
list_occupied_bps(const bionic_id & bio_id,const std::string & intro,const bool each_bp_on_new_line)2596 std::string list_occupied_bps( const bionic_id &bio_id, const std::string &intro,
2597                                const bool each_bp_on_new_line )
2598 {
2599     if( bio_id->occupied_bodyparts.empty() ) {
2600         return "";
2601     }
2602     std::string desc = intro;
2603     for( const std::pair<const bodypart_str_id, size_t> &elem : bio_id->occupied_bodyparts ) {
2604         desc += ( each_bp_on_new_line ? "\n" : " " );
2605         //~ <Bodypart name> (<number of occupied slots> slots);
2606         desc += string_format( _( "%s (%i slots);" ),
2607                                body_part_name_as_heading( elem.first.id(), 1 ),
2608                                elem.second );
2609     }
2610     return desc;
2611 }
2612 
get_used_bionics_slots(const bodypart_id & bp) const2613 int Character::get_used_bionics_slots( const bodypart_id &bp ) const
2614 {
2615     int used_slots = 0;
2616     for( const bionic_id &bid : get_bionics() ) {
2617         auto search = bid->occupied_bodyparts.find( bp.id() );
2618         if( search != bid->occupied_bodyparts.end() ) {
2619             used_slots += search->second;
2620         }
2621     }
2622 
2623     return used_slots;
2624 }
2625 
bionic_installation_issues(const bionic_id & bioid)2626 std::map<bodypart_id, int> Character::bionic_installation_issues( const bionic_id &bioid )
2627 {
2628     std::map<bodypart_id, int> issues;
2629     if( !get_option < bool >( "CBM_SLOTS_ENABLED" ) ) {
2630         return issues;
2631     }
2632     for( const std::pair<const string_id<body_part_type>, size_t> &elem : bioid->occupied_bodyparts ) {
2633         const int lacked_slots = elem.second - get_free_bionics_slots( elem.first );
2634         if( lacked_slots > 0 ) {
2635             issues.emplace( elem.first, lacked_slots );
2636         }
2637     }
2638     return issues;
2639 }
2640 
get_total_bionics_slots(const bodypart_id & bp) const2641 int Character::get_total_bionics_slots( const bodypart_id &bp ) const
2642 {
2643     const bodypart_str_id &id = bp.id();
2644     int mut_bio_slots = 0;
2645     for( const trait_id &mut : get_mutations() ) {
2646         mut_bio_slots += mut->bionic_slot_bonus( id );
2647     }
2648     return bp->bionic_slots() + mut_bio_slots;
2649 }
2650 
get_free_bionics_slots(const bodypart_id & bp) const2651 int Character::get_free_bionics_slots( const bodypart_id &bp ) const
2652 {
2653     return get_total_bionics_slots( bp ) - get_used_bionics_slots( bp );
2654 }
2655 
add_bionic(const bionic_id & b)2656 void Character::add_bionic( const bionic_id &b )
2657 {
2658     if( has_bionic( b ) ) {
2659         debugmsg( "Tried to install bionic %s that is already installed!", b.c_str() );
2660         return;
2661     }
2662 
2663     const units::energy pow_up = b->capacity;
2664     mod_max_power_level( pow_up );
2665     if( b == bio_power_storage || b == bio_power_storage_mkII ) {
2666         add_msg_if_player( m_good, _( "Increased storage capacity by %i." ),
2667                            units::to_kilojoule( pow_up ) );
2668         // Power Storage CBMs are not real bionic units, so return without adding it to my_bionics
2669         return;
2670     }
2671 
2672     my_bionics->push_back( bionic( b, get_free_invlet( *this ) ) );
2673     if( b == bio_tools || b == bio_ears ) {
2674         activate_bionic( my_bionics->size() - 1 );
2675     }
2676 
2677     for( const bionic_id &inc_bid : b->included_bionics ) {
2678         add_bionic( inc_bid );
2679     }
2680 
2681     for( const std::pair<const spell_id, int> &spell_pair : b->learned_spells ) {
2682         const spell_id learned_spell = spell_pair.first;
2683         if( learned_spell->spell_class != trait_id( "NONE" ) ) {
2684             const trait_id spell_class = learned_spell->spell_class;
2685             // spells you learn from a bionic overwrite the opposite spell class.
2686             // for best UX, include those spell classes in "canceled_mutations"
2687             if( !has_trait( spell_class ) ) {
2688                 set_mutation( spell_class );
2689                 on_mutation_gain( spell_class );
2690                 add_msg_if_player( spell_class->desc() );
2691             }
2692         }
2693         if( !magic->knows_spell( learned_spell ) ) {
2694             magic->learn_spell( learned_spell, *this, true );
2695         }
2696         spell &known_spell = magic->get_spell( learned_spell );
2697         // spells you learn from installing a bionic upgrade spells you know if they are the same
2698         if( known_spell.get_level() < spell_pair.second ) {
2699             known_spell.set_level( spell_pair.second );
2700         }
2701     }
2702 
2703     for( const proficiency_id &learned : b->proficiencies ) {
2704         add_proficiency( learned );
2705     }
2706 
2707     calc_encumbrance();
2708     recalc_sight_limits();
2709     if( !b->enchantments.empty() ) {
2710         recalculate_enchantment_cache();
2711     }
2712 }
2713 
remove_bionic(const bionic_id & b)2714 void Character::remove_bionic( const bionic_id &b )
2715 {
2716     bionic_collection new_my_bionics;
2717     // any spells you should not forget due to still having a bionic installed that has it.
2718     std::set<spell_id> cbm_spells;
2719     for( bionic &i : *my_bionics ) {
2720         if( b == i.id ) {
2721             continue;
2722         }
2723 
2724         // Linked bionics: if either is removed, the other is removed as well.
2725         if( b->is_included( i.id ) || i.id->is_included( b ) ) {
2726             continue;
2727         }
2728 
2729         for( const std::pair<const spell_id, int> &spell_pair : i.id->learned_spells ) {
2730             cbm_spells.emplace( spell_pair.first );
2731         }
2732 
2733         new_my_bionics.push_back( bionic( i.id, i.invlet ) );
2734     }
2735 
2736     // any spells you learn from installing a bionic you forget.
2737     for( const std::pair<const spell_id, int> &spell_pair : b->learned_spells ) {
2738         if( cbm_spells.count( spell_pair.first ) == 0 ) {
2739             magic->forget_spell( spell_pair.first );
2740         }
2741     }
2742 
2743     for( const proficiency_id &lost : b->proficiencies ) {
2744         lose_proficiency( lost );
2745     }
2746 
2747     *my_bionics = new_my_bionics;
2748     calc_encumbrance();
2749     recalc_sight_limits();
2750     if( !b->enchantments.empty() ) {
2751         recalculate_enchantment_cache();
2752     }
2753 }
2754 
num_bionics() const2755 int Character::num_bionics() const
2756 {
2757     return my_bionics->size();
2758 }
2759 
amount_of_storage_bionics() const2760 std::pair<int, int> Character::amount_of_storage_bionics() const
2761 {
2762     units::energy lvl = get_max_power_level();
2763 
2764     // exclude amount of power capacity obtained via non-power-storage CBMs
2765     for( const bionic &it : *my_bionics ) {
2766         lvl -= it.info().capacity;
2767     }
2768 
2769     std::pair<int, int> results( 0, 0 );
2770     if( lvl <= 0_kJ ) {
2771         return results;
2772     }
2773 
2774     const units::energy pow_mkI = bio_power_storage->capacity;
2775     const units::energy pow_mkII = bio_power_storage_mkII->capacity;
2776 
2777     while( lvl >= std::min( pow_mkI, pow_mkII ) ) {
2778         if( one_in( 2 ) ) {
2779             if( lvl >= pow_mkI ) {
2780                 results.first++;
2781                 lvl -= pow_mkI;
2782             }
2783         } else {
2784             if( lvl >= pow_mkII ) {
2785                 results.second++;
2786                 lvl -= pow_mkII;
2787             }
2788         }
2789     }
2790     return results;
2791 }
2792 
bionic_at_index(int i)2793 bionic &Character::bionic_at_index( int i )
2794 {
2795     return ( *my_bionics )[i];
2796 }
2797 
clear_bionics()2798 void Character::clear_bionics()
2799 {
2800     my_bionics->clear();
2801 }
2802 
reset_bionics()2803 void reset_bionics()
2804 {
2805     faulty_bionics.clear();
2806     bionic_factory.reset();
2807 }
2808 
set_flag(const std::string & flag)2809 void bionic::set_flag( const std::string &flag )
2810 {
2811     bionic_tags.insert( flag );
2812 }
2813 
remove_flag(const std::string & flag)2814 void bionic::remove_flag( const std::string &flag )
2815 {
2816     bionic_tags.erase( flag );
2817 }
2818 
has_flag(const std::string & flag) const2819 bool bionic::has_flag( const std::string &flag ) const
2820 {
2821     return bionic_tags.find( flag ) != bionic_tags.end();
2822 }
2823 
get_quality(const quality_id & quality) const2824 int bionic::get_quality( const quality_id &quality ) const
2825 {
2826     const bionic_data &i = info();
2827     if( i.fake_item.is_empty() ) {
2828         return INT_MIN;
2829     }
2830 
2831     return item( i.fake_item ).get_quality( quality );
2832 }
2833 
is_this_fuel_powered(const material_id & this_fuel) const2834 bool bionic::is_this_fuel_powered( const material_id &this_fuel ) const
2835 {
2836     const std::vector<material_id> fuel_op = info().fuel_opts;
2837     return std::find( fuel_op.begin(), fuel_op.end(), this_fuel ) != fuel_op.end();
2838 }
2839 
toggle_safe_fuel_mod()2840 void bionic::toggle_safe_fuel_mod()
2841 {
2842     if( !info().fuel_opts.empty() || info().is_remote_fueled ) {
2843         uilist tmenu;
2844         tmenu.text = _( "Chose Safe Fuel Level Threshold" );
2845         tmenu.addentry( 1, true, 'o', _( "Full Power" ) );
2846         if( get_auto_start_thresh() < 0.80 ) {
2847             tmenu.addentry( 2, true, 't', _( "Above 80 %%" ) );
2848         }
2849         if( get_auto_start_thresh() < 0.55 ) {
2850             tmenu.addentry( 3, true, 'f', _( "Above 55 %%" ) );
2851         }
2852         if( get_auto_start_thresh() < 0.30 ) {
2853             tmenu.addentry( 4, true, 's', _( "Above 30 %%" ) );
2854         }
2855         tmenu.addentry( 5, true, 'd', _( "Disabled" ) );
2856         tmenu.query();
2857 
2858         switch( tmenu.ret ) {
2859             case 1:
2860                 set_safe_fuel_thresh( 1.0f );
2861                 break;
2862             case 2:
2863                 set_safe_fuel_thresh( 0.80f );
2864                 break;
2865             case 3:
2866                 set_safe_fuel_thresh( 0.55f );
2867                 break;
2868             case 4:
2869                 set_safe_fuel_thresh( 0.30f );
2870                 break;
2871             case 5:
2872                 set_safe_fuel_thresh( -1.0f );
2873             default:
2874                 break;
2875         }
2876     }
2877 }
2878 
toggle_auto_start_mod()2879 void bionic::toggle_auto_start_mod()
2880 {
2881     if( info().fuel_opts.empty() && !info().is_remote_fueled ) {
2882         return;
2883     }
2884     if( !is_auto_start_on() ) {
2885         uilist tmenu;
2886         tmenu.text = _( "Chose Start Power Level Threshold" );
2887         tmenu.addentry( 1, true, 'o', _( "No Power Left" ) );
2888         if( get_safe_fuel_thresh() > 0.25 ) {
2889             tmenu.addentry( 2, true, 't', _( "Below 25 %%" ) );
2890         }
2891         if( get_safe_fuel_thresh() > 0.50 ) {
2892             tmenu.addentry( 3, true, 'f', _( "Below 50 %%" ) );
2893         }
2894         if( get_safe_fuel_thresh() > 0.75 ) {
2895             tmenu.addentry( 4, true, 's', _( "Below 75 %%" ) );
2896         }
2897         tmenu.query();
2898 
2899         switch( tmenu.ret ) {
2900             case 1:
2901                 set_auto_start_thresh( 0.0 );
2902                 break;
2903             case 2:
2904                 set_auto_start_thresh( 0.25 );
2905                 break;
2906             case 3:
2907                 set_auto_start_thresh( 0.5 );
2908                 break;
2909             case 4:
2910                 set_auto_start_thresh( 0.75 );
2911                 break;
2912             default:
2913                 break;
2914         }
2915     } else {
2916         set_auto_start_thresh( -1.0 );
2917     }
2918 }
2919 
set_auto_start_thresh(float val)2920 void bionic::set_auto_start_thresh( float val )
2921 {
2922     auto_start_threshold = val;
2923 }
2924 
get_auto_start_thresh() const2925 float bionic::get_auto_start_thresh() const
2926 {
2927     return auto_start_threshold;
2928 }
2929 
is_auto_start_on() const2930 bool bionic::is_auto_start_on() const
2931 {
2932     return get_auto_start_thresh() > -1.0;
2933 }
2934 
get_safe_fuel_thresh() const2935 float bionic::get_safe_fuel_thresh() const
2936 {
2937     return safe_fuel_threshold;
2938 }
2939 
is_safe_fuel_on() const2940 bool bionic::is_safe_fuel_on() const
2941 {
2942     return get_safe_fuel_thresh() < 2.0;
2943 }
2944 
set_safe_fuel_thresh(float val)2945 void bionic::set_safe_fuel_thresh( float val )
2946 {
2947     safe_fuel_threshold = val;
2948 }
2949 
serialize(JsonOut & json) const2950 void bionic::serialize( JsonOut &json ) const
2951 {
2952     json.start_object();
2953     json.member( "id", id );
2954     json.member( "invlet", static_cast<int>( invlet ) );
2955     json.member( "powered", powered );
2956     json.member( "charge", charge_timer );
2957     json.member( "ammo_loaded", ammo_loaded );
2958     json.member( "ammo_count", ammo_count );
2959     json.member( "bionic_tags", bionic_tags );
2960     if( incapacitated_time > 0_turns ) {
2961         json.member( "incapacitated_time", incapacitated_time );
2962     }
2963     if( is_auto_start_on() ) {
2964         json.member( "auto_start_threshold", auto_start_threshold );
2965     }
2966     if( is_safe_fuel_on() ) {
2967         json.member( "safe_fuel_threshold", safe_fuel_threshold );
2968     }
2969 
2970     json.end_object();
2971 }
2972 
deserialize(JsonIn & jsin)2973 void bionic::deserialize( JsonIn &jsin )
2974 {
2975     JsonObject jo = jsin.get_object();
2976     id = bionic_id( jo.get_string( "id" ) );
2977     invlet = jo.get_int( "invlet" );
2978     powered = jo.get_bool( "powered" );
2979     charge_timer = jo.get_int( "charge" );
2980     if( jo.has_string( "ammo_loaded" ) ) {
2981         jo.read( "ammo_loaded", ammo_loaded, true );
2982     }
2983     if( jo.has_int( "ammo_count" ) ) {
2984         ammo_count = jo.get_int( "ammo_count" );
2985     }
2986     if( jo.has_int( "incapacitated_time" ) ) {
2987         incapacitated_time = 1_turns * jo.get_int( "incapacitated_time" );
2988     }
2989     if( jo.has_float( "auto_start_threshold" ) ) {
2990         auto_start_threshold = jo.get_float( "auto_start_threshold" );
2991     }
2992     if( jo.has_float( "safe_fuel_threshold" ) ) {
2993         safe_fuel_threshold = jo.get_float( "safe_fuel_threshold" );
2994     }
2995     if( jo.has_array( "bionic_tags" ) ) {
2996         for( const std::string line : jo.get_array( "bionic_tags" ) ) {
2997             bionic_tags.insert( line );
2998         }
2999     }
3000 
3001 }
3002 
bionics_cancelling_trait(const std::vector<bionic_id> & bios,const trait_id & tid)3003 std::vector<bionic_id> bionics_cancelling_trait( const std::vector<bionic_id> &bios,
3004         const trait_id &tid )
3005 {
3006     // Vector of bionics to return
3007     std::vector<bionic_id> bionics_cancelling;
3008 
3009     // Search through the vector of of bionics, and see if the trait is cancelled by one of them
3010     for( const bionic_id &bid : bios ) {
3011         for( const trait_id &trait : bid->canceled_mutations ) {
3012             if( trait == tid ) {
3013                 bionics_cancelling.emplace_back( bid );
3014             }
3015         }
3016     }
3017 
3018     return bionics_cancelling;
3019 }
3020 
introduce_into_anesthesia(const time_duration & duration,player & installer,bool needs_anesthesia)3021 void Character::introduce_into_anesthesia( const time_duration &duration, player &installer,
3022         bool needs_anesthesia )   //used by the Autodoc
3023 {
3024     if( installer.has_trait( trait_DEBUG_BIONICS ) ) {
3025         installer.add_msg_if_player( m_info,
3026                                      _( "You tell the pain to bug off and proceed with the operation." ) );
3027         return;
3028     }
3029     installer.add_msg_player_or_npc( m_info,
3030                                      _( "You set up the operation step-by-step, configuring the Autodoc to manipulate a CBM." ),
3031                                      _( "<npcname> sets up the operation, configuring the Autodoc to manipulate a CBM." ) );
3032 
3033     add_msg_player_or_npc( m_info,
3034                            _( "You settle into position, sliding your right wrist into the couch's strap." ),
3035                            _( "<npcname> settles into position, sliding their wrist into the couch's strap." ) );
3036     if( needs_anesthesia ) {
3037         //post-threshold medical mutants do not fear operations.
3038         if( has_trait( trait_THRESH_MEDICAL ) ) {
3039             add_msg_if_player( m_mixed,
3040                                _( "You feel excited as the operation starts." ) );
3041         }
3042 
3043         add_msg_if_player( m_mixed,
3044                            _( "You feel a tiny pricking sensation in your right arm, and lose all sensation before abruptly blacking out." ) );
3045 
3046         //post-threshold medical mutants with Deadened don't need anesthesia due to their inability to feel pain
3047     } else {
3048         //post-threshold medical mutants do not fear operations.
3049         if( has_trait( trait_THRESH_MEDICAL ) ) {
3050             add_msg_if_player( m_mixed,
3051                                _( "You feel excited as the Autodoc slices painlessly into you.  You enjoy the sight of scalpels slicing you apart." ) );
3052         } else {
3053             add_msg_if_player( m_mixed,
3054                                _( "You stay very, very still, focusing intently on an interesting stain on the ceiling, as the Autodoc slices painlessly into you." ) );
3055         }
3056     }
3057 
3058     //Pain junkies feel sorry about missed pain from operation.
3059     if( has_trait( trait_MASOCHIST ) || has_trait( trait_MASOCHIST_MED ) ||
3060         has_trait( trait_CENOBITE ) ) {
3061         add_msg_if_player( m_mixed,
3062                            _( "As your consciousness slips away, you feel regret that you won't be able to enjoy the operation." ) );
3063     }
3064 
3065     if( has_effect( effect_narcosis ) ) {
3066         const time_duration remaining_time = get_effect_dur( effect_narcosis );
3067         if( remaining_time < duration ) {
3068             const time_duration top_off_time = duration - remaining_time;
3069             add_effect( effect_narcosis, top_off_time );
3070             fall_asleep( top_off_time );
3071         }
3072     } else {
3073         add_effect( effect_narcosis, duration );
3074         fall_asleep( duration );
3075     }
3076 }
3077