Files
Bubberstation/code/datums/components/pet_commands/obeys_commands.dm
SmArtKar fc5e7099ee Hovering over things on the same tile no longer closes the pet command menu, automatically closes it when you let go of the hotkey (#92788)
## About The Pull Request

Hovering over mobs on the same tile as the commanded mob will no longer
close the command menu, and it will automatically get closed once the
user lets go of the hotkey without having to forcefully time out.

## Why It's Good For The Game

The same tile requirement prevents the menu from fading out if you're
trying to command the mob you're riding, and not closing the menu when
unregistering the hover signal actually prevented you from getting rid
of the menu without waiting for the automatic timeout, which is super
weird and unintuitive.

## Changelog
🆑
qol: Letting go of the pet command hotkey (Shift by default) will
instantly close the menu
fix: Hovering over things on the same tile no longer closes the pet
command menu
/🆑
2025-09-06 20:57:18 +10:00

144 lines
6.1 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_LIST_ASSOC_VAL(available_commands)
return ..()
/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)
remove_from_viewers(source)
/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
var/mob/living/owner = parent
if(isliving(mouse_hovered) && mouse_hovered.loc != owner.loc)
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