Files
Bubberstation/code/datums/components/pet_commands/obeys_commands.dm
StaringGasMask bae82830f2 Fixes a few bugs with stargazer targeting and attacks (#89068)
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request

Fixes #89060 and a few other inconsistencies that I think aren't
reported yet. In particular, these are:
- Ordering the Stargazer to attack a window, machinery, and essentially
anything that isn't a wall or person will result in refusal. This
behavior was different.
- The spell to remotely command the Stargazer doesn't work.
- The shift-hover radial menu is weirdly offset.

I'm still trying to figure out what makes the mob not attack windows
unlike before, so I'll set it as a draft until I fix it.

## Why It's Good For The Game

Summoning an elder god bound to your will would be even better if it was
actually bound to your will.

## Changelog

<!-- If your PR modifies aspects of the game that can be concretely
observed by players or admins you should add a changelog. If your change
does NOT meet this description, remove this section. Be sure to properly
mark your PRs to prevent unnecessary GBP loss. You can read up on GBP
and its effects on PRs in the tgstation guides for contributors. Please
note that maintainers freely reserve the right to remove and add tags
should they deem it appropriate. You can attempt to finagle the system
all you want, but it's best to shoot for clear communication right off
the bat. -->

🆑
fix: The Stargazer, a cosmic heretic's ascension, can be properly
commanded once more.
code: Basic mob pets can now target objects, or objects and walls by
using two new targeting strategies.
/🆑

<!-- Both 🆑's are required for the changelog to work! You can put
your name to the right of the first 🆑 if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->
2025-01-23 23:07:59 +02:00

141 lines
6.0 KiB
Plaintext

/**
* # Obeys Commands Component
* Manages a list of pet command datums, allowing you to boss it around
* Creates a radial menu of pet commands when this creature is alt-clicked, if it has any
*/
#define DEFAULT_RADIAL_VIEWING_DISTANCE 9
/datum/component/obeys_commands
/// List of commands you can give to the owner of this component
var/list/available_commands = list()
///Users currently viewing our radial options
var/list/radial_viewers = list()
///radius of our radial menu
var/radial_menu_radius = 48
///after how long we shutdown radial menus
var/radial_menu_lifetime = 7 SECONDS
///offset to display the radial menu
var/list/radial_menu_offset
///should the commands move with the pet owner's screen?
var/radial_relative_to_user
/// The available_commands parameter should be passed as a list of typepaths
/datum/component/obeys_commands/Initialize(list/command_typepaths = list(), list/radial_menu_offset = list(0, 0), radial_menu_lifetime = 7 SECONDS, radial_relative_to_user = FALSE)
. = ..()
if (!isliving(parent))
return COMPONENT_INCOMPATIBLE
var/mob/living/living_parent = parent
if (!living_parent.ai_controller)
return COMPONENT_INCOMPATIBLE
if (!length(command_typepaths))
CRASH("Initialised obedience component with no commands.")
src.radial_menu_offset = radial_menu_offset
src.radial_relative_to_user = radial_relative_to_user
src.radial_menu_lifetime = radial_menu_lifetime
for (var/command_path in command_typepaths)
var/datum/pet_command/new_command = new command_path(parent)
available_commands[new_command.command_name] = new_command
/datum/component/obeys_commands/Destroy(force)
. = ..()
QDEL_NULL(available_commands)
/datum/component/obeys_commands/RegisterWithParent()
RegisterSignal(parent, COMSIG_LIVING_BEFRIENDED, PROC_REF(add_friend))
RegisterSignal(parent, COMSIG_LIVING_UNFRIENDED, PROC_REF(remove_friend))
RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine))
/datum/component/obeys_commands/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_LIVING_BEFRIENDED, COMSIG_LIVING_UNFRIENDED, COMSIG_ATOM_EXAMINE, COMSIG_CLICK_ALT))
/// Add someone to our friends list
/datum/component/obeys_commands/proc/add_friend(datum/source, mob/living/new_friend)
SIGNAL_HANDLER
RegisterSignal(new_friend, COMSIG_KB_LIVING_VIEW_PET_COMMANDS, PROC_REF(on_key_pressed))
RegisterSignal(new_friend, DEACTIVATE_KEYBIND(COMSIG_KB_LIVING_VIEW_PET_COMMANDS), PROC_REF(on_key_unpressed))
for (var/command_name as anything in available_commands)
var/datum/pet_command/command = available_commands[command_name]
INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, add_new_friend), new_friend)
/datum/component/obeys_commands/proc/on_key_unpressed(mob/living/source)
SIGNAL_HANDLER
UnregisterSignal(source, COMSIG_ATOM_MOUSE_ENTERED)
/datum/component/obeys_commands/proc/remove_from_viewers(mob/living/source)
radial_viewers -= REF(source)
/// Remove someone from our friends list
/datum/component/obeys_commands/proc/remove_friend(datum/source, mob/living/old_friend)
SIGNAL_HANDLER
UnregisterSignal(old_friend, list(
COMSIG_KB_LIVING_VIEW_PET_COMMANDS,
DEACTIVATE_KEYBIND(COMSIG_KB_LIVING_VIEW_PET_COMMANDS),
))
for (var/command_name as anything in available_commands)
var/datum/pet_command/command = available_commands[command_name]
INVOKE_ASYNC(command, TYPE_PROC_REF(/datum/pet_command, remove_friend), old_friend)
/// Add a note about whether they will follow the instructions of the inspecting mob
/datum/component/obeys_commands/proc/on_examine(mob/living/source, mob/user, list/examine_list)
SIGNAL_HANDLER
if (IS_DEAD_OR_INCAP(source))
return
if (!(user in source.ai_controller?.blackboard[BB_FRIENDS_LIST]))
return
examine_list += span_notice("[source.p_They()] seem[source.p_s()] happy to see you!")
/datum/component/obeys_commands/proc/on_key_pressed(mob/living/friend)
SIGNAL_HANDLER
RegisterSignal(friend, COMSIG_ATOM_MOUSE_ENTERED, PROC_REF(on_mouse_hover))
/datum/component/obeys_commands/proc/on_mouse_hover(mob/living/friend, atom/mouse_hovered)
SIGNAL_HANDLER
if(mouse_hovered == parent)
display_menu(friend)
return
if(isliving(mouse_hovered))
remove_from_viewers(friend)
/// Displays a radial menu of commands
/datum/component/obeys_commands/proc/display_menu(mob/living/friend)
var/mob/living/living_parent = parent
if (IS_DEAD_OR_INCAP(living_parent) || friend.stat != CONSCIOUS)
return
if (!(friend in living_parent.ai_controller?.blackboard[BB_FRIENDS_LIST]))
return // Not our friend, can't boss us around
if(radial_viewers[REF(friend)])
return
if(!can_see(friend, parent, DEFAULT_RADIAL_VIEWING_DISTANCE))
return
INVOKE_ASYNC(src, PROC_REF(display_radial_menu), friend)
/// Actually display the radial menu and then do something with the result
/datum/component/obeys_commands/proc/display_radial_menu(mob/living/friend)
var/list/radial_options = list()
for (var/command_name as anything in available_commands)
var/datum/pet_command/command = available_commands[command_name]
var/datum/radial_menu_choice/choice = command.provide_radial_data()
if (!choice)
continue
radial_options += choice
radial_viewers[REF(friend)] = world.time + radial_menu_lifetime
var/pick = show_radial_menu(friend, parent, radial_options, radius = radial_menu_radius, button_animation_flags = BUTTON_FADE_IN | BUTTON_FADE_OUT, custom_check = CALLBACK(src, PROC_REF(check_menu_viewer), friend), check_delay = 0.15 SECONDS, display_close_button = FALSE, radial_menu_offset = radial_menu_offset, user_space = radial_relative_to_user)
remove_from_viewers(friend)
if(!pick)
return
var/datum/pet_command/picked_command = available_commands[pick]
picked_command.try_activate_command(friend, radial_command = TRUE)
/datum/component/obeys_commands/proc/check_menu_viewer(mob/living/user)
if(QDELETED(user) || !radial_viewers[REF(user)])
return FALSE
if(world.time > radial_viewers[REF(user)])
return FALSE
var/viewing_distance = DEFAULT_RADIAL_VIEWING_DISTANCE
if(!can_see(user, parent, viewing_distance))
return FALSE
return TRUE
#undef DEFAULT_RADIAL_VIEWING_DISTANCE