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
+1 -1
View File
@@ -10,7 +10,7 @@
. = ..()
var/find_this_thing = search_tactic(controller, locate_path, search_range)
if(find_this_thing)
controller.blackboard[set_key] = WEAKREF(find_this_thing)
controller.set_blackboard_key(set_key, find_this_thing)
finish_action(controller, TRUE)
else
finish_action(controller, FALSE)
+33 -35
View File
@@ -31,20 +31,25 @@
/datum/ai_behavior/break_spine/setup(datum/ai_controller/controller, target_key)
. = ..()
set_movement_target(controller, controller.blackboard[target_key])
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/break_spine/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/mob/living/batman = controller.blackboard[target_key]
var/mob/living/big_guy = controller.pawn //he was molded by the darkness
if(batman.stat)
finish_action(controller, TRUE, target_key)
if(get_dist(batman, big_guy) >= give_up_distance)
if(QDELETED(batman) || get_dist(batman, big_guy) >= give_up_distance)
finish_action(controller, FALSE, target_key)
return
if(batman.stat != CONSCIOUS)
finish_action(controller, TRUE, target_key)
return
big_guy.start_pulling(batman)
big_guy.setDir(get_dir(big_guy, batman))
big_guy.face_atom(batman)
batman.visible_message(span_warning("[batman] gets a slightly too tight hug from [big_guy]!"), span_userdanger("You feel your body break as [big_guy] embraces you!"))
@@ -61,7 +66,9 @@
/datum/ai_behavior/break_spine/finish_action(datum/ai_controller/controller, succeeded, target_key)
if(succeeded)
controller.blackboard -= target_key
var/mob/living/bane = controller.pawn
bane.stop_pulling()
controller.clear_blackboard_key(target_key)
return ..()
/// Use in hand the currently held item
@@ -86,9 +93,8 @@
/datum/ai_behavior/use_on_object/setup(datum/ai_controller/controller, target_key)
. = ..()
var/datum/weakref/target_ref = controller.blackboard[target_key]
var/target = target_ref?.resolve()
if(!target)
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
@@ -96,10 +102,8 @@
. = ..()
var/mob/living/pawn = controller.pawn
var/obj/item/held_item = pawn.get_item_by_slot(pawn.get_active_hand())
var/datum/weakref/target_ref = controller.blackboard[target_key]
var/atom/target = target_ref?.resolve()
if(!target || !pawn.CanReach(target))
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target) || !pawn.CanReach(target))
finish_action(controller, FALSE)
return
@@ -118,15 +122,13 @@
/datum/ai_behavior/give/setup(datum/ai_controller/controller, target_key)
. = ..()
var/datum/weakref/target_ref = controller.blackboard[target_key]
set_movement_target(controller, target_ref?.resolve())
set_movement_target(controller, controller.blackboard[target_key])
/datum/ai_behavior/give/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
. = ..()
var/mob/living/pawn = controller.pawn
var/obj/item/held_item = pawn.get_active_held_item()
var/datum/weakref/target_ref = controller.blackboard[target_key]
var/atom/target = target_ref?.resolve()
var/atom/target = controller.blackboard[target_key]
if(!held_item) //if held_item is null, we pretend that action was succesful
finish_action(controller, TRUE)
@@ -185,14 +187,14 @@
/datum/ai_behavior/consume/setup(datum/ai_controller/controller, target_key)
. = ..()
var/datum/weakref/target_ref = controller.blackboard[target_key]
set_movement_target(controller, target_ref?.resolve())
set_movement_target(controller, controller.blackboard[target_key])
/datum/ai_behavior/consume/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hunger_timer_key)
. = ..()
var/mob/living/living_pawn = controller.pawn
var/datum/weakref/target_ref = controller.blackboard[target_key]
var/obj/item/target = target_ref.resolve()
var/obj/item/target = controller.blackboard[target_key]
if(QDELETED(target))
return
if(!(target in living_pawn.held_items))
if(!living_pawn.get_empty_held_indexes() || !living_pawn.put_in_hands(target))
@@ -207,7 +209,7 @@
/datum/ai_behavior/consume/finish_action(datum/ai_controller/controller, succeeded, target_key, hunger_timer_key)
. = ..()
if(succeeded)
controller.blackboard[hunger_timer_key] = world.time + rand(12 SECONDS, 60 SECONDS)
controller.set_blackboard_key(hunger_timer_key, world.time + rand(12 SECONDS, 60 SECONDS))
/**
* Drops items in hands, very important for future behaviors that require the pawn to grab stuff
@@ -234,9 +236,8 @@
if(!istype(living_pawn) || !isturf(living_pawn.loc))
return
var/datum/weakref/attack_ref = controller.blackboard[BB_ATTACK_TARGET]
var/atom/movable/attack_target = attack_ref?.resolve()
if(!attack_target || !can_see(living_pawn, attack_target, length=controller.blackboard[BB_VISION_RANGE]))
var/atom/movable/attack_target = controller.blackboard[BB_ATTACK_TARGET]
if(!attack_target || !can_see(living_pawn, attack_target, length = controller.blackboard[BB_VISION_RANGE]))
finish_action(controller, FALSE)
return
@@ -250,7 +251,7 @@
/datum/ai_behavior/attack/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
controller.blackboard[BB_ATTACK_TARGET] = null
controller.clear_blackboard_key(BB_ATTACK_TARGET)
/// A proc representing when the mob is pushed to actually attack the target. Again, subtypes can be used to represent different attacks from different animals, or it can be some other generic behavior
/datum/ai_behavior/attack/proc/attack(datum/ai_controller/controller, mob/living/living_target)
@@ -270,8 +271,7 @@
if(!istype(living_pawn) || !isturf(living_pawn.loc))
return
var/datum/weakref/follow_ref = controller.blackboard[BB_FOLLOW_TARGET]
var/atom/movable/follow_target = follow_ref?.resolve()
var/atom/movable/follow_target = controller.blackboard[BB_FOLLOW_TARGET]
if(!follow_target || get_dist(living_pawn, follow_target) > controller.blackboard[BB_VISION_RANGE])
finish_action(controller, FALSE)
return
@@ -285,7 +285,7 @@
/datum/ai_behavior/follow/finish_action(datum/ai_controller/controller, succeeded)
. = ..()
controller.blackboard[BB_FOLLOW_TARGET] = null
controller.clear_blackboard_key(BB_FOLLOW_TARGET)
@@ -316,8 +316,7 @@
/datum/ai_behavior/setup_instrument/perform(seconds_per_tick, datum/ai_controller/controller, song_instrument_key, song_lines_key)
. = ..()
var/datum/weakref/instrument_ref = controller.blackboard[song_instrument_key]
var/obj/item/instrument/song_instrument = instrument_ref.resolve()
var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key]
var/datum/song/song = song_instrument.song
var/song_lines = controller.blackboard[song_lines_key]
@@ -333,8 +332,7 @@
/datum/ai_behavior/play_instrument/perform(seconds_per_tick, datum/ai_controller/controller, song_instrument_key)
. = ..()
var/datum/weakref/instrument_ref = controller.blackboard[song_instrument_key]
var/obj/item/instrument/song_instrument = instrument_ref.resolve()
var/obj/item/instrument/song_instrument = controller.blackboard[song_instrument_key]
var/datum/song/song = song_instrument.song
song.start_playing(controller.pawn)
@@ -354,5 +352,5 @@
possible_targets += thing
if(!possible_targets.len)
finish_action(controller, FALSE)
controller.blackboard[target_key] = WEAKREF(pick(possible_targets))
controller.set_blackboard_key(target_key, pick(possible_targets))
finish_action(controller, TRUE)
+8 -8
View File
@@ -8,8 +8,7 @@
* * BB_SONG_LINES - not set by this subtree, is the song loaded into the song datum.
*/
/datum/ai_planning_subtree/generic_play_instrument/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/weakref/player_ref = controller.blackboard[BB_SONG_INSTRUMENT]
var/obj/item/instrument/song_player = player_ref?.resolve()
var/obj/item/instrument/song_player = controller.blackboard[BB_SONG_INSTRUMENT]
if(!song_player)
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_hands, BB_SONG_INSTRUMENT, /obj/item/instrument)
@@ -47,15 +46,16 @@
* * BB_NEXT_HUNGRY - set by this subtree, is when the controller is next hungry
*/
/datum/ai_planning_subtree/generic_hunger/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
//inits the blackboard timer
if(!controller.blackboard[BB_NEXT_HUNGRY])
controller.blackboard[BB_NEXT_HUNGRY] = world.time + rand(0, 30 SECONDS)
var/next_eat = controller.blackboard[BB_NEXT_HUNGRY]
if(!next_eat)
//inits the blackboard timer
next_eat = world.time + rand(0, 30 SECONDS)
controller.set_blackboard_key(BB_NEXT_HUNGRY, next_eat)
if(world.time < controller.blackboard[BB_NEXT_HUNGRY])
if(world.time < next_eat)
return
var/datum/weakref/food_ref = controller.blackboard[BB_FOOD_TARGET]
if(!food_ref?.resolve())
if(!controller.blackboard[BB_FOOD_TARGET])
controller.queue_behavior(/datum/ai_behavior/find_and_set/edible, BB_FOOD_TARGET, /obj/item, 2)
return