Skip to content

Commit

Permalink
Don't lose your heads over infinite loops (#35808)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidpwbrown authored and ZhilkinSerg committed Dec 2, 2019
1 parent c238070 commit d27f54c
Show file tree
Hide file tree
Showing 9 changed files with 75 additions and 5 deletions.
6 changes: 6 additions & 0 deletions data/json/effects.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"name": [ "Ridden" ],
"desc": [ "AI tag for when critter is being ridden. This is a bug if you have it." ]
},
{
"type": "effect_type",
"id": "npc_suspend",
"name": [ "npc_suspend" ],
"desc": [ "AI tag for when an NPC needs to be rebooted after an infinite loop." ]
},
{
"type": "effect_type",
"id": "harnessed",
Expand Down
9 changes: 9 additions & 0 deletions data/json/npcs/TALK_COMMON_ALLY.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@
{ "text": "Go back to sleep.", "topic": "TALK_DONE" }
]
},
{
"id": "TALK_REBOOT",
"type": "talk_topic",
"dynamic_line": "This character was previously put into a suspension state due to an AI bug. If you have reason to believe the problem was fixed, you can wake them up. Do you want to do that now?",
"responses": [
{ "text": "yes, wake up!", "topic": "TALK_NONE", "effect": "wake_up" },
{ "text": "no, go back to sleep.", "topic": "TALK_DONE" }
]
},
{
"id": [ "TALK_FRIEND", "TALK_GIVE_ITEM", "TALK_USE_ITEM", "TALK_RADIO" ],
"type": "talk_topic",
Expand Down
4 changes: 3 additions & 1 deletion src/creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const efftype_id effect_blind( "blind" );
const efftype_id effect_bounced( "bounced" );
const efftype_id effect_downed( "downed" );
const efftype_id effect_onfire( "onfire" );
const efftype_id effect_npc_suspend( "npc_suspend" );
const efftype_id effect_sap( "sap" );
const efftype_id effect_sleep( "sleep" );
const efftype_id effect_stunned( "stunned" );
Expand Down Expand Up @@ -1301,7 +1302,8 @@ void Creature::set_moves( int nmoves )

bool Creature::in_sleep_state() const
{
return has_effect( effect_sleep ) || has_effect( effect_lying_down );
return has_effect( effect_sleep ) || has_effect( effect_lying_down ) ||
has_effect( effect_npc_suspend );
}

/*
Expand Down
11 changes: 7 additions & 4 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ const efftype_id effect_hot( "hot" );
const efftype_id effect_infected( "infected" );
const efftype_id effect_laserlocked( "laserlocked" );
const efftype_id effect_no_sight( "no_sight" );
const efftype_id effect_npc_suspend( "npc_suspend" );
const efftype_id effect_onfire( "onfire" );
const efftype_id effect_pacified( "pacified" );
const efftype_id effect_paid( "paid" );
Expand Down Expand Up @@ -4242,7 +4243,9 @@ void game::monmove()
for( npc &guy : g->all_npcs() ) {
int turns = 0;
m.creature_in_field( guy );
guy.process_turn();
if( !guy.has_effect( effect_npc_suspend ) ) {
guy.process_turn();
}
while( !guy.is_dead() && ( !guy.in_sleep_state() || guy.activity.id() == "ACT_OPERATION" ) &&
guy.moves > 0 && turns < 10 ) {
int moves = guy.moves;
Expand All @@ -4263,10 +4266,10 @@ void game::monmove()
}

// If we spun too long trying to decide what to do (without spending moves),
// Invoke cranial detonation to prevent an infinite loop.
// Invoke cognitive suspension to prevent an infinite loop.
if( turns == 10 ) {
add_msg( _( "%s's brain explodes!" ), guy.name );
guy.die( nullptr );
add_msg( _( "%s faints!" ), guy.name );
guy.reboot();
}

if( !guy.is_dead() ) {
Expand Down
40 changes: 40 additions & 0 deletions src/npc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ const efftype_id effect_high( "high" );
const efftype_id effect_pkill1( "pkill1" );
const efftype_id effect_pkill2( "pkill2" );
const efftype_id effect_pkill3( "pkill3" );
const efftype_id effect_npc_suspend( "npc_suspend" );
const efftype_id effect_pkill_l( "pkill_l" );
const efftype_id effect_infection( "infection" );
const efftype_id effect_bouldering( "bouldering" );
Expand Down Expand Up @@ -2375,6 +2376,45 @@ bool npc::is_dead() const
return dead || is_dead_state();
}

void npc::reboot()
{
//The NPC got into an infinite loop, in game.cpp -monmove() - a debugmsg just popped up
// informing player of this.
// put them to sleep and reboot their brain.
// they can be woken up by the player, and if their brain is fixed, great,
// if not, they will faint again, and the NPC can be kept asleep until the bug is fixed.
cancel_activity();
path.clear();
last_player_seen_pos = cata::nullopt;
last_seen_player_turn = 999;
wanted_item_pos = no_goal_point;
guard_pos = no_goal_point;
goal = no_goal_point;
fetching_item = false;
has_new_items = true;
worst_item_value = 0;
mission = NPC_MISSION_NULL;
patience = 0;
ai_cache.danger = 0;
ai_cache.total_danger = 0;
ai_cache.danger_assessment = 0;
ai_cache.target.reset();
ai_cache.ally.reset();
ai_cache.can_heal.clear_all();
ai_cache.sound_alerts.clear();
ai_cache.s_abs_pos = tripoint_zero;
ai_cache.stuck = 0;
ai_cache.guard_pos = cata::nullopt;
ai_cache.my_weapon_value = 0;
ai_cache.friends.clear();
ai_cache.dangerous_explosives.clear();
ai_cache.threat_map.clear();
ai_cache.searched_tiles.clear();
activity = player_activity();
clear_destination();
add_effect( effect_npc_suspend, 24_hours, num_bp, true, 1 );
}

void npc::die( Creature *nkiller )
{
if( dead ) {
Expand Down
1 change: 1 addition & 0 deletions src/npc.h
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,7 @@ class npc : public player
}
void say( const std::string &line, int priority = 0 ) const;
void decide_needs();
void reboot();
void die( Creature *killer ) override;
bool is_dead() const;
// How well we smash terrain (not corpses!)
Expand Down
3 changes: 3 additions & 0 deletions src/npcmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,9 @@ void npc::regen_ai_cache()

void npc::move()
{
// dont just return from this function without doing something
// that will eventually subtract moves, or change the NPC to a different type of action.
// because this will result in an infinite loop
if( attitude == NPCATT_FLEE ) {
set_attitude( NPCATT_FLEE_TEMP ); // Only run for so many hours
} else if( attitude == NPCATT_FLEE_TEMP && !has_effect( effect_npc_flee_player ) ) {
Expand Down
4 changes: 4 additions & 0 deletions src/npctalk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ const efftype_id effect_infected( "infected" );
const efftype_id effect_infection( "infection" );
const efftype_id effect_lying_down( "lying_down" );
const efftype_id effect_narcosis( "narcosis" );
const efftype_id effect_npc_suspend( "npc_suspend" );
const efftype_id effect_sleep( "sleep" );
const efftype_id effect_under_op( "under_operation" );
const efftype_id effect_riding( "riding" );
Expand Down Expand Up @@ -755,6 +756,9 @@ void npc::talk_to_u( bool text_only, bool radio_contact )
}

// Needs
if( has_effect( effect_npc_suspend ) ) {
d.add_topic( "TALK_REBOOT" );
}
if( has_effect( effect_sleep ) || has_effect( effect_lying_down ) ) {
if( has_effect( effect_narcosis ) ) {
d.add_topic( "TALK_SEDATED" );
Expand Down
2 changes: 2 additions & 0 deletions src/npctalk_funcs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ const efftype_id effect_bleed( "bleed" );
const efftype_id effect_currently_busy( "currently_busy" );
const efftype_id effect_infected( "infected" );
const efftype_id effect_lying_down( "lying_down" );
const efftype_id effect_npc_suspend( "npc_suspend" );
const efftype_id effect_sleep( "sleep" );
const efftype_id effect_pet( "pet" );
const efftype_id effect_controlled( "controlled" );
Expand Down Expand Up @@ -422,6 +423,7 @@ void talk_function::wake_up( npc &p )
p.rules.enable_override( ally_rule::allow_sleep );
p.remove_effect( effect_allow_sleep );
p.remove_effect( effect_lying_down );
p.remove_effect( effect_npc_suspend );
p.remove_effect( effect_sleep );
// TODO: Get mad at player for waking us up unless we're in danger
}
Expand Down

0 comments on commit d27f54c

Please sign in to comment.