diff --git a/code/__DEFINES/_click.dm b/code/__DEFINES/_click.dm
new file mode 100644
index 0000000000..4118fb873d
--- /dev/null
+++ b/code/__DEFINES/_click.dm
@@ -0,0 +1,2 @@
+//The button used for dragging (only sent for unrelated mouse up/down messages during a drag)
+#define DRAG "drag"
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 4477c8aee8..6b9fc0d6ba 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -196,18 +196,6 @@
#define COMSIG_ENTER_AREA "enter_area" //from base of area/Entered(): (/area)
#define COMSIG_EXIT_AREA "exit_area" //from base of area/Exited(): (/area)
-#define COMSIG_CLICK "atom_click" //from base of atom/Click(): (location, control, params, mob/user)
-#define COMSIG_CLICK_SHIFT "shift_click" //from base of atom/ShiftClick(): (/mob), return flags also used by other signals.
- #define COMPONENT_ALLOW_EXAMINATE 1
- #define COMPONENT_DENY_EXAMINATE 2 //Higher priority compared to the above one
-
-#define COMSIG_CLICK_CTRL "ctrl_click" //from base of atom/CtrlClickOn(): (/mob)
-#define COMSIG_CLICK_ALT "alt_click" //from base of atom/AltClick(): (/mob)
-#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" //from base of atom/CtrlShiftClick(/mob)
-#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto" //from base of atom/MouseDrop(): (/atom/over, /mob/user)
- #define COMPONENT_NO_MOUSEDROP 1
-#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user)
-
// /area signals
#define COMSIG_AREA_ENTERED "area_entered" //from base of area/Entered(): (atom/movable/M)
#define COMSIG_AREA_EXITED "area_exited" //from base of area/Exited(): (atom/movable/M)
diff --git a/code/__DEFINES/dcs/signals/signals_action.dm b/code/__DEFINES/dcs/signals/signals_action.dm
new file mode 100644
index 0000000000..62eccf2823
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_action.dm
@@ -0,0 +1,6 @@
+/// From base of /datum/action/cooldown/proc/PreActivate(), sent to the action owner: (datum/action/cooldown/activated)
+#define COMSIG_MOB_ABILITY_STARTED "mob_ability_base_started"
+ /// Return to block the ability from starting / activating
+ #define COMPONENT_BLOCK_ABILITY_START (1<<0)
+/// From base of /datum/action/cooldown/proc/PreActivate(), sent to the action owner: (datum/action/cooldown/finished)
+#define COMSIG_MOB_ABILITY_FINISHED "mob_ability_base_finished"
diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
new file mode 100644
index 0000000000..2b0c9c297e
--- /dev/null
+++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_mouse.dm
@@ -0,0 +1,25 @@
+// mouse signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+///from base of client/Click(): (atom/target, atom/location, control, params, mob/user)
+#define COMSIG_CLIENT_CLICK "atom_client_click"
+///from base of atom/Click(): (atom/location, control, params, mob/user)
+#define COMSIG_CLICK "atom_click"
+///from base of atom/ShiftClick(): (/mob)
+#define COMSIG_CLICK_SHIFT "shift_click"
+ /// Allows the user to examinate regardless of client.eye.
+ #define COMPONENT_ALLOW_EXAMINATE (1<<0)
+ /// Higher priority compared to the above one
+ #define COMPONENT_DENY_EXAMINATE (1<<1)
+///from base of atom/CtrlClickOn(): (/mob)
+#define COMSIG_CLICK_CTRL "ctrl_click"
+///from base of atom/AltClick(): (/mob)
+#define COMSIG_CLICK_ALT "alt_click"
+///from base of atom/CtrlShiftClick(/mob)
+#define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click"
+///from base of atom/MouseDrop(): (/atom/over, /mob/user)
+#define COMSIG_MOUSEDROP_ONTO "mousedrop_onto"
+ #define COMPONENT_NO_MOUSEDROP (1<<0)
+///from base of atom/MouseDrop_T: (/atom/from, /mob/user)
+#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto" //from base of atom/MouseDrop_T: (/atom/from, /mob/user)
diff --git a/code/__DEFINES/hud.dm b/code/__DEFINES/hud.dm
index 88cba1c449..94755e3bd2 100644
--- a/code/__DEFINES/hud.dm
+++ b/code/__DEFINES/hud.dm
@@ -1,9 +1,232 @@
//HUD styles. Index order defines how they are cycled in F12.
-#define HUD_STYLE_STANDARD 1 //Standard hud
-#define HUD_STYLE_REDUCED 2 //Reduced hud (just hands and intent switcher)
-#define HUD_STYLE_NOHUD 3 //No hud (for screenshots)
+/// Standard hud
+#define HUD_STYLE_STANDARD 1
+/// Reduced hud (just hands and intent switcher)
+#define HUD_STYLE_REDUCED 2
+/// No hud (for screenshots)
+#define HUD_STYLE_NOHUD 3
-#define HUD_VERSIONS 3 //Used in show_hud(); Please ensure this is the same as the maximum index.
+/// Used in show_hud(); Please ensure this is the same as the maximum index.
+#define HUD_VERSIONS 3
+// Consider these images/atoms as part of the UI/HUD (apart of the appearance_flags)
+/// Used for progress bars and chat messages
+#define APPEARANCE_UI_IGNORE_ALPHA (RESET_COLOR|RESET_TRANSFORM|NO_CLIENT_COLOR|RESET_ALPHA|PIXEL_SCALE)
+/// Used for HUD objects
+#define APPEARANCE_UI (RESET_COLOR|RESET_TRANSFORM|NO_CLIENT_COLOR|PIXEL_SCALE)
+
+/*
+ These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var.
+
+ The short version:
+
+ Everything is encoded as strings because apparently that's how Byond rolls.
+
+ "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid.
+ "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid.
+ Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy.
+
+ In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective
+ screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your
+ UI to scale with screen size.
+
+ The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15".
+ Therefore, the top right corner (except during admin shenanigans) is at "15,15"
+*/
+
+/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5)
+ var/x_off = -(!(i % 2))
+ var/y_off = round((i-1) / 2)
+ return"CENTER+[x_off]:16,SOUTH+[y_off]:5"
+
+/proc/ui_equip_position(mob/M)
+ var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5)
+ 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((M.held_items.len-1) / 2)
+ return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5"
+
+//Lower left, persistent menu
+#define ui_inventory "WEST:6,SOUTH:5"
+
+//Middle left indicators
+#define ui_lingchemdisplay "WEST,CENTER-1:15"
+#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
+
+#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
+
+//Lower center, persistent menu
+#define ui_sstore1 "CENTER-5:10,SOUTH:5"
+#define ui_id "CENTER-4:12,SOUTH:5"
+#define ui_belt "CENTER-3:14,SOUTH:5"
+#define ui_back "CENTER-2:14,SOUTH:5"
+#define ui_storage1 "CENTER+1:18,SOUTH:5"
+#define ui_storage2 "CENTER+2:20,SOUTH:5"
+#define ui_combo "CENTER+4:24,SOUTH+1:7" // combo meter for martial arts
+
+//Lower right, persistent menu
+#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
+#define ui_pull_resist "EAST-2:26,SOUTH+1:7"
+#define ui_movi "EAST-2:26,SOUTH:5"
+#define ui_sprintbufferloc "EAST-2:26,SOUTH:18"
+#define ui_acti "EAST-3:24,SOUTH:5"
+#define ui_zonesel "EAST-1:28,SOUTH:5"
+#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12)
+#define ui_crafting "EAST-5:20,SOUTH:5"//CIT CHANGE - moves this over one tile to accommodate for combat mode toggle
+#define ui_building "EAST-5:20,SOUTH:21"//CIT CHANGE - ditto
+#define ui_language_menu "EAST-5:4,SOUTH:21"//CIT CHANGE - ditto
+#define ui_voremode "EAST-5:20,SOUTH:5"
+
+//Upper-middle right (alerts)
+#define ui_alert1 "EAST-1:28,CENTER+5:27"
+#define ui_alert2 "EAST-1:28,CENTER+4:25"
+#define ui_alert3 "EAST-1:28,CENTER+3:23"
+#define ui_alert4 "EAST-1:28,CENTER+2:21"
+#define ui_alert5 "EAST-1:28,CENTER+1:19"
+
+//Upper left (action buttons)
+#define ui_action_palette "WEST+0:23,NORTH-1:5"
+#define ui_action_palette_offset(north_offset) ("WEST+0:23,NORTH-[1+north_offset]:5")
+
+#define ui_palette_scroll "WEST+1:8,NORTH-6:28"
+#define ui_palette_scroll_offset(north_offset) ("WEST+1:8,NORTH-[6+north_offset]:28")
+
+//Middle right (status indicators)
+#define ui_healthdoll "EAST-1:28,CENTER-2:13"
+#define ui_health "EAST-1:28,CENTER-1:15"
+#define ui_internal "EAST-1:28,CENTER+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter
+#define ui_mood "EAST-1:28,CENTER-3:10"
+// #define ui_spacesuit "EAST-1:28,CENTER-4:10"
+
+//Pop-up inventory
+#define ui_shoes "WEST+1:8,SOUTH:5"
+#define ui_iclothing "WEST:6,SOUTH+1:7"
+#define ui_oclothing "WEST+1:8,SOUTH+1:7"
+#define ui_gloves "WEST+2:10,SOUTH+1:7"
+#define ui_glasses "WEST:6,SOUTH+3:11"
+#define ui_mask "WEST+1:8,SOUTH+2:9"
+#define ui_ears "WEST+2:10,SOUTH+2:9"
+#define ui_neck "WEST:6,SOUTH+2:9"
+#define ui_head "WEST+1:8,SOUTH+3:11"
+
+//Generic living
+#define ui_living_pull "EAST-1:28,CENTER-2:15"
+#define ui_living_health "EAST-1:28,CENTER:15"
+#define ui_living_healthdoll "EAST-1:28,CENTER-1:15"
+
+//Monkeys
+#define ui_monkey_head "CENTER-5:13,SOUTH:5"
+#define ui_monkey_mask "CENTER-4:14,SOUTH:5"
+#define ui_monkey_neck "CENTER-3:15,SOUTH:5"
+#define ui_monkey_back "CENTER-2:16,SOUTH:5"
+
+//Drones
+#define ui_drone_drop "CENTER+1:18,SOUTH:5"
+#define ui_drone_pull "CENTER+2:2,SOUTH:5"
+#define ui_drone_storage "CENTER-2:14,SOUTH:5"
+#define ui_drone_head "CENTER-3:14,SOUTH:5"
+
+//Cyborgs
+#define ui_borg_health "EAST-1:28,CENTER-1:15"
+#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
+#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
+#define ui_borg_intents "EAST-2:26,SOUTH:5"
+#define ui_borg_lamp "CENTER-3:16, SOUTH:5"
+#define ui_borg_tablet "CENTER-4:16, SOUTH:5"
+#define ui_inv1 "CENTER-2:16,SOUTH:5"
+#define ui_inv2 "CENTER-1 :16,SOUTH:5"
+#define ui_inv3 "CENTER :16,SOUTH:5"
+#define ui_borg_module "CENTER+1:16,SOUTH:5"
+#define ui_borg_store "CENTER+2:16,SOUTH:5"
+#define ui_borg_camera "CENTER+3:21,SOUTH:5"
+#define ui_borg_alerts "CENTER+4:21,SOUTH:5"
+#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5"
#define ui_borg_pda_send "CENTER+5:21,SOUTH:5" // To the right of the alert panel
#define ui_borg_pda_log "CENTER+6:21,SOUTH:5"
+#define ui_borg_sensor "CENTER-6:16, SOUTH:5" //LEGACY
+#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" //LEGACY
+
+//Aliens
+#define ui_alien_health "EAST,CENTER-1:15"
+#define ui_alienplasmadisplay "EAST,CENTER-2:15"
+#define ui_alien_queen_finder "EAST,CENTER-3:15"
+#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"
+#define ui_alien_language_menu "EAST-3:26,SOUTH:5"
+
+//Constructs
+#define ui_construct_pull "EAST,CENTER-2:15"
+#define ui_construct_health "EAST,CENTER:15"
+
+//AI
+#define ui_ai_core "BOTTOM:6,RIGHT-4"
+#define ui_ai_shuttle "BOTTOM:6,RIGHT-3"
+#define ui_ai_announcement "BOTTOM:6,RIGHT-2"
+#define ui_ai_state_laws "BOTTOM:6,RIGHT-1"
+#define ui_ai_pda_log "BOTTOM:6,RIGHT"
+#define ui_ai_pda_send "BOTTOM+1:6,RIGHT"
+#define ui_ai_language_menu "BOTTOM+1:8,RIGHT-2:30"
+
+#define ui_ai_crew_monitor "BOTTOM:6,CENTER-1"
+#define ui_ai_crew_manifest "BOTTOM:6,CENTER"
+#define ui_ai_alerts "BOTTOM:6,CENTER+1"
+
+#define ui_ai_view_images "BOTTOM:6,LEFT+4"
+#define ui_ai_camera_list "BOTTOM:6,LEFT+3"
+#define ui_ai_track_with_camera "BOTTOM:6,LEFT+2"
+#define ui_ai_camera_light "BOTTOM:6,LEFT+1"
+#define ui_ai_sensor "BOTTOM:6,LEFT"
+#define ui_ai_multicam "BOTTOM+1:6,LEFT+1"
+#define ui_ai_add_multicam "BOTTOM+1:6,LEFT"
+#define ui_ai_take_picture "BOTTOM+2:6,LEFT"
+
+
+// pAI
+// #define ui_pai_software "SOUTH:6,WEST"
+// #define ui_pai_shell "SOUTH:6,WEST+1"
+// #define ui_pai_chassis "SOUTH:6,WEST+2"
+// #define ui_pai_rest "SOUTH:6,WEST+3"
+// #define ui_pai_light "SOUTH:6,WEST+4"
+// #define ui_pai_newscaster "SOUTH:6,WEST+5"
+// #define ui_pai_host_monitor "SOUTH:6,WEST+6"
+// #define ui_pai_crew_manifest "SOUTH:6,WEST+7"
+// #define ui_pai_state_laws "SOUTH:6,WEST+8"
+// #define ui_pai_pda_send "SOUTH:6,WEST+9"
+// #define ui_pai_pda_log "SOUTH:6,WEST+10"
+// #define ui_pai_take_picture "SOUTH:6,WEST+12"
+// #define ui_pai_view_images "SOUTH:6,WEST+13"
+
+//Ghosts
+#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24"
+#define ui_ghost_orbit "SOUTH:6,CENTER-2:24"
+#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24"
+#define ui_ghost_teleport "SOUTH:6,CENTER:24"
+#define ui_ghost_pai "SOUTH: 6, CENTER+1:24"
+#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24"
+#define ui_ghost_spawners "SOUTH: 6, CENTER+1:24" // LEGACY. SAME LOC AS PAI
+
+//UI position overrides for 1:1 screen layout. (default is 7:5)
+#define ui_stamina "EAST-1:28,CENTER:17" // replacing internals button
+#define ui_overridden_resist "EAST-3:24,SOUTH+1:7"
+#define ui_clickdelay "CENTER,SOUTH+1:-31"
+#define ui_resistdelay "EAST-3:24,SOUTH+1:4"
+#define ui_combat_toggle "EAST-4:22,SOUTH:5"
+
+#define ui_boxcraft "EAST-4:22,SOUTH+1:6"
+#define ui_boxarea "EAST-4:6,SOUTH+1:6"
+#define ui_boxlang "EAST-5:22,SOUTH+1:6"
+#define ui_boxvore "EAST-5:22,SOUTH+1:6"
+
+#define ui_wanted_lvl "NORTH,11"
+
+// 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/maths.dm b/code/__DEFINES/maths.dm
index 60d02c1e49..01a295634f 100644
--- a/code/__DEFINES/maths.dm
+++ b/code/__DEFINES/maths.dm
@@ -30,6 +30,8 @@
#define CEILING(x, y) ( -round(-(x) / (y)) * (y) )
+#define ROUND_UP(x) ( -round(-(x)))
+
// round() acts like floor(x, 1) by default but can't handle other values
#define FLOOR(x, y) ( round((x) / (y)) * (y) )
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 4165b1cec3..da01956e64 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -304,10 +304,6 @@ GLOBAL_LIST_INIT(pda_reskins, list(
*/
-// Consider these images/atoms as part of the UI/HUD
-#define APPEARANCE_UI_IGNORE_ALPHA (RESET_COLOR|RESET_TRANSFORM|NO_CLIENT_COLOR|RESET_ALPHA|PIXEL_SCALE)
-#define APPEARANCE_UI (RESET_COLOR|RESET_TRANSFORM|NO_CLIENT_COLOR|PIXEL_SCALE)
-
//Just space
#define SPACE_ICON_STATE "[((x + y) ^ ~(x * y) + z) % 25]"
diff --git a/code/__HELPERS/screen_objs.dm b/code/__HELPERS/screen_objs.dm
new file mode 100644
index 0000000000..aa86b6d579
--- /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/__HELPERS/view.dm b/code/__HELPERS/view.dm
index 1c92971802..6a4210bc53 100644
--- a/code/__HELPERS/view.dm
+++ b/code/__HELPERS/view.dm
@@ -1,15 +1,19 @@
/proc/getviewsize(view)
- var/viewX
- var/viewY
if(isnum(view))
var/totalviewrange = (view < 0 ? -1 : 1) + 2 * view
- viewX = totalviewrange
- viewY = totalviewrange
+ return list(totalviewrange, totalviewrange)
else
var/list/viewrangelist = splittext(view,"x")
- viewX = text2num(viewrangelist[1])
- viewY = text2num(viewrangelist[2])
- return list(viewX, viewY)
+ return list(text2num(viewrangelist[1]), text2num(viewrangelist[2]))
+
+/// 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
/proc/in_view_range(mob/user, atom/A)
var/list/view_range = getviewsize(user.client.view)
diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm
index a698706f1a..fffbbc059f 100644
--- a/code/_onclick/drag_drop.dm
+++ b/code/_onclick/drag_drop.dm
@@ -91,31 +91,31 @@
/atom/movable/screen/click_catcher/IsAutoclickable()
. = 1
-//Please don't roast me too hard
-/client/MouseMove(object,location,control,params)
- mouseParams = params
- mouseLocation = location
- mouseObject = object
- mouseControlObject = control
- if(mob)
- SEND_SIGNAL(mob, COMSIG_MOB_CLIENT_MOUSEMOVE, object, location, control, params)
- // god forgive me for i have sinned - used for autoparry. currently at 5 objects.
- moused_over_objects[object] = world.time
- if(moused_over_objects.len > 7)
- moused_over_objects.Cut(1, 2)
- ..()
-
/client/MouseDrag(src_object,atom/over_object,src_location,over_location,src_control,over_control,params)
+ var/list/modifiers = params2list(params)
+ if (LAZYACCESS(modifiers, MIDDLE_CLICK))
+ if (src_object && src_location != over_location)
+ middragtime = world.time
+ middle_drag_atom_ref = WEAKREF(src_object)
+ else
+ middragtime = 0
+ middle_drag_atom_ref = null
mouseParams = params
- mouseLocation = over_location
- mouseObject = over_object
- mouseControlObject = over_control
- if(selected_target[1] && over_object && over_object.IsAutoclickable())
+ mouse_location_ref = WEAKREF(over_location)
+ mouse_object_ref = WEAKREF(over_object)
+ if(selected_target[1] && over_object?.IsAutoclickable())
selected_target[1] = over_object
selected_target[2] = params
if(active_mousedown_item)
active_mousedown_item.onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
SEND_SIGNAL(src, COMSIG_CLIENT_MOUSEDRAG, src_object, over_object, src_location, over_location, src_control, over_control, params)
+ return ..()
/obj/item/proc/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
return
+
+/client/MouseDrop(atom/src_object, atom/over_object, atom/src_location, atom/over_location, src_control, over_control, params)
+ if (IS_WEAKREF_OF(src_object, middle_drag_atom_ref))
+ middragtime = 0
+ middle_drag_atom_ref = null
+ ..()
diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm
deleted file mode 100644
index 233ae17a4d..0000000000
--- a/code/_onclick/hud/_defines.dm
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- These defines specificy screen locations. For more information, see the byond documentation on the screen_loc var.
-
- The short version:
-
- Everything is encoded as strings because apparently that's how Byond rolls.
-
- "1,1" is the bottom left square of the user's screen. This aligns perfectly with the turf grid.
- "1:2,3:4" is the square (1,3) with pixel offsets (+2, +4); slightly right and slightly above the turf grid.
- Pixel offsets are used so you don't perfectly hide the turf under them, that would be crappy.
-
- In addition, the keywords NORTH, SOUTH, EAST, WEST and CENTER can be used to represent their respective
- screen borders. NORTH-1, for example, is the row just below the upper edge. Useful if you want your
- UI to scale with screen size.
-
- The size of the user's screen is defined by client.view (indirectly by world.view), in our case "15x15".
- Therefore, the top right corner (except during admin shenanigans) is at "15,15"
-*/
-
-/proc/ui_hand_position(i) //values based on old hand ui positions (CENTER:-/+16,SOUTH:5)
- var/x_off = -(!(i % 2))
- var/y_off = round((i-1) / 2)
- return"CENTER+[x_off]:16,SOUTH+[y_off]:5"
-
-/proc/ui_equip_position(mob/M)
- var/y_off = round((M.held_items.len-1) / 2) //values based on old equip ui position (CENTER: +/-16,SOUTH+1:5)
- 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((M.held_items.len-1) / 2)
- return "CENTER+[x_off]:16,SOUTH+[y_off+1]:5"
-
-//Lower left, persistent menu
-#define ui_inventory "WEST:6,SOUTH:5"
-
-//Middle left indicators
-#define ui_lingchemdisplay "WEST,CENTER-1:15"
-#define ui_lingstingdisplay "WEST:6,CENTER-3:11"
-
-#define ui_devilsouldisplay "WEST:6,CENTER-1:15"
-
-//Lower center, persistent menu
-#define ui_sstore1 "CENTER-5:10,SOUTH:5"
-#define ui_id "CENTER-4:12,SOUTH:5"
-#define ui_belt "CENTER-3:14,SOUTH:5"
-#define ui_back "CENTER-2:14,SOUTH:5"
-#define ui_storage1 "CENTER+1:18,SOUTH:5"
-#define ui_storage2 "CENTER+2:20,SOUTH:5"
-#define ui_combo "CENTER+4:24,SOUTH+1:7" // combo meter for martial arts
-
-//Lower right, persistent menu
-#define ui_drop_throw "EAST-1:28,SOUTH+1:7"
-#define ui_pull_resist "EAST-2:26,SOUTH+1:7"
-#define ui_movi "EAST-2:26,SOUTH:5"
-#define ui_sprintbufferloc "EAST-2:26,SOUTH:18"
-#define ui_acti "EAST-3:24,SOUTH:5"
-#define ui_zonesel "EAST-1:28,SOUTH:5"
-#define ui_acti_alt "EAST-1:28,SOUTH:5" //alternative intent switcher for when the interface is hidden (F12)
-#define ui_crafting "EAST-5:20,SOUTH:5"//CIT CHANGE - moves this over one tile to accommodate for combat mode toggle
-#define ui_building "EAST-5:20,SOUTH:21"//CIT CHANGE - ditto
-#define ui_language_menu "EAST-5:4,SOUTH:21"//CIT CHANGE - ditto
-#define ui_voremode "EAST-5:20,SOUTH:5"
-
-//Upper-middle right (alerts)
-#define ui_alert1 "EAST-1:28,CENTER+5:27"
-#define ui_alert2 "EAST-1:28,CENTER+4:25"
-#define ui_alert3 "EAST-1:28,CENTER+3:23"
-#define ui_alert4 "EAST-1:28,CENTER+2:21"
-#define ui_alert5 "EAST-1:28,CENTER+1:19"
-
-//Middle right (status indicators)
-#define ui_healthdoll "EAST-1:28,CENTER-2:13"
-#define ui_health "EAST-1:28,CENTER-1:15"
-#define ui_internal "EAST-1:28,CENTER+1:19"//CIT CHANGE - moves internal icon up a little bit to accommodate for the stamina meter
-#define ui_mood "EAST-1:28,CENTER-3:10"
-// #define ui_spacesuit "EAST-1:28,CENTER-4:10"
-
-//Pop-up inventory
-#define ui_shoes "WEST+1:8,SOUTH:5"
-#define ui_iclothing "WEST:6,SOUTH+1:7"
-#define ui_oclothing "WEST+1:8,SOUTH+1:7"
-#define ui_gloves "WEST+2:10,SOUTH+1:7"
-#define ui_glasses "WEST:6,SOUTH+3:11"
-#define ui_mask "WEST+1:8,SOUTH+2:9"
-#define ui_ears "WEST+2:10,SOUTH+2:9"
-#define ui_neck "WEST:6,SOUTH+2:9"
-#define ui_head "WEST+1:8,SOUTH+3:11"
-
-//Generic living
-#define ui_living_pull "EAST-1:28,CENTER-2:15"
-#define ui_living_health "EAST-1:28,CENTER:15"
-#define ui_living_healthdoll "EAST-1:28,CENTER-1:15"
-
-//Monkeys
-#define ui_monkey_head "CENTER-5:13,SOUTH:5"
-#define ui_monkey_mask "CENTER-4:14,SOUTH:5"
-#define ui_monkey_neck "CENTER-3:15,SOUTH:5"
-#define ui_monkey_back "CENTER-2:16,SOUTH:5"
-
-//Drones
-#define ui_drone_drop "CENTER+1:18,SOUTH:5"
-#define ui_drone_pull "CENTER+2:2,SOUTH:5"
-#define ui_drone_storage "CENTER-2:14,SOUTH:5"
-#define ui_drone_head "CENTER-3:14,SOUTH:5"
-
-//Cyborgs
-#define ui_borg_health "EAST-1:28,CENTER-1:15"
-#define ui_borg_pull "EAST-2:26,SOUTH+1:7"
-#define ui_borg_radio "EAST-1:28,SOUTH+1:7"
-#define ui_borg_intents "EAST-2:26,SOUTH:5"
-#define ui_borg_lamp "CENTER-3:16, SOUTH:5"
-#define ui_borg_tablet "CENTER-4:16, SOUTH:5"
-#define ui_inv1 "CENTER-2:16,SOUTH:5"
-#define ui_inv2 "CENTER-1 :16,SOUTH:5"
-#define ui_inv3 "CENTER :16,SOUTH:5"
-#define ui_borg_module "CENTER+1:16,SOUTH:5"
-#define ui_borg_store "CENTER+2:16,SOUTH:5"
-#define ui_borg_camera "CENTER+3:21,SOUTH:5"
-#define ui_borg_alerts "CENTER+4:21,SOUTH:5"
-#define ui_borg_language_menu "CENTER+4:21,SOUTH+1:5"
-#define ui_borg_sensor "CENTER-6:16, SOUTH:5" //LEGACY
-#define ui_borg_thrusters "CENTER-5:16, SOUTH:5" //LEGACY
-
-//Aliens
-#define ui_alien_health "EAST,CENTER-1:15"
-#define ui_alienplasmadisplay "EAST,CENTER-2:15"
-#define ui_alien_queen_finder "EAST,CENTER-3:15"
-#define ui_alien_storage_r "CENTER+1:18,SOUTH:5"
-#define ui_alien_language_menu "EAST-3:26,SOUTH:5"
-
-//Constructs
-#define ui_construct_pull "EAST,CENTER-2:15"
-#define ui_construct_health "EAST,CENTER:15"
-
-//AI
-#define ui_ai_core "BOTTOM:6,RIGHT-4"
-#define ui_ai_shuttle "BOTTOM:6,RIGHT-3"
-#define ui_ai_announcement "BOTTOM:6,RIGHT-2"
-#define ui_ai_state_laws "BOTTOM:6,RIGHT-1"
-#define ui_ai_pda_log "BOTTOM:6,RIGHT"
-#define ui_ai_pda_send "BOTTOM+1:6,RIGHT"
-#define ui_ai_language_menu "BOTTOM+1:8,RIGHT-2:30"
-
-#define ui_ai_crew_monitor "BOTTOM:6,CENTER-1"
-#define ui_ai_crew_manifest "BOTTOM:6,CENTER"
-#define ui_ai_alerts "BOTTOM:6,CENTER+1"
-
-#define ui_ai_view_images "BOTTOM:6,LEFT+4"
-#define ui_ai_camera_list "BOTTOM:6,LEFT+3"
-#define ui_ai_track_with_camera "BOTTOM:6,LEFT+2"
-#define ui_ai_camera_light "BOTTOM:6,LEFT+1"
-#define ui_ai_sensor "BOTTOM:6,LEFT"
-#define ui_ai_multicam "BOTTOM+1:6,LEFT+1"
-#define ui_ai_add_multicam "BOTTOM+1:6,LEFT"
-#define ui_ai_take_picture "BOTTOM+2:6,LEFT"
-
-
-// pAI
-// #define ui_pai_software "SOUTH:6,WEST"
-// #define ui_pai_shell "SOUTH:6,WEST+1"
-// #define ui_pai_chassis "SOUTH:6,WEST+2"
-// #define ui_pai_rest "SOUTH:6,WEST+3"
-// #define ui_pai_light "SOUTH:6,WEST+4"
-// #define ui_pai_newscaster "SOUTH:6,WEST+5"
-// #define ui_pai_host_monitor "SOUTH:6,WEST+6"
-// #define ui_pai_crew_manifest "SOUTH:6,WEST+7"
-// #define ui_pai_state_laws "SOUTH:6,WEST+8"
-// #define ui_pai_pda_send "SOUTH:6,WEST+9"
-// #define ui_pai_pda_log "SOUTH:6,WEST+10"
-// #define ui_pai_take_picture "SOUTH:6,WEST+12"
-// #define ui_pai_view_images "SOUTH:6,WEST+13"
-
-//Ghosts
-#define ui_ghost_jumptomob "SOUTH:6,CENTER-3:24"
-#define ui_ghost_orbit "SOUTH:6,CENTER-2:24"
-#define ui_ghost_reenter_corpse "SOUTH:6,CENTER-1:24"
-#define ui_ghost_teleport "SOUTH:6,CENTER:24"
-#define ui_ghost_pai "SOUTH: 6, CENTER+1:24"
-#define ui_ghost_mafia "SOUTH: 6, CENTER+2:24"
-#define ui_ghost_spawners "SOUTH: 6, CENTER+1:24" // LEGACY. SAME LOC AS PAI
-
-//UI position overrides for 1:1 screen layout. (default is 7:5)
-#define ui_stamina "EAST-1:28,CENTER:17" // replacing internals button
-#define ui_overridden_resist "EAST-3:24,SOUTH+1:7"
-#define ui_clickdelay "CENTER,SOUTH+1:-31"
-#define ui_resistdelay "EAST-3:24,SOUTH+1:4"
-#define ui_combat_toggle "EAST-4:22,SOUTH:5"
-
-#define ui_boxcraft "EAST-4:22,SOUTH+1:6"
-#define ui_boxarea "EAST-4:6,SOUTH+1:6"
-#define ui_boxlang "EAST-5:22,SOUTH+1:6"
-#define ui_boxvore "EAST-5:22,SOUTH+1:6"
-
-#define ui_wanted_lvl "NORTH,11"
diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm
index d97e1f5d04..6b70d8cd45 100644
--- a/code/_onclick/hud/action_button.dm
+++ b/code/_onclick/hud/action_button.dm
@@ -2,186 +2,154 @@
/atom/movable/screen/movable/action_button
var/datum/action/linked_action
+ var/datum/hud/our_hud
var/actiontooltipstyle = ""
screen_loc = null
var/button_icon_state
var/appearance_cache
+ /// 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
- var/ordered = TRUE //If the button gets placed into the default bar
+ /// A weakref of the last thing we hovered over
+ /// God I hate how dragging works
+ var/datum/weakref/last_hovored_ref
+
+/atom/movable/screen/movable/action_button/Destroy()
+ if(our_hud)
+ var/mob/viewer = our_hud.mymob
+ our_hud.hide_action(src)
+ viewer?.client?.screen -= src
+ linked_action.viewers -= our_hud
+ viewer.update_action_buttons()
+ our_hud = null
+ linked_action = null
+ return ..()
/atom/movable/screen/movable/action_button/proc/can_use(mob/user)
if (linked_action)
- return linked_action.owner == user
+ if(linked_action.viewers[user.hud_used])
+ return TRUE
+ return FALSE
else if (isobserver(user))
var/mob/dead/observer/O = user
return !O.observetarget
else
return TRUE
-/atom/movable/screen/movable/action_button/MouseDrop(over_object)
- if(!can_use(usr))
- return
- if((istype(over_object, /atom/movable/screen/movable/action_button) && !istype(over_object, /atom/movable/screen/movable/action_button/hide_toggle)))
- if(locked)
- to_chat(usr, "Action button \"[name]\" is locked, unlock it first.")
- return
- var/atom/movable/screen/movable/action_button/B = over_object
- var/list/actions = usr.actions
- actions.Swap(actions.Find(src.linked_action), actions.Find(B.linked_action))
- moved = FALSE
- ordered = TRUE
- B.moved = FALSE
- B.ordered = TRUE
- usr.update_action_buttons()
- else
- return ..()
-
/atom/movable/screen/movable/action_button/Click(location,control,params)
if (!can_use(usr))
return
var/list/modifiers = params2list(params)
if(modifiers["shift"])
- if(locked)
- to_chat(usr, "Action button \"[name]\" is locked, unlock it first.")
- return TRUE
- moved = 0
- usr.update_action_buttons() //redraw buttons that are no longer considered "moved"
- return TRUE
- if(modifiers["ctrl"])
- locked = !locked
- to_chat(usr, "Action button \"[name]\" [locked ? "" : "un"]locked.")
- if(id && usr.client) //try to (un)remember position
- usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = locked ? moved : null
+ var/datum/hud/our_hud = usr.hud_used
+ our_hud.position_action(src, SCRN_OBJ_DEFAULT)
return TRUE
linked_action.Trigger()
return TRUE
-//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 it in place. Alt-click this button to reset all buttons to their default positions."
- icon = 'icons/mob/actions.dmi'
- icon_state = "bg_default"
- var/hidden = 0
- var/hide_icon = 'icons/mob/actions.dmi'
- var/hide_state = "hide"
- var/show_state = "show"
- var/mutable_appearance/hide_appearance
- var/mutable_appearance/show_appearance
-
-/atom/movable/screen/movable/action_button/hide_toggle/Initialize(mapload)
+// 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)
. = ..()
- var/static/list/icon_cache = list()
+ if(!can_use(usr))
+ return
+ if(IS_WEAKREF_OF(over_object, last_hovored_ref))
+ return
+ var/atom/old_object
+ if(last_hovored_ref)
+ old_object = last_hovored_ref?.resolve()
+ else // If there's no current ref, we assume it was us. We also treat this as our "first go" location
+ old_object = src
+ var/datum/hud/our_hud = usr.hud_used
+ our_hud?.generate_landings(src)
- var/cache_key = "[hide_icon][hide_state]"
- hide_appearance = icon_cache[cache_key]
- if(!hide_appearance)
- hide_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, hide_state)
+ if(old_object)
+ old_object.MouseExited(over_location, over_control, params)
- cache_key = "[hide_icon][show_state]"
- show_appearance = icon_cache[cache_key]
- if(!show_appearance)
- show_appearance = icon_cache[cache_key] = mutable_appearance(hide_icon, show_state)
+ last_hovored_ref = WEAKREF(over_object)
+ over_object.MouseEntered(over_location, over_control, params)
-/atom/movable/screen/movable/action_button/hide_toggle/Click(location,control,params)
- if (!can_use(usr))
+/atom/movable/screen/movable/action_button/MouseEntered(location, control, params)
+ . = ..()
+ if(!QDELETED(src))
+ openToolTip(usr, src, params, title = name, content = desc, theme = actiontooltipstyle)
+
+/atom/movable/screen/movable/action_button/MouseExited(location, control, params)
+ closeToolTip(usr)
+ return ..()
+
+/atom/movable/screen/movable/action_button/MouseDrop(over_object)
+ last_hovored_ref = null
+ if(!can_use(usr))
+ return
+ var/datum/hud/our_hud = usr.hud_used
+ if(over_object == src)
+ our_hud.hide_landings()
+ return
+ if(istype(over_object, /atom/movable/screen/action_landing))
+ var/atom/movable/screen/action_landing/reserve = over_object
+ reserve.hit_by(src)
+ our_hud.hide_landings()
+ save_position()
return
- var/list/modifiers = params2list(params)
- 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)
- return TRUE
- if(modifiers["ctrl"])
- locked = !locked
- to_chat(usr, "Action button \"[name]\" [locked ? "" : "un"]locked.")
- if(id && usr.client) //try to (un)remember position
- usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = locked ? moved : null
- return TRUE
- if(modifiers["alt"])
- for(var/V in usr.actions)
- var/datum/action/A = V
- var/atom/movable/screen/movable/action_button/B = A.button
- B.moved = FALSE
- if(B.id && usr.client)
- usr.client.prefs.action_buttons_screen_locs["[B.name]_[B.id]"] = null
- B.locked = usr.client.prefs.buttons_locked
- locked = usr.client.prefs.buttons_locked
- moved = FALSE
- if(id && usr.client)
- usr.client.prefs.action_buttons_screen_locs["[name]_[id]"] = null
- usr.update_action_buttons(TRUE)
- to_chat(usr, "Action button positions have been reset.")
- 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()
- 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.")
- return TRUE
-
-
-/atom/movable/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(datum/hud/owner_hud)
- var/settings = owner_hud.get_action_buttons_icons()
- icon = settings["bg_icon"]
- icon_state = settings["bg_state"]
- hide_icon = settings["toggle_icon"]
- hide_state = settings["toggle_hide"]
- show_state = settings["toggle_show"]
- update_icon()
-
-/atom/movable/screen/movable/action_button/hide_toggle/update_overlays()
+ our_hud.hide_landings()
+ if(istype(over_object, /atom/movable/screen/button_palette) || istype(over_object, /atom/movable/screen/palette_scroll))
+ our_hud.position_action(src, SCRN_OBJ_IN_PALETTE)
+ save_position()
+ return
+ if(istype(over_object, /atom/movable/screen/movable/action_button))
+ var/atom/movable/screen/movable/action_button/button = over_object
+ our_hud.position_action_relative(src, button)
+ save_position()
+ return
. = ..()
- if(hidden)
- . += show_appearance
- else
- . += hide_appearance
+ our_hud.position_action(src, screen_loc)
+ save_position()
+/atom/movable/screen/movable/action_button/proc/save_position()
+ var/mob/user = our_hud.mymob
+ if(!user?.client)
+ return
+ var/position_info = ""
+ switch(location)
+ if(SCRN_OBJ_FLOATING)
+ position_info = screen_loc
+ if(SCRN_OBJ_IN_LIST)
+ position_info = SCRN_OBJ_IN_LIST
+ if(SCRN_OBJ_IN_PALETTE)
+ position_info = SCRN_OBJ_IN_PALETTE
-/atom/movable/screen/movable/action_button/MouseEntered(location,control,params)
- if(!QDELETED(src))
- openToolTip(usr,src,params,title = name,content = desc,theme = actiontooltipstyle)
+ user.client.prefs.action_buttons_screen_locs["[name]_[id]"] = position_info
+/atom/movable/screen/movable/action_button/proc/load_position()
+ var/mob/user = our_hud.mymob
+ if(!user)
+ return
+ var/position_info = user.client?.prefs?.action_buttons_screen_locs["[name]_[id]"] || SCRN_OBJ_DEFAULT
+ user.hud_used.position_action(src, position_info)
-/atom/movable/screen/movable/action_button/MouseExited()
- closeToolTip(usr)
+/atom/movable/screen/movable/action_button/proc/dump_save()
+ var/mob/user = our_hud.mymob
+ if(!user?.client)
+ return
+ user.client.prefs.action_buttons_screen_locs -= "[name]_[id]"
/datum/hud/proc/get_action_buttons_icons()
. = list()
.["bg_icon"] = ui_style
.["bg_state"] = "template"
- //TODO : Make these fit theme
- .["toggle_icon"] = 'icons/mob/actions.dmi'
- .["toggle_hide"] = "hide"
- .["toggle_show"] = "show"
-
//see human and alien hud for specific implementations.
/mob/proc/update_action_buttons_icon(status_only = FALSE)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon(status_only)
+ A.UpdateButtons(status_only)
//This is the proc used to update all the action buttons.
/mob/proc/update_action_buttons(reload_screen)
@@ -191,56 +159,243 @@
if(hud_used.hud_shown != HUD_STYLE_STANDARD)
return
- var/button_number = 0
- var/list/cview = getviewsize(client.view)
- var/supportedcolumns = cview[1]-2
+ 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
- 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.UpdateButtonIcon()
- var/atom/movable/screen/movable/action_button/B = A.button
- if(B.ordered)
- button_number++
- if(B.moved)
- B.screen_loc = B.moved
- else
- B.screen_loc = hud_used.ButtonNumberToScreenCoords(button_number, supportedcolumns)
- 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, supportedcolumns)
- 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()
-/datum/hud/proc/ButtonNumberToScreenCoords(number, supportedcolumns) // TODO : Make this zero-indexed for readabilty
- var/row = round((number - 1)/supportedcolumns)
- var/col = ((number - 1)%(supportedcolumns)) + 1
+/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"
+ 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_col = "+[col-1]"
- var/coord_col_offset = 2 + 2 * col
+/atom/movable/screen/button_palette/Destroy()
+ if(our_hud)
+ our_hud.mymob?.client?.screen -= src
+ our_hud.toggle_palette = null
+ our_hud = null
+ return ..()
- var/coord_row = "[row ? -row : "+0"]"
+/atom/movable/screen/button_palette/Initialize(mapload)
+ . = ..()
+ update_appearance()
- return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-6"
+/atom/movable/screen/button_palette/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
-/datum/hud/proc/SetButtonCoords(atom/movable/screen/button,number, supportedcolumns)
- var/row = round((number-1)/supportedcolumns)
- var/col = ((number - 1)%(supportedcolumns)) + 1
- var/x_offset = 32*(col-1) + 4 + 2*col
- var/y_offset = -32*(row+1) + 26
+/atom/movable/screen/button_palette/update_name(updates)
+ . = ..()
+ if(expanded)
+ name = "Hide Buttons"
+ else
+ name = "Show Buttons"
- var/matrix/M = matrix()
- M.Translate(x_offset,y_offset)
- button.transform = M
+/atom/movable/screen/button_palette/proc/refresh_owner()
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
+
+ var/list/settings = our_hud.get_action_buttons_icons()
+ var/ui_icon = "[settings["bg_icon"]]"
+ var/list/ui_segments = splittext(ui_icon, ".")
+ var/list/ui_paths = splittext(ui_segments[1], "/")
+ var/ui_name = ui_paths[length(ui_paths)]
+
+ icon_state = "[ui_name]_palette"
+
+/atom/movable/screen/button_palette/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ show_tooltip(params)
+
+/atom/movable/screen/button_palette/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/atom/movable/screen/button_palette/proc/show_tooltip(params)
+ openToolTip(usr, src, params, title = name, content = desc)
+
+GLOBAL_LIST_INIT(palette_added_matrix, list(0.4,0.5,0.2,0, 0,1.4,0,0, 0,0.4,0.6,0, 0,0,0,1, 0,0,0,0))
+GLOBAL_LIST_INIT(palette_removed_matrix, list(1.4,0,0,0, 0.7,0.4,0,0, 0.4,0,0.6,0, 0,0,0,1, 0,0,0,0))
+
+/atom/movable/screen/button_palette/proc/play_item_added()
+ color_for_now(GLOB.palette_added_matrix)
+
+/atom/movable/screen/button_palette/proc/play_item_removed()
+ color_for_now(GLOB.palette_removed_matrix)
+
+/atom/movable/screen/button_palette/proc/color_for_now(list/color)
+ if(color_timer_id)
+ return
+ add_atom_colour(color, TEMPORARY_COLOUR_PRIORITY) //We unfortunately cannot animate matrix colors. Curse you lummy it would be ~~non~~trivial to interpolate between the two valuessssssssss
+ color_timer_id = addtimer(CALLBACK(src, .proc/remove_color, color), 2 SECONDS)
+
+/atom/movable/screen/button_palette/proc/remove_color(list/to_remove)
+ color_timer_id = null
+ remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, to_remove)
+
+/atom/movable/screen/button_palette/proc/can_use(mob/user)
+ if (isobserver(user))
+ var/mob/dead/observer/O = user
+ return !O.observetarget
+ return TRUE
+
+/atom/movable/screen/button_palette/Click(location, control, params)
+ if(!can_use(usr))
+ return
+
+ var/list/modifiers = params2list(params)
+
+ if(LAZYACCESS(modifiers, ALT_CLICK))
+ for(var/datum/action/action as anything in usr.actions) // Reset action positions to default
+ for(var/datum/hud/hud as anything in action.viewers)
+ var/atom/movable/screen/movable/action_button/button = action.viewers[hud]
+ hud.position_action(button, SCRN_OBJ_DEFAULT)
+ to_chat(usr, span_notice("Action button positions have been reset."))
+ return TRUE
+
+ set_expanded(!expanded)
+
+/atom/movable/screen/button_palette/proc/clicked_while_open(datum/source, atom/target, atom/location, control, params, mob/user)
+ if(istype(target, /atom/movable/screen/movable/action_button) || istype(target, /atom/movable/screen/palette_scroll) || target == src) // If you're clicking on an action button, or us, you can live
+ return
+ set_expanded(FALSE)
+ if(source)
+ UnregisterSignal(source, COMSIG_CLIENT_CLICK)
+
+/atom/movable/screen/button_palette/proc/set_expanded(new_expanded)
+ var/datum/action_group/our_group = our_hud.palette_actions
+ if(!length(our_group.actions)) //Looks dumb, trust me lad
+ new_expanded = FALSE
+ if(expanded == new_expanded)
+ return
+
+ expanded = new_expanded
+ our_group.refresh_actions()
+ update_appearance()
+
+ if(!usr.client)
+ return
+
+ if(expanded)
+ RegisterSignal(usr.client, COMSIG_CLIENT_CLICK, .proc/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)
+ if (isobserver(user))
+ var/mob/dead/observer/O = user
+ return !O.observetarget
+ return TRUE
+
+/atom/movable/screen/palette_scroll/proc/set_hud(datum/hud/our_hud)
+ src.our_hud = our_hud
+ refresh_owner()
+
+/atom/movable/screen/palette_scroll/proc/refresh_owner()
+ var/mob/viewer = our_hud.mymob
+ if(viewer.client)
+ viewer.client.screen |= src
+
+ var/list/settings = our_hud.get_action_buttons_icons()
+ icon = settings["bg_icon"]
+
+/atom/movable/screen/palette_scroll/Click(location, control, params)
+ if(!can_use(usr))
+ return
+ our_hud.palette_actions.scroll(scroll_direction)
+
+/atom/movable/screen/palette_scroll/MouseEntered(location, control, params)
+ . = ..()
+ if(QDELETED(src))
+ return
+ openToolTip(usr, src, params, title = name, content = desc)
+
+/atom/movable/screen/palette_scroll/MouseExited()
+ closeToolTip(usr)
+ return ..()
+
+/atom/movable/screen/palette_scroll/down
+ name = "Scroll Down"
+ desc = "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)
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 9d762332c6..740174138d 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -73,8 +73,13 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
// We don't actually do proccalls really yet, so let's grab at prefs
- var/atom/movable/screen/movable/action_button/hide_toggle/hide_actions_toggle
- var/action_buttons_hidden = FALSE
+ 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
+
+ var/datum/action_group/palette/palette_actions
+ var/datum/action_group/listed/listed_actions
+ var/list/floating_actions
var/atom/movable/screen/healths
var/atom/movable/screen/healthdoll
@@ -91,10 +96,12 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
// will fall back to the default if any of these are null
ui_style = ui_style2icon(owner.client && owner.client.prefs && owner.client.prefs.UI_style)
- hide_actions_toggle = new
- hide_actions_toggle.InitialiseIcon(src)
- if(mymob.client)
- hide_actions_toggle.locked = mymob.client.prefs.buttons_locked
+ toggle_palette = new()
+ toggle_palette.set_hud(src)
+ palette_down = new()
+ palette_down.set_hud(src)
+ palette_up = new()
+ palette_up.set_hud(src)
hand_slots = list()
@@ -110,7 +117,13 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
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(floating_actions)
+
QDEL_NULL(module_store_icon)
QDEL_LIST(static_inventory)
@@ -148,10 +161,14 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/mob/proc/create_mob_hud()
if(!client || hud_used)
return
- hud_used = new hud_type(src)
+ 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()
+
//Version denotes which style should be displayed. blank or 0 means "next version"
/datum/hud/proc/show_hud(version = 0, mob/viewmob)
if(!ismob(mymob))
@@ -160,6 +177,8 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if(!screenmob.client)
return FALSE
+ // This code is the absolute fucking worst, I want it to go die in a fire
+ // Seriously, why
screenmob.client.screen = list()
screenmob.client.update_clickcatcher()
@@ -181,7 +200,7 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
if(infodisplay.len)
screenmob.client.screen += infodisplay
- screenmob.client.screen += hide_actions_toggle
+ screenmob.client.screen += toggle_palette
if(action_intent)
action_intent.screen_loc = initial(action_intent.screen_loc) //Restore intent selection to the original position
@@ -272,7 +291,6 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
ui_style = new_ui_style
build_hand_slots()
- hide_actions_toggle.InitialiseIcon(src)
//Triggered when F12 is pressed (Unless someone changed something in the DMF)
/mob/verb/button_pressed_F12()
@@ -320,3 +338,306 @@ GLOBAL_LIST_INIT(available_ui_styles, list(
/datum/hud/proc/update_locked_slots()
return
+
+/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
+ button.dump_save() // Nuke any existing saves
+ position_action(button, button.linked_action.default_button_position)
+ return
+ if(SCRN_OBJ_IN_LIST)
+ listed_actions.insert_action(button)
+ if(SCRN_OBJ_IN_PALETTE)
+ palette_actions.insert_action(button)
+ else // If we don't have it as a define, this is a screen_loc, and we should be floating
+ floating_actions += button
+ button.screen_loc = position
+ position = SCRN_OBJ_FLOATING
+
+ button.location = position
+
+/datum/hud/proc/position_action_relative(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
+ button.screen_loc = get_valid_screen_location(relative_to.screen_loc, world.icon_size, our_client.view_size.getView()) // 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)
+
+/datum/action_group
+ /// The hud we're owned by
+ var/datum/hud/owner
+ /// The actions 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
+ /// 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(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)
+ return "WEST[coord_col]:[coord_col_offset],NORTH[coord_row]:-[pixel_north_offset]"
+
+/datum/action_group/proc/check_against_view()
+ var/owner_view = owner?.mymob?.client?.view
+ if(!owner_view)
+ return
+ // Unlikey as it is, we may have been changed. Want to start from our target position and fail down
+ column_max = initial(column_max)
+ // Convert our viewer's view var into a workable offset
+ var/list/view_size = view_to_pixels(owner_view)
+
+ // We're primarially concerned about width here, if someone makes us 1x2000 I wish them a swift and watery death
+ var/furthest_screen_loc = ButtonNumberToScreenCoords(column_max - 1)
+ var/list/offsets = screen_loc_to_offset(furthest_screen_loc, owner_view)
+ if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2]) // We're all good
+ return
+
+ for(column_max in column_max - 1 to 1 step -1) // Yes I could do this by unwrapping ButtonNumberToScreenCoords, but I don't feel like it
+ var/tested_screen_loc = ButtonNumberToScreenCoords(column_max)
+ offsets = screen_loc_to_offset(tested_screen_loc, owner_view)
+ // We've found a valid max length, pack it in
+ if(offsets[1] > world.icon_size && offsets[1] < view_size[1] && offsets[2] > world.icon_size && offsets[2] < view_size[2])
+ break
+ // Use our newly resized column max
+ refresh_actions()
+
+/// Returns the amount of objects we're storing at the moment
+/datum/action_group/proc/size()
+ var/amount = length(actions)
+ if(landing)
+ amount += 1
+ return amount
+
+/datum/action_group/proc/index_of(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/insert_action(atom/movable/screen/action, index)
+ . = ..()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ palette.play_item_added()
+
+/datum/action_group/palette/remove_action(atom/movable/screen/action)
+ . = ..()
+ var/atom/movable/screen/button_palette/palette = owner.toggle_palette
+ palette.play_item_removed()
+ if(!length(actions))
+ palette.set_expanded(FALSE)
+
+/datum/action_group/palette/refresh_actions()
+ var/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/movable_screen_objects.dm b/code/_onclick/hud/movable_screen_objects.dm
index a00687e263..6c9f852ef5 100644
--- a/code/_onclick/hud/movable_screen_objects.dm
+++ b/code/_onclick/hud/movable_screen_objects.dm
@@ -9,9 +9,8 @@
//Not tied to the grid, places it's center where the cursor is
/atom/movable/screen/movable
+ mouse_drag_pointer = 'icons/effects/mouse_pointers/screen_drag.dmi'
var/snap2grid = FALSE
- var/moved = FALSE
- var/locked = FALSE
var/x_off = -16
var/y_off = -16
@@ -21,35 +20,29 @@
/atom/movable/screen/movable/snap
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/screentip.dm b/code/_onclick/hud/screentip.dm
index 5128448561..b9b27072b3 100644
--- a/code/_onclick/hud/screentip.dm
+++ b/code/_onclick/hud/screentip.dm
@@ -17,4 +17,4 @@
SIGNAL_HANDLER
if(!hud || !hud.mymob.client.view_size) //Might not have been initialized by now
return
- maptext_width = getviewsize(hud.mymob.client.view_size.getView())[1] * world.icon_size
+ maptext_width = view_to_pixels(hud.mymob.client.view_size.getView())[1]
diff --git a/code/controllers/subsystem/augury.dm b/code/controllers/subsystem/augury.dm
index 53c86004a6..f7a7c66718 100644
--- a/code/controllers/subsystem/augury.dm
+++ b/code/controllers/subsystem/augury.dm
@@ -69,15 +69,15 @@ SUBSYSTEM_DEF(augury)
SSaugury.watchers += owner
to_chat(owner, "You are now auto-following debris.")
active = TRUE
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/augury/Deactivate()
SSaugury.watchers -= owner
to_chat(owner, "You are no longer auto-following debris.")
active = FALSE
- UpdateButtonIcon()
+ UpdateButtons()
-/datum/action/innate/augury/UpdateButtonIcon(status_only = FALSE, force)
+/datum/action/innate/augury/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force)
..()
if(active)
button.icon_state = "template_active"
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 2d6af6c2de..29c4fa90da 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -11,9 +11,11 @@
var/check_flags = 0
var/required_mobility_flags = MOBILITY_USE
var/processing = FALSE
- var/atom/movable/screen/movable/action_button/button = null
var/buttontooltipstyle = ""
var/transparent_when_unavailable = TRUE
+ /// Where any buttons we create should be by default. Accepts screen_loc and location defines
+ var/default_button_position = SCRN_OBJ_IN_LIST
+
var/use_target_appearance = FALSE
var/list/target_appearance_matrix //if set, will be used to transform the target button appearance as an arglist.
@@ -28,12 +30,6 @@
/datum/action/New(Target)
link_to(Target)
- button = new
- button.linked_action = src
- button.name = name
- button.actiontooltipstyle = buttontooltipstyle
- if(desc)
- button.desc = desc
/datum/action/proc/link_to(Target)
target = Target
@@ -43,51 +39,42 @@
if(owner)
Remove(owner)
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)
- if(owner)
- if(owner == M)
- return
- Remove(owner)
- owner = M
-
- //button id generation
- var/counter = 0
- var/bitfield = 0
- for(var/datum/action/A in M.actions)
- if(A.name == name && A.button.id)
- counter += 1
- bitfield |= A.button.id
- bitfield = ~bitfield
- var/bitflag = 1
- for(var/i in 1 to (counter + 1))
- if(bitfield & bitflag)
- button.id = bitflag
- break
- bitflag *= 2
-
- M.actions += src
- if(M.client)
- M.client.screen += button
- button.locked = M.client.prefs.buttons_locked || button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE //even if it's not defaultly locked we should remember we locked it before
- button.moved = button.id ? M.client.prefs.action_buttons_screen_locs["[name]_[button.id]"] : FALSE
- M.update_action_buttons()
- else
+ if(!M)
Remove(owner)
+ return
+ if(owner)
+ if(owner == M)
+ return
+ Remove(owner)
+ owner = M
+ RegisterSignal(owner, COMSIG_PARENT_QDELETING, .proc/clear_ref, override = TRUE)
+
+ GiveAction(M)
+
+/datum/action/proc/clear_ref(datum/ref)
+ SIGNAL_HANDLER
+ if(ref == owner)
+ Remove(owner)
+ if(ref == target)
+ qdel(src)
/datum/action/proc/Remove(mob/M)
- if(M)
- if(M.client)
- M.client.screen -= button
- M.actions -= src
- M.update_action_buttons()
- owner = null
- button.moved = FALSE //so the button appears in its normal position when given to another owner.
- button.locked = FALSE
- button.id = null
+ for(var/datum/hud/hud in viewers)
+ if(!hud.mymob)
+ continue
+ HideFrom(hud.mymob)
+ LAZYREMOVE(M.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared
+ viewers = list()
+
+ if(owner)
+ UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
+ if(target == owner)
+ RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/clear_ref)
+ owner = null
/datum/action/proc/Trigger()
if(!IsAvailable())
@@ -125,15 +112,15 @@
/datum/action/proc/UpdateButtons(status_only, force)
for(var/datum/hud/hud in viewers)
var/atom/movable/screen/movable/button = viewers[hud]
- UpdateButtonIcon(button, status_only, force)
+ UpdateButton(button, status_only, force)
-/datum/action/proc/UpdateButtonIcon(status_only = FALSE, force = FALSE)
+/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 && owner.hud_used && background_icon_state == ACTION_BUTTON_DEFAULT_BACKGROUND)
+ 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"]
@@ -145,25 +132,13 @@
if(button.icon_state != background_icon_state)
button.icon_state = background_icon_state
- if(!use_target_appearance)
- ApplyIcon(button, force)
+ ApplyIcon(button, force)
- else if(target && button.appearance_cache != target.appearance) //replace with /ref comparison if this is not valid.
- var/mutable_appearance/M = new(target)
- M.layer = FLOAT_LAYER
- M.plane = FLOAT_PLANE
- if(target_appearance_matrix)
- var/list/L = target_appearance_matrix
- M.transform = matrix(L[1], L[2], L[3], L[4], L[5], L[6])
- button.cut_overlays()
- button.add_overlay(M)
- button.appearance_cache = target.appearance
-
- if(!IsAvailable(TRUE))
+ if(!IsAvailable())
button.color = transparent_when_unavailable ? rgb(128,0,0,128) : rgb(128,0,0)
else
button.color = rgb(255,255,255,255)
- return 1
+ return TRUE
/datum/action/proc/ApplyIcon(atom/movable/screen/movable/action_button/current_button, force = FALSE)
if(icon_icon && button_icon_state && ((current_button.button_icon_state != button_icon_state) || force))
@@ -184,7 +159,67 @@
M.ghostize(can_reenter_corpse = TRUE, voluntary = TRUE)
/datum/action/proc/OnUpdatedIcon()
- addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 1) //Hopefully runs after new icon overlays have been compiled.
+ SIGNAL_HANDLER
+ UpdateButtons()
+
+//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)
+ qdel(button)
+
+/datum/action/proc/CreateButton()
+ var/atom/movable/screen/movable/action_button/button = new()
+ button.linked_action = src
+ button.actiontooltipstyle = buttontooltipstyle
+ if(desc)
+ button.desc = desc
+ 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
//Presets for item actions
/datum/action/item_action
@@ -258,12 +293,14 @@
/datum/action/item_action/set_internals
name = "Set Internals"
-/datum/action/item_action/set_internals/UpdateButtonIcon(status_only = FALSE, force)
- if(..()) //button available
- if(iscarbon(owner))
- var/mob/living/carbon/C = owner
- if(target == C.internal)
- button.icon_state = "template_active"
+/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/pick_color
name = "Choose A Color"
@@ -309,9 +346,9 @@
/datum/action/item_action/toggle_unfriendly_fire/Trigger()
if(..())
- UpdateButtonIcon()
+ UpdateButtons()
-/datum/action/item_action/toggle_unfriendly_fire/UpdateButtonIcon(status_only = FALSE, force)
+/datum/action/item_action/toggle_unfriendly_fire/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force)
if(istype(target, /obj/item/hierophant_club))
var/obj/item/hierophant_club/H = target
if(H.friendly_fire_check)
@@ -380,7 +417,6 @@
/datum/action/item_action/toggle/New(Target)
..()
name = "Toggle [target.name]"
- button.name = name
/datum/action/item_action/halt
name = "HALT!"
@@ -409,7 +445,6 @@
/datum/action/item_action/adjust/New(Target)
..()
name = "Adjust [target.name]"
- button.name = name
/datum/action/item_action/switch_hud
name = "Switch HUD"
@@ -495,12 +530,10 @@
/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/cult_dagger
name = "Draw Blood Rune"
@@ -509,14 +542,13 @@
button_icon_state = "draw"
buttontooltipstyle = "cult"
background_icon_state = "bg_demon"
+ default_button_position = "6:157,4:-2"
/datum/action/item_action/cult_dagger/Grant(mob/M)
- if(iscultist(M))
- ..()
- button.screen_loc = "6:157,4:-2"
- button.moved = "6:157,4:-2"
- else
+ if(!iscultist(M))
Remove(owner)
+ return
+ return ..()
/datum/action/item_action/cult_dagger/Trigger()
for(var/obj/item/H in owner.held_items) //In case we were already holding another dagger
@@ -643,7 +675,6 @@
icon_icon = S.action_icon
button_icon_state = S.action_icon_state
background_icon_state = S.action_background_icon_state
- button.name = name
/datum/action/spell_action/Destroy()
var/obj/effect/proc_holder/S = target
@@ -711,44 +742,114 @@
/datum/action/cooldown
check_flags = 0
transparent_when_unavailable = FALSE
+ // The default cooldown applied when StartCooldown() is called
var/cooldown_time = 0
+ // The actual next time this ability can be used
var/next_use_time = 0
+ // Whether or not you want the cooldown for the ability to display in text form
+ var/text_cooldown = TRUE
+ // Setting for intercepting clicks before activating the ability
+ var/click_to_activate = FALSE
+ // Shares cooldowns with other cooldown abilities of the same value, not active if null
+ var/shared_cooldown
-/datum/action/cooldown/New()
- ..()
+/datum/action/cooldown/CreateButton()
+ var/atom/movable/screen/movable/action_button/button = ..()
button.maptext = ""
button.maptext_x = 8
button.maptext_y = 0
button.maptext_width = 24
button.maptext_height = 12
+ return button
-/datum/action/cooldown/IsAvailable(silent = FALSE)
- return next_use_time <= world.time
+/datum/action/cooldown/IsAvailable()
+ return ..() && (next_use_time <= world.time)
-/datum/action/cooldown/proc/StartCooldown()
- next_use_time = world.time + cooldown_time
- button.maptext = MAPTEXT_TINY_UNICODE("[round(cooldown_time/10, 0.1)]")
- UpdateButtonIcon()
+/// Starts a cooldown time to be shared with similar abilities, will use default cooldown time if an override is not specified
+/datum/action/cooldown/proc/StartCooldown(override_cooldown_time)
+ if(shared_cooldown)
+ for(var/datum/action/cooldown/shared_ability in owner.actions - src)
+ if(shared_cooldown == shared_ability.shared_cooldown)
+ if(isnum(override_cooldown_time))
+ shared_ability.StartCooldownSelf(override_cooldown_time)
+ else
+ shared_ability.StartCooldownSelf(cooldown_time)
+ StartCooldownSelf(override_cooldown_time)
+
+/// Starts a cooldown time for this ability only, will use default cooldown time if an override is not specified
+/datum/action/cooldown/proc/StartCooldownSelf(override_cooldown_time)
+ if(isnum(override_cooldown_time))
+ next_use_time = world.time + override_cooldown_time
+ else
+ next_use_time = world.time + cooldown_time
+ UpdateButtons()
START_PROCESSING(SSfastprocess, src)
-/datum/action/cooldown/process()
+/datum/action/cooldown/Trigger(trigger_flags, atom/target)
+ . = ..()
+ if(!.)
+ return
if(!owner)
+ return FALSE
+ if(click_to_activate)
+ if(target)
+ // For automatic / mob handling
+ return InterceptClickOn(owner, null, target)
+ if(owner.click_intercept == src)
+ owner.click_intercept = null
+ else
+ owner.click_intercept = src
+ for(var/datum/action/cooldown/ability in owner.actions)
+ ability.UpdateButtons()
+ return TRUE
+ return PreActivate(owner)
+
+/// Intercepts client owner clicks to activate the ability
+/datum/action/cooldown/proc/InterceptClickOn(mob/living/caller, params, atom/target)
+ if(!IsAvailable())
+ return FALSE
+ if(!target)
+ return FALSE
+ PreActivate(target)
+ caller.click_intercept = null
+ return TRUE
+
+/// For signal calling
+/datum/action/cooldown/proc/PreActivate(atom/target)
+ if(SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_STARTED, src) & COMPONENT_BLOCK_ABILITY_START)
+ return
+ . = Activate(target)
+ SEND_SIGNAL(owner, COMSIG_MOB_ABILITY_FINISHED, src)
+
+/// To be implemented by subtypes
+/datum/action/cooldown/proc/Activate(atom/target)
+ return
+
+/datum/action/cooldown/UpdateButton(atom/movable/screen/movable/action_button/button, status_only = FALSE, force = FALSE)
+ . = ..()
+ if(!button)
+ return
+ var/time_left = max(next_use_time - world.time, 0)
+ if(text_cooldown)
+ button.maptext = MAPTEXT("[round(time_left/10, 0.1)]")
+ if(!owner || time_left == 0)
button.maptext = ""
+ if(IsAvailable() && owner.click_intercept == src)
+ button.color = COLOR_GREEN
+
+/datum/action/cooldown/process()
+ var/time_left = max(next_use_time - world.time, 0)
+ if(!owner || time_left == 0)
STOP_PROCESSING(SSfastprocess, src)
- var/timeleft = max(next_use_time - world.time, 0)
- if(timeleft == 0)
- button.maptext = ""
- UpdateButtonIcon()
- STOP_PROCESSING(SSfastprocess, src)
- else
- button.maptext = MAPTEXT_TINY_UNICODE("[round(cooldown_time/10, 0.1)]")
+ UpdateButtons()
/datum/action/cooldown/Grant(mob/M)
..()
- if(owner)
- UpdateButtonIcon()
- if(next_use_time > world.time)
- START_PROCESSING(SSfastprocess, src)
+ if(!owner)
+ return
+ UpdateButtons()
+ if(next_use_time > world.time)
+ START_PROCESSING(SSfastprocess, src)
//surf_ss13
/datum/action/item_action/bhop
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index f35389f171..c7e0522128 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -225,7 +225,7 @@
name = "Hide"
desc = "Hide yourself from your owner's sight."
button_icon_state = "hide"
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/imaginary_hide/Activate()
var/mob/camera/imaginary_friend/I = owner
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 00fbfc4f24..9b144e74bf 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -1661,7 +1661,7 @@ GLOBAL_LIST(objective_choices)
if(istype(S, type))
continue
S.charge_counter = delay
- S.updateButtonIcon()
+ S.UpdateButton()
INVOKE_ASYNC(S, /obj/effect/proc_holder/spell.proc/start_recharge)
/datum/mind/proc/get_ghost(even_if_they_cant_reenter)
diff --git a/code/datums/view.dm b/code/datums/view.dm
index 5610fb040e..7313cc4c18 100644
--- a/code/datums/view.dm
+++ b/code/datums/view.dm
@@ -1,108 +1,110 @@
//This is intended to be a full wrapper. DO NOT directly modify its values
///Container for client viewsize
-/datum/viewData
+/datum/view_data
var/width = 0
var/height = 0
var/default = ""
var/is_suppressed = FALSE
var/client/chief = null
-/datum/viewData/New(client/owner, view_string)
+/datum/view_data/New(client/owner, view_string)
default = view_string
chief = owner
apply()
-/datum/viewData/proc/setDefault(string)
+/datum/view_data/proc/setDefault(string)
default = string
apply()
-/datum/viewData/proc/safeApplyFormat()
+/datum/view_data/proc/afterViewChange()
if(isZooming())
assertFormat()
- return
- resetFormat()
+ else
+ resetFormat()
+ var/datum/hud/our_hud = chief?.mob?.hud_used
+ our_hud.view_audit_buttons() // Make sure our hud's buttons are in our new size
-/datum/viewData/proc/assertFormat()//T-Pose
+/datum/view_data/proc/assertFormat()//T-Pose
// winset(chief, "mapwindow.map", "zoom=0")
// Citadel Edit - We're using icon dropdown instead
-/datum/viewData/proc/resetFormat()//Cuck
+/datum/view_data/proc/resetFormat()//Cuck
// winset(chief, "mapwindow.map", "zoom=[chief.prefs.pixel_size]")
// Citadel Edit - We're using icon dropdown instead
-/datum/viewData/proc/setZoomMode()
+/datum/view_data/proc/setZoomMode()
// winset(chief, "mapwindow.map", "zoom-mode=[chief.prefs.scaling_method]")
// Citadel Edit - We're using icon dropdown instead
-/datum/viewData/proc/isZooming()
+/datum/view_data/proc/isZooming()
return (width || height)
-/datum/viewData/proc/resetToDefault()
+/datum/view_data/proc/resetToDefault()
width = 0
height = 0
apply()
-/datum/viewData/proc/add(toAdd)
+/datum/view_data/proc/add(toAdd)
width += toAdd
height += toAdd
apply()
-/datum/viewData/proc/addTo(toAdd)
+/datum/view_data/proc/addTo(toAdd)
var/list/shitcode = getviewsize(toAdd)
width += shitcode[1]
height += shitcode[2]
apply()
-/datum/viewData/proc/setTo(toAdd)
+/datum/view_data/proc/setTo(toAdd)
var/list/shitcode = getviewsize(toAdd) //Backward compatability to account
width = shitcode[1] //for a change in how sizes get calculated. we used to include world.view in
height = shitcode[2] //this, but it was jank, so I had to move it
apply()
-/datum/viewData/proc/setBoth(wid, hei)
+/datum/view_data/proc/setBoth(wid, hei)
width = wid
height = hei
apply()
-/datum/viewData/proc/setWidth(wid)
+/datum/view_data/proc/setWidth(wid)
width = wid
apply()
-/datum/viewData/proc/setHeight(hei)
+/datum/view_data/proc/setHeight(hei)
width = hei
apply()
-/datum/viewData/proc/addToWidth(toAdd)
+/datum/view_data/proc/addToWidth(toAdd)
width += toAdd
apply()
-/datum/viewData/proc/addToHeight(screen, toAdd)
+/datum/view_data/proc/addToHeight(screen, toAdd)
height += toAdd
apply()
-/datum/viewData/proc/apply()
+/datum/view_data/proc/apply()
chief.change_view(getView())
- safeApplyFormat()
+ afterViewChange()
-/datum/viewData/proc/supress()
+/datum/view_data/proc/supress()
is_suppressed = TRUE
apply()
-/datum/viewData/proc/unsupress()
+/datum/view_data/proc/unsupress()
is_suppressed = FALSE
apply()
-/datum/viewData/proc/getView()
+/datum/view_data/proc/getView()
var/list/temp = getviewsize(default)
if(is_suppressed)
return "[temp[1]]x[temp[2]]"
return "[width + temp[1]]x[height + temp[2]]"
-/datum/viewData/proc/zoomIn()
+/datum/view_data/proc/zoomIn()
resetToDefault()
animate(chief, pixel_x = 0, pixel_y = 0, 0, FALSE, LINEAR_EASING, ANIMATION_END_NOW)
-/datum/viewData/proc/zoomOut(radius = 0, offset = 0, direction = FALSE)
+/datum/view_data/proc/zoomOut(radius = 0, offset = 0, direction = FALSE)
if(direction)
var/_x = 0
var/_y = 0
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 4440b3f91a..ea3531dcd0 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1283,7 +1283,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb
*/
/obj/item/proc/update_action_buttons(status_only = FALSE, force = FALSE)
for(var/datum/action/current_action as anything in actions)
- current_action.UpdateButtonIcon(status_only, force)
+ current_action.UpdateButtons(status_only, force)
/// Special stuff you want to do when an outfit equips this item.
/obj/item/proc/on_outfit_equip(mob/living/carbon/human/outfit_wearer, visuals_only, item_slot)
diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm
index 880358a2c1..6873b59146 100644
--- a/code/game/objects/items/defib.dm
+++ b/code/game/objects/items/defib.dm
@@ -173,7 +173,7 @@
update_power()
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
/obj/item/defibrillator/proc/make_paddles()
if(!combat)
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index 79489c3c51..3f64be4729 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -40,7 +40,7 @@
playsound(src, on ? 'sound/weapons/magin.ogg' : 'sound/weapons/magout.ogg', 40, TRUE)
for(var/X in actions)
var/datum/action/A = X
- A.UpdateButtonIcon()
+ A.UpdateButtons()
return TRUE
/obj/item/flashlight/DoRevenantThrowEffects(atom/target)
diff --git a/code/game/objects/items/granters.dm b/code/game/objects/items/granters.dm
index f83d01f4ef..801befc9c6 100644
--- a/code/game/objects/items/granters.dm
+++ b/code/game/objects/items/granters.dm
@@ -129,12 +129,12 @@
/datum/action/innate/drink_fling/Activate()
button_icon_state = "drinkfling_on"
active = TRUE
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/drink_fling/Deactivate()
button_icon_state = "drinkfling_off"
active = FALSE
- UpdateButtonIcon()
+ UpdateButtons()
/obj/item/book/granter/action/origami
granted_action = /datum/action/innate/origami
@@ -155,13 +155,13 @@
to_chat(owner, "You will now fold origami planes.")
button_icon_state = "origami_on"
active = TRUE
- UpdateButtonIcon()
+ UpdateButtons()
/datum/action/innate/origami/Deactivate()
to_chat(owner, "You will no longer fold origami planes.")
button_icon_state = "origami_off"
active = FALSE
- UpdateButtonIcon()
+ UpdateButtons()
///SPELLS///
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 173b420710..d8730cc95b 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -719,6 +719,6 @@
user.vtec = initial(user.vtec) - maxReduction * 1
action.button_icon_state = "Chevron_State_[currentState]"
- action.UpdateButtonIcon()
+ action.UpdateButtons()
return TRUE
diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm
index 25e2f1a292..92e46836b0 100644
--- a/code/game/objects/items/tanks/jetpack.dm
+++ b/code/game/objects/items/tanks/jetpack.dm
@@ -50,7 +50,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)
on = TRUE
diff --git a/code/game/objects/structures/manned_turret.dm b/code/game/objects/structures/manned_turret.dm
index 3f1cb5f4c1..639180356d 100644
--- a/code/game/objects/structures/manned_turret.dm
+++ b/code/game/objects/structures/manned_turret.dm
@@ -80,7 +80,7 @@
return FALSE
var/client/C = controller.client
if(C)
- var/atom/A = C.mouseObject
+ var/atom/A = WEAKREF(C.mouse_object_ref)
var/turf/T = get_turf(A)
if(istype(T)) //They're hovering over something in the map.
direction_track(controller, T)
diff --git a/code/modules/antagonists/abductor/equipment/abduction_gear.dm b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
index 343faccad6..293b5bac4b 100644
--- a/code/modules/antagonists/abductor/equipment/abduction_gear.dm
+++ b/code/modules/antagonists/abductor/equipment/abduction_gear.dm
@@ -59,7 +59,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, datum/action/A)
if(slot == ITEM_SLOT_OCLOTHING) //we only give the mob the ability to activate the vest if he's actually wearing it.
diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm
index c5f1cfbb40..a3b7fd4c0f 100644
--- a/code/modules/antagonists/bloodsucker/bloodsucker_life.dm
+++ b/code/modules/antagonists/bloodsucker/bloodsucker_life.dm
@@ -216,7 +216,7 @@
return
// Disable Powers: Masquerade * NOTE * This should happen as a FLAW!
//if (stat >= UNCONSCIOUS)
- // for (var/datum/action/bloodsucker/masquerade/P in powers)
+ // for (var/datum/action/cooldown/bloodsucker/masquerade/P in powers)
// P.Deactivate()
// TEMP DEATH
var/total_brute = owner.current.getBruteLoss_nonProsthetic()
@@ -252,7 +252,7 @@
owner.current.update_sight()
owner.current.reload_fullscreen()
// Disable ALL Powers
- for(var/datum/action/bloodsucker/power in powers)
+ for(var/datum/action/cooldown/bloodsucker/power in powers)
if(power.active && !power.can_use_in_torpor)
power.DeactivatePower()
if(owner.current.suiciding)
diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_powers.dm b/code/modules/antagonists/bloodsucker/bloodsucker_powers.dm
index 0a799021e9..019bbe6fbd 100644
--- a/code/modules/antagonists/bloodsucker/bloodsucker_powers.dm
+++ b/code/modules/antagonists/bloodsucker/bloodsucker_powers.dm
@@ -1,22 +1,26 @@
-/datum/action/bloodsucker
+/datum/action/cooldown/bloodsucker
name = "Vampiric Gift"
desc = "A vampiric gift."
button_icon = 'icons/mob/actions/bloodsucker.dmi' //This is the file for the BACKGROUND icon
background_icon_state = "vamp_power_off" //And this is the state for the background icon
- var/background_icon_state_on = "vamp_power_on" // FULP: Our "ON" icon alternative.
- var/background_icon_state_off = "vamp_power_off" // FULP: Our "OFF" icon alternative.
icon_icon = 'icons/mob/actions/bloodsucker.dmi' //This is the file for the ACTION icon
button_icon_state = "power_feed" //And this is the state for the action icon
buttontooltipstyle = "cult"
+ transparent_when_unavailable = TRUE
+
+ /// Cooldown you'll have to wait between each use, decreases depending on level.
+ cooldown_time = 2 SECONDS
+
+ ///Background icon when the Power is active.
+ var/background_icon_state_on = "vamp_power_on"
+ ///Background icon when the Power is NOT active.
+ var/background_icon_state_off = "vamp_power_off"
// Action-Related
- //var/amPassive = FALSE // REMOVED: Just made it its own kind. // Am I just "on" at all times? (aka NO ICON)
var/amTargetted = FALSE // Am I asked to choose a target when enabled? (Shows as toggled ON when armed)
var/amToggle = FALSE // Can I be actively turned on and off?
var/amSingleUse = FALSE // Am I removed after a single use?
var/active = FALSE
- var/cooldown = 20 // 10 ticks, 1 second.
- var/cooldownUntil = 0 // From action.dm: next_use_time = world.time + cooldown_time
// Power-Related
var/level_current = 0 // Can increase to yield new abilities. Each power goes up in strength each Rank.
//var/level_max = 1 //
@@ -32,7 +36,7 @@
//var/not_bloodsucker = FALSE // This goes to Vassals or Hunters, but NOT bloodsuckers.
var/must_be_concious = TRUE //Can't use this ability while unconcious.
-/datum/action/bloodsucker/New()
+/datum/action/cooldown/bloodsucker/New()
if(bloodcost > 0)
desc += "
COST: [bloodcost] Blood" // Modify description to add cost.
if(warn_constant_cost)
@@ -46,7 +50,7 @@
// click.dm <--- Where we can take over mouse clicks
// spells.dm /add_ranged_ability() <--- How we take over the mouse click to use a power on a target.
-/datum/action/bloodsucker/Trigger()
+/datum/action/cooldown/bloodsucker/Trigger()
// Active? DEACTIVATE AND END!
if(active && CheckCanDeactivate(TRUE))
DeactivatePower()
@@ -56,7 +60,7 @@
PayCost()
if(amToggle)
active = !active
- UpdateButtonIcon()
+ UpdateButtons()
if(!amToggle || !active)
StartCooldown() // Must come AFTER UpdateButton(), otherwise icon will revert.
ActivatePower() // NOTE: ActivatePower() freezes this power in place until it ends.
@@ -65,13 +69,13 @@
if(amSingleUse)
RemoveAfterUse()
-/datum/action/bloodsucker/proc/CheckCanPayCost(display_error)
+/datum/action/cooldown/bloodsucker/proc/CheckCanPayCost(display_error)
if(!owner || !owner.mind)
return FALSE
// Cooldown?
- if(cooldownUntil > world.time)
+ if(next_use_time > world.time)
if(display_error)
- to_chat(owner, "[src] is unavailable. Wait [(cooldownUntil - world.time) / 10] seconds.")
+ to_chat(owner, "[src] is unavailable. Wait [(next_use_time - world.time) / 10] seconds.")
return FALSE
// Have enough blood?
var/mob/living/L = owner
@@ -81,7 +85,7 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/proc/CheckCanUse(display_error) // These checks can be scanned every frame while a ranged power is on.
+/datum/action/cooldown/bloodsucker/proc/CheckCanUse(display_error) // These checks can be scanned every frame while a ranged power is on.
if(!owner || !owner.mind)
return FALSE
// Torpor?
@@ -123,79 +127,43 @@
return FALSE
return TRUE
-/datum/action/bloodsucker/proc/StartCooldown()
- set waitfor = FALSE
- // Alpha Out
- button.color = rgb(128,0,0,128)
- button.alpha = 100
- // Calculate Cooldown (by power's level)
- var/this_cooldown = (cooldown_static || amSingleUse) ? cooldown : max(cooldown / 2, cooldown - (cooldown / 16 * (level_current-1)))
- // NOTE: With this formula, you'll hit half cooldown at level 8 for that power.
-
- // Wait for cooldown
- cooldownUntil = world.time + this_cooldown
- spawn(this_cooldown)
- // Alpha In
- button.color = rgb(255,255,255,255)
- button.alpha = 255
-
-/datum/action/bloodsucker/proc/CheckCanDeactivate(display_error)
+/datum/action/cooldown/bloodsucker/proc/CheckCanDeactivate(display_error)
return TRUE
-/datum/action/bloodsucker/UpdateButtonIcon(force = FALSE)
+/datum/action/cooldown/bloodsucker/UpdateButton(atom/movable/screen/movable/action_button/button, force = FALSE)
background_icon_state = active? background_icon_state_on : background_icon_state_off
- ..()//UpdateButtonIcon()
+ ..()//UpdateButton()
-
-/datum/action/bloodsucker/proc/PayCost()
+/datum/action/cooldown/bloodsucker/proc/PayCost()
// owner for actions is the mob, not mind.
var/mob/living/L = owner
L.blood_volume -= bloodcost
-/datum/action/bloodsucker/proc/ActivatePower()
+/datum/action/cooldown/bloodsucker/proc/ActivatePower()
-/datum/action/bloodsucker/proc/DeactivatePower(mob/living/user = owner, mob/living/target)
+/datum/action/cooldown/bloodsucker/proc/DeactivatePower(mob/living/user = owner, mob/living/target)
active = FALSE
- UpdateButtonIcon()
+ UpdateButtons()
StartCooldown()
-/datum/action/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target) // Used by loops to make sure this power can stay active.
+/datum/action/cooldown/bloodsucker/proc/ContinueActive(mob/living/user, mob/living/target) // Used by loops to make sure this power can stay active.
return active && user && (!warn_constant_cost || user.blood_volume > 0)
-/datum/action/bloodsucker/proc/RemoveAfterUse()
+/datum/action/cooldown/bloodsucker/proc/RemoveAfterUse()
// Un-Learn Me! (GO HOME
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if (istype(bloodsuckerdatum))
bloodsuckerdatum.powers -= src
Remove(owner)
-/datum/action/bloodsucker/proc/Upgrade()
+/datum/action/cooldown/bloodsucker/proc/Upgrade()
level_current ++
-/////////////////////////////////// PASSIVE POWERS ///////////////////////////////////
-
-// New Type: Passive (Always on, no button)
-/datum/action/bloodsucker/passive
-
-/datum/action/bloodsucker/passive/New()
- // REMOVED: DO NOTHBING!
- ..()
- // Don't Display Button! (it doesn't do anything anyhow)
- button.screen_loc = DEFAULT_BLOODSPELLS
- button.moved = DEFAULT_BLOODSPELLS
- button.ordered = FALSE
-
-/datum/action/bloodsucker/passive/Destroy()
- if(owner)
- Remove(owner)
- target = null
- return ..()
-
/////////////////////////////////// TARGETTED POWERS ///////////////////////////////////
-/datum/action/bloodsucker/targeted
+/datum/action/cooldown/bloodsucker/targeted
// NOTE: All Targeted spells are Toggles! We just don't bother checking here.
var/target_range = 99
var/message_Trigger = "Select a target."
@@ -204,7 +172,7 @@
var/power_in_use = FALSE // Is this power LOCKED due to being used?
-/datum/action/bloodsucker/targeted/New(Target)
+/datum/action/cooldown/bloodsucker/targeted/New(Target)
desc += "
\[Targeted Power\]" // Modify description to add notice that this is aimed.
..()
// Create Proc Holder for intercepting clicks
@@ -212,7 +180,7 @@
bs_proc_holder.linked_power = src
// Click power: Begin Aim
-/datum/action/bloodsucker/targeted/Trigger()
+/datum/action/cooldown/bloodsucker/targeted/Trigger()
if(active && CheckCanDeactivate(TRUE))
DeactivateRangedAbility()
DeactivatePower()
@@ -220,7 +188,7 @@
if(!CheckCanPayCost(TRUE) || !CheckCanUse(TRUE))
return
active = !active
- UpdateButtonIcon()
+ UpdateButtons()
// Create & Link Targeting Proc
var/mob/living/L = owner
if(L.ranged_ability)
@@ -230,7 +198,7 @@
if(message_Trigger != "")
to_chat(owner, "[message_Trigger]")
-/datum/action/bloodsucker/targeted/CheckCanUse(display_error)
+/datum/action/cooldown/bloodsucker/targeted/CheckCanUse(display_error)
. = ..()
if(!.)
return
@@ -238,21 +206,21 @@
return FALSE // doesn't let you remove powers if you're not there. So, let's just cancel the power entirely.
return TRUE
-/datum/action/bloodsucker/targeted/DeactivatePower(mob/living/user = owner, mob/living/target)
+/datum/action/cooldown/bloodsucker/targeted/DeactivatePower(mob/living/user = owner, mob/living/target)
// Don't run ..(), we don't want to engage the cooldown until we USE this power!
active = FALSE
- UpdateButtonIcon()
+ UpdateButtons()
-/datum/action/bloodsucker/targeted/proc/DeactivateRangedAbility()
+/datum/action/cooldown/bloodsucker/targeted/proc/DeactivateRangedAbility()
// Only Turned off when CLICK is disabled...aka, when you successfully clicked (or
bs_proc_holder.remove_ranged_ability()
// Check if target is VALID (wall, turf, or character?)
-/datum/action/bloodsucker/targeted/proc/CheckValidTarget(atom/A)
+/datum/action/cooldown/bloodsucker/targeted/proc/CheckValidTarget(atom/A)
return FALSE // FALSE targets nothing.
// Check if valid target meets conditions
-/datum/action/bloodsucker/targeted/proc/CheckCanTarget(atom/A, display_error)
+/datum/action/cooldown/bloodsucker/targeted/proc/CheckCanTarget(atom/A, display_error)
// Out of Range
if(!(A in view(target_range, owner)))
if(display_error && target_range > 1) // Only warn for range if it's greater than 1. Brawn doesn't need to announce itself.
@@ -261,7 +229,7 @@
return istype(A)
// Click Target
-/datum/action/bloodsucker/targeted/proc/ClickWithPower(atom/A)
+/datum/action/cooldown/bloodsucker/targeted/proc/ClickWithPower(atom/A)
// CANCEL RANGED TARGET check
if(power_in_use || !CheckValidTarget(A))
return FALSE
@@ -276,21 +244,21 @@
power_in_use = FALSE
return TRUE
-/datum/action/bloodsucker/targeted/proc/FireTargetedPower(atom/A)
+/datum/action/cooldown/bloodsucker/targeted/proc/FireTargetedPower(atom/A)
// Like ActivatePower, but specific to Targeted (and takes an atom input). We don't use ActivatePower for targeted.
-/datum/action/bloodsucker/targeted/proc/PowerActivatedSuccessfully()
+/datum/action/cooldown/bloodsucker/targeted/proc/PowerActivatedSuccessfully()
// The power went off! We now pay the cost of the power.
PayCost()
DeactivateRangedAbility()
DeactivatePower()
StartCooldown() // Do AFTER UpdateIcon() inside of DeactivatePower. Otherwise icon just gets wiped.
-/datum/action/bloodsucker/targeted/ContinueActive(mob/living/user, mob/living/target) // Used by loops to make sure this power can stay active.
+/datum/action/cooldown/bloodsucker/targeted/ContinueActive(mob/living/user, mob/living/target) // Used by loops to make sure this power can stay active.
return ..()
// Target Proc Holder
/obj/effect/proc_holder/bloodsucker
- var/datum/action/bloodsucker/targeted/linked_power
+ var/datum/action/cooldown/bloodsucker/targeted/linked_power
/obj/effect/proc_holder/bloodsucker/remove_ranged_ability(msg)
..()
diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm b/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm
index a86cb374d2..88a4b6bb32 100644
--- a/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm
+++ b/code/modules/antagonists/bloodsucker/bloodsucker_sunlight.dm
@@ -55,8 +55,8 @@
bloodsuckerdatum.warn_sun_locker = FALSE
bloodsuckerdatum.warn_sun_burn = FALSE
// Remove Dawn Powers
- for(var/datum/action/bloodsucker/P in bloodsuckerdatum.powers)
- if(istype(P, /datum/action/bloodsucker/gohome))
+ for(var/datum/action/cooldown/bloodsucker/P in bloodsuckerdatum.powers)
+ if(istype(P, /datum/action/cooldown/bloodsucker/gohome))
bloodsuckerdatum.powers -= P
P.Remove(M.current)
nighttime_duration += 100 //Each day makes the night a minute longer.
@@ -149,5 +149,5 @@
if(!istype(M) || !istype(M.current))
continue
var/datum/antagonist/bloodsucker/bloodsuckerdatum = M.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
- if(istype(bloodsuckerdatum) && bloodsuckerdatum.lair && !(locate(/datum/action/bloodsucker/gohome) in bloodsuckerdatum.powers))
- bloodsuckerdatum.BuyPower(new /datum/action/bloodsucker/gohome)
+ if(istype(bloodsuckerdatum) && bloodsuckerdatum.lair && !(locate(/datum/action/cooldown/bloodsucker/gohome) in bloodsuckerdatum.powers))
+ bloodsuckerdatum.BuyPower(new /datum/action/cooldown/bloodsucker/gohome)
diff --git a/code/modules/antagonists/bloodsucker/bloodsucker_ui.dm b/code/modules/antagonists/bloodsucker/bloodsucker_ui.dm
index 486ae51117..3edffe9c31 100644
--- a/code/modules/antagonists/bloodsucker/bloodsucker_ui.dm
+++ b/code/modules/antagonists/bloodsucker/bloodsucker_ui.dm
@@ -14,7 +14,7 @@
dat += "