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 17:07:17 -06:00
committed by GitHub
parent 23b09f8a14
commit ed2f04f486
92 changed files with 688 additions and 592 deletions
@@ -118,8 +118,9 @@
teleport = new(src)
teleport.Grant(src)
ai_controller.blackboard[BB_CARP_RIFT] = WEAKREF(teleport)
ai_controller.blackboard[BB_OBSTACLE_TARGETTING_WHITELIST] = allowed_obstacle_targets
ai_controller.set_blackboard_key(BB_CARP_RIFT, teleport)
ai_controller.set_blackboard_key(BB_OBSTACLE_TARGETTING_WHITELIST, allowed_obstacle_targets)
/mob/living/basic/carp/Destroy()
QDEL_NULL(teleport)
@@ -129,7 +130,7 @@
/mob/living/basic/carp/proc/setup_eating()
AddElement(/datum/element/basic_eating, 10, 0, null, desired_food)
AddElement(/datum/element/basic_eating, 0, 10, BRUTE, desired_trash) // We are killing our planet
ai_controller.blackboard[BB_BASIC_FOODS] = desired_food + desired_trash
ai_controller.set_blackboard_key(BB_BASIC_FOODS, desired_food + desired_trash)
/// Set a random colour on the carp, override to do something else
/mob/living/basic/carp/proc/apply_colour()
@@ -151,9 +152,16 @@
/mob/living/basic/carp/ranged_secondary_attack(atom/atom_target, modifiers)
teleport.Trigger(target = atom_target)
/// Gives the carp a list of destinations to try and travel between when it has nothing better to do
/mob/living/basic/carp/proc/migrate_to(list/migration_points)
ai_controller.blackboard[BB_CARP_MIGRATION_PATH] = migration_points
/// Gives the carp a list of weakrefs of destinations to try and travel between when it has nothing better to do
/mob/living/basic/carp/proc/migrate_to(list/datum/weakref/migration_points)
var/list/actual_points = list()
for(var/datum/weakref/point_ref as anything in migration_points)
var/turf/point_resolved = point_ref.resolve()
if(QDELETED(point_resolved))
return // invalid list, we can't migrate to this
actual_points += point_resolved
ai_controller.set_blackboard_key(BB_CARP_MIGRATION_PATH, actual_points)
/**
* Holographic carp from the holodeck
@@ -42,8 +42,7 @@
/datum/ai_planning_subtree/find_nearest_magicarp_spell_target
/datum/ai_planning_subtree/find_nearest_magicarp_spell_target/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/weakref/weak_action = controller.blackboard[BB_MAGICARP_SPELL]
var/datum/action/cooldown/using_action = weak_action?.resolve()
var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL]
if (QDELETED(using_action))
return
if (!using_action.IsAvailable())
@@ -22,8 +22,7 @@
if (!length(migration_points))
return
var/datum/weakref/weak_target = controller.blackboard[BB_CARP_MIGRATION_TARGET]
var/turf/moving_to = weak_target?.resolve()
var/turf/moving_to = controller.blackboard[BB_CARP_MIGRATION_TARGET]
// If we don't have a target or are close enough to it, pick a new one
if (isnull(moving_to) || get_dist(controller.pawn, moving_to) <= CARP_DESTINATION_SEARCH_RANGE)
@@ -48,13 +47,12 @@
var/list/blackboard_points = controller.blackboard[path_key]
var/list/potential_migration_points = blackboard_points.Copy()
while (length(potential_migration_points))
var/datum/weakref/weak_destination = popleft(potential_migration_points)
var/turf/potential_destination = weak_destination.resolve()
var/turf/potential_destination = popleft(potential_migration_points)
if (!isnull(potential_destination) && get_dist(controller.pawn, potential_destination) > CARP_DESTINATION_SEARCH_RANGE)
controller.blackboard[target_key] = weak_destination
controller.set_blackboard_key(target_key, potential_destination)
finish_action(controller, succeeded = TRUE)
return
controller.blackboard[path_key] = potential_migration_points.Copy()
controller.set_blackboard_key(path_key, potential_migration_points.Copy())
finish_action(controller, succeeded = FALSE)
@@ -12,14 +12,9 @@
if (!rift_behaviour)
CRASH("Forgot to specify rift behaviour for [src]")
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/mob/living/target = weak_target?.resolve()
if (!target)
return
var/datum/weakref/weak_action = controller.blackboard[BB_CARP_RIFT]
var/datum/action/cooldown/using_action = weak_action?.resolve()
if (isnull(using_action) || !using_action.IsAvailable())
var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/datum/action/cooldown/using_action = controller.blackboard[BB_CARP_RIFT]
if (QDELETED(target) || QDELETED(using_action) || !using_action.IsAvailable())
return
controller.queue_behavior(rift_behaviour, BB_CARP_RIFT, BB_BASIC_MOB_CURRENT_TARGET)
@@ -53,20 +48,12 @@
/datum/ai_behavior/make_carp_rift
/datum/ai_behavior/make_carp_rift/setup(datum/ai_controller/controller, ability_key, target_key)
var/datum/weakref/weak_action = controller.blackboard[ability_key]
var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability = weak_action?.resolve()
if (!ability)
return FALSE
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
return target
return controller.blackboard[ability_key] && controller.blackboard[target_key]
/datum/ai_behavior/make_carp_rift/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key)
. = ..()
var/datum/weakref/weak_action = controller.blackboard[ability_key]
var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability = weak_action?.resolve()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/datum/action/cooldown/mob_cooldown/lesser_carp_rift/ability = controller.blackboard[ability_key]
var/atom/target = controller.blackboard[target_key]
if (!validate_target(controller, target, ability))
finish_action(controller, FALSE, ability_key, target_key)
@@ -187,9 +174,8 @@
var/minimum_distance = 2
/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/mob/living/target = weak_target?.resolve()
if (isnull(target))
var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if (QDELETED(target))
return
var/distance_to_target = get_dist(controller.pawn, target)
@@ -88,7 +88,7 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list(
spell.projectile_type = spell_type
spell.button_icon_state = initial(spell_type.icon_state)
spell.Grant(src)
ai_controller.blackboard[BB_MAGICARP_SPELL] = WEAKREF(spell)
ai_controller.set_blackboard_key(BB_MAGICARP_SPELL, spell)
assign_spell_ai(spell_type)
/// If you have certain spells, use a different targetting datum
@@ -99,7 +99,7 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list(
/obj/projectile/magic/resurrection = MAGICARP_SPELL_CORPSES,
)
ai_controller.blackboard[BB_MAGICARP_SPELL_SPECIAL_TARGETTING] = spell_special_targetting[spell_type]
ai_controller.set_blackboard_key(BB_MAGICARP_SPELL_SPECIAL_TARGETTING, spell_special_targetting[spell_type])
/// Shoot when you click away from you
/mob/living/basic/carp/magic/RangedAttack(atom/atom_target, modifiers)
@@ -123,7 +123,7 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list(
chaos_bolt.permitted_projectiles = allowed_projectile_types
chaos_bolt.Grant(src)
spell = chaos_bolt
ai_controller.blackboard[BB_MAGICARP_SPELL] = spell
ai_controller.set_blackboard_key(BB_MAGICARP_SPELL, spell)
RegisterSignal(spell, COMSIG_ACTION_TRIGGER, PROC_REF(apply_colour))
/// Has a more limited spell pool but can appear from gold slime cores