diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 1ef8b9bb66..6255319a08 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -190,6 +190,7 @@
#define COMSIG_LIVING_MINOR_SHOCK "living_minor_shock" //sent by stuff like stunbatons and tasers: ()
#define COMSIG_LIVING_REVIVE "living_revive" //from base of mob/living/revive() (full_heal, admin_revive)
#define COMSIG_MOB_CLIENT_LOGIN "comsig_mob_client_login" //sent when a mob/login() finishes: (client)
+#define COMSIG_MOB_CLIENT_LOGOUT "comsig_mob_client_logout" //sent when a mob/logout() starts: (client)
#define COMSIG_MOB_CLIENT_MOVE "comsig_mob_client_move" //sent when client/Move() finishes with no early returns: (client, direction, n, oldloc)
#define COMSIG_LIVING_GUN_PROCESS_FIRE "living_gun_process_fire" //from base of /obj/item/gun/proc/process_fire(): (atom/target, params, zone_override)
#define COMSIG_LIVING_COMBAT_ENABLED "combatmode_enabled" //from base of mob/living/enable_combat_mode() (was_forced)
diff --git a/code/__DEFINES/storage.dm b/code/__DEFINES/storage.dm
index 065551b975..27bf502252 100644
--- a/code/__DEFINES/storage.dm
+++ b/code/__DEFINES/storage.dm
@@ -30,3 +30,5 @@
// UI defines
/// Minimum pixels an item must have in volumetric scaled storage UI
#define MINIMUM_PIXELS_PER_ITEM 5
+/// Maximum number of objects that will be allowed to be displayed using the volumetric display system. Arbitrary number to prevent server lockups.
+#define MAXIMUM_VOLUMETRIC_ITEMS 256
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index f5eb8535a5..4ed286eb08 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -210,20 +210,6 @@
user.swap_hand(held_index)
return TRUE
-/obj/screen/close
- name = "close"
- layer = ABOVE_HUD_LAYER
- plane = ABOVE_HUD_PLANE
- icon_state = "backpack_close"
-
-/obj/screen/close/Initialize(mapload, new_master)
- . = ..()
- master = new_master
-
-/obj/screen/close/Click()
- var/datum/component/storage/S = master
- S.hide_from(usr)
- return TRUE
/obj/screen/drop
name = "drop"
@@ -406,30 +392,6 @@
else
icon_state = "act_rest0"
-/obj/screen/storage
- name = "storage"
- icon_state = "block"
- screen_loc = "7,7 to 10,8"
- layer = HUD_LAYER
- plane = HUD_PLANE
-
-/obj/screen/storage/Initialize(mapload, new_master)
- . = ..()
- master = new_master
-
-/obj/screen/storage/Click(location, control, params)
- if(world.time <= usr.next_move)
- return TRUE
- if(usr.incapacitated())
- return TRUE
- if (ismecha(usr.loc)) // stops inventory actions in a mech
- return TRUE
- if(master)
- var/obj/item/I = usr.get_active_held_item()
- if(I)
- master.attackby(null, I, usr, params)
- return TRUE
-
/obj/screen/throw_catch
name = "throw/catch"
icon = 'icons/mob/screen_midnight.dmi'
diff --git a/code/_onclick/hud/screen_objects/storage.dm b/code/_onclick/hud/screen_objects/storage.dm
new file mode 100644
index 0000000000..5e78ba45e0
--- /dev/null
+++ b/code/_onclick/hud/screen_objects/storage.dm
@@ -0,0 +1,41 @@
+/obj/screen/storage
+ name = "storage"
+ var/insertion_click = FALSE
+
+/obj/screen/storage/Initialize(mapload, new_master)
+ . = ..()
+ master = new_master
+
+/obj/screen/storage/Click(location, control, params)
+ if(!insertion_click)
+ return ..()
+ if(world.time <= usr.next_move)
+ return TRUE
+ if(usr.incapacitated())
+ return TRUE
+ if (ismecha(usr.loc)) // stops inventory actions in a mech
+ return TRUE
+ if(master)
+ var/obj/item/I = usr.get_active_held_item()
+ if(I)
+ master.attackby(null, I, usr, params)
+ return TRUE
+
+/obj/screen/storage/boxes
+ name = "storage"
+ icon_state = "block"
+ screen_loc = "7,7 to 10,8"
+ layer = HUD_LAYER
+ plane = HUD_PLANE
+ insertion_click = TRUE
+
+/obj/screen/storage/close
+ name = "close"
+ layer = ABOVE_HUD_LAYER
+ plane = ABOVE_HUD_PLANE
+ icon_state = "backpack_close"
+
+/obj/screen/storage/close/Click()
+ var/datum/component/storage/S = master
+ S.hide_from(usr)
+ return TRUE
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index 576bd196dc..c3ac3c48a8 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -46,8 +46,19 @@
var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number.
- var/obj/screen/storage/boxes //storage display object
- var/obj/screen/close/closer //close button object
+ /// "legacy"/default view mode's storage "boxes"
+ var/obj/screen/storage/boxes/ui_boxes
+ /// New volumetric storage display mode's left side
+ var/obj/screen/storage/left/ui_left
+ /// New volumetric storage display mode's right side
+ var/obj/screen/storage/right/ui_right
+ /// New volumetric storage display mode's center 'blocks'
+ var/obj/screen/storage/continuous/ui_continuous
+ /// The close button, used in all modes.
+ var/obj/screen/storage/close/ui_closer
+ /// Associative list of list(item = screen object) for volumetric storage item screen blocks
+ var/list/ui_item_blocks
+
var/current_maxscreensize
var/allow_big_nesting = FALSE //allow storage objects of the same or greater size.
@@ -120,8 +131,15 @@
/datum/component/storage/Destroy()
close_all()
- QDEL_NULL(boxes)
- QDEL_NULL(closer)
+ QDEL_NULL(ui_boxes)
+ QDEL_NULL(ui_closer)
+ QDEL_NULL(ui_continuous)
+ QDEL_NULL(ui_left)
+ QDEL_NULL(ui_right)
+ // DO NOT USE QDEL_LIST_ASSOC.
+ for(var/i in ui_item_blocks)
+ qdel(ui_item_blocks[i]) //qdel the screen object not the item
+ ui_item_blocks.Cut()
LAZYCLEARLIST(is_using)
return ..()
@@ -309,103 +327,6 @@
if(check_locked())
close_all()
-/datum/component/storage/proc/_process_numerical_display()
- . = list()
- for(var/obj/item/I in accessible_items())
- if(QDELETED(I))
- continue
- if(!.[I.type])
- .[I.type] = new /datum/numbered_display(I, 1)
- else
- var/datum/numbered_display/ND = .[I.type]
- ND.number++
- . = sortTim(., /proc/cmp_numbered_displays_name_asc, associative = TRUE)
-
-//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing.
-/datum/component/storage/proc/orient2hud(mob/user, maxcolumns)
- var/list/accessible_contents = accessible_items()
- var/adjusted_contents = length(accessible_contents)
-
- //Numbered contents display
- var/list/datum/numbered_display/numbered_contents
- if(display_numerical_stacking)
- numbered_contents = _process_numerical_display()
- adjusted_contents = numbered_contents.len
-
- var/columns = CLAMP(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns)
- var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows)
- standard_orient_objs(rows, columns, numbered_contents)
-
-//This proc draws out the inventory and places the items on it. It uses the standard position.
-/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents)
- boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
- var/cx = screen_start_x
- var/cy = screen_start_y
- if(islist(numerical_display_contents))
- for(var/type in numerical_display_contents)
- var/datum/numbered_display/ND = numerical_display_contents[type]
- ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
- ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
- ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]"
- ND.sample_object.layer = ABOVE_HUD_LAYER
- ND.sample_object.plane = ABOVE_HUD_PLANE
- cx++
- if(cx - screen_start_x >= cols)
- cx = screen_start_x
- cy++
- if(cy - screen_start_y >= rows)
- break
- else
- for(var/obj/O in accessible_items())
- if(QDELETED(O))
- continue
- O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
- O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
- O.maptext = ""
- O.layer = ABOVE_HUD_LAYER
- O.plane = ABOVE_HUD_PLANE
- cx++
- if(cx - screen_start_x >= cols)
- cx = screen_start_x
- cy++
- if(cy - screen_start_y >= rows)
- break
- closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]"
-
-/datum/component/storage/proc/show_to(mob/M, set_screen_size = TRUE)
- if(!M.client)
- return FALSE
- var/list/cview = getviewsize(M.client.view)
- var/maxallowedscreensize = cview[1]-8
- if(set_screen_size)
- current_maxscreensize = maxallowedscreensize
- else if(current_maxscreensize)
- maxallowedscreensize = current_maxscreensize
- if(M.active_storage != src && (M.stat == CONSCIOUS))
- for(var/obj/item/I in accessible_items())
- if(I.on_found(M))
- return FALSE
- if(M.active_storage)
- M.active_storage.hide_from(M)
- orient2hud(M, (isliving(M) ? maxallowedscreensize : 7))
- M.client.screen |= boxes
- M.client.screen |= closer
- M.client.screen |= accessible_items()
- M.active_storage = src
- LAZYOR(is_using, M)
- return TRUE
-
-/datum/component/storage/proc/hide_from(mob/M)
- if(!M.client)
- return TRUE
- var/atom/real_location = real_location()
- M.client.screen -= boxes
- M.client.screen -= closer
- M.client.screen -= real_location.contents
- if(M.active_storage == src)
- M.active_storage = null
- LAZYREMOVE(is_using, M)
- return TRUE
/datum/component/storage/proc/close(mob/M)
hide_from(M)
@@ -427,24 +348,6 @@
var/datum/component/storage/concrete/master = master()
master.emp_act(source, severity)
-//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right.
-//The numbers are calculated from the bottom-left The bottom-left slot being 1,1.
-/datum/component/storage/proc/orient_objs(tx, ty, mx, my)
- var/atom/real_location = real_location()
- var/cx = tx
- var/cy = ty
- boxes.screen_loc = "[tx]:,[ty] to [mx],[my]"
- for(var/obj/O in real_location)
- if(QDELETED(O))
- continue
- O.screen_loc = "[cx],[cy]"
- O.layer = ABOVE_HUD_LAYER
- O.plane = ABOVE_HUD_PLANE
- cx++
- if(cx > mx)
- cx = tx
- cy--
- closer.screen_loc = "[mx+1],[my]"
//Resets something that is being removed from storage.
/datum/component/storage/proc/_removal_reset(atom/movable/thing)
diff --git a/code/datums/components/storage/ui.dm b/code/datums/components/storage/ui.dm
new file mode 100644
index 0000000000..f73aff43ec
--- /dev/null
+++ b/code/datums/components/storage/ui.dm
@@ -0,0 +1,259 @@
+/**
+ * Generates a list of numbered_display datums for the numerical display system.
+ */
+/datum/component/storage/proc/_process_numerical_display()
+ . = list()
+ for(var/obj/item/I in accessible_items())
+ if(QDELETED(I))
+ continue
+ if(!.[I.type])
+ .[I.type] = new /datum/numbered_display(I, 1)
+ else
+ var/datum/numbered_display/ND = .[I.type]
+ ND.number++
+ . = sortTim(., /proc/cmp_numbered_displays_name_asc, associative = TRUE)
+
+/**
+ * Orients all objects in legacy mode, and returns the objects to show to the user.
+ */
+/datum/component/storage/proc/orient2hud_legacy(mob/user, maxcolumns)
+ . = list()
+ var/list/accessible_contents = accessible_items()
+ var/adjusted_contents = length(accessible_contents)
+
+ //Numbered contents display
+ var/list/datum/numbered_display/numbered_contents
+ if(display_numerical_stacking)
+ numbered_contents = _process_numerical_display()
+ adjusted_contents = numbered_contents.len
+
+ var/columns = CLAMP(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns)
+ var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows)
+
+ // First, boxes.
+ ui_boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+columns-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_boxes
+ // Then, closer.
+ closer.screen_loc = "[screen_start_x + columns]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]"
+ . += ui_closer
+ // Then orient the actual items.
+ var/cx = screen_start_x
+ var/cy = screen_start_y
+ if(islist(numerical_display_contents))
+ for(var/type in numerical_display_contents)
+ var/datum/numbered_display/ND = numerical_display_contents[type]
+ ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
+ ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
+ ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]"
+ ND.sample_object.layer = ABOVE_HUD_LAYER
+ ND.sample_object.plane = ABOVE_HUD_PLANE
+ . += ND.sample_object
+ cx++
+ if(cx - screen_start_x >= columns)
+ cx = screen_start_x
+ cy++
+ if(cy - screen_start_y >= rows)
+ break
+ else
+ for(var/obj/O in accessible_items())
+ if(QDELETED(O))
+ continue
+ O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
+ O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
+ O.maptext = ""
+ O.layer = ABOVE_HUD_LAYER
+ O.plane = ABOVE_HUD_PLANE
+ . += O
+ cx++
+ if(cx - screen_start_x >= columns)
+ cx = screen_start_x
+ cy++
+ if(cy - screen_start_y >= rows)
+ break
+
+/**
+ * Orients all objects in .. volumetric mode.
+ */
+/datum/component/storage/proc/orient2hud_volumetric(mob/user, maxcolumns)
+ . = list()
+ var/list/accessible_contents = accessible_items()
+ var/adjusted_contents = length(accessible_contents)
+
+ //Numbered contents display
+ var/list/datum/numbered_display/numbered_contents
+ if(display_numerical_stacking)
+ numbered_contents = _process_numerical_display()
+ adjusted_contents = numbered_contents.len
+
+ var/columns = CLAMP(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns)
+ var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows)
+
+ // First, continuous section.
+ ui_continuous.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+columns-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_continuous
+ // Then, left and right.
+ ui_left.screen_loc = "[screen_start_x]:[screen_pixel_x - 2],[screen_start_y]:[screen_pixel_y] to [screen_start_x]:[screen_pixel_x - 2],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_left
+ ui_right.screen_loc = "[screen_start_x+columns-1]:[screen_pixel_x + 2],[screen_start_y]:[screen_pixel_y] to [screen_start_x+columns-1]:[screen_pixel_x + 2],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_right
+ // Then, closer.
+ closer.screen_loc = "[screen_start_x + columns]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]"
+ . += ui_closer
+ // Generate ui_item_blocks for missing ones.
+
+
+
+
+
+
+ var/cx = screen_start_x
+ var/cy = screen_start_y
+ if(islist(numerical_display_contents))
+ for(var/type in numerical_display_contents)
+ var/datum/numbered_display/ND = numerical_display_contents[type]
+ ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE
+ ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
+ ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]"
+ ND.sample_object.layer = ABOVE_HUD_LAYER
+ ND.sample_object.plane = ABOVE_HUD_PLANE
+ . += ND.sample_object
+ cx++
+ if(cx - screen_start_x >= columns)
+ cx = screen_start_x
+ cy++
+ if(cy - screen_start_y >= rows)
+ break
+ else
+ for(var/obj/O in accessible_items())
+ if(QDELETED(O))
+ continue
+ O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip"
+ O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]"
+ O.maptext = ""
+ O.layer = ABOVE_HUD_LAYER
+ O.plane = ABOVE_HUD_PLANE
+ . += O
+ cx++
+ if(cx - screen_start_x >= columns)
+ cx = screen_start_x
+ cy++
+ if(cy - screen_start_y >= rows)
+ break
+
+/**
+ * Shows our UI to a mob.
+ */
+/datum/component/storage/proc/ui_show(mob/M, set_screen_size = TRUE)
+ if(!M.client)
+ return FALSE
+ var/list/cview = getviewsize(M.client.view)
+ // in tiles
+ var/maxallowedscreensize = cview[1]-8
+ if(set_screen_size)
+ current_maxscreensize = maxallowedscreensize
+ else if(current_maxscreensize)
+ maxallowedscreensize = current_maxscreensize
+ // we got screen size, register signal
+ RegisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_logout)
+ if(M.active_storage)
+ M.active_storage.ui_hide(M)
+ M.active_storage = src
+ LAZOR(is_using, M)
+ if(volumetric_ui())
+ //new volumetric ui bay-style
+ M.client.screen |= orient2hud_volumetric(M, maxallowedscreensize)
+ else
+ //old ui
+ M.client.screen |= orient2hud_legacy(M, maxallowedscreensize)
+ return TRUE
+
+/**
+ * VV hooked to ensure no lingering screen objects.
+ */
+/datum/component/storage/vv_edit_var(var_name, var_value)
+ var/list/old
+ if(var_name == NAMEOF(storage_flags))
+ old = is_using.Copy()
+ for(var/i in is_using)
+ ui_hide(i)
+ . = ..()
+ if(old)
+ for(var/i in old)
+ ui_show(i)
+
+/**
+ * Proc triggered by signal to ensure logging out clients don't linger.
+ */
+/datum/component/storage/proc/on_logout(datum/source, client/C)
+ ui_hide(source)
+
+/**
+ * Hides our UI from a mob
+ */
+/datum/component/storage/proc/ui_hide(mob/M)
+ if(!M.client)
+ return TRUE
+ M.client.screen -= list(ui_boxes, ui_closer, ui_left, ui_right, ui_continuous, get_ui_item_objects_hide())
+ if(M.active_storage == src)
+ M.active_storage = null
+ LAZYREMOVE(is_using, M)
+ return TRUE
+
+/**
+ * Returns TRUE if we are using volumetric UI instead of box UI
+ */
+/datum/component/storage/proc/volumetric_ui()
+ var/atom/real_location = real_location()
+ return (storage_flags & STORAGE_LIMIT_VOLUME) && (length(real_location.contents) <= MAXIMUM_VOLUMETRIC_OBJECTS)
+
+/**
+ * Gets the ui item objects to ui_hide.
+ */
+/datum/component/storage/proc/get_ui_item_objects_hide()
+ if(!volumetric_ui())
+ var/atom/real_location = real_location()
+ return real_location.contents
+ else
+ . = list()
+ for(var/i in ui_item_blocks)
+ . += ui_item_blocks[i] //get the block not the item
+
+/**
+ * Gets our ui_boxes, making it if it doesn't exist.
+ */
+/datum/component/storage/proc/get_ui_boxes()
+ if(!ui_boxes)
+ ui_boxes = new(null, src)
+ return ui_boxes
+
+/**
+ * Gets our ui_left, making it if it doesn't exist.
+ */
+/datum/component/storage/proc/get_ui_left()
+ if(!ui_left)
+ ui_left = new(null, src)
+ return ui_left
+
+/**
+ * Gets our ui_right, making it if it doesn't exist.
+ */
+/datum/component/storage/proc/get_ui_right()
+ if(!ui_right)
+ ui_right = new(null, src)
+ return ui_right
+
+/**
+ * Gets our ui_close, making it if it doesn't exist.
+ */
+/datum/component/storage/proc/get_ui_close()
+ if(!ui_close)
+ ui_close = new(null, src)
+ return ui_close
+
+/**
+ * Gets our ui_continuous, making it if it doesn't exist.
+ */
+/datum/component/storage/proc/get_ui_continuous()
+ if(!ui_continuous)
+ ui_continuous = new(null, src)
+ return ui_continuous
diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm
index e5aaa016bd..536eacca7d 100644
--- a/code/modules/mob/logout.dm
+++ b/code/modules/mob/logout.dm
@@ -1,4 +1,5 @@
/mob/Logout()
+ SEND_SIGNAL(src, COMSIG_MOB_CLIENT_LOGOUT, client)
log_message("[key_name(src)] is no longer owning mob [src]([src.type])", LOG_OWNERSHIP)
SStgui.on_logout(src)
unset_machine()
diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi
index ea946f60d9..ffbffcab0d 100644
Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ