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