Files
Bubberstation/code/datums/components/pet_commands/fetch.dm
Jacquerel eb6c0eb37c Dogs use the Pet Command system (#72045)
About The Pull Request

Chiefly this refactors dogs to use the newer component/datum system for "pet which follows instructions". It also refactors it a little bit because I had better ideas while working on this than I had last week. Specifically, instead of passing around keys we just stick a weakref to our currently active behaviour in the blackboard. Basically the same but skipping an unecessary step.

Additionally it adds a component for the previous "befriending a mob by clicking it repeatedly" behaviour which hopefully we won't use too much because it's not very exciting (I am planning on replacing it for dogs some time after Christmas).
The biggest effort in here was making the Fetch command more generic, which includes multiple behaviours (which might be used on their own?) and another component (for holding an item without hands).

Additionally I noticed that dogs would keep following my instructions after they died.
This seems unideal, and would be unideal for virtually any AI controller, so I added it as an AI_flag just in case there's some circumstance where you do want to process AI on a dead mob.

Finally this should replicate all behaviour Ian already had plus "follow" (from rats) and a new bonus easter egg reaction, however I noticed that the fetch command is supposed to have Ian eat any food that you try to get him to fetch.
This has been broken for some time and I will be away from my desk for a couple weeks pretty soon, so I wrote the behaviour for this but left it unused. I will come back to this in the future, once I figure out a way to implement it which does not require adding the "you can hit this" flag to every edible item.

Also I had to refit the recent addition of dogs barking at felinids to fit into this, with a side effect that now dogs won't get mad at a Felinid they are friends with. This... feels like intended behaviour anyway?
Why It's Good For The Game

It's good for these to work the same way instead of reimplementing the same behaviour in multiple files.
Being able to have Ian (or other dogs) follow you around the station is both fun and cute, and also makes him significantly more vulnerable to being murdered.
Changelog

cl
add: Ian has learned some new tricks, tell him what a good boy he is!
add: Ian will come on a walk with you, if you are his friend.
refactor: Ian's tricks work the same way as some other mobs' tricks and should be extendable to future mobs.
fix: Dogs no longer run at the maximum possible speed for a mob at all times.
add: When Ian gets old, he also slows down. Poor little guy.
add: Dogs will no longer dislike the presence of Felinids who have taken the time to befriend them.
/cl
2022-12-29 14:37:25 +13:00

117 lines
4.9 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] != WEAKREF(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] && blackboard[BB_FETCH_IGNORE_LIST][WEAKREF(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.blackboard[BB_FETCH_DELIVER_TO] = WEAKREF(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.blackboard[BB_FETCH_DELIVER_TO] = WEAKREF(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()
// We got something to fetch so go fetch it
if (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/datum/weakref/carried_ref = controller.blackboard[BB_SIMPLE_CARRY_ITEM]
var/obj/item/carried_item = carried_ref?.resolve()
if (!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))
// We don't know where to return this to so we're just going to keep it
controller.blackboard[BB_ACTIVE_PET_COMMAND] = null
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