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