Files
Bubberstation/code/datums/components/pet_commands/fetch.dm
SkyratBot e6f66d3a4a [MIRROR] Experiment with replacing weakrefs in AI blackboard with deleting signals, ideally making it easier to work with and harder to cause hard deletes [MDB IGNORE] (#20719)
* 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
/🆑

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

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
2023-04-26 21:17:15 +01:00

114 lines
4.7 KiB
Plaintext

/**
* # Pet Command: Fetch
* Watch for someone throwing or pointing at something and then go get it and bring it back.
* If it's food we might eat it instead.
*/
/datum/pet_command/point_targetting/fetch
command_name = "Fetch"
command_desc = "Command your pet to retrieve something you throw or point at."
radial_icon = 'icons/mob/actions/actions_spells.dmi'
radial_icon_state = "summons"
speech_commands = list("fetch")
command_feedback = "bounces"
pointed_reaction = "with great interest"
/// If true, this command will trigger if the pet sees a friend throw any item, if they're not doing anything else
var/trigger_on_throw = TRUE
/// If true, this is a poorly trained pet who will eat food you throw instead of bringing it back
var/will_eat_targets = TRUE
/datum/pet_command/point_targetting/fetch/New(mob/living/parent)
. = ..()
parent.AddElement(/datum/element/ai_held_item) // We don't remove this on destroy because they might still be holding something
/datum/pet_command/point_targetting/fetch/add_new_friend(mob/living/tamer)
. = ..()
RegisterSignal(tamer, COMSIG_MOB_THROW, PROC_REF(listened_throw))
/datum/pet_command/point_targetting/fetch/remove_friend(mob/living/unfriended)
. = ..()
UnregisterSignal(unfriended, COMSIG_MOB_THROW)
/// A friend has thrown something, if we're listening or at least not busy then go get it
/datum/pet_command/point_targetting/fetch/proc/listened_throw(mob/living/carbon/thrower)
SIGNAL_HANDLER
var/mob/living/parent = weak_parent.resolve()
if (!parent)
return
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] != 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
if (!can_see(parent, thrower, length = sense_radius))
return // Can't see it
var/obj/item/thrown_thing = thrower.get_active_held_item()
if (!isitem(thrown_thing))
return
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))
/// A throw we were listening to has finished, see if it's in range for us to try grabbing it
/datum/pet_command/point_targetting/fetch/proc/listen_throw_land(obj/item/thrown_thing, datum/thrownthing/throwing_datum)
SIGNAL_HANDLER
UnregisterSignal(thrown_thing, COMSIG_MOVABLE_THROW_LANDED)
var/mob/living/parent = weak_parent.resolve()
if (!parent)
return
if (!isturf(thrown_thing.loc))
return
if (!can_see(parent, thrown_thing, length = sense_radius))
return
try_activate_command(throwing_datum.thrower)
set_command_target(parent, thrown_thing)
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)
if (!istype(pointed_atom))
return FALSE
if (pointed_atom.anchored)
return FALSE
. = ..()
if (!.)
return FALSE
var/mob/living/parent = weak_parent.resolve()
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/atom/target = controller.blackboard[BB_CURRENT_PET_TARGET]
// We got something to fetch so go fetch it
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
// If mobs could attack food you would branch here to call `eat_fetched_snack`, however that's a task for the future
controller.queue_behavior(/datum/ai_behavior/pick_up_item, BB_CURRENT_PET_TARGET, BB_SIMPLE_CARRY_ITEM)
return SUBTREE_RETURN_FINISH_PLANNING
var/obj/item/carried_item = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
if (QDELETED(carried_item))
return
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.clear_blackboard_key(BB_ACTIVE_PET_COMMAND)
return
// We got something to deliver and someone to deliver it to
controller.queue_behavior(/datum/ai_behavior/deliver_fetched_item, BB_FETCH_DELIVER_TO, BB_SIMPLE_CARRY_ITEM)
return SUBTREE_RETURN_FINISH_PLANNING