diff --git a/data/json/effects.json b/data/json/effects.json index aa31d93272882..45e42994f98e6 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -244,6 +244,12 @@ "name": [ "Pushed" ], "desc": [ "AI tag used for monsters pushing each other. This is a bug if you have it." ] }, + { + "type": "effect_type", + "id": "worked_on", + "name": [ "Worked On" ], + "desc": [ "AI tag used for robots being disabled. This is a bug if you have it." ] + }, { "type": "effect_type", "id": "venom_player1", diff --git a/data/json/player_activities.json b/data/json/player_activities.json index 947c20723245e..2b966cdc3f3ff 100644 --- a/data/json/player_activities.json +++ b/data/json/player_activities.json @@ -943,6 +943,14 @@ "verb": { "ctxt": "training", "str": "working out" }, "based_on": "time" }, + { + "id": "ACT_DISABLE", + "type": "activity_type", + "activity_level": "NO_EXERCISE", + "verb": "disabling", + "suspendable": false, + "based_on": "speed" + }, { "id": "ACT_FURNITURE_MOVE", "type": "activity_type", diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index d391d1a4dd8c7..eb4d27c2daae9 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -77,9 +77,12 @@ #include "vehicle.h" #include "vpart_position.h" +static const efftype_id effect_docile( "docile" ); static const efftype_id effect_pet( "pet" ); +static const efftype_id effect_sensor_stun( "sensor_stun" ); static const efftype_id effect_sheared( "sheared" ); static const efftype_id effect_sleep( "sleep" ); +static const efftype_id effect_worked_on( "worked_on" ); static const efftype_id effect_tied( "tied" ); static const itype_id itype_bone_human( "bone_human" ); @@ -88,12 +91,15 @@ static const itype_id itype_electrohack( "electrohack" ); static const itype_id itype_pseudo_bio_picklock( "pseudo_bio_picklock" ); static const skill_id skill_computer( "computer" ); +static const skill_id skill_electronics( "electronics" ); static const skill_id skill_mechanics( "mechanics" ); static const skill_id skill_traps( "traps" ); static const proficiency_id proficiency_prof_lockpicking( "prof_lockpicking" ); static const proficiency_id proficiency_prof_lockpicking_expert( "prof_lockpicking_expert" ); + +static const mtype_id mon_manhack( "mon_manhack" ); static const mtype_id mon_zombie( "mon_zombie" ); static const mtype_id mon_zombie_fat( "mon_zombie_fat" ); static const mtype_id mon_zombie_rot( "mon_zombie_rot" ); @@ -2408,6 +2414,108 @@ std::unique_ptr stash_activity_actor::deserialize( JsonIn &jsin return actor.clone(); } +void disable_activity_actor::start( player_activity &act, Character &/*who*/ ) +{ + act.moves_total = moves_total; + act.moves_left = moves_total; + monster &critter = *( g->critter_at( target ) ); + critter.add_effect( effect_worked_on, 1_turns ); +} + +void disable_activity_actor::do_turn( player_activity &, Character &who ) +{ + monster *const mon_ptr = g->critter_at( target ); + if( !mon_ptr ) { + who.add_msg_if_player( _( "The robot has moved somewhere else." ) ); + who.cancel_activity(); + return; + } + + monster &critter = *mon_ptr; + if( !can_disable_or_reprogram( critter ) ) { + // I think recovery from stunned is the only reason this could happen + who.add_msg_if_player( _( "The %s recovers before you can finish." ), critter.name() ); + who.cancel_activity(); + return; + } + + critter.add_effect( effect_worked_on, 1_turns ); +} + +void disable_activity_actor::finish( player_activity &act, Character &/*who*/ ) +{ + // Should never be null as we just checked in do_turn + monster &critter = *( g->critter_at( target ) ); + + if( reprogram ) { + if( critter.has_effect( effect_docile ) ) { + critter.remove_effect( effect_docile ); + if( one_in( 3 ) ) { + add_msg( _( "The %s hovers momentarily as it surveys the area." ), + critter.name() ); + } + } else { + critter.add_effect( effect_docile, 1_turns, true ); + if( one_in( 3 ) ) { + add_msg( _( "The %s lets out a whirring noise and starts to follow you." ), + critter.name() ); + } + } + } else { + get_map().add_item_or_charges( target, critter.to_item() ); + if( !critter.has_flag( MF_INTERIOR_AMMO ) ) { + for( std::pair &ammodef : critter.ammo ) { + if( ammodef.second > 0 ) { + get_map().spawn_item( target.xy(), ammodef.first, 1, ammodef.second, calendar::turn ); + } + } + } + g->remove_zombie( critter ); + } + + act.set_to_null(); +} + +bool disable_activity_actor::can_disable_or_reprogram( const monster &monster ) +{ + if( get_avatar().get_skill_level( skill_electronics ) + get_avatar().get_skill_level( + skill_mechanics ) <= 0 ) { + return false; + } + + return ( ( monster.friendly != 0 || monster.has_effect( effect_sensor_stun ) ) && + !monster.has_flag( MF_RIDEABLE_MECH ) && + !( monster.has_flag( MF_PAY_BOT ) && monster.has_effect( efftype_id( "paid" ) ) ) ) && + ( !monster.type->revert_to_itype.is_empty() || monster.type->id == mon_manhack ); +} + +int disable_activity_actor::get_disable_turns() +{ + return 2000 / ( get_avatar().get_skill_level( skill_electronics ) + get_avatar().get_skill_level( + skill_mechanics ) ); +} + +void disable_activity_actor::serialize( JsonOut &jsout ) const +{ + jsout.start_object(); + jsout.member( "target", target ); + jsout.member( "reprogram", reprogram ); + jsout.member( "moves_total", moves_total ); + jsout.end_object(); +} + +std::unique_ptr disable_activity_actor::deserialize( JsonIn &jsin ) +{ + disable_activity_actor actor = disable_activity_actor(); + + JsonObject data = jsin.get_object(); + + data.read( "target", actor.target ); + data.read( "reprogram", actor.reprogram ); + + return actor.clone(); +} + void move_furniture_activity_actor::start( player_activity &act, Character & ) { int moves = g->grabbed_furn_move_time( dp ); @@ -3243,6 +3351,7 @@ deserialize_functions = { { activity_id( "ACT_CRAFT" ), &craft_activity_actor::deserialize }, { activity_id( "ACT_DIG" ), &dig_activity_actor::deserialize }, { activity_id( "ACT_DIG_CHANNEL" ), &dig_channel_activity_actor::deserialize }, + { activity_id( "ACT_DISABLE" ), &disable_activity_actor::deserialize }, { activity_id( "ACT_DISASSEMBLE" ), &disassemble_activity_actor::deserialize }, { activity_id( "ACT_DROP" ), &drop_activity_actor::deserialize }, { activity_id( "ACT_GUNMOD_REMOVE" ), &gunmod_remove_activity_actor::deserialize }, diff --git a/src/activity_actor.h b/src/activity_actor.h index 102f8a92b7073..d7e48f56746aa 100644 --- a/src/activity_actor.h +++ b/src/activity_actor.h @@ -8,11 +8,13 @@ #include "activity_type.h" #include "clone_ptr.h" +#include "point.h" #include "type_id.h" class Character; class JsonIn; class JsonOut; +class monster; class player_activity; class activity_actor @@ -117,6 +119,39 @@ class activity_actor void serialize( const cata::clone_ptr &actor, JsonOut &jsout ); void deserialize( cata::clone_ptr &actor, JsonIn &jsin ); +class disable_activity_actor : public activity_actor +{ + public: + disable_activity_actor() = default; + disable_activity_actor( const tripoint &target, int moves_total, + bool reprogram ) : target( target ), moves_total( moves_total ), reprogram( reprogram ) {} + + activity_id get_type() const override { + return activity_id( "ACT_DISABLE" ); + } + + void start( player_activity &act, Character &who ) override; + void do_turn( player_activity & /*&act*/, Character &who ) 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 ); + + /** Returns whether the given monster is a robot and can currently be disabled or reprogrammed */ + static bool can_disable_or_reprogram( const monster &monster ); + + static int get_disable_turns(); + + private: + tripoint target; + int moves_total; + bool reprogram; +}; + namespace activity_actors { diff --git a/src/effect.cpp b/src/effect.cpp index 8b8bd42eac25a..0a8ebcfc3e57c 100644 --- a/src/effect.cpp +++ b/src/effect.cpp @@ -34,6 +34,7 @@ static const efftype_id effect_lightsnare( "lightsnare" ); static const efftype_id effect_tied( "tied" ); static const efftype_id effect_webbed( "webbed" ); static const efftype_id effect_weed_high( "weed_high" ); +static const efftype_id effect_worked_on( "worked_on" ); static const itype_id itype_holybook_bible( "holybook_bible" ); static const itype_id itype_money_bundle( "money_bundle" ); @@ -1354,6 +1355,7 @@ static const std::unordered_set hardcoded_movement_impairing = {{ effect_lightsnare, effect_tied, effect_webbed, + effect_worked_on, } }; diff --git a/src/game.cpp b/src/game.cpp index af1b2f02ed708..013226198fca8 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -221,13 +221,11 @@ static const efftype_id effect_laserlocked( "laserlocked" ); static const efftype_id effect_no_sight( "no_sight" ); static const efftype_id effect_npc_suspend( "npc_suspend" ); static const efftype_id effect_onfire( "onfire" ); -static const efftype_id effect_paid( "paid" ); static const efftype_id effect_pet( "pet" ); static const efftype_id effect_ridden( "ridden" ); static const efftype_id effect_riding( "riding" ); static const efftype_id effect_sleep( "sleep" ); static const efftype_id effect_stunned( "stunned" ); -static const efftype_id effect_sensor_stun( "sensor_stun" ); static const efftype_id effect_tetanus( "tetanus" ); static const efftype_id effect_tied( "tied" ); static const efftype_id effect_winded( "winded" ); @@ -9512,27 +9510,17 @@ bool game::disable_robot( const tripoint &p ) return false; } monster &critter = *mon_ptr; - if( ( critter.friendly == 0 && !critter.has_effect( effect_sensor_stun ) ) || - critter.has_flag( MF_RIDEABLE_MECH ) || - ( critter.has_flag( MF_PAY_BOT ) && critter.has_effect( effect_paid ) ) ) { - // Can only disable / reprogram friendly or stunned monsters + if( !disable_activity_actor::can_disable_or_reprogram( critter ) ) { return false; } + const mtype_id mid = critter.type->id; const itype_id mon_item_id = critter.type->revert_to_itype; if( !mon_item_id.is_empty() && query_yn( _( "Deactivate the %s?" ), critter.name() ) ) { - u.moves -= 100; - m.add_item_or_charges( p, critter.to_item() ); - if( !critter.has_flag( MF_INTERIOR_AMMO ) ) { - for( std::pair &ammodef : critter.ammo ) { - if( ammodef.second > 0 ) { - m.spawn_item( p.xy(), ammodef.first, 1, ammodef.second, calendar::turn ); - } - } - } - remove_zombie( critter ); + u.assign_activity( player_activity( disable_activity_actor( p, + disable_activity_actor::get_disable_turns(), false ) ) ); return true; } // Manhacks are special, they have their own menu here. @@ -9543,25 +9531,10 @@ bool game::disable_robot( const tripoint &p ) } else { choice = uilist( _( "Reprogram the manhack?" ), { _( "Follow me." ) } ); } - switch( choice ) { - case 0: - if( critter.has_effect( effect_docile ) ) { - critter.remove_effect( effect_docile ); - if( one_in( 3 ) ) { - add_msg( _( "The %s hovers momentarily as it surveys the area." ), - critter.name() ); - } - } else { - critter.add_effect( effect_docile, 1_turns, true ); - if( one_in( 3 ) ) { - add_msg( _( "The %s lets out a whirring noise and starts to follow you." ), - critter.name() ); - } - } - u.moves -= 100; - return true; - default: - break; + + if( choice == 0 ) { + u.assign_activity( player_activity( disable_activity_actor( p, + disable_activity_actor::get_disable_turns(), true ) ) ); } } return false; diff --git a/src/monster.cpp b/src/monster.cpp index 381a39eee428a..318a55fbd7d03 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -82,6 +82,7 @@ static const efftype_id effect_hit_by_player( "hit_by_player" ); static const efftype_id effect_in_pit( "in_pit" ); static const efftype_id effect_lightsnare( "lightsnare" ); static const efftype_id effect_monster_armor( "monster_armor" ); +static const efftype_id effect_natures_commune( "natures_commune" ); static const efftype_id effect_no_sight( "no_sight" ); static const efftype_id effect_onfire( "onfire" ); static const efftype_id effect_pacified( "pacified" ); @@ -98,7 +99,7 @@ static const efftype_id effect_venom_player1( "venom_player1" ); static const efftype_id effect_venom_player2( "venom_player2" ); static const efftype_id effect_venom_weaken( "venom_weaken" ); static const efftype_id effect_webbed( "webbed" ); -static const efftype_id effect_natures_commune( "natures_commune" ); +static const efftype_id effect_worked_on( "worked_on" ); static const itype_id itype_corpse( "corpse" ); static const itype_id itype_milk( "milk" ); @@ -1924,6 +1925,9 @@ bool monster::move_effects( bool ) } return false; } + if( has_effect( effect_worked_on ) ) { + return false; + } // If we ever get more effects that force movement on success this will need to be reworked to // only trigger success effects if /all/ rolls succeed