diff --git a/code/__defines/action.dm b/code/__defines/action.dm
index 3f9b8a5e41..25babfe390 100644
--- a/code/__defines/action.dm
+++ b/code/__defines/action.dm
@@ -2,3 +2,16 @@
#define AB_CHECK_STUNNED 2
#define AB_CHECK_LYING 4
#define AB_CHECK_CONSCIOUS 8
+
+///Action button triggered with right click
+#define TRIGGER_SECONDARY_ACTION (1<<0)
+///Action triggered to ignore any availability checks
+#define TRIGGER_FORCE_AVAILABLE (1<<1)
+
+#define ACTION_BUTTON_DEFAULT_BACKGROUND "_use_ui_default_background"
+
+#define UPDATE_BUTTON_NAME (1<<0)
+#define UPDATE_BUTTON_ICON (1<<1)
+#define UPDATE_BUTTON_BACKGROUND (1<<2)
+#define UPDATE_BUTTON_OVERLAY (1<<3)
+#define UPDATE_BUTTON_STATUS (1<<4)
diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm
index 2623cadaaf..5faf4792d7 100644
--- a/code/__defines/dcs/signals.dm
+++ b/code/__defines/dcs/signals.dm
@@ -191,6 +191,8 @@
#define COMSIG_ENTER_AREA "enter_area"
///from base of area/Exited(): (/area)
#define COMSIG_EXIT_AREA "exit_area"
+///from base of client/Click(): (atom/target, atom/location, control, params, mob/user)
+#define COMSIG_CLIENT_CLICK "atom_client_click"
///from base of atom/Click(): (location, control, params, mob/user)
#define COMSIG_CLICK "atom_click"
///from base of atom/ShiftClick(): (/mob)
@@ -714,6 +716,16 @@
///from base of datum/action/proc/Trigger(): (datum/action)
#define COMSIG_ACTION_TRIGGER "action_trigger"
#define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0)
+/// From /datum/action/Grant(): (mob/grant_to)
+#define COMSIG_ACTION_GRANTED "action_grant"
+/// From /datum/action/Remove(): (mob/removed_from)
+#define COMSIG_ACTION_REMOVED "action_removed"
+/// From /datum/action/Grant(): (datum/action)
+#define COMSIG_MOB_GRANTED_ACTION "mob_action_grant"
+/// From /datum/action/Remove(): (datum/action)
+#define COMSIG_MOB_REMOVED_ACTION "mob_action_removed"
+/// From /datum/action/apply_button_overlay()
+#define COMSIG_ACTION_OVERLAY_APPLY "action_overlay_applied"
//Xenobio hotkeys
diff --git a/code/__defines/flags.dm b/code/__defines/flags.dm
index ae83c705de..3cbce52bc0 100644
--- a/code/__defines/flags.dm
+++ b/code/__defines/flags.dm
@@ -57,3 +57,19 @@ GLOBAL_LIST_INIT(bitflags, list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 204
#define TASK_TARGET_EXCLUSIVE (1<<1)
#define TASK_USER_EXCLUSIVE (1<<2)
#define TASK_ALL_EXCLUSIVE TASK_TARGET_EXCLUSIVE | TASK_USER_EXCLUSIVE
+
+// Update flags for [/atom/proc/update_appearance]
+/// Update the atom's name
+#define UPDATE_NAME (1<<0)
+/// Update the atom's desc
+#define UPDATE_DESC (1<<1)
+/// Update the atom's icon state
+#define UPDATE_ICON_STATE (1<<2)
+/// Update the atom's overlays
+#define UPDATE_OVERLAYS (1<<3)
+/// Update the atom's greyscaling
+#define UPDATE_GREYSCALE (1<<4)
+/// Update the atom's smoothing. (More accurately, queue it for an update)
+#define UPDATE_SMOOTHING (1<<5)
+/// Update the atom's icon
+#define UPDATE_ICON (UPDATE_ICON_STATE|UPDATE_OVERLAYS)
diff --git a/code/_onclick/hud/_defines.dm b/code/__defines/hud.dm
similarity index 94%
rename from code/_onclick/hud/_defines.dm
rename to code/__defines/hud.dm
index e89b3beb2f..0291f2e4fc 100644
--- a/code/_onclick/hud/_defines.dm
+++ b/code/__defines/hud.dm
@@ -203,3 +203,14 @@
#define ui_mech_deco2_f "WEST+2:-7, SOUTH+9"
#define ui_smallquad "EAST-4:22,SOUTH:5"
+
+//Upper left (action buttons)
+#define ui_action_palette "WEST+0:23,NORTH-1:5"
+#define ui_action_palette_offset(north_offset) ("WEST+0:23,NORTH-[1+north_offset]:5")
+
+#define ui_palette_scroll "WEST+1:8,NORTH-6:28"
+#define ui_palette_scroll_offset(north_offset) ("WEST+1:8,NORTH-[6+north_offset]:28")
+
+#define ui_shadekin_display "EAST-1:28,CENTER-3:15"
+#define ui_lleill_display "EAST-1:28,CENTER-3:15"
+#define ui_xenochimera_danger_display "EAST-1:28,CENTER-3:15"
diff --git a/code/_helpers/weakref.dm b/code/_helpers/weakref.dm
new file mode 100644
index 0000000000..25b65e63ec
--- /dev/null
+++ b/code/_helpers/weakref.dm
@@ -0,0 +1,2 @@
+/// Checks if potential_weakref is a weakref of thing.
+#define IS_WEAKREF_OF(thing, potential_weakref) (isdatum(thing) && !isnull(potential_weakref) && thing.weak_reference == potential_weakref)
diff --git a/code/_onclick/hud/_defines_vr.dm b/code/_onclick/hud/_defines_vr.dm
deleted file mode 100644
index 82d0af8b02..0000000000
--- a/code/_onclick/hud/_defines_vr.dm
+++ /dev/null
@@ -1,3 +0,0 @@
-#define ui_shadekin_display "EAST-1:28,CENTER-3:15"
-#define ui_lleill_display "EAST-1:28,CENTER-3:15"
-#define ui_xenochimera_danger_display "EAST-1:28,CENTER-3:15"
\ No newline at end of file
diff --git a/code/_onclick/hud/action/action.dm b/code/_onclick/hud/action/action.dm
index d1f4441c7d..a5a512be68 100644
--- a/code/_onclick/hud/action/action.dm
+++ b/code/_onclick/hud/action/action.dm
@@ -1,96 +1,130 @@
+/**
+ * # Action system
+ *
+ * A simple base for an modular behavior attached to atom or datum.
+ */
/datum/action
+ /// The name of the action
var/name = "Generic Action"
- var/desc = null
-
- var/atom/movable/target = null
-
+ /// The description of what the action does, shown in button tooltips
+ var/desc
+ /// The target the action is attached to. If the target datum is deleted, the action is as well.
+ /// Set in New() via the proc link_to(). PLEASE set a target if you're making an action
+ var/datum/target
+ /// Where any buttons we create should be by default. Accepts screen_loc and location defines
+ var/default_button_position = SCRN_OBJ_IN_LIST
+ /// This is who currently owns the action, and most often, this is who is using the action if it is triggered
+ /// This can be the same as "target" but is not ALWAYS the same - this is set and unset with Grant() and Remove()
+ var/mob/owner
+ /// Flags that will determine of the owner / user of the action can... use the action
var/check_flags = NONE
- var/processing = FALSE
-
- var/obj/screen/movable/action_button/button = null
-
- var/button_icon = 'icons/mob/actions.dmi'
- var/background_icon_state = "bg_default"
- var/buttontooltipstyle = ""
+ /// Whether the button becomes transparent when it can't be used or just reddened
var/transparent_when_unavailable = TRUE
+ /// List of all mobs that are viewing our action button -> A unique movable for them to view.
+ var/list/viewers = list()
+ /// If TRUE, this action button will be shown to observers / other mobs who view from this action's owner's eyes.
+ /// Used in [/mob/proc/show_other_mob_action_buttons]
+ /// (Not really, this behavior is unimplemented)
+ var/show_to_observers = TRUE
- var/icon_icon = 'icons/mob/actions.dmi'
+ /// The style the button's tooltips appear to be
+ var/buttontooltipstyle = ""
+
+ /// This is the file for the BACKGROUND underlay icon of the button
+ var/background_icon = 'icons/mob/actions/backgrounds.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_state = ACTION_BUTTON_DEFAULT_BACKGROUND
+
+ /// This is the file for the icon that appears on the button
+ var/button_icon = 'icons/mob/actions.dmi'
+ /// This is the icon state for the icon that appears on the button
var/button_icon_state = "default"
- var/mob/owner
+ /// This is the file for any FOREGROUND overlay icons on the button (such as borders)
+ var/overlay_icon = 'icons/mob/actions/backgrounds.dmi'
+ /// This is the icon state for any FOREGROUND overlay icons on the button (such as borders)
+ var/overlay_icon_state
/datum/action/New(Target)
link_to(Target)
- button = new
- button.linked_action = src
- button.name = name
- button.actiontooltipstyle = buttontooltipstyle
- if(desc)
- button.desc = desc
+/// Links the passed target to our action, registering any relevant signals
/datum/action/proc/link_to(Target)
target = Target
- RegisterSignal(Target, COMSIG_ATOM_UPDATED_ICON, .proc/OnUpdatedIcon)
+ 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))
+
+ // if(istype(target, /datum/mind))
+ // RegisterSignal(target, COMSIG_MIND_TRANSFERRED, .proc/on_target_mind_swapped)
/datum/action/Destroy()
if(owner)
Remove(owner)
target = null
- QDEL_NULL(button)
+ QDEL_LIST_ASSOC_VAL(viewers)
return ..()
-/datum/action/proc/Grant(mob/M)
- if(M)
- if(owner)
- if(owner == M)
- return
- Remove(owner)
- owner = M
+/// Signal proc that clears any references based on the owner or target deleting
+/// If the owner's deleted, we will simply remove from them, but if the target's deleted, we will self-delete
+/datum/action/proc/clear_ref(datum/ref)
+ SIGNAL_HANDLER
+ if(ref == owner)
+ Remove(owner)
+ if(ref == target)
+ qdel(src)
- // button id generation
- var/counter = 0
- var/bitfield = 0
- for(var/datum/action/A as anything in M.actions)
- if(A.name == name && A.button.id)
- counter += 1
- bitfield |= A.button.id
- bitfield = !bitfield
- var/bitflag = 1
- for(var/i in 1 to (counter + 1))
- if(bitfield & bitflag)
- button.id = bitflag
- break
- bitflag *= 2
-
- LAZYADD(M.actions, src)
- if(M.client)
- M.client.screen += button
- button.locked = /* M.client.prefs.buttons_locked || */ button.id ? LAZYACCESS(M.client.prefs.action_button_screen_locs, "[name]_[button.id]") : FALSE //even if its not defaultly locked we should remember we locked it before
- button.moved = button.id ? LAZYACCESS(M.client.prefs.action_button_screen_locs, "[name]_[button.id]") : FALSE
- M.update_action_buttons(TRUE)
- else
+/// Grants the action to the passed mob, making it the owner
+/datum/action/proc/Grant(mob/grant_to)
+ if(!grant_to)
+ Remove(owner)
+ return
+ if(owner)
+ if(owner == grant_to)
+ return
Remove(owner)
-/datum/action/proc/Remove(mob/M)
- if(M)
- if(M.client)
- M.client.screen -= button
- button.moved = FALSE
- LAZYREMOVE(M.actions, src)
- M.update_action_buttons(TRUE)
- owner = null
- button.moved = FALSE //so the button appears in its normal position when given to another owner.
- button.locked = FALSE
- button.id = null
+ SEND_SIGNAL(src, COMSIG_ACTION_GRANTED, grant_to)
+ SEND_SIGNAL(grant_to, COMSIG_MOB_GRANTED_ACTION, src)
+ owner = grant_to
+ RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
-/datum/action/proc/Trigger()
+ GiveAction(grant_to)
+
+/// Remove the passed mob from being owner of our action
+/datum/action/proc/Remove(mob/remove_from)
+ SHOULD_CALL_PARENT(TRUE)
+
+ for(var/datum/hud/hud in viewers)
+ if(!hud.mymob)
+ continue
+ HideFrom(hud.mymob)
+ LAZYREMOVE(remove_from.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
+ viewers = list()
+
+ if(owner)
+ SEND_SIGNAL(src, COMSIG_ACTION_REMOVED, owner)
+ SEND_SIGNAL(owner, COMSIG_MOB_REMOVED_ACTION, src)
+
+ UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
+ if(target == owner)
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref))
+
+ owner = null
+
+/// Actually triggers the effects of the action.
+/// Called when the on-screen button is clicked, for example.
+/datum/action/proc/Trigger(trigger_flags)
if(!IsAvailable())
return FALSE
if(SEND_SIGNAL(src, COMSIG_ACTION_TRIGGER, src) & COMPONENT_ACTION_BLOCK_TRIGGER)
return FALSE
return TRUE
+/// Whether our action is currently available to use or not
/datum/action/proc/IsAvailable()
if(!owner)
return FALSE
@@ -108,147 +142,207 @@
return FALSE
return TRUE
-/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE)
+/// 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)
+
+/**
+ * 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(obj/screen/movable/action_button/button, update_flags = ALL, force = FALSE)
+ if(!button)
+ return
+
+ 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.
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/update_button_name(obj/screen/movable/action_button/button, force = FALSE)
+ button.name = name
+ if(desc)
+ button.desc = desc
+
+/**
+ * Creates the background underlay for the button
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/apply_button_background(obj/screen/movable/action_button/current_button, force = FALSE)
+ if(!background_icon || !background_icon_state || (current_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(current_button) ? "bg_state_active" : "bg_state"
+
+ // Make the underlay
+ current_button.underlays.Cut()
+ current_button.underlays += image(icon = icon_settings["bg_icon"], icon_state = icon_settings[used_icon_key])
+ current_button.active_underlay_icon_state = icon_settings[used_icon_key]
+
+/**
+ * Applies our button icon and icon state to the button
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/apply_button_icon(obj/screen/movable/action_button/current_button, force = FALSE)
+ if(!button_icon || !button_icon_state || (current_button.icon_state == button_icon_state && !force))
+ return
+
+ current_button.icon = button_icon
+ current_button.icon_state = button_icon_state
+
+/**
+ * Applies any overlays to our button
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/apply_button_overlay(obj/screen/movable/action_button/current_button, force = FALSE)
+ SEND_SIGNAL(src, COMSIG_ACTION_OVERLAY_APPLY, current_button, force)
+
+ if(!overlay_icon || !overlay_icon_state || (current_button.active_overlay_icon_state == overlay_icon_state && !force))
+ return
+
+ current_button.cut_overlay(current_button.button_overlay)
+ current_button.button_overlay = mutable_appearance(icon = overlay_icon, icon_state = overlay_icon_state)
+ current_button.add_overlay(current_button.button_overlay)
+ current_button.active_overlay_icon_state = overlay_icon_state
+
+/**
+ * Any other miscellaneous "status" updates within the action button is handled here,
+ * such as redding out when unavailable or modifying maptext.
+ *
+ * current_button - what button are we editing?
+ * force - whether an update is forced regardless of existing status
+ */
+/datum/action/proc/update_button_status(obj/screen/movable/action_button/current_button, force = FALSE)
+ if(IsAvailable())
+ current_button.color = rgb(255,255,255,255)
+ else
+ current_button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
+
+/// Gives our action to the passed viewer.
+/// Puts our action in their actions list and shows them the button.
+/datum/action/proc/GiveAction(mob/viewer)
+ var/datum/hud/our_hud = viewer.hud_used
+ if(viewers[our_hud]) // Already have a copy of us? go away
+ return
+
+ LAZYOR(viewer.actions, src) // Move this in
+ ShowTo(viewer)
+
+/// Adds our action button to the screen of the passed viewer.
+/datum/action/proc/ShowTo(mob/viewer)
+ var/datum/hud/our_hud = viewer.hud_used
+ if(!our_hud || viewers[our_hud]) // There's no point in this if you have no hud in the first place
+ return
+
+ var/obj/screen/movable/action_button/button = create_button()
+ SetId(button, viewer)
+
+ button.our_hud = our_hud
+ viewers[our_hud] = button
+ if(viewer.client)
+ viewer.client.screen += button
+
+ button.load_position(viewer)
+ viewer.update_action_buttons()
+
+/// Removes our action from the passed viewer.
+/datum/action/proc/HideFrom(mob/viewer)
+ var/datum/hud/our_hud = viewer.hud_used
+ var/obj/screen/movable/action_button/button = viewers[our_hud]
+ LAZYREMOVE(viewer.actions, src)
if(button)
- if(!status_only)
- button.name = name
- button.desc = desc
+ qdel(button)
- // if(owner && owner.hud_used && 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
+/// Creates an action button movable for the passed mob, and returns it.
+/datum/action/proc/create_button()
+ var/obj/screen/movable/action_button/button = new()
+ button.linked_action = src
+ build_button_icon(button, ALL, TRUE)
+ return button
- if(button.icon != button_icon)
- button.icon = button_icon
- if(button.icon_state != background_icon_state)
- button.icon_state = background_icon_state
+/datum/action/proc/SetId(obj/screen/movable/action_button/our_button, mob/owner)
+ //button id generation
+ var/bitfield = 0
+ for(var/datum/action/action in owner.actions)
+ if(action == src) // This could be us, which is dumb
+ continue
+ var/obj/screen/movable/action_button/button = action.viewers[owner.hud_used]
+ if(action.name == name && button.id)
+ bitfield |= button.id
- ApplyIcon(button, force)
+ bitfield = ~bitfield // Flip our possible ids, so we can check if we've found a unique one
+ for(var/i in 0 to 23) // We get 24 possible bitflags in dm
+ var/bitflag = 1 << i // Shift us over one
+ if(bitfield & bitflag)
+ our_button.id = bitflag
+ return
- if(!IsAvailable())
- button.color = transparent_when_unavailable ? rgb(128, 0, 0, 128) : rgb(128, 0, 0)
- else
- button.color = rgb(255, 255, 255, 255)
- return TRUE
-
-/datum/action/proc/ApplyIcon(obj/screen/movable/action_button/current_button, force = FALSE)
- if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force))
- current_button.cut_overlays(TRUE)
- current_button.add_overlay(mutable_appearance(icon_icon, button_icon_state))
- current_button.button_icon_state = button_icon_state
-
-// Currently never triggered
-/datum/action/proc/OnUpdatedIcon()
+/// Updates our buttons if our target's icon was updated
+/// Still never triggered lmao
+/datum/action/proc/on_target_icon_update(datum/source, updates, updated)
SIGNAL_HANDLER
- UpdateButtonIcon()
-//Presets for item actions
-/datum/action/item_action
- check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING|AB_CHECK_CONSCIOUS
- button_icon_state = null
- // If you want to override the normal icon being the item
- // then change this to an icon state
+ 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
-/datum/action/item_action/New(Target)
- . = ..()
- var/obj/item/I = target
- LAZYADD(I.actions, src)
+ // Force the update if an icon state or overlay change was done
+ build_all_button_icons(update_flag, forced)
-/datum/action/item_action/Destroy()
- var/obj/item/I = target
- LAZYREMOVE(I.actions, src)
- return ..()
-
-/datum/action/item_action/Trigger()
- if(!..())
- return 0
- if(target)
- var/obj/item/I = target
- I.ui_action_click(owner, src.type)
- return 1
-
-/datum/action/item_action/ApplyIcon(obj/screen/movable/action_button/current_button, force)
- if(button_icon && button_icon_state)
- // If set, use the custom icon that we set instead
- // of the item appearence
- return ..()
- else if(target && ((current_button.appearance_cache != target.appearance) || force))
- var/mutable_appearance/ma = new(target.appearance)
- ma.plane = FLOAT_PLANE
- ma.layer = FLOAT_LAYER
- ma.pixel_x = 0
- ma.pixel_y = 0
-
- current_button.cut_overlays()
- current_button.add_overlay(ma)
- current_button.appearance_cache = target.appearance
-
-/datum/action/item_action/hands_free
- check_flags = AB_CHECK_CONSCIOUS
-
-
-/datum/action/innate
- check_flags = NONE
- var/active = 0
-
-/datum/action/innate/Trigger()
- if(!..())
- return 0
- if(!active)
- Activate()
- else
- Deactivate()
- return 1
-
-/datum/action/innate/proc/Activate()
- return
-
-/datum/action/innate/proc/Deactivate()
- return
-
-//Preset for an action with a cooldown
-/datum/action/cooldown
- check_flags = NONE
- transparent_when_unavailable = FALSE
- var/cooldown_time = 0
- var/next_use_time = 0
-
-/datum/action/cooldown/New()
- ..()
- button.maptext = ""
- button.maptext_x = 8
- button.maptext_y = 0
- button.maptext_width = 24
- button.maptext_height = 12
-
-/datum/action/cooldown/IsAvailable()
- return next_use_time <= world.time
-
-/datum/action/cooldown/proc/StartCooldown()
- next_use_time = world.time + cooldown_time
- button.maptext = span_maptext(span_bold("[round(cooldown_time/10, 0.1)]"))
- UpdateButtonIcon()
- START_PROCESSING(SSfastprocess, src)
-
-/datum/action/cooldown/process()
- if(!owner)
- button.maptext = ""
- STOP_PROCESSING(SSfastprocess, src)
- var/timeleft = max(next_use_time - world.time, 0)
- if(timeleft == 0)
- button.maptext = ""
- UpdateButtonIcon()
- STOP_PROCESSING(SSfastprocess, src)
- else
- button.maptext = span_maptext(span_bold("[round(timeleft/10, 0.1)]"))
-
-/datum/action/cooldown/Grant(mob/M)
- ..()
- if(owner)
- UpdateButtonIcon()
- if(next_use_time > world.time)
- START_PROCESSING(SSfastprocess, src)
+/// Checks if our action is actively selected. Used for selecting icons primarily.
+/datum/action/proc/is_action_active(obj/screen/movable/action_button/current_button)
+ return FALSE
diff --git a/code/_onclick/hud/action/action_group.dm b/code/_onclick/hud/action/action_group.dm
new file mode 100644
index 0000000000..d0b2f8df11
--- /dev/null
+++ b/code/_onclick/hud/action/action_group.dm
@@ -0,0 +1,212 @@
+/datum/action_group
+ /// The hud we're owned by
+ var/datum/hud/owner
+ /// The actions we're managing
+ var/list/obj/screen/movable/action_button/actions
+ /// The initial vertical offset of our action buttons
+ var/north_offset = 0
+ /// The pixel vertical offset of our action buttons
+ var/pixel_north_offset = 0
+ /// Max amount of buttons we can have per row
+ /// Indexes at 1
+ var/column_max = 0
+ /// How far "ahead" of the first row we start. Lets us "scroll" our rows
+ /// Indexes at 1
+ var/row_offset = 0
+ /// How many rows of actions we can have at max before we just stop hiding
+ /// Indexes at 1
+ var/max_rows = INFINITY
+ /// The screen location we go by
+ var/location
+ /// Our landing screen object
+ var/obj/screen/action_landing/landing
+
+/datum/action_group/New(datum/hud/owner)
+ ..()
+ actions = list()
+ src.owner = owner
+
+/datum/action_group/Destroy()
+ owner = null
+ QDEL_NULL(landing)
+ QDEL_LIST(actions)
+ return ..()
+
+/datum/action_group/proc/insert_action(obj/screen/action, index)
+ if(action in actions)
+ if(actions[index] == action)
+ return
+ actions -= action // Don't dupe, come on
+ if(!index)
+ index = length(actions) + 1
+ index = min(length(actions) + 1, index)
+ actions.Insert(index, action)
+ refresh_actions()
+
+/datum/action_group/proc/remove_action(obj/screen/action)
+ actions -= action
+ if(!QDELING(src))
+ refresh_actions()
+
+/datum/action_group/proc/refresh_actions()
+ // We don't use size() here because landings are not canon
+ var/total_rows = ROUND_UP(length(actions) / column_max)
+ total_rows -= max_rows // Lets get the amount of rows we're off from our max
+ row_offset = clamp(row_offset, 0, total_rows) // You're not allowed to offset so far that we have a row of blank space
+
+ var/button_number = 0
+ for(var/obj/screen/button as anything in actions)
+ var/postion = ButtonNumberToScreenCoords(button_number )
+ button.screen_loc = postion
+ button_number++
+
+ if(landing)
+ var/postion = ButtonNumberToScreenCoords(button_number, landing = TRUE) // Need a good way to count buttons off screen, but allow this to display in the right place if it's being placed with no concern for dropdown
+ landing.screen_loc = postion
+ button_number++
+
+/// Accepts a number represeting our position in the group, indexes at 0 to make the math nicer
+/datum/action_group/proc/ButtonNumberToScreenCoords(number, landing = FALSE)
+ var/row = round(number / column_max)
+ row -= row_offset // If you're less then 0, you don't get to render, this lets us "scroll" rows ya feel?
+ if(row < 0)
+ return null
+
+ // Could use >= here, but I think it's worth noting that the two start at different places, since row is based on number here
+ if(row > max_rows - 1)
+ if(!landing) // If you're not a landing, go away please. thx
+ return null
+ // We always want to render landings, even if their action button can't be displayed.
+ // So we set a row equal to the max amount of rows + 1. Willing to overrun that max slightly to properly display the landing spot
+ row = max_rows // Remembering that max_rows indexes at 1, and row indexes at 0
+
+ // We're going to need to set our column to match the first item in the last row, so let's set number properly now
+ number = row * column_max
+
+ var/visual_row = row + north_offset
+ var/coord_row = visual_row ? "-[visual_row]" : "+0"
+
+ var/visual_column = number % column_max
+ var/coord_col = "+[visual_column]"
+ var/coord_col_offset = 4 + 2 * (visual_column + 1)
+ return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-[pixel_north_offset]"
+
+/datum/action_group/proc/check_against_view()
+ var/owner_view = owner?.mymob?.client?.view
+ if(!owner_view)
+ return
+ // Unlikey as it is, we may have been changed. Want to start from our target position and fail down
+ column_max = initial(column_max)
+ // Convert our viewer's view var into a workable offset
+ var/list/view_size = view_to_pixels(owner_view)
+
+ // We're primarially concerned about width here, if someone makes us 1x2000 I wish them a swift and watery death
+ var/furthest_screen_loc = ButtonNumberToScreenCoords(column_max - 1)
+ var/list/offsets = screen_loc_to_offset(furthest_screen_loc, owner_view)
+ if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2]) // We're all good
+ return
+
+ for(column_max in column_max - 1 to 1 step -1) // Yes I could do this by unwrapping ButtonNumberToScreenCoords, but I don't feel like it
+ var/tested_screen_loc = ButtonNumberToScreenCoords(column_max)
+ offsets = screen_loc_to_offset(tested_screen_loc, owner_view)
+ // We've found a valid max length, pack it in
+ if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2])
+ break
+ // Use our newly resized column max
+ refresh_actions()
+
+/// Returns the amount of objects we're storing at the moment
+/datum/action_group/proc/size()
+ var/amount = length(actions)
+ if(landing)
+ amount += 1
+ return amount
+
+/datum/action_group/proc/index_of(obj/screen/get_location)
+ return actions.Find(get_location)
+
+/// Generates a landing object that can be dropped on to join this group
+/datum/action_group/proc/generate_landing()
+ if(landing)
+ return
+ landing = new()
+ landing.set_owner(src)
+ refresh_actions()
+
+/// Clears any landing objects we may currently have
+/datum/action_group/proc/clear_landing()
+ QDEL_NULL(landing)
+
+/datum/action_group/proc/update_landing()
+ if(!landing)
+ return
+ landing.refresh_owner()
+
+/datum/action_group/proc/scroll(amount)
+ row_offset += amount
+ refresh_actions()
+
+/datum/action_group/palette
+ north_offset = 2
+ column_max = 3
+ max_rows = 3
+ location = SCRN_OBJ_IN_PALETTE
+
+/datum/action_group/palette/insert_action(obj/screen/action, index)
+ . = ..()
+ var/obj/screen/button_palette/palette = owner.toggle_palette
+ palette.play_item_added()
+
+/datum/action_group/palette/remove_action(obj/screen/action)
+ . = ..()
+ var/obj/screen/button_palette/palette = owner.toggle_palette
+ palette.play_item_removed()
+ if(!length(actions))
+ palette.set_expanded(FALSE)
+
+/datum/action_group/palette/refresh_actions()
+ var/obj/screen/button_palette/palette = owner.toggle_palette
+ var/obj/screen/palette_scroll/scroll_down = owner.palette_down
+ var/obj/screen/palette_scroll/scroll_up = owner.palette_up
+ if(!palette || !scroll_down || !scroll_up)
+ return
+
+ var/actions_above = round((owner.listed_actions.size() - 1) / owner.listed_actions.column_max)
+ north_offset = initial(north_offset) + actions_above
+
+ palette.screen_loc = ui_action_palette_offset(actions_above)
+ var/action_count = length(owner?.mymob?.actions)
+ var/our_row_count = round((length(actions) - 1) / column_max)
+ if(!action_count)
+ palette.screen_loc = null
+
+ if(palette.expanded && action_count && our_row_count >= max_rows)
+ scroll_down.screen_loc = ui_palette_scroll_offset(actions_above)
+ scroll_up.screen_loc = ui_palette_scroll_offset(actions_above)
+ else
+ scroll_down.screen_loc = null
+ scroll_up.screen_loc = null
+
+ return ..()
+
+/datum/action_group/palette/ButtonNumberToScreenCoords(number, landing)
+ var/obj/screen/button_palette/palette = owner.toggle_palette
+ if(palette.expanded)
+ return ..()
+
+ if(!landing)
+ return null
+
+ // We only render the landing in this case, so we force it to be the second item displayed (Second rather then first since it looks nicer)
+ // Remember the number var indexes at 0
+ return ..(1 + (row_offset * column_max), landing)
+
+
+/datum/action_group/listed
+ pixel_north_offset = 6
+ column_max = 10
+ location = SCRN_OBJ_IN_LIST
+
+/datum/action_group/listed/refresh_actions()
+ . = ..()
+ owner?.palette_actions.refresh_actions() // We effect them, so we gotta refresh em
diff --git a/code/_onclick/hud/action/action_group_helpers.dm b/code/_onclick/hud/action/action_group_helpers.dm
new file mode 100644
index 0000000000..14ca719433
--- /dev/null
+++ b/code/_onclick/hud/action/action_group_helpers.dm
@@ -0,0 +1,41 @@
+/// Generates visual landings for all groups that the button is not a memeber of
+/datum/hud/proc/generate_landings(obj/screen/movable/action_button/button)
+ listed_actions.generate_landing()
+ palette_actions.generate_landing()
+
+/// Clears all currently visible landings
+/datum/hud/proc/hide_landings()
+ listed_actions.clear_landing()
+ palette_actions.clear_landing()
+
+// Updates any existing "owned" visuals, ensures they continue to be visible
+/datum/hud/proc/update_our_owner()
+ toggle_palette.refresh_owner()
+ palette_down.refresh_owner()
+ palette_up.refresh_owner()
+ listed_actions.update_landing()
+ palette_actions.update_landing()
+
+/// Ensures all of our buttons are properly within the bounds of our client's view, moves them if they're not
+/datum/hud/proc/view_audit_buttons()
+ var/our_view = mymob?.client?.view
+ if(!our_view)
+ return
+ listed_actions.check_against_view()
+ palette_actions.check_against_view()
+ for(var/obj/screen/movable/action_button/floating_button as anything in floating_actions)
+ var/list/current_offsets = screen_loc_to_offset(floating_button.screen_loc)
+ // We set the view arg here, so the output will be properly hemm'd in by our new view
+ floating_button.screen_loc = offset_to_screen_loc(current_offsets[1], current_offsets[2], view = our_view)
+
+/// Generates and fills new action groups with our mob's current actions
+/datum/hud/proc/build_action_groups()
+ listed_actions = new(src)
+ palette_actions = new(src)
+ floating_actions = list()
+ for(var/datum/action/action as anything in mymob.actions)
+ var/obj/screen/movable/action_button/button = action.viewers[src]
+ if(!button)
+ action.ShowTo(mymob)
+ button = action.viewers[src]
+ position_action(button, button.location)
diff --git a/code/_onclick/hud/action/action_item_overlay.dm b/code/_onclick/hud/action/action_item_overlay.dm
new file mode 100644
index 0000000000..7d1af7309e
--- /dev/null
+++ b/code/_onclick/hud/action/action_item_overlay.dm
@@ -0,0 +1,73 @@
+/**
+ * Apply to an action to allow it to take an item
+ * and apply it as an overlay of the action button
+ */
+/datum/component/action_item_overlay
+ /// Weakref to what item the component uses to apply as an overlay.
+ var/datum/weakref/item_ref
+ /// Callback that dictates what item the component uses to apply as an overlay.
+ var/datum/callback/item_callback
+
+ /// The appearance of the item we've applied
+ var/mutable_appearance/item_appearance
+
+/datum/component/action_item_overlay/Initialize(atom/movable/item, datum/callback/item_callback)
+ if(!istype(parent, /datum/action))
+ return COMPONENT_INCOMPATIBLE
+
+ if(!item && !item_callback)
+ stack_trace("[type] created without a reference item or an item callback - one or the other is required.")
+ return COMPONENT_INCOMPATIBLE
+
+ src.item_ref = WEAKREF(item)
+ src.item_callback = item_callback
+
+/datum/component/action_item_overlay/Destroy(force, silent)
+ item_ref = null
+ QDEL_NULL(item_callback)
+ item_appearance = null
+ return ..()
+
+/datum/component/action_item_overlay/RegisterWithParent()
+ RegisterSignal(parent, COMSIG_ACTION_OVERLAY_APPLY, PROC_REF(on_overlays_applied))
+
+ var/datum/action/parent_action = parent
+ parent_action.build_all_button_icons(UPDATE_BUTTON_OVERLAY)
+
+/datum/component/action_item_overlay/UnregisterFromParent()
+ UnregisterSignal(parent, COMSIG_ACTION_OVERLAY_APPLY)
+
+ // If we're being unregistered / deleted but our parent is sticking around,
+ // force an overlay update to get rid of our item appearance
+ if(!QDELING(parent))
+ var/datum/action/parent_action = parent
+ parent_action.build_all_button_icons(UPDATE_BUTTON_OVERLAY)
+
+/// Signal proc for [COMSIG_ACTION_OVERLAY_APPLY], applies the item appearance if possible.
+/datum/component/action_item_overlay/proc/on_overlays_applied(datum/action/source, obj/screen/movable/action_button/current_button, force)
+ SIGNAL_HANDLER
+
+ // We're in the middle of being removed / deleted, remove our associated overlay
+ if(QDELING(src) && item_appearance)
+ current_button.cut_overlay(item_appearance)
+ item_appearance = null
+ return
+
+ var/atom/movable/muse = item_callback?.Invoke() || item_ref?.resolve()
+ if(!istype(muse))
+ return
+
+ if(item_appearance)
+ // For caching purposes, we will try not to update if we don't need to
+ if(!force && item_appearance.icon == muse.icon && item_appearance.icon_state == muse.icon_state)
+ return
+ current_button.cut_overlay(item_appearance)
+
+ var/mutable_appearance/muse_appearance = new(muse.appearance)
+ muse_appearance.plane = FLOAT_PLANE
+ muse_appearance.layer = FLOAT_LAYER
+ muse_appearance.pixel_x = 0
+ muse_appearance.pixel_y = 0
+
+ current_button.add_overlay(muse_appearance)
+ item_appearance = muse_appearance
diff --git a/code/_onclick/hud/action/action_screen_objects.dm b/code/_onclick/hud/action/action_screen_objects.dm
index fd64a38d57..503274f86f 100644
--- a/code/_onclick/hud/action/action_screen_objects.dm
+++ b/code/_onclick/hud/action/action_screen_objects.dm
@@ -1,39 +1,48 @@
-
/obj/screen/movable/action_button
var/datum/action/linked_action
+ var/datum/hud/our_hud
var/actiontooltipstyle = ""
screen_loc = null
+ icon = null // we don't use the base icon at all, just underlays and overlays
- var/button_icon_state
- var/appearance_cache
+ /// 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
+
+/obj/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 ..()
/obj/screen/movable/action_button/proc/can_use(mob/user)
- if(linked_action)
- return linked_action.owner == user
- else if(isobserver(user))
- // var/mob/dead/observer/O = user
- // return !O.observetarget
- return TRUE
- else
- return TRUE
+ // 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
-/obj/screen/movable/action_button/MouseDrop(over_object)
- if(!can_use(usr))
- return
- if((istype(over_object, /obj/screen/movable/action_button) && !istype(over_object, /obj/screen/movable/action_button/hide_toggle)))
- if(locked)
- to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first."))
- return
- var/obj/screen/movable/action_button/B = over_object
- var/list/actions = usr.actions
- actions.Swap(actions.Find(src.linked_action), actions.Find(B.linked_action))
- moved = FALSE
- B.moved = FALSE
- usr.update_action_buttons()
- else
- return ..()
+ if(linked_action)
+ if(linked_action.viewers[user.hud_used])
+ return TRUE
+ return FALSE
+
+ return TRUE
/obj/screen/movable/action_button/Click(location,control,params)
if(!can_use(usr))
@@ -41,108 +50,40 @@
var/list/modifiers = params2list(params)
if(LAZYACCESS(modifiers, SHIFT_CLICK))
- if(locked)
- to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first."))
- return TRUE
- moved = FALSE
- usr.update_action_buttons()
- return TRUE
- if(LAZYACCESS(modifiers, CTRL_CLICK))
- locked = !locked
- to_chat(usr, span_notice("Action button \"[name]\" [locked ? "" : "un"]locked."))
- if(id && usr?.client?.prefs) // try to (un)remember position
- LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[id]", locked ? moved : null)
+ var/datum/hud/our_hud = usr.hud_used
+ our_hud.position_action(src, SCRN_OBJ_DEFAULT)
return TRUE
if(!usr.checkClickCooldown())
return
usr.setClickCooldown(1)
- linked_action.Trigger()
+ var/trigger_flags
+ if(LAZYACCESS(modifiers, RIGHT_CLICK))
+ trigger_flags |= TRIGGER_SECONDARY_ACTION
+ linked_action.Trigger(trigger_flags = trigger_flags)
return TRUE
-//Hide/Show Action Buttons ... Button
-/obj/screen/movable/action_button/hide_toggle
- name = "Hide Buttons"
- desc = "Shift-click any button to reset its position, and Control-click it to lock it in place. Alt-click this button reset all buttons to their default positions."
- icon = 'icons/mob/actions.dmi'
- icon_state = "bg_default"
- var/hidden = FALSE
-
-// /obj/screen/movable/action_button/hide_toggle/Initialize()
-// . = ..()
-// var/static/list/icon_cache = list()
-
-// var/cache_key = "[hide_icon][hide_state]"
-// hide_appearance = icon_cache[cache_key]
-// if(!hide_appearance)
-// hide_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, hide_state)
-// cache_key = "[hide_icon][show_state]"
-// show_appearance = icon_cache[cache_key]
-// if(!show_appearance)
-// show_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, show_state)
-
-/obj/screen/movable/action_button/hide_toggle/Click(location, control, params)
+// 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
+/obj/screen/movable/action_button/MouseDrag(atom/over_object, src_location, over_location, src_control, over_control, params)
+ . = ..()
if(!can_use(usr))
- return FALSE
+ return
+ if(IS_WEAKREF_OF(over_object, last_hovored_ref))
+ return
- var/list/modifiers = params2list(params)
- if(LAZYACCESS(modifiers, SHIFT_CLICK))
- if(locked)
- to_chat(usr, span_warning("Action button \"[name]\" is locked, unlock it first."))
- return TRUE
- moved = FALSE
- usr.update_action_buttons(TRUE)
- return TRUE
- if(LAZYACCESS(modifiers, CTRL_CLICK))
- locked = !locked
- to_chat(usr, span_notice("Action button \"[name]\" [locked ? "" : "un"]locked."))
- if(id && usr?.client?.prefs) // try to (un)remember position
- LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[id]", locked ? moved : null)
- return TRUE
- if(LAZYACCESS(modifiers, ALT_CLICK))
- AltClick(usr)
- return TRUE
+ var/atom/old_object
+ if(last_hovored_ref)
+ old_object = last_hovored_ref.resolve()
+ else // If there is 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)
- usr.hud_used.action_buttons_hidden = !usr.hud_used.action_buttons_hidden
+ if(old_object)
+ old_object.MouseExited(over_location, over_control, params)
- hidden = usr.hud_used.action_buttons_hidden
- if(hidden)
- name = "Show Buttons"
- else
- name = "Hide Buttons"
- update_icon()
- usr.update_action_buttons()
-
-/obj/screen/movable/action_button/hide_toggle/AltClick(mob/user)
- for(var/datum/action/A as anything in user.actions)
- var/obj/screen/movable/action_button/B = A.button
- B.moved = FALSE
- if(B.id && usr?.client?.prefs)
- LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[B.id]", null)
- // B.locked = usr.client.prefs.buttons_locked
- // locked = usr.client.prefs.buttons_locked
- if(id && usr?.client?.prefs)
- LAZYSET(usr.client.prefs.action_button_screen_locs, "[name]_[id]", null)
- moved = FALSE
- user.update_action_buttons(TRUE)
- to_chat(user, span_notice("Action button positions have been reset."))
-
-/obj/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(mob/user)
- // var/settings = owner_hud.get_action_buttons_icons()
- // icon = settings["bg_icon"]
- // icon_state = settings["bg_state"]
- // hide_icon = settings["toggle_icon"]
- // hide_state = settings["toggle_hide"]
- // show_state = settings["toggle_show"]
- if(isalien(user))
- icon_state = "bg_alien"
- else
- icon_state = "bg_default"
- update_icon()
-
-/obj/screen/movable/action_button/hide_toggle/update_icon()
- cut_overlays()
- add_overlay(mutable_appearance(icon, hidden ? "show" : "hide"))
- // add_overlay(mutable_appearance(hide_icon, hidden ? show_state : hide_state))
+ last_hovored_ref = WEAKREF(over_object)
+ over_object?.MouseEntered(over_location, over_control, params)
/obj/screen/movable/action_button/MouseEntered(location, control, params)
. = ..()
@@ -151,89 +92,391 @@
/obj/screen/movable/action_button/MouseExited(location, control, params)
closeToolTip(usr)
+ return ..()
-// /datum/hud/proc/get_action_buttons_icons()
-// . = list()
-// .["bg_icon"] = ui_style_icon
-// .["bg_state"] = "template"
+/obj/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, /obj/screen/action_landing))
+ var/obj/screen/action_landing/reserve = over_object
+ reserve.hit_by(src)
+ our_hud.hide_landings()
+ save_position()
+ return
-// //TODO : Make these fit theme
-// .["toggle_icon"] = 'icons/mob/actions.dmi'
-// .["toggle_hide"] = "hide"
-// .["toggle_show"] = "show"
+ our_hud.hide_landings()
+ if(istype(over_object, /obj/screen/button_palette) || istype(over_object, /obj/screen/palette_scroll))
+ our_hud.position_action(src, SCRN_OBJ_IN_PALETTE)
+ save_position()
+ return
+ if(istype(over_object, /obj/screen/movable/action_button))
+ var/obj/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()
-//used to update the buttons icon.
-/mob/proc/update_action_buttons_icon(status_only = FALSE)
- for(var/X in actions)
- var/datum/action/A = X
- A.UpdateButtonIcon(status_only)
+/obj/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
-//This is the proc used to update all the action buttons.
-/mob/proc/update_action_buttons(reload_screen)
+ LAZYSET(user.client.prefs.action_button_screen_locs, "[name]_[id]", position_info)
+
+/obj/screen/movable/action_button/proc/load_position()
+ var/mob/user = our_hud.mymob
+ if(!user)
+ return
+ var/position_info = LAZYACCESS(user.client?.prefs?.action_button_screen_locs, "[name]_[id]") || SCRN_OBJ_DEFAULT
+ user.hud_used.position_action(src, position_info)
+
+/obj/screen/movable/action_button/proc/dump_save()
+ var/mob/user = our_hud.mymob
+ if(!user?.client)
+ return
+ LAZYREMOVE(user.client.prefs.action_button_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"] = 'icons/mob/actions/backgrounds.dmi' // ui_style // TODO: Implement hud-specific icon stuff
+ .["bg_state"] = "bg_default"
+ .["bg_state_active"] = "bg_default_on"
+
+
+/**
+ * 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 toggled to minimal
return
- var/button_number = 0
-
- if(hud_used.action_buttons_hidden)
- for(var/datum/action/A in actions)
- A.button.screen_loc = null
- if(reload_screen)
- client.screen += A.button
- else
- for(var/datum/action/A in actions)
- button_number++
- A.UpdateButtonIcon()
- var/obj/screen/movable/action_button/B = A.button
- if(!B.moved)
- B.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number)
- else
- B.screen_loc = B.moved
- if(reload_screen)
- client.screen += B
-
- if(!button_number)
- hud_used.hide_actions_toggle?.screen_loc = null
- return
-
- // not exactly sure how this happens but it does
- if(hud_used.hide_actions_toggle)
- if(!hud_used.hide_actions_toggle.moved)
- hud_used.hide_actions_toggle.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number+1)
- else
- hud_used.hide_actions_toggle.screen_loc = hud_used.hide_actions_toggle.moved
+ for(var/datum/action/action as anything in actions)
+ var/obj/screen/movable/action_button/button = action.viewers[hud_used]
+ action.build_all_button_icons()
if(reload_screen)
- client.screen += hud_used.hide_actions_toggle
+ client.screen += button
-#define AB_WEST_OFFSET 4
-#define AB_NORTH_OFFSET 26
-#define AB_MAX_COLUMNS 10
+ if(reload_screen)
+ hud_used.update_our_owner()
+ // This holds the logic for the palette buttons
+ hud_used?.palette_actions?.refresh_actions()
-/datum/hud/proc/ButtonNumberToScreenCoords(var/number) // TODO : Make this zero-indexed for readabilty
- var/row = round((number-1)/AB_MAX_COLUMNS)
- var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1
+/**
+ * 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
- var/coord_col = "+[col-1]"
- var/coord_col_offset = AB_WEST_OFFSET+2*col
+ 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))
- var/coord_row = "[-1 - row]"
- var/coord_row_offset = AB_NORTH_OFFSET
+/**
+ * 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))
- return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:[coord_row_offset]"
+/// 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
-/datum/hud/proc/SetButtonCoords(var/obj/screen/button,var/number)
- var/row = round((number-1)/AB_MAX_COLUMNS)
- var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1
- var/x_offset = 32*(col-1) + AB_WEST_OFFSET + 2*col
- var/y_offset = -32*(row+1) + AB_NORTH_OFFSET
+ if(!action.show_to_observers)
+ return
+ action.GiveAction(src)
- var/matrix/M = matrix()
- M.Translate(x_offset,y_offset)
- button.transform = M
+/// 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
-#undef AB_WEST_OFFSET
-#undef AB_NORTH_OFFSET
-#undef AB_MAX_COLUMNS
+ action.HideFrom(src)
+
+// Button Palette
+// A new way to interact with actions
+
+/obj/screen/button_palette
+ desc = "Drag buttons to move them
Shift-click any button to reset it
Alt-click 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
+
+/obj/screen/button_palette/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.toggle_palette = null
+ our_hud = null
+ return ..()
+
+/obj/screen/button_palette/Initialize(mapload)
+ . = ..()
+ // update_appearance()
+ update_name()
+
+/obj/screen/button_palette/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
+
+// /obj/screen/button_palette/update_name(updates)
+/obj/screen/button_palette/proc/update_name()
+ // . = ..()
+ if(expanded)
+ name = "Hide Buttons"
+ else
+ name = "Show Buttons"
+
+/obj/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"
+
+/obj/screen/button_palette/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ show_tooltip(params)
+
+/obj/screen/button_palette/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/obj/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))
+
+/obj/screen/button_palette/proc/play_item_added()
+ color_for_now(GLOB.palette_added_matrix)
+
+/obj/screen/button_palette/proc/play_item_removed()
+ color_for_now(GLOB.palette_removed_matrix)
+
+/obj/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)
+
+/obj/screen/button_palette/proc/remove_color(list/to_remove)
+ color_timer_id = null
+ remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, to_remove)
+
+/obj/screen/button_palette/proc/can_use(mob/user)
+ if(isobserver(user))
+ // var/mob/dead/observer/O = user
+ // return !O.observetarget
+ return TRUE
+ return TRUE
+
+/obj/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/obj/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)
+
+/obj/screen/button_palette/proc/clicked_while_open(datum/source, atom/target, atom/location, control, params, mob/user)
+ if(istype(target, /obj/screen/movable/action_button) || istype(target, /obj/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)
+
+/obj/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()
+ update_name()
+
+ 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
+
+/obj/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
+
+/obj/screen/palette_scroll/proc/can_use(mob/user)
+ if(isobserver(user))
+ // var/mob/dead/observer/O = user
+ // return !O.observetarget
+ return TRUE
+ return TRUE
+
+/obj/screen/palette_scroll/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
+
+/obj/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"]
+
+/obj/screen/palette_scroll/Click(location, control, params)
+ if(!can_use(usr))
+ return
+ our_hud.palette_actions.scroll(scroll_direction)
+
+/obj/screen/palette_scroll/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ openToolTip(usr, src, params, title = name, content = desc)
+
+/obj/screen/palette_scroll/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/obj/screen/palette_scroll/down
+ name = "Scroll Down"
+ desc = "Click on this to scroll the actions above down"
+ icon_state = "scroll_down"
+ scroll_direction = 1
+
+/obj/screen/palette_scroll/down/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.palette_down = null
+ our_hud = null
+ return ..()
+
+/obj/screen/palette_scroll/up
+ name = "Scroll Up"
+ desc = "Click on this to scroll the actions above up"
+ icon_state = "scroll_up"
+ scroll_direction = -1
+
+/obj/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
+/obj/screen/action_landing
+ name = "Button Space"
+ desc = "Drag and drop a button into this spot
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
+
+/obj/screen/action_landing/Destroy()
+ if(owner)
+ owner.landing = null
+ owner?.owner?.mymob?.client?.screen -= src
+ owner.refresh_actions()
+ owner = null
+ return ..()
+
+/obj/screen/action_landing/proc/set_owner(datum/action_group/owner)
+ src.owner = owner
+ refresh_owner()
+
+/obj/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
+/obj/screen/action_landing/proc/hit_by(obj/screen/movable/action_button/button)
+ var/datum/hud/our_hud = owner.owner
+ our_hud.position_action(button, owner.location)
diff --git a/code/_onclick/hud/action/innate_action.dm b/code/_onclick/hud/action/innate_action.dm
new file mode 100644
index 0000000000..cdfb169ddb
--- /dev/null
+++ b/code/_onclick/hud/action/innate_action.dm
@@ -0,0 +1,20 @@
+//Preset for general and toggled actions
+/datum/action/innate
+ check_flags = NONE
+ /// Whether we're active or not, if we're a innate - toggle action.
+ var/active = 0
+
+/datum/action/innate/Trigger(trigger_flags)
+ if(!..())
+ return 0
+ if(!active)
+ Activate()
+ else
+ Deactivate()
+ return 1
+
+/datum/action/innate/proc/Activate()
+ return
+
+/datum/action/innate/proc/Deactivate()
+ return
diff --git a/code/_onclick/hud/action/item_action.dm b/code/_onclick/hud/action/item_action.dm
new file mode 100644
index 0000000000..4644efbf83
--- /dev/null
+++ b/code/_onclick/hud/action/item_action.dm
@@ -0,0 +1,37 @@
+
+//Presets for item actions
+/datum/action/item_action
+ check_flags = AB_CHECK_RESTRAINED|AB_CHECK_STUNNED|AB_CHECK_LYING|AB_CHECK_CONSCIOUS
+ // If you want to override the normal icon being the item
+ // then change this to an icon state
+ button_icon_state = null
+
+/datum/action/item_action/New(Target)
+ . = ..()
+
+ // 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/vv_edit_var(var_name, var_value)
+ . = ..()
+ if(!. || !target)
+ return
+
+ if(var_name == NAMEOF(src, button_icon_state))
+ // If someone vv's our icon either add or remove the component
+ if(isnull(var_name))
+ AddComponent(/datum/component/action_item_overlay, target)
+ else
+ qdel(GetComponent(/datum/component/action_item_overlay))
+
+/datum/action/item_action/Trigger(trigger_flags)
+ if(!..())
+ return 0
+ if(target)
+ var/obj/item/item_target = target
+ item_target.ui_action_click(owner, src.type)
+ return 1
+
+/datum/action/item_action/hands_free
+ check_flags = AB_CHECK_CONSCIOUS
diff --git a/code/_onclick/hud/action/positioning.dm b/code/_onclick/hud/action/positioning.dm
new file mode 100644
index 0000000000..1ed18d87cd
--- /dev/null
+++ b/code/_onclick/hud/action/positioning.dm
@@ -0,0 +1,49 @@
+/datum/hud/proc/position_action(obj/screen/movable/action_button/button, position)
+ if(button.location != SCRN_OBJ_DEFAULT)
+ hide_action(button)
+ switch(position)
+ if(SCRN_OBJ_DEFAULT) // Reset to the default
+ button.dump_save() // Nuke any existing saves
+ position_action(button, button.linked_action.default_button_position)
+ return
+ if(SCRN_OBJ_IN_LIST)
+ listed_actions.insert_action(button)
+ if(SCRN_OBJ_IN_PALETTE)
+ palette_actions.insert_action(button)
+ else // If we don't have it as a define, this is a screen_loc, and we should be floating
+ floating_actions += button
+ button.screen_loc = position
+ position = SCRN_OBJ_FLOATING
+
+ button.location = position
+
+/datum/hud/proc/position_action_relative(obj/screen/movable/action_button/button, obj/screen/movable/action_button/relative_to)
+ if(button.location != SCRN_OBJ_DEFAULT)
+ hide_action(button)
+ switch(relative_to.location)
+ if(SCRN_OBJ_IN_LIST)
+ listed_actions.insert_action(button, listed_actions.index_of(relative_to))
+ if(SCRN_OBJ_IN_PALETTE)
+ palette_actions.insert_action(button, palette_actions.index_of(relative_to))
+ if(SCRN_OBJ_FLOATING) // If we don't have it as a define, this is a screen_loc, and we should be floating
+ floating_actions += button
+ var/client/our_client = mymob.client
+ if(!our_client)
+ position_action(button, button.linked_action.default_button_position)
+ return
+ button.screen_loc = get_valid_screen_location(relative_to.screen_loc, world.icon_size, our_client.view) // Asks for a location adjacent to our button that won't overflow the map
+
+ button.location = relative_to.location
+
+/// Removes the passed in action from its current position on the screen
+/datum/hud/proc/hide_action(obj/screen/movable/action_button/button)
+ switch(button.location)
+ if(SCRN_OBJ_DEFAULT) // Invalid
+ CRASH("We just tried to hide an action buttion that somehow has the default position as its location, you done fucked up")
+ if(SCRN_OBJ_FLOATING)
+ floating_actions -= button
+ if(SCRN_OBJ_IN_LIST)
+ listed_actions.remove_action(button)
+ if(SCRN_OBJ_IN_PALETTE)
+ palette_actions.remove_action(button)
+ button.screen_loc = null
diff --git a/code/_onclick/hud/action/types/item.dm b/code/_onclick/hud/action/types/item.dm
index df2d770aec..6adec05d61 100644
--- a/code/_onclick/hud/action/types/item.dm
+++ b/code/_onclick/hud/action/types/item.dm
@@ -4,7 +4,6 @@
/datum/action/item_action/activate/New(Target, name)
. = ..()
src.name = name
- button.name = name
// Specific names
/datum/action/item_action/toggle_grippers
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index c2b54a001b..774acba147 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -186,8 +186,15 @@ var/list/global_huds = list(
var/list/miniobjs
var/list/obj/screen/hotkeybuttons
- var/obj/screen/movable/action_button/hide_toggle/hide_actions_toggle
- var/action_buttons_hidden = 0
+ var/obj/screen/button_palette/toggle_palette
+ var/obj/screen/palette_scroll/down/palette_down
+ var/obj/screen/palette_scroll/up/palette_up
+
+ var/datum/action_group/palette/palette_actions
+ var/datum/action_group/listed/listed_actions
+ var/list/floating_actions
+
+
var/list/slot_info
var/icon/ui_style
@@ -205,9 +212,19 @@ var/list/global_huds = list(
..()
/datum/hud/Destroy()
- . = ..()
+ if(mymob.hud_used == src)
+ mymob.hud_used = null
+
QDEL_NULL_LIST(minihuds)
- QDEL_NULL(hide_actions_toggle)
+
+ // Actions
+ QDEL_NULL(toggle_palette)
+ QDEL_NULL(palette_down)
+ QDEL_NULL(palette_up)
+ QDEL_NULL(palette_actions)
+ QDEL_NULL(listed_actions)
+ QDEL_LIST(floating_actions)
+
grab_intent = null
hurt_intent = null
disarm_intent = null
@@ -232,6 +249,8 @@ var/list/global_huds = list(
ammo_hud_list = null
mymob = null
+ return ..()
+
/datum/hud/proc/hidden_inventory_update()
if(!mymob) return
if(ishuman(mymob))
@@ -323,10 +342,15 @@ var/list/global_huds = list(
return 0
mymob.create_mob_hud(src)
- hide_actions_toggle = new()
- hide_actions_toggle.InitialiseIcon(mymob)
- // if(mymob.client)
- // hide_actions_toggle.locked = mymob.client.prefs.buttons_locked
+
+ // Past this point, mymob.hud_used is set
+
+ toggle_palette = new()
+ toggle_palette.set_hud(src)
+ palette_down = new()
+ palette_down.set_hud(src)
+ palette_up = new()
+ palette_up.set_hud(src)
persistant_inventory_update()
mymob.reload_fullscreen() // Reload any fullscreen overlays this mob has.
@@ -340,6 +364,11 @@ var/list/global_huds = list(
HUD.ui_style = ui_style2icon(client?.prefs?.UI_style)
HUD.ui_color = client?.prefs?.UI_style_color
HUD.ui_alpha = client?.prefs?.UI_style_alpha
+ set_hud_used(HUD)
+
+/mob/proc/set_hud_used(datum/hud/new_hud)
+ hud_used = new_hud
+ new_hud.build_action_groups()
/datum/hud/proc/apply_minihud(var/datum/mini_hud/MH)
if(MH in minihuds)
@@ -409,7 +438,7 @@ var/list/global_huds = list(
hud_used?.action_intent.screen_loc = ui_acti //Restore intent selection to the original position
client.screen += zone_sel //This one is a special snowflake
- client.screen += hud_used.hide_actions_toggle
+ client.screen += hud_used.toggle_palette
hud_used.hidden_inventory_update()
hud_used.persistant_inventory_update()
diff --git a/code/_onclick/hud/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm
index daa82352e4..6179fd09c6 100644
--- a/code/_onclick/hud/movable_screen_objects.dm
+++ b/code/_onclick/hud/movable_screen_objects.dm
@@ -11,6 +11,7 @@
/obj/screen/movable
mouse_drag_pointer = 'icons/effects/mouse_pointers/screen_drag.dmi'
var/snap2grid = FALSE
+ // TODO: Check if these can safely be deleted
var/moved = FALSE
var/locked = FALSE
var/x_off = -16
diff --git a/code/game/gamemodes/technomancer/devices/shield_armor.dm b/code/game/gamemodes/technomancer/devices/shield_armor.dm
index c991f6e164..20c42faa30 100644
--- a/code/game/gamemodes/technomancer/devices/shield_armor.dm
+++ b/code/game/gamemodes/technomancer/devices/shield_armor.dm
@@ -78,7 +78,7 @@
to_chat(user, span_notice("You [active ? "" : "de"]activate \the [src]."))
update_icon()
user.update_inv_wear_suit()
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
/obj/item/clothing/suit/armor/shield/update_icon()
icon_state = "shield_armor_[active]"
diff --git a/code/game/gamemodes/technomancer/devices/tesla_armor.dm b/code/game/gamemodes/technomancer/devices/tesla_armor.dm
index 01a65b5a66..c6e1ae3cc4 100644
--- a/code/game/gamemodes/technomancer/devices/tesla_armor.dm
+++ b/code/game/gamemodes/technomancer/devices/tesla_armor.dm
@@ -57,7 +57,7 @@
to_chat(user, span_notice("You [active ? "" : "de"]activate \the [src]."))
update_icon()
user.update_inv_wear_suit()
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
/obj/item/clothing/suit/armor/tesla/update_icon()
if(active && ready)
@@ -72,7 +72,7 @@
if(ishuman(loc))
var/mob/living/carbon/human/H = loc
H.update_inv_wear_suit(0)
- H.update_action_buttons_icon()
+ H.update_mob_action_buttons()
..()
/obj/item/clothing/suit/armor/tesla/proc/shoot_lightning(mob/target, power)
diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm
index 0ce8eaf590..d3d5b1bd72 100644
--- a/code/game/mecha/mecha_actions.dm
+++ b/code/game/mecha/mecha_actions.dm
@@ -63,7 +63,9 @@
/datum/action/innate/mecha
check_flags = AB_CHECK_RESTRAINED | AB_CHECK_STUNNED | AB_CHECK_CONSCIOUS
+ background_icon = 'icons/effects/actions_mecha.dmi'
button_icon = 'icons/effects/actions_mecha.dmi'
+ overlay_icon = 'icons/effects/actions_mecha.dmi'
var/obj/mecha/chassis
/datum/action/innate/mecha/Destroy()
@@ -82,7 +84,7 @@
/datum/action/innate/mecha/mech_toggle_lights/Activate()
button_icon_state = "mech_lights_[chassis.lights ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.lights()
@@ -93,7 +95,7 @@
/datum/action/innate/mecha/mech_toggle_internals/Activate()
button_icon_state = "mech_internals_[chassis.use_internal_tank ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.internal_tank()
@@ -122,7 +124,7 @@
/datum/action/innate/mecha/strafe/Activate()
button_icon_state = "mech_strafe_[chassis.strafing ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.strafing()
@@ -133,7 +135,7 @@
/datum/action/innate/mecha/mech_defence_mode/Activate()
button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.defence_mode()
@@ -144,7 +146,7 @@
/datum/action/innate/mecha/mech_overload_mode/Activate()
button_icon_state = "mech_overload_[chassis.overload ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.overload()
@@ -155,7 +157,7 @@
/datum/action/innate/mecha/mech_smoke/Activate()
//button_icon_state = "mech_smoke_[chassis.smoke ? "off" : "on"]"
- //UpdateButtonIcon() //Dual colors notneeded ATM
+ //build_all_button_icons() //Dual colors notneeded ATM
chassis.smoke()
@@ -166,7 +168,7 @@
/datum/action/innate/mecha/mech_zoom/Activate()
button_icon_state = "mech_zoom_[chassis.zoom ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.zoom()
@@ -177,7 +179,7 @@
/datum/action/innate/mecha/mech_toggle_thrusters/Activate()
button_icon_state = "mech_thrusters_[chassis.thrusters ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.thrusters()
@@ -202,7 +204,7 @@
chassis.occupant_message("You select [chassis.selected]")
send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list())
button_icon_state = "mech_cycle_equip_on"
- UpdateButtonIcon()
+ build_all_button_icons()
return
var/number = 0
for(var/A in available_equipment)
@@ -217,7 +219,7 @@
chassis.occupant_message("You switch to [chassis.selected]")
button_icon_state = "mech_cycle_equip_on"
send_byjax(chassis.occupant,"exosuit.browser","eq_list",chassis.get_equipment_list())
- UpdateButtonIcon()
+ build_all_button_icons()
return
@@ -232,7 +234,7 @@
button_icon_state = "mech_damtype_[chassis.damtype]"
playsound(src, 'sound/mecha/mechmove01.ogg', 50, 1)
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.query_damtype()
@@ -243,7 +245,7 @@
/datum/action/innate/mecha/mech_toggle_phasing/Activate()
button_icon_state = "mech_phasing_[chassis.phasing ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.phasing()
@@ -254,7 +256,7 @@
/datum/action/innate/mecha/mech_toggle_cloaking/Activate()
button_icon_state = "mech_phasing_[chassis.cloaked ? "off" : "on"]"
- UpdateButtonIcon()
+ build_all_button_icons()
chassis.toggle_cloaking()
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index a4629ace92..10aeda384b 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -114,9 +114,9 @@
/obj/item/Initialize(mapload) //CHOMPedit I stg I'm going to overwrite these many uncommented edits.
. = ..()
for(var/path in actions_types)
- new path(src)
- if(islist(origin_tech))
- origin_tech = typelist(NAMEOF(src, origin_tech), origin_tech)
+ add_item_action(path)
+ /* if(islist(origin_tech)) // CHOMPAdded section, removed because it has a major flaw. If an item has a different origin list than the other items of the same type, they coulf all get that modified list
+ origin_tech = typelist(NAMEOF(src, origin_tech), origin_tech) */
if(embed_chance < 0)
if(sharp)
embed_chance = max(5, round(force/w_class))
@@ -137,10 +137,55 @@
m.update_inv_r_hand()
m.update_inv_l_hand()
src.loc = null
- for(var/X in actions)
- qdel(X)
+
+ // Handle celaning up our actions list
+ for(var/datum/action/action as anything in actions)
+ remove_item_action(action)
+
return ..()
+
+/// Called when an action associated with our item is deleted
+/obj/item/proc/on_action_deleted(datum/source)
+ SIGNAL_HANDLER
+
+ if(!(source in actions))
+ CRASH("An action ([source.type]) was deleted that was associated with an item ([src]), but was not found in the item's actions list.")
+
+ LAZYREMOVE(actions, source)
+
+/// Adds an item action to our list of item actions.
+/// Item actions are actions linked to our item, that are granted to mobs who equip us.
+/// This also ensures that the actions are properly tracked in the actions list and removed if they're deleted.
+/// Can be be passed a typepath of an action or an instance of an action.
+/obj/item/proc/add_item_action(action_or_action_type)
+ var/datum/action/action
+ if(ispath(action_or_action_type, /datum/action))
+ action = new action_or_action_type(src)
+ else if(istype(action_or_action_type, /datum/action))
+ action = action_or_action_type
+ else
+ CRASH("item add_item_action got a type or instance of something that wasn't an action.")
+
+ LAZYADD(actions, action)
+ RegisterSignal(action, COMSIG_PARENT_QDELETING, PROC_REF(on_action_deleted))
+ if(ismob(loc))
+ // We're being held or are equipped by someone while adding an action?
+ // Then they should also probably be granted the action, given it's in a correct slot
+ var/mob/holder = loc
+ give_item_action(action, holder, holder.get_inventory_slot(src))
+
+ return action
+
+/// Removes an instance of an action from our list of item actions.
+/obj/item/proc/remove_item_action(datum/action/action)
+ if(!action)
+ return
+
+ UnregisterSignal(action, COMSIG_PARENT_QDELETING)
+ LAZYREMOVE(actions, action)
+ qdel(action)
+
// Check if target is reasonable for us to operate on.
/obj/item/proc/check_allowed_items(atom/target, not_inside, target_self)
if(((src in target) && !target_self) || ((!istype(target.loc, /turf)) && (!istype(target, /turf)) && (not_inside)))
@@ -338,8 +383,9 @@
if(zoom)
zoom() //binoculars, scope, etc
appearance_flags &= ~NO_CLIENT_COLOR
- for(var/datum/action/A as anything in actions)
- A.Remove(user)
+ // Remove any item actions we temporary gave out.
+ for(var/datum/action/action_item_has as anything in actions)
+ action_item_has.Remove(user)
// called just as an item is picked up (loc is not yet changed)
/obj/item/proc/pickup(mob/user)
@@ -365,10 +411,9 @@
// for items that can be placed in multiple slots
// note this isn't called during the initial dressing of a player
/obj/item/proc/equipped(var/mob/user, var/slot)
- for(var/X in actions)
- var/datum/action/A = X
- if(item_action_slot_check(slot, user)) //some items only give their actions buttons when in a specific slot.
- A.Grant(user)
+ // Give out actions our item has to people who equip it.
+ for(var/datum/action/action as anything in actions)
+ give_item_action(action, user, slot)
hud_layerise()
user.position_hud_item(src,slot)
if(user.client) user.client.screen |= src
@@ -383,6 +428,18 @@
playsound(src, pickup_sound, 20, preference = /datum/preference/toggle/pickup_sounds)
return
+/// Gives one of our item actions to a mob, when equipped to a certain slot
+/obj/item/proc/give_item_action(datum/action/action, mob/to_who, slot)
+ // Some items only give their actions buttons when in a specific slot.
+ if(!item_action_slot_check(slot, to_who))
+ // There is a chance we still have our item action currently,
+ // and are moving it from a "valid slot" to an "invalid slot".
+ // So call Remove() here regardless, even if excessive.
+ action.Remove(to_who)
+ return
+
+ action.Grant(to_who)
+
//sometimes we only want to grant the item's action if it's equipped in a specific slot.
/obj/item/proc/item_action_slot_check(slot, mob/user)
if(slot == SLOT_BACK || slot == LEGS) //these aren't true slots, so avoid granting actions there
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index f78e3e9966..bd04398f2e 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -102,7 +102,7 @@
STOP_PROCESSING(SSobj, src)
playsound(src, 'sound/weapons/empty.ogg', 15, 1, -3) // VOREStation Edit
update_brightness()
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
return 1
/obj/item/flashlight/emp_act(severity)
diff --git a/code/game/objects/items/weapons/shields_vr.dm b/code/game/objects/items/weapons/shields_vr.dm
index f1eb816f67..1de6070647 100644
--- a/code/game/objects/items/weapons/shields_vr.dm
+++ b/code/game/objects/items/weapons/shields_vr.dm
@@ -64,7 +64,7 @@
set_light(0)
light_applied = 0
update_icon(user)
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
playsound(src, 'sound/weapons/empty.ogg', 15, 1, -3)
/obj/item/shield/riot/explorer/update_icon()
diff --git a/code/game/objects/items/weapons/tanks/jetpack.dm b/code/game/objects/items/weapons/tanks/jetpack.dm
index 42b67842e4..7660cb8e67 100644
--- a/code/game/objects/items/weapons/tanks/jetpack.dm
+++ b/code/game/objects/items/weapons/tanks/jetpack.dm
@@ -55,7 +55,7 @@
if (ismob(usr))
var/mob/M = usr
M.update_inv_back()
- M.update_action_buttons_icon()
+ M.update_mob_action_buttons()
to_chat(usr, "You toggle the thrusters [on? "on":"off"].")
diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm
index acf5fe0dc3..133fea38fd 100644
--- a/code/modules/ai/ai_holder.dm
+++ b/code/modules/ai/ai_holder.dm
@@ -52,8 +52,7 @@
return
if(istype(src, /mob/living/carbon/human))
var/mob/living/carbon/human/H = src
- H.hud_used = new /datum/hud(H)
- H.create_mob_hud(H.hud_used)
+ new /datum/hud(H)
/datum/ai_holder
var/mob/living/holder = null // The mob this datum is going to control.
diff --git a/code/modules/catalogue/cataloguer.dm b/code/modules/catalogue/cataloguer.dm
index 766fd2e939..2ea1045dca 100644
--- a/code/modules/catalogue/cataloguer.dm
+++ b/code/modules/catalogue/cataloguer.dm
@@ -368,7 +368,7 @@ GLOBAL_LIST_EMPTY(all_cataloguers)
if (ismob(usr))
var/mob/M = usr
- M.update_action_buttons_icon()
+ M.update_mob_action_buttons()
/obj/item/cataloguer/compact/afterattack(atom/target, mob/user, proximity_flag)
if(!deployed)
diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm
index f9bb791283..72c6f5a171 100644
--- a/code/modules/client/client procs.dm
+++ b/code/modules/client/client procs.dm
@@ -732,3 +732,9 @@
if("Set-Tab")
stat_tab = payload["tab"]
SSstatpanels.immediate_send_stat_data(src)
+
+
+// Mouse stuff
+/client/Click(atom/object, atom/location, control, params)
+ SEND_SIGNAL(src, COMSIG_CLIENT_CLICK, object, location, control, params, usr)
+ . = ..()
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index dd6ce9c347..c9388ddea2 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -485,8 +485,7 @@
update_light()
update_icon(user)
- spawn(10) // FIXME: Remove when SSoverlays stops queueing overlay changes
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
/obj/item/clothing/head/attack_ai(var/mob/user)
if(!mob_wear_hat(user))
diff --git a/code/modules/clothing/glasses/glasses.dm b/code/modules/clothing/glasses/glasses.dm
index 5ed049694c..572f45c0b5 100644
--- a/code/modules/clothing/glasses/glasses.dm
+++ b/code/modules/clothing/glasses/glasses.dm
@@ -71,7 +71,7 @@ BLIND // can't see anything
tint = initial(tint)
enables_planes = away_planes
away_planes = null
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
user.recalculate_vis()
/obj/item/clothing/glasses/attack_self(mob/user)
@@ -408,7 +408,7 @@ BLIND // can't see anything
tint = TINT_NONE
to_chat(usr, "You push \the [src] up out of your face.")
update_clothing_icon()
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
/obj/item/clothing/glasses/welding/superior
name = "superior welding goggles"
@@ -503,7 +503,7 @@ BLIND // can't see anything
user << activation_sound
user.recalculate_vis()
user.update_inv_glasses()
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
/obj/item/clothing/glasses/sunglasses/sechud/aviator/update_icon()
if(on)
@@ -623,4 +623,4 @@ BLIND // can't see anything
icon_state = "[initial(icon_state)]up"
to_chat(usr, "You push \the [src] up from in front of your eyes.")
update_clothing_icon()
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
diff --git a/code/modules/clothing/glasses/glasses_vr.dm b/code/modules/clothing/glasses/glasses_vr.dm
index 58d7c4ce3e..bfebf06912 100644
--- a/code/modules/clothing/glasses/glasses_vr.dm
+++ b/code/modules/clothing/glasses/glasses_vr.dm
@@ -106,7 +106,7 @@
if(src && choice && !user.incapacitated() && in_range(user,src))
icon_state = options[choice]
user.update_inv_glasses()
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
to_chat(user, span_notice("Your [src] now displays [choice] ."))
return 1
diff --git a/code/modules/clothing/glasses/hud_vr.dm b/code/modules/clothing/glasses/hud_vr.dm
index 2bad04d4bc..93a9708d43 100644
--- a/code/modules/clothing/glasses/hud_vr.dm
+++ b/code/modules/clothing/glasses/hud_vr.dm
@@ -141,7 +141,7 @@
away_planes = null
to_chat(usr, span_notice("You enabled the Augmented Reality HUD of your [src.name]."))
ar_toggled = !ar_toggled
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
usr.recalculate_vis()
@@ -249,7 +249,7 @@
item_state = initial(item_state)
usr.update_inv_glasses()
to_chat(usr, "You activate the retinal projector on the [src].")
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
/obj/item/clothing/glasses/omnihud/all
name = "\improper AR-B glasses"
diff --git a/code/modules/clothing/head/helmet_vr.dm b/code/modules/clothing/head/helmet_vr.dm
index b02024594c..7b7890b423 100644
--- a/code/modules/clothing/head/helmet_vr.dm
+++ b/code/modules/clothing/head/helmet_vr.dm
@@ -83,7 +83,7 @@
if (ismob(src.loc)) //should allow masks to update when it is opened/closed
var/mob/M = src.loc
M.update_inv_wear_mask()
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
// Costume Versions Here
/obj/item/clothing/head/helmet/combat/crusader_costume
@@ -140,4 +140,4 @@
if (ismob(src.loc)) //should allow masks to update when it is opened/closed
var/mob/M = src.loc
M.update_inv_wear_mask()
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
diff --git a/code/modules/clothing/head/misc_special.dm b/code/modules/clothing/head/misc_special.dm
index 1b4afb4d09..3f179d3e36 100644
--- a/code/modules/clothing/head/misc_special.dm
+++ b/code/modules/clothing/head/misc_special.dm
@@ -65,7 +65,7 @@
if (ismob(src.loc)) //should allow masks to update when it is opened/closed
var/mob/M = src.loc
M.update_inv_wear_mask()
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
/obj/item/clothing/head/welding/demon
name = "demonic welding helmet"
diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm
index c821aeeca2..be5dad9f66 100644
--- a/code/modules/clothing/masks/miscellaneous.dm
+++ b/code/modules/clothing/masks/miscellaneous.dm
@@ -373,7 +373,7 @@
if(src && choice && !user.incapacitated() && in_range(user,src))
icon_state = options[choice]
user.update_inv_wear_mask()
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
to_chat(user, span_notice("Your paper mask now is now [choice]."))
return 1
@@ -408,7 +408,7 @@
if(src && choice && !user.incapacitated() && in_range(user,src))
icon_state = options[choice]
user.update_inv_wear_mask()
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
to_chat(user, span_notice("Your [src] now displays a [choice] emotion."))
return 1
diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm
index f7729dd89d..1df7f1c759 100644
--- a/code/modules/clothing/shoes/magboots.dm
+++ b/code/modules/clothing/shoes/magboots.dm
@@ -42,7 +42,7 @@
playsound(src, 'sound/effects/magnetclamp.ogg', 20)
to_chat(user, "You enable the mag-pulse traction system.")
user.update_inv_shoes() //so our mob-overlays update
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
/obj/item/clothing/shoes/magboots/mob_can_equip(mob/user, slot, disable_warning = FALSE)
var/mob/living/carbon/human/H = user
@@ -114,7 +114,7 @@
magpulse = 1
canremove = FALSE //kinda hard to take off magclaws when you are gripping them tightly.
to_chat(user, "You dig your claws deeply into the flooring, bracing yourself.")
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
//In case they somehow come off while enabled.
/obj/item/clothing/shoes/magboots/vox/dropped(mob/user as mob)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 56e464bcf5..66e4e29dd7 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -465,7 +465,7 @@
throw_alert("handcuffed", /obj/screen/alert/restrained/handcuffed, new_master = handcuffed)
else
clear_alert("handcuffed")
- update_action_buttons_icon() //some of our action buttons might be unusable when we're handcuffed.
+ update_mob_action_buttons() //some of our action buttons might be unusable when we're handcuffed.
update_inv_handcuffed()
// Clears blood overlays
diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm
index d6a81795eb..417d3d35d6 100644
--- a/code/modules/mob/living/death.dm
+++ b/code/modules/mob/living/death.dm
@@ -1,6 +1,6 @@
/mob/living/death(gibbed)
clear_fullscreens()
- update_action_buttons_icon()
+ update_mob_action_buttons()
if(ai_holder)
ai_holder.go_sleep()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 566cc0113e..82810d0da5 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -1041,7 +1041,7 @@
if(lying != lying_prev)
lying_prev = lying
update_transform()
- update_action_buttons_icon(status_only = TRUE)
+ update_mob_action_buttons()
//VOREStation Add
if(lying && LAZYLEN(buckled_mobs))
for(var/mob/living/L as anything in buckled_mobs)
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 7f72aa87e3..1d5c8a1d49 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -38,8 +38,9 @@
client.images = null //remove the images such as AIs being unable to see runes
client.screen = list() //remove hud items just in case
- if(hud_used) qdel(hud_used) //remove the hud objects
- hud_used = new /datum/hud(src)
+ if(hud_used)
+ qdel(hud_used) //remove the hud objects
+ new /datum/hud(src)
if(client.prefs && client.prefs.client_fps)
client.fps = client.prefs.client_fps
diff --git a/code/modules/projectiles/guns/projectile/shotgun_vr.dm b/code/modules/projectiles/guns/projectile/shotgun_vr.dm
index 8ee2e7d049..f8cdfc8d3b 100644
--- a/code/modules/projectiles/guns/projectile/shotgun_vr.dm
+++ b/code/modules/projectiles/guns/projectile/shotgun_vr.dm
@@ -69,7 +69,7 @@
H.update_inv_r_hand()
playsound(src, 'sound/weapons/targeton.ogg', 50, 1)
- user.update_action_buttons_icon()
+ user.update_mob_action_buttons()
/obj/item/gun/projectile/shotgun/compact/verb/verb_toggle_stock(mob/user as mob)
set category = "Object"
diff --git a/code/modules/vore/fluffstuff/custom_clothes_vr.dm b/code/modules/vore/fluffstuff/custom_clothes_vr.dm
index 5c90cefa94..3bd2856644 100644
--- a/code/modules/vore/fluffstuff/custom_clothes_vr.dm
+++ b/code/modules/vore/fluffstuff/custom_clothes_vr.dm
@@ -1131,7 +1131,7 @@ No. With a teleporter? Just *no*. - Hawk, YW
if (ismob(loc)) //should allow masks to update when it is opened/closed
var/mob/M = loc
M.update_inv_wear_mask()
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
//Vorrarkul: Theodora Lindt
/obj/item/clothing/suit/chococoat
@@ -2601,7 +2601,7 @@ Departamental Swimsuits, for general use
if (ismob(loc)) //should allow masks to update when it is opened/closed
var/mob/M = loc
M.update_inv_wear_mask()
- usr.update_action_buttons_icon()
+ usr.update_mob_action_buttons()
/obj/item/clothing/suit/storage/toggle/labcoat/fluff/zeracloak
name = "Grand Purple Cloak"
diff --git a/icons/hud/64x16_actions.dmi b/icons/hud/64x16_actions.dmi
new file mode 100644
index 0000000000..812d888846
Binary files /dev/null and b/icons/hud/64x16_actions.dmi differ
diff --git a/icons/hud/screen_gen.dmi b/icons/hud/screen_gen.dmi
new file mode 100644
index 0000000000..86383abbef
Binary files /dev/null and b/icons/hud/screen_gen.dmi differ
diff --git a/icons/mob/actions/backgrounds.dmi b/icons/mob/actions/backgrounds.dmi
new file mode 100644
index 0000000000..c8f8b723f9
Binary files /dev/null and b/icons/mob/actions/backgrounds.dmi differ
diff --git a/vorestation.dme b/vorestation.dme
index df662e3d2f..0328b575ab 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -70,6 +70,7 @@
#include "code\__defines\holder.dm"
#include "code\__defines\holomap.dm"
#include "code\__defines\hoses.dm"
+#include "code\__defines\hud.dm"
#include "code\__defines\implant.dm"
#include "code\__defines\input.dm"
#include "code\__defines\instruments.dm"
@@ -236,6 +237,7 @@
#include "code\_helpers\visual_filters.dm"
#include "code\_helpers\widelists_ch.dm"
#include "code\_helpers\graphs\astar_ch.dm"
+#include "code\_helpers\weakref.dm"
#include "code\_helpers\icons\flatten.dm"
#include "code\_helpers\logging\debug.dm"
#include "code\_helpers\logging\ui.dm"
@@ -256,8 +258,6 @@
#include "code\_onclick\other_mobs.dm"
#include "code\_onclick\rig.dm"
#include "code\_onclick\telekinesis.dm"
-#include "code\_onclick\hud\_defines.dm"
-#include "code\_onclick\hud\_defines_vr.dm"
#include "code\_onclick\hud\ability_screen_objects.dm"
#include "code\_onclick\hud\ai.dm"
#include "code\_onclick\hud\alert.dm"
@@ -287,7 +287,13 @@
#include "code\_onclick\hud\soulcatcher_guest.dm"
#include "code\_onclick\hud\spell_screen_objects.dm"
#include "code\_onclick\hud\action\action.dm"
+#include "code\_onclick\hud\action\action_group.dm"
+#include "code\_onclick\hud\action\action_group_helpers.dm"
+#include "code\_onclick\hud\action\action_item_overlay.dm"
#include "code\_onclick\hud\action\action_screen_objects.dm"
+#include "code\_onclick\hud\action\innate_action.dm"
+#include "code\_onclick\hud\action\item_action.dm"
+#include "code\_onclick\hud\action\positioning.dm"
#include "code\_onclick\hud\action\types\item.dm"
#include "code\ATMOSPHERICS\_atmos_setup.dm"
#include "code\ATMOSPHERICS\_atmospherics_helpers.dm"