1 #include "computer_session.h"
2
3 #include <algorithm>
4 #include <cstdlib>
5 #include <functional>
6 #include <memory>
7 #include <new>
8 #include <string>
9 #include <type_traits>
10 #include <utility>
11
12 #include "avatar.h"
13 #include "calendar.h"
14 #include "character.h"
15 #include "character_id.h"
16 #include "colony.h"
17 #include "color.h"
18 #include "coordinate_conversions.h"
19 #include "coordinates.h"
20 #include "creature.h"
21 #include "debug.h"
22 #include "enums.h"
23 #include "event.h"
24 #include "event_bus.h"
25 #include "explosion.h"
26 #include "field_type.h"
27 #include "game.h"
28 #include "game_constants.h"
29 #include "game_inventory.h"
30 #include "input.h"
31 #include "item.h"
32 #include "item_contents.h"
33 #include "item_factory.h"
34 #include "item_location.h"
35 #include "item_pocket.h"
36 #include "line.h"
37 #include "map.h"
38 #include "map_iterator.h"
39 #include "mapdata.h"
40 #include "messages.h"
41 #include "mission.h"
42 #include "monster.h"
43 #include "mtype.h"
44 #include "optional.h"
45 #include "options.h"
46 #include "output.h"
47 #include "overmap.h"
48 #include "overmapbuffer.h"
49 #include "player.h"
50 #include "point.h"
51 #include "ret_val.h"
52 #include "rng.h"
53 #include "sounds.h"
54 #include "string_formatter.h"
55 #include "text_snippets.h"
56 #include "timed_event.h"
57 #include "translations.h"
58 #include "trap.h"
59 #include "type_id.h"
60 #include "ui.h"
61 #include "ui_manager.h"
62
63 static const efftype_id effect_amigara( "amigara" );
64
65 static const itype_id itype_black_box( "black_box" );
66 static const itype_id itype_blood( "blood" );
67 static const itype_id itype_c4( "c4" );
68 static const itype_id itype_cobalt_60( "cobalt_60" );
69 static const itype_id itype_mininuke( "mininuke" );
70 static const itype_id itype_mininuke_act( "mininuke_act" );
71 static const itype_id itype_radio_repeater_mod( "radio_repeater_mod" );
72 static const itype_id itype_sarcophagus_access_code( "sarcophagus_access_code" );
73 static const itype_id itype_sewage( "sewage" );
74 static const itype_id itype_usb_drive( "usb_drive" );
75 static const itype_id itype_vacutainer( "vacutainer" );
76
77 static const skill_id skill_computer( "computer" );
78
79 static const species_id species_HUMAN( "HUMAN" );
80 static const species_id species_ZOMBIE( "ZOMBIE" );
81
82 static const mtype_id mon_manhack( "mon_manhack" );
83 static const mtype_id mon_secubot( "mon_secubot" );
84
85 static const std::string flag_CONSOLE( "CONSOLE" );
86
init_window()87 static catacurses::window init_window()
88 {
89 const int width = FULL_SCREEN_WIDTH;
90 const int height = FULL_SCREEN_HEIGHT;
91 const point p( std::max( 0, ( TERMX - width ) / 2 ), std::max( 0, ( TERMY - height ) / 2 ) );
92 return catacurses::newwin( height, width, p );
93 }
94
computer_session(computer & comp)95 computer_session::computer_session( computer &comp ) : comp( comp ),
96 win( init_window() ), left( 1 ), top( 1 ), width( getmaxx( win ) - 2 ),
97 height( getmaxy( win ) - 2 )
98 {
99 }
100
use()101 void computer_session::use()
102 {
103 ui_adaptor ui;
104 ui.on_screen_resize( [this]( ui_adaptor & ui ) {
105 const int width = getmaxx( win );
106 const int height = getmaxy( win );
107 const point p( std::max( 0, ( TERMX - width ) / 2 ), std::max( 0, ( TERMY - height ) / 2 ) );
108 win = catacurses::newwin( height, width, p );
109 ui.position_from_window( win );
110 } );
111 ui.mark_resize();
112 ui.on_redraw( [this]( const ui_adaptor & ) {
113 refresh();
114 } );
115
116 avatar &player_character = get_avatar();
117 // Login
118 print_line( _( "Logging into %s…" ), comp.name );
119 if( comp.security > 0 ) {
120 if( calendar::turn < comp.next_attempt ) {
121 print_error( _( "Access is temporary blocked for security purposes." ) );
122 query_any( _( "Please contact the system administrator." ) );
123 reset_terminal();
124 return;
125 }
126 print_error( "%s", comp.access_denied );
127 switch( query_ynq( _( "Bypass security?" ) ) ) {
128 case ynq::quit:
129 reset_terminal();
130 return;
131
132 case ynq::no:
133 query_any( _( "Shutting down… press any key." ) );
134 reset_terminal();
135 return;
136
137 case ynq::yes:
138 if( !hack_attempt( player_character ) ) {
139 if( comp.failures.empty() ) {
140 query_any( _( "Maximum login attempts exceeded. Press any key…" ) );
141 reset_terminal();
142 return;
143 }
144 activate_random_failure();
145 reset_terminal();
146 return;
147 } else { // Successful hack attempt
148 comp.security = 0;
149 query_any( _( "Login successful. Press any key…" ) );
150 reset_terminal();
151 }
152 }
153 } else { // No security
154 query_any( _( "Login successful. Press any key…" ) );
155 reset_terminal();
156 }
157
158 // Main computer loop
159 int sel = 0;
160 while( true ) {
161 size_t options_size = comp.options.size();
162
163 uilist computer_menu;
164 computer_menu.text = string_format( _( "%s - Root Menu" ), comp.name );
165 computer_menu.selected = sel;
166 computer_menu.fselected = sel;
167
168 for( size_t i = 0; i < options_size; i++ ) {
169 bool activatable = can_activate( comp.options[i].action );
170 std::string action_name = comp.options[i].name;
171 if( !activatable ) {
172 action_name = string_format( _( "%s (UNAVAILABLE)" ), action_name );
173 }
174 computer_menu.addentry( i, activatable, MENU_AUTOASSIGN, action_name );
175 }
176
177 ui_manager::redraw();
178 computer_menu.query();
179 if( computer_menu.ret < 0 || static_cast<size_t>( computer_menu.ret ) >= comp.options.size() ) {
180 break;
181 }
182
183 sel = computer_menu.ret;
184 computer_option current = comp.options[sel];
185 reset_terminal();
186 // Once you trip the security, you have to roll every time you want to do something
187 if( current.security + comp.alerts > 0 ) {
188 print_error( _( "Password required." ) );
189 if( query_bool( _( "Hack into system?" ) ) ) {
190 if( !hack_attempt( player_character, current.security ) ) {
191 activate_random_failure();
192 reset_terminal();
193 return;
194 } else {
195 // Successfully hacked function
196 comp.options[sel].security = 0;
197 activate_function( current.action );
198 }
199 }
200 } else { // No need to hack, just activate
201 activate_function( current.action );
202 }
203 reset_terminal();
204 // Done processing a selected option.
205 }
206
207 reset_terminal(); // This should have been done by now, but just in case.
208 }
209
hack_attempt(player & p,int Security)210 bool computer_session::hack_attempt( player &p, int Security )
211 {
212 if( Security == -1 ) {
213 Security = comp.security; // Set to main system security if no value passed
214 }
215 const int hack_skill = p.get_skill_level( skill_computer );
216
217 // Every time you dig for lab notes, (or, in future, do other suspicious stuff?)
218 // +2 dice to the system's hack-resistance
219 // So practical max files from a given terminal = 5, at 10 Computer
220 if( comp.alerts > 0 ) {
221 Security += ( comp.alerts * 2 );
222 }
223
224 p.moves -= 10 * ( 5 + Security * 2 ) / std::max( 1, hack_skill + 1 );
225 int player_roll = hack_skill;
226 ///\EFFECT_INT <8 randomly penalizes hack attempts, 50% of the time
227 if( p.int_cur < 8 && one_in( 2 ) ) {
228 player_roll -= rng( 0, 8 - p.int_cur );
229 ///\EFFECT_INT >8 randomly benefits hack attempts, 33% of the time
230 } else if( p.int_cur > 8 && one_in( 3 ) ) {
231 player_roll += rng( 0, p.int_cur - 8 );
232 }
233
234 ///\EFFECT_COMPUTER increases chance of successful hack attempt, vs Security level
235 bool successful_attempt = ( dice( player_roll, 6 ) >= dice( Security, 6 ) );
236 p.practice( skill_computer, successful_attempt ? ( 15 + Security * 3 ) : 7 );
237 return successful_attempt;
238 }
239
pick_usb()240 static item *pick_usb()
241 {
242 auto filter = []( const item & it ) {
243 return it.typeId() == itype_usb_drive;
244 };
245
246 item_location loc = game_menus::inv::titled_filter_menu( filter, get_avatar(),
247 _( "Choose drive:" ) );
248 if( loc ) {
249 return &*loc;
250 }
251 return nullptr;
252 }
253
remove_submap_turrets()254 static void remove_submap_turrets()
255 {
256 Character &player_character = get_player_character();
257 map &here = get_map();
258 for( monster &critter : g->all_monsters() ) {
259 // Check 1) same overmap coords, 2) turret, 3) hostile
260 if( ms_to_omt_copy( here.getabs( critter.pos() ) ) == ms_to_omt_copy( here.getabs(
261 player_character.pos() ) ) &&
262 critter.has_flag( MF_CONSOLE_DESPAWN ) &&
263 critter.attitude_to( player_character ) == Creature::Attitude::HOSTILE ) {
264 g->remove_zombie( critter );
265 }
266 }
267 }
268
269 const std::map<computer_action, void ( computer_session::* )()>
270 computer_session::computer_action_functions = {
271 { COMPACT_AMIGARA_LOG, &computer_session::action_amigara_log },
272 { COMPACT_AMIGARA_START, &computer_session::action_amigara_start },
273 { COMPACT_BLOOD_ANAL, &computer_session::action_blood_anal },
274 { COMPACT_CASCADE, &computer_session::action_cascade },
275 { COMPACT_COMPLETE_DISABLE_EXTERNAL_POWER, &computer_session::action_complete_disable_external_power },
276 { COMPACT_CONVEYOR, &computer_session::action_conveyor },
277 { COMPACT_DATA_ANAL, &computer_session::action_data_anal },
278 { COMPACT_DEACTIVATE_SHOCK_VENT, &computer_session::action_deactivate_shock_vent },
279 { COMPACT_DISCONNECT, &computer_session::action_disconnect },
280 { COMPACT_DOWNLOAD_SOFTWARE, &computer_session::action_download_software },
281 { COMPACT_ELEVATOR_ON, &computer_session::action_elevator_on },
282 { COMPACT_EMERG_MESS, &computer_session::action_emerg_mess },
283 { COMPACT_EMERG_REF_CENTER, &computer_session::action_emerg_ref_center },
284 { COMPACT_EXTRACT_RAD_SOURCE, &computer_session::action_extract_rad_source },
285 { COMPACT_GEIGER, &computer_session::action_geiger },
286 { COMPACT_IRRADIATOR, &computer_session::action_irradiator },
287 { COMPACT_LIST_BIONICS, &computer_session::action_list_bionics },
288 { COMPACT_LOCK, &computer_session::action_lock },
289 { COMPACT_MAP_SEWER, &computer_session::action_map_sewer },
290 { COMPACT_MAP_SUBWAY, &computer_session::action_map_subway },
291 { COMPACT_MAPS, &computer_session::action_maps },
292 { COMPACT_MISS_DISARM, &computer_session::action_miss_disarm },
293 { COMPACT_OPEN, &computer_session::action_open },
294 { COMPACT_OPEN_DISARM, &computer_session::action_open_disarm },
295 { COMPACT_PORTAL, &computer_session::action_portal },
296 { COMPACT_RADIO_ARCHIVE, &computer_session::action_radio_archive },
297 { COMPACT_RELEASE, &computer_session::action_release },
298 { COMPACT_RELEASE_BIONICS, &computer_session::action_release_bionics },
299 { COMPACT_RELEASE_DISARM, &computer_session::action_release_disarm },
300 { COMPACT_REPEATER_MOD, &computer_session::action_repeater_mod },
301 { COMPACT_RESEARCH, &computer_session::action_research },
302 { COMPACT_SAMPLE, &computer_session::action_sample },
303 { COMPACT_SHUTTERS, &computer_session::action_shutters },
304 { COMPACT_SR1_MESS, &computer_session::action_sr1_mess },
305 { COMPACT_SR2_MESS, &computer_session::action_sr2_mess },
306 { COMPACT_SR3_MESS, &computer_session::action_sr3_mess },
307 { COMPACT_SR4_MESS, &computer_session::action_sr4_mess },
308 { COMPACT_SRCF_1_MESS, &computer_session::action_srcf_1_mess },
309 { COMPACT_SRCF_2_MESS, &computer_session::action_srcf_2_mess },
310 { COMPACT_SRCF_3_MESS, &computer_session::action_srcf_3_mess },
311 { COMPACT_SRCF_ELEVATOR, &computer_session::action_srcf_elevator },
312 { COMPACT_SRCF_SEAL, &computer_session::action_srcf_seal },
313 { COMPACT_SRCF_SEAL_ORDER, &computer_session::action_srcf_seal_order },
314 { COMPACT_TERMINATE, &computer_session::action_terminate },
315 { COMPACT_TOLL, &computer_session::action_toll },
316 { COMPACT_TOWER_UNRESPONSIVE, &computer_session::action_tower_unresponsive },
317 { COMPACT_UNLOCK, &computer_session::action_unlock },
318 { COMPACT_UNLOCK_DISARM, &computer_session::action_unlock_disarm },
319 };
320
can_activate(computer_action action)321 bool computer_session::can_activate( computer_action action )
322 {
323 switch( action ) {
324 case COMPACT_LOCK:
325 return get_map().has_nearby_ter( get_player_character().pos(), t_door_metal_c, 8 );
326
327 case COMPACT_RELEASE:
328 case COMPACT_RELEASE_DISARM:
329 return get_map().has_nearby_ter( get_player_character().pos(), t_reinforced_glass, 25 );
330
331 case COMPACT_RELEASE_BIONICS:
332 return get_map().has_nearby_ter( get_player_character().pos(), t_reinforced_glass, 3 );
333
334 case COMPACT_TERMINATE: {
335 map &here = get_map();
336 for( const tripoint &p : here.points_on_zlevel() ) {
337 monster *const mon = g->critter_at<monster>( p );
338 if( !mon ) {
339 continue;
340 }
341 if( ( here.ter( p + tripoint_north ) == t_reinforced_glass &&
342 here.ter( p + tripoint_south ) == t_concrete_wall ) ||
343 ( here.ter( p + tripoint_south ) == t_reinforced_glass &&
344 here.ter( p + tripoint_north ) == t_concrete_wall ) ) {
345 return true;
346 }
347 }
348 return false;
349 }
350
351 case COMPACT_UNLOCK:
352 case COMPACT_UNLOCK_DISARM:
353 return get_map().has_nearby_ter( get_player_character().pos(), t_door_metal_locked, 8 );
354
355 default:
356 return true;
357 }
358 }
359
activate_function(computer_action action)360 void computer_session::activate_function( computer_action action )
361 {
362 const auto it = computer_action_functions.find( action );
363 if( it != computer_action_functions.end() ) {
364 // Token move cost for any action, if an action takes longer decrement moves further.
365 get_player_character().moves -= 30;
366 ( this->*( it->second ) )();
367 }
368 }
369
action_open_disarm()370 void computer_session::action_open_disarm()
371 {
372 remove_submap_turrets();
373 action_open();
374 }
375
action_open()376 void computer_session::action_open()
377 {
378 get_map().translate_radius( t_door_metal_locked, t_floor, 25.0, get_player_character().pos(),
379 true );
380 query_any( _( "Doors opened. Press any key…" ) );
381 }
382
383 //LOCK AND UNLOCK are used to build more complex buildings
384 // that can have multiple doors that can be locked and be
385 // unlocked by different computers.
386 //Simply uses translate_radius which take a given radius and
387 // player position to determine which terrain tiles to edit.
action_lock()388 void computer_session::action_lock()
389 {
390 get_map().translate_radius( t_door_metal_c, t_door_metal_locked, 8.0, get_player_character().pos(),
391 true );
392 query_any( _( "Lock enabled. Press any key…" ) );
393 }
394
action_unlock_disarm()395 void computer_session::action_unlock_disarm()
396 {
397 remove_submap_turrets();
398 action_unlock();
399 }
400
action_unlock()401 void computer_session::action_unlock()
402 {
403 get_map().translate_radius( t_door_metal_locked, t_door_metal_c, 8.0, get_player_character().pos(),
404 true );
405 query_any( _( "Lock disabled. Press any key…" ) );
406 }
407
408 //Toll is required for the church computer/mechanism to function
action_toll()409 void computer_session::action_toll()
410 {
411 sounds::sound( get_player_character().pos(), 120, sounds::sound_t::music,
412 //~ the sound of a church bell ringing
413 _( "Bohm… Bohm… Bohm…" ), true, "environment", "church_bells" );
414 }
415
action_sample()416 void computer_session::action_sample()
417 {
418 get_player_character().moves -= 30;
419 map &here = get_map();
420 for( const tripoint &p : here.points_on_zlevel() ) {
421 if( here.ter( p ) != t_sewage_pump ) {
422 continue;
423 }
424 for( const tripoint &n : here.points_in_radius( p, 1 ) ) {
425 if( here.furn( n ) != f_counter ) {
426 continue;
427 }
428 bool found_item = false;
429 item sewage( itype_sewage, calendar::turn );
430 for( item &elem : here.i_at( n ) ) {
431 int capa = elem.get_remaining_capacity_for_liquid( sewage );
432 if( capa <= 0 ) {
433 continue;
434 }
435 sewage.charges = std::min( sewage.charges, capa );
436 if( elem.can_contain( sewage ) ) {
437 elem.put_in( sewage, item_pocket::pocket_type::CONTAINER );
438 }
439 found_item = true;
440 break;
441 }
442 if( !found_item ) {
443 here.add_item_or_charges( n, sewage );
444 }
445 }
446 }
447 }
448
action_release()449 void computer_session::action_release()
450 {
451 get_event_bus().send<event_type::releases_subspace_specimens>();
452 Character &player_character = get_player_character();
453 sounds::sound( player_character.pos(), 40, sounds::sound_t::alarm, _( "an alarm sound!" ), false,
454 "environment",
455 "alarm" );
456 get_map().translate_radius( t_reinforced_glass, t_thconc_floor, 25.0, player_character.pos(),
457 true );
458 query_any( _( "Containment shields opened. Press any key…" ) );
459 }
460
action_release_disarm()461 void computer_session::action_release_disarm()
462 {
463 remove_submap_turrets();
464 action_release_bionics();
465 }
466
action_release_bionics()467 void computer_session::action_release_bionics()
468 {
469 Character &player_character = get_player_character();
470 sounds::sound( player_character.pos(), 40, sounds::sound_t::alarm, _( "an alarm sound!" ), false,
471 "environment",
472 "alarm" );
473 get_map().translate_radius( t_reinforced_glass, t_thconc_floor, 3.0, player_character.pos(), true );
474 query_any( _( "Containment shields opened. Press any key…" ) );
475 }
476
action_terminate()477 void computer_session::action_terminate()
478 {
479 get_event_bus().send<event_type::terminates_subspace_specimens>();
480 Character &player_character = get_player_character();
481 map &here = get_map();
482 for( const tripoint &p : here.points_on_zlevel() ) {
483 monster *const mon = g->critter_at<monster>( p );
484 if( !mon ) {
485 continue;
486 }
487 if( ( here.ter( p + tripoint_north ) == t_reinforced_glass &&
488 here.ter( p + tripoint_south ) == t_concrete_wall ) ||
489 ( here.ter( p + tripoint_south ) == t_reinforced_glass &&
490 here.ter( p + tripoint_north ) == t_concrete_wall ) ) {
491 mon->die( &player_character );
492 }
493 }
494 query_any( _( "Subjects terminated. Press any key…" ) );
495 }
496
action_portal()497 void computer_session::action_portal()
498 {
499 get_event_bus().send<event_type::opens_portal>();
500 map &here = get_map();
501 for( const tripoint &tmp : here.points_on_zlevel() ) {
502 int numtowers = 0;
503 for( const tripoint &tmp2 : here.points_in_radius( tmp, 2 ) ) {
504 if( here.ter( tmp2 ) == t_radio_tower ) {
505 numtowers++;
506 }
507 }
508 if( numtowers >= 4 ) {
509 if( here.tr_at( tmp ).id == trap_str_id( "tr_portal" ) ) {
510 here.remove_trap( tmp );
511 } else {
512 here.trap_set( tmp, tr_portal );
513 }
514 }
515 }
516 }
517
action_cascade()518 void computer_session::action_cascade()
519 {
520 if( !query_bool( _( "WARNING: Resonance cascade carries severe risk! Continue?" ) ) ) {
521 return;
522 }
523 get_event_bus().send<event_type::causes_resonance_cascade>();
524 tripoint player_pos = get_player_character().pos();
525 map &here = get_map();
526 std::vector<tripoint> cascade_points;
527 for( const tripoint &dest : here.points_in_radius( player_pos, 10 ) ) {
528 if( here.ter( dest ) == t_radio_tower ) {
529 cascade_points.push_back( dest );
530 }
531 }
532 explosion_handler::resonance_cascade( random_entry( cascade_points, player_pos ) );
533 }
534
action_research()535 void computer_session::action_research()
536 {
537 map &here = get_map();
538 // TODO: seed should probably be a member of the computer, or better: of the computer action.
539 // It is here to ensure one computer reporting the same text on each invocation.
540 const int seed = std::hash<tripoint> {}( here.get_abs_sub() ) + comp.alerts;
541 cata::optional<translation> log = SNIPPET.random_from_category( "lab_notes", seed );
542 if( !log.has_value() ) {
543 log = to_translation( "No data found." );
544 } else {
545 get_player_character().moves -= 70;
546 }
547
548 print_text( "%s", log.value() );
549 // One's an anomaly
550 if( comp.alerts == 0 ) {
551 query_any( _( "Local data-access error logged, alerting helpdesk. Press any key…" ) );
552 comp.alerts ++;
553 } else {
554 // Two's a trend.
555 query_any(
556 _( "Warning: anomalous archive-access activity detected at this node. Press any key…" ) );
557 comp.alerts ++;
558 }
559 }
560
action_radio_archive()561 void computer_session::action_radio_archive()
562 {
563 get_player_character().moves -= 300;
564 sfx::fade_audio_channel( sfx::channel::radio, 100 );
565 sfx::play_ambient_variant_sound( "radio", "inaudible_chatter", 100, sfx::channel::radio,
566 2000 );
567 print_text( "Accessing archive. Playing audio recording nr %d.\n%s", rng( 1, 9999 ),
568 SNIPPET.random_from_category( "radio_archive" ).value_or( translation() ) );
569 if( one_in( 3 ) ) {
570 query_any( _( "Warning: restricted data access. Attempt logged. Press any key…" ) );
571 comp.alerts ++;
572 } else {
573 query_any( _( "Press any key…" ) );
574 }
575 sfx::fade_audio_channel( sfx::channel::radio, 100 );
576 }
577
action_maps()578 void computer_session::action_maps()
579 {
580 Character &player_character = get_player_character();
581 player_character.moves -= 30;
582 const tripoint_abs_omt center = player_character.global_omt_location();
583 overmap_buffer.reveal( center.xy(), 40, 0 );
584 query_any(
585 _( "Surface map data downloaded. Local anomalous-access error logged. Press any key…" ) );
586 comp.remove_option( COMPACT_MAPS );
587 comp.alerts ++;
588 }
589
action_map_sewer()590 void computer_session::action_map_sewer()
591 {
592 Character &player_character = get_player_character();
593 player_character.moves -= 30;
594 const tripoint_abs_omt center = player_character.global_omt_location();
595 for( int i = -60; i <= 60; i++ ) {
596 for( int j = -60; j <= 60; j++ ) {
597 point offset( i, j );
598 const oter_id &oter = overmap_buffer.ter( center + offset );
599 if( is_ot_match( "sewer", oter, ot_match_type::type ) ||
600 is_ot_match( "sewage", oter, ot_match_type::prefix ) ) {
601 overmap_buffer.set_seen( center + offset, true );
602 }
603 }
604 }
605 query_any( _( "Sewage map data downloaded. Press any key…" ) );
606 comp.remove_option( COMPACT_MAP_SEWER );
607 }
608
action_map_subway()609 void computer_session::action_map_subway()
610 {
611 Character &player_character = get_player_character();
612 player_character.moves -= 30;
613 const tripoint_abs_omt center = player_character.global_omt_location();
614 for( int i = -60; i <= 60; i++ ) {
615 for( int j = -60; j <= 60; j++ ) {
616 point offset( i, j );
617 const oter_id &oter = overmap_buffer.ter( center + offset );
618 if( is_ot_match( "subway", oter, ot_match_type::type ) ||
619 is_ot_match( "lab_train_depot", oter, ot_match_type::contains ) ) {
620 overmap_buffer.set_seen( center + offset, true );
621 }
622 }
623 }
624 query_any( _( "Subway map data downloaded. Press any key…" ) );
625 comp.remove_option( COMPACT_MAP_SUBWAY );
626 }
627
action_miss_disarm()628 void computer_session::action_miss_disarm()
629 {
630 // TODO: stop the nuke from creating radioactive clouds.
631 if( query_yn( _( "Disarm missile." ) ) ) {
632 get_event_bus().send<event_type::disarms_nuke>();
633 add_msg( m_info, _( "Nuclear missile disarmed!" ) );
634 //disable missile.
635 comp.options.clear();
636 activate_failure( COMPFAIL_SHUTDOWN );
637 } else {
638 add_msg( m_neutral, _( "Nuclear missile remains active." ) );
639 return;
640 }
641 }
642
action_list_bionics()643 void computer_session::action_list_bionics()
644 {
645 get_player_character().moves -= 30;
646 std::vector<std::string> names;
647 int more = 0;
648 map &here = get_map();
649 for( const tripoint &p : here.points_on_zlevel() ) {
650 for( item &elem : here.i_at( p ) ) {
651 if( elem.is_bionic() ) {
652 if( static_cast<int>( names.size() ) < TERMY - 8 ) {
653 names.push_back( elem.type_name() );
654 } else {
655 more++;
656 }
657 }
658 }
659 }
660
661 reset_terminal();
662
663 print_newline();
664 print_line( _( "Bionic access - Manifest:" ) );
665 print_newline();
666
667 for( auto &name : names ) {
668 print_line( "%s", name );
669 }
670 if( more > 0 ) {
671 print_line( ngettext( "%d OTHER FOUND…", "%d OTHERS FOUND…", more ), more );
672 }
673
674 print_newline();
675 query_any( _( "Press any key…" ) );
676 }
677
action_elevator_on()678 void computer_session::action_elevator_on()
679 {
680 map &here = get_map();
681 for( const tripoint &p : here.points_on_zlevel() ) {
682 if( here.ter( p ) == t_elevator_control_off ) {
683 here.ter_set( p, t_elevator_control );
684 }
685 }
686 query_any( _( "Elevator activated. Press any key…" ) );
687 }
688
action_amigara_log()689 void computer_session::action_amigara_log()
690 {
691 Character &player_character = get_player_character();
692 player_character.moves -= 30;
693 reset_terminal();
694 point abs_sub = get_map().get_abs_sub().xy();
695 print_line( _( "NEPower Mine(%d:%d) Log" ), abs_sub.x, abs_sub.y );
696 print_text( "%s", SNIPPET.random_from_category( "amigara1" ).value_or( translation() ) );
697
698 if( !query_bool( _( "Continue reading?" ) ) ) {
699 return;
700 }
701 player_character.moves -= 30;
702 reset_terminal();
703 print_line( _( "NEPower Mine(%d:%d) Log" ), abs_sub.x, abs_sub.y );
704 print_text( "%s", SNIPPET.random_from_category( "amigara2" ).value_or( translation() ) );
705
706 if( !query_bool( _( "Continue reading?" ) ) ) {
707 return;
708 }
709 player_character.moves -= 30;
710 reset_terminal();
711 print_line( _( "NEPower Mine(%d:%d) Log" ), abs_sub.x, abs_sub.y );
712 print_text( "%s", SNIPPET.random_from_category( "amigara3" ).value_or( translation() ) );
713
714 if( !query_bool( _( "Continue reading?" ) ) ) {
715 return;
716 }
717 reset_terminal();
718 for( int i = 0; i < 10; i++ ) {
719 print_gibberish_line();
720 }
721 print_newline();
722 print_newline();
723 print_newline();
724 print_line( _( "AMIGARA PROJECT" ) );
725 print_newline();
726 print_newline();
727 if( !query_bool( _( "Continue reading?" ) ) ) {
728 return;
729 }
730 player_character.moves -= 30;
731 reset_terminal();
732 tripoint abs_loc = get_map().get_abs_sub();
733 print_line( _( "SITE %d%d%d\n"
734 "PERTINENT FOREMAN LOGS WILL BE PREPENDED TO NOTES" ),
735 abs_loc.x, abs_loc.y, std::abs( abs_loc.z ) );
736 print_text( "%s", SNIPPET.random_from_category( "amigara4" ).value_or( translation() ) );
737 print_gibberish_line();
738 print_gibberish_line();
739 print_newline();
740 print_error( _( "FILE CORRUPTED, PRESS ANY KEY…" ) );
741 query_any();
742 reset_terminal();
743 }
744
action_amigara_start()745 void computer_session::action_amigara_start()
746 {
747 get_timed_events().add( timed_event_type::AMIGARA, calendar::turn + 1_minutes );
748 // Disable this action to prevent further amigara events, which would lead to
749 // further amigara monster, which would lead to further artifacts.
750 comp.remove_option( COMPACT_AMIGARA_START );
751 }
752
action_complete_disable_external_power()753 void computer_session::action_complete_disable_external_power()
754 {
755 for( mission *miss : get_avatar().get_active_missions() ) {
756 static const mission_type_id commo_2 = mission_type_id( "MISSION_OLD_GUARD_NEC_COMMO_2" );
757 if( miss->mission_id() == commo_2 ) {
758 print_error( _( "--ACCESS GRANTED--" ) );
759 print_error( _( "Mission Complete!" ) );
760 miss->step_complete( 1 );
761 query_any();
762 return;
763 }
764 }
765 print_error( _( "ACCESS DENIED" ) );
766 query_any();
767 }
768
action_repeater_mod()769 void computer_session::action_repeater_mod()
770 {
771 avatar &player_character = get_avatar();
772 if( player_character.has_amount( itype_radio_repeater_mod, 1 ) ) {
773 for( mission *miss : player_character.get_active_missions() ) {
774 static const mission_type_id commo_3 = mission_type_id( "MISSION_OLD_GUARD_NEC_COMMO_3" ),
775 commo_4 = mission_type_id( "MISSION_OLD_GUARD_NEC_COMMO_4" );
776 if( miss->mission_id() == commo_3 || miss->mission_id() == commo_4 ) {
777 miss->step_complete( 1 );
778 print_error( _( "Repeater mod installed…" ) );
779 print_error( _( "Mission Complete!" ) );
780 player_character.use_amount( itype_radio_repeater_mod, 1 );
781 query_any();
782 comp.options.clear();
783 activate_failure( COMPFAIL_SHUTDOWN );
784 break;
785 }
786 }
787 } else {
788 print_error( _( "You do not have a repeater mod to install…" ) );
789 query_any();
790 }
791 }
792
action_download_software()793 void computer_session::action_download_software()
794 {
795 if( item *const usb = pick_usb() ) {
796 mission *miss = mission::find( comp.mission_id );
797 if( miss == nullptr ) {
798 debugmsg( _( "Computer couldn't find its mission!" ) );
799 return;
800 }
801 get_player_character().moves -= 30;
802 item software( miss->get_item_id(), calendar::turn_zero );
803 software.mission_id = comp.mission_id;
804 usb->contents.clear_items();
805 usb->put_in( software, item_pocket::pocket_type::SOFTWARE );
806 print_line( _( "Software downloaded." ) );
807 } else {
808 print_error( _( "USB drive required!" ) );
809 }
810 query_any();
811 }
812
action_blood_anal()813 void computer_session::action_blood_anal()
814 {
815 Character &player_character = get_player_character();
816 player_character.moves -= 70;
817 map &here = get_map();
818 for( const tripoint &dest : here.points_in_radius( player_character.pos(), 2 ) ) {
819 if( here.furn( dest ) == furn_str_id( "f_centrifuge" ) ) {
820 map_stack items = here.i_at( dest );
821 if( items.empty() ) {
822 print_error( _( "ERROR: Please place sample in centrifuge." ) );
823 } else if( items.size() > 1 ) {
824 print_error( _( "ERROR: Please remove all but one sample from centrifuge." ) );
825 } else if( items.only_item().contents.empty() ) {
826 print_error( _( "ERROR: Please only use container with blood sample." ) );
827 } else if( items.only_item().contents.legacy_front().typeId() != itype_blood ) {
828 print_error( _( "ERROR: Please only use blood samples." ) );
829 } else { // Success!
830 const item &blood = items.only_item().contents.legacy_front();
831 const mtype *mt = blood.get_mtype();
832 if( mt == nullptr || mt->id == mtype_id::NULL_ID() ) {
833 print_line( _( "Result: Human blood, no pathogens found." ) );
834 } else if( mt->in_species( species_ZOMBIE ) ) {
835 if( mt->in_species( species_HUMAN ) ) {
836 print_line( _( "Result: Human blood. Unknown pathogen found." ) );
837 } else {
838 print_line( _( "Result: Unknown blood type. Unknown pathogen found." ) );
839 }
840 print_line( _( "Pathogen bonded to erythrocytes and leukocytes." ) );
841 if( query_bool( _( "Download data?" ) ) ) {
842 if( item *const usb = pick_usb() ) {
843 item software( "software_blood_data", calendar::turn_zero );
844 usb->contents.clear_items();
845 usb->put_in( software, item_pocket::pocket_type::SOFTWARE );
846 print_line( _( "Software downloaded." ) );
847 } else {
848 print_error( _( "USB drive required!" ) );
849 }
850 }
851 } else {
852 print_line( _( "Result: Unknown blood type. Test non-conclusive." ) );
853 }
854 }
855 }
856 }
857 query_any( _( "Press any key…" ) );
858 }
859
action_data_anal()860 void computer_session::action_data_anal()
861 {
862 Character &player_character = get_player_character();
863 player_character.moves -= 30;
864 map &here = get_map();
865 for( const tripoint &dest : here.points_in_radius( player_character.pos(), 2 ) ) {
866 if( here.ter( dest ) == t_floor_blue ) {
867 print_error( _( "PROCESSING DATA" ) );
868 map_stack items = here.i_at( dest );
869 if( items.empty() ) {
870 print_error( _( "ERROR: Please place memory bank in scan area." ) );
871 } else if( items.size() > 1 ) {
872 print_error( _( "ERROR: Please only scan one item at a time." ) );
873 } else if( items.only_item().typeId() != itype_usb_drive &&
874 items.only_item().typeId() != itype_black_box ) {
875 print_error( _( "ERROR: Memory bank destroyed or not present." ) );
876 } else if( items.only_item().typeId() == itype_usb_drive &&
877 items.only_item().contents.empty() ) {
878 print_error( _( "ERROR: Memory bank is empty." ) );
879 } else { // Success!
880 if( items.only_item().typeId() == itype_black_box ) {
881 print_line( _( "Memory Bank: Military Hexron Encryption\nPrinting Transcript\n" ) );
882 item transcript( "black_box_transcript", calendar::turn );
883 here.add_item_or_charges( player_character.pos(), transcript );
884 } else {
885 print_line( _( "Memory Bank: Unencrypted\nNothing of interest.\n" ) );
886 }
887 }
888 }
889 }
890 query_any( _( "Press any key…" ) );
891 }
892
action_disconnect()893 void computer_session::action_disconnect()
894 {
895 reset_terminal();
896 print_line( _( "\n"
897 "ERROR: NETWORK DISCONNECT\n"
898 "UNABLE TO REACH NETWORK ROUTER OR PROXY. PLEASE CONTACT YOUR\n"
899 "SYSTEM ADMINISTRATOR TO RESOLVE THIS ISSUE.\n"
900 " \n" ) );
901 query_any( _( "Press any key to continue…" ) );
902 }
903
action_emerg_mess()904 void computer_session::action_emerg_mess()
905 {
906 print_line( _( "GREETINGS CITIZEN. A BIOLOGICAL ATTACK HAS TAKEN PLACE AND A STATE OF\n"
907 "EMERGENCY HAS BEEN DECLARED. EMERGENCY PERSONNEL WILL BE AIDING YOU\n"
908 "SHORTLY. TO ENSURE YOUR SAFETY PLEASE FOLLOW THE STEPS BELOW.\n"
909 "\n"
910 "1. DO NOT PANIC.\n"
911 "2. REMAIN INSIDE THE BUILDING.\n"
912 "3. SEEK SHELTER IN THE BASEMENT.\n"
913 "4. USE PROVIDED GAS MASKS.\n"
914 "5. AWAIT FURTHER INSTRUCTIONS.\n"
915 "\n"
916 " \n" ) );
917 query_any( _( "Press any key to continue…" ) );
918 }
919
action_tower_unresponsive()920 void computer_session::action_tower_unresponsive()
921 {
922 print_line( _( " WARNING, RADIO TOWER IS UNRESPONSIVE.\n"
923 " \n"
924 " BACKUP POWER INSUFFICIENT TO MEET BROADCASTING REQUIREMENTS.\n"
925 " IN THE EVENT OF AN EMERGENCY, CONTACT LOCAL NATIONAL GUARD\n"
926 " UNITS TO RECEIVE PRIORITY WHEN GENERATORS ARE BEING DEPLOYED.\n"
927 " \n"
928 " \n" ) );
929 query_any( _( "Press any key to continue…" ) );
930 }
931
action_sr1_mess()932 void computer_session::action_sr1_mess()
933 {
934 reset_terminal();
935 print_text( "%s", SNIPPET.random_from_category( "sr1_mess" ).value_or( translation() ) );
936 query_any( _( "Press any key to continue…" ) );
937 }
938
action_sr2_mess()939 void computer_session::action_sr2_mess()
940 {
941 reset_terminal();
942 print_text( "%s", SNIPPET.random_from_category( "sr2_mess" ).value_or( translation() ) );
943 query_any( _( "Press any key to continue…" ) );
944 }
945
action_sr3_mess()946 void computer_session::action_sr3_mess()
947 {
948 reset_terminal();
949 print_text( "%s", SNIPPET.random_from_category( "sr3_mess" ).value_or( translation() ) );
950 query_any( _( "Press any key to continue…" ) );
951 }
952
action_sr4_mess()953 void computer_session::action_sr4_mess()
954 {
955 reset_terminal();
956 print_text( "%s", SNIPPET.random_from_category( "sr4_mess" ).value_or( translation() ) );
957 query_any( _( "Press any key to continue…" ) );
958 }
959
action_srcf_1_mess()960 void computer_session::action_srcf_1_mess()
961 {
962 reset_terminal();
963 print_text( "%s", SNIPPET.random_from_category( "scrf_1_mess" ).value_or( translation() ) );
964 query_any( _( "Press any key to continue…" ) );
965 }
966
action_srcf_2_mess()967 void computer_session::action_srcf_2_mess()
968 {
969 reset_terminal();
970 print_text( "%s", SNIPPET.random_from_category( "scrf_2_1_mess" ).value_or( translation() ) );
971 query_any( _( "Press any key to continue…" ) );
972 reset_terminal();
973 print_text( "%s", SNIPPET.random_from_category( "scrf_2_2_mess" ).value_or( translation() ) );
974 query_any( _( "Press any key to continue…" ) );
975 }
976
action_srcf_3_mess()977 void computer_session::action_srcf_3_mess()
978 {
979 reset_terminal();
980 print_text( "%s", SNIPPET.random_from_category( "scrf_3_mess" ).value_or( translation() ) );
981 query_any( _( "Press any key to continue…" ) );
982 }
983
action_srcf_seal_order()984 void computer_session::action_srcf_seal_order()
985 {
986 reset_terminal();
987 print_text( "%s", SNIPPET.random_from_category( "scrf_seal_order" ).value_or( translation() ) );
988 query_any( _( "Press any key to continue…" ) );
989 }
990
action_srcf_seal()991 void computer_session::action_srcf_seal()
992 {
993 get_event_bus().send<event_type::seals_hazardous_material_sarcophagus>();
994 print_line( _( "Charges Detonated" ) );
995 print_line( _( "Backup Generator Power Failing" ) );
996 print_line( _( "Evacuate Immediately" ) );
997 add_msg( m_warning, _( "Evacuate Immediately!" ) );
998 map &here = get_map();
999 for( const tripoint &p : here.points_on_zlevel() ) {
1000 if( here.ter( p ) == t_elevator || here.ter( p ) == t_vat ) {
1001 here.make_rubble( p, f_rubble_rock, true );
1002 explosion_handler::explosion( p, 40, 0.7, true );
1003 }
1004 if( here.ter( p ) == t_wall_glass ) {
1005 here.make_rubble( p, f_rubble_rock, true );
1006 }
1007 if( here.ter( p ) == t_sewage_pipe || here.ter( p ) == t_sewage || here.ter( p ) == t_grate ) {
1008 here.make_rubble( p, f_rubble_rock, true );
1009 }
1010 if( here.ter( p ) == t_sewage_pump ) {
1011 here.make_rubble( p, f_rubble_rock, true );
1012 explosion_handler::explosion( p, 50, 0.7, true );
1013 }
1014 }
1015 comp.options.clear(); // Disable the terminal.
1016 activate_failure( COMPFAIL_SHUTDOWN );
1017 }
1018
action_srcf_elevator()1019 void computer_session::action_srcf_elevator()
1020 {
1021 Character &player_character = get_player_character();
1022 map &here = get_map();
1023 tripoint surface_elevator;
1024 tripoint underground_elevator;
1025 bool is_surface_elevator_on = false;
1026 bool is_surface_elevator_exist = false;
1027 bool is_underground_elevator_on = false;
1028 bool is_underground_elevator_exist = false;
1029
1030 for( const tripoint &p : here.points_on_zlevel( 0 ) ) {
1031 if( here.ter( p ) == t_elevator_control_off || here.ter( p ) == t_elevator_control ) {
1032 surface_elevator = p;
1033 is_surface_elevator_on = here.ter( p ) == t_elevator_control;
1034 is_surface_elevator_exist = true;
1035 }
1036 }
1037 for( const tripoint &p : here.points_on_zlevel( -2 ) ) {
1038 if( here.ter( p ) == t_elevator_control_off || here.ter( p ) == t_elevator_control ) {
1039 underground_elevator = p;
1040 is_underground_elevator_on = here.ter( p ) == t_elevator_control;
1041 is_underground_elevator_exist = true;
1042 }
1043 }
1044
1045 //If some are destroyed
1046 if( !is_surface_elevator_exist || !is_underground_elevator_exist ) {
1047 reset_terminal();
1048 print_error(
1049 _( "\nElevator control network unreachable!\n\n" ) );
1050 }
1051
1052 //If both are disabled try to enable
1053 else if( !is_surface_elevator_on && !is_underground_elevator_on ) {
1054 if( !player_character.has_amount( itype_sarcophagus_access_code, 1 ) ) {
1055 print_error( _( "Access code required!\n\n" ) );
1056 } else {
1057 player_character.use_amount( itype_sarcophagus_access_code, 1 );
1058 here.ter_set( surface_elevator, t_elevator_control );
1059 is_surface_elevator_on = true;
1060 here.ter_set( underground_elevator, t_elevator_control );
1061 is_underground_elevator_on = true;
1062 }
1063 }
1064
1065 //If only one is enabled, enable the other one. Fix for before this change
1066 else if( is_surface_elevator_on && !is_underground_elevator_on && is_underground_elevator_exist ) {
1067 here.ter_set( underground_elevator, t_elevator_control );
1068 is_underground_elevator_on = true;
1069 }
1070
1071 else if( is_underground_elevator_on && !is_surface_elevator_on && is_surface_elevator_exist ) {
1072 here.ter_set( surface_elevator, t_elevator_control );
1073 is_surface_elevator_on = true;
1074 }
1075
1076 //If the elevator is working
1077 if( is_surface_elevator_on && is_underground_elevator_on ) {
1078 reset_terminal();
1079 print_line(
1080 _( "\nPower: Backup Only\nRadiation Level: Very Dangerous\nOperational: Overridden\n\n" ) );
1081 }
1082
1083 query_any( _( "Press any key…" ) );
1084 }
1085
1086 //irradiates food at t_rad_platform, adds radiation
action_irradiator()1087 void computer_session::action_irradiator()
1088 {
1089 Character &player_character = get_player_character();
1090 player_character.moves -= 30;
1091 bool error = false;
1092 bool platform_exists = false;
1093 map &here = get_map();
1094 for( const tripoint &dest : here.points_in_radius( player_character.pos(), 10 ) ) {
1095 if( here.ter( dest ) == t_rad_platform ) {
1096 platform_exists = true;
1097 if( here.i_at( dest ).empty() ) {
1098 print_error( _( "ERROR: Processing platform empty." ) );
1099 } else {
1100 player_character.moves -= 300;
1101 for( auto it = here.i_at( dest ).begin(); it != here.i_at( dest ).end(); ++it ) {
1102 // actual food processing
1103 itype_id irradiated_type( "irradiated_" + it->typeId().str() );
1104 if( !it->rotten() && item_controller->has_template( irradiated_type ) ) {
1105 it->convert( irradiated_type );
1106 }
1107 // critical failure - radiation spike sets off electronic detonators
1108 if( it->typeId() == itype_mininuke || it->typeId() == itype_mininuke_act ||
1109 it->typeId() == itype_c4 ) {
1110 explosion_handler::explosion( dest, 40 );
1111 reset_terminal();
1112 print_error( _( "WARNING [409]: Primary sensors offline!" ) );
1113 print_error( _( " >> Initialize secondary sensors: Geiger profiling…" ) );
1114 print_error( _( " >> Radiation spike detected!\n" ) );
1115 print_error( _( "WARNING [912]: Catastrophic malfunction! Contamination detected!" ) );
1116 print_error( _( "EMERGENCY PROCEDURE [1]: Evacuate. Evacuate. Evacuate.\n" ) );
1117 sounds::sound( player_character.pos(), 30, sounds::sound_t::alarm, _( "an alarm sound!" ), false,
1118 "environment",
1119 "alarm" );
1120 here.i_rem( dest, it );
1121 here.make_rubble( dest );
1122 here.propagate_field( dest, fd_nuke_gas, 100, 3 );
1123 here.translate_radius( t_water_pool, t_sewage, 8.0, dest, true );
1124 here.adjust_radiation( dest, rng( 50, 500 ) );
1125 for( const tripoint &radorigin : here.points_in_radius( dest, 5 ) ) {
1126 here.adjust_radiation( radorigin, rng( 50, 500 ) / ( rl_dist( radorigin,
1127 dest ) > 0 ? rl_dist( radorigin, dest ) : 1 ) );
1128 }
1129 if( here.pl_sees( dest, 10 ) ) {
1130 player_character.irradiate( rng_float( 50, 250 ) / rl_dist( player_character.pos(), dest ) );
1131 } else {
1132 player_character.irradiate( rng_float( 20, 100 ) / rl_dist( player_character.pos(), dest ) );
1133 }
1134 query_any( _( "EMERGENCY SHUTDOWN! Press any key…" ) );
1135 error = true;
1136 comp.options.clear(); // Disable the terminal.
1137 activate_failure( COMPFAIL_SHUTDOWN );
1138 break;
1139 }
1140 here.adjust_radiation( dest, rng( 20, 50 ) );
1141 for( const tripoint &radorigin : here.points_in_radius( dest, 5 ) ) {
1142 here.adjust_radiation( radorigin, rng( 20, 50 ) / ( rl_dist( radorigin,
1143 dest ) > 0 ? rl_dist( radorigin, dest ) : 1 ) );
1144 }
1145 // if unshielded, rad source irradiates player directly, reduced by distance to source
1146 if( here.pl_sees( dest, 10 ) ) {
1147 player_character.irradiate( rng_float( 5, 25 ) / rl_dist( player_character.pos(), dest ) );
1148 }
1149 }
1150 if( !error && platform_exists ) {
1151 print_error( _( "PROCESSING… CYCLE COMPLETE." ) );
1152 print_error( _( "GEIGER COUNTER @ PLATFORM: %s mSv/h." ), here.get_radiation( dest ) );
1153 }
1154 }
1155 }
1156 }
1157 if( !platform_exists ) {
1158 print_error(
1159 _( "CRITICAL ERROR… RADIATION PLATFORM UNRESPONSIVE. COMPLY TO PROCEDURE RP_M_01_rev.03." ) );
1160 }
1161 if( !error ) {
1162 query_any( _( "Press any key…" ) );
1163 }
1164 }
1165
1166 // geiger counter for irradiator, primary measurement at t_rad_platform, secondary at player loacation
action_geiger()1167 void computer_session::action_geiger()
1168 {
1169 Character &player_character = get_player_character();
1170 player_character.moves -= 30;
1171 tripoint platform;
1172 bool source_exists = false;
1173 int sum_rads = 0;
1174 int peak_rad = 0;
1175 int tiles_counted = 0;
1176 map &here = get_map();
1177 print_error( _( "RADIATION MEASUREMENTS:" ) );
1178 for( const tripoint &dest : here.points_in_radius( player_character.pos(), 10 ) ) {
1179 if( here.ter( dest ) == t_rad_platform ) {
1180 source_exists = true;
1181 platform = dest;
1182 }
1183 }
1184 if( source_exists ) {
1185 for( const tripoint &dest : here.points_in_radius( platform, 3 ) ) {
1186 sum_rads += here.get_radiation( dest );
1187 tiles_counted ++;
1188 if( here.get_radiation( dest ) > peak_rad ) {
1189 peak_rad = here.get_radiation( dest );
1190 }
1191 sum_rads += here.get_radiation( platform );
1192 tiles_counted ++;
1193 if( here.get_radiation( platform ) > peak_rad ) {
1194 peak_rad = here.get_radiation( platform );
1195 }
1196 }
1197 print_error( _( "GEIGER COUNTER @ ZONE:… AVG %s mSv/h." ), sum_rads / tiles_counted );
1198 print_error( _( "GEIGER COUNTER @ ZONE:… MAX %s mSv/h." ), peak_rad );
1199 print_newline();
1200 }
1201 print_error( _( "GEIGER COUNTER @ CONSOLE:… %s mSv/h." ),
1202 here.get_radiation( player_character.pos() ) );
1203 print_error( _( "PERSONAL DOSIMETRY:… %s mSv." ), player_character.get_rad() );
1204 print_newline();
1205 query_any( _( "Press any key…" ) );
1206 }
1207
1208 // imitates item movement through conveyor belt through 3 different loading/unloading bays
1209 // ensure only bay of each type in range
action_conveyor()1210 void computer_session::action_conveyor()
1211 {
1212 Character &player_character = get_player_character();
1213 player_character.moves -= 300;
1214 tripoint loading; // red tile = loading bay
1215 tripoint unloading; // green tile = unloading bay
1216 tripoint platform; // radiation platform = middle point
1217 bool l_exists = false;
1218 bool u_exists = false;
1219 bool p_exists = false;
1220 map &here = get_map();
1221 for( const tripoint &dest : here.points_in_radius( player_character.pos(), 10 ) ) {
1222 if( here.ter( dest ) == t_rad_platform ) {
1223 platform = dest;
1224 p_exists = true;
1225 } else if( here.ter( dest ) == t_floor_red ) {
1226 loading = dest;
1227 l_exists = true;
1228 } else if( here.ter( dest ) == t_floor_green ) {
1229 unloading = dest;
1230 u_exists = true;
1231 }
1232 }
1233 if( !l_exists || !p_exists || !u_exists ) {
1234 print_error( _( "Conveyor belt malfunction. Consult maintenance team." ) );
1235 query_any( _( "Press any key…" ) );
1236 return;
1237 }
1238 map_stack items = here.i_at( platform );
1239 if( !items.empty() ) {
1240 print_line( _( "Moving items: PLATFORM --> UNLOADING BAY." ) );
1241 } else {
1242 print_line( _( "No items detected at: PLATFORM." ) );
1243 }
1244 for( const auto &it : items ) {
1245 here.add_item_or_charges( unloading, it );
1246 }
1247 here.i_clear( platform );
1248 items = here.i_at( loading );
1249 if( !items.empty() ) {
1250 print_line( _( "Moving items: LOADING BAY --> PLATFORM." ) );
1251 } else {
1252 print_line( _( "No items detected at: LOADING BAY." ) );
1253 }
1254 for( const auto &it : items ) {
1255 if( !it.made_of_from_type( phase_id::LIQUID ) ) {
1256 here.add_item_or_charges( platform, it );
1257 }
1258 }
1259 here.i_clear( loading );
1260 query_any( _( "Conveyor belt cycle complete. Press any key…" ) );
1261 }
1262
1263 // toggles reinforced glass shutters open->closed and closed->open depending on their current state
action_shutters()1264 void computer_session::action_shutters()
1265 {
1266 Character &player_character = get_player_character();
1267 player_character.moves -= 300;
1268 get_map().translate_radius( t_reinforced_glass_shutter_open, t_reinforced_glass_shutter, 8.0,
1269 player_character.pos(),
1270 true, true );
1271 query_any( _( "Toggling shutters. Press any key…" ) );
1272 }
1273
1274 // extract radiation source material from irradiator
action_extract_rad_source()1275 void computer_session::action_extract_rad_source()
1276 {
1277 Character &player_character = get_player_character();
1278 if( query_yn( _( "Operation irreversible. Extract radioactive material?" ) ) ) {
1279 player_character.moves -= 300;
1280 tripoint platform;
1281 bool p_exists = false;
1282 map &here = get_map();
1283 for( const tripoint &dest : here.points_in_radius( player_character.pos(), 10 ) ) {
1284 if( here.ter( dest ) == t_rad_platform ) {
1285 platform = dest;
1286 p_exists = true;
1287 }
1288 }
1289 if( p_exists ) {
1290 here.spawn_item( platform, itype_cobalt_60, rng( 8, 15 ) );
1291 here.translate_radius( t_rad_platform, t_concrete, 8.0, player_character.pos(), true );
1292 comp.remove_option( COMPACT_IRRADIATOR );
1293 comp.remove_option( COMPACT_EXTRACT_RAD_SOURCE );
1294 query_any( _( "Extraction sequence complete… Press any key." ) );
1295 } else {
1296 query_any( _( "ERROR! Radiation platform unresponsive… Press any key." ) );
1297 }
1298 }
1299 }
1300
1301 // remove shock vent fields; check for existing plutonium generators in radius
action_deactivate_shock_vent()1302 void computer_session::action_deactivate_shock_vent()
1303 {
1304 Character &player_character = get_player_character();
1305 player_character.moves -= 30;
1306 bool has_vent = false;
1307 bool has_generator = false;
1308 map &here = get_map();
1309 for( const tripoint &dest : here.points_in_radius( player_character.pos(), 10 ) ) {
1310 if( here.get_field( dest, fd_shock_vent ) != nullptr ) {
1311 has_vent = true;
1312 }
1313 if( here.ter( dest ) == t_plut_generator ) {
1314 has_generator = true;
1315 }
1316 here.remove_field( dest, fd_shock_vent );
1317 }
1318 print_line( _( "Initiating POWER-DIAG ver.2.34…" ) );
1319 if( has_vent ) {
1320 print_error( _( "Short circuit detected!" ) );
1321 print_error( _( "Short circuit rerouted." ) );
1322 print_error( _( "Fuse reset." ) );
1323 print_error( _( "Ground re-enabled." ) );
1324 } else {
1325 print_line( _( "Internal power lines status: 85%% OFFLINE. Reason: DAMAGED." ) );
1326 }
1327 print_line(
1328 _( "External power lines status: 100%% OFFLINE. Reason: NO EXTERNAL POWER DETECTED." ) );
1329 if( has_generator ) {
1330 print_line( _( "Backup power status: STANDBY MODE." ) );
1331 } else {
1332 print_error( _( "Backup power status: OFFLINE. Reason: UNKNOWN" ) );
1333 }
1334 query_any( _( "Press any key…" ) );
1335 }
1336
activate_random_failure()1337 void computer_session::activate_random_failure()
1338 {
1339 comp.next_attempt = calendar::turn + 45_minutes;
1340 static const computer_failure default_failure( COMPFAIL_SHUTDOWN );
1341 const computer_failure &fail = random_entry( comp.failures, default_failure );
1342 activate_failure( fail.type );
1343 }
1344
1345 const std::map<computer_failure_type, void( computer_session::* )()>
1346 computer_session::computer_failure_functions = {
1347 { COMPFAIL_ALARM, &computer_session::failure_alarm },
1348 { COMPFAIL_AMIGARA, &computer_session::failure_amigara },
1349 { COMPFAIL_DAMAGE, &computer_session::failure_damage },
1350 { COMPFAIL_DESTROY_BLOOD, &computer_session::failure_destroy_blood },
1351 { COMPFAIL_DESTROY_DATA, &computer_session::failure_destroy_data },
1352 { COMPFAIL_MANHACKS, &computer_session::failure_manhacks },
1353 { COMPFAIL_PUMP_EXPLODE, &computer_session::failure_pump_explode },
1354 { COMPFAIL_PUMP_LEAK, &computer_session::failure_pump_leak },
1355 { COMPFAIL_SECUBOTS, &computer_session::failure_secubots },
1356 { COMPFAIL_SHUTDOWN, &computer_session::failure_shutdown },
1357 };
1358
activate_failure(computer_failure_type fail)1359 void computer_session::activate_failure( computer_failure_type fail )
1360 {
1361 const auto it = computer_failure_functions.find( fail );
1362 if( it != computer_failure_functions.end() ) {
1363 ( this->*( it->second ) )();
1364 }
1365 }
1366
failure_shutdown()1367 void computer_session::failure_shutdown()
1368 {
1369 bool found_tile = false;
1370 map &here = get_map();
1371 for( const tripoint &p : here.points_in_radius( get_player_character().pos(), 1 ) ) {
1372 if( here.has_flag( flag_CONSOLE, p ) ) {
1373 here.furn_set( p, furn_str_id( "f_console_broken" ) );
1374 add_msg( m_bad, _( "The console shuts down." ) );
1375 found_tile = true;
1376 }
1377 }
1378 if( found_tile ) {
1379 return;
1380 }
1381 for( const tripoint &p : here.points_on_zlevel() ) {
1382 if( here.has_flag( flag_CONSOLE, p ) ) {
1383 here.furn_set( p, furn_str_id( "f_console_broken" ) );
1384 add_msg( m_bad, _( "The console shuts down." ) );
1385 }
1386 }
1387 }
1388
failure_alarm()1389 void computer_session::failure_alarm()
1390 {
1391 Character &player_character = get_player_character();
1392 get_event_bus().send<event_type::triggers_alarm>( player_character.getID() );
1393 sounds::sound( player_character.pos(), 60, sounds::sound_t::alarm, _( "an alarm sound!" ), false,
1394 "environment",
1395 "alarm" );
1396 if( get_map().get_abs_sub().z > 0 && !get_timed_events().queued( timed_event_type::WANTED ) ) {
1397 get_timed_events().add( timed_event_type::WANTED, calendar::turn + 30_minutes, 0,
1398 player_character.global_sm_location() );
1399 }
1400 }
1401
failure_manhacks()1402 void computer_session::failure_manhacks()
1403 {
1404 int num_robots = rng( 4, 8 );
1405 const tripoint_range<tripoint> range =
1406 get_map().points_in_radius( get_player_character().pos(), 3 );
1407 for( int i = 0; i < num_robots; i++ ) {
1408 if( g->place_critter_within( mon_manhack, range ) ) {
1409 add_msg( m_warning, _( "Manhacks drop from compartments in the ceiling." ) );
1410 }
1411 }
1412 }
1413
failure_secubots()1414 void computer_session::failure_secubots()
1415 {
1416 int num_robots = 1;
1417 const tripoint_range<tripoint> range =
1418 get_map().points_in_radius( get_player_character().pos(), 3 );
1419 for( int i = 0; i < num_robots; i++ ) {
1420 if( g->place_critter_within( mon_secubot, range ) ) {
1421 add_msg( m_warning, _( "Secubots emerge from compartments in the floor." ) );
1422 }
1423 }
1424 }
1425
failure_damage()1426 void computer_session::failure_damage()
1427 {
1428 add_msg( m_neutral, _( "The console shocks you." ) );
1429 Character &player_character = get_player_character();
1430 if( player_character.is_elec_immune() ) {
1431 add_msg( m_good, _( "You're protected from electric shocks." ) );
1432 } else {
1433 add_msg( m_bad, _( "Your body is damaged by the electric shock!" ) );
1434 player_character.hurtall( rng( 1, 10 ), nullptr );
1435 }
1436 }
1437
failure_pump_explode()1438 void computer_session::failure_pump_explode()
1439 {
1440 add_msg( m_warning, _( "The pump explodes!" ) );
1441 map &here = get_map();
1442 for( const tripoint &p : here.points_on_zlevel() ) {
1443 if( here.ter( p ) == t_sewage_pump ) {
1444 here.make_rubble( p );
1445 explosion_handler::explosion( p, 10 );
1446 }
1447 }
1448 }
1449
failure_pump_leak()1450 void computer_session::failure_pump_leak()
1451 {
1452 add_msg( m_warning, _( "Sewage leaks!" ) );
1453 map &here = get_map();
1454 for( const tripoint &p : here.points_on_zlevel() ) {
1455 if( here.ter( p ) != t_sewage_pump ) {
1456 continue;
1457 }
1458 const int leak_size = rng( 4, 10 );
1459 for( int i = 0; i < leak_size; i++ ) {
1460 std::vector<tripoint> next_move;
1461 if( here.passable( p + point_north ) ) {
1462 next_move.push_back( p + point_north );
1463 }
1464 if( here.passable( p + point_east ) ) {
1465 next_move.push_back( p + point_east );
1466 }
1467 if( here.passable( p + point_south ) ) {
1468 next_move.push_back( p + point_south );
1469 }
1470 if( here.passable( p + point_west ) ) {
1471 next_move.push_back( p + point_west );
1472 }
1473 if( next_move.empty() ) {
1474 break;
1475 }
1476 here.ter_set( random_entry( next_move ), t_sewage );
1477 }
1478 }
1479 }
1480
failure_amigara()1481 void computer_session::failure_amigara()
1482 {
1483 get_timed_events().add( timed_event_type::AMIGARA, calendar::turn + 30_seconds );
1484 get_player_character().add_effect( effect_amigara, 2_minutes );
1485 map &here = get_map();
1486 explosion_handler::explosion( tripoint( rng( 0, MAPSIZE_X ), rng( 0, MAPSIZE_Y ),
1487 here.get_abs_sub().z ), 10, 0.7, false, 10 );
1488 explosion_handler::explosion( tripoint( rng( 0, MAPSIZE_X ), rng( 0, MAPSIZE_Y ),
1489 here.get_abs_sub().z ), 10, 0.7, false, 10 );
1490 comp.remove_option( COMPACT_AMIGARA_START );
1491 }
1492
failure_destroy_blood()1493 void computer_session::failure_destroy_blood()
1494 {
1495 print_error( _( "ERROR: Disruptive Spin" ) );
1496 map &here = get_map();
1497 for( const tripoint &dest : here.points_in_radius( get_player_character().pos(), 2 ) ) {
1498 if( here.furn( dest ) == furn_str_id( "f_centrifuge" ) ) {
1499 map_stack items = here.i_at( dest );
1500 if( items.empty() ) {
1501 print_error( _( "ERROR: Please place sample in centrifuge." ) );
1502 } else if( items.size() > 1 ) {
1503 print_error( _( "ERROR: Please remove all but one sample from centrifuge." ) );
1504 } else if( items.only_item().typeId() != itype_vacutainer ) {
1505 print_error( _( "ERROR: Please use blood-contained samples." ) );
1506 } else if( items.only_item().contents.empty() ) {
1507 print_error( _( "ERROR: Blood draw kit, empty." ) );
1508 } else if( items.only_item().contents.legacy_front().typeId() != itype_blood ) {
1509 print_error( _( "ERROR: Please only use blood samples." ) );
1510 } else {
1511 print_error( _( "ERROR: Blood sample destroyed." ) );
1512 here.i_clear( dest );
1513 }
1514 }
1515 }
1516 query_any();
1517 }
1518
failure_destroy_data()1519 void computer_session::failure_destroy_data()
1520 {
1521 print_error( _( "ERROR: ACCESSING DATA MALFUNCTION" ) );
1522 map &here = get_map();
1523 for( const tripoint &p : here.points_in_radius( get_player_character().pos(), 2 ) ) {
1524 if( here.ter( p ) == t_floor_blue ) {
1525 map_stack items = here.i_at( p );
1526 if( items.empty() ) {
1527 print_error( _( "ERROR: Please place memory bank in scan area." ) );
1528 } else if( items.size() > 1 ) {
1529 print_error( _( "ERROR: Please only scan one item at a time." ) );
1530 } else if( items.only_item().typeId() != itype_usb_drive ) {
1531 print_error( _( "ERROR: Memory bank destroyed or not present." ) );
1532 } else if( items.only_item().contents.empty() ) {
1533 print_error( _( "ERROR: Memory bank is empty." ) );
1534 } else {
1535 print_error( _( "ERROR: Data bank destroyed." ) );
1536 here.i_clear( p );
1537 }
1538 }
1539 }
1540 query_any();
1541 }
1542
action_emerg_ref_center()1543 void computer_session::action_emerg_ref_center()
1544 {
1545 reset_terminal();
1546 print_line( _( "SEARCHING FOR NEAREST REFUGEE CENTER, PLEASE WAIT…" ) );
1547
1548 const mission_type_id &mission_type = mission_type_id( "MISSION_REACH_REFUGEE_CENTER" );
1549 tripoint_abs_omt mission_target;
1550 avatar &player_character = get_avatar();
1551 // Check completed missions too, so people can't repeatedly get the mission.
1552 const std::vector<mission *> completed_missions = player_character.get_completed_missions();
1553 std::vector<mission *> missions = player_character.get_active_missions();
1554 missions.insert( missions.end(), completed_missions.begin(), completed_missions.end() );
1555
1556 const bool has_mission = std::any_of( missions.begin(), missions.end(), [ &mission_type,
1557 &mission_target ]( mission * mission ) {
1558 if( mission->get_type().id == mission_type ) {
1559 mission_target = mission->get_target();
1560 return true;
1561 }
1562
1563 return false;
1564 } );
1565
1566 if( !has_mission ) {
1567 mission *new_mission = mission::reserve_new( mission_type, character_id() );
1568 new_mission->assign( player_character );
1569 mission_target = new_mission->get_target();
1570 }
1571
1572 //~555-0164 is a fake phone number in the US, please replace it with a number that will not cause issues in your locale if possible.
1573 print_line( _( "\nREFUGEE CENTER FOUND! LOCATION: %d %s\n\n"
1574 "IF YOU HAVE ANY FEEDBACK CONCERNING YOUR VISIT PLEASE CONTACT\n"
1575 "THE DEPARTMENT OF EMERGENCY MANAGEMENT PUBLIC AFFAIRS OFFICE.\n"
1576 "THE LOCAL OFFICE CAN BE REACHED BETWEEN THE HOURS OF 9AM AND\n"
1577 "4PM AT 555-0164.\n"
1578 "\n"
1579 "IF YOU WOULD LIKE TO SPEAK WITH SOMEONE IN PERSON OR WOULD LIKE\n"
1580 "TO WRITE US A LETTER PLEASE SEND IT TO…\n" ),
1581 rl_dist( player_character.global_omt_location(), mission_target ),
1582 direction_name_short(
1583 direction_from( player_character.global_omt_location(), mission_target ) ) );
1584
1585 query_any( _( "Press any key to continue…" ) );
1586 reset_terminal();
1587 }
1588
1589 template<typename ...Args>
query_bool(const std::string & text,Args &&...args)1590 bool computer_session::query_bool( const std::string &text, Args &&... args )
1591 {
1592 return query_ynq( text, std::forward<Args>( args )... ) == ynq::yes;
1593 }
1594
1595 template<typename ...Args>
query_any(const std::string & text,Args &&...args)1596 bool computer_session::query_any( const std::string &text, Args &&... args )
1597 {
1598 print_indented_line( 0, width, text, std::forward<Args>( args )... );
1599 return query_any();
1600 }
1601
query_any()1602 bool computer_session::query_any()
1603 {
1604 ui_manager::redraw();
1605 inp_mngr.wait_for_any_key();
1606 return true;
1607 }
1608
1609 template<typename ...Args>
query_ynq(const std::string & text,Args &&...args)1610 computer_session::ynq computer_session::query_ynq( const std::string &text, Args &&... args )
1611 {
1612 const std::string formatted_text = string_format( text, std::forward<Args>( args )... );
1613 const bool force_uc = get_option<bool>( "FORCE_CAPITAL_YN" );
1614 const auto &allow_key = force_uc ? input_context::disallow_lower_case_or_non_modified_letters
1615 : input_context::allow_all_keys;
1616 input_context ctxt( "YESNOQUIT", keyboard_mode::keycode );
1617 ctxt.register_action( "YES" );
1618 ctxt.register_action( "NO" );
1619 ctxt.register_action( "QUIT" );
1620 ctxt.register_action( "HELP_KEYBINDINGS" );
1621 print_indented_line( 0, width, force_uc && !is_keycode_mode_supported()
1622 //~ 1st: query string, 2nd-4th: keybinding descriptions
1623 ? pgettext( "query_ynq", "%s %s, %s, %s (Case sensitive)" )
1624 //~ 1st: query string, 2nd-4th: keybinding descriptions
1625 : pgettext( "query_ynq", "%s %s, %s, %s" ),
1626 formatted_text,
1627 ctxt.describe_key_and_name( "YES", allow_key ),
1628 ctxt.describe_key_and_name( "NO", allow_key ),
1629 ctxt.describe_key_and_name( "QUIT", allow_key ) );
1630
1631 do {
1632 ui_manager::redraw();
1633 const std::string action = ctxt.handle_input();
1634 if( allow_key( ctxt.get_raw_input() ) ) {
1635 if( action == "YES" ) {
1636 return ynq::yes;
1637 } else if( action == "NO" ) {
1638 return ynq::no;
1639 } else if( action == "QUIT" ) {
1640 return ynq::quit;
1641 }
1642 }
1643 } while( true );
1644 }
1645
refresh()1646 void computer_session::refresh()
1647 {
1648 werase( win );
1649 draw_border( win );
1650 for( size_t i = 0; i < lines.size(); ++i ) {
1651 nc_color dummy = c_green;
1652 print_colored_text( win, point( left + lines[i].first, top + static_cast<int>( i ) ),
1653 dummy, dummy, lines[i].second );
1654 }
1655 wnoutrefresh( win );
1656 }
1657
1658 template<typename ...Args>
print_indented_line(const int indent,const int text_width,const std::string & text,Args &&...args)1659 void computer_session::print_indented_line( const int indent, const int text_width,
1660 const std::string &text, Args &&... args )
1661 {
1662 if( text_width <= 0 || height <= 0 ) {
1663 return;
1664 }
1665 const size_t uheight = static_cast<size_t>( height );
1666 const std::string formatted_text = string_format( text, std::forward<Args>( args )... );
1667 std::vector<std::string> folded = foldstring( formatted_text, text_width );
1668 if( folded.size() >= uheight ) {
1669 lines.clear();
1670 } else if( lines.size() + folded.size() > uheight ) {
1671 lines.erase( lines.begin(), lines.begin() + ( lines.size() + folded.size() - uheight ) );
1672 }
1673 lines.reserve( uheight );
1674 for( auto it = folded.size() >= uheight ? folded.end() - uheight : folded.begin();
1675 it < folded.end(); ++it ) {
1676 lines.emplace_back( indent, *it );
1677 }
1678 }
1679
1680 template<typename ...Args>
print_line(const std::string & text,Args &&...args)1681 void computer_session::print_line( const std::string &text, Args &&... args )
1682 {
1683 print_indented_line( 0, width, text, std::forward<Args>( args )... );
1684 }
1685
1686 template<typename ...Args>
print_error(const std::string & text,Args &&...args)1687 void computer_session::print_error( const std::string &text, Args &&... args )
1688 {
1689 const std::string formatted_text = string_format( text, std::forward<Args>( args )... );
1690 print_indented_line( 0, width, "%s", colorize( formatted_text, c_red ) );
1691 }
1692
1693 template<typename ...Args>
print_text(const std::string & text,Args &&...args)1694 void computer_session::print_text( const std::string &text, Args &&... args )
1695 {
1696 print_indented_line( 1, width - 2, text, std::forward<Args>( args )... );
1697 print_newline();
1698 }
1699
print_gibberish_line()1700 void computer_session::print_gibberish_line()
1701 {
1702 std::string gibberish;
1703 int length = rng( 50, 70 );
1704 for( int i = 0; i < length; i++ ) {
1705 switch( rng( 0, 4 ) ) {
1706 case 0:
1707 gibberish += static_cast<char>( '0' + rng( 0, 9 ) );
1708 break;
1709 case 1:
1710 case 2:
1711 gibberish += static_cast<char>( 'a' + rng( 0, 25 ) );
1712 break;
1713 case 3:
1714 case 4:
1715 gibberish += static_cast<char>( 'A' + rng( 0, 25 ) );
1716 break;
1717 }
1718 }
1719 print_indented_line( 0, width, "%s", colorize( gibberish, c_yellow ) );
1720 }
1721
reset_terminal()1722 void computer_session::reset_terminal()
1723 {
1724 lines.clear();
1725 }
1726
print_newline()1727 void computer_session::print_newline()
1728 {
1729 if( height <= 0 ) {
1730 return;
1731 }
1732 const size_t uheight = static_cast<size_t>( height );
1733 if( lines.size() >= uheight ) {
1734 lines.erase( lines.begin(), lines.end() - ( uheight - 1 ) );
1735 }
1736 lines.emplace_back();
1737 }
1738