Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#44459 make disabling a monster an activity with variable duration #44670

Merged
merged 10 commits into from
Jul 17, 2021
5 changes: 5 additions & 0 deletions data/json/effects.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@
},
{
"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." ]
},
{
"id": "venom_player1",
kevingranade marked this conversation as resolved.
Show resolved Hide resolved
"name": [ "Weak Player Venom" ],
"desc": [ "Don't worry, you shouldn't get this." ],
Expand Down
8 changes: 8 additions & 0 deletions data/json/player_activities.json
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,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",
Expand Down
110 changes: 110 additions & 0 deletions src/activity_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,11 @@
#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_sleep( "sleep" );
static const efftype_id effect_worked_on( "worked_on" );

static const efftype_id effect_tied( "tied" );

Expand All @@ -87,12 +90,18 @@ 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 std::string flag_MAG_DESTROY( "MAG_DESTROY" );
static const std::string flag_PERFECT_LOCKPICK( "PERFECT_LOCKPICK" );
static const std::string flag_RELOAD_AND_SHOOT( "RELOAD_AND_SHOOT" );
kevingranade marked this conversation as resolved.
Show resolved Hide resolved

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" );
Expand Down Expand Up @@ -2394,6 +2403,106 @@ std::unique_ptr<activity_actor> 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<monster>( 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<monster>( 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<monster>( 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<const itype_id, int> &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 || critter.has_effect( effect_sensor_stun ) ) &&
kevingranade marked this conversation as resolved.
Show resolved Hide resolved
!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<activity_actor> 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 );
kevingranade marked this conversation as resolved.
Show resolved Hide resolved
}

void move_furniture_activity_actor::start( player_activity &act, Character & )
{
int moves = g->grabbed_furn_move_time( dp );
Expand Down Expand Up @@ -2969,6 +3078,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 },
Expand Down
33 changes: 33 additions & 0 deletions src/activity_actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,39 @@ class activity_actor
void serialize( const cata::clone_ptr<activity_actor> &actor, JsonOut &jsout );
void deserialize( cata::clone_ptr<activity_actor> &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<activity_actor> clone() const override {
return std::make_unique<disable_activity_actor>( *this );
}

void serialize( JsonOut &jsout ) const override;
static std::unique_ptr<activity_actor> 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
{

Expand Down
2 changes: 2 additions & 0 deletions src/effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" );
Expand Down Expand Up @@ -1351,6 +1352,7 @@ static const std::unordered_set<efftype_id> hardcoded_movement_impairing = {{
effect_lightsnare,
effect_tied,
effect_webbed,
effect_worked_on,
}
};

Expand Down
41 changes: 8 additions & 33 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9455,27 +9455,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<const itype_id, int> &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.
Expand All @@ -9486,25 +9476,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;
Expand Down
4 changes: 4 additions & 0 deletions src/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,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_worked_on( "worked_on" );

static const itype_id itype_corpse( "corpse" );
static const itype_id itype_milk( "milk" );
Expand Down Expand Up @@ -1885,6 +1886,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
Expand Down