From 18047d74fe43c0162f11788d2c9f4f65e2127234 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sun, 10 Dec 2023 20:22:56 -0500 Subject: [PATCH] Fixes a bunch of AI related CI runtimes (#80202) ## About The Pull Request
A bunch of the numerous CI issues ![image](https://github.com/tgstation/tgstation/assets/13398309/70b0419e-0ac4-4a59-8acb-02511f8d6987) ![image](https://github.com/tgstation/tgstation/assets/13398309/4303923d-aaea-438f-9eb2-d27b510c7bc6)
You can view the full list of them here https://github.com/Skyrat-SS13/Skyrat-tg/actions/runs/7148986054/job/19470671408. What seems to be happening is, the `ai_controller` `fire()`s, and at some point the the `pawn` var has become null from qdeletion. Many of the `SelectBehaviors()` procs make use of that var, and then try to access it without any safeties whatsoever. I believe it is mainly happening because of long `do_after()`s and other procs that sleep. This PR just adds those safeties. I probably didn't get them all, but this should fix the ones I have seen in CI. There may be a better solution to cover all future cases of this but I will wait on feedback to proceed. See below comments: --- I don't know if you would rather this to always be checked at the controller level instead (or in `able_to_plan()` perhaps?) but I could do that if it's wanted. I wasn't sure if there were certain things that depended on `SelectBehaviors()` running for cleanup so I opted against that. On that note, shouldn't we just be qdeleting the `ai_controller` when the pawn gets qdeleted? Is that not already happening? And if not, is there a reason for it? That would probably be the best way to handle it... ## Why It's Good For The Game I would like to stop seeing so many random CI failures, wouldn't you? ## Changelog :cl: fix: fixes some AI runtimes that were caused by the pawn becoming null /:cl: --- .../basic_subtrees/opportunistic_ventcrawler.dm | 2 +- code/datums/ai/generic/generic_subtrees.dm | 2 ++ code/datums/ai/hunting_behavior/hunting_behaviors.dm | 2 +- code/datums/ai/monkey/monkey_controller.dm | 2 ++ code/datums/ai/monkey/monkey_subtrees.dm | 3 +++ code/modules/mob/living/basic/bots/bot_ai.dm | 4 ++-- .../mob/living/basic/bots/cleanbot/cleanbot_ai.dm | 8 ++++---- .../modules/mob/living/basic/bots/medbot/medbot_ai.dm | 11 ++++++----- code/modules/mob/living/basic/pets/cat/cat_ai.dm | 6 +++++- .../basic/pets/parrot/parrot_ai/parrot_perching.dm | 7 +++++-- 10 files changed, 31 insertions(+), 16 deletions(-) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm index 240272d1ef4..8d2dd2443de 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/opportunistic_ventcrawler.dm @@ -2,7 +2,7 @@ /datum/ai_planning_subtree/opportunistic_ventcrawler /datum/ai_planning_subtree/opportunistic_ventcrawler/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - if(HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING)) + if(QDELETED(controller.pawn) || HAS_TRAIT(controller.pawn, TRAIT_MOVE_VENTCRAWLING)) return SUBTREE_RETURN_FINISH_PLANNING // hold on let me cook var/obj/machinery/atmospherics/components/unary/vent_pump/target = controller.blackboard[BB_ENTRY_VENT_TARGET] diff --git a/code/datums/ai/generic/generic_subtrees.dm b/code/datums/ai/generic/generic_subtrees.dm index 12007a26124..c33091a64d4 100644 --- a/code/datums/ai/generic/generic_subtrees.dm +++ b/code/datums/ai/generic/generic_subtrees.dm @@ -32,6 +32,8 @@ */ /datum/ai_planning_subtree/generic_resist/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/living_pawn = controller.pawn + if(QDELETED(living_pawn)) + return if(SHOULD_RESIST(living_pawn) && SPT_PROB(RESIST_SUBTREE_PROB, seconds_per_tick)) controller.queue_behavior(/datum/ai_behavior/resist) //BRO IM ON FUCKING FIRE BRO diff --git a/code/datums/ai/hunting_behavior/hunting_behaviors.dm b/code/datums/ai/hunting_behavior/hunting_behaviors.dm index 3e747640be3..f3ffa8c130b 100644 --- a/code/datums/ai/hunting_behavior/hunting_behaviors.dm +++ b/code/datums/ai/hunting_behavior/hunting_behaviors.dm @@ -30,7 +30,7 @@ return var/mob/living/living_pawn = controller.pawn // We can't hunt if we're indisposed - if(HAS_TRAIT(controller.pawn, TRAIT_HANDS_BLOCKED) || living_pawn.stat != CONSCIOUS) + if(QDELETED(living_pawn) || HAS_TRAIT(controller.pawn, TRAIT_HANDS_BLOCKED) || living_pawn.stat != CONSCIOUS) return var/atom/hunted = controller.blackboard[target_key] diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 951147007f9..4e2f0a90b1e 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -97,6 +97,8 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey/proc/set_trip_mode(mode = TRUE) var/mob/living/carbon/regressed_monkey = pawn + if(QDELETED(regressed_monkey)) + return var/brain = regressed_monkey.get_organ_slot(ORGAN_SLOT_BRAIN) if(istype(brain, /obj/item/organ/internal/brain/primate)) // In case we are a monkey AI in a human brain by who was previously controlled by a client but it now not by some marvel var/obj/item/organ/internal/brain/primate/monkeybrain = brain diff --git a/code/datums/ai/monkey/monkey_subtrees.dm b/code/datums/ai/monkey/monkey_subtrees.dm index 572eeb6b56e..75d2a3eb3a5 100644 --- a/code/datums/ai/monkey/monkey_subtrees.dm +++ b/code/datums/ai/monkey/monkey_subtrees.dm @@ -26,6 +26,9 @@ ///monkey combat subtree. /datum/ai_planning_subtree/monkey_combat/SelectBehaviors(datum/ai_controller/monkey/controller, seconds_per_tick) var/mob/living/living_pawn = controller.pawn + if(QDELETED(living_pawn)) + return + var/list/enemies = controller.blackboard[BB_MONKEY_ENEMIES] if((HAS_TRAIT(controller.pawn, TRAIT_PACIFISM)) || (!length(enemies) && !controller.blackboard[BB_MONKEY_AGGRESSIVE])) //Pacifist, or we have no enemies and we're not pissed diff --git a/code/modules/mob/living/basic/bots/bot_ai.dm b/code/modules/mob/living/basic/bots/bot_ai.dm index b6ea8f4519c..5fe2678dfb7 100644 --- a/code/modules/mob/living/basic/bots/bot_ai.dm +++ b/code/modules/mob/living/basic/bots/bot_ai.dm @@ -95,7 +95,7 @@ /datum/ai_planning_subtree/find_patrol_beacon/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/basic/bot/bot_pawn = controller.pawn - if(!(bot_pawn.bot_mode_flags & BOT_MODE_AUTOPATROL) || bot_pawn.mode == BOT_SUMMON) + if(QDELETED(bot_pawn) || !(bot_pawn.bot_mode_flags & BOT_MODE_AUTOPATROL) || bot_pawn.mode == BOT_SUMMON) return if(controller.blackboard_key_exists(BB_BEACON_TARGET)) @@ -186,7 +186,7 @@ /datum/ai_planning_subtree/salute_authority/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/basic/bot/bot_pawn = controller.pawn //we are criminals, dont salute the dirty pigs - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + if(QDELETED(bot_pawn) || bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) return if(controller.blackboard_key_exists(BB_SALUTE_TARGET)) controller.queue_behavior(/datum/ai_behavior/salute_authority, BB_SALUTE_TARGET, BB_SALUTE_MESSAGES) diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index 22ec3a15cd9..a5daba20367 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -41,7 +41,7 @@ /datum/ai_planning_subtree/pet_planning/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) var/mob/living/basic/bot/bot_pawn = controller.pawn //we are DONE listening to orders - if(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) + if(QDELETED(bot_pawn) || bot_pawn.bot_access_flags & BOT_COVER_EMAGGED) return return ..() @@ -51,7 +51,7 @@ /datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick) var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn - if(LAZYLEN(bot_pawn.do_afters)) + if(QDELETED(bot_pawn) || LAZYLEN(bot_pawn.do_afters)) return SUBTREE_RETURN_FINISH_PLANNING if(controller.reachable_key(BB_CLEAN_TARGET, BOT_CLEAN_PATH_LIMIT)) @@ -85,7 +85,7 @@ /datum/ai_planning_subtree/acid_spray/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn - if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) + if(QDELETED(bot_pawn) || !(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) return if(controller.reachable_key(BB_ACID_SPRAY_TARGET, BOT_CLEAN_PATH_LIMIT)) controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_ACID_SPRAY_TARGET) @@ -150,7 +150,7 @@ /datum/ai_planning_subtree/use_mob_ability/foam_area/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) var/mob/living/basic/bot/bot_pawn = controller.pawn - if(!(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) + if(QDELETED(bot_pawn) || !(bot_pawn.bot_access_flags & BOT_COVER_EMAGGED)) return return ..() diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm index 7b09493bca3..4c31567f9e0 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -23,7 +23,7 @@ /datum/ai_movement/jps/bot/medbot/allowed_to_move(datum/move_loop/source) var/datum/ai_controller/controller = source.extra_info var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) + if(QDELETED(bot_pawn) || bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) return FALSE return ..() @@ -32,7 +32,7 @@ /datum/ai_planning_subtree/treat_wounded_target/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE) + if(QDELETED(bot_pawn) || bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE) controller.clear_blackboard_key(BB_PATIENT_TARGET) return var/reach_distance = (bot_pawn.medical_mode_flags & MEDBOT_STATIONARY_MODE) ? 1 : BOT_PATIENT_PATH_LIMIT @@ -98,7 +98,8 @@ if(patient.stat >= HARD_CRIT && prob(5)) var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] announcement?.announce(pick(controller.blackboard[BB_NEAR_DEATH_SPEECH])) - bot_pawn.melee_attack(patient) + if(!QDELETED(bot_pawn)) + bot_pawn.melee_attack(patient) finish_action(controller, TRUE, target_key) // only clear the target if they get healed @@ -126,7 +127,7 @@ /datum/ai_planning_subtree/handle_medbot_speech/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn //we cant speak! - if(!(bot_pawn.medical_mode_flags & MEDBOT_SPEAK_MODE)) + if(QDELETED(bot_pawn) || !(bot_pawn.medical_mode_flags & MEDBOT_SPEAK_MODE)) return var/currently_tipped = bot_pawn.medical_mode_flags & MEDBOT_TIPPED_MODE @@ -173,7 +174,7 @@ /datum/ai_planning_subtree/find_and_hunt_target/patients_in_crit/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) var/mob/living/basic/bot/medbot/bot_pawn = controller.pawn - if(!(bot_pawn.medical_mode_flags & MEDBOT_DECLARE_CRIT)) + if(QDELETED(bot_pawn) || !(bot_pawn.medical_mode_flags & MEDBOT_DECLARE_CRIT)) return return ..() diff --git a/code/modules/mob/living/basic/pets/cat/cat_ai.dm b/code/modules/mob/living/basic/pets/cat/cat_ai.dm index b9436b555e5..c3660d87f05 100644 --- a/code/modules/mob/living/basic/pets/cat/cat_ai.dm +++ b/code/modules/mob/living/basic/pets/cat/cat_ai.dm @@ -27,6 +27,8 @@ /datum/ai_planning_subtree/reside_in_home/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/living_pawn = controller.pawn + if(QDELETED(living_pawn)) + return if(controller.blackboard_key_exists(BB_CAT_HOME)) controller.queue_behavior(/datum/ai_behavior/enter_cat_home, BB_CAT_HOME) @@ -86,7 +88,7 @@ /datum/ai_planning_subtree/territorial_struggle/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/living_pawn = controller.pawn - if(living_pawn.gender != MALE || !SPT_PROB(hostility_chance, seconds_per_tick)) + if(QDELETED(living_pawn) || living_pawn.gender != MALE || !SPT_PROB(hostility_chance, seconds_per_tick)) return if(controller.blackboard_key_exists(BB_TRESSPASSER_TARGET)) controller.queue_behavior(/datum/ai_behavior/territorial_struggle, BB_TRESSPASSER_TARGET, BB_HOSTILE_MEOWS) @@ -165,6 +167,8 @@ /datum/ai_planning_subtree/find_and_hunt_target/hunt_mice/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/living_pawn = controller.pawn + if(QDELETED(living_pawn)) + return var/list/items_we_carry = typecache_filter_list(living_pawn, controller.blackboard[BB_HUNTABLE_PREY]) if(length(items_we_carry)) return diff --git a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm index 1bb5c6fc960..847d123f767 100644 --- a/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm +++ b/code/modules/mob/living/basic/pets/parrot/parrot_ai/parrot_perching.dm @@ -8,10 +8,13 @@ /datum/ai_planning_subtree/perch_on_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/mob/living/living_pawn = controller.pawn - var/atom/buckled_too = living_pawn.buckled + if(QDELETED(living_pawn)) + return + + var/atom/buckled_to = living_pawn.buckled //do we have a current target or is chance to unbuckle has passed? then unbuckle! - if(buckled_too) + if(buckled_to) if((SPT_PROB(unperch_chance, seconds_per_tick) || controller.blackboard_key_exists(BB_BASIC_MOB_CURRENT_TARGET))) controller.queue_behavior(/datum/ai_behavior/unbuckle_mob) return