diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index e0f7457a4a..53348b3467 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)
// This returns flags as defined for block in __DEFINES/combat.dm!
diff --git a/code/__DEFINES/inventory.dm b/code/__DEFINES/inventory.dm
index f4954f8f05..fd50b8b70d 100644
--- a/code/__DEFINES/inventory.dm
+++ b/code/__DEFINES/inventory.dm
@@ -1,13 +1,5 @@
/*ALL DEFINES RELATED TO INVENTORY OBJECTS, MANAGEMENT, ETC, GO HERE*/
-//ITEM INVENTORY WEIGHT, FOR w_class
-#define WEIGHT_CLASS_TINY 1 //Usually items smaller then a human hand, ex: Playing Cards, Lighter, Scalpel, Coins/Money
-#define WEIGHT_CLASS_SMALL 2 //Pockets can hold small and tiny items, ex: Flashlight, Multitool, Grenades, GPS Device
-#define WEIGHT_CLASS_NORMAL 3 //Standard backpacks can carry tiny, small & normal items, ex: Fire extinguisher, Stunbaton, Gas Mask, Metal Sheets
-#define WEIGHT_CLASS_BULKY 4 //Items that can be weilded or equipped but not stored in a normal bag, ex: Defibrillator, Backpack, Space Suits
-#define WEIGHT_CLASS_HUGE 5 //Usually represents objects that require two hands to operate, ex: Shotgun, Two Handed Melee Weapons - Can not fit in Boh
-#define WEIGHT_CLASS_GIGANTIC 6 //Essentially means it cannot be picked up or placed in an inventory, ex: Mech Parts, Safe - Can not fit in Boh
-
//Inventory depth: limits how many nested storage items you can access directly.
//1: stuff in mob, 2: stuff in backpack, 3: stuff in box in backpack, etc
#define INVENTORY_DEPTH 3
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers_planes.dm
similarity index 90%
rename from code/__DEFINES/layers.dm
rename to code/__DEFINES/layers_planes.dm
index 35947b320e..d0f9f8bc28 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers_planes.dm
@@ -129,11 +129,20 @@
#define HUD_PLANE 21
#define HUD_LAYER 21
#define HUD_RENDER_TARGET "HUD_PLANE"
-#define ABOVE_HUD_PLANE 22
-#define ABOVE_HUD_LAYER 22
+
+#define VOLUMETRIC_STORAGE_BOX_PLANE 23
+#define VOLUMETRIC_STORAGE_BOX_LAYER 23
+#define VOLUMETRIC_STORAGE_BOX_RENDER_TARGET "VOLUME_STORAGE_BOX_PLANE"
+
+#define VOLUMETRIC_STORAGE_ITEM_PLANE 24
+#define VOLUMETRIC_STORAGE_ITEM_LAYER 24
+#define VOLUMETRIC_STORAGE_ITEM_RENDER_TARGET "VOLUME_STORAGE_ITEM_PLANE"
+
+#define ABOVE_HUD_PLANE 25
+#define ABOVE_HUD_LAYER 25
#define ABOVE_HUD_RENDER_TARGET "ABOVE_HUD_PLANE"
-#define SPLASHSCREEN_LAYER 23
-#define SPLASHSCREEN_PLANE 23
+#define SPLASHSCREEN_LAYER 30
+#define SPLASHSCREEN_PLANE 30
#define SPLASHSCREEN_RENDER_TARGET "SPLASHSCREEN_PLANE"
diff --git a/code/__DEFINES/storage.dm b/code/__DEFINES/storage.dm
new file mode 100644
index 0000000000..b4240455fe
--- /dev/null
+++ b/code/__DEFINES/storage.dm
@@ -0,0 +1,47 @@
+// storage_flags variable on /datum/component/storage
+
+// Storage limits. These can be combined (and usually are combined).
+/// Check max_items and contents.len when trying to insert
+#define STORAGE_LIMIT_MAX_ITEMS (1<<0)
+/// Check max_combined_w_class.
+#define STORAGE_LIMIT_COMBINED_W_CLASS (1<<1)
+/// Use the new volume system. Will automatically force rendering to use the new volume/baystation scaling UI so this is kind of incompatible with stuff like stack storage etc etc.
+#define STORAGE_LIMIT_VOLUME (1<<2)
+/// Use max_w_class
+#define STORAGE_LIMIT_MAX_W_CLASS (1<<3)
+
+#define STORAGE_FLAGS_LEGACY_DEFAULT (STORAGE_LIMIT_MAX_ITEMS | STORAGE_LIMIT_COMBINED_W_CLASS | STORAGE_LIMIT_MAX_W_CLASS)
+#define STORAGE_FLAGS_VOLUME_DEFAULT (STORAGE_LIMIT_MAX_ITEMS | STORAGE_LIMIT_VOLUME | STORAGE_LIMIT_MAX_W_CLASS)
+
+//ITEM INVENTORY WEIGHT, FOR w_class
+/// Usually items smaller then a human hand, ex: Playing Cards, Lighter, Scalpel, Coins/Money
+#define WEIGHT_CLASS_TINY 1
+/// Pockets can hold small and tiny items, ex: Flashlight, Multitool, Grenades, GPS Device
+#define WEIGHT_CLASS_SMALL 2
+/// Standard backpacks can carry tiny, small & normal items, ex: Fire extinguisher, Stunbaton, Gas Mask, Metal Sheets
+#define WEIGHT_CLASS_NORMAL 3
+/// Items that can be weilded or equipped but not stored in a normal bag, ex: Defibrillator, Backpack, Space Suits
+#define WEIGHT_CLASS_BULKY 4
+/// Usually represents objects that require two hands to operate, ex: Shotgun, Two Handed Melee Weapons - Can not fit in Boh
+#define WEIGHT_CLASS_HUGE 5
+/// Essentially means it cannot be picked up or placed in an inventory, ex: Mech Parts, Safe - Can not fit in Boh
+#define WEIGHT_CLASS_GIGANTIC 6
+
+/// Macro for automatically getting the volume of an item from its w_class.
+#define AUTO_SCALE_VOLUME(w_class) (2 ** w_class)
+/// Macro for automatically getting the volume of a storage item from its max_w_class and max_combined_w_class.
+#define AUTO_SCALE_STORAGE_VOLUME(w_class, max_combined_w_class) (AUTO_SCALE_VOLUME(w_class) * (max_combined_w_class / w_class))
+
+// UI defines
+/// Size of volumetric box icon
+#define VOLUMETRIC_STORAGE_BOX_ICON_SIZE 32
+/// Size of EACH left/right border icon for volumetric boxes
+#define VOLUMETRIC_STORAGE_BOX_BORDER_SIZE 1
+/// Minimum pixels an item must have in volumetric scaled storage UI
+#define MINIMUM_PIXELS_PER_ITEM 6
+/// 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
+/// How much padding to give between items
+#define VOLUMETRIC_STORAGE_ITEM_PADDING 1
+/// How much padding to give to edges
+#define VOLUMETRIC_STORAGE_EDGE_PADDING 1
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index 5367322d8e..f0c854c398 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -249,5 +249,11 @@ GLOBAL_LIST_INIT(bitfields, list(
"COMBAT_FLAG_SOFT_STAMCRIT" = COMBAT_FLAG_SOFT_STAMCRIT,
"COMBAT_FLAG_INTENTIONALLY_RESTING" = COMBAT_FLAG_INTENTIONALLY_RESTING,
"COMBAT_FLAG_RESISTING_REST" = COMBAT_FLAG_RESISTING_REST
+ ),
+ "storage_flags" = list(
+ "STORAGE_LIMIT_MAX_ITEMS" = STORAGE_LIMIT_MAX_ITEMS,
+ "STORAGE_LIMIT_MAX_W_CLASS" = STORAGE_LIMIT_MAX_W_CLASS,
+ "STORAGE_LIMIT_COMBINED_W_CLASS" = STORAGE_LIMIT_COMBINED_W_CLASS,
+ "STORAGE_LIMIT_VOLUME" = STORAGE_LIMIT_VOLUME
)
))
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..db287565f2
--- /dev/null
+++ b/code/_onclick/hud/screen_objects/storage.dm
@@ -0,0 +1,110 @@
+/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.close(usr)
+ return TRUE
+
+/obj/screen/storage/left
+ icon_state = "storage_start"
+ insertion_click = TRUE
+
+/obj/screen/storage/right
+ icon_state = "storage_end"
+ insertion_click = TRUE
+
+/obj/screen/storage/continuous
+ icon_state = "storage_continue"
+ insertion_click = TRUE
+
+/obj/screen/storage/volumetric_box
+ icon_state = "stored_continue"
+ var/obj/item/our_item
+
+/obj/screen/storage/volumetric_box/Initialize(mapload, new_master, our_item)
+ src.our_item = our_item
+ return ..()
+
+/obj/screen/storage/volumetric_box/Destroy()
+ our_item = null
+ return ..()
+
+/obj/screen/storage/volumetric_box/Click(location, control, params)
+ return our_item.Click(location, control, params)
+
+/obj/screen/storage/volumetric_box/center
+ icon_state = "stored_continue"
+ var/obj/screen/storage/stored_left/left
+ var/obj/screen/storage/stored_right/right
+ var/pixel_size
+
+/obj/screen/storage/volumetric_box/center/Initialize(mapload, new_master, our_item)
+ left = new(null, src, our_item)
+ right = new(null, src, our_item)
+ return ..()
+
+/obj/screen/storage/volumetric_box/center/Destroy()
+ QDEL_NULL(left)
+ QDEL_NULL(right)
+ return ..()
+
+/obj/screen/storage/volumetric_box/center/proc/on_screen_objects()
+ return list(src, left, right)
+
+/**
+ * Sets the size of this box screen object and regenerates its left/right borders. This includes the actual border's size!
+ */
+/obj/screen/storage/volumetric_box/center/proc/set_pixel_size(pixels)
+ if(pixel_size == pixels)
+ return
+ pixel_size = pixels
+ cut_overlays()
+ //our icon size is 32 pixels.
+ transform = matrix((pixels - (VOLUMETRIC_STORAGE_BOX_BORDER_SIZE * 2)) / VOLUMETRIC_STORAGE_BOX_ICON_SIZE, 0, 0, 0, 1, 0)
+ left.pixel_x = -((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) - VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
+ right.pixel_x = ((pixels - VOLUMETRIC_STORAGE_BOX_ICON_SIZE) * 0.5) + VOLUMETRIC_STORAGE_BOX_BORDER_SIZE
+ add_overlay(left)
+ add_overlay(right)
+
+/obj/screen/storage/stored_left
+ icon_state = "stored_start"
+ appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM // Yes I know RESET_TRANSFORM is in APPEARANCE_UI but we're hard-asserting this incase someone changes it.
+
+/obj/screen/storage/stored_right
+ icon_state = "stored_end"
+ appearance_flags = APPEARANCE_UI | KEEP_APART | RESET_TRANSFORM
diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm
index 4c8f1b4c97..e044a59668 100644
--- a/code/datums/components/storage/concrete/_concrete.dm
+++ b/code/datums/components/storage/concrete/_concrete.dm
@@ -60,7 +60,7 @@
_contents_limbo = null
if(_user_limbo)
for(var/i in _user_limbo)
- show_to(i)
+ ui_show(i)
_user_limbo = null
/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE)
diff --git a/code/datums/components/storage/concrete/emergency.dm b/code/datums/components/storage/concrete/emergency.dm
index 348821a913..faaeada13d 100644
--- a/code/datums/components/storage/concrete/emergency.dm
+++ b/code/datums/components/storage/concrete/emergency.dm
@@ -18,7 +18,7 @@
return
. = COMPONENT_NO_ATTACK_HAND
if(!check_locked(source, user, TRUE))
- show_to(user)
+ ui_show(user)
A.do_jiggle()
if(rustle_sound)
playsound(A, "rustle", 50, 1, -5)
diff --git a/code/datums/components/storage/concrete/rped.dm b/code/datums/components/storage/concrete/rped.dm
index 2f95466238..d919828e8e 100644
--- a/code/datums/components/storage/concrete/rped.dm
+++ b/code/datums/components/storage/concrete/rped.dm
@@ -3,6 +3,7 @@
allow_quick_gather = TRUE
allow_quick_empty = TRUE
click_gather = TRUE
+ storage_flags = STORAGE_FLAGS_LEGACY_DEFAULT
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = 100
max_items = 100
diff --git a/code/datums/components/storage/concrete/stack.dm b/code/datums/components/storage/concrete/stack.dm
index 1f0c44c650..a3f1e526a0 100644
--- a/code/datums/components/storage/concrete/stack.dm
+++ b/code/datums/components/storage/concrete/stack.dm
@@ -1,6 +1,7 @@
//Stack-only storage.
/datum/component/storage/concrete/stack
display_numerical_stacking = TRUE
+ storage_flags = STORAGE_FLAGS_LEGACY_DEFAULT
var/max_combined_stack_amount = 300
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = WEIGHT_CLASS_NORMAL * 14
diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm
index 3ed66dcf92..4100942fec 100644
--- a/code/datums/components/storage/storage.dm
+++ b/code/datums/components/storage/storage.dm
@@ -21,9 +21,16 @@
var/locked = FALSE //when locked nothing can see inside or use it.
- var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit.
- var/max_combined_w_class = 14 //max combined sizes of objects that will fit.
- var/max_items = 7 //max number of objects that will fit.
+ /// Storage flags, including what kinds of limiters we use for how many items we can hold
+ var/storage_flags = STORAGE_FLAGS_LEGACY_DEFAULT
+ /// Max w_class we can hold. Applies to [STORAGE_LIMIT_COMBINED_W_CLASS] and [STORAGE_LIMIT_VOLUME]
+ var/max_w_class = WEIGHT_CLASS_SMALL
+ /// Max combined w_class. Applies to [STORAGE_LIMIT_COMBINED_W_CLASS]
+ var/max_combined_w_class = WEIGHT_CLASS_SMALL * 7
+ /// Max items we can hold. Applies to [STORAGE_LIMIT_MAX_ITEMS]
+ var/max_items = 7
+ /// Max volume we can hold. Applies to [STORAGE_LIMIT_VOLUME]. Auto scaled on New() if unset.
+ var/max_volume
var/emp_shielded = FALSE
@@ -39,8 +46,17 @@
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 center 'blocks'
+ var/obj/screen/storage/continuous/ui_continuous
+ /// The close button, used in all modes. Frames right side in volumetric mode.
+ var/obj/screen/storage/close/ui_close
+ /// 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.
@@ -68,9 +84,6 @@
return COMPONENT_INCOMPATIBLE
if(master)
change_master(master)
- boxes = new(null, src)
- closer = new(null, src)
- orient2hud()
RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check)
RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked)
@@ -111,8 +124,15 @@
/datum/component/storage/Destroy()
close_all()
- QDEL_NULL(boxes)
- QDEL_NULL(closer)
+ QDEL_NULL(ui_boxes)
+ QDEL_NULL(ui_close)
+ QDEL_NULL(ui_continuous)
+ QDEL_NULL(ui_left)
+ // DO NOT USE QDEL_LIST_ASSOC.
+ if(ui_item_blocks)
+ 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 ..()
@@ -286,7 +306,7 @@
if(!_target)
_target = get_turf(parent)
if(usr)
- hide_from(usr)
+ ui_hide(usr)
var/list/contents = contents()
var/atom/real_location = real_location()
for(var/obj/item/I in contents)
@@ -300,106 +320,8 @@
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)
+ ui_hide(M)
/datum/component/storage/proc/close_all()
. = FALSE
@@ -418,25 +340,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)
if(!istype(thing))
@@ -448,6 +351,9 @@
/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing)
_removal_reset(thing)
+ if(LAZYACCESS(ui_item_blocks, thing))
+ qdel(ui_item_blocks[thing])
+ ui_item_blocks -= thing
refresh_mob_views()
//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted
@@ -462,7 +368,7 @@
/datum/component/storage/proc/refresh_mob_views()
var/list/seeing = can_see_contents()
for(var/i in seeing)
- show_to(i)
+ ui_show(i)
return TRUE
/datum/component/storage/proc/can_see_contents()
@@ -559,7 +465,7 @@
A.add_fingerprint(M)
if(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE)))
return FALSE
- show_to(M, !ghost)
+ ui_show(M, !ghost)
/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M)
if(isitem(O))
@@ -587,10 +493,6 @@
if(M && !stop_messages)
host.add_fingerprint(M)
return FALSE
- if(real_location.contents.len >= max_items)
- if(!stop_messages)
- to_chat(M, "[host] is full, make some space!")
- return FALSE //Storage item is full
if(length(can_hold))
if(!is_type_in_typecache(I, can_hold))
if(!stop_messages)
@@ -600,17 +502,34 @@
if(!stop_messages)
to_chat(M, "[host] cannot hold [I]!")
return FALSE
- if(I.w_class > max_w_class)
- if(!stop_messages)
- to_chat(M, "[I] is too big for [host]!")
- return FALSE
- var/sum_w_class = I.w_class
- for(var/obj/item/_I in real_location)
- sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it.
- if(sum_w_class > max_combined_w_class)
- if(!stop_messages)
- to_chat(M, "[I] won't fit in [host], make some space!")
- return FALSE
+ // STORAGE LIMITS
+ if(storage_flags & STORAGE_LIMIT_MAX_ITEMS)
+ if(real_location.contents.len >= max_items)
+ if(!stop_messages)
+ to_chat(M, "[host] has too many things in it, make some space!")
+ return FALSE //Storage item is full
+ if(storage_flags & STORAGE_LIMIT_MAX_W_CLASS)
+ if(I.w_class > max_w_class)
+ if(!stop_messages)
+ to_chat(M, "[I] is too long for [host]!")
+ return FALSE
+ if(storage_flags & STORAGE_LIMIT_COMBINED_W_CLASS)
+ var/sum_w_class = I.w_class
+ for(var/obj/item/_I in real_location)
+ sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it.
+ if(sum_w_class > max_combined_w_class)
+ if(!stop_messages)
+ to_chat(M, "[I] won't fit in [host], make some space!")
+ return FALSE
+ if(storage_flags & STORAGE_LIMIT_VOLUME)
+ var/sum_volume = I.get_w_volume()
+ for(var/obj/item/_I in real_location)
+ sum_volume += _I.get_w_volume()
+ if(sum_volume > get_max_volume())
+ if(!stop_messages)
+ to_chat(M, "[I] is too spacious to fit in [host], make some space!")
+ return FALSE
+ /////////////////
if(isitem(host))
var/obj/item/IP = host
var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage)
@@ -742,7 +661,7 @@
if(A.loc == user)
. = COMPONENT_NO_ATTACK_HAND
if(!check_locked(source, user, TRUE))
- show_to(user)
+ ui_show(user)
A.do_jiggle()
/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user)
@@ -761,7 +680,7 @@
return do_quick_empty(loctarget)
/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target)
- return hide_from(target)
+ return ui_hide(target)
/datum/component/storage/proc/on_alt_click(datum/source, mob/user)
if(!isliving(user) || !user.CanReach(parent))
@@ -803,3 +722,9 @@
to_chat(user, "[parent] now picks up all items in a tile at once.")
if(COLLECT_ONE)
to_chat(user, "[parent] now picks up one item at a time.")
+
+/**
+ * Gets our max volume
+ */
+/datum/component/storage/proc/get_max_volume()
+ return max_volume || AUTO_SCALE_STORAGE_VOLUME(max_w_class, max_combined_w_class)
diff --git a/code/datums/components/storage/ui.dm b/code/datums/components/storage/ui.dm
new file mode 100644
index 0000000000..c1c049dfd3
--- /dev/null
+++ b/code/datums/components/storage/ui.dm
@@ -0,0 +1,293 @@
+/**
+ * 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 = get_ui_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.
+ ui_close = get_ui_close()
+ ui_close.screen_loc = "[screen_start_x + columns]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]"
+ . += ui_close
+ // Then orient the actual items.
+ var/cx = screen_start_x
+ var/cy = screen_start_y
+ if(islist(numbered_contents))
+ for(var/type in numbered_contents)
+ var/datum/numbered_display/ND = numbered_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. Does not support numerical display!
+ */
+/datum/component/storage/proc/orient2hud_volumetric(mob/user, maxcolumns)
+ . = list()
+
+ // Generate ui_item_blocks for missing ones and render+orient.
+ var/list/atom/contents = accessible_items()
+ // our volume
+ var/our_volume = get_max_volume()
+ var/horizontal_pixels = (maxcolumns * world.icon_size) - (VOLUMETRIC_STORAGE_EDGE_PADDING * 2)
+ var/max_horizontal_pixels = horizontal_pixels * screen_max_rows
+ // sigh loopmania time
+ var/used = 0
+ // define outside for performance
+ var/volume
+ var/list/volume_by_item = list()
+ var/list/percentage_by_item = list()
+ for(var/obj/item/I in contents)
+ volume = I.get_w_volume()
+ used += volume
+ volume_by_item[I] = volume
+ percentage_by_item[I] = volume / get_max_volume()
+ var/padding_pixels = ((length(percentage_by_item) - 1) * VOLUMETRIC_STORAGE_ITEM_PADDING) + VOLUMETRIC_STORAGE_EDGE_PADDING * 2
+ var/min_pixels = (MINIMUM_PIXELS_PER_ITEM * length(percentage_by_item)) + padding_pixels
+ // do the check for fallback for when someone has too much gamer gear
+ if((min_pixels) > (max_horizontal_pixels + 4)) // 4 pixel grace zone
+ to_chat(user, "[parent] was showed to you in legacy mode due to your items overrunning the three row limit! Consider not carrying too much or bugging a maintainer to raise this limit!")
+ return orient2hud_legacy(user, maxcolumns)
+ // after this point we are sure we can somehow fit all items into our max number of rows.
+
+ // determine rows
+ var/rows = CLAMP(CEILING(min_pixels / horizontal_pixels, 1), 1, screen_max_rows)
+
+ var/overrun = FALSE
+ if(used > our_volume)
+ // congratulations we are now in overrun mode. everything will be crammed to minimum storage pixels.
+ to_chat(user, "[parent] rendered in overrun mode due to more items inside than the maximum volume supports.")
+ overrun = TRUE
+
+ // how much we are using
+ var/using_horizontal_pixels = horizontal_pixels * rows
+
+ // item padding
+ using_horizontal_pixels -= padding_pixels
+
+ // define outside for marginal performance boost
+ var/obj/item/I
+ // start at this pixel from screen_start_x.
+ var/current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
+ var/row = 1
+
+ LAZYINITLIST(ui_item_blocks)
+
+ for(var/i in percentage_by_item)
+ I = i
+ var/percent = percentage_by_item[I]
+ if(!ui_item_blocks[I])
+ ui_item_blocks[I] = new /obj/screen/storage/volumetric_box/center(null, src, I)
+ var/obj/screen/storage/volumetric_box/center/B = ui_item_blocks[I]
+ var/pixels_to_use = overrun? MINIMUM_PIXELS_PER_ITEM : max(using_horizontal_pixels * percent, MINIMUM_PIXELS_PER_ITEM)
+ var/addrow = FALSE
+ if(CEILING(pixels_to_use, 1) >= FLOOR(horizontal_pixels - current_pixel - VOLUMETRIC_STORAGE_EDGE_PADDING, 1))
+ pixels_to_use = horizontal_pixels - current_pixel - VOLUMETRIC_STORAGE_EDGE_PADDING
+ addrow = TRUE
+
+ // now that we have pixels_to_use, place our thing and add it to the returned list.
+
+ B.screen_loc = I.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + VOLUMETRIC_STORAGE_ITEM_PADDING, 1)],[screen_start_y+row-1]:[screen_pixel_y]"
+ // add the used pixels to pixel after we place the object
+ current_pixel += pixels_to_use + VOLUMETRIC_STORAGE_ITEM_PADDING
+
+ // set various things
+ B.set_pixel_size(pixels_to_use)
+ B.layer = VOLUMETRIC_STORAGE_BOX_LAYER
+ B.plane = VOLUMETRIC_STORAGE_BOX_PLANE
+ B.name = I.name
+
+ I.mouse_opacity = MOUSE_OPACITY_ICON
+ I.maptext = ""
+ I.layer = VOLUMETRIC_STORAGE_ITEM_LAYER
+ I.plane = VOLUMETRIC_STORAGE_ITEM_PLANE
+
+ // finally add our things.
+ . += B.on_screen_objects()
+ . += I
+
+ // go up a row if needed
+ if(addrow)
+ row++
+ current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
+
+ // Then, continuous section.
+ ui_continuous = get_ui_continuous()
+ ui_continuous.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+maxcolumns-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]"
+ . += ui_continuous
+ // Then, left.
+ ui_left = get_ui_left()
+ 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
+ // Then, closer, which is also our right element.
+ ui_close = get_ui_close()
+ ui_close.screen_loc = "[screen_start_x + maxcolumns]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x + maxcolumns]:[screen_pixel_x],[screen_start_y + row - 1]:[screen_pixel_y]"
+ . += ui_close
+
+/**
+ * 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, override = TRUE)
+ if(M.active_storage != src)
+ if(M.active_storage)
+ M.active_storage.ui_hide(M)
+ M.active_storage = src
+ LAZYOR(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(src, 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
+ UnregisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT)
+ M.client.screen -= list(ui_boxes, ui_close, ui_left, 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_ITEMS) && !display_numerical_stacking
+
+/**
+ * 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)
+ // get both the box and the item
+ . += ui_item_blocks[i]
+ . += i
+
+/**
+ * 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_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/game/atoms.dm b/code/game/atoms.dm
index 266d5b5bb5..44a891e24a 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -584,11 +584,8 @@
stoplag(1)
qdel(progress)
to_chat(user, "You dump as much of [src_object.parent]'s contents into [STR.insert_preposition]to [src] as you can.")
- STR.orient2hud(user)
- src_object.orient2hud(user)
if(user.active_storage) //refresh the HUD to show the transfered contents
- user.active_storage.close(user)
- user.active_storage.show_to(user)
+ user.active_storage.ui_show(user)
return TRUE
/atom/proc/get_dumping_location(obj/item/storage/source,mob/user)
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 271bb7a385..22769b05b1 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -41,7 +41,11 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
var/hitsound = null
var/usesound = null
var/throwhitsound = null
+
+ /// Weight class for how much storage capacity it uses and how big it physically is meaning storages can't hold it if their maximum weight class isn't as high as it.
var/w_class = WEIGHT_CLASS_NORMAL
+ /// Volume override for the item, otherwise automatically calculated from w_class.
+ var/w_volume
/// The amount of stamina it takes to swing an item in a normal melee attack do not lie to me and say it's for realism because it ain't. If null it will autocalculate from w_class.
var/total_mass //Total mass in arbitrary pound-like values. If there's no balance reasons for an item to have otherwise, this var should be the item's weight in pounds.
@@ -864,6 +868,11 @@ GLOBAL_VAR_INIT(rpg_loot_items, FALSE)
return
return ..()
+/// Get an item's volume that it uses when being stored.
+/obj/item/proc/get_w_volume()
+ // if w_volume is 0 you fucked up anyways lol
+ return w_volume || AUTO_SCALE_VOLUME(w_class)
+
/obj/item/proc/embedded(mob/living/carbon/human/embedded_mob)
return
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..60c413c372 100644
Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index d7fa983a7c..26bb4e2e5a 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -59,7 +59,7 @@
#include "code\__DEFINES\is_helpers.dm"
#include "code\__DEFINES\jobs.dm"
#include "code\__DEFINES\language.dm"
-#include "code\__DEFINES\layers.dm"
+#include "code\__DEFINES\layers_planes.dm"
#include "code\__DEFINES\lighting.dm"
#include "code\__DEFINES\logging.dm"
#include "code\__DEFINES\machines.dm"
@@ -104,6 +104,7 @@
#include "code\__DEFINES\stat.dm"
#include "code\__DEFINES\stat_tracking.dm"
#include "code\__DEFINES\status_effects.dm"
+#include "code\__DEFINES\storage.dm"
#include "code\__DEFINES\subsystems.dm"
#include "code\__DEFINES\tgs.config.dm"
#include "code\__DEFINES\tgs.dm"
@@ -226,6 +227,7 @@
#include "code\_onclick\hud\robot.dm"
#include "code\_onclick\hud\screen_objects.dm"
#include "code\_onclick\hud\swarmer.dm"
+#include "code\_onclick\hud\screen_objects\storage.dm"
#include "code\controllers\admin.dm"
#include "code\controllers\configuration_citadel.dm"
#include "code\controllers\controller.dm"
@@ -437,6 +439,7 @@
#include "code\datums\components\fantasy\prefixes.dm"
#include "code\datums\components\fantasy\suffixes.dm"
#include "code\datums\components\storage\storage.dm"
+#include "code\datums\components\storage\ui.dm"
#include "code\datums\components\storage\concrete\_concrete.dm"
#include "code\datums\components\storage\concrete\bag_of_holding.dm"
#include "code\datums\components\storage\concrete\bluespace.dm"