diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 1b875941fbada..dc67593ac32ee 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -6,16 +6,196 @@ #include "activity_handlers.h" // put_into_vehicle_or_drop and drop_on_map #include "avatar.h" #include "character.h" +#include "computer_session.h" #include "debug.h" #include "enums.h" +#include "game.h" +#include "iexamine.h" #include "item.h" #include "item_location.h" #include "json.h" #include "line.h" +#include "map.h" +#include "map_iterator.h" #include "npc.h" +#include "output.h" #include "pickup.h" #include "player_activity.h" #include "point.h" +#include "timed_event.h" +#include "uistate.h" + +static const bionic_id bio_fingerhack( "bio_fingerhack" ); + +static const skill_id skill_computer( "computer" ); + +static const trait_id trait_ILLITERATE( "ILLITERATE" ); + +void hacking_activity_actor::start( player_activity &act, Character & ) +{ + act.moves_total = to_moves( 5_minutes ); + act.moves_left = to_moves( 5_minutes ); +} + +enum hack_result { + HACK_UNABLE, + HACK_FAIL, + HACK_NOTHING, + HACK_SUCCESS +}; + +enum hack_type { + HACK_SAFE, + HACK_DOOR, + HACK_GAS, + HACK_NULL +}; + +static int hack_level( const Character &who ) +{ + ///\EFFECT_COMPUTER increases success chance of hacking card readers + // odds go up with int>8, down with int<8 + // 4 int stat is worth 1 computer skill here + ///\EFFECT_INT increases success chance of hacking card readers + return who.get_skill_level( skill_computer ) + who.int_cur / 2 - 8; +} + +static hack_result hack_attempt( Character &who ) +{ + if( who.has_trait( trait_ILLITERATE ) ) { + return HACK_UNABLE; + } + const bool using_electrohack = who.has_charges( "electrohack", 25 ) && + query_yn( _( "Use electrohack?" ) ); + const bool using_fingerhack = !using_electrohack && who.has_bionic( bio_fingerhack ) && + who.get_power_level() > 24_kJ && query_yn( _( "Use fingerhack?" ) ); + + if( !( using_electrohack || using_fingerhack ) ) { + return HACK_UNABLE; + } + + // TODO: Remove this once player -> Character migration is complete + { + player *p = dynamic_cast( &who ); + p->practice( skill_computer, 20 ); + } + if( using_fingerhack ) { + who.mod_power_level( -25_kJ ); + } else { + who.use_charges( "electrohack", 25 ); + } + + // only skilled supergenius never cause short circuits, but the odds are low for people + // with moderate skills + const int hack_stddev = 5; + int success = std::ceil( normal_roll( hack_level( who ), hack_stddev ) ); + if( success < 0 ) { + who.add_msg_if_player( _( "You cause a short circuit!" ) ); + if( using_fingerhack ) { + who.mod_power_level( -25_kJ ); + } else { + who.use_charges( "electrohack", 25 ); + } + + if( success <= -5 ) { + if( using_electrohack ) { + who.add_msg_if_player( m_bad, _( "Your electrohack is ruined!" ) ); + who.use_amount( "electrohack", 1 ); + } else { + who.add_msg_if_player( m_bad, _( "Your power is drained!" ) ); + who.mod_power_level( units::from_kilojoule( -rng( 25, + units::to_kilojoule( who.get_power_level() ) ) ) ); + } + } + return HACK_FAIL; + } else if( success < 6 ) { + return HACK_NOTHING; + } else { + return HACK_SUCCESS; + } +} + +static hack_type get_hack_type( tripoint examp ) +{ + hack_type type = HACK_NULL; + const furn_t &xfurn_t = g->m.furn( examp ).obj(); + const ter_t &xter_t = g->m.ter( examp ).obj(); + if( xter_t.examine == &iexamine::pay_gas || xfurn_t.examine == &iexamine::pay_gas ) { + type = HACK_GAS; + } else if( xter_t.examine == &iexamine::cardreader || xfurn_t.examine == &iexamine::cardreader ) { + type = HACK_DOOR; + } else if( xter_t.examine == &iexamine::gunsafe_el || xfurn_t.examine == &iexamine::gunsafe_el ) { + type = HACK_SAFE; + } + return type; +} + +void hacking_activity_actor::finish( player_activity &act, Character &who ) +{ + tripoint examp = act.placement; + hack_type type = get_hack_type( examp ); + switch( hack_attempt( who ) ) { + case HACK_UNABLE: + who.add_msg_if_player( _( "You cannot hack this." ) ); + break; + case HACK_FAIL: + // currently all things that can be hacked have equivalent alarm failure states. + // this may not always be the case with new hackable things. + g->events().send( who.getID() ); + sounds::sound( who.pos(), 60, sounds::sound_t::music, _( "an alarm sound!" ), true, "environment", + "alarm" ); + if( examp.z > 0 && !g->timed_events.queued( TIMED_EVENT_WANTED ) ) { + g->timed_events.add( TIMED_EVENT_WANTED, calendar::turn + 30_minutes, 0, + who.global_sm_location() ); + } + break; + case HACK_NOTHING: + who.add_msg_if_player( _( "You fail the hack, but no alarms are triggered." ) ); + break; + case HACK_SUCCESS: + if( type == HACK_GAS ) { + int tankGasUnits; + const cata::optional pTank_ = iexamine::getNearFilledGasTank( examp, tankGasUnits ); + if( !pTank_ ) { + break; + } + const tripoint pTank = *pTank_; + const cata::optional pGasPump = iexamine::getGasPumpByNumber( examp, + uistate.ags_pay_gas_selected_pump ); + if( pGasPump && iexamine::toPumpFuel( pTank, *pGasPump, tankGasUnits ) ) { + who.add_msg_if_player( _( "You hack the terminal and route all available fuel to your pump!" ) ); + sounds::sound( examp, 6, sounds::sound_t::activity, + _( "Glug Glug Glug Glug Glug Glug Glug Glug Glug" ), true, "tool", "gaspump" ); + } else { + who.add_msg_if_player( _( "Nothing happens." ) ); + } + } else if( type == HACK_SAFE ) { + who.add_msg_if_player( m_good, _( "The door on the safe swings open." ) ); + g->m.furn_set( examp, furn_str_id( "f_safe_o" ) ); + } else if( type == HACK_DOOR ) { + who.add_msg_if_player( _( "You activate the panel!" ) ); + who.add_msg_if_player( m_good, _( "The nearby doors unlock." ) ); + g->m.ter_set( examp, t_card_reader_broken ); + for( const tripoint &tmp : g->m.points_in_radius( ( examp ), 3 ) ) { + if( g->m.ter( tmp ) == t_door_metal_locked ) { + g->m.ter_set( tmp, t_door_metal_c ); + } + } + } + break; + } + act.set_to_null(); +} + +void hacking_activity_actor::serialize( JsonOut &jsout ) const +{ + jsout.write_null(); +} + +std::unique_ptr hacking_activity_actor::deserialize( JsonIn & ) +{ + return hacking_activity_actor().clone(); +} void move_items_activity_actor::do_turn( player_activity &act, Character &who ) { @@ -132,12 +312,13 @@ std::unique_ptr migration_cancel_activity_actor::deserialize( Js namespace activity_actors { +// Please keep this alphabetically sorted const std::unordered_map( * )( JsonIn & )> deserialize_functions = { + { activity_id( "ACT_HACKING" ), &hacking_activity_actor::deserialize }, { activity_id( "ACT_MIGRATION_CANCEL" ), &migration_cancel_activity_actor::deserialize }, { activity_id( "ACT_MOVE_ITEMS" ), &move_items_activity_actor::deserialize }, }; - } // namespace activity_actors void serialize( const cata::clone_ptr &actor, JsonOut &jsout ) diff --git a/src/activity_actor.h b/src/activity_actor.h index 505c612345368..2f102f762c05d 100644 --- a/src/activity_actor.h +++ b/src/activity_actor.h @@ -67,6 +67,27 @@ class activity_actor virtual void serialize( JsonOut &jsout ) const = 0; }; +class hacking_activity_actor : public activity_actor +{ + public: + hacking_activity_actor() = default; + + activity_id get_type() const override { + return activity_id( "ACT_HACKING" ); + } + + void start( player_activity &act, Character &who ) override; + void do_turn( player_activity &, Character & ) override {}; + void finish( player_activity &act, Character &who ) override; + + std::unique_ptr clone() const override { + return std::make_unique( *this ); + } + + void serialize( JsonOut &jsout ) const override; + static std::unique_ptr deserialize( JsonIn &jsin ); +}; + class move_items_activity_actor : public activity_actor { private: diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index c85fc8b0f45ef..1e882bd894bb8 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -145,7 +145,6 @@ static const activity_id ACT_FORAGE( "ACT_FORAGE" ); static const activity_id ACT_GAME( "ACT_GAME" ); static const activity_id ACT_GENERIC_GAME( "ACT_GENERIC_GAME" ); static const activity_id ACT_GUNMOD_ADD( "ACT_GUNMOD_ADD" ); -static const activity_id ACT_HACKING( "ACT_HACKING" ); static const activity_id ACT_HACKSAW( "ACT_HACKSAW" ); static const activity_id ACT_HAIRCUT( "ACT_HAIRCUT" ); static const activity_id ACT_HAND_CRANK( "ACT_HAND_CRANK" ); @@ -418,7 +417,6 @@ activity_handlers::finish_functions = { { ACT_UNLOAD_MAG, unload_mag_finish }, { ACT_ROBOT_CONTROL, robot_control_finish }, { ACT_MIND_SPLICER, mind_splicer_finish }, - { ACT_HACKING, hacking_finish }, { ACT_SPELLCASTING, spellcasting_finish }, { ACT_STUDY_SPELL, study_spell_finish } }; @@ -4596,139 +4594,6 @@ void activity_handlers::tree_communion_do_turn( player_activity *act, player *p act->set_to_null(); } -static int hack_level( const player &p ) -{ - ///\EFFECT_COMPUTER increases success chance of hacking card readers - // odds go up with int>8, down with int<8 - // 4 int stat is worth 1 computer skill here - ///\EFFECT_INT increases success chance of hacking card readers - return p.get_skill_level( skill_computer ) + p.int_cur / 2 - 8; -} - -static hack_result hack_attempt( player &p ) -{ - if( p.has_trait( trait_ILLITERATE ) ) { - return HACK_UNABLE; - } - const bool using_electrohack = p.has_charges( "electrohack", 25 ) && - query_yn( _( "Use electrohack?" ) ); - const bool using_fingerhack = !using_electrohack && p.has_bionic( bio_fingerhack ) && - p.get_power_level() > 24_kJ && query_yn( _( "Use fingerhack?" ) ); - - if( !( using_electrohack || using_fingerhack ) ) { - return HACK_UNABLE; - } - - p.practice( skill_computer, 20 ); - if( using_fingerhack ) { - p.mod_power_level( -25_kJ ); - } else { - p.use_charges( "electrohack", 25 ); - } - - // only skilled supergenius never cause short circuits, but the odds are low for people - // with moderate skills - const int hack_stddev = 5; - int success = std::ceil( normal_roll( hack_level( p ), hack_stddev ) ); - if( success < 0 ) { - add_msg( _( "You cause a short circuit!" ) ); - if( using_fingerhack ) { - p.mod_power_level( -25_kJ ); - } else { - p.use_charges( "electrohack", 25 ); - } - - if( success <= -5 ) { - if( using_electrohack ) { - add_msg( m_bad, _( "Your electrohack is ruined!" ) ); - p.use_amount( "electrohack", 1 ); - } else { - add_msg( m_bad, _( "Your power is drained!" ) ); - p.mod_power_level( units::from_kilojoule( -rng( 25, - units::to_kilojoule( p.get_power_level() ) ) ) ); - } - } - return HACK_FAIL; - } else if( success < 6 ) { - return HACK_NOTHING; - } else { - return HACK_SUCCESS; - } -} - -static hack_type get_hack_type( tripoint examp ) -{ - hack_type type = HACK_NULL; - const furn_t &xfurn_t = g->m.furn( examp ).obj(); - const ter_t &xter_t = g->m.ter( examp ).obj(); - if( xter_t.examine == &iexamine::pay_gas || xfurn_t.examine == &iexamine::pay_gas ) { - type = HACK_GAS; - } else if( xter_t.examine == &iexamine::cardreader || xfurn_t.examine == &iexamine::cardreader ) { - type = HACK_DOOR; - } else if( xter_t.examine == &iexamine::gunsafe_el || xfurn_t.examine == &iexamine::gunsafe_el ) { - type = HACK_SAFE; - } - return type; - -} - -void activity_handlers::hacking_finish( player_activity *act, player *p ) -{ - tripoint examp = act->placement; - hack_type type = get_hack_type( examp ); - switch( hack_attempt( *p ) ) { - case HACK_UNABLE: - p->add_msg_if_player( _( "You cannot hack this." ) ); - break; - case HACK_FAIL: - // currently all things that can be hacked have equivalent alarm failure states. - // this may not always be the case with new hackable things. - g->events().send( p->getID() ); - sounds::sound( p->pos(), 60, sounds::sound_t::music, _( "an alarm sound!" ), true, "environment", - "alarm" ); - if( examp.z > 0 && !g->timed_events.queued( TIMED_EVENT_WANTED ) ) { - g->timed_events.add( TIMED_EVENT_WANTED, calendar::turn + 30_minutes, 0, - p->global_sm_location() ); - } - break; - case HACK_NOTHING: - p->add_msg_if_player( _( "You fail the hack, but no alarms are triggered." ) ); - break; - case HACK_SUCCESS: - if( type == HACK_GAS ) { - int tankGasUnits; - const cata::optional pTank_ = iexamine::getNearFilledGasTank( examp, tankGasUnits ); - if( !pTank_ ) { - break; - } - const tripoint pTank = *pTank_; - const cata::optional pGasPump = iexamine::getGasPumpByNumber( examp, - uistate.ags_pay_gas_selected_pump ); - if( pGasPump && iexamine::toPumpFuel( pTank, *pGasPump, tankGasUnits ) ) { - p->add_msg_if_player( _( "You hack the terminal and route all available fuel to your pump!" ) ); - sounds::sound( examp, 6, sounds::sound_t::activity, - _( "Glug Glug Glug Glug Glug Glug Glug Glug Glug" ), true, "tool", "gaspump" ); - } else { - p->add_msg_if_player( _( "Nothing happens." ) ); - } - } else if( type == HACK_SAFE ) { - p->add_msg_if_player( m_good, _( "The door on the safe swings open." ) ); - g->m.furn_set( examp, furn_str_id( "f_safe_o" ) ); - } else if( type == HACK_DOOR ) { - p->add_msg_if_player( _( "You activate the panel!" ) ); - p->add_msg_if_player( m_good, _( "The nearby doors unlock." ) ); - g->m.ter_set( examp, t_card_reader_broken ); - for( const tripoint &tmp : g->m.points_in_radius( ( examp ), 3 ) ) { - if( g->m.ter( tmp ) == t_door_metal_locked ) { - g->m.ter_set( tmp, t_door_metal_c ); - } - } - } - break; - } - act->set_to_null(); -} - static void blood_magic( player *p, int cost ) { static std::array part = { { diff --git a/src/activity_handlers.h b/src/activity_handlers.h index fcc46e16f138d..77dea5e8fd8da 100644 --- a/src/activity_handlers.h +++ b/src/activity_handlers.h @@ -122,20 +122,6 @@ void drop_on_map( Character &c, item_drop_reason reason, const std::list & namespace activity_handlers { -enum hack_result { - HACK_UNABLE, - HACK_FAIL, - HACK_NOTHING, - HACK_SUCCESS -}; - -enum hack_type { - HACK_SAFE, - HACK_DOOR, - HACK_GAS, - HACK_NULL -}; - bool resume_for_multi_activities( player &p ); /** activity_do_turn functions: */ void burrow_do_turn( player_activity *act, player *p ); @@ -264,7 +250,6 @@ void haircut_finish( player_activity *act, player *p ); void unload_mag_finish( player_activity *act, player *p ); void robot_control_finish( player_activity *act, player *p ); void mind_splicer_finish( player_activity *act, player *p ); -void hacking_finish( player_activity *act, player *p ); void spellcasting_finish( player_activity *act, player *p ); void study_spell_finish( player_activity *act, player *p ); diff --git a/src/iexamine.cpp b/src/iexamine.cpp index bc2e400743c45..d8f63c766a66e 100644 --- a/src/iexamine.cpp +++ b/src/iexamine.cpp @@ -12,6 +12,7 @@ #include #include "ammo.h" +#include "activity_actor.h" #include "avatar.h" #include "basecamp.h" #include "bionics.h" @@ -97,7 +98,6 @@ static const activity_id ACT_BUILD( "ACT_BUILD" ); static const activity_id ACT_CLEAR_RUBBLE( "ACT_CLEAR_RUBBLE" ); static const activity_id ACT_CRACKING( "ACT_CRACKING" ); static const activity_id ACT_FORAGE( "ACT_FORAGE" ); -static const activity_id ACT_HACKING( "ACT_HACKING" ); static const activity_id ACT_PICKUP( "ACT_PICKUP" ); static const activity_id ACT_PLANT_SEED( "ACT_PLANT_SEED" ); @@ -890,7 +890,7 @@ void iexamine::cardreader( player &p, const tripoint &examp ) add_msg( _( "The nearby doors are already opened." ) ); } } else if( query_yn( _( "Attempt to hack this card-reader?" ) ) ) { - p.assign_activity( ACT_HACKING, to_moves( 5_minutes ) ); + p.assign_activity( player_activity( hacking_activity_actor() ) ); p.activity.placement = examp; } } @@ -951,7 +951,7 @@ void iexamine::cardreader_foodplace( player &p, const tripoint &examp ) _( "\"Your face is inadequate. Please go away.\"" ), true, "speech", "welcome" ); if( query_yn( _( "Attempt to hack this card-reader?" ) ) ) { - p.assign_activity( ACT_HACKING, to_moves( 5_minutes ) ); + p.assign_activity( player_activity( hacking_activity_actor() ) ); p.activity.placement = examp; } } @@ -1344,7 +1344,7 @@ void iexamine::gunsafe_ml( player &p, const tripoint &examp ) void iexamine::gunsafe_el( player &p, const tripoint &examp ) { if( query_yn( _( "Attempt to hack this safe?" ) ) ) { - p.assign_activity( ACT_HACKING, to_moves( 5_minutes ) ); + p.assign_activity( player_activity( hacking_activity_actor() ) ); p.activity.placement = examp; } } @@ -4200,7 +4200,7 @@ void iexamine::pay_gas( player &p, const tripoint &examp ) } if( hack == choice ) { - p.assign_activity( ACT_HACKING, to_moves( 5_minutes ) ); + p.assign_activity( player_activity( hacking_activity_actor() ) ); p.activity.placement = examp; }