refactor action buttons (#29416)

* refactor action buttons

* fix cult spell charge overlay

* lewc review

* update for a/mhelp buttons and xenobio organs

* update for minebot

* properly create button each time

* directly add/remove unavail overlay for reasons

* lewc review
This commit is contained in:
warriorstar-orion
2025-07-28 19:13:28 +00:00
committed by GitHub
parent f9016e65ff
commit 56f4960ed4
115 changed files with 815 additions and 584 deletions
+175 -50
View File
@@ -3,14 +3,22 @@
var/desc = null
var/obj/target = null
var/check_flags = 0
/// Icon that our button screen object overlay and background
var/button_overlay_icon = 'icons/mob/actions/actions.dmi'
/// Icon state of screen object overlay
var/button_overlay_icon_state = ACTION_BUTTON_DEFAULT_OVERLAY
/// Icon that our button screen object background will have
var/button_background_icon = 'icons/mob/actions/actions.dmi'
/// This is the icon state state for the BACKGROUND underlay icon of the button
/// (If set to ACTION_BUTTON_DEFAULT_BACKGROUND, uses the hud's default background)
var/background_icon = 'icons/mob/actions/actions.dmi'
/// Icon state of screen object background
var/button_background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
/// This is the file for the icon that appears on the button
var/button_icon = 'icons/mob/actions/actions.dmi'
/// This is the icon state for the icon that appears on the button
var/button_icon_state = ACTION_BUTTON_DEFAULT_OVERLAY
/// This is the file for any FOREGROUND overlay icons on the button (such as borders)
var/overlay_icon = 'icons/mob/actions/actions.dmi'
/// This is the icon state for any FOREGROUND overlay icons on the button (such as borders)
var/overlay_icon_state
var/buttontooltipstyle = ""
var/transparent_when_unavailable = TRUE
var/mob/owner
@@ -20,10 +28,21 @@
var/list/viewers = list()
/// Whether or not this will be shown to observers
var/show_to_observers = TRUE
/// Toggles whether this action is usable or not
var/action_disabled = FALSE
/// The appearance used as an overlay for when the action is unavailable
var/mutable_appearance/unavailable_effect
/datum/action/New(target)
link_to(target)
/datum/action/New(Target)
target = Target
/// Links the passed target to our action, registering any relevant signals
/datum/action/proc/link_to(target_)
target = target_
RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
if(isatom(target))
RegisterSignal(target, COMSIG_ATOM_UPDATED_ICON, PROC_REF(on_target_icon_update))
/datum/action/proc/should_draw_cooldown()
return !IsAvailable()
@@ -79,10 +98,10 @@
UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
owner = null
/datum/action/proc/UpdateButtons(status_only, force)
for(var/datum/hud/hud in viewers)
var/atom/movable/screen/movable/button = viewers[hud]
UpdateButton(button, status_only, force)
/// Builds / updates all buttons we have shared or given out
/datum/action/proc/build_all_button_icons(update_flags = ALL, force)
for(var/datum/hud/hud as anything in viewers)
build_button_icon(viewers[hud], update_flags, force)
/datum/action/proc/Trigger(left_click = TRUE)
if(!IsAvailable())
@@ -123,31 +142,60 @@
return FALSE
return TRUE
/datum/action/proc/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
/**
* Builds the icon of the button.
*
* Concept:
* - Underlay (Background icon)
* - Icon (button icon)
* - Maptext
* - Overlay (Background border)
*
* button - which button we are modifying the icon of
* force - whether we're forcing a full update
*/
/datum/action/proc/build_button_icon(atom/movable/screen/movable/action_button/button, update_flags = ALL, force = FALSE)
if(!button)
return
if(!status_only)
button.name = name
if(desc)
button.desc = "[desc] [initial(button.desc)]"
if(owner?.hud_used && button_background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND)
var/list/settings = owner.hud_used.get_action_buttons_icons()
if(button.icon != settings["bg_icon"])
button.icon = settings["bg_icon"]
if(button.icon_state != settings["bg_state"])
button.icon_state = settings["bg_state"]
else
if(button.icon != button_background_icon)
button.icon = button_background_icon
if(button.icon_state != button_background_icon_state)
button.icon_state = button_background_icon_state
if(update_flags & UPDATE_BUTTON_NAME)
update_button_name(button, force)
if(update_flags & UPDATE_BUTTON_BACKGROUND)
apply_button_background(button, force)
if(update_flags & UPDATE_BUTTON_ICON)
apply_button_icon(button, force)
if(update_flags & UPDATE_BUTTON_OVERLAY)
apply_button_overlay(button, force)
if(update_flags & UPDATE_BUTTON_STATUS)
update_button_status(button, force)
/**
* Updates the name and description of the button to match our action name and discription.
*
* button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/update_button_name(atom/movable/screen/movable/action_button/button, force = FALSE)
button.name = name
if(desc)
button.desc = desc
/**
* Any other miscellaneous "status" updates within the action button is handled here,
* such as redding out when unavailable or modifying maptext.
*
* button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/update_button_status(atom/movable/screen/movable/action_button/button, force = FALSE)
button.overlays -= unavailable_effect
button.maptext = ""
if(should_draw_cooldown())
apply_unavailable_effect(button)
else
return TRUE
//Give our action button to the player
/datum/action/proc/GiveAction(mob/viewer)
@@ -164,7 +212,7 @@
return
var/atom/movable/screen/movable/action_button/button = CreateButton()
var/atom/movable/screen/movable/action_button/button = create_button()
SetId(button, viewer)
button.our_hud = our_hud
@@ -186,10 +234,11 @@
qdel(button)
/datum/action/proc/CreateButton()
/datum/action/proc/create_button()
var/atom/movable/screen/movable/action_button/button = new()
button.linked_action = src
button.name = name
build_button_icon(button, ALL, force = TRUE)
// TODO: Pull all of this into the build button structure somehow later
button.actiontooltipstyle = buttontooltipstyle
var/list/our_description = list()
our_description += desc
@@ -197,7 +246,6 @@
button.desc = our_description.Join(" ")
return button
/datum/action/proc/SetId(atom/movable/screen/movable/action_button/our_button, mob/owner)
//button id generation
var/bitfield = 0
@@ -215,20 +263,97 @@
our_button.id = bitflag
return
/datum/action/proc/apply_unavailable_effect(atom/movable/screen/movable/action_button/B)
var/image/img = image('icons/mob/screen_white.dmi', icon_state = "template")
img.alpha = 200
img.appearance_flags = RESET_COLOR | RESET_ALPHA
img.color = "#000000"
img.plane = FLOAT_PLANE + 1
B.add_overlay(img)
/datum/action/proc/apply_unavailable_effect(atom/movable/screen/movable/action_button/button)
if(isnull(unavailable_effect))
unavailable_effect = mutable_appearance('icons/mob/screen_white.dmi', icon_state = "template")
unavailable_effect.alpha = 200
unavailable_effect.appearance_flags = RESET_COLOR | RESET_ALPHA
unavailable_effect.color = "#000000"
unavailable_effect.plane = FLOAT_PLANE + 1
button.overlays |= unavailable_effect
/**
* Applies any overlays to our button
*
* button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/apply_button_overlay(atom/movable/screen/movable/action_button/button, force = FALSE)
SEND_SIGNAL(src, COMSIG_ACTION_OVERLAY_APPLY, button, force)
/datum/action/proc/apply_button_overlay(atom/movable/screen/movable/action_button/current_button)
current_button.cut_overlays()
if(button_overlay_icon && button_overlay_icon_state)
var/image/img = image(button_overlay_icon, current_button, button_overlay_icon_state)
img.appearance_flags = RESET_COLOR | RESET_ALPHA
img.pixel_x = 0
img.pixel_y = 0
current_button.add_overlay(img)
if(!overlay_icon || !overlay_icon_state || (button.active_overlay_icon_state == overlay_icon_state && !force))
return
button.cut_overlay(button.button_overlay)
button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = overlay_icon_state, appearance_flags = (RESET_COLOR|RESET_ALPHA))
button.add_overlay(button.button_overlay)
button.active_overlay_icon_state = overlay_icon_state
/// Checks if our action is actively selected. Used for selecting icons primarily.
/datum/action/proc/is_action_active(atom/movable/screen/movable/action_button/button)
return FALSE
/**
* Creates the background underlay for the button
*
* button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/apply_button_background(atom/movable/screen/movable/action_button/button, force = FALSE)
if(!background_icon || !background_icon_state || (button.active_underlay_icon_state == background_icon_state && !force))
return
// What icons we use for our background
var/list/icon_settings = list(
// The icon file
"bg_icon" = background_icon,
// The icon state, if is_action_active() returns FALSE
"bg_state" = background_icon_state,
// The icon state, if is_action_active() returns TRUE
"bg_state_active" = background_icon_state,
)
// If background_icon_state is ACTION_BUTTON_DEFAULT_BACKGROUND instead use our hud's action button scheme
if(background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND && owner?.hud_used)
icon_settings = owner.hud_used.get_action_buttons_icons()
// Determine which icon to use
var/used_icon_key = is_action_active(button) ? "bg_state_active" : "bg_state"
// Make the underlay
button.underlays.Cut()
var/image/underlay = image(icon = icon_settings["bg_icon"], icon_state = icon_settings[used_icon_key])
button.underlays += underlay
button.active_underlay_icon_state = icon_settings[used_icon_key]
/**
* Applies our button icon and icon state to the button
*
* button - what button are we editing?
* force - whether an update is forced regardless of existing status
*/
/datum/action/proc/apply_button_icon(atom/movable/screen/movable/action_button/button, force = FALSE)
if(!button_icon || !button_icon_state || (button.icon_state == button_icon_state && !force))
return
button.icon = button_icon
button.icon_state = button_icon_state
/// Updates our buttons if our target's icon was updated
/datum/action/proc/on_target_icon_update(datum/source, updates, updated)
SIGNAL_HANDLER // COMSIG_ATOM_UPDATED_ICON
var/update_flag = NONE
var/forced = FALSE
if(updates & UPDATE_ICON_STATE)
update_flag |= UPDATE_BUTTON_ICON
forced = TRUE
if(updates & UPDATE_OVERLAYS)
update_flag |= UPDATE_BUTTON_OVERLAY
forced = TRUE
if(updates & (UPDATE_NAME|UPDATE_DESC))
update_flag |= UPDATE_BUTTON_NAME
// Status is not relevant, and background is not relevant. Neither will change
// Force the update if an icon state or overlay change was done
build_all_button_icons(update_flag, forced)
+22 -51
View File
@@ -1,17 +1,15 @@
//Presets for item actions
/datum/action/item_action
check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_HANDS_BLOCKED|AB_CHECK_CONSCIOUS
var/use_itemicon = TRUE
button_icon_state = null
/datum/action/item_action/New(Target, custom_icon, custom_icon_state)
/datum/action/item_action/New(Target)
..()
var/obj/item/I = target
I.actions += src
if(custom_icon && custom_icon_state)
use_itemicon = FALSE
button_overlay_icon = custom_icon
button_overlay_icon_state = custom_icon_state
UpdateButtons()
// If our button state is null, use the target's icon instead
if(target && isnull(button_icon_state))
AddComponent(/datum/component/action_item_overlay, target)
/datum/action/item_action/Destroy()
var/obj/item/I = target
@@ -27,25 +25,6 @@
I.ui_action_click(owner, type, left_click)
return TRUE
/datum/action/item_action/apply_button_overlay(atom/movable/screen/movable/action_button/current_button)
if(use_itemicon)
if(target)
var/obj/item/I = target
var/old_layer = I.layer
var/old_plane = I.plane
var/old_appearance_flags = I.appearance_flags
I.layer = FLOAT_LAYER //AAAH
I.plane = FLOAT_PLANE //^ what that guy said
I.appearance_flags |= RESET_COLOR | RESET_ALPHA
current_button.cut_overlays()
current_button.add_overlay(I)
I.layer = old_layer
I.plane = old_plane
I.appearance_flags = old_appearance_flags
else
..()
/datum/action/item_action/toggle_light
name = "Toggle Light"
@@ -60,13 +39,11 @@
/datum/action/item_action/print_forensic_report
name = "Print Report"
button_overlay_icon_state = "scanner_print"
use_itemicon = FALSE
button_icon_state = "scanner_print"
/datum/action/item_action/clear_records
name = "Clear Scanner Records"
button_overlay_icon_state = "scanner_clear"
use_itemicon = FALSE
button_icon_state = "scanner_clear"
/datum/action/item_action/toggle_gunlight
name = "Toggle Gunlight"
@@ -86,14 +63,9 @@
/datum/action/item_action/set_internals
name = "Set Internals"
/datum/action/item_action/set_internals/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force)
if(!..()) // no button available
return
if(!iscarbon(owner))
return
var/mob/living/carbon/C = owner
if(target == C.internal)
button.icon_state = "template_active"
/datum/action/item_action/set_internals/is_action_active(atom/movable/screen/movable/action_button/button)
var/mob/living/carbon/carbon_owner = owner
return istype(carbon_owner) && target == carbon_owner.internal
/datum/action/item_action/toggle_mister
name = "Toggle Mister"
@@ -121,27 +93,27 @@
/datum/action/item_action/toggle_unfriendly_fire
name = "Toggle Friendly Fire \[ON\]"
desc = "Toggles if the club's blasts cause friendly fire."
button_overlay_icon_state = "vortex_ff_on"
button_icon_state = "vortex_ff_on"
/datum/action/item_action/toggle_unfriendly_fire/Trigger(left_click)
if(..())
UpdateButtons()
build_all_button_icons()
/datum/action/item_action/toggle_unfriendly_fire/UpdateButtons()
/datum/action/item_action/toggle_unfriendly_fire/build_all_button_icons(update_flags, force)
if(istype(target, /obj/item/hierophant_club))
var/obj/item/hierophant_club/H = target
if(H.friendly_fire_check)
button_overlay_icon_state = "vortex_ff_off"
button_icon_state = "vortex_ff_off"
name = "Toggle Friendly Fire \[OFF\]"
else
button_overlay_icon_state = "vortex_ff_on"
button_icon_state = "vortex_ff_on"
name = "Toggle Friendly Fire \[ON\]"
..()
/datum/action/item_action/vortex_recall
name = "Vortex Recall"
desc = "Recall yourself, and anyone nearby, to an attuned hierophant beacon at any time.<br>If the beacon is still attached, will detach it."
button_overlay_icon_state = "vortex_recall"
button_icon_state = "vortex_recall"
/datum/action/item_action/vortex_recall/IsAvailable()
if(istype(target, /obj/item/hierophant_club))
@@ -278,7 +250,7 @@
/datum/action/item_action/toggle_research_scanner
name = "Toggle Research Scanner"
button_overlay_icon_state = "scan_mode"
button_icon_state = "scan_mode"
/datum/action/item_action/toggle_research_scanner/Trigger(left_click)
if(IsAvailable())
@@ -293,8 +265,8 @@
/datum/action/item_action/toggle_research_scanner/apply_button_overlay(atom/movable/screen/movable/action_button/current_button)
current_button.cut_overlays()
if(button_overlay_icon && button_overlay_icon_state)
var/image/img = image(button_overlay_icon, current_button, "scan_mode")
if(button_icon && button_icon_state)
var/image/img = image(button_icon, current_button, "scan_mode")
img.appearance_flags = RESET_COLOR | RESET_ALPHA
current_button.overlays += img
@@ -320,15 +292,13 @@
/datum/action/item_action/slipping
name = "Tactical Slip"
desc = "Activates the clown shoes' ankle-stimulating module, allowing the user to do a short slip forward going under anyone."
button_overlay_icon_state = "clown"
button_icon_state = "clown"
// Jump boots
/datum/action/item_action/bhop
name = "Activate Jump Boots"
desc = "Activates the jump boot's internal propulsion system, allowing the user to dash over 4-wide gaps."
button_overlay_icon_state = "jetboot"
use_itemicon = FALSE
button_icon_state = "jetboot"
/datum/action/item_action/gravity_jump
name = "Gravity jump"
@@ -408,3 +378,4 @@
/datum/action/item_action/call_link
name = "Call MODlink"
+16 -17
View File
@@ -1,6 +1,6 @@
//Preset for spells
/datum/action/spell_action
button_background_icon_state = "bg_spell"
background_icon_state = "bg_spell"
var/recharge_text_color = "#FFFFFF"
/datum/action/spell_action/New(Target)
@@ -9,12 +9,11 @@
S.action = src
name = S.name
desc = S.desc
button_overlay_icon = S.action_icon
button_background_icon = S.action_background_icon
button_overlay_icon_state = S.action_icon_state
button_background_icon_state = S.action_background_icon_state
UpdateButtons()
button_icon = S.action_icon
button_icon_state = S.action_icon_state
background_icon = S.action_background_icon
background_icon_state = S.action_background_icon_state
build_all_button_icons()
/datum/action/spell_action/Destroy()
var/datum/spell/S = target
@@ -53,17 +52,17 @@
if(!istype(S))
return ..()
var/alpha = S.cooldown_handler.get_cooldown_alpha()
unavailable_effect = mutable_appearance('icons/mob/screen_white.dmi', icon_state = "template")
unavailable_effect.appearance_flags = RESET_COLOR | RESET_ALPHA
unavailable_effect.color = "#000000"
unavailable_effect.plane = FLOAT_PLANE + 1
unavailable_effect.alpha = S.cooldown_handler.get_cooldown_alpha()
var/image/img = image('icons/mob/screen_white.dmi', icon_state = "template")
img.alpha = alpha
img.appearance_flags = RESET_COLOR | RESET_ALPHA
img.color = "#000000"
img.plane = FLOAT_PLANE + 1
button.add_overlay(img)
// Make a holder for the charge text
var/image/count_down_holder = image('icons/effects/effects.dmi', icon_state = "nothing")
count_down_holder.plane = FLOAT_PLANE + 1.1
var/image/count_down_holder = mutable_appearance('icons/effects/effects.dmi', icon_state = "nothing", appearance_flags = RESET_COLOR | RESET_ALPHA)
var/text = S.cooldown_handler.cooldown_info()
count_down_holder.maptext_y = 4
count_down_holder.maptext = "<div style=\"font-size:6pt;color:[recharge_text_color];font:'Small Fonts';text-align:center;\" valign=\"bottom\">[text]</div>"
button.add_overlay(count_down_holder)
unavailable_effect.add_overlay(count_down_holder)
button.overlays |= unavailable_effect