Files
Bubberstation/code/_onclick/hud/action_button.dm
T
MrMelbert 329921639a Rewrites how action buttons icons are generated, makes them layer nicer. Allows observers to see a mob's action buttons. (#71339)
## About The Pull Request

- Rewrites how action button icons are generated.
- Prior, generated an action button icon was fairly simplistic and
didn't allow for many changes. Someone recently added the option for
overlays to be generated over action buttons, but the framework was very
weak.
- Now, action button icon generation is split across multiple procs,
like atom icon updates.
      - The background of action buttons are underlays
- The actual icon of the action button is the icon and icon state of the
action button movable
- The rim / border of the button is an overlay, layered overtop the
button.

- Allows observers to see what action buttons a mob has. They even
update in real time! And no, the observers cannot click on them.

## Why It's Good For The Game

- Runechat text of action buttons are no longer hidden behind the actual
icon. This was very ugly with cooldown actions, as the cooldown text was
hidden behind a lot of spell icons.
- Cuts down on a lot of icon duplication. 
- Gives much finer control over action button icons
- Saves a bit of processing from generating full action button icons
when not necessary. Not implemented in many places, but is in some.


![image](https://user-images.githubusercontent.com/51863163/202816617-342e87e6-2cc6-488e-9af2-4b2053dc3dc6.png)


![image](https://user-images.githubusercontent.com/51863163/202816604-da8d4821-0e2b-45af-b289-7442367f98ce.png)

## Changelog

🆑 Melbert
add: Observers can now see what action buttons an observed mob has. No,
you can't click them. And no it doesn't show EVERY action.
refactor: Refactored how action button icons are generated. Some actions
will now use a colored border when active instead of just turning green.
Cooldown text will also appear on the top layer of actions too. If you
see any funky lookin' icons (namely their borders), let me know.
refactor: Bluespace Golem's teleport action is now a cooldown action.
fix: Construct actions go to the middle of the screen like expected. 
/🆑
2022-12-03 19:01:08 -08:00

472 lines
15 KiB
Plaintext

/atom/movable/screen/movable/action_button
var/datum/action/linked_action
var/datum/hud/our_hud
var/actiontooltipstyle = ""
screen_loc = null
/// The icon state of our active overlay, used to prevent re-applying identical overlays
var/active_overlay_icon_state
/// The icon state of our active underlay, used to prevent re-applying identical underlays
var/active_underlay_icon_state
/// The overlay we have overtop our button
var/mutable_appearance/button_overlay
/// Where we are currently placed on the hud. SCRN_OBJ_DEFAULT asks the linked action what it thinks
var/location = SCRN_OBJ_DEFAULT
/// A unique bitflag, combined with the name of our linked action this lets us persistently remember any user changes to our position
var/id
/// A weakref of the last thing we hovered over
/// God I hate how dragging works
var/datum/weakref/last_hovored_ref
/atom/movable/screen/movable/action_button/Destroy()
if(our_hud)
var/mob/viewer = our_hud.mymob
our_hud.hide_action(src)
viewer?.client?.screen -= src
linked_action.viewers -= our_hud
viewer.update_action_buttons()
our_hud = null
linked_action = null
return ..()
/atom/movable/screen/movable/action_button/proc/can_use(mob/user)
if(isobserver(user))
var/mob/dead/observer/dead_mob = user
if(dead_mob.observetarget) // Observers can only click on action buttons if they're not observing something
return FALSE
if(linked_action)
if(linked_action.viewers[user.hud_used])
return TRUE
return FALSE
return TRUE
/atom/movable/screen/movable/action_button/Click(location,control,params)
if (!can_use(usr))
return FALSE
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, SHIFT_CLICK))
var/datum/hud/our_hud = usr.hud_used
our_hud.position_action(src, SCRN_OBJ_DEFAULT)
return TRUE
if(usr.next_click > world.time)
return
usr.next_click = world.time + 1
var/trigger_flags
if(LAZYACCESS(modifiers, RIGHT_CLICK))
trigger_flags |= TRIGGER_SECONDARY_ACTION
linked_action.Trigger(trigger_flags = trigger_flags)
return TRUE
// Entered and Exited won't fire while you're dragging something, because you're still "holding" it
// Very much byond logic, but I want nice behavior, so we fake it with drag
/atom/movable/screen/movable/action_button/MouseDrag(atom/over_object, src_location, over_location, src_control, over_control, params)
. = ..()
if(!can_use(usr))
return
if(IS_WEAKREF_OF(over_object, last_hovored_ref))
return
var/atom/old_object
if(last_hovored_ref)
old_object = last_hovored_ref?.resolve()
else // If there's no current ref, we assume it was us. We also treat this as our "first go" location
old_object = src
var/datum/hud/our_hud = usr.hud_used
our_hud?.generate_landings(src)
if(old_object)
old_object.MouseExited(over_location, over_control, params)
last_hovored_ref = WEAKREF(over_object)
over_object.MouseEntered(over_location, over_control, params)
/atom/movable/screen/movable/action_button/MouseEntered(location, control, params)
. = ..()
if(!QDELETED(src))
openToolTip(usr, src, params, title = name, content = desc, theme = actiontooltipstyle)
/atom/movable/screen/movable/action_button/MouseExited(location, control, params)
closeToolTip(usr)
return ..()
/atom/movable/screen/movable/action_button/MouseDrop(over_object)
last_hovored_ref = null
if(!can_use(usr))
return
var/datum/hud/our_hud = usr.hud_used
if(over_object == src)
our_hud.hide_landings()
return
if(istype(over_object, /atom/movable/screen/action_landing))
var/atom/movable/screen/action_landing/reserve = over_object
reserve.hit_by(src)
our_hud.hide_landings()
save_position()
return
our_hud.hide_landings()
if(istype(over_object, /atom/movable/screen/button_palette) || istype(over_object, /atom/movable/screen/palette_scroll))
our_hud.position_action(src, SCRN_OBJ_IN_PALETTE)
save_position()
return
if(istype(over_object, /atom/movable/screen/movable/action_button))
var/atom/movable/screen/movable/action_button/button = over_object
our_hud.position_action_relative(src, button)
save_position()
return
. = ..()
our_hud.position_action(src, screen_loc)
save_position()
/atom/movable/screen/movable/action_button/proc/save_position()
var/mob/user = our_hud.mymob
if(!user?.client)
return
var/position_info = ""
switch(location)
if(SCRN_OBJ_FLOATING)
position_info = screen_loc
if(SCRN_OBJ_IN_LIST)
position_info = SCRN_OBJ_IN_LIST
if(SCRN_OBJ_IN_PALETTE)
position_info = SCRN_OBJ_IN_PALETTE
user.client.prefs.action_buttons_screen_locs["[name]_[id]"] = position_info
/atom/movable/screen/movable/action_button/proc/load_position()
var/mob/user = our_hud.mymob
if(!user)
return
var/position_info = user.client?.prefs?.action_buttons_screen_locs["[name]_[id]"] || SCRN_OBJ_DEFAULT
user.hud_used.position_action(src, position_info)
/atom/movable/screen/movable/action_button/proc/dump_save()
var/mob/user = our_hud.mymob
if(!user?.client)
return
user.client.prefs.action_buttons_screen_locs -= "[name]_[id]"
/**
* This is a silly proc used in hud code code to determine what icon and icon state we should be using
* for hud elements (such as action buttons) that don't have their own icon and icon state set.
*
* It returns a list, which is pretty much just a struct of info
*/
/datum/hud/proc/get_action_buttons_icons()
. = list()
.["bg_icon"] = ui_style
.["bg_state"] = "template"
.["bg_state_active"] = "template_active"
/**
* Updates all action buttons this mob has.
*
* Arguments:
* * update_flags - Which flags of the action should we update
* * force - Force buttons update even if the given button icon state has not changed
*/
/mob/proc/update_mob_action_buttons(update_flags = ALL, force = FALSE)
for(var/datum/action/current_action as anything in actions)
current_action.build_all_button_icons(update_flags, force)
/**
* This proc handles adding all of the mob's actions to their screen
*
* If you just need to update existing buttons, use [/mob/proc/update_mob_action_buttons]!
*
* Arguments:
* * update_flags - reload_screen - bool, if TRUE, this proc will add the button to the screen of the passed mob as well
*/
/mob/proc/update_action_buttons(reload_screen = FALSE)
if(!hud_used || !client)
return
if(hud_used.hud_shown != HUD_STYLE_STANDARD)
return
for(var/datum/action/action as anything in actions)
var/atom/movable/screen/movable/action_button/button = action.viewers[hud_used]
action.build_all_button_icons()
if(reload_screen)
client.screen += button
if(reload_screen)
hud_used.update_our_owner()
// This holds the logic for the palette buttons
hud_used.palette_actions.refresh_actions()
/**
* Show (most) of the another mob's action buttons to this mob
*
* Used for observers viewing another mob's screen
*/
/mob/proc/show_other_mob_action_buttons(mob/take_from)
if(!hud_used || !client)
return
for(var/datum/action/action as anything in take_from.actions)
if(!action.show_to_observers)
continue
action.GiveAction(src)
RegisterSignal(take_from, COMSIG_MOB_GRANTED_ACTION, PROC_REF(on_observing_action_granted))
RegisterSignal(take_from, COMSIG_MOB_REMOVED_ACTION, PROC_REF(on_observing_action_removed))
/**
* Hide another mob's action buttons from this mob
*
* Used for observers viewing another mob's screen
*/
/mob/proc/hide_other_mob_action_buttons(mob/take_from)
for(var/datum/action/action as anything in take_from.actions)
action.HideFrom(src)
UnregisterSignal(take_from, list(COMSIG_MOB_GRANTED_ACTION, COMSIG_MOB_REMOVED_ACTION))
/// Signal proc for [COMSIG_MOB_GRANTED_ACTION] - If we're viewing another mob's action buttons,
/// we need to update with any newly added buttons granted to the mob.
/mob/proc/on_observing_action_granted(mob/living/source, datum/action/action)
SIGNAL_HANDLER
if(!action.show_to_observers)
return
action.GiveAction(src)
/// Signal proc for [COMSIG_MOB_REMOVED_ACTION] - If we're viewing another mob's action buttons,
/// we need to update with any removed buttons from the mob.
/mob/proc/on_observing_action_removed(mob/living/source, datum/action/action)
SIGNAL_HANDLER
action.HideFrom(src)
/atom/movable/screen/button_palette
desc = "<b>Drag</b> buttons to move them<br><b>Shift-click</b> any button to reset it<br><b>Alt-click</b> this to reset all buttons"
icon = 'icons/hud/64x16_actions.dmi'
icon_state = "screen_gen_palette"
screen_loc = ui_action_palette
var/datum/hud/our_hud
var/expanded = FALSE
/// Id of any currently running timers that set our color matrix
var/color_timer_id
/atom/movable/screen/button_palette/Destroy()
if(our_hud)
our_hud.mymob?.client?.screen -= src
our_hud.toggle_palette = null
our_hud = null
return ..()
/atom/movable/screen/button_palette/Initialize(mapload)
. = ..()
update_appearance()
/atom/movable/screen/button_palette/proc/set_hud(datum/hud/our_hud)
src.our_hud = our_hud
refresh_owner()
/atom/movable/screen/button_palette/update_name(updates)
. = ..()
if(expanded)
name = "Hide Buttons"
else
name = "Show Buttons"
/atom/movable/screen/button_palette/proc/refresh_owner()
var/mob/viewer = our_hud.mymob
if(viewer.client)
viewer.client.screen |= src
var/list/settings = our_hud.get_action_buttons_icons()
var/ui_icon = "[settings["bg_icon"]]"
var/list/ui_segments = splittext(ui_icon, ".")
var/list/ui_paths = splittext(ui_segments[1], "/")
var/ui_name = ui_paths[length(ui_paths)]
icon_state = "[ui_name]_palette"
/atom/movable/screen/button_palette/MouseEntered(location, control, params)
. = ..()
if(QDELETED(src))
return
show_tooltip(params)
/atom/movable/screen/button_palette/MouseExited()
closeToolTip(usr)
return ..()
/atom/movable/screen/button_palette/proc/show_tooltip(params)
openToolTip(usr, src, params, title = name, content = desc)
GLOBAL_LIST_INIT(palette_added_matrix, list(0.4,0.5,0.2,0, 0,1.4,0,0, 0,0.4,0.6,0, 0,0,0,1, 0,0,0,0))
GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,0, 0,0,0,1, 0,0,0,0))
/atom/movable/screen/button_palette/proc/play_item_added()
color_for_now(GLOB.palette_added_matrix)
/atom/movable/screen/button_palette/proc/play_item_removed()
color_for_now(GLOB.palette_removed_matrix)
/atom/movable/screen/button_palette/proc/color_for_now(list/color)
if(color_timer_id)
return
add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY) //We unfortunately cannot animate matrix colors. Curse you lummy it would be ~~non~~trivial to interpolate between the two valuessssssssss
color_timer_id = addtimer(CALLBACK(src, PROC_REF(remove_color), color), 2 SECONDS)
/atom/movable/screen/button_palette/proc/remove_color(list/to_remove)
color_timer_id = null
remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, to_remove)
/atom/movable/screen/button_palette/proc/can_use(mob/user)
if (isobserver(user))
var/mob/dead/observer/O = user
return !O.observetarget
return TRUE
/atom/movable/screen/button_palette/Click(location, control, params)
if(!can_use(usr))
return
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, ALT_CLICK))
for(var/datum/action/action as anything in usr.actions) // Reset action positions to default
for(var/datum/hud/hud as anything in action.viewers)
var/atom/movable/screen/movable/action_button/button = action.viewers[hud]
hud.position_action(button, SCRN_OBJ_DEFAULT)
to_chat(usr, span_notice("Action button positions have been reset."))
return TRUE
set_expanded(!expanded)
/atom/movable/screen/button_palette/proc/clicked_while_open(datum/source, atom/target, atom/location, control, params, mob/user)
if(istype(target, /atom/movable/screen/movable/action_button) || istype(target, /atom/movable/screen/palette_scroll) || target == src) // If you're clicking on an action button, or us, you can live
return
set_expanded(FALSE)
if(source)
UnregisterSignal(source, COMSIG_CLIENT_CLICK)
/atom/movable/screen/button_palette/proc/set_expanded(new_expanded)
var/datum/action_group/our_group = our_hud.palette_actions
if(!length(our_group.actions)) //Looks dumb, trust me lad
new_expanded = FALSE
if(expanded == new_expanded)
return
expanded = new_expanded
our_group.refresh_actions()
update_appearance()
if(!usr.client)
return
if(expanded)
RegisterSignal(usr.client, COMSIG_CLIENT_CLICK, PROC_REF(clicked_while_open))
else
UnregisterSignal(usr.client, COMSIG_CLIENT_CLICK)
closeToolTip(usr) //Our tooltips are now invalid, can't seem to update them in one frame, so here, just close them
/atom/movable/screen/palette_scroll
icon = 'icons/hud/screen_gen.dmi'
screen_loc = ui_palette_scroll
/// How should we move the palette's actions?
/// Positive scrolls down the list, negative scrolls back
var/scroll_direction = 0
var/datum/hud/our_hud
/atom/movable/screen/palette_scroll/proc/can_use(mob/user)
if (isobserver(user))
var/mob/dead/observer/O = user
return !O.observetarget
return TRUE
/atom/movable/screen/palette_scroll/proc/set_hud(datum/hud/our_hud)
src.our_hud = our_hud
refresh_owner()
/atom/movable/screen/palette_scroll/proc/refresh_owner()
var/mob/viewer = our_hud.mymob
if(viewer.client)
viewer.client.screen |= src
var/list/settings = our_hud.get_action_buttons_icons()
icon = settings["bg_icon"]
/atom/movable/screen/palette_scroll/Click(location, control, params)
if(!can_use(usr))
return
our_hud.palette_actions.scroll(scroll_direction)
/atom/movable/screen/palette_scroll/MouseEntered(location, control, params)
. = ..()
if(QDELETED(src))
return
openToolTip(usr, src, params, title = name, content = desc)
/atom/movable/screen/palette_scroll/MouseExited()
closeToolTip(usr)
return ..()
/atom/movable/screen/palette_scroll/down
name = "Scroll Down"
desc = "<b>Click</b> on this to scroll the actions above down"
icon_state = "scroll_down"
scroll_direction = 1
/atom/movable/screen/palette_scroll/down/Destroy()
if(our_hud)
our_hud.mymob?.client?.screen -= src
our_hud.palette_down = null
our_hud = null
return ..()
/atom/movable/screen/palette_scroll/up
name = "Scroll Up"
desc = "<b>Click</b> on this to scroll the actions above up"
icon_state = "scroll_up"
scroll_direction = -1
/atom/movable/screen/palette_scroll/up/Destroy()
if(our_hud)
our_hud.mymob?.client?.screen -= src
our_hud.palette_up = null
our_hud = null
return ..()
/// Exists so you have a place to put your buttons when you move them around
/atom/movable/screen/action_landing
name = "Button Space"
desc = "<b>Drag and drop</b> a button into this spot<br>to add it to the group"
icon = 'icons/hud/screen_gen.dmi'
icon_state = "reserved"
// We want our whole 32x32 space to be clickable, so dropping's forgiving
mouse_opacity = MOUSE_OPACITY_OPAQUE
var/datum/action_group/owner
/atom/movable/screen/action_landing/Destroy()
if(owner)
owner.landing = null
owner?.owner?.mymob?.client?.screen -= src
owner.refresh_actions()
owner = null
return ..()
/atom/movable/screen/action_landing/proc/set_owner(datum/action_group/owner)
src.owner = owner
refresh_owner()
/atom/movable/screen/action_landing/proc/refresh_owner()
var/datum/hud/our_hud = owner.owner
var/mob/viewer = our_hud.mymob
if(viewer.client)
viewer.client.screen |= src
var/list/settings = our_hud.get_action_buttons_icons()
icon = settings["bg_icon"]
/// Reacts to having a button dropped on it
/atom/movable/screen/action_landing/proc/hit_by(atom/movable/screen/movable/action_button/button)
var/datum/hud/our_hud = owner.owner
our_hud.position_action(button, owner.location)