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
@@ -4,22 +4,22 @@
/datum/ai_behavior/basic_melee_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
//Hiding location is priority
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target)
return FALSE
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
if (!targetting_datum)
return
if(isnull(targetting_datum))
CRASH("No target datum was supplied in the blackboard for [controller.pawn]")
//Hiding location is priority
var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/basic_melee_attack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
var/mob/living/basic/basic_mob = controller.pawn
//targetting datum will kill the action if not real anymore
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/atom/target = controller.blackboard[target_key]
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
if(!targetting_datum.can_attack(basic_mob, target))
@@ -28,7 +28,7 @@
var/hiding_target = targetting_datum.find_hidden_mobs(basic_mob, target) //If this is valid, theyre hidden in something!
controller.blackboard[hiding_location_key] = hiding_target
controller.set_blackboard_key(hiding_location_key, hiding_target)
if(hiding_target) //Slap it!
basic_mob.melee_attack(hiding_target)
@@ -39,7 +39,7 @@
/datum/ai_behavior/basic_melee_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key)
. = ..()
if(!succeeded)
controller.blackboard -= target_key
controller.clear_blackboard_key(target_key)
/datum/ai_behavior/basic_melee_attack/average_speed
action_cooldown = 1 SECONDS
@@ -55,9 +55,8 @@
/datum/ai_behavior/basic_ranged_attack/setup(datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target)
var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
@@ -65,8 +64,7 @@
. = ..()
var/mob/living/basic/basic_mob = controller.pawn
//targetting datum will kill the action if not real anymore
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/atom/target = controller.blackboard[target_key]
var/datum/targetting_datum/targetting_datum = controller.blackboard[targetting_datum_key]
if(!targetting_datum.can_attack(basic_mob, target))
@@ -80,7 +78,7 @@
finish_action(controller, FALSE, target_key)
return
controller.blackboard[hiding_location_key] = WEAKREF(hiding_target)
controller.set_blackboard_key(hiding_location_key, hiding_target)
if(shots>1)
var/atom/burst_target = final_target
@@ -94,4 +92,4 @@
/datum/ai_behavior/basic_ranged_attack/finish_action(datum/ai_controller/controller, succeeded, target_key, targetting_datum_key, hiding_location_key)
. = ..()
if(!succeeded)
controller.blackboard -= target_key
controller.clear_blackboard_key(target_key)
@@ -7,18 +7,16 @@
/datum/ai_behavior/pick_up_item/setup(datum/ai_controller/controller, target_key, storage_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/obj/item/target = weak_target?.resolve()
var/obj/item/target = controller.blackboard[target_key]
return isitem(target) && isturf(target.loc) && !target.anchored
/datum/ai_behavior/pick_up_item/perform(seconds_per_tick, datum/ai_controller/controller, target_key, storage_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/obj/item/target = weak_target?.resolve()
if(!isturf(target?.loc)) // Someone picked it up or it got deleted
var/obj/item/target = controller.blackboard[target_key]
if(QDELETED(target) || !isturf(target.loc)) // Someone picked it up or it got deleted
finish_action(controller, FALSE, target_key)
return
if(!in_range(controller.pawn, target)) // It teleported
if(!controller.pawn.Adjacent(target)) // It teleported
finish_action(controller, FALSE, target_key)
return
pickup_item(controller, target, storage_key)
@@ -26,22 +24,21 @@
/datum/ai_behavior/pick_up_item/finish_action(datum/ai_controller/controller, success, target_key, storage_key)
. = ..()
controller.blackboard[target_key] = null
controller.clear_blackboard_key(target_key)
/datum/ai_behavior/pick_up_item/proc/pickup_item(datum/ai_controller/controller, obj/item/target, storage_key)
var/atom/pawn = controller.pawn
drop_existing_item(controller, storage_key)
pawn.visible_message(span_notice("[pawn] picks up [target]."))
target.forceMove(pawn)
controller.blackboard[storage_key] = WEAKREF(target)
controller.set_blackboard_key(storage_key, target)
return TRUE
/datum/ai_behavior/pick_up_item/proc/drop_existing_item(datum/ai_controller/controller, storage_key)
var/datum/weakref/carried_ref = controller.blackboard[storage_key]
var/obj/item/carried_item = carried_ref?.resolve()
var/obj/item/carried_item = controller.blackboard[storage_key]
if(!carried_item)
return
controller.blackboard[storage_key] = null
controller.clear_blackboard_key(storage_key)
var/atom/pawn = controller.pawn
if(carried_item.loc != pawn)
return
@@ -7,9 +7,8 @@
var/run_distance = 9
/datum/ai_behavior/run_away_from_target/setup(datum/ai_controller/controller, target_key, hiding_location_key)
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target)
var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
if(!plot_path_away_from(controller, target))
return FALSE
@@ -17,9 +16,8 @@
/datum/ai_behavior/run_away_from_target/perform(seconds_per_tick, datum/ai_controller/controller, target_key, hiding_location_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/escaped = !target || !can_see(controller.pawn, target, run_distance) // If we can't see it we got away
var/atom/target = controller.blackboard[hiding_location_key] || controller.blackboard[target_key]
var/escaped = QDELETED(target) || !can_see(controller.pawn, target, run_distance) // If we can't see it we got away
if (escaped)
finish_action(controller, succeeded = TRUE, target_key = target_key, hiding_location_key = hiding_location_key)
return
@@ -43,4 +41,4 @@
/datum/ai_behavior/run_away_from_target/finish_action(datum/ai_controller/controller, succeeded, target_key, hiding_location_key)
. = ..()
controller.blackboard[target_key] = null
controller.clear_blackboard_key(target_key)
@@ -11,13 +11,12 @@
var/step_distance = 3
/datum/ai_behavior/step_towards_turf/setup(datum/ai_controller/controller, turf_key)
var/datum/weakref/weak_turf = controller.blackboard[turf_key]
var/turf/target_turf = weak_turf?.resolve()
if (!target_turf || target_turf.is_blocked_turf(exclude_mobs = TRUE))
var/turf/target_turf = controller.blackboard[turf_key]
if (QDELETED(target_turf) || target_turf.is_blocked_turf(exclude_mobs = TRUE))
target_turf = find_destination_turf(args)
if (!target_turf)
return FALSE
controller.blackboard[turf_key] = WEAKREF(target_turf)
controller.set_blackboard_key(turf_key, target_turf)
if (target_turf.z != controller.pawn.z)
return FALSE
@@ -4,16 +4,14 @@
/datum/ai_behavior/stop_and_stare/setup(datum/ai_controller/controller, target_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/movable/target = weak_target?.resolve()
var/atom/movable/target = controller.blackboard[target_key]
return ismovable(target) && isturf(target.loc) && ismob(controller.pawn)
/datum/ai_behavior/stop_and_stare/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
// i don't really like doing this but we wanna make sure that the cooldown is pertinent to what we need for this specific controller before we invoke parent
action_cooldown = controller.blackboard[BB_STATIONARY_COOLDOWN]
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/movable/target = weak_target?.resolve()
var/atom/movable/target = controller.blackboard[target_key]
if(!ismovable(target) || !isturf(target.loc)) // just to make sure that nothing funky happened between setup and perform
return
@@ -5,12 +5,9 @@
/datum/ai_behavior/targeted_mob_ability
/datum/ai_behavior/targeted_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key)
var/datum/weakref/weak_ability = controller.blackboard[ability_key]
var/datum/action/cooldown/ability = weak_ability?.resolve()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/mob/living/target = weak_target?.resolve()
if(!ability || QDELETED(target))
var/datum/action/cooldown/ability = controller.blackboard[ability_key]
var/mob/living/target = controller.blackboard[target_key]
if(QDELETED(ability) || QDELETED(target))
finish_action(controller, FALSE, ability_key, target_key)
return
var/mob/pawn = controller.pawn
@@ -19,16 +16,15 @@
/datum/ai_behavior/targeted_mob_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/atom/target = controller.blackboard[target_key]
if (QDELETED(target))
controller.blackboard[target_key] = null
controller.clear_blackboard_key(target_key)
return
if (!isliving(target))
return
var/mob/living/living_target = target
if(living_target.stat >= UNCONSCIOUS)
controller.blackboard[target_key] = null
controller.clear_blackboard_key(target_key)
/**
* # Try Mob Ability and plan execute
@@ -38,7 +34,7 @@
/datum/ai_behavior/targeted_mob_ability/and_plan_execute
/datum/ai_behavior/targeted_mob_ability/and_plan_execute/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key)
controller.blackboard[BB_BASIC_MOB_EXECUTION_TARGET] = controller.blackboard[target_key]
controller.set_blackboard_key(BB_BASIC_MOB_EXECUTION_TARGET, controller.blackboard[target_key])
return ..()
/**
@@ -49,4 +45,4 @@
/datum/ai_behavior/targeted_mob_ability/and_clear_target/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key)
. = ..()
controller.blackboard[target_key] = null
controller.clear_blackboard_key(target_key)
@@ -13,13 +13,12 @@
if(!targetting_datum)
CRASH("No target datum was supplied in the blackboard for [controller.pawn]")
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/current_target = weak_target?.resolve()
var/atom/current_target = controller.blackboard[target_key]
if (targetting_datum.can_attack(living_mob, current_target))
finish_action(controller, succeeded = FALSE)
return
controller.blackboard[target_key] = null
controller.clear_blackboard_key(target_key)
var/list/potential_targets = hearers(vision_range, controller.pawn) - living_mob //Remove self, so we don't suicide
for(var/HM in typecache_filter_list(range(vision_range, living_mob), hostile_machines)) //Can we see any hostile machines?
@@ -42,12 +41,12 @@
return
var/atom/target = pick_final_target(controller, filtered_targets)
controller.blackboard[target_key] = WEAKREF(target)
controller.set_blackboard_key(target_key, target)
var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, target)
if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially.
controller.blackboard[hiding_location_key] = WEAKREF(potential_hiding_location)
controller.set_blackboard_key(hiding_location_key, potential_hiding_location)
finish_action(controller, succeeded = TRUE)
@@ -33,5 +33,5 @@
/datum/ai_behavior/tipped_reaction/finish_action(datum/ai_controller/controller, succeeded, tipper_key, reacting_key)
. = ..()
//I'VE SAID MY PEACE...
controller.blackboard[reacting_key] = FALSE
controller.blackboard[tipper_key] = null
controller.set_blackboard_key(reacting_key, FALSE)
controller.clear_blackboard_key(tipper_key)
@@ -9,9 +9,8 @@
/datum/ai_behavior/travel_towards/setup(datum/ai_controller/controller, target_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(isnull(target))
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return FALSE
set_movement_target(controller, target)
@@ -7,10 +7,8 @@
/datum/ai_planning_subtree/attack_obstacle_in_path/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(isnull(target))
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return
var/turf/next_step = get_step_towards(controller.pawn, target)
@@ -31,10 +29,9 @@
/datum/ai_behavior/attack_obstructions/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
. = ..()
var/mob/living/basic/basic_mob = controller.pawn
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/atom/target = controller.blackboard[target_key]
if (!target)
if (QDELETED(target))
finish_action(controller, succeeded = FALSE)
return
@@ -1,10 +1,11 @@
/// similar to finding a target but looks for food types in the
/// similar to finding a target but looks for food types in the // the what?
/datum/ai_planning_subtree/find_food
/datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(target && !QDELETED(target))
if(!QDELETED(target))
// Busy with something
return
var/list/wanted = controller.blackboard[BB_BASIC_FOODS]
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_BASIC_MOB_CURRENT_TARGET, wanted)
controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list, BB_BASIC_MOB_CURRENT_TARGET, controller.blackboard[BB_BASIC_FOODS])
@@ -11,9 +11,8 @@
. = ..()
if (!controller.blackboard[BB_BASIC_MOB_FLEEING])
return
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if(!target || QDELETED(target))
var/atom/target = controller.blackboard[target_key]
if(QDELETED(target))
return
controller.queue_behavior(flee_behaviour, target_key, hiding_place_key)
return SUBTREE_RETURN_FINISH_PLANNING //we gotta get out of here.
@@ -3,10 +3,8 @@
/datum/ai_planning_subtree/basic_melee_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()
if(!target || QDELETED(target))
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(QDELETED(target))
return
controller.queue_behavior(melee_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.
@@ -20,9 +18,8 @@
/datum/ai_planning_subtree/basic_ranged_attack_subtree/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
var/atom/target = weak_target?.resolve()
if(!target || QDELETED(target))
var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if(QDELETED(target))
return
controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION)
return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions.
@@ -15,16 +15,15 @@
var/time_to_wait = 10
/datum/ai_behavior/sleep_after_targetless_time/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
var/atom/target = controller.blackboard[target_key]
finish_action(controller, succeeded = !target, seconds_per_tick = seconds_per_tick)
/datum/ai_behavior/sleep_after_targetless_time/finish_action(datum/ai_controller/controller, succeeded, seconds_per_tick)
. = ..()
if (!succeeded)
controller.blackboard[BB_TARGETLESS_TIME] = 0
controller.set_blackboard_key(BB_TARGETLESS_TIME, 0)
return
controller.blackboard[BB_TARGETLESS_TIME] += seconds_per_tick
controller.add_blackboard_key(BB_TARGETLESS_TIME, seconds_per_tick)
if (controller.blackboard[BB_TARGETLESS_TIME] > time_to_wait)
enter_sleep(controller)
@@ -2,8 +2,7 @@
/datum/ai_planning_subtree/stare_at_thing
/datum/ai_planning_subtree/stare_at_thing/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/weakref/weak_target = controller.blackboard[BB_STATIONARY_CAUSE]
var/atom/target = weak_target?.resolve()
var/atom/target = controller.blackboard[BB_STATIONARY_CAUSE]
if(isnull(target)) // No target? Time to locate one using the list we set in this mob's blackboard.
var/list/potential_scares = controller.blackboard[BB_STATIONARY_TARGETS]
@@ -11,4 +10,3 @@
return
controller.queue_behavior(/datum/ai_behavior/stop_and_stare, BB_STATIONARY_CAUSE)
@@ -35,36 +35,22 @@
if(!targetting_datum)
CRASH("No target datum was supplied in the blackboard for [controller.pawn]")
var/list/enemy_refs = controller.blackboard[shitlist_key]
if (!length(enemy_refs))
finish_action(controller, succeeded = FALSE)
return
var/list/enemies_list = list()
for (var/datum/weakref/enemy_ref as anything in enemy_refs)
var/atom/enemy = enemy_ref.resolve()
if (!can_attack_target(living_mob, enemy, targetting_datum))
controller.blackboard[shitlist_key] -= enemy_ref
continue
enemies_list += enemy
var/list/enemies_list = controller.blackboard[shitlist_key]
if (!length(enemies_list))
finish_action(controller, succeeded = FALSE)
return
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if (target && (locate(target) in enemies_list)) // Don't bother changing
if (controller.blackboard[target_key] in enemies_list) // Don't bother changing
finish_action(controller, succeeded = FALSE)
return
var/atom/new_target = pick_final_target(controller, enemies_list)
controller.blackboard[target_key] = WEAKREF(new_target)
controller.set_blackboard_key(target_key, new_target)
var/atom/potential_hiding_location = targetting_datum.find_hidden_mobs(living_mob, new_target)
if(potential_hiding_location) //If they're hiding inside of something, we need to know so we can go for that instead initially.
controller.blackboard[hiding_location_key] = WEAKREF(potential_hiding_location)
controller.set_blackboard_key(hiding_location_key, potential_hiding_location)
finish_action(controller, succeeded = TRUE)
@@ -13,14 +13,9 @@
if (!ability_key)
CRASH("You forgot to tell this mob where to find its ability")
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/mob/living/target = weak_target?.resolve()
if (QDELETED(target))
return
var/datum/weakref/weak_ability = controller.blackboard[ability_key]
var/datum/action/cooldown/using_action = weak_ability?.resolve()
if (!using_action || !using_action.IsAvailable())
var/mob/living/target = controller.blackboard[target_key]
var/datum/action/cooldown/using_action = controller.blackboard[ability_key]
if (QDELETED(target) || QDELETED(using_action) || !using_action.IsAvailable())
return
controller.queue_behavior(use_ability_behaviour, ability_key, target_key)
@@ -14,9 +14,8 @@
if (!ability_key)
CRASH("You forgot to tell this mob where to find its ability")
var/datum/weakref/weak_ability = controller.blackboard[ability_key]
var/datum/action/cooldown/using_action = weak_ability?.resolve()
if (!using_action || !using_action.IsAvailable())
var/datum/action/cooldown/using_action = controller.blackboard[ability_key]
if (QDELETED(using_action) || !using_action.IsAvailable())
return
controller.queue_behavior(use_ability_behaviour, ability_key)
@@ -26,9 +25,8 @@
/datum/ai_behavior/use_mob_ability
/datum/ai_behavior/use_mob_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key)
var/datum/weakref/weak_ability = controller.blackboard[ability_key]
var/datum/action/cooldown/using_action = weak_ability?.resolve()
if (!using_action)
var/datum/action/cooldown/using_action = controller.blackboard[ability_key]
if (QDELETED(using_action))
finish_action(controller, FALSE, ability_key)
return
var/result = using_action.Trigger()
+24 -35
View File
@@ -7,21 +7,18 @@
/datum/ai_behavior/fetch_seek/setup(datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/fetch_thing = thing_ref?.resolve()
var/obj/item/fetch_thing = controller.blackboard[target_key]
// It stopped existing
if (!fetch_thing)
if (QDELETED(fetch_thing))
return FALSE
set_movement_target(controller, fetch_thing)
/datum/ai_behavior/fetch_seek/perform(seconds_per_tick, datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/fetch_thing = thing_ref?.resolve()
var/obj/item/fetch_thing = controller.blackboard[target_key]
// It stopped existing
if (!fetch_thing)
if (QDELETED(fetch_thing))
finish_action(controller, FALSE, target_key, delivery_key)
return
var/mob/living/living_pawn = controller.pawn
@@ -37,12 +34,11 @@
if (success)
return
// Blacklist item if we failed
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/target = thing_ref?.resolve()
var/obj/item/target = controller.blackboard[target_key]
if (target)
controller.blackboard[BB_FETCH_IGNORE_LIST][thing_ref] = TRUE
controller.blackboard[target_key] = null
controller.blackboard[delivery_key] = null
controller.set_blackboard_key_assoc_lazylist(BB_FETCH_IGNORE_LIST, target, TRUE)
controller.clear_blackboard_key(target_key)
controller.clear_blackboard_key(delivery_key)
/**
* The second half of fetching, deliver the item to a target.
@@ -52,17 +48,15 @@
/datum/ai_behavior/deliver_fetched_item/setup(datum/ai_controller/controller, delivery_key, storage_key)
. = ..()
var/datum/weakref/return_ref = controller.blackboard[delivery_key]
var/mob/living/return_target = return_ref?.resolve()
if(!return_target) // Guess it's mine now
var/mob/living/return_target = controller.blackboard[delivery_key]
if(QDELETED(return_target)) // Guess it's mine now
return FALSE
set_movement_target(controller, return_target)
/datum/ai_behavior/deliver_fetched_item/perform(seconds_per_tick, datum/ai_controller/controller, delivery_key, storage_key)
. = ..()
var/datum/weakref/return_ref = controller.blackboard[delivery_key]
var/mob/living/return_target = return_ref?.resolve()
if(!return_target)
var/mob/living/return_target = controller.blackboard[delivery_key]
if(QDELETED(return_target))
finish_action(controller, FALSE, delivery_key)
return
@@ -71,22 +65,21 @@
/datum/ai_behavior/deliver_fetched_item/finish_action(datum/ai_controller/controller, success, delivery_key)
. = ..()
controller.blackboard[delivery_key] = null
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
controller.clear_blackboard_key(delivery_key)
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
/// Actually deliver the fetched item to the target, if we still have it
/datum/ai_behavior/deliver_fetched_item/proc/deliver_item(datum/ai_controller/controller, return_target, storage_key)
var/mob/pawn = controller.pawn
var/datum/weakref/carried_ref = controller.blackboard[storage_key]
var/obj/item/carried_item = carried_ref?.resolve()
if(!carried_item || carried_item.loc != pawn)
var/obj/item/carried_item = controller.blackboard[storage_key]
if(QDELETED(carried_item) || carried_item.loc != pawn)
pawn.visible_message(span_notice("[pawn] looks around as if [pawn.p_they()] [pawn.p_have()] lost something."))
finish_action(controller, FALSE)
return
pawn.visible_message(span_notice("[pawn] delivers [carried_item] to [return_target]."))
carried_item.forceMove(get_turf(return_target))
controller.blackboard[storage_key] = null
controller.clear_blackboard_key(storage_key)
return TRUE
/**
@@ -101,19 +94,15 @@
/datum/ai_behavior/eat_fetched_snack/setup(datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/snack = thing_ref?.resolve()
var/obj/item/snack = controller.blackboard[target_key]
if(!istype(snack) || !IS_EDIBLE(snack) || !(isturf(snack.loc) || ishuman(snack.loc)))
return FALSE // This isn't food at all!
set_movement_target(controller, snack)
/datum/ai_behavior/eat_fetched_snack/perform(seconds_per_tick, datum/ai_controller/controller, target_key, delivery_key)
. = ..()
var/datum/weakref/thing_ref = controller.blackboard[target_key]
var/obj/item/snack = thing_ref?.resolve()
if(!(isturf(snack.loc) || ishuman(snack.loc)))
var/obj/item/snack = controller.blackboard[target_key]
if(QDELETED(snack) || !isturf(snack.loc) || ishuman(snack.loc))
finish_action(controller, FALSE) // Where did it go?
var/mob/living/basic/basic_pawn = controller.pawn
@@ -130,9 +119,9 @@
/datum/ai_behavior/eat_fetched_snack/finish_action(datum/ai_controller/controller, succeeded, target_key, delivery_key)
. = ..()
controller.blackboard[target_key] = null
controller.blackboard[delivery_key] = null
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
controller.clear_blackboard_key(target_key)
controller.clear_blackboard_key(delivery_key)
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
/**
* Clear our failed fetch list every so often
@@ -153,5 +142,5 @@
/datum/ai_behavior/forget_failed_fetches/perform(seconds_per_tick, datum/ai_controller/controller)
. = ..()
COOLDOWN_START(src, reset_ignore_cooldown, cooldown_duration)
controller.blackboard[BB_FETCH_IGNORE_LIST] = list()
controller.clear_blackboard_key(BB_FETCH_IGNORE_LIST)
finish_action(controller, TRUE)
@@ -8,8 +8,7 @@
/datum/ai_planning_subtree/pet_planning
/datum/ai_planning_subtree/pet_planning/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
var/datum/weakref/weak_command = controller.blackboard[BB_ACTIVE_PET_COMMAND]
var/datum/pet_command/command = weak_command?.resolve()
var/datum/pet_command/command = controller.blackboard[BB_ACTIVE_PET_COMMAND]
if (!command)
return // Do something else
return command.execute_action(controller)
@@ -4,16 +4,14 @@
/datum/ai_behavior/pet_follow_friend/setup(datum/ai_controller/controller, target_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if (!target)
var/atom/target = controller.blackboard[target_key]
if (QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/pet_follow_friend/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/atom/target = weak_target?.resolve()
if (!target)
var/atom/target = controller.blackboard[target_key]
if (QDELETED(target))
finish_action(controller, FALSE, target_key)
return
@@ -4,17 +4,15 @@
/datum/ai_behavior/pet_use_ability/setup(datum/ai_controller/controller, ability_key, target_key)
. = ..()
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/mob/living/target = weak_target?.resolve()
var/mob/living/target = controller.blackboard[target_key]
if (QDELETED(target))
return FALSE
set_movement_target(controller, target)
/datum/ai_behavior/pet_use_ability/perform(seconds_per_tick, datum/ai_controller/controller, ability_key, target_key)
var/datum/action/cooldown/mob_cooldown/ability = controller.blackboard[ability_key]
var/datum/weakref/weak_target = controller.blackboard[target_key]
var/mob/living/target = weak_target?.resolve()
if (!ability || QDELETED(target))
var/mob/living/target = controller.blackboard[target_key]
if (QDELETED(ability) || QDELETED(target))
finish_action(controller, FALSE, ability_key, target_key)
return
var/mob/pawn = controller.pawn
@@ -23,4 +21,4 @@
/datum/ai_behavior/pet_use_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key)
. = ..()
controller.blackboard[target_key] = null
controller.clear_blackboard_key(target_key)
@@ -21,4 +21,4 @@
return
basic_pawn.visible_message(span_notice("[basic_pawn] miraculously springs back to life!"))
basic_pawn.look_alive()
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
controller.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
@@ -30,11 +30,10 @@
if (!living_mob.ai_controller) // How did you get here?
return FALSE
var/list/friends_list = living_mob.ai_controller.blackboard[BB_FRIENDS_LIST]
if (!friends_list)
return TRUE // We don't have any friends, anything's fair game
if (!friends_list[WEAKREF(target)])
return TRUE // This is not our friend, fire at will
if (!(target in living_mob.ai_controller.blackboard[BB_FRIENDS_LIST]))
// We don't have any friends, anything's fair game
// OR This is not our friend, fire at will
return TRUE
return FALSE