From 7ec669ecd493940f383f08f19f2518fbd4782301 Mon Sep 17 00:00:00 2001 From: kevinz000 <2003111+kevinz000@users.noreply.github.com> Date: Tue, 7 Apr 2020 16:11:58 -0700 Subject: [PATCH] bet --- code/__DEFINES/dcs/signals.dm | 1 + code/__DEFINES/storage.dm | 2 + code/_onclick/hud/screen_objects.dm | 38 --- code/_onclick/hud/screen_objects/storage.dm | 41 ++++ code/datums/components/storage/storage.dm | 141 ++--------- code/datums/components/storage/ui.dm | 259 ++++++++++++++++++++ code/modules/mob/logout.dm | 1 + icons/mob/screen_gen.dmi | Bin 118425 -> 118971 bytes 8 files changed, 326 insertions(+), 157 deletions(-) create mode 100644 code/_onclick/hud/screen_objects/storage.dm create mode 100644 code/datums/components/storage/ui.dm 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 ea946f60d9c31139c6e5d3e8aed79794e0294ff0..ffbffcab0dd1b8c809830e4544b548ad075c5ae5 100644 GIT binary patch delta 7900 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 bQ*{fvrhuQ-ZehW|h5#dd6TMPh$0z>