Experiment with replacing weakrefs in AI blackboard with deleting signals, ideally making it easier to work with and harder to cause hard deletes (#74791)

## About The Pull Request

Replaces weakref usage in AI blackboards with deleting signals

All blackboard var setting must go through setters rather than directly

## Why It's Good For The Game

This both makes it a ton easier to develop AI for, and also makes it
harder for hard deletes to sneak in, as has been seen with recent 515
prs showing hard deletes in AI blackboards

(To quantify "making it easier to develop AI", I found multiple bugs in
existing AI code due to the usage of weakrefs.)

I'm looking for `@Jacquerel` `@tralezab` 's opinions on the matter, also
maybe `@LemonInTheDark` if they're interested

## Changelog

🆑 Melbert
refactor: Mob ai refactored once again
/🆑
This commit is contained in:
MrMelbert
2023-04-23 18:07:17 -05:00
committed by GitHub
parent 23b09f8a14
commit ed2f04f486
92 changed files with 688 additions and 592 deletions
@@ -25,9 +25,6 @@
if (!victim.ai_controller)
return
var/list/enemy_refs = victim.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST]
if (!enemy_refs)
enemy_refs = list()
enemy_refs |= WEAKREF(attacker)
victim.ai_controller.blackboard[BB_BASIC_MOB_RETALIATE_LIST] = enemy_refs
victim.ai_controller.insert_blackboard_key_lazylist(BB_BASIC_MOB_RETALIATE_LIST, attacker)
post_retaliate_callback?.InvokeAsync(attacker)
+11 -14
View File
@@ -39,7 +39,7 @@
var/list/blackboard = parent.ai_controller.blackboard
if (!trigger_on_throw && !blackboard[BB_ACTIVE_PET_COMMAND])
return // We don't have a command and we only listen to commands
if (blackboard[BB_ACTIVE_PET_COMMAND] && blackboard[BB_ACTIVE_PET_COMMAND] != WEAKREF(src))
if (blackboard[BB_ACTIVE_PET_COMMAND] && blackboard[BB_ACTIVE_PET_COMMAND] != src)
return // We have a command and it's not this one
if (blackboard[BB_CURRENT_PET_TARGET] || blackboard[BB_FETCH_DELIVER_TO])
return // We're already very fetching
@@ -49,7 +49,7 @@
var/obj/item/thrown_thing = thrower.get_active_held_item()
if (!isitem(thrown_thing))
return
if (blackboard[BB_FETCH_IGNORE_LIST] && blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(thrown_thing)])
if (blackboard[BB_FETCH_IGNORE_LIST]?[thrown_thing])
return // We're ignoring it already
RegisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(listen_throw_land))
@@ -69,7 +69,7 @@
try_activate_command(throwing_datum.thrower)
set_command_target(parent, thrown_thing)
parent.ai_controller.blackboard[BB_FETCH_DELIVER_TO] = WEAKREF(throwing_datum.thrower)
parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, throwing_datum.thrower)
// Don't try and fetch turfs or anchored objects if someone points at them
/datum/pet_command/point_targetting/fetch/look_for_target(mob/living/pointing_friend, obj/item/pointed_atom)
@@ -82,16 +82,15 @@
return FALSE
var/mob/living/parent = weak_parent.resolve()
parent.ai_controller.blackboard[BB_FETCH_DELIVER_TO] = WEAKREF(pointing_friend)
parent.ai_controller.set_blackboard_key(BB_FETCH_DELIVER_TO, pointing_friend)
// Finally, plan our actions
/datum/pet_command/point_targetting/fetch/execute_action(datum/ai_controller/controller)
controller.queue_behavior(/datum/ai_behavior/forget_failed_fetches)
var/datum/weakref/weak_target = controller.blackboard[BB_CURRENT_PET_TARGET]
var/atom/target = weak_target?.resolve()
var/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET]
// We got something to fetch so go fetch it
if (target)
if (!QDELETED(target))
if (get_dist(controller.pawn, target) > 1) // We're not there yet
controller.queue_behavior(/datum/ai_behavior/fetch_seek, BB_CURRENT_PET_TARGET, BB_FETCH_DELIVER_TO)
return SUBTREE_RETURN_FINISH_PLANNING
@@ -99,16 +98,14 @@
controller.queue_behavior(/datum/ai_behavior/pick_up_item, BB_CURRENT_PET_TARGET, BB_SIMPLE_CARRY_ITEM)
return SUBTREE_RETURN_FINISH_PLANNING
var/datum/weakref/carried_ref = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
var/obj/item/carried_item = carried_ref?.resolve()
if (!carried_item)
var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
if (QDELETED(carried_item))
return
var/datum/weakref/delivery_ref = controller.blackboard[BB_FETCH_DELIVER_TO]
var/atom/delivery_target = delivery_ref?.resolve()
if (!delivery_target || !can_see(controller.pawn, delivery_target, sense_radius))
var/atom/delivery_target = controller.blackboard[BB_FETCH_DELIVER_TO]
if (QDELETED(delivery_target) || !can_see(controller.pawn, delivery_target, sense_radius))
// We don't know where to return this to so we're just going to keep it
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
return
// We got something to deliver and someone to deliver it to
@@ -57,10 +57,7 @@
if (IS_DEAD_OR_INCAP(source))
return
if (!source.ai_controller)
return
var/list/friends_list = source.ai_controller.blackboard[BB_FRIENDS_LIST]
if (!friends_list || !friends_list[WEAKREF(user)])
if (!(user in source.ai_controller?.blackboard[BB_FRIENDS_LIST]))
return
examine_list += span_notice("[source.p_they(capitalized = TRUE)] seem[source.p_s()] happy to see you!")
@@ -71,10 +68,7 @@
var/mob/living/living_parent = parent
if (IS_DEAD_OR_INCAP(living_parent))
return
if (!living_parent.ai_controller)
return
var/list/friends_list = living_parent.ai_controller.blackboard[BB_FRIENDS_LIST]
if (!friends_list || !friends_list[WEAKREF(clicker)])
if (!(clicker in living_parent.ai_controller?.blackboard[BB_FRIENDS_LIST]))
return // Not our friend, can't boss us around
INVOKE_ASYNC(src, PROC_REF(display_radial_menu), clicker)
@@ -68,7 +68,7 @@
return
if (IS_DEAD_OR_INCAP(parent)) // Probably can't hear them if we're dead
return
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] == WEAKREF(src)) // We're already doing it
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] == src) // We're already doing it
return
set_command_active(parent, commander)
@@ -77,13 +77,13 @@
set_command_target(parent, null)
parent.ai_controller.CancelActions() // Stop whatever you're doing and do this instead
parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] = WEAKREF(src)
parent.ai_controller.set_blackboard_key(BB_ACTIVE_PET_COMMAND, src)
if (command_feedback)
parent.balloon_alert_to_viewers("[command_feedback]") // If we get a nicer runechat way to do this, refactor this
/// Store the target for the AI blackboard
/datum/pet_command/proc/set_command_target(mob/living/parent, atom/target)
parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] = WEAKREF(target)
parent.ai_controller.set_blackboard_key(BB_CURRENT_PET_TARGET, target)
/// Provide information about how to display this command in a radial menu
/datum/pet_command/proc/provide_radial_data()
@@ -137,9 +137,9 @@
return FALSE
if (IS_DEAD_OR_INCAP(parent))
return FALSE
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != WEAKREF(src)) // We're not listening right now
if (parent.ai_controller.blackboard[BB_ACTIVE_PET_COMMAND] != src) // We're not listening right now
return FALSE
if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == WEAKREF(pointed_atom)) // That's already our target
if (parent.ai_controller.blackboard[BB_CURRENT_PET_TARGET] == pointed_atom) // That's already our target
return FALSE
if (!can_see(parent, pointed_atom, sense_radius))
return FALSE
@@ -28,7 +28,7 @@
command_feedback = "relaxes"
/datum/pet_command/free/execute_action(datum/ai_controller/controller)
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
return // Just move on to the next planning subtree.
/**
@@ -87,7 +87,7 @@
// If we get past this point someone has finally added a non-binary dog
/datum/pet_command/good_boy/execute_action(datum/ai_controller/controller)
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
var/mob/living/parent = weak_parent.resolve()
if (!parent)
return SUBTREE_RETURN_FINISH_PLANNING