From 1fe7819c1b7d25c84055d5865cb871b732893b1f Mon Sep 17 00:00:00 2001
From: Luc <89928798+lewcc@users.noreply.github.com>
Date: Sun, 24 Mar 2024 13:38:53 -0400
Subject: [PATCH] Ports action button refactor from /tg/ (#24482)
* add the new stuff
* guh
* add the brunt of it
* action button stuff
* adds a lot of other stuff
* careful find and replace
* it loads
* so many huds
* ehh yeah this kinda works
* fixes this runtime
* hacks in some DMI stuff, we'll fix it later
* Adds better mouse pointer for dragging buttons
* gets dragging into place working again, updates DMI
* moves define
* remove extra hud defines, now it compiles
* CHECK GREP
* owww
* rename action defines
* ticks
* are we good
* let's try that again
* renames mob vars
* renames
* fixes some hud issues with huds getting the wrong variable
* fixes some alignment issues
* Adds mouse-wheel scroll support
* left -> right
* I am the entire circus
* first self-review pass
* Another review pass -- just realized I need to fix keybinds
* more testing
* update icons
* oops
* Fix spell icons not appearing properly
* fixes some outstanding issues
- Action button backgrounds now work properly
- Fixes some runtimes on dropping
- Fixes some misplaced icons
* hmm
* weh
* again?
* nah?
* maybe this?
* asdf
* should finally fix CI
* add client check
* move the ?
* add missing override causing runtimes
* shift-clicking now doesn't trigger the button
* better docs
---
code/__DEFINES/_click.dm | 29 +
code/__DEFINES/_math.dm | 2 +
code/__DEFINES/action_button_defines.dm | 21 +
code/__DEFINES/action_defines.dm | 6 +
code/__DEFINES/dcs/signals.dm | 7 +
code/__DEFINES/hud.dm | 27 +
code/__DEFINES/misc_defines.dm | 5 -
code/__HELPERS/lists.dm | 1 +
code/__HELPERS/mob_helpers.dm | 9 +-
code/__HELPERS/screen_objs.dm | 91 +++
code/_onclick/hud/action_button.dm | 628 +++++++++++++-----
code/_onclick/hud/action_group.dm | 223 +++++++
code/_onclick/hud/ai_hud.dm | 4 -
code/_onclick/hud/alien_hud.dm | 5 -
code/_onclick/hud/alien_larva.dm | 4 -
code/_onclick/hud/blob_overmind.dm | 4 -
code/_onclick/hud/bot_hud.dm | 4 -
code/_onclick/hud/constructs_hud.dm | 19 -
code/_onclick/hud/ghost_hud.dm | 5 -
code/_onclick/hud/guardian_hud.dm | 4 -
code/_onclick/hud/hud_datum.dm | 102 ++-
code/_onclick/hud/human_hud.dm | 2 +-
code/_onclick/hud/movable_screen_objects.dm | 39 +-
code/_onclick/hud/other_mobs_hud.dm | 8 -
code/_onclick/hud/robot_hud.dm | 5 -
code/_onclick/hud/screentip.dm | 6 +-
code/_onclick/hud/slime_hud.dm | 4 -
code/datums/action.dm | 233 ++++---
code/datums/spell.dm | 10 +-
code/datums/spell_cooldown/spell_charges.dm | 2 +-
code/datums/spell_cooldown/spell_cooldown.dm | 4 +-
.../alien_spells/basetype_alien_spell.dm | 2 +-
.../spells/alien_spells/neurotoxin_spit.dm | 2 +-
code/datums/spells/lichdom.dm | 12 +-
code/datums/spells/wizard_spells.dm | 2 +-
code/game/atoms.dm | 2 +-
code/game/gamemodes/cult/blood_magic.dm | 62 +-
code/game/gamemodes/cult/cult_actions.dm | 6 +-
code/game/gamemodes/cult/cult_items.dm | 1 +
.../gamemodes/malfunction/Malf_Modules.dm | 9 +-
.../miniantags/abduction/abduction_gear.dm | 4 +-
.../gamemodes/miniantags/guardian/guardian.dm | 1 +
.../miniantags/guardian/host_actions.dm | 4 +-
code/game/gamemodes/miniantags/morph/morph.dm | 10 +-
.../miniantags/pulsedemon/pulsedemon.dm | 4 +-
.../pulsedemon/pulsedemon_abilities.dm | 6 +-
.../miniantags/revenant/revenant_abilities.dm | 2 +-
code/game/jobs/job/support.dm | 4 +-
code/game/mecha/mecha_actions.dm | 14 +-
code/game/objects/items.dm | 8 +-
code/game/objects/items/devices/flashlight.dm | 2 +-
.../objects/items/devices/traitordevices.dm | 2 +-
.../objects/items/devices/voice_changer.dm | 2 +-
.../objects/items/robot/robot_upgrades.dm | 2 +-
.../weapons/bio_chips/bio_chip_stealth.dm | 4 +-
code/game/objects/items/weapons/defib.dm | 2 +-
.../objects/items/weapons/holy_weapons.dm | 3 -
.../objects/items/weapons/tanks/jetpack.dm | 2 +-
code/game/objects/items/weapons/twohanded.dm | 2 +-
.../modules/antagonists/vampire/vamp_datum.dm | 2 +-
.../vampire/vampire_powers/umbrae_powers.dm | 4 +-
code/modules/client/client_procs.dm | 5 +
code/modules/clothing/chameleon.dm | 10 +-
code/modules/clothing/clothing.dm | 8 +-
.../clothing/glasses/engine_goggles.dm | 2 +-
code/modules/clothing/head/hardhat.dm | 2 +-
code/modules/clothing/head/soft_caps.dm | 2 +-
code/modules/clothing/shoes/magboots.dm | 2 +-
code/modules/clothing/spacesuits/hardsuit.dm | 4 +-
code/modules/clothing/spacesuits/plasmamen.dm | 2 +-
code/modules/clothing/suits/armor_suits.dm | 2 +-
code/modules/clothing/suits/hood.dm | 4 +-
code/modules/clothing/suits/misc_suits.dm | 2 +-
code/modules/customitems/item_defines.dm | 6 +-
code/modules/events/blob/overmind.dm | 2 +
code/modules/games/cards.dm | 2 +-
code/modules/input/keybindings_procs.dm | 7 +-
.../mining/equipment/kinetic_crusher.dm | 2 +-
.../mining/equipment/regenerative_core.dm | 2 +-
.../mob/dead/observer/observer_base.dm | 1 +
code/modules/mob/inventory_procs.dm | 2 +-
code/modules/mob/living/brain/MMI.dm | 4 +-
.../mob/living/carbon/alien/alien_base.dm | 2 +-
.../living/carbon/alien/humanoid/humanoid.dm | 5 +-
.../mob/living/carbon/alien/larva/larva.dm | 1 +
.../mob/living/carbon/human/species/golem.dm | 4 +-
code/modules/mob/living/silicon/ai/ai_mob.dm | 1 +
.../mob/living/silicon/robot/robot_mob.dm | 1 +
.../mob/living/simple_animal/bot/bot.dm | 2 +
.../mob/living/simple_animal/constructs.dm | 5 +
.../mob/living/simple_animal/friendly/dog.dm | 1 +
.../mob/living/simple_animal/friendly/pet.dm | 1 +
.../hostile/mining/elites/elite.dm | 6 +-
.../hostile/terror_spiders/empress_terror.dm | 3 +-
.../modules/mob/living/simple_animal/shade.dm | 1 +
.../mob/living/simple_animal/simple_animal.dm | 1 +
.../living/simple_animal/slime/slime_mob.dm | 1 +
code/modules/mob/living/stat_states.dm | 2 +-
code/modules/mob/mob.dm | 3 +-
code/modules/mob/mob_vars.dm | 4 +
code/modules/mod/mod_actions.dm | 4 +-
code/modules/projectiles/gun.dm | 2 +-
.../projectiles/guns/projectile/automatic.dm | 2 +-
code/modules/surgery/organs/heart.dm | 4 +-
.../surgery/organs/subtypes/skrell_organs.dm | 2 +-
icons/hud/64x16_actions.dmi | Bin 0 -> 3404 bytes
icons/mob/screen_ai.dmi | Bin 2494 -> 6373 bytes
icons/mob/screen_alien.dmi | Bin 26695 -> 28996 bytes
icons/mob/screen_gen.dmi | Bin 137781 -> 138971 bytes
icons/mob/screen_midnight.dmi | Bin 28412 -> 29918 bytes
icons/mob/screen_operative.dmi | Bin 29310 -> 30286 bytes
icons/mob/screen_plasmafire.dmi | Bin 28625 -> 29551 bytes
icons/mob/screen_retro.dmi | Bin 10820 -> 11769 bytes
icons/mob/screen_robot.dmi | Bin 27353 -> 28942 bytes
icons/mob/screen_slimecore.dmi | Bin 27090 -> 28027 bytes
icons/mouse_icons/screen_drag.dmi | Bin 0 -> 326 bytes
paradise.dme | 3 +
117 files changed, 1328 insertions(+), 501 deletions(-)
create mode 100644 code/__DEFINES/action_defines.dm
create mode 100644 code/__HELPERS/screen_objs.dm
create mode 100644 code/_onclick/hud/action_group.dm
create mode 100644 icons/hud/64x16_actions.dmi
create mode 100644 icons/mouse_icons/screen_drag.dmi
diff --git a/code/__DEFINES/_click.dm b/code/__DEFINES/_click.dm
index 86396a730f0..07d044f8b89 100644
--- a/code/__DEFINES/_click.dm
+++ b/code/__DEFINES/_click.dm
@@ -14,3 +14,32 @@
#define CTRL_CLICK "ctrl"
#define ALT_CLICK "alt"
#define SHIFT_CLICK "shift"
+
+//Cells involved if using a Grid control
+#define DRAG_CELL "drag-cell"
+#define DROP_CELL "drop-cell"
+
+//The button used for dragging (only sent for unrelated mouse up/down messages during a drag)
+#define DRAG "drag"
+
+//If the mouse is over a link in maptext, or this event is related to clicking such a link
+#define LINK "link"
+
+//Pixel coordinates relative to the icon's position on screen
+#define VIS_X "vis-x"
+#define VIS_Y "vis-y"
+
+//Pixel coordinates within the icon, in the icon's coordinate space
+#define ICON_X "icon-x"
+#define ICON_Y "icon-y"
+
+//Pixel coordinates in screen_loc format ("[tile_x]:[pixel_x],[tile_y]:[pixel_y]")
+#define SCREEN_LOC "screen-loc"
+
+//https://secure.byond.com/docs/ref/info.html#/atom/var/mouse_opacity
+/// Objects will ignore being clicked on regardless of their transparency (used in parallax, lighting effects, holograms, lasers, etc.)
+#define MOUSE_OPACITY_TRANSPARENT 0
+/// Objects will be clicked on if it is the topmost object and the pixel isn't transparent at the position of the mouse (default behavior for 99.99% of game objects)
+#define MOUSE_OPACITY_ICON 1
+/// Objects will be always be clicked on regardless of pixel transparency or other objects at that location (used in space vines, megafauna, storage containers)
+#define MOUSE_OPACITY_OPAQUE 2
diff --git a/code/__DEFINES/_math.dm b/code/__DEFINES/_math.dm
index d7b0946c614..28c03149a60 100644
--- a/code/__DEFINES/_math.dm
+++ b/code/__DEFINES/_math.dm
@@ -28,6 +28,8 @@
// round() acts like floor(x, 1) by default but can't handle other values
#define FLOOR(x, y) ( round((x) / (y)) * (y) )
+#define ROUND_UP(x) ( -round(-(x)))
+
// Similar to clamp but the bottom rolls around to the top and vice versa. min is inclusive, max is exclusive
#define WRAP(val, min, max) clamp(( min == max ? min : (val) - (round(((val) - (min))/((max) - (min))) * ((max) - (min))) ),min,max)
diff --git a/code/__DEFINES/action_button_defines.dm b/code/__DEFINES/action_button_defines.dm
index 315c1b1af53..ac840dafb9a 100644
--- a/code/__DEFINES/action_button_defines.dm
+++ b/code/__DEFINES/action_button_defines.dm
@@ -5,3 +5,24 @@
#define AB_CHECK_TURF (1<<4)
#define AB_CHECK_HANDS_BLOCKED (1<<5)
#define AB_CHECK_IMMOBILE (1<<6)
+
+#define ACTION_BUTTON_DEFAULT_BACKGROUND "_use_ui_default_background"
+
+
+//Upper left (action buttons)
+
+#define ui_action_palette "WEST+0:23,NORTH-1:3"
+#define ui_action_palette_offset(north_offset) ("WEST+0:23,NORTH-[1+north_offset]:3")
+
+#define ui_palette_scroll "WEST+1:8,NORTH-6:28"
+#define ui_palette_scroll_offset(north_offset) ("WEST+1:8,NORTH-[6+north_offset]:15")
+// Defines relating to action button positions
+
+/// Whatever the base action datum thinks is best
+#define SCRN_OBJ_DEFAULT "default"
+/// Floating somewhere on the hud, not in any predefined place
+#define SCRN_OBJ_FLOATING "floating"
+/// In the list of buttons stored at the top of the screen
+#define SCRN_OBJ_IN_LIST "list"
+/// In the collapseable palette
+#define SCRN_OBJ_IN_PALETTE "palette"
diff --git a/code/__DEFINES/action_defines.dm b/code/__DEFINES/action_defines.dm
new file mode 100644
index 00000000000..726148081d9
--- /dev/null
+++ b/code/__DEFINES/action_defines.dm
@@ -0,0 +1,6 @@
+
+#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 142b1be96b8..66b7ca87f19 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -219,6 +219,9 @@
/////////////////
+///from base of client/Click(): (atom/target, atom/location, control, params, mob/user)
+#define COMSIG_CLIENT_CLICK "atom_client_click"
+
///from base of area/Entered(): (/area)
#define COMSIG_ENTER_AREA "enter_area"
///from base of area/Exited(): (/area)
@@ -924,6 +927,10 @@
#define COMSIG_ACTION_TRIGGER "action_trigger"
#define COMPONENT_ACTION_BLOCK_TRIGGER (1<<0)
+// Note that this is only defined for actions because this could be a good bit expensive otherwise
+/// From base of /atom/movable/screen/movable/action_button/MouseWheel(src, delta_x, delta_y, location, control, params)
+#define COMSIG_ACTION_SCROLLED "action_scrolled"
+
//Xenobio hotkeys
///from slime CtrlClickOn(): (/mob)
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index e2b9380850c..8b1670845eb 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -70,3 +70,30 @@
#define EXAMINE_HUD_MEDICAL_READ "medical_read"
#define EXAMINE_HUD_MEDICAL_WRITE "medical_write"
#define EXAMINE_HUD_SKILLS "skills"
+
+/proc/ui_hand_position(i)
+ // values based on old hand ui positions (CENTER:-/+16,SOUTH:5)
+ var/x_off = i % 2 ? 0 : -1
+ var/y_off = round((i-1) / 2)
+ return"CENTER+[x_off]:16,SOUTH+[y_off]:5"
+
+/proc/ui_equip_position(mob/M)
+ // values based on old equip ui position (CENTER: +/-16,SOUTH+1:5)
+ var/y_off = round(1 / 2)
+ return "CENTER:-16,SOUTH+[y_off+1]:5"
+
+/proc/ui_swaphand_position(mob/M, which = 1)
+ // values based on old swaphand ui positions (CENTER: +/-16,SOUTH+1:5)
+ var/x_off = which == 1 ? -1 : 0
+ var/y_off = round(1 / 2)
+ return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5"
+
+
+/// Takes a string or num view, and converts it to pixel width/height in a list(pixel_width, pixel_height)
+/proc/view_to_pixels(view)
+ if(!view)
+ return list(0, 0)
+ var/list/view_info = getviewsize(view)
+ view_info[1] *= world.icon_size
+ view_info[2] *= world.icon_size
+ return view_info
diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index 2d0e6525127..db931f29746 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -412,11 +412,6 @@
#define AREASELECT_CORNERA "corner A"
#define AREASELECT_CORNERB "corner B"
-//https://secure.byond.com/docs/ref/info.html#/atom/var/mouse_opacity
-#define MOUSE_OPACITY_TRANSPARENT 0
-#define MOUSE_OPACITY_ICON 1
-#define MOUSE_OPACITY_OPAQUE 2
-
// Defib stats
/// Past this much time the patient is unrecoverable (in deciseconds).
#define BASE_DEFIB_TIME_LIMIT (300 SECONDS)
diff --git a/code/__HELPERS/lists.dm b/code/__HELPERS/lists.dm
index 812f8369e23..f6dec8c1b78 100644
--- a/code/__HELPERS/lists.dm
+++ b/code/__HELPERS/lists.dm
@@ -676,6 +676,7 @@
#define UNSETEMPTY(L) if(L && !L.len) L = null
#define LAZYREMOVE(L, I) if(L) { L -= I; if(!L.len) { L = null; } }
#define LAZYADD(L, I) if(!L) { L = list(); } L += I;
+#define LAZYOR(L, I) if(!L) { L = list(); } L |= I;
/// Adds I to L, initializing L if necessary, if I is not already in L
#define LAZYDISTINCTADD(L, I) if(!L) { L = list(); } L |= I;
#define LAZYACCESS(L, I) (L ? (isnum(I) ? (I > 0 && I <= L.len ? L[I] : null) : L[I]) : null)
diff --git a/code/__HELPERS/mob_helpers.dm b/code/__HELPERS/mob_helpers.dm
index fef111d3f39..6abb34f3423 100644
--- a/code/__HELPERS/mob_helpers.dm
+++ b/code/__HELPERS/mob_helpers.dm
@@ -609,10 +609,17 @@ GLOBAL_LIST_EMPTY(do_after_once_tracker)
var/totalviewrange = 1 + 2 * view
viewX = totalviewrange
viewY = totalviewrange
- else
+ else if(istext(view))
var/list/viewrangelist = splittext(view, "x")
viewX = text2num(viewrangelist[1])
viewY = text2num(viewrangelist[2])
+ else if(islist(view) && length(view) == 2 && isnum(view[1]) && isnum(view[2]))
+ // better be a list of nums!
+ viewX = view[1]
+ viewY = view[2]
+ else
+ CRASH("Invalid view type parameter passed to getviewsize: [view]")
+
return list(viewX, viewY)
//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value.
diff --git a/code/__HELPERS/screen_objs.dm b/code/__HELPERS/screen_objs.dm
new file mode 100644
index 00000000000..aa86b6d5795
--- /dev/null
+++ b/code/__HELPERS/screen_objs.dm
@@ -0,0 +1,91 @@
+/// Takes a screen loc string in the format
+/// "+-left-offset:+-pixel,+-bottom-offset:+-pixel"
+/// Where the :pixel is optional, and returns
+/// A list in the format (x_offset, y_offset)
+/// We require context to get info out of screen locs that contain relative info, so NORTH, SOUTH, etc
+/proc/screen_loc_to_offset(screen_loc, view)
+ if(!screen_loc)
+ return list(64, 64)
+ var/list/view_size = view_to_pixels(view)
+ var/x = 0
+ var/y = 0
+ // Time to parse for directional relative offsets
+ if(findtext(screen_loc, "EAST")) // If you're starting from the east, we start from the east too
+ x += view_size[1]
+ if(findtext(screen_loc, "WEST")) // HHHHHHHHHHHHHHHHHHHHHH WEST is technically a 1 tile offset from the start. Shoot me please
+ x += world.icon_size
+ if(findtext(screen_loc, "NORTH"))
+ y += view_size[2]
+ if(findtext(screen_loc, "SOUTH"))
+ y += world.icon_size
+ // Cut out everything we just parsed
+ screen_loc = cut_relative_direction(screen_loc)
+
+ var/list/x_and_y = splittext(screen_loc, ",")
+ var/list/x_pack = splittext(x_and_y[1], ":")
+ var/list/y_pack = splittext(x_and_y[2], ":")
+ x += text2num(x_pack[1]) * world.icon_size
+ y += text2num(y_pack[1]) * world.icon_size
+
+ if(length(x_pack) > 1)
+ x += text2num(x_pack[2])
+ if(length(y_pack) > 1)
+ y += text2num(y_pack[2])
+ return list(x, y)
+
+/// Takes a list in the form (x_offset, y_offset)
+/// And converts it to a screen loc string
+/// Accepts an optional view string/size to force the screen_loc around, so it can't go out of scope
+/proc/offset_to_screen_loc(x_offset, y_offset, view = null)
+ if(view)
+ var/list/view_bounds = view_to_pixels(view)
+ x_offset = clamp(x_offset, world.icon_size, view_bounds[1])
+ y_offset = clamp(y_offset, world.icon_size, view_bounds[2])
+
+ // Round with no argument is floor, so we get the non pixel offset here
+ var/x = round(x_offset / world.icon_size)
+ var/pixel_x = x_offset % world.icon_size
+ var/y = round(y_offset / world.icon_size)
+ var/pixel_y = y_offset % world.icon_size
+
+ var/list/generated_loc = list()
+ generated_loc += "[x]"
+ if(pixel_x)
+ generated_loc += ":[pixel_x]"
+ generated_loc += ",[y]"
+ if(pixel_y)
+ generated_loc += ":[pixel_y]"
+ return jointext(generated_loc, "")
+
+/**
+ * Returns a valid location to place a screen object without overflowing the viewport
+ *
+ * * target: The target location as a purely number based screen_loc string "+-left-offset:+-pixel,+-bottom-offset:+-pixel"
+ * * target_offset: The amount we want to offset the target location by. We explictly don't care about direction here, we will try all 4
+ * * view: The view variable of the client we're doing this for. We use this to get the size of the screen
+ *
+ * Returns a screen loc representing the valid location
+**/
+/proc/get_valid_screen_location(target_loc, target_offset, view)
+ var/list/offsets = screen_loc_to_offset(target_loc)
+ var/base_x = offsets[1]
+ var/base_y = offsets[2]
+
+ var/list/view_size = view_to_pixels(view)
+
+ // Bias to the right, down, left, and then finally up
+ if(base_x + target_offset < view_size[1])
+ return offset_to_screen_loc(base_x + target_offset, base_y, view)
+ if(base_y - target_offset > world.icon_size)
+ return offset_to_screen_loc(base_x, base_y - target_offset, view)
+ if(base_x - target_offset > world.icon_size)
+ return offset_to_screen_loc(base_x - target_offset, base_y, view)
+ if(base_y + target_offset < view_size[2])
+ return offset_to_screen_loc(base_x, base_y + target_offset, view)
+ stack_trace("You passed in a scren location {[target_loc]} and offset {[target_offset]} that can't be fit in the viewport Width {[view_size[1]]}, Height {[view_size[2]]}. what did you do lad")
+ return null // The fuck did you do lad
+
+/// Takes a screen_loc string and cut out any directions like NORTH or SOUTH
+/proc/cut_relative_direction(fragment)
+ var/static/regex/regex = regex(@"([A-Z])\w+", "g")
+ return regex.Replace(fragment, "")
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index e67eaf0dc82..b78612c0fbd 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -1,36 +1,104 @@
/atom/movable/screen/movable/action_button
desc = "CTRL-Shift click on this button to bind it to a hotkey."
- var/datum/action/linked_action
- var/actiontooltipstyle = ""
screen_loc = null
- var/ordered = TRUE
+ /// The action triggered by this button.
+ var/datum/action/linked_action
+ /// The style of tool-tip.
+ var/actiontooltipstyle = ""
+ /// The keybind that will trigger this action button.
var/datum/keybinding/mob/trigger_action_button/linked_keybind
+ /// The HUD this action button belongs to
+ var/datum/hud/our_hud
+
+ /// 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
+ /// UID of the last thing we hovered over. Used for managing action button dragging.
+ var/last_hovered_ref
+
+/atom/movable/screen/movable/action_button/Destroy()
+ . = ..()
+ if(our_hud)
+ var/mob/viewer = our_hud.mymob
+ our_hud.hide_action(src)
+ viewer?.client?.screen -= src
+ linked_action.viewers -= our_hud
+ viewer.update_action_buttons()
+ our_hud = null
+ linked_action = null
+ return ..()
+
+/atom/movable/screen/movable/action_button/proc/can_use(mob/user)
+ if(!linked_action)
+ return TRUE
+ return !isnull(linked_action.viewers[user.hud_used])
+
+// Entered and Exited won't fire while you're dragging something, because you're still "holding" it
+// Very much byond logic, but I want nice behavior, so we fake it with drag
+/atom/movable/screen/movable/action_button/MouseDrag(atom/over_object, src_location, over_location, src_control, over_control, params)
+ . = ..()
+ if(!can_use(usr))
+ return
+
+
+ // We're basically holding a ref here to the last object we were over.
+ var/atom/last_hovered = locateUID(last_hovered_ref)
+ if(last_hovered == over_object)
+ return
+ else
+ if(!istype(last_hovered))
+ // no current ref, assume it was us. This is our "first go" location
+ last_hovered = src
+ var/datum/hud/our_hud = usr.hud_used
+ our_hud?.generate_landings(src)
+ // close the tooltips on our button so we don't get stuck with them
+
+ if(last_hovered)
+ last_hovered.MouseExited(over_location, over_control, params)
+ closeToolTip(usr)
+ last_hovered_ref = UID(over_object)
+ over_object.MouseEntered(over_location, over_control, params)
/atom/movable/screen/movable/action_button/MouseDrop(over_object)
- if(locked && could_be_click_lag()) // in case something bad happend and game realised we dragged our ability instead of pressing it
- Click()
- drag_start = 0
+ last_hovered_ref = null
+ if(!can_use(usr))
return
- drag_start = 0
- if(locked)
- to_chat(usr, "Action button \"[name]\" is locked, unlock it first.")
- closeToolTip(usr)
+ var/datum/hud/our_hud = usr.hud_used
+ if(over_object == src)
+ our_hud.hide_landings()
return
- if((istype(over_object, /atom/movable/screen/movable/action_button) && !istype(over_object, /atom/movable/screen/movable/action_button/hide_toggle)))
- var/atom/movable/screen/movable/action_button/B = over_object
- var/list/actions = usr.actions
- actions.Swap(actions.Find(linked_action), actions.Find(B.linked_action))
- moved = FALSE
- ordered = TRUE
- B.moved = FALSE
- B.ordered = TRUE
- closeToolTip(usr)
- usr.update_action_buttons()
- else if(istype(over_object, /atom/movable/screen/movable/action_button/hide_toggle))
- closeToolTip(usr)
- else
- closeToolTip(usr)
- return ..()
+
+ if(istype(over_object, /atom/movable/screen/action_landing))
+ var/atom/movable/screen/action_landing/reserve = over_object
+ reserve.hit_by(src)
+ our_hud.hide_landings()
+ return
+
+ our_hud.hide_landings()
+
+ if(istype(over_object, /atom/movable/screen/button_palette) || istype(over_object, /atom/movable/screen/palette_scroll))
+ our_hud.position_action(src, SCRN_OBJ_IN_PALETTE)
+ return
+ if(istype(over_object, /atom/movable/screen/movable/action_button))
+ var/atom/movable/screen/movable/action_button/button = over_object
+ our_hud.position_action_relative(src, button)
+ return
+
+ . = ..()
+
+ our_hud.position_action(src, screen_loc)
+
+/atom/movable/screen/movable/action_button/MouseWheel(delta_x, delta_y, location, control, params)
+ . = ..()
+ SEND_SIGNAL(src, COMSIG_ACTION_SCROLLED, delta_x, delta_y, location, control, params)
+
+/atom/movable/screen/movable/action_button/proc/load_position()
+ var/mob/user = our_hud.mymob
+ if(!user)
+ return
+ var/position_info = SCRN_OBJ_DEFAULT
+ user.hud_used.position_action(src, position_info)
/atom/movable/screen/movable/action_button/Click(location, control, params)
var/list/modifiers = params2list(params)
@@ -41,15 +109,8 @@
return FALSE
usr.changeNext_click(1)
if(modifiers["shift"])
- if(locked)
- to_chat(usr, "Action button \"[name]\" is locked, unlock it first.")
- return TRUE
- moved = FALSE
- usr.update_action_buttons(TRUE) //redraw buttons that are no longer considered "moved"
- return TRUE
- if(modifiers["ctrl"])
- locked = !locked
- to_chat(usr, "Action button \"[name]\" [locked ? "" : "un"]locked.")
+ var/datum/hud/our_hud = usr.hud_used
+ our_hud.position_action(src, SCRN_OBJ_DEFAULT)
return TRUE
if(modifiers["alt"])
AltClick(usr)
@@ -57,12 +118,28 @@
if(modifiers["middle"])
linked_action.Trigger(left_click = FALSE)
return TRUE
+ if(modifiers["ctrl"])
+ CtrlClick(usr)
+ return TRUE
linked_action.Trigger(TRUE)
transform = transform.Scale(0.8, 0.8)
alpha = 200
animate(src, transform = matrix(), time = 0.4 SECONDS, alpha = 255)
return TRUE
+
+/**
+ * This is a silly proc used in hud code code to determine what icon and icon state we should be using
+ * for hud elements (such as action buttons) that don't have their own icon and icon state set.
+ *
+ * It returns a list, which is pretty much just a struct of info
+ */
+/datum/hud/proc/get_action_buttons_icons()
+ . = list()
+ .["bg_icon"] = ui_style2icon(mymob.client?.prefs.UI_style)
+ .["bg_state"] = "template"
+ .["bg_state_active"] = "template_active"
+
/atom/movable/screen/movable/action_button/proc/set_to_keybind(mob/user)
var/keybind_to_set_to = uppertext(input(user, "What keybind do you want to set this action button to?") as text)
if(keybind_to_set_to)
@@ -82,82 +159,21 @@
return linked_action.AltTrigger()
/atom/movable/screen/movable/action_button/proc/clean_up_keybinds(mob/owner)
- if(linked_keybind)
- owner.client.active_keybindings[linked_keybind.binded_to] -= (linked_keybind)
- if(!length(owner.client.active_keybindings[linked_keybind.binded_to]))
- owner.client.active_keybindings[linked_keybind.binded_to] = null
- owner.client.active_keybindings -= linked_keybind.binded_to
- QDEL_NULL(linked_keybind)
-
-//Hide/Show Action Buttons ... Button
-/atom/movable/screen/movable/action_button/hide_toggle
- name = "Hide Buttons"
- desc = "Shift-click any button to reset its position, and Control-click it to lock/unlock its position. Alt-click this button to reset all buttons to their default positions."
- icon = 'icons/mob/actions/actions.dmi'
- icon_state = "bg_default"
- var/hidden = FALSE
-
-/atom/movable/screen/movable/action_button/hide_toggle/MouseDrop(over_object)
- if(istype(over_object, /atom/movable/screen/movable/action_button))
- closeToolTip(usr)
- else
- closeToolTip(usr)
- return ..()
-
-/atom/movable/screen/movable/action_button/hide_toggle/Click(location, control, params)
- var/list/modifiers = params2list(params)
-
- if(modifiers["alt"])
- AltClick(usr)
- return TRUE
-
- usr.hud_used.action_buttons_hidden = !usr.hud_used.action_buttons_hidden
-
- hidden = usr.hud_used.action_buttons_hidden
- if(hidden)
- name = "Show Buttons"
- else
- name = "Hide Buttons"
- update_icon(UPDATE_OVERLAYS)
- usr.update_action_buttons()
-
-/atom/movable/screen/movable/action_button/hide_toggle/AltClick(mob/user)
- for(var/V in user.actions)
- var/datum/action/A = V
- var/atom/movable/screen/movable/action_button/B = A.button
- B.moved = FALSE
- if(moved)
- moved = FALSE
- user.update_action_buttons(TRUE)
- to_chat(user, "Action button positions have been reset.")
-
-/atom/movable/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(mob/living/user)
- if(isalien(user))
- icon = 'icons/mob/actions/actions.dmi'
- icon_state = "bg_alien"
- else
- icon = initial(icon)
- icon_state = "bg_default"
- if(user.client) // Apply the client's UI style
- icon = ui_style2icon(user.client.prefs.UI_style)
- icon_state = "template"
- if(user.client)
- alpha = user.client.prefs.UI_style_alpha
- color = user.client.prefs.UI_style_color
- update_icon(UPDATE_OVERLAYS)
-
-/atom/movable/screen/movable/action_button/hide_toggle/update_overlays()
- . = ..()
- var/image/img = image(initial(icon), src, hidden ? "show" : "hide")
- img.appearance_flags = RESET_COLOR | RESET_ALPHA
- . += img
+ if(!linked_keybind || !owner.client)
+ return
+ owner.client.active_keybindings[linked_keybind.binded_to] -= (linked_keybind)
+ if(!length(owner.client.active_keybindings[linked_keybind.binded_to]))
+ owner.client.active_keybindings[linked_keybind.binded_to] = null
+ owner.client.active_keybindings -= linked_keybind.binded_to
+ QDEL_NULL(linked_keybind)
/atom/movable/screen/movable/action_button/MouseEntered(location, control, params)
. = ..()
- if(!QDELETED(src))
+ var/list/modifiers = params2list(params)
+ if(!QDELETED(src) && !LAZYACCESS(modifiers, DRAG) && !LAZYACCESS(modifiers, LEFT_CLICK)) // tooltips opening on drag prevents things from being dropped onto their actual objects)
if(!linked_keybind)
openToolTip(usr, src, params, title = name, content = desc, theme = actiontooltipstyle)
- else
+ else if(linked_keybind)
var/list/desc_information = list()
desc_information += desc
desc_information += "This action is currently bound to the [linked_keybind.binded_to] key."
@@ -166,11 +182,12 @@
/atom/movable/screen/movable/action_button/MouseExited()
closeToolTip(usr)
+ return ..()
-/mob/proc/update_action_buttons_icon()
+/mob/proc/update_action_buttons_icon(status_only = FALSE)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons(status_only)
//This is the proc used to update all the action buttons.
/mob/proc/update_action_buttons(reload_screen)
@@ -180,61 +197,368 @@
if(hud_used.hud_shown != HUD_STYLE_STANDARD)
return
- var/button_number = 0
+ for(var/datum/action/action as anything in actions)
+ var/atom/movable/screen/movable/action_button/button = action.viewers[hud_used]
+ action.UpdateButtons()
+ if(reload_screen)
+ client.screen += button
+ client.update_active_keybindings()
- 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)
- A.override_location() // If the action has a location override, call it
- A.UpdateButtonIcon()
-
- var/atom/movable/screen/movable/action_button/B = A.button
- if(B.ordered)
- button_number++
- 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
-
- 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
if(reload_screen)
- client.screen += hud_used.hide_actions_toggle
+ hud_used.update_our_owner()
+
+ // This holds the logic for the palette buttons
+ hud_used.palette_actions.refresh_actions()
#define AB_MAX_COLUMNS 10
-/datum/hud/proc/ButtonNumberToScreenCoords(number) // TODO : Make this zero-indexed for readabilty
- var/row = round((number - 1)/AB_MAX_COLUMNS)
- var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1
- var/coord_col = "+[col-1]"
- var/coord_col_offset = 4 + 2 * col
+/atom/movable/screen/button_palette
+ desc = "Drag buttons to move them.
Shift-click any button to reset it.
Alt-click this to reset all buttons.
Ctrl-click this to get a detailed explanation of how to use this."
+ 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
- var/coord_row = "[row ? -row : "+0"]"
+/atom/movable/screen/button_palette/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.toggle_palette = null
+ our_hud = null
+ return ..()
- return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-6"
+/atom/movable/screen/button_palette/Initialize(mapload)
+ . = ..()
+ update_appearance()
-/datum/hud/proc/SetButtonCoords(atom/movable/screen/button,number)
- var/row = round((number-1)/AB_MAX_COLUMNS)
- var/col = ((number - 1)%(AB_MAX_COLUMNS)) + 1
- var/x_offset = 32*(col-1) + 4 + 2*col
- var/y_offset = -32*(row+1) + 26
+/atom/movable/screen/button_palette/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
- var/matrix/M = matrix()
- M.Translate(x_offset,y_offset)
- button.transform = M
+/atom/movable/screen/button_palette/update_name(updates)
+ . = ..()
+ if(expanded)
+ name = "Hide Buttons"
+ else
+ name = "Show Buttons"
+
+/atom/movable/screen/button_palette/CtrlClick(mob/user)
+ if(!istype(user))
+ return
+
+ var/list/explanation = list()
+
+ explanation.Insert(
+ "
The Action Palette [bicon(src)] and You",
+ "The Action Palette [bicon(src)] is the new way to hide action buttons. You can hide only some buttons, and quickly bring them up when needed, instead of hiding all of them.",
+ "To use it, drag action buttons onto it, or onto other buttons already in the palette. You can then click the palette to hide or show it. If you click away from the palette, it will be hidden.",
+ "If you have more than 9 action buttons, you can use the scroll arrows to see all of the buttons present, and you can always drag buttons out of the palette whenever you want.",
+ "You can drag action buttons anywhere on the screen, onto other buttons, or onto the little squares that appear when you start dragging them.",
+ "If you drag a button onto another button, it'll snap onto that other button and form a little group.",
+ "", // empty space to force another br
+ "If you need more help, check the wiki or send a mentorhelp."
+ )
+
+ to_chat(user, chat_box_notice(explanation.Join("
")))
+
+
+
+/atom/movable/screen/button_palette/proc/refresh_owner()
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
+
+ var/list/settings = our_hud.get_action_buttons_icons()
+ var/ui_icon = "[settings["bg_icon"]]"
+ var/list/ui_segments = splittext(ui_icon, ".")
+ var/list/ui_paths = splittext(ui_segments[1], "/")
+ var/ui_name = ui_paths[length(ui_paths)]
+
+ icon_state = "[ui_name]_palette"
+
+
+/atom/movable/screen/button_palette/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ var/list/modifiers = params2list(params)
+ // don't show the tooltip if we're dragging
+ if(!LAZYACCESS(modifiers, DRAG) && !LAZYACCESS(modifiers, LEFT_CLICK))
+ show_tooltip(params)
+
+/atom/movable/screen/button_palette/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/atom/movable/screen/button_palette/proc/show_tooltip(params)
+ openToolTip(usr, src, params, title = name, content = desc)
+
+GLOBAL_LIST_INIT(palette_added_matrix, list(0.4,0.5,0.2,0, 0,1.4,0,0, 0,0.4,0.6,0, 0,0,0,1, 0,0,0,0))
+GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,0, 0,0,0,1, 0,0,0,0))
+
+/atom/movable/screen/button_palette/proc/play_item_added()
+ color_for_now(GLOB.palette_added_matrix)
+
+/atom/movable/screen/button_palette/proc/play_item_removed()
+ color_for_now(GLOB.palette_removed_matrix)
+
+/atom/movable/screen/button_palette/proc/color_for_now(list/color)
+ if(color_timer_id)
+ return
+ add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY) //We unfortunately cannot animate matrix colors. Curse you lummy it would be ~~non~~trivial to interpolate between the two valuessssssssss
+ color_timer_id = addtimer(CALLBACK(src, PROC_REF(remove_color), color), 2 SECONDS)
+
+/atom/movable/screen/button_palette/proc/remove_color(list/to_remove)
+ color_timer_id = null
+ remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, to_remove)
+
+/atom/movable/screen/button_palette/proc/can_use(mob/user)
+ return TRUE
+
+/atom/movable/screen/button_palette/Click(location, control, params)
+ if(!can_use(usr))
+ return
+
+ var/list/modifiers = params2list(params)
+
+ if(LAZYACCESS(modifiers, CTRL_CLICK))
+ CtrlClick(usr)
+ return TRUE
+
+ if(LAZYACCESS(modifiers, ALT_CLICK))
+ for(var/datum/action/action as anything in usr.actions) // Reset action positions to default
+ for(var/datum/hud/hud as anything in action.viewers)
+ var/atom/movable/screen/movable/action_button/button = action.viewers[hud]
+ hud.position_action(button, SCRN_OBJ_DEFAULT)
+ to_chat(usr, "Action button positions have been reset.")
+ return TRUE
+
+ set_expanded(!expanded)
+
+/atom/movable/screen/button_palette/proc/clicked_while_open(datum/source, atom/target, atom/location, control, params, mob/user)
+ if(istype(target, /atom/movable/screen/movable/action_button) || istype(target, /atom/movable/screen/palette_scroll) || target == src) // If you're clicking on an action button, or us, you can live
+ return
+ set_expanded(FALSE)
+ if(source)
+ UnregisterSignal(source, COMSIG_CLIENT_CLICK)
+
+
+/atom/movable/screen/button_palette/proc/set_expanded(new_expanded)
+ var/datum/action_group/our_group = our_hud.palette_actions
+ if(!length(our_group.actions)) //Looks dumb, trust me lad
+ new_expanded = FALSE
+ if(expanded == new_expanded)
+ return
+
+ expanded = new_expanded
+ our_group.refresh_actions()
+ update_appearance()
+
+ if(!usr.client)
+ return
+
+ if(expanded)
+ RegisterSignal(usr.client, COMSIG_CLIENT_CLICK, PROC_REF(clicked_while_open))
+ else
+ UnregisterSignal(usr.client, COMSIG_CLIENT_CLICK)
+
+ closeToolTip(usr) //Our tooltips are now invalid, can't seem to update them in one frame, so here, just close them
+
+
+/atom/movable/screen/palette_scroll
+ icon = 'icons/mob/screen_gen.dmi'
+ screen_loc = ui_palette_scroll
+ /// How should we move the palette's actions?
+ /// Positive scrolls down the list, negative scrolls back
+ var/scroll_direction = 0
+ var/datum/hud/our_hud
+
+/atom/movable/screen/palette_scroll/proc/can_use(mob/user)
+ return TRUE
+
+
+/atom/movable/screen/palette_scroll/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
+
+/atom/movable/screen/palette_scroll/proc/refresh_owner()
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
+ var/list/settings = our_hud.get_action_buttons_icons()
+ icon = settings["bg_icon"]
+
+/atom/movable/screen/palette_scroll/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ openToolTip(usr, src, params, title = name, content = desc)
+
+/atom/movable/screen/palette_scroll/Click(location, control, params)
+ if(!can_use(usr))
+ return
+ our_hud.palette_actions.scroll(scroll_direction)
+
+/atom/movable/screen/palette_scroll/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/atom/movable/screen/palette_scroll/down
+ name = "Scroll Down"
+ desc = "Click on this to scroll the actions above down"
+ icon_state = "scroll_down"
+ scroll_direction = 1
+
+/atom/movable/screen/palette_scroll/down/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.palette_down = null
+ our_hud = null
+ return ..()
+
+/atom/movable/screen/palette_scroll/up
+ name = "Scroll Up"
+ desc = "Click on this to scroll the actions above up"
+ icon_state = "scroll_up"
+ scroll_direction = -1
+
+/atom/movable/screen/palette_scroll/up/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.palette_up = null
+ our_hud = null
+ return ..()
+
+/// Exists so you have a place to put your buttons when you move them around
+/atom/movable/screen/action_landing
+ name = "Button Space"
+ desc = "Drag and drop a button into this spot
to add it to the group"
+ icon = 'icons/mob/screen_gen.dmi'
+ icon_state = "reserved"
+ // We want our whole 32x32 space to be clickable, so dropping's forgiving
+ mouse_opacity = MOUSE_OPACITY_OPAQUE
+ var/datum/action_group/owner
+
+/atom/movable/screen/action_landing/Destroy()
+ if(owner)
+ owner.landing = null
+ owner?.owner?.mymob?.client?.screen -= src
+ owner.refresh_actions()
+ owner = null
+ return ..()
+
+/atom/movable/screen/action_landing/proc/set_owner(datum/action_group/owner)
+ src.owner = owner
+ refresh_owner()
+
+/atom/movable/screen/action_landing/proc/refresh_owner()
+ var/datum/hud/our_hud = owner.owner
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
+
+ var/list/settings = our_hud.get_action_buttons_icons()
+ icon = settings["bg_icon"]
+
+/// Reacts to having a button dropped on it
+/atom/movable/screen/action_landing/proc/hit_by(atom/movable/screen/movable/action_button/button)
+ var/datum/hud/our_hud = owner.owner
+ our_hud.position_action(button, owner.location)
+
+/datum/hud/proc/position_action(atom/movable/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
+ 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(atom/movable/screen/movable/action_button/button, atom/movable/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
+ var/client_view_size = getviewsize(our_client.view)
+ button.screen_loc = get_valid_screen_location(relative_to.screen_loc, world.icon_size, client_view_size) // 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(atom/movable/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
+
+/// Generates visual landings for all groups that the button is not a memeber of
+/datum/hud/proc/generate_landings(atom/movable/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/atom/movable/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/atom/movable/screen/movable/action_button/button = action.viewers[src]
+ if(!button)
+ action.ShowTo(mymob)
+ button = action.viewers[src]
+ position_action(button, button.location)
#undef AB_MAX_COLUMNS
diff --git a/code/_onclick/hud/action_group.dm b/code/_onclick/hud/action_group.dm
new file mode 100644
index 00000000000..480e4ba02ca
--- /dev/null
+++ b/code/_onclick/hud/action_group.dm
@@ -0,0 +1,223 @@
+/datum/action_group
+ /// The hud we're owned by
+ var/datum/hud/owner
+ /// The action buttons we're managing
+ var/list/atom/movable/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
+ /// The pixel horizontal offset of our action buttons
+ var/pixel_horiz_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/atom/movable/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_CONTENTS(actions)
+ return ..()
+
+/datum/action_group/proc/insert_action(atom/movable/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(atom/movable/screen/action)
+ actions -= action
+ 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/atom/movable/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)
+ var/coord_row_offset = pixel_north_offset + 2 * (1 * visual_row)
+ return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-[coord_row_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(atom/movable/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/proc/on_scroll(atom/movable/screen/movable/action_button/button, delta_x, delta_y, location, control, params)
+ SIGNAL_HANDLER // COMSIG_ACTION_SCROLLED
+ if(delta_y > 0)
+ owner.palette_actions.scroll(1)
+ else if(delta_y < 0)
+ owner.palette_actions.scroll(-1)
+
+
+/datum/action_group/palette/insert_action(atom/movable/screen/action, index)
+ . = ..()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ RegisterSignal(action, COMSIG_ACTION_SCROLLED, PROC_REF(on_scroll))
+ palette.play_item_added()
+
+/datum/action_group/palette/remove_action(atom/movable/screen/action)
+ . = ..()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ UnregisterSignal(action, COMSIG_ACTION_SCROLLED)
+ palette.play_item_removed()
+ if(!length(actions))
+ palette.set_expanded(FALSE)
+
+/datum/action_group/palette/refresh_actions()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ var/atom/movable/screen/palette_scroll/scroll_down = owner.palette_down
+ var/atom/movable/screen/palette_scroll/scroll_up = owner.palette_up
+
+ 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/atom/movable/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/ai_hud.dm b/code/_onclick/hud/ai_hud.dm
index 918cfa8d432..d99b737cc1f 100644
--- a/code/_onclick/hud/ai_hud.dm
+++ b/code/_onclick/hud/ai_hud.dm
@@ -140,10 +140,6 @@
var/mob/living/silicon/robot/borg = usr
borg.sensor_mode()
-/mob/living/silicon/ai/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/ai(src)
-
/datum/hud/ai/New(mob/owner)
..()
diff --git a/code/_onclick/hud/alien_hud.dm b/code/_onclick/hud/alien_hud.dm
index c0e4a3ee097..eff377796e0 100644
--- a/code/_onclick/hud/alien_hud.dm
+++ b/code/_onclick/hud/alien_hud.dm
@@ -38,11 +38,6 @@
name = "plasma stored"
screen_loc = ui_alienplasmadisplay
-
-/mob/living/carbon/alien/humanoid/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/alien(src)
-
/datum/hud/alien/New(mob/living/carbon/alien/humanoid/owner)
..()
diff --git a/code/_onclick/hud/alien_larva.dm b/code/_onclick/hud/alien_larva.dm
index e911c2bb396..83f304279f7 100644
--- a/code/_onclick/hud/alien_larva.dm
+++ b/code/_onclick/hud/alien_larva.dm
@@ -1,7 +1,3 @@
-/mob/living/carbon/alien/larva/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/larva(src)
-
/datum/hud/larva/New(mob/owner)
..()
diff --git a/code/_onclick/hud/blob_overmind.dm b/code/_onclick/hud/blob_overmind.dm
index dfb74955d1b..13a2fa73efd 100644
--- a/code/_onclick/hud/blob_overmind.dm
+++ b/code/_onclick/hud/blob_overmind.dm
@@ -1,7 +1,3 @@
-/mob/camera/blob/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/blob_overmind(src)
-
/atom/movable/screen/blob
icon = 'icons/mob/blob.dmi'
diff --git a/code/_onclick/hud/bot_hud.dm b/code/_onclick/hud/bot_hud.dm
index 9a27937a5be..d854058a4c1 100644
--- a/code/_onclick/hud/bot_hud.dm
+++ b/code/_onclick/hud/bot_hud.dm
@@ -11,10 +11,6 @@
var/mob/living/simple_animal/bot/B = usr
B.Radio.interact(usr)
-/mob/living/simple_animal/bot/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/bot(src)
-
/datum/hud/bot/New(mob/owner)
..()
var/atom/movable/screen/using
diff --git a/code/_onclick/hud/constructs_hud.dm b/code/_onclick/hud/constructs_hud.dm
index e2dea642407..941f25fad0a 100644
--- a/code/_onclick/hud/constructs_hud.dm
+++ b/code/_onclick/hud/constructs_hud.dm
@@ -1,11 +1,3 @@
-/mob/living/simple_animal/hostile/construct/armoured/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/construct/armoured(src)
-
-/mob/living/simple_animal/hostile/construct/behemoth/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/construct/armoured(src)
-
/datum/hud/construct/armoured/New(mob/owner)
..()
mymob.healths = new /atom/movable/screen()
@@ -15,10 +7,6 @@
mymob.healths.screen_loc = ui_construct_health
infodisplay += mymob.healths
-/mob/living/simple_animal/hostile/construct/builder/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/construct/builder(src)
-
/datum/hud/construct/builder/New(mob/owner)
..()
mymob.healths = new /atom/movable/screen()
@@ -28,10 +16,6 @@
mymob.healths.screen_loc = ui_construct_health
infodisplay += mymob.healths
-/mob/living/simple_animal/hostile/construct/wraith/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/construct/wraith(src)
-
/datum/hud/construct/wraith/New(mob/owner)
..()
mymob.healths = new /atom/movable/screen()
@@ -41,9 +25,6 @@
mymob.healths.screen_loc = ui_construct_health
infodisplay += mymob.healths
-/mob/living/simple_animal/hostile/construct/harvester/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/construct/harvester(src)
/datum/hud/construct/harvester/New(mob/owner)
..()
diff --git a/code/_onclick/hud/ghost_hud.dm b/code/_onclick/hud/ghost_hud.dm
index 251800750a1..f7994d9aa45 100644
--- a/code/_onclick/hud/ghost_hud.dm
+++ b/code/_onclick/hud/ghost_hud.dm
@@ -1,8 +1,3 @@
-/mob/dead/observer/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/ghost(src)
- SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED)
-
/atom/movable/screen/ghost
icon = 'icons/mob/screen_ghost.dmi'
diff --git a/code/_onclick/hud/guardian_hud.dm b/code/_onclick/hud/guardian_hud.dm
index 122b2408c5e..231020265a1 100644
--- a/code/_onclick/hud/guardian_hud.dm
+++ b/code/_onclick/hud/guardian_hud.dm
@@ -1,7 +1,3 @@
-/mob/living/simple_animal/hostile/guardian/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/guardian(src)
-
/datum/hud/guardian/New(mob/owner)
..()
var/atom/movable/screen/using
diff --git a/code/_onclick/hud/hud_datum.dm b/code/_onclick/hud/hud_datum.dm
index 6cfe661d3ac..6ac4cce04b8 100644
--- a/code/_onclick/hud/hud_datum.dm
+++ b/code/_onclick/hud/hud_datum.dm
@@ -7,10 +7,14 @@
/datum/hud
var/mob/mymob
- var/hud_shown = TRUE //Used for the HUD toggle (F12)
- var/hud_version = 1 //Current displayed version of the HUD
- var/inventory_shown = TRUE //the inventory
- var/hotkey_ui_hidden = FALSE //This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons)
+ /// Used for the HUD toggle (F12)
+ var/hud_shown = TRUE
+ /// Current displayed version of the HUD
+ var/hud_version = 1
+ /// Whether or not their toggleable inventory
+ var/inventory_shown = TRUE
+ /// This is to hide the buttons that can be used via hotkeys. (hotkeybuttons list of buttons)
+ var/hotkey_ui_hidden = FALSE
var/atom/movable/screen/lingchemdisplay
var/atom/movable/screen/lingstingdisplay
@@ -28,14 +32,28 @@
var/atom/movable/screen/module_store_icon
var/atom/movable/screen/combo/combo_display
- var/list/static_inventory = list() //the screen objects which are static
- var/list/toggleable_inventory = list() //the screen objects which can be hidden
- var/list/hotkeybuttons = list() //the buttons that can be used via hotkeys
- var/list/infodisplay = list() //the screen objects that display mob info (health, alien plasma, etc...)
- var/list/inv_slots[SLOT_HUD_AMOUNT] // /atom/movable/screen/inventory objects, ordered by their slot ID.
+ // Elements used for action buttons
+ // -- the main clickable buttons
+ var/atom/movable/screen/button_palette/toggle_palette
+ var/atom/movable/screen/palette_scroll/down/palette_down
+ var/atom/movable/screen/palette_scroll/up/palette_up
+ /// the groups of actions, such as palette (previously normal) actions
+ var/datum/action_group/palette/palette_actions
+ /// action group for expanded actions, the normal action set
+ var/datum/action_group/listed/listed_actions
+ /// A list of action buttons which aren't owned by any action group, and are just floating somewhere on the hud.
+ var/list/floating_actions
- var/atom/movable/screen/movable/action_button/hide_toggle/hide_actions_toggle
- var/action_buttons_hidden = FALSE
+ // the screen objects which are static
+ var/list/static_inventory = list()
+ /// the screen objects which can be hidden (your human items on the left)
+ var/list/toggleable_inventory = list()
+ /// the buttons that can be used via hotkeys
+ var/list/hotkeybuttons = list()
+ //the screen objects that display mob info (health, alien plasma, etc...)
+ var/list/infodisplay = list()
+ /// /atom/movable/screen/inventory objects, ordered by their slot ID.
+ var/list/inv_slots[SLOT_HUD_AMOUNT]
var/list/atom/movable/screen/plane_master/plane_masters = list() // see "appearance_flags" in the ref, assoc list of "[plane]" = object
///Assoc list of controller groups, associated with key string group name with value of the plane master controller ref
@@ -45,13 +63,31 @@
/mob/proc/create_mob_hud()
if(client && !hud_used)
- hud_used = new /datum/hud(src)
+ if(!ispath(hud_type))
+ CRASH("Hud type must be a type, was instead [hud_type]!")
+ set_hud_used(new hud_type(src))
update_sight()
+ SEND_SIGNAL(src, COMSIG_MOB_HUD_CREATED)
+
+/mob/proc/set_hud_used(datum/hud/new_hud)
+ hud_used = new_hud
+ new_hud.build_action_groups()
+
+/datum/hud/proc/get_all_action_buttons()
+ var/list/all_action_buttons = list()
+ all_action_buttons += palette_actions.actions
+ all_action_buttons += listed_actions.actions
+ return all_action_buttons
/datum/hud/New(mob/owner)
mymob = owner
- hide_actions_toggle = new
- hide_actions_toggle.InitialiseIcon(mymob)
+
+ toggle_palette = new()
+ toggle_palette.set_hud(src)
+ palette_down = new()
+ palette_down.set_hud(src)
+ palette_up = new()
+ palette_up.set_hud(src)
for(var/mytype in subtypesof(/atom/movable/screen/plane_master))
var/atom/movable/screen/plane_master/instance = new mytype()
@@ -69,10 +105,14 @@
if(mymob.hud_used == src)
mymob.hud_used = null
- QDEL_NULL(hide_actions_toggle)
+ QDEL_NULL(toggle_palette)
+ QDEL_NULL(palette_down)
+ QDEL_NULL(palette_up)
+ QDEL_NULL(palette_actions)
+ QDEL_NULL(listed_actions)
+ QDEL_LIST_CONTENTS(floating_actions)
QDEL_NULL(module_store_icon)
-
QDEL_LIST_CONTENTS(static_inventory)
inv_slots.Cut()
@@ -125,29 +165,29 @@
switch(display_hud_version)
if(HUD_STYLE_STANDARD) //Default HUD
hud_shown = TRUE //Governs behavior of other procs
- if(static_inventory.len)
+ if(length(static_inventory))
mymob.client.screen += static_inventory
- if(toggleable_inventory.len && inventory_shown)
+ if(length(toggleable_inventory) && inventory_shown)
mymob.client.screen += toggleable_inventory
- if(hotkeybuttons.len && !hotkey_ui_hidden)
+ if(length(hotkeybuttons) && !hotkey_ui_hidden)
mymob.client.screen += hotkeybuttons
- if(infodisplay.len)
+ if(length(infodisplay))
mymob.client.screen += infodisplay
- mymob.client.screen += hide_actions_toggle
+ mymob.client.screen += toggle_palette
if(action_intent)
action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position
if(HUD_STYLE_REDUCED) //Reduced HUD
hud_shown = FALSE //Governs behavior of other procs
- if(static_inventory.len)
+ if(length(static_inventory))
mymob.client.screen -= static_inventory
- if(toggleable_inventory.len)
+ if(length(toggleable_inventory))
mymob.client.screen -= toggleable_inventory
- if(hotkeybuttons.len)
+ if(length(hotkeybuttons))
mymob.client.screen -= hotkeybuttons
- if(infodisplay.len)
+ if(length(infodisplay))
mymob.client.screen += infodisplay
//These ones are a part of 'static_inventory', 'toggleable_inventory' or 'hotkeybuttons' but we want them to stay
@@ -161,13 +201,13 @@
if(HUD_STYLE_NOHUD) //No HUD
hud_shown = FALSE //Governs behavior of other procs
- if(static_inventory.len)
+ if(length(static_inventory))
mymob.client.screen -= static_inventory
- if(toggleable_inventory.len)
+ if(length(toggleable_inventory))
mymob.client.screen -= toggleable_inventory
- if(hotkeybuttons.len)
+ if(length(hotkeybuttons))
mymob.client.screen -= hotkeybuttons
- if(infodisplay.len)
+ if(length(infodisplay))
mymob.client.screen -= infodisplay
hud_version = display_hud_version
@@ -206,9 +246,9 @@
/mob/proc/hide_hud()
if(hud_used && client)
hud_used.show_hud() //Shows the next hud preset
- to_chat(src, "Switched HUD mode. Press the key you just pressed to toggle the HUD mode again.")
+ to_chat(src, "Switched HUD mode. Press the key you just pressed to toggle the HUD mode again.")
else
- to_chat(src, "This mob type does not use a HUD.")
+ to_chat(src, "This mob type does not use a HUD.")
/datum/hud/proc/update_locked_slots()
return
diff --git a/code/_onclick/hud/human_hud.dm b/code/_onclick/hud/human_hud.dm
index c69e78f6404..110b278f63a 100644
--- a/code/_onclick/hud/human_hud.dm
+++ b/code/_onclick/hud/human_hud.dm
@@ -51,7 +51,7 @@
/mob/living/carbon/human/create_mob_hud()
if(client && !hud_used)
- hud_used = new /datum/hud/human(src, ui_style2icon(client.prefs.UI_style), client.prefs.UI_style_color, client.prefs.UI_style_alpha)
+ set_hud_used(new /datum/hud/human(src, ui_style2icon(client.prefs.UI_style), client.prefs.UI_style_color, client.prefs.UI_style_alpha))
SEND_SIGNAL(src, COMSIG_HUMAN_CREATE_MOB_HUD)
/datum/hud/human
diff --git a/code/_onclick/hud/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm
index f43effee417..65fd2f13c20 100644
--- a/code/_onclick/hud/movable_screen_objects.dm
+++ b/code/_onclick/hud/movable_screen_objects.dm
@@ -10,10 +10,11 @@
/atom/movable/screen/movable
var/snap2grid = FALSE
- var/moved = FALSE
- var/locked = TRUE
var/x_off = -16
var/y_off = -16
+ // add a green overlay
+ mouse_drag_pointer = 'icons/mouse_icons/screen_drag.dmi'
+
//Snap Screen Object
//Tied to the grid, snaps to the nearest turf
@@ -22,33 +23,31 @@
snap2grid = TRUE
/atom/movable/screen/movable/MouseDrop(over_object, src_location, over_location, src_control, over_control, params)
- if(locked) //no! I am locked! begone!
+ var/position = mouse_params_to_position(params)
+ if(!position)
return
- var/list/PM = params2list(params)
+ screen_loc = position
+
+/// Takes mouse parmas as input, returns a string representing the appropriate mouse position
+/atom/movable/screen/movable/proc/mouse_params_to_position(params)
+
+ var/list/modifiers = params2list(params)
//No screen-loc information? abort.
- if(!PM || !PM["screen-loc"])
+ if(!LAZYACCESS(modifiers, SCREEN_LOC))
return
- //Split screen-loc up into X+Pixel_X and Y+Pixel_Y
- var/list/screen_loc_params = splittext(PM["screen-loc"], ",")
-
- //Split X+Pixel_X up into list(X, Pixel_X)
- var/list/screen_loc_X = splittext(screen_loc_params[1],":")
-
- //Split Y+Pixel_Y up into list(Y, Pixel_Y)
- var/list/screen_loc_Y = splittext(screen_loc_params[2],":")
+ var/client/our_client = usr.client
+ var/list/offset = screen_loc_to_offset(LAZYACCESS(modifiers, SCREEN_LOC))
if(snap2grid) //Discard Pixel Values
- screen_loc = "[screen_loc_X[1]],[screen_loc_Y[1]]"
-
+ offset[1] = FLOOR(offset[1], world.icon_size) // drops any pixel offset
+ offset[2] = FLOOR(offset[2], world.icon_size) // drops any pixel offset
else //Normalise Pixel Values (So the object drops at the center of the mouse, not 16 pixels off)
- var/pix_X = text2num(screen_loc_X[2]) + x_off
- var/pix_Y = text2num(screen_loc_Y[2]) + y_off
- screen_loc = "[screen_loc_X[1]]:[pix_X],[screen_loc_Y[1]]:[pix_Y]"
-
- moved = screen_loc
+ offset[1] += x_off
+ offset[2] += y_off
+ return offset_to_screen_loc(offset[1], offset[2], our_client?.view)
//Debug procs
/client/proc/test_movable_UI()
diff --git a/code/_onclick/hud/other_mobs_hud.dm b/code/_onclick/hud/other_mobs_hud.dm
index 27fe7bf5524..39fe3a31552 100644
--- a/code/_onclick/hud/other_mobs_hud.dm
+++ b/code/_onclick/hud/other_mobs_hud.dm
@@ -1,6 +1,3 @@
-/mob/living/simple_animal/create_mob_hud()
- hud_used = new /datum/hud/simple_animal(src)
-
/datum/hud/simple_animal/New(mob/user)
..()
@@ -15,11 +12,6 @@
user.overlay_fullscreen("see_through_darkness", /atom/movable/screen/fullscreen/see_through_darkness)
-
-/mob/living/simple_animal/pet/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/corgi(src)
-
/datum/hud/corgi/New(mob/user)
..()
diff --git a/code/_onclick/hud/robot_hud.dm b/code/_onclick/hud/robot_hud.dm
index c34807563c5..ae1e54f9fe2 100644
--- a/code/_onclick/hud/robot_hud.dm
+++ b/code/_onclick/hud/robot_hud.dm
@@ -85,11 +85,6 @@
/atom/movable/screen/robot/mov_intent/Click()
usr.toggle_move_intent()
-
-/mob/living/silicon/robot/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/robot(src)
-
/datum/hud/robot/New(mob/user)
..()
diff --git a/code/_onclick/hud/screentip.dm b/code/_onclick/hud/screentip.dm
index 542c96e626f..d70cbd72e32 100644
--- a/code/_onclick/hud/screentip.dm
+++ b/code/_onclick/hud/screentip.dm
@@ -13,6 +13,8 @@
update_view()
/atom/movable/screen/screentip/proc/update_view(datum/source)
- if(!hud) //Might not have been initialized by now
+ if(!hud?.mymob?.client) //Might not have been initialized by now
return
- maptext_width = getviewsize(hud.mymob.client.view)[1] * world.icon_size
+ var/list/view_size = getviewsize(hud.mymob.client.view)
+ var/view = "[view_size[1]]x[view_size[2]]"
+ maptext_width = view_to_pixels(view)[1]
diff --git a/code/_onclick/hud/slime_hud.dm b/code/_onclick/hud/slime_hud.dm
index 58a579e3599..d0337832a80 100644
--- a/code/_onclick/hud/slime_hud.dm
+++ b/code/_onclick/hud/slime_hud.dm
@@ -2,7 +2,3 @@
..()
mymob.healths = new /atom/movable/screen/healths/slime()
infodisplay += mymob.healths
-
-/mob/living/simple_animal/slime/create_mob_hud()
- if(client && !hud_used)
- hud_used = new /datum/hud/slime(src)
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 5ab9d6fa023..7e1c0752ec8 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -4,56 +4,73 @@
var/desc = null
var/obj/target = null
var/check_flags = 0
- var/atom/movable/screen/movable/action_button/button = null
var/button_icon = 'icons/mob/actions/actions.dmi'
- var/background_icon_state = "bg_default"
+ var/background_icon = 'icons/mob/actions/actions.dmi'
+ var/background_icon_state = ACTION_BUTTON_DEFAULT_BACKGROUND
var/buttontooltipstyle = ""
var/icon_icon = 'icons/mob/actions/actions.dmi'
var/button_icon_state = "default"
+ var/transparent_when_unavailable = TRUE
var/mob/owner
+ /// Where any buttons we create should be by default. Accepts screen_loc and location defines
+ var/default_button_position = SCRN_OBJ_IN_LIST
+ /// Map of huds viewing a button with our action -> their button
+ var/list/viewers = list()
+
/datum/action/New(Target)
target = Target
- button = new
- button.linked_action = src
- button.name = name
- button.actiontooltipstyle = buttontooltipstyle
- var/list/our_description = list()
- our_description += desc
- our_description += button.desc
- button.desc = our_description.Join(" ")
+
+/datum/action/proc/clear_ref(datum/ref)
+ SIGNAL_HANDLER
+ if(ref == owner)
+ Remove(owner)
+ if(ref == target)
+ qdel(src)
/datum/action/Destroy()
if(owner)
Remove(owner)
if(target)
target = null
- QDEL_NULL(button)
+ QDEL_LIST_ASSOC_VAL(viewers) // Qdel the buttons in the viewers list **NOT THE HUDS**
return ..()
/datum/action/proc/Grant(mob/M)
+ if(!M)
+ Remove(owner)
+ return
if(owner)
if(owner == M)
return
Remove(owner)
owner = M
- M.actions += src
- if(M.client)
- M.client.screen += button
- button.locked = TRUE
- M.update_action_buttons()
+ RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
+ GiveAction(M)
-/datum/action/proc/Remove(mob/M)
- owner = null
- if(!M)
+/datum/action/proc/Remove(mob/remove_from)
+ 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()
+ // owner = null
+
+ if(isnull(owner))
return
- if(M.client)
- M.client.screen -= button
- button.clean_up_keybinds(M)
- button.moved = FALSE //so the button appears in its normal position when given to another owner.
- button.locked = FALSE
- M.actions -= src
- M.update_action_buttons()
+
+ if(target == owner)
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(clear_ref), override = TRUE)
+ if(owner == remove_from)
+ 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)
/datum/action/proc/Trigger(left_click = TRUE)
if(!IsAvailable())
@@ -95,31 +112,105 @@
return FALSE
return TRUE
-/datum/action/proc/UpdateButtonIcon()
+/datum/action/proc/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
+ if(!button)
+ return
+ if(!status_only)
+ button.name = name
+ button.desc = desc
+ if(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
+ if(button.icon != button_icon)
+ button.icon = button_icon
+ if(button.icon_state != background_icon_state)
+ button.icon_state = background_icon_state
+
+ ApplyIcon(button, force)
+
+ if(!IsAvailable())
+ apply_unavailable_effect(button)
+ else
+ return TRUE
+
+//Give our action button to the player
+/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 a player
+/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/atom/movable/screen/movable/action_button/button = CreateButton()
+ 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 button from the screen of a player
+/datum/action/proc/HideFrom(mob/viewer)
+ var/datum/hud/our_hud = viewer.hud_used
+ var/atom/movable/screen/movable/action_button/button = viewers[our_hud]
+ LAZYREMOVE(viewer.actions, src)
if(button)
- if(owner && owner.client && background_icon_state == "bg_default") // If it's a default action background, apply the custom HUD style
- button.alpha = owner.client.prefs.UI_style_alpha
- button.color = owner.client.prefs.UI_style_color
- button.icon = ui_style2icon(owner.client.prefs.UI_style)
- button.icon_state = "template"
- else
- button.icon = button_icon
- button.icon_state = background_icon_state
+ button.clean_up_keybinds(viewer)
+ qdel(button)
- ApplyIcon(button)
- var/obj/effect/proc_holder/spell/S = target
- if(istype(S) && S.cooldown_handler.should_draw_cooldown() || !IsAvailable())
- apply_unavailable_effect()
- else
- return TRUE
-/datum/action/proc/apply_unavailable_effect()
+/datum/action/proc/CreateButton()
+ var/atom/movable/screen/movable/action_button/button = new()
+ button.linked_action = src
+ button.name = name
+ button.actiontooltipstyle = buttontooltipstyle
+ var/list/our_description = list()
+ our_description += desc
+ our_description += button.desc
+ 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
+ for(var/datum/action/action in owner.actions)
+ if(action == src) // This could be us, which is dumb
+ continue
+ var/atom/movable/screen/movable/action_button/button = action.viewers[owner.hud_used]
+ if(action.name == name && button.id)
+ bitfield |= button.id
+
+ 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
+
+/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
- button.add_overlay(img)
+ B.add_overlay(img)
+
/datum/action/proc/ApplyIcon(atom/movable/screen/movable/action_button/current_button)
current_button.cut_overlays()
@@ -143,10 +234,12 @@
use_itemicon = FALSE
icon_icon = custom_icon
button_icon_state = custom_icon_state
+ UpdateButtons()
/datum/action/item_action/Destroy()
var/obj/item/I = target
- I.actions -= src
+ if(islist(I?.actions))
+ I.actions -= src
return ..()
/datum/action/item_action/Trigger(left_click = TRUE, attack_self = TRUE) //Maybe we don't want to click the thing itself
@@ -217,13 +310,14 @@
/datum/action/item_action/set_internals
name = "Set Internals"
-/datum/action/item_action/set_internals/UpdateButtonIcon()
- if(..()) //button available
- if(iscarbon(owner))
- var/mob/living/carbon/C = owner
- if(target == C.internal)
- button.icon = 'icons/mob/actions/actions.dmi'
- button.icon_state = "bg_default_on"
+/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/toggle_mister
name = "Toggle Mister"
@@ -255,19 +349,17 @@
/datum/action/item_action/toggle_unfriendly_fire/Trigger(left_click)
if(..())
- UpdateButtonIcon()
+ UpdateButtons()
-/datum/action/item_action/toggle_unfriendly_fire/UpdateButtonIcon()
+/datum/action/item_action/toggle_unfriendly_fire/UpdateButtons()
if(istype(target, /obj/item/hierophant_club))
var/obj/item/hierophant_club/H = target
if(H.friendly_fire_check)
button_icon_state = "vortex_ff_off"
name = "Toggle Friendly Fire \[OFF\]"
- button.name = name
else
button_icon_state = "vortex_ff_on"
name = "Toggle Friendly Fire \[ON\]"
- button.name = name
..()
/datum/action/item_action/vortex_recall
@@ -290,28 +382,24 @@
/datum/action/item_action/toggle/New(Target)
..()
name = "Toggle [target.name]"
- button.name = name
/datum/action/item_action/openclose
/datum/action/item_action/openclose/New(Target)
..()
name = "Open/Close [target.name]"
- button.name = name
/datum/action/item_action/button
/datum/action/item_action/button/New(Target)
..()
name = "Button/Unbutton [target.name]"
- button.name = name
/datum/action/item_action/zipper
/datum/action/item_action/zipper/New(Target)
..()
name = "Zip/Unzip [target.name]"
- button.name = name
/datum/action/item_action/halt
name = "HALT!"
@@ -352,7 +440,6 @@
/datum/action/item_action/adjust/New(Target)
..()
name = "Adjust [target.name]"
- button.name = name
/datum/action/item_action/pontificate
name = "Pontificate Evilly"
@@ -495,17 +582,14 @@
/datum/action/item_action/organ_action/toggle/New(Target)
..()
name = "Toggle [target.name]"
- button.name = name
/datum/action/item_action/organ_action/use/New(Target)
..()
name = "Use [target.name]"
- button.name = name
/datum/action/item_action/organ_action/use/eyesofgod/New(target)
..()
name = "See with the Eyes of the Gods"
- button.name = name
/datum/action/item_action/voice_changer/toggle
name = "Toggle Voice Changer"
@@ -556,14 +640,12 @@
var/obj/effect/proc_holder/spell/S = target
S.action = src
name = S.name
- var/list/our_description = list()
- our_description += S.desc
- our_description += button.desc
- button.desc = our_description.Join(" ")
+ desc = S.desc
button_icon = S.action_icon
button_icon_state = S.action_icon_state
background_icon_state = S.action_background_icon_state
- button.name = name
+ UpdateButtons()
+
/datum/action/spell_action/Destroy()
var/obj/effect/proc_holder/spell/S = target
@@ -593,7 +675,7 @@
return spell.can_cast(owner)
return FALSE
-/datum/action/spell_action/apply_unavailable_effect()
+/datum/action/spell_action/apply_unavailable_effect(atom/movable/screen/movable/action_button/button)
var/obj/effect/proc_holder/spell/S = target
if(!istype(S))
return ..()
@@ -613,19 +695,6 @@
count_down_holder.maptext = "[text]
"
button.add_overlay(count_down_holder)
-/*
-/datum/action/spell_action/alien
-
-/datum/action/spell_action/alien/IsAvailable()
- if(!target)
- return 0
- var/obj/effect/proc_holder/alien/ab = target
-
- if(owner)
- return ab.cost_check(ab.check_turf, owner, 1)
- return 0
-*/
-
//Preset for general and toggled actions
/datum/action/innate
check_flags = 0
diff --git a/code/datums/spell.dm b/code/datums/spell.dm
index 3cd988a0624..e20a939e364 100644
--- a/code/datums/spell.dm
+++ b/code/datums/spell.dm
@@ -193,7 +193,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
custom_handler?.spend_spell_cost(user, src)
if(action)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/obj/effect/proc_holder/spell/proc/invocation(mob/user) //spelling the spell out and setting it on recharge/reducing charges amount
switch(invocation_type)
@@ -362,7 +362,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
cast(targets, user = user)
after_cast(targets, user)
if(action)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/**
* Will write additional logs if create_custom_logs is TRUE and the caster has a ckey. Override this
@@ -435,11 +435,11 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
custom_handler?.revert_cast(user, src)
if(action)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
-/obj/effect/proc_holder/spell/proc/updateButtonIcon()
+/obj/effect/proc_holder/spell/proc/UpdateButtons()
if(action)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/obj/effect/proc_holder/spell/proc/adjust_var(mob/living/target = usr, type, amount) //handles the adjustment of the var when the spell is used. has some hardcoded types
switch(type)
diff --git a/code/datums/spell_cooldown/spell_charges.dm b/code/datums/spell_cooldown/spell_charges.dm
index 165caeba027..bdbe9230405 100644
--- a/code/datums/spell_cooldown/spell_charges.dm
+++ b/code/datums/spell_cooldown/spell_charges.dm
@@ -28,7 +28,7 @@
if(recharge_time > world.time)
return FALSE
current_charges++
- spell_parent.action.UpdateButtonIcon()
+ spell_parent.action.UpdateButtons()
if(current_charges < max_charges) // we have more recharges to go
recharge_time = world.time + recharge_duration
return FALSE
diff --git a/code/datums/spell_cooldown/spell_cooldown.dm b/code/datums/spell_cooldown/spell_cooldown.dm
index 3d320bdca8a..68f935e5f00 100644
--- a/code/datums/spell_cooldown/spell_cooldown.dm
+++ b/code/datums/spell_cooldown/spell_cooldown.dm
@@ -33,7 +33,7 @@
if(!spell_parent.action)
stack_trace("[spell_parent.type] ended up with a null action")
return PROCESS_KILL
- spell_parent.action.UpdateButtonIcon()
+ spell_parent.action.UpdateButtons()
if(should_end_cooldown())
return PROCESS_KILL
@@ -58,7 +58,7 @@
else
recharge_time = get_recharge_time()
if(spell_parent.action)
- spell_parent.action.UpdateButtonIcon()
+ spell_parent.action.UpdateButtons()
START_PROCESSING(SSfastprocess, src)
/datum/spell_cooldown/proc/revert_cast()
diff --git a/code/datums/spells/alien_spells/basetype_alien_spell.dm b/code/datums/spells/alien_spells/basetype_alien_spell.dm
index c4413e45d90..f192962592e 100644
--- a/code/datums/spells/alien_spells/basetype_alien_spell.dm
+++ b/code/datums/spells/alien_spells/basetype_alien_spell.dm
@@ -15,7 +15,7 @@ Updates the spell's actions on use as well, so they know when they can or can't
vessel.stored_plasma = clamp(vessel.stored_plasma + amount, 0, vessel.max_plasma)
update_plasma_display(src)
for(var/datum/action/spell_action/action in actions)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/obj/effect/proc_holder/spell/alien_spell
action_background_icon_state = "bg_alien"
diff --git a/code/datums/spells/alien_spells/neurotoxin_spit.dm b/code/datums/spells/alien_spells/neurotoxin_spit.dm
index ad4048f53a1..d3cabc594c0 100644
--- a/code/datums/spells/alien_spells/neurotoxin_spit.dm
+++ b/code/datums/spells/alien_spells/neurotoxin_spit.dm
@@ -16,7 +16,7 @@
if(!action)
return
action.button_icon_state = "alien_neurotoxin_[active]"
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/obj/effect/proc_holder/spell/alien_spell/neurotoxin/cast(list/targets, mob/living/carbon/user)
var/target = targets[1]
diff --git a/code/datums/spells/lichdom.dm b/code/datums/spells/lichdom.dm
index b882d6dec2c..ccdad7e9863 100644
--- a/code/datums/spells/lichdom.dm
+++ b/code/datums/spells/lichdom.dm
@@ -96,7 +96,7 @@
to_chat(lich, "Your bones clatter and shudder as they're pulled back into this world!")
/obj/effect/proc_holder/spell/lichdom/proc/attempt_mark_item(mob/user)
- var/obj/item/target = user.get_active_hand()
+ var/obj/item/target = user.get_active_hand()
if(!target)
to_chat(user, "You must hold an item you wish to make your phylactery!")
return
@@ -114,10 +114,10 @@
stat_allowed = UNCONSCIOUS
cooldown_handler.recharge_duration = 3 MINUTES
cooldown_handler.revert_cast()
-
- if(action && action.button)
- action.name = action.button.name = name
- action.desc = action.button.desc = desc
+ if(action)
+ action.name = name
+ action.desc = desc
+ UpdateButtons()
target.name = "ensouled [target.name]"
target.desc += "
A terrible aura surrounds this item, its very existence is offensive to life itself..."
@@ -137,7 +137,7 @@
RegisterSignal(target, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_Z_CHANGED), PROC_REF(check_revivability_handler))
RegisterSignal(current_body, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_Z_CHANGED), PROC_REF(check_revivability_handler))
to_chat(user, "With a hideous feeling of emptiness you watch in horrified fascination as skin sloughs off bone! Blood boils, nerves disintegrate, eyes boil in their sockets! As your organs crumble to dust in your fleshless chest you come to terms with your choice. You're a lich!")
-
+
existence_stops_round_end = TRUE
GLOB.configuration.gamemode.disable_certain_round_early_end = TRUE
diff --git a/code/datums/spells/wizard_spells.dm b/code/datums/spells/wizard_spells.dm
index e68728e9c4f..c39d4742f34 100644
--- a/code/datums/spells/wizard_spells.dm
+++ b/code/datums/spells/wizard_spells.dm
@@ -367,7 +367,7 @@
if(!action)
return
action.button_icon_state = "fireball[active]"
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/obj/effect/proc_holder/spell/fireball/cast(list/targets, mob/living/user = usr)
var/target = targets[1] //There is only ever one target for fireball
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 57211a2fd69..cf70c3d27bb 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -632,7 +632,7 @@
. = ..()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/atom/proc/get_filter(name)
if(filter_data && filter_data[name])
diff --git a/code/game/gamemodes/cult/blood_magic.dm b/code/game/gamemodes/cult/blood_magic.dm
index 3b90a7c9add..47ca9460e58 100644
--- a/code/game/gamemodes/cult/blood_magic.dm
+++ b/code/game/gamemodes/cult/blood_magic.dm
@@ -5,27 +5,29 @@
desc = "Prepare blood magic by carving runes into your flesh. This is easier with an empowering rune."
var/list/spells = list()
var/channeling = FALSE
+ default_button_position = DEFAULT_BLOODSPELLS
/datum/action/innate/cult/blood_magic/Remove()
for(var/X in spells)
qdel(X)
..()
-/datum/action/innate/cult/blood_magic/override_location()
- button.ordered = FALSE
- button.screen_loc = DEFAULT_BLOODSPELLS
- button.moved = DEFAULT_BLOODSPELLS
-
/datum/action/innate/cult/blood_magic/proc/Positioning()
- var/list/screen_loc_split = splittext(button.screen_loc, ",")
- var/list/screen_loc_X = splittext(screen_loc_split[1], ":")
- var/list/screen_loc_Y = splittext(screen_loc_split[2], ":")
- var/pix_X = text2num(screen_loc_X[2])
- for(var/datum/action/innate/cult/blood_spell/B in spells)
- if(B.button.locked)
- var/order = pix_X + spells.Find(B) * 31
- B.button.screen_loc = "[screen_loc_X[1]]:[order],[screen_loc_Y[1]]:[screen_loc_Y[2]]"
- B.button.moved = B.button.screen_loc
+ for(var/datum/hud/hud as anything in viewers)
+ var/our_view = hud.mymob?.client?.view || "15x15"
+ var/atom/movable/screen/movable/action_button/button = viewers[hud]
+ var/position = screen_loc_to_offset(button.screen_loc)
+ var/spells_iterated = 0
+ for(var/datum/action/innate/cult/blood_spell/blood_spell in spells)
+ spells_iterated += 1
+ if(blood_spell.positioned)
+ continue
+ var/atom/movable/screen/movable/action_button/moving_button = blood_spell.viewers[hud]
+ if(!moving_button)
+ continue
+ var/our_x = position[1] + spells_iterated * world.icon_size // Offset any new buttons into our list
+ hud.position_action(moving_button, offset_to_screen_loc(our_x, position[2], our_view))
+ blood_spell.positioned = TRUE
/datum/action/innate/cult/blood_magic/Activate()
var/rune = FALSE
@@ -98,6 +100,9 @@
var/base_desc //To allow for updating tooltips
var/invocation = "Hoi there something's wrong!"
var/health_cost = 0
+ /// Have we already been positioned into our starting location?
+ var/positioned = FALSE
+
/datum/action/innate/cult/blood_spell/proc/get_panel_text()
if(initial(charges) == 1)
@@ -105,7 +110,7 @@
var/available_charges = hand_magic ? "[hand_magic.uses]" : "[charges]"
return "[available_charges]/[initial(charges)]"
-/datum/action/innate/cult/blood_spell/UpdateButtonIcon()
+/datum/action/innate/cult/blood_spell/UpdateButton(atom/movable/screen/movable/action_button/button, status_only, force)
. = ..()
var/text = get_panel_text()
if(!text || !button)
@@ -120,11 +125,12 @@
base_desc = desc
desc += "
Has [charges] use\s remaining."
all_magic = BM
- button.ordered = FALSE
+ // todo blood magic guh
+ // button.ordered = FALSE
..()
/datum/action/innate/cult/blood_spell/override_location()
- button.locked = TRUE
+ // button.locked = TRUE
all_magic.Positioning()
/datum/action/innate/cult/blood_spell/Remove()
@@ -311,10 +317,10 @@
var/mob/living/carbon/human/H = target
H.Hallucinate(120 SECONDS)
attached_action.charges--
- attached_action.UpdateButtonIcon()
+ attached_action.UpdateButtons()
attached_action.desc = attached_action.base_desc
attached_action.desc += "
Has [attached_action.charges] use\s remaining."
- attached_action.UpdateButtonIcon()
+ attached_action.UpdateButtons()
user.ranged_ability.remove_ranged_ability(user, "[H] has been cursed with living nightmares!")
if(attached_action.charges <= 0)
to_chat(ranged_ability_user, "You have exhausted the spell's power!")
@@ -364,7 +370,7 @@
qdel(src)
desc = "[revealing ? "Reveals" : "Conceals"] nearby cult structures, airlocks, and runes."
desc += "
Has [charges] use\s remaining."
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/cult/blood_spell/manipulation
name = "Blood Rites"
@@ -418,7 +424,7 @@
source.charges = uses
source.desc = source.base_desc
source.desc += "
Has [uses] use\s remaining."
- source.UpdateButtonIcon()
+ source.UpdateButtons()
return ..()
/obj/item/melee/blood_magic/customised_abstract_text(mob/living/carbon/owner)
@@ -446,7 +452,7 @@
else if(source)
source.desc = source.base_desc
source.desc += "
Has [uses] use\s remaining."
- source.UpdateButtonIcon()
+ source.UpdateButtons()
//The spell effects
@@ -585,7 +591,7 @@
else
user.visible_message("This victim doesn't have enough arms to complete the restraint!")
return
- source.UpdateButtonIcon()
+ source.UpdateButtons()
..()
/obj/item/melee/blood_magic/shackles/proc/CuffAttack(mob/living/carbon/C, mob/living/user)
@@ -879,12 +885,12 @@
target.clean_blood()
else
steal_blood(user, target)
- source.UpdateButtonIcon()
+ source.UpdateButtons()
return
if(isconstruct(target))
heal_construct(user, target)
- source.UpdateButtonIcon()
+ source.UpdateButtons()
return
if(istype(target, /obj/item/blood_orb))
@@ -894,10 +900,10 @@
to_chat(user, "You obtain [candidate.blood] blood from the orb of blood!")
playsound(user, 'sound/misc/enter_blood.ogg', 50, extrarange = SOUND_RANGE_SET(7))
qdel(candidate)
- source.UpdateButtonIcon()
+ source.UpdateButtons()
return
blood_draw(target, user)
- source.UpdateButtonIcon()
+ source.UpdateButtons()
/obj/item/melee/blood_magic/manipulator/proc/blood_draw(atom/target, mob/living/carbon/human/user)
var/temp = 0
@@ -1001,4 +1007,4 @@
to_chat(user, "You need a free hand for this rite!")
uses += BLOOD_BARRAGE_COST // Refund the charges
qdel(rite)
- source.UpdateButtonIcon()
+ source.UpdateButtons()
diff --git a/code/game/gamemodes/cult/cult_actions.dm b/code/game/gamemodes/cult/cult_actions.dm
index 1bafae2b646..e26efbad2d4 100644
--- a/code/game/gamemodes/cult/cult_actions.dm
+++ b/code/game/gamemodes/cult/cult_actions.dm
@@ -112,16 +112,12 @@
name = "Draw Blood Rune"
desc = "Use the ritual dagger to create a powerful blood rune"
button_icon_state = "blood_dagger"
+ default_button_position = "6:157,4:-2"
/datum/action/innate/cult/use_dagger/Grant()
button_icon_state = GET_CULT_DATA(dagger_icon, "blood_dagger")
..()
-/datum/action/innate/cult/use_dagger/override_location()
- button.ordered = FALSE
- button.screen_loc = "6:157,4:-2"
- button.moved = "6:157,4:-2"
-
/datum/action/innate/cult/use_dagger/Activate()
var/obj/item/melee/cultblade/dagger/D = owner.find_item(/obj/item/melee/cultblade/dagger)
if(D)
diff --git a/code/game/gamemodes/cult/cult_items.dm b/code/game/gamemodes/cult/cult_items.dm
index 0126e8f88a1..dbc810b059a 100644
--- a/code/game/gamemodes/cult/cult_items.dm
+++ b/code/game/gamemodes/cult/cult_items.dm
@@ -633,6 +633,7 @@
button_icon_state = "bloodspear"
var/obj/item/cult_spear/spear
var/cooldown = 0
+ default_button_position = "6:157,4:-2"
/datum/action/innate/cult/spear/Grant(mob/user, obj/blood_spear)
. = ..()
diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm
index 85dbc3cda17..114bc2a24e5 100644
--- a/code/game/gamemodes/malfunction/Malf_Modules.dm
+++ b/code/game/gamemodes/malfunction/Malf_Modules.dm
@@ -42,9 +42,10 @@
if(initial(uses) > 1) //no need to tell 'em if it was one-use anyway!
to_chat(owner, "[name] has run out of uses!")
qdel(src)
- else
- desc = "[initial(desc)] It has [uses] use\s remaining."
- UpdateButtonIcon()
+ if(QDELETED(src) || uses) //Not sure if not having src here would cause a runtime, so it's here to be safe
+ return
+ desc = "[initial(desc)] It has [uses] use\s remaining."
+ UpdateButtons()
//Framework for ranged abilities that can have different effects by left-clicking stuff.
/datum/action/innate/ai/ranged
@@ -190,7 +191,7 @@
else //Adding uses to an existing module
action.uses += initial(action.uses)
action.desc = "[initial(action.desc)] It has [action.uses] use\s remaining."
- action.UpdateButtonIcon()
+ action.UpdateButtons()
temp = "Additional use[action.uses > 1 ? "s" : ""] added to [action.name]!"
processing_time -= AM.cost
diff --git a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm b/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
index cb0ea1b2811..6c98ab8b3f1 100644
--- a/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
+++ b/code/game/gamemodes/miniantags/abduction/abduction_gear.dm
@@ -49,7 +49,7 @@
H.update_inv_wear_suit()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/suit/armor/abductor/vest/item_action_slot_check(slot, mob/user)
if(slot == SLOT_HUD_OUTER_SUIT) //we only give the mob the ability to activate the vest if he's actually wearing it.
@@ -426,7 +426,7 @@ Congratulations! You are now trained for invasive xenobiology research!"}
update_icon(UPDATE_ICON_STATE)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/abductor_baton/update_icon_state()
switch(mode)
diff --git a/code/game/gamemodes/miniantags/guardian/guardian.dm b/code/game/gamemodes/miniantags/guardian/guardian.dm
index e9a5c252d88..e56038057e2 100644
--- a/code/game/gamemodes/miniantags/guardian/guardian.dm
+++ b/code/game/gamemodes/miniantags/guardian/guardian.dm
@@ -30,6 +30,7 @@
melee_damage_upper = 15
AIStatus = AI_OFF
butcher_results = list(/obj/item/food/snacks/ectoplasm = 1)
+ hud_type = /datum/hud/guardian
var/summoned = FALSE
var/cooldown = 0
var/damage_transfer = 1 //how much damage from each attack we transfer to the owner
diff --git a/code/game/gamemodes/miniantags/guardian/host_actions.dm b/code/game/gamemodes/miniantags/guardian/host_actions.dm
index 8a622789e34..75157f84991 100644
--- a/code/game/gamemodes/miniantags/guardian/host_actions.dm
+++ b/code/game/gamemodes/miniantags/guardian/host_actions.dm
@@ -82,7 +82,7 @@
// Do this immediately, so the user can't spam a bunch of polls.
cooldown_timer = addtimer(CALLBACK(src, PROC_REF(reset_cooldown)), 5 MINUTES)
- UpdateButtonIcon()
+ UpdateButtons()
to_chat(owner, "Searching for a replacement ghost...")
var/list/mob/dead/observer/candidates = SSghost_spawns.poll_candidates("Do you want to play as [guardian.real_name]?", ROLE_GUARDIAN, FALSE, 15 SECONDS, source = guardian)
@@ -181,7 +181,7 @@
*/
/datum/action/guardian/reset_guardian/proc/reset_cooldown()
cooldown_timer = null
- UpdateButtonIcon()
+ UpdateButtons()
/**
* Grants all existing `/datum/action/guardian` type actions to the src mob.
diff --git a/code/game/gamemodes/miniantags/morph/morph.dm b/code/game/gamemodes/miniantags/morph/morph.dm
index 7f1b0587de8..46a2f0af1d2 100644
--- a/code/game/gamemodes/miniantags/morph/morph.dm
+++ b/code/game/gamemodes/miniantags/morph/morph.dm
@@ -145,7 +145,7 @@
/mob/living/simple_animal/hostile/morph/proc/add_food(amount)
gathered_food += amount
for(var/datum/action/spell_action/action in actions)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/mob/living/simple_animal/hostile/morph/proc/assume()
@@ -155,8 +155,8 @@
melee_damage_lower = 5
melee_damage_upper = 5
speed = MORPHED_SPEED
- ambush_spell.updateButtonIcon()
- pass_airlock_spell.updateButtonIcon()
+ ambush_spell.UpdateButtons()
+ pass_airlock_spell.UpdateButtons()
move_resist = MOVE_FORCE_DEFAULT // They become more fragile and easier to move
/mob/living/simple_animal/hostile/morph/proc/restore()
@@ -171,7 +171,7 @@
if(ambush_prepared)
to_chat(src, "The ambush potential has faded as you take your true form.")
failed_ambush()
- pass_airlock_spell.updateButtonIcon()
+ pass_airlock_spell.UpdateButtons()
move_resist = MOVE_FORCE_STRONG // Return to their fatness
@@ -183,7 +183,7 @@
/mob/living/simple_animal/hostile/morph/proc/failed_ambush()
ambush_prepared = FALSE
- ambush_spell.updateButtonIcon()
+ ambush_spell.UpdateButtons()
mimic_spell.perfect_disguise = FALSE // Reset the perfect disguise
remove_status_effect(/datum/status_effect/morph_ambush)
UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
index fa0c590e6b0..664ffe97b59 100644
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
+++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon.dm
@@ -322,7 +322,7 @@
if(!S.action || S.locked)
continue
if(S.requires_area)
- S.action.UpdateButtonIcon()
+ S.action.UpdateButtons()
// can enter an apc at all?
/mob/living/simple_animal/demon/pulse_demon/proc/is_valid_apc(obj/machinery/power/apc/A)
@@ -435,7 +435,7 @@
var/dist = S.cast_cost - orig
// only update icon if the amount is actually enough to change a spell's availability
if(dist == 0 || (dist > 0 && realdelta >= dist) || (dist < 0 && realdelta <= dist))
- S.action.UpdateButtonIcon()
+ S.action.UpdateButtons()
return realdelta
// logarithmic scale for glow strength, see table:
diff --git a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
index 86c22acaeb6..6901e46e7d7 100644
--- a/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
+++ b/code/game/gamemodes/miniantags/pulsedemon/pulsedemon_abilities.dm
@@ -32,9 +32,9 @@
else
name = "[initial(name)][cast_cost == 0 ? "" : " ([format_si_suffix(cast_cost)]W)"]"
desc = "[initial(desc)][spell_level == level_max ? "" : " It costs [format_si_suffix(upgrade_cost)]W to upgrade."]"
- action.button.name = name
+ action.name = name
action.desc = desc
- action.UpdateButtonIcon()
+ action.UpdateButtons()
/obj/effect/proc_holder/spell/pulse_demon/can_cast(mob/living/simple_animal/demon/pulse_demon/user, charge_check, show_message)
if(!..())
@@ -257,7 +257,7 @@
/obj/effect/proc_holder/spell/pulse_demon/toggle/proc/do_toggle(varstate, mob/user)
if(action)
action.background_icon_state = varstate ? action_background_icon_state : "[action_background_icon_state]_disabled"
- action.UpdateButtonIcon()
+ action.UpdateButtons()
if(user)
to_chat(user, "You will [varstate ? "now" : "no longer"] [base_message]")
return varstate
diff --git a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
index 2ea56a6ccdf..bc3b622ad25 100644
--- a/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
+++ b/code/game/gamemodes/miniantags/revenant/revenant_abilities.dm
@@ -200,7 +200,7 @@
user.reveal(reveal)
user.stun(stun)
if(action)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
return TRUE
//Overload Light: Breaks a light that's online and sends out lightning bolts to all nearby people.
diff --git a/code/game/jobs/job/support.dm b/code/game/jobs/job/support.dm
index 306a441e26a..5d636d988f3 100644
--- a/code/game/jobs/job/support.dm
+++ b/code/game/jobs/job/support.dm
@@ -358,7 +358,7 @@
singlemutcheck(H, GLOB.clumsyblock, MUTCHK_FORCED)
active = TRUE
background_icon_state = "bg_spell"
- UpdateButtonIcon()
+ UpdateButtons()
to_chat(H, "You start acting clumsy to throw suspicions off. Focus again before using weapons.")
/datum/action/innate/toggle_clumsy/Deactivate()
@@ -367,7 +367,7 @@
singlemutcheck(H, GLOB.clumsyblock, MUTCHK_FORCED)
active = FALSE
background_icon_state = "bg_default"
- UpdateButtonIcon()
+ UpdateButtons()
to_chat(H, "You focus and can now use weapons regularly.")
/datum/job/mime
diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm
index d638bfe4123..dcafd77e1dc 100644
--- a/code/game/mecha/mecha_actions.dm
+++ b/code/game/mecha/mecha_actions.dm
@@ -66,7 +66,7 @@
button_icon_state = "mech_internals_[chassis.use_internal_tank ? "on" : "off"]"
chassis.occupant_message("Now taking air from [chassis.use_internal_tank ? "internal airtank" : "environment"].")
chassis.log_message("Now taking air from [chassis.use_internal_tank ? "internal airtank" : "environment"].")
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/mecha/mech_toggle_lights
name = "Toggle Lights"
@@ -84,7 +84,7 @@
button_icon_state = "mech_lights_off"
chassis.occupant_message("Toggled lights [chassis.lights ? "on" : "off"].")
chassis.log_message("Toggled lights [chassis.lights ? "on" : "off"].")
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/mecha/mech_view_stats
name = "View Stats"
@@ -114,7 +114,7 @@
chassis.deflect_chance = initial(chassis.deflect_chance)
chassis.occupant_message("You disable [chassis] defence mode.")
chassis.log_message("Toggled defence mode.")
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/mecha/mech_overload_mode
name = "Toggle leg actuators overload"
@@ -144,7 +144,7 @@
chassis.step_in = initial(chassis.step_in)
chassis.step_energy_drain = chassis.normal_step_energy_drain
chassis.occupant_message("You disable leg actuators overload.")
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/mecha/mech_toggle_thrusters
name = "Toggle Thrusters"
@@ -194,7 +194,7 @@
SEND_SOUND(owner, sound(chassis.zoomsound, volume = 50))
else
owner.client.RemoveViewMod("mecha")
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/mecha/mech_toggle_phasing
name = "Toggle Phasing"
@@ -206,7 +206,7 @@
chassis.phasing = !chassis.phasing
button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]"
chassis.occupant_message("En":"#f00\">Dis"]abled phasing.")
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/mecha/mech_switch_damtype
@@ -230,4 +230,4 @@
chassis.damtype = new_damtype
button_icon_state = "mech_damtype_[new_damtype]"
playsound(src, 'sound/mecha/mechmove01.ogg', 50, 1)
- UpdateButtonIcon()
+ UpdateButtons()
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 222e4ebaa40..fffdab16013 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -179,7 +179,13 @@ GLOBAL_DATUM_INIT(welding_sparks, /mutable_appearance, mutable_appearance('icons
if(ismob(loc))
var/mob/m = loc
m.unEquip(src, 1)
- QDEL_LIST_CONTENTS(actions)
+ // actions clear themselves from the list on cut
+ if(islist(actions))
+ for(var/datum/action/A as anything in actions)
+ qdel(A)
+ if(!isnull(actions))
+ actions.Cut()
+
master = null
return ..()
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 909451a633d..5118c56e628 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -39,7 +39,7 @@
update_brightness()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
return TRUE
/obj/item/flashlight/attack(mob/living/M as mob, mob/living/user as mob)
diff --git a/code/game/objects/items/devices/traitordevices.dm b/code/game/objects/items/devices/traitordevices.dm
index 0282857720a..a8817a1b722 100644
--- a/code/game/objects/items/devices/traitordevices.dm
+++ b/code/game/objects/items/devices/traitordevices.dm
@@ -28,7 +28,7 @@
else
GLOB.active_jammers -= src
for(var/datum/action/item_action/toggle_radio_jammer/A in actions)
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/teleporter
name = "syndicate teleporter"
diff --git a/code/game/objects/items/devices/voice_changer.dm b/code/game/objects/items/devices/voice_changer.dm
index 779409f16c6..1fd3e0bdeb0 100644
--- a/code/game/objects/items/devices/voice_changer.dm
+++ b/code/game/objects/items/devices/voice_changer.dm
@@ -31,7 +31,7 @@
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/voice_changer/proc/set_voice(mob/user)
var/chosen_voice = clean_input("What voice would you like to mimic? Leave this empty to use the voice on your ID card.", "Set Voice Changer", voice, user)
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 80d1c2da512..e5986a9833c 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -202,7 +202,7 @@
icon_state = "selfrepair_[on ? "on" : "off"]"
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
else
icon_state = "cyborg_upgrade5"
diff --git a/code/game/objects/items/weapons/bio_chips/bio_chip_stealth.dm b/code/game/objects/items/weapons/bio_chips/bio_chip_stealth.dm
index 0dfdf4e864c..8003d7dfe0e 100644
--- a/code/game/objects/items/weapons/bio_chips/bio_chip_stealth.dm
+++ b/code/game/objects/items/weapons/bio_chips/bio_chip_stealth.dm
@@ -63,11 +63,11 @@
SIGNAL_HANDLER
on_cooldown = TRUE
addtimer(CALLBACK(src, PROC_REF(end_cooldown)), 10 SECONDS)
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/item_action/agent_box/proc/end_cooldown()
on_cooldown = FALSE
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/item_action/agent_box/IsAvailable()
if(..() && !on_cooldown)
diff --git a/code/game/objects/items/weapons/defib.dm b/code/game/objects/items/weapons/defib.dm
index 693084f5069..55b7a9b25a0 100644
--- a/code/game/objects/items/weapons/defib.dm
+++ b/code/game/objects/items/weapons/defib.dm
@@ -159,7 +159,7 @@
update_icon(UPDATE_OVERLAYS)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/defibrillator/equipped(mob/user, slot)
..()
diff --git a/code/game/objects/items/weapons/holy_weapons.dm b/code/game/objects/items/weapons/holy_weapons.dm
index 28dbe865f5c..0cf30e2db5e 100644
--- a/code/game/objects/items/weapons/holy_weapons.dm
+++ b/code/game/objects/items/weapons/holy_weapons.dm
@@ -375,9 +375,6 @@
melee_attack_chain(attacking_shade, attacking_atom)
force += 5
-/mob/living/simple_animal/shade/sword/create_mob_hud()
- hud_used = new /datum/hud/sword(src)
-
/datum/hud/sword/New(mob/user)
..()
diff --git a/code/game/objects/items/weapons/tanks/jetpack.dm b/code/game/objects/items/weapons/tanks/jetpack.dm
index eace17ea30d..865861edeb0 100644
--- a/code/game/objects/items/weapons/tanks/jetpack.dm
+++ b/code/game/objects/items/weapons/tanks/jetpack.dm
@@ -52,7 +52,7 @@
to_chat(user, "You turn the jetpack off.")
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/tank/jetpack/proc/turn_on(mob/user)
diff --git a/code/game/objects/items/weapons/twohanded.dm b/code/game/objects/items/weapons/twohanded.dm
index 694768dd3b9..71a0768a94c 100644
--- a/code/game/objects/items/weapons/twohanded.dm
+++ b/code/game/objects/items/weapons/twohanded.dm
@@ -448,7 +448,7 @@
user.update_inv_r_hand()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/chainsaw/attack_hand(mob/user)
. = ..()
diff --git a/code/modules/antagonists/vampire/vamp_datum.dm b/code/modules/antagonists/vampire/vamp_datum.dm
index d5a096bcef5..65625b191a5 100644
--- a/code/modules/antagonists/vampire/vamp_datum.dm
+++ b/code/modules/antagonists/vampire/vamp_datum.dm
@@ -310,7 +310,7 @@
check_vampire_upgrade(TRUE)
for(var/obj/effect/proc_holder/spell/S in powers)
if(S.action)
- S.action.UpdateButtonIcon()
+ S.action.UpdateButtons()
/datum/antagonist/vampire/proc/vamp_burn(burn_chance)
if(prob(burn_chance) && owner.current.health >= 50)
diff --git a/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm b/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm
index f00aa187994..5d75d682faf 100644
--- a/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm
+++ b/code/modules/antagonists/vampire/vampire_powers/umbrae_powers.dm
@@ -17,7 +17,9 @@
var/datum/antagonist/vampire/V = user.mind.has_antag_datum(/datum/antagonist/vampire)
if(!V)
return
- action.button.name = "[initial(name)] ([V.iscloaking ? "Deactivate" : "Activate"])"
+
+ action.name = "[initial(name)] ([V.iscloaking ? "Deactivate" : "Activate"])"
+ UpdateButtons()
/obj/effect/proc_holder/spell/vampire/self/cloak/cast(list/targets, mob/user = usr)
var/datum/antagonist/vampire/V = user.mind.has_antag_datum(/datum/antagonist/vampire)
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 0aaea933918..113e3d1c4c8 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -1176,6 +1176,11 @@
var/list/screensize = getviewsize(view)
return max(screensize[1], screensize[2])
+/client/Click(object, location, control, params)
+ . = ..()
+ // please yell at me if this is Too Much
+ SEND_SIGNAL(src, COMSIG_CLIENT_CLICK, object, location, control, params, usr)
+
/// Compiles a full list of verbs and sends it to the browser
/client/proc/init_verbs()
if(IsAdminAdvancedProcCall())
diff --git a/code/modules/clothing/chameleon.dm b/code/modules/clothing/chameleon.dm
index 0387be33942..0bc12e7c74b 100644
--- a/code/modules/clothing/chameleon.dm
+++ b/code/modules/clothing/chameleon.dm
@@ -94,10 +94,14 @@
qdel(O)
..()
-/datum/action/item_action/chameleon/change/proc/initialize_disguises()
- if(button)
+/datum/action/item_action/chameleon/change/UpdateButton(atom/movable/screen/movable/action_button/button, status_only, force)
+ . = ..()
+ if(.)
button.name = "Change [chameleon_name] Appearance"
+
+/datum/action/item_action/chameleon/change/proc/initialize_disguises()
+ UpdateButtons()
chameleon_blacklist |= typecacheof(target.type)
for(var/V in typesof(chameleon_type))
if(ispath(V) && ispath(V, /obj/item))
@@ -141,7 +145,7 @@
update_item(picked_item)
var/obj/item/thing = target
thing.update_slot_icon()
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/item_action/chameleon/change/proc/update_item(obj/item/picked_item)
target.name = initial(picked_item.name)
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index 055bd13fa97..461afdbf469 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -62,7 +62,7 @@
C.head_update(src, forced = 1)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
return TRUE
/obj/item/clothing/proc/visor_toggling() //handles all the actual toggling of flags
@@ -446,7 +446,7 @@
usr.update_inv_head()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
// Changes the speech verb when wearing a mask if a value is returned
/obj/item/clothing/mask/proc/change_speech_verb()
@@ -646,7 +646,7 @@
to_chat(user, "You [flavour] \the [src].")
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
else
var/flavour = "open"
icon_state += "_open"
@@ -656,7 +656,7 @@
to_chat(user, "You [flavour] \the [src].")
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
suit_adjusted = !suit_adjusted
update_icon(UPDATE_ICON_STATE)
diff --git a/code/modules/clothing/glasses/engine_goggles.dm b/code/modules/clothing/glasses/engine_goggles.dm
index 03bde7c6b0e..e593c0eb03d 100644
--- a/code/modules/clothing/glasses/engine_goggles.dm
+++ b/code/modules/clothing/glasses/engine_goggles.dm
@@ -65,7 +65,7 @@
update_icon(UPDATE_ICON_STATE)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/glasses/meson/engine/attack_self(mob/user)
toggle_mode(user, TRUE)
diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm
index 84752b623df..62dda694baf 100644
--- a/code/modules/clothing/head/hardhat.dm
+++ b/code/modules/clothing/head/hardhat.dm
@@ -36,7 +36,7 @@
H.update_inv_head()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/head/hardhat/proc/turn_on(mob/user)
set_light(brightness_on)
diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm
index c8cdf01e64e..c61869c24b5 100644
--- a/code/modules/clothing/head/soft_caps.dm
+++ b/code/modules/clothing/head/soft_caps.dm
@@ -32,7 +32,7 @@
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/head/soft/red
name = "red cap"
diff --git a/code/modules/clothing/shoes/magboots.dm b/code/modules/clothing/shoes/magboots.dm
index fb1bce27ee5..797006d07ad 100644
--- a/code/modules/clothing/shoes/magboots.dm
+++ b/code/modules/clothing/shoes/magboots.dm
@@ -52,7 +52,7 @@
user.update_gravity(user.mob_has_gravity())
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
check_mag_pulse(user)
/obj/item/clothing/shoes/magboots/proc/check_mag_pulse(mob/user)
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index 48520b2adaf..a430ed04f40 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -66,7 +66,7 @@
set_light(0)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/head/helmet/space/hardsuit/extinguish_light(force = FALSE)
if(on)
@@ -310,7 +310,7 @@
C.head_update(src, forced = 1)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/head/helmet/space/hardsuit/syndi/proc/toggle_hardsuit_mode(mob/user) //Helmet Toggles Suit Mode
if(linkedsuit)
diff --git a/code/modules/clothing/spacesuits/plasmamen.dm b/code/modules/clothing/spacesuits/plasmamen.dm
index cfc22e69e6e..4519e320e1f 100644
--- a/code/modules/clothing/spacesuits/plasmamen.dm
+++ b/code/modules/clothing/spacesuits/plasmamen.dm
@@ -45,7 +45,7 @@
. = ..()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/head/helmet/space/plasmaman/update_icon_state()
if(!up)
diff --git a/code/modules/clothing/suits/armor_suits.dm b/code/modules/clothing/suits/armor_suits.dm
index a2853fbe952..9a5dda61757 100644
--- a/code/modules/clothing/suits/armor_suits.dm
+++ b/code/modules/clothing/suits/armor_suits.dm
@@ -434,7 +434,7 @@
user.update_inv_wear_suit()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/suit/armor/reactive/emp_act(severity)
var/emp_power = 5 + (severity-1 ? 0 : 5)
diff --git a/code/modules/clothing/suits/hood.dm b/code/modules/clothing/suits/hood.dm
index 723ffb80d1d..7df145f0fa6 100644
--- a/code/modules/clothing/suits/hood.dm
+++ b/code/modules/clothing/suits/hood.dm
@@ -45,7 +45,7 @@
hood.forceMove(src)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/suit/hooded/dropped()
..()
@@ -67,7 +67,7 @@
H.update_inv_wear_suit()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
else
if((hood?.flags & NODROP) && respects_nodrop)
if(ishuman(loc))
diff --git a/code/modules/clothing/suits/misc_suits.dm b/code/modules/clothing/suits/misc_suits.dm
index 648694fa4e2..301c4c86314 100644
--- a/code/modules/clothing/suits/misc_suits.dm
+++ b/code/modules/clothing/suits/misc_suits.dm
@@ -1085,7 +1085,7 @@
usr.update_inv_wear_suit()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/clothing/suit/lordadmiral
name = "lord admiral's coat"
diff --git a/code/modules/customitems/item_defines.dm b/code/modules/customitems/item_defines.dm
index 6d2a3cce5a1..aaee4575f7a 100644
--- a/code/modules/customitems/item_defines.dm
+++ b/code/modules/customitems/item_defines.dm
@@ -1003,7 +1003,7 @@
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
to_chat(user, "You turn [src]'s lighting system [flavour].")
user.update_inv_wear_suit()
@@ -1331,7 +1331,7 @@
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/toy/plushie/fluff/fox/ui_action_click(mob/user)
change_color(user)
@@ -1407,7 +1407,7 @@
usr.update_inv_head()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/// chronx100: Hughe O'Splash
/obj/item/clothing/suit/chaplain_hoodie/fluff/chronx
diff --git a/code/modules/events/blob/overmind.dm b/code/modules/events/blob/overmind.dm
index c31363e81c7..a06f0c1ce15 100644
--- a/code/modules/events/blob/overmind.dm
+++ b/code/modules/events/blob/overmind.dm
@@ -12,6 +12,8 @@
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
+ hud_type = /datum/hud/blob_overmind
+
var/obj/structure/blob/core/blob_core = null // The blob overmind's core
var/blob_points = 0
var/max_blob_points = 100
diff --git a/code/modules/games/cards.dm b/code/modules/games/cards.dm
index 0093de60001..3746b18a15b 100644
--- a/code/modules/games/cards.dm
+++ b/code/modules/games/cards.dm
@@ -496,7 +496,7 @@
if(length(cards) <= 2)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
..()
/obj/item/cardhand/update_name()
diff --git a/code/modules/input/keybindings_procs.dm b/code/modules/input/keybindings_procs.dm
index 8ae07693672..b5e95307f01 100644
--- a/code/modules/input/keybindings_procs.dm
+++ b/code/modules/input/keybindings_procs.dm
@@ -14,9 +14,10 @@
active_keybindings[key] += list(KB)
if(!mob) // Clients can join before world/new is setup, so we gotta mob check em
return active_keybindings
- for(var/datum/action/action as anything in mob.actions)
- if(action.button?.linked_keybind?.binded_to)
- var/datum/keybinding/mob/trigger_action_button/linked_bind = action.button.linked_keybind
+ for(var/atom/movable/screen/movable/action_button/button as anything in mob.hud_used.get_all_action_buttons())
+ // TODO VERIFY THIS (AND KEYBINDS) WORK PROPERLY
+ if(button.linked_keybind?.binded_to)
+ var/datum/keybinding/mob/trigger_action_button/linked_bind = button.linked_keybind
active_keybindings[linked_bind.binded_to] += list(linked_bind)
return active_keybindings
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index dc43a12ccca..c34741ccaaa 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -185,7 +185,7 @@
spawn(1)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
//destablizing force
/obj/item/projectile/destabilizer
diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm
index 8eed8473919..2df3f2bd9fc 100644
--- a/code/modules/mining/equipment/regenerative_core.dm
+++ b/code/modules/mining/equipment/regenerative_core.dm
@@ -129,7 +129,7 @@
. += "legion_soul_crackle"
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/organ/internal/regenerative_core/legion/go_inert()
..()
diff --git a/code/modules/mob/dead/observer/observer_base.dm b/code/modules/mob/dead/observer/observer_base.dm
index 78185dc9e3e..2c5640f1915 100644
--- a/code/modules/mob/dead/observer/observer_base.dm
+++ b/code/modules/mob/dead/observer/observer_base.dm
@@ -39,6 +39,7 @@ GLOBAL_DATUM_INIT(ghost_crew_monitor, /datum/ui_module/crew_monitor/ghost, new)
var/datum/orbit_menu/orbit_menu
/// The "color" their runechat would have had
var/alive_runechat_color = "#FFFFFF"
+ hud_type = /datum/hud/ghost
/mob/dead/observer/New(mob/body=null, flags=1)
set_invisibility(GLOB.observer_default_invisibility)
diff --git a/code/modules/mob/inventory_procs.dm b/code/modules/mob/inventory_procs.dm
index 171aeaf9c2f..fb4369f170e 100644
--- a/code/modules/mob/inventory_procs.dm
+++ b/code/modules/mob/inventory_procs.dm
@@ -114,7 +114,7 @@
W.forceMove(drop_location())
W.layer = initial(W.layer)
W.plane = initial(W.plane)
- W.dropped()
+ W.dropped(src)
/mob/proc/drop_item_v() //this is dumb.
if(stat == CONSCIOUS && isturf(loc))
diff --git a/code/modules/mob/living/brain/MMI.dm b/code/modules/mob/living/brain/MMI.dm
index 5b0293c0bb2..7a0acac7751 100644
--- a/code/modules/mob/living/brain/MMI.dm
+++ b/code/modules/mob/living/brain/MMI.dm
@@ -81,7 +81,7 @@
alien = 0
if(radio_action)
- radio_action.UpdateButtonIcon()
+ radio_action.UpdateButtons()
SSblackbox.record_feedback("amount", "mmis_filled", 1)
else
to_chat(user, "You can't drop [B]!")
@@ -180,7 +180,7 @@
/obj/item/mmi/proc/become_occupied(new_icon)
icon_state = new_icon
if(radio)
- radio_action.UpdateButtonIcon()
+ radio_action.UpdateButtons()
/obj/item/mmi/examine(mob/user)
. = ..()
diff --git a/code/modules/mob/living/carbon/alien/alien_base.dm b/code/modules/mob/living/carbon/alien/alien_base.dm
index 9a6ae766c88..8392f8a09bf 100644
--- a/code/modules/mob/living/carbon/alien/alien_base.dm
+++ b/code/modules/mob/living/carbon/alien/alien_base.dm
@@ -213,7 +213,7 @@ Des: Removes all infected images from the alien.
and carry the owner just to make sure*/
/mob/living/carbon/proc/update_plasma_display(mob/owner)
for(var/datum/action/spell_action/action in actions)
- action.UpdateButtonIcon()
+ action.UpdateButtons()
if(!hud_used || !isalien(owner)) //clientless aliens or non aliens
return
hud_used.alien_plasma_display.maptext = " [get_plasma()]
"
diff --git a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
index 5b139d6652d..f952bfe2797 100644
--- a/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
+++ b/code/modules/mob/living/carbon/alien/humanoid/humanoid.dm
@@ -14,6 +14,7 @@
var/alien_movement_delay = 0 //This can be + or -, how fast an alien moves
var/temperature_resistance = T0C+75
pass_flags = PASSTABLE
+ hud_type = /datum/hud/alien
//This is fine right now, if we're adding organ specific damage this needs to be updated
/mob/living/carbon/alien/humanoid/Initialize(mapload)
@@ -32,9 +33,9 @@
return FALSE
/mob/living/carbon/alien/humanoid/emp_act(severity)
- if(r_store)
+ if(r_store)
r_store.emp_act(severity)
- if(l_store)
+ if(l_store)
l_store.emp_act(severity)
..()
diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm
index f6508a8286b..7f9bb302712 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva.dm
@@ -14,6 +14,7 @@
var/max_grown = 200
death_message = "lets out a waning high-pitched cry."
death_sound = null
+ hud_type = /datum/hud/larva
//This is fine right now, if we're adding organ specific damage this needs to be updated
/mob/living/carbon/alien/larva/Initialize(mapload)
diff --git a/code/modules/mob/living/carbon/human/species/golem.dm b/code/modules/mob/living/carbon/human/species/golem.dm
index ac5c1a5f8e4..2c1426512ea 100644
--- a/code/modules/mob/living/carbon/human/species/golem.dm
+++ b/code/modules/mob/living/carbon/human/species/golem.dm
@@ -541,9 +541,9 @@
H.buckled.unbuckle_mob(H, force = TRUE)
do_teleport(H, picked)
last_teleport = world.time
- UpdateButtonIcon() //action icon looks unavailable
+ UpdateButtons() //action icon looks unavailable
sleep(cooldown + 5)
- UpdateButtonIcon() //action icon looks available again
+ UpdateButtons() //action icon looks available again
/datum/unarmed_attack/golem/bluespace
attack_verb = "bluespace punch"
diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm
index 9711290c71b..f660e415f72 100644
--- a/code/modules/mob/living/silicon/ai/ai_mob.dm
+++ b/code/modules/mob/living/silicon/ai/ai_mob.dm
@@ -47,6 +47,7 @@ GLOBAL_LIST_INIT(ai_verbs_default, list(
see_invisible = SEE_INVISIBLE_LIVING_AI
see_in_dark = 8
can_strip = FALSE
+ hud_type = /datum/hud/ai
hat_offset_y = 3
is_centered = TRUE
can_be_hatted = TRUE
diff --git a/code/modules/mob/living/silicon/robot/robot_mob.dm b/code/modules/mob/living/silicon/robot/robot_mob.dm
index 1bde5789c74..420db674ac7 100644
--- a/code/modules/mob/living/silicon/robot/robot_mob.dm
+++ b/code/modules/mob/living/silicon/robot/robot_mob.dm
@@ -13,6 +13,7 @@ GLOBAL_LIST_INIT(robot_verbs_default, list(
universal_understand = TRUE
deathgasp_on_death = TRUE
blocks_emissive = EMISSIVE_BLOCK_UNIQUE
+ hud_type = /datum/hud/robot
var/sight_mode = 0
var/custom_name = ""
diff --git a/code/modules/mob/living/simple_animal/bot/bot.dm b/code/modules/mob/living/simple_animal/bot/bot.dm
index cf9c15ffaa6..c03dcb1b381 100644
--- a/code/modules/mob/living/simple_animal/bot/bot.dm
+++ b/code/modules/mob/living/simple_animal/bot/bot.dm
@@ -17,6 +17,7 @@
sentience_type = SENTIENCE_ARTIFICIAL
status_flags = 0 // No default canpush
can_strip = FALSE
+ hud_type = /datum/hud/bot
speak_emote = list("states")
friendly = "boops"
@@ -139,6 +140,7 @@
/// Will be true if we lost target we were chasing
var/lost_target = FALSE
+
/obj/item/radio/headset/bot
requires_tcomms = FALSE
canhear_range = 0
diff --git a/code/modules/mob/living/simple_animal/constructs.dm b/code/modules/mob/living/simple_animal/constructs.dm
index b02d0a309f7..1540c3aaf06 100644
--- a/code/modules/mob/living/simple_animal/constructs.dm
+++ b/code/modules/mob/living/simple_animal/constructs.dm
@@ -139,6 +139,7 @@
desc = "A possessed suit of armour driven by the will of the restless dead"
icon_state = "behemoth"
icon_living = "behemoth"
+ hud_type = /datum/hud/construct/armoured
maxHealth = 250
health = 250
response_harm = "harmlessly punches"
@@ -182,6 +183,7 @@
desc = "A wicked bladed shell contraption piloted by a bound spirit"
icon_state = "floating"
icon_living = "floating"
+ hud_type = /datum/hud/construct/wraith
maxHealth = 75
health = 75
melee_damage_lower = 25
@@ -211,6 +213,7 @@
desc = "A bulbous construct dedicated to building and maintaining Cult armies."
icon_state = "artificer"
icon_living = "artificer"
+ hud_type = /datum/hud/construct/builder
maxHealth = 50
health = 50
response_harm = "viciously beats"
@@ -304,6 +307,7 @@
attack_sound = 'sound/weapons/punch4.ogg'
force_threshold = 11
construct_type = "behemoth"
+ hud_type = /datum/hud/construct/armoured
var/energy = 0
var/max_energy = 1000
@@ -321,6 +325,7 @@
desc = "A harbinger of enlightenment. It'll be all over soon."
icon_state = "harvester"
icon_living = "harvester"
+ hud_type = /datum/hud/construct/harvester
maxHealth = 40
health = 40
melee_damage_lower = 20
diff --git a/code/modules/mob/living/simple_animal/friendly/dog.dm b/code/modules/mob/living/simple_animal/friendly/dog.dm
index af1762e3bcc..6886ae40901 100644
--- a/code/modules/mob/living/simple_animal/friendly/dog.dm
+++ b/code/modules/mob/living/simple_animal/friendly/dog.dm
@@ -74,6 +74,7 @@
childtype = list(/mob/living/simple_animal/pet/dog/corgi/puppy = 95, /mob/living/simple_animal/pet/dog/corgi/puppy/void = 5)
animal_species = /mob/living/simple_animal/pet/dog
collar_type = "corgi"
+ hud_type = /datum/hud/corgi
var/obj/item/inventory_head
var/obj/item/inventory_back
var/shaved = FALSE
diff --git a/code/modules/mob/living/simple_animal/friendly/pet.dm b/code/modules/mob/living/simple_animal/friendly/pet.dm
index c7735d55ccd..5c5003dcf21 100644
--- a/code/modules/mob/living/simple_animal/friendly/pet.dm
+++ b/code/modules/mob/living/simple_animal/friendly/pet.dm
@@ -5,6 +5,7 @@
blood_volume = BLOOD_VOLUME_NORMAL
can_collar = TRUE
speed = 0 // same speed as a person.
+ hud_type = /datum/hud/corgi
/mob/living/simple_animal/pet/attackby(obj/item/O, mob/user, params)
if(istype(O, /obj/item/newspaper))
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm b/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm
index 1785a9682d8..5c6f883a02e 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/elites/elite.dm
@@ -111,8 +111,8 @@ While using this makes the system rely on OnFire, it still gives options for tim
///The internal attack ID for the elite's OpenFire() proc to use
var/chosen_attack_num = 0
-/datum/action/innate/elite_attack/New(Target)
- . = ..()
+/datum/action/innate/elite_attack/CreateButton()
+ var/atom/movable/screen/movable/action_button/button = ..()
button.maptext = ""
button.maptext_x = 8
button.maptext_y = 0
@@ -127,7 +127,7 @@ While using this makes the system rely on OnFire, it still gives options for tim
return
UpdateButton()
-/datum/action/innate/elite_attack/proc/UpdateButton(status_only = FALSE)
+/datum/action/innate/elite_attack/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
if(status_only)
return
var/mob/living/simple_animal/hostile/asteroid/elite/elite_owner = owner
diff --git a/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress_terror.dm b/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress_terror.dm
index 4c8524671b3..a17cdffafdb 100644
--- a/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress_terror.dm
+++ b/code/modules/mob/living/simple_animal/hostile/terror_spiders/empress_terror.dm
@@ -46,7 +46,8 @@
/mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/NestMode()
..()
- queeneggs_action.button.name = "Empress Eggs"
+ queeneggs_action.name = "Empress Eggs"
+ queeneggs_action.UpdateButtons()
/mob/living/simple_animal/hostile/poison/terror_spider/queen/empress/LayQueenEggs()
var/eggtype = tgui_input_list(src, "What kind of eggs?", "Egg laying", list(TS_DESC_QUEEN, TS_DESC_MOTHER, TS_DESC_PRINCE, TS_DESC_PRINCESS, TS_DESC_RED, TS_DESC_GRAY, TS_DESC_GREEN, TS_DESC_BLACK, TS_DESC_PURPLE, TS_DESC_WHITE, TS_DESC_BROWN))
diff --git a/code/modules/mob/living/simple_animal/shade.dm b/code/modules/mob/living/simple_animal/shade.dm
index ca63f9a1217..bd5b9b270d3 100644
--- a/code/modules/mob/living/simple_animal/shade.dm
+++ b/code/modules/mob/living/simple_animal/shade.dm
@@ -66,6 +66,7 @@
health = 100
maxHealth = 100
weather_immunities = list("ash")
+ hud_type = /datum/hud/sword
/mob/living/simple_animal/shade/sword/Initialize(mapload)
.=..()
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index d7e94514dc2..9df1fb62e00 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -4,6 +4,7 @@
health = 20
maxHealth = 20
gender = PLURAL //placeholder
+ hud_type = /datum/hud/simple_animal
universal_understand = TRUE
universal_speak = FALSE
diff --git a/code/modules/mob/living/simple_animal/slime/slime_mob.dm b/code/modules/mob/living/simple_animal/slime/slime_mob.dm
index 7ac51e83d0c..2e4fca91f6f 100644
--- a/code/modules/mob/living/simple_animal/slime/slime_mob.dm
+++ b/code/modules/mob/living/simple_animal/slime/slime_mob.dm
@@ -2,6 +2,7 @@
name = "grey baby slime (123)"
icon = 'icons/mob/slimes.dmi'
icon_state = "grey baby slime"
+ hud_type = /datum/hud/slime
pass_flags = PASSTABLE | PASSGRILLE
ventcrawler = VENTCRAWLER_ALWAYS
gender = NEUTER
diff --git a/code/modules/mob/living/stat_states.dm b/code/modules/mob/living/stat_states.dm
index c55ba82fedd..e46e0aac96c 100644
--- a/code/modules/mob/living/stat_states.dm
+++ b/code/modules/mob/living/stat_states.dm
@@ -76,7 +76,7 @@
if(mind)
for(var/S in mind.spell_list)
var/obj/effect/proc_holder/spell/spell = S
- spell.updateButtonIcon()
+ spell.UpdateButtons()
return TRUE
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index c4388e17cf5..3a8a7459958 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -5,7 +5,8 @@
input_focus = null
if(s_active)
s_active.close(src)
- QDEL_NULL(hud_used)
+ if(!QDELETED(hud_used))
+ QDEL_NULL(hud_used)
if(mind && mind.current == src)
spellremove()
mobspellremove()
diff --git a/code/modules/mob/mob_vars.dm b/code/modules/mob/mob_vars.dm
index cab03a2c440..2b3e4cc88e1 100644
--- a/code/modules/mob/mob_vars.dm
+++ b/code/modules/mob/mob_vars.dm
@@ -84,6 +84,7 @@
var/obj/item/storage/s_active = null //Carbon
var/obj/item/clothing/mask/wear_mask = null //Carbon
+ /// The instantiated version of the mob's hud.
var/datum/hud/hud_used = null
hud_possible = list(SPECIALROLE_HUD)
@@ -121,6 +122,9 @@
var/move_on_shuttle = TRUE // Can move on the shuttle.
+ /// The type of HUD that this mob uses. Not to
+ var/hud_type = /datum/hud
+
var/antagHUD = FALSE // Whether AntagHUD is active right now
var/can_change_intents = TRUE //all mobs can change intents by default.
///Override for sound_environments. If this is set the user will always hear a specific type of reverb (Instead of the area defined reverb)
diff --git a/code/modules/mod/mod_actions.dm b/code/modules/mod/mod_actions.dm
index f2108b27782..3a8f5a9e694 100644
--- a/code/modules/mod/mod_actions.dm
+++ b/code/modules/mod/mod_actions.dm
@@ -49,7 +49,7 @@
if(!ready && left_click)
ready = TRUE
button_icon_state = "activate-ready"
- UpdateButtonIcon()
+ UpdateButtons()
addtimer(CALLBACK(src, PROC_REF(reset_ready)), 3 SECONDS)
return
var/obj/item/mod/control/mod = target
@@ -60,7 +60,7 @@
/datum/action/item_action/mod/activate/proc/reset_ready()
ready = FALSE
button_icon_state = initial(button_icon_state)
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/item_action/mod/module
name = "Toggle Module"
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index d4b5bbfdb41..ed3abfcfec5 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -388,7 +388,7 @@
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/gun/proc/clear_bayonet()
if(!bayonet)
diff --git a/code/modules/projectiles/guns/projectile/automatic.dm b/code/modules/projectiles/guns/projectile/automatic.dm
index c83645824c5..94e39bc7f68 100644
--- a/code/modules/projectiles/guns/projectile/automatic.dm
+++ b/code/modules/projectiles/guns/projectile/automatic.dm
@@ -63,7 +63,7 @@
update_icon()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/gun/projectile/automatic/can_shoot()
return get_ammo()
diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm
index 4c0d3967f50..c2e847de6a9 100644
--- a/code/modules/surgery/organs/heart.dm
+++ b/code/modules/surgery/organs/heart.dm
@@ -180,7 +180,9 @@
/datum/action/item_action/organ_action/cursed_heart/proc/poll_keybinds()
if(alert(owner, "You've been given a cursed heart! Do you want to bind its action to a keybind?", "Cursed Heart", "Yes", "No") == "Yes")
- button.set_to_keybind(owner)
+ return
+ // button.set_to_keybind(owner)
+ // TODO GAHHH SORRY GDN
/obj/item/organ/internal/heart/cybernetic
name = "cybernetic heart"
diff --git a/code/modules/surgery/organs/subtypes/skrell_organs.dm b/code/modules/surgery/organs/subtypes/skrell_organs.dm
index de58d05a866..25506e7b210 100644
--- a/code/modules/surgery/organs/subtypes/skrell_organs.dm
+++ b/code/modules/surgery/organs/subtypes/skrell_organs.dm
@@ -22,7 +22,7 @@
/obj/item/organ/internal/headpocket/proc/update_button_state()
for(var/datum/action/item_action/T in actions)
T.button_icon_state = "skrell_headpocket[held_item ? "_out" : "_in"]"
- T.UpdateButtonIcon()
+ T.UpdateButtons()
/obj/item/organ/internal/headpocket/Destroy()
empty_contents()
diff --git a/icons/hud/64x16_actions.dmi b/icons/hud/64x16_actions.dmi
new file mode 100644
index 0000000000000000000000000000000000000000..ae81ace4d55a2c26a5c12fcfd6dcac28ee029db8
GIT binary patch
literal 3404
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
UWbCdm^m_!%jn5jD5}j}V9|Ff!mjD0&
literal 0
HcmV?d00001
diff --git a/icons/mob/screen_ai.dmi b/icons/mob/screen_ai.dmi
index 78071da391f0f8a9563d11b7351fb4a47ab16378..df250d94beb4350aee0f4f09bb113ca32d712fea 100644
GIT binary patch
literal 6373
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
literal 2494
zcmV;v2|@OWP)004jp0{{R3ySnmH5MUF
z3r7HGPylsT0Dk~BW&ko?01zPnb?N|a-N3-W08pR+3>mXfTVwzL00DGTPE!Ct=GbNc
z008fLR9JLGWpiV4X>fFDZ*Bkpc$|&Xv2Ft)3;@vi_zINlRois!QYES~G{1mc&dFH;
z6B*y#?}tm4PW5fZdNN?c)_)zok0-zXIl2j@Qepq;KVDv4H&d@eTkPIlHy~XGeJ-kk
zVhcAwAr`@n>w1#fGHr$mILp@FZtc$Y|NrKd#5)pT;%>=@C~Mpu1zS8w0u=h`(?gES=c?R`
z{;4AN6SvlX`usFi52n3Z1AJ9w>
z2I?ibK}v95O(tL4bhv8WQL1pucVgy&X@wSnP
z&_zmD2(loE0MWpJFzCs*f`N`-Mi6d?5Q6{^KCnQ%snW-S
zfs9}@4+&~Ukb=kio}yF%2=^0Avls$gz{1497{b;-k1!!Pw+Sk|oqNPG)1^WXL?Afj
zg16I*U~1r11l`@2*#c!KR7r4Y6Z9eM5K;^eJeUy-!U;y{_#nne7R4T*H;_1$c~S*}zUcYD+#iKtP{rtC=GNVFfd%+nFqi
z?|tKb2c?a+{vi*!x!gJk`uaOhGq}pTfLE__L<9B1Kv2a2Dp%A>ozYLvMGP>4vA$C7%1T57^+Q}D>ng#p<@yQ|wp}Sf
z8(tJ@b%M;%f`OMyuYQO()m0yq4NM6d3NKA1Qd9Fm=4e^{@N$Xu!_g?l=YG?5g062m
zR`@tUB{*72&@#65L%|2zc@XaW&3v2Z@5|Tki$();qg4S0G5eE*-5_KNJWy
zqI8I$<)=A8FtBvAdi`*k47`Lq_l$2ZFE1tG=z|LooUM}yK6pY$K^>1`96RP{wOyZ~
zUkuYfKR@H;zZZev&M5W{h;;*jVBu)>nE*N}>FF`AlSmNL_yPdGFBtD7LDzXIU5ayY
zwrll{Rv~BzA-pp^j?=5`gB^mdf65K7;DcV2lCn#&w599j=%1OXo2jzzJ*^^c#e_U!&{
zt|XH)0}t&fw4{HvPq~gDnXFujIY_Xo*V!k>*MsV36tTv~W@jX~NJv^$P!dIR8I)Y1w
zz)`*5*=Rpsj}d$erPVhO-ovAM8j1yGhyY>r+dMkSv^&3b_FSiee2enMlZA0n=
z)jGSc+VOP2<*n>tuHQO(-hQ&jkE#Tf{gU4a{D%`<9D(Ix){U(PNsp&3)@7^c-{0Rg
z{9yZwu%1*)onU#OK-ksU2fWh*G#J1MfgU#hpx-Kb=zzfvK>LeuX2Bi#5DBs~8OfeV
zAh=cuk^x&q|4yZycWf^aB!)GDRCJ{Z$n1V;5i}KoRNQ>GLoDcR9a^JaA~^Zlsz?wI
zWZ5IIbfBb35QcD(2n7Qw`jt9RY40PME}Y;(1Tj$Xj2rwc4T3a;B0)QZRlWC7N$(l*
zJeQx_QCbVqLRz&hj3fK&-qqu8dAx*oizrpKBgl@0Ab
zMo_y~9AZW=Bw83EiO?~9hhQ*pu|cv3J_vKvW<{PU)@?hWr>d**74RB|+y^#Z@Lqsxg8)&i|o-$tVv-`C)?n^M7Pu9Oc0%Kk9=!
z&i`2hi&!r6U~PyGNAZsHf6l->mdhwU71s~TgFDXuc>}XpE~9uVt{FjLsv3ymt`7mb
z#6bOLn4mOO4b86B*B`#w;rraRY#(DjNl=C-@tp{xZi#0;~|ISf3oTqN0)*FsQ>@~07*qo
IM6N<$g5ma$$^ZZW
diff --git a/icons/mob/screen_alien.dmi b/icons/mob/screen_alien.dmi
index 209ec83993967186a01e972175fdc6d1e3fa3c33..6275b65a4372229b965f52c4e10258b57e91ecb2 100644
GIT binary patch
literal 28996
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