diff --git a/beestation.dme b/beestation.dme index b2d63768a1b02..b821820446fe3 100644 --- a/beestation.dme +++ b/beestation.dme @@ -1903,6 +1903,7 @@ #include "code\modules\admin\view_variables\topic_basic.dm" #include "code\modules\admin\view_variables\topic_list.dm" #include "code\modules\admin\view_variables\view_variables.dm" +#include "code\modules\admin\view_variables\vv_ghost.dm" #include "code\modules\antagonists\eldritch.dm" #include "code\modules\antagonists\toddsie.dm" #include "code\modules\antagonists\_common\antag_datum.dm" diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index f91c1f44bad1c..beb339be799f5 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -18,7 +18,7 @@ GLOBAL_VAR_INIT(magic_appearance_detecting_image, new /image) // appearances are // The filters list has the same ref type id as a filter, but isnt one and also isnt a list, so we have to check if the thing has Cut() instead GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur"))) -#define isfilter(thing) (!hascall(thing, "Cut") && TYPEID(thing) == GLOB.refid_filter) +#define isfilter(thing) (!islist(thing) && hascall(thing, "Cut") && TYPEID(thing) == GLOB.refid_filter) GLOBAL_DATUM_INIT(regex_rgb_text, /regex, regex(@"^#?(([0-9a-fA-F]{8})|([0-9a-fA-F]{6})|([0-9a-fA-F]{3}))$")) #define iscolortext(thing) (istext(thing) && GLOB.regex_rgb_text.Find(thing)) diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 37b857bba8133..7602316bbbeac 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -32,8 +32,7 @@ #define VV_MSG_EDITED "
Var Edited" #define VV_MSG_DELETED "
Deleted" -#define VV_NORMAL_LIST_NO_EXPAND_THRESHOLD 50 -#define VV_SPECIAL_LIST_NO_EXPAND_THRESHOLD 150 +#define VV_BIG_SIZED_LIST_THRESHOLD 50 //#define IS_VALID_ASSOC_KEY(V) (istext(V) || ispath(V) || isdatum(V) || islist(V)) #define IS_VALID_ASSOC_KEY(V) (!isnum_safe(V)) //hhmmm.. @@ -45,6 +44,9 @@ #define VV_HREF_TARGETREF(targetref, href_key, text) "[text]" #define VV_HREF_TARGET_1V(target, href_key, text, varname) "[text]" //for stuff like basic varedits, one variable #define VV_HREF_TARGETREF_1V(targetref, href_key, text, varname) "[text]" +//! Non-standard helper for special list vv. this doesn't use VV_HK_TARGET and REF because special list doesn't work in a sane sense. +#define VV_HREF_SPECIAL(dmlist_origin_ref, href_action, text, list_index, dmlist_varname) "[text]" +#define VV_HREF_SPECIAL_MENU(dmlist_origin_ref, href_action, dmlist_varname) "?_src_=vars;[HrefToken()];[href_action]=TRUE;[VV_HK_DO_LIST_EDIT]=TRUE;dmlist_origin_ref=[dmlist_origin_ref];dmlist_varname=[dmlist_varname]" #define GET_VV_TARGET locate(href_list[VV_HK_TARGET]) #define GET_VV_VAR_TARGET href_list[VV_HK_VARNAME] @@ -71,6 +73,9 @@ #define VV_HK_LIST_SHUFFLE "listshuffle" #define VV_HK_LIST_SET_LENGTH "listlen" +// I exist alone here just for special list edit. God, why. +#define VV_HK_DO_LIST_EDIT "do_vv_list_edit" + // vv_do_basic() keys #define VV_HK_BASIC_EDIT "datumedit" #define VV_HK_BASIC_CHANGE "datumchange" @@ -182,3 +187,42 @@ /// ALWAYS render a reduced list, useful for fuckoff big datums that need to be condensed for the sake of client load #define VV_ALWAYS_CONTRACT_LIST (1<<0) +#define VV_READ_ONLY (1<<1) + + +#define VV_LIST_PROTECTED (1) /// Can not vv the list. Doing vv this list is not safe. +#define VV_LIST_READ_ONLY (2) /// Can vv the list, but can not edit. +#define VV_LIST_EDITABLE (3) /// Can vv the list, and edit. + +// Becomes read only at live, editable at debug, dynamically +#ifdef DEBUG +#define VV_LIST_READ_ONLY___DEBUG_EDITABLE (3) +#else +#define VV_LIST_READ_ONLY___DEBUG_EDITABLE (2) +#endif + +/// A list of all the special byond lists that need to be handled different by vv. +/// manually adding var name is recommanded. +GLOBAL_LIST_INIT(vv_special_lists, list( + // /datum + "vars" = VV_LIST_READ_ONLY, + // /atom + "overlays" = VV_LIST_EDITABLE, + "underlays" = VV_LIST_EDITABLE, + "vis_contents" = VV_LIST_EDITABLE, + "vis_locs" = VV_LIST_READ_ONLY___DEBUG_EDITABLE, + "contents" = VV_LIST_EDITABLE, + "locs" = VV_LIST_READ_ONLY___DEBUG_EDITABLE, + "verbs" = VV_LIST_READ_ONLY___DEBUG_EDITABLE, // verb is not safe to edit in live server + "filters" = VV_LIST_PROTECTED, // This is not good to change in vv, yet. + // /client + "bounds" = VV_LIST_PROTECTED, // DM document says it's read-only. Better not to edit this. + "images" = VV_LIST_EDITABLE, + "screen" = VV_LIST_EDITABLE, +)) +// NOTE: this is highly attached to how /datum/vv_ghost works. + + +#ifndef DEBUG +GLOBAL_PROTECT(vv_special_lists) // changing this in live server is a bad idea +#endif diff --git a/code/_globalvars/lists/admin.dm b/code/_globalvars/lists/admin.dm index aa7075fd54dda..4d46c5358641d 100644 --- a/code/_globalvars/lists/admin.dm +++ b/code/_globalvars/lists/admin.dm @@ -8,19 +8,3 @@ GLOBAL_LIST_INIT_TYPED(smite_list, /datum/smite, init_smites()) GLOBAL_VAR_INIT(admin_notice, "") // Admin notice that all clients see when joining the server - -// A list of all the special byond lists that need to be handled different by vv -GLOBAL_LIST_INIT(vv_special_lists, init_special_list_names()) - -/proc/init_special_list_names() - var/list/output = list() - var/obj/sacrifice = new - for(var/varname in sacrifice.vars) - var/value = sacrifice.vars[varname] - if(!islist(value)) - if(!isdatum(value) && hascall(value, "Cut")) - output += varname - continue - if(isnull(locate(REF(value)))) - output += varname - return output diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm index df2673666bc22..4a14f26c7d54d 100644 --- a/code/modules/admin/view_variables/debug_variables.dm +++ b/code/modules/admin/view_variables/debug_variables.dm @@ -1,20 +1,57 @@ #define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing ) + +// defines of hints for how a proc should build strings +#define STYLE_READ_ONLY (1) +#define STYLE_NORMAL (2) +#define STYLE_LIST (3) +#define STYLE_SPECIAL (4) +#define STYLE_EMPTY (5) + /// Get displayed variable in VV variable list /proc/debug_variable(name, value, level, datum/owner, sanitize = TRUE, display_flags = NONE) //if D is a list, name will be index, and value will be assoc value. + // variables to store values + var/index + var/list/owner_list + var/datum/vv_ghost/vv_spectre + + // ------------------------------------------------------------ + // checks if a thing is /list, or /vv_ghost to deliver a special list, and then reassign name/value if(owner) - if(islist(owner)) - var/index = name - var/list/owner_list = owner + if(istype(owner, /datum/vv_ghost)) + vv_spectre = owner + if(islist(owner) || vv_spectre) + index = name + owner_list = vv_spectre?.dmlist_holder || owner if (value) name = owner_list[name] //name is really the index until this line else value = owner_list[name] - . = "
  • ([VV_HREF_TARGET_1V(owner_list, VV_HK_LIST_EDIT, "E", index)]) ([VV_HREF_TARGET_1V(owner_list, VV_HK_LIST_CHANGE, "C", index)]) ([VV_HREF_TARGET_1V(owner_list, VV_HK_LIST_REMOVE, "-", index)]) " - else + + // ------------------------------------------------------------ + // Builds hyperlink strings with edit options + var/special_list_secure_level = (istext(name) && (isdatum(owner) || vv_spectre) ) ? GLOB.vv_special_lists[name] : null + var/is_read_only = CHECK_BITFIELD(display_flags, VV_READ_ONLY) || (special_list_secure_level && (special_list_secure_level <= VV_LIST_READ_ONLY)) + var/hyperlink_style =\ + (is_read_only && level) ? STYLE_EMPTY \ + : is_read_only ? STYLE_READ_ONLY \ + : vv_spectre ? STYLE_SPECIAL \ + : owner_list ? STYLE_LIST \ + : owner ? STYLE_NORMAL \ + : STYLE_EMPTY + + switch(hyperlink_style) + if(STYLE_READ_ONLY) + . = "
  • (Disabled) " + if(STYLE_NORMAL) . = "
  • ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_EDIT, "E", name)]) ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_CHANGE, "C", name)]) ([VV_HREF_TARGET_1V(owner, VV_HK_BASIC_MASSEDIT, "M", name)]) " - else - . = "
  • " + if(STYLE_LIST) + . = "
  • ([VV_HREF_TARGET_1V(owner_list, VV_HK_LIST_EDIT, "E", index)]) ([VV_HREF_TARGET_1V(owner_list, VV_HK_LIST_CHANGE, "C", index)]) ([VV_HREF_TARGET_1V(owner_list, VV_HK_LIST_REMOVE, "-", index)]) " + if(STYLE_SPECIAL) + . = "
  • ([VV_HREF_SPECIAL(vv_spectre.dmlist_origin_ref, VV_HK_LIST_EDIT, "E", index, vv_spectre.dmlist_varname)]) ([VV_HREF_SPECIAL(vv_spectre.dmlist_origin_ref, VV_HK_LIST_CHANGE, "C", index, vv_spectre.dmlist_varname)]) ([VV_HREF_SPECIAL(vv_spectre.dmlist_origin_ref, VV_HK_LIST_REMOVE, "-", index, vv_spectre.dmlist_varname)]) " + if(STYLE_EMPTY) + . = "
  • " + // ------------------------------------------------------------ var/name_part = VV_HTML_ENCODE(name) if(level > 0 || islist(owner)) //handling keys in assoc lists if(istype(name,/datum)) @@ -30,7 +67,7 @@ return "[.][item]
  • " // This is split into a seperate proc mostly to make errors that happen not break things too much -/proc/_debug_variable_value(name, value, level, datum/owner, sanitize, display_flags) +/proc/_debug_variable_value(name, datum/value, level, datum/owner, sanitize, display_flags) . = "DISPLAY_ERROR: ([value] [REF(value)])" // Make sure this line can never runtime if(isnull(value)) @@ -60,9 +97,12 @@ var/image/image = value return "[image.type] ([get_appearance_vv_summary_name(image)]) [REF(value)]" - if(isfilter(value)) - var/datum/filter_value = value - return "/filter ([filter_value.type] [REF(filter_value)])" + // fun fact: there are two types of /filters. `/filters(/filters(), /filters(), ...)` + // isfilter() doesn't know if it's a parent filter(that has [/filters]s inside of itself), or a child filter + var/isfilter = isfilter(value) + var/is_child_filter = isfilter && !isdatum(owner) && !isappearance(owner) // 'child_filter' means each /filters in /atom.filters + if(is_child_filter) + return "/filters\[child\] ([value.type])" if(isfile(value)) return "'[value]'" @@ -71,17 +111,37 @@ var/datum/datum_value = value return datum_value.debug_variable_value(name, level, owner, sanitize, display_flags) - if(islist(value) || (name in GLOB.vv_special_lists)) // Some special lists arent detectable as a list through istype + var/special_list_secure_level = (istext(name) && isdatum(owner)) ? GLOB.vv_special_lists[name] : null + var/islist = islist(value) || special_list_secure_level + if(islist) var/list/list_value = value - var/list/items = list() - - // This is becuse some lists either dont count as lists or a locate on their ref will return null - var/link_vars = "Vars=[REF(value)]" - if(name in GLOB.vv_special_lists) - link_vars = "Vars=[REF(owner)];special_varname=[name]" - if (!(display_flags & VV_ALWAYS_CONTRACT_LIST) && list_value.len > 0 && list_value.len <= (IS_NORMAL_LIST(list_value) ? VV_NORMAL_LIST_NO_EXPAND_THRESHOLD : VV_SPECIAL_LIST_NO_EXPAND_THRESHOLD)) - for (var/i in 1 to list_value.len) + var/list_type = \ + isfilter ? "/filters\[parent\]" \ + : special_list_secure_level ? "/special_list" \ + : /list + + // Hyperlink to open a /list window. + var/a_open = null + var/a_close = null + + // some '/list' instance is dangerous to open. + var/can_open_list_window = !( (special_list_secure_level == VV_LIST_PROTECTED) || isappearance(owner) ) + if(can_open_list_window) + var/href_reference_string = \ + special_list_secure_level \ + ? "dmlist_origin_ref=[REF(owner)];dmlist_varname=[name]" \ + : "Vars=[REF(value)]" + a_open = "" + a_close = "" + + var/should_fold_list_items = (display_flags & VV_ALWAYS_CONTRACT_LIST) || length(list_value) > VV_BIG_SIZED_LIST_THRESHOLD + if(can_open_list_window && should_fold_list_items) + return "[a_open][list_type] ([length(list_value)])[a_close]" + else + var/flag = (special_list_secure_level && (special_list_secure_level <= VV_LIST_READ_ONLY)) ? VV_READ_ONLY : null + var/list/items = list() + for (var/i in 1 to length(list_value)) var/key = list_value[i] var/val if (IS_NORMAL_LIST(list_value) && !isnum(key)) @@ -90,11 +150,9 @@ val = key key = i - items += debug_variable(key, val, level + 1, sanitize = sanitize) + items += debug_variable(key, val, level + 1, sanitize = sanitize, display_flags = flag) - return "/list ([list_value.len])" - else - return "/list ([list_value.len])" + return "[a_open][list_type] ([length(list_value)])[a_close]" if(name in GLOB.bitfields) var/list/flags = list() @@ -130,3 +188,8 @@  "} //TODO link to modify_transform wrapper for all matrices #undef VV_HTML_ENCODE +#undef STYLE_READ_ONLY +#undef STYLE_NORMAL +#undef STYLE_LIST +#undef STYLE_SPECIAL +#undef STYLE_EMPTY diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm index f9fa46664469f..4deac4d4a6389 100644 --- a/code/modules/admin/view_variables/topic.dm +++ b/code/modules/admin/view_variables/topic.dm @@ -4,17 +4,37 @@ if( (usr.client != src) || !src.holder || !holder.CheckAdminHref(href, href_list)) return var/target = GET_VV_TARGET + var/vv_refresh_target /// If this var has a reference, vv window will be auto-refreshed + vv_do_basic(target, href_list, href) - if(istype(target, /datum)) - var/datum/D = target - D.vv_do_topic(href_list) + // for non-standard special list + if(href_list["dmlist_origin_ref"]) + var/datum/located = locate(href_list["dmlist_origin_ref"]) + var/dmlist_varname = href_list["dmlist_varname"] + if(!isdatum(located) || !GLOB.vv_special_lists[dmlist_varname] || !(dmlist_varname in located.vars)) + return + if(GET_VV_VAR_TARGET || href_list[VV_HK_DO_LIST_EDIT]) // if href_list["targetvar"] exists, we do vv_edit to list. if not, it's just viewing. + vv_do_list(located.vars[dmlist_varname], href_list) + GLOB.vv_ghost.mark_special(href_list["dmlist_origin_ref"], dmlist_varname) + vv_refresh_target = GLOB.vv_ghost + // for standard /list else if(islist(target)) vv_do_list(target, href_list) - if(href_list["Vars"]) - var/datum/vars_target = locate(href_list["Vars"]) - if(href_list["special_varname"]) // Some special vars can't be located even if you have their ref, you have to use this instead - vars_target = vars_target.vars[href_list["special_varname"]] - debug_variables(vars_target) + GLOB.vv_ghost.mark_list(target) + vv_refresh_target = GLOB.vv_ghost + // for standard /datum + else if(istype(target, /datum)) + var/datum/D = target + D.vv_do_topic(href_list) + + // if there is no `href_list["target"]`, we check `href_list["Vars"]` to see if we want see it + if(!target && !vv_refresh_target) + vv_refresh_target = locate(href_list["Vars"]) + // "Vars" means we want to view-variables this thing. + + if(vv_refresh_target) + debug_variables(vv_refresh_target) + return //Stuff below aren't in dropdowns/etc. @@ -122,11 +142,3 @@ log_admin(log_msg) admin_ticket_log(L, "[log_msg]") vv_update_display(L, Text, "[newamt]") - - - //Finally, refresh if something modified the list. - if(href_list["datumrefresh"]) - var/datum/DAT = locate(href_list["datumrefresh"]) - if(istype(DAT, /datum) || istype(DAT, /client) || islist(DAT)) - debug_variables(DAT) - diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index 5765199b84bb9..b17d2a219d924 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -36,7 +36,7 @@ if(!target) to_chat(usr, "The object you tried to expose to [C] no longer exists (nulled or hard-deled)") return - message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a VV window") + message_admins("[key_name_admin(usr)] Showed [key_name_admin(C)] a VV window") log_admin("Admin [key_name(usr)] Showed [key_name(C)] a VV window of a [target]") to_chat(C, "[holder.fakekey ? "an Administrator" : "[usr.client.key]"] has granted you access to view a View Variables window") C.debug_variables(target) diff --git a/code/modules/admin/view_variables/topic_list.dm b/code/modules/admin/view_variables/topic_list.dm index 3922254288b4b..4a3c4b01b2711 100644 --- a/code/modules/admin/view_variables/topic_list.dm +++ b/code/modules/admin/view_variables/topic_list.dm @@ -1,7 +1,24 @@ //LISTS - CAN NOT DO VV_DO_TOPIC BECAUSE LISTS AREN'T DATUMS :( /client/proc/vv_do_list(list/target, href_list) + if(IsAdminAdvancedProcCall()) + to_chat(usr, "Advanced ProcCall detected - You shouldn't call /vv_do_list() directly.") + return var/target_index = text2num(GET_VV_VAR_TARGET) if(check_rights(R_VAREDIT)) + var/dmlist_varname = href_list["dmlist_varname"] + if(dmlist_varname) + var/dmlist_secure_level = GLOB.vv_special_lists[dmlist_varname] + if(isnull(dmlist_secure_level)) // href protection to make sure + log_admin("[key_name(src)] attempted to edit a special list ([dmlist_varname]), but this doesn't exist.") + return + else if(dmlist_secure_level == VV_LIST_EDITABLE) + log_world("### vv_do_list() called: [src] attempted to edit a special list ([dmlist_varname]) Security-level:[dmlist_secure_level](allowed)") + log_admin("[key_name(src)] attempted to edit a special list ([dmlist_varname]) Security-level:[dmlist_secure_level](allowed)") + else // fuck you exploiters + log_world("### vv_do_list() called: [src] attempted to edit a special list ([dmlist_varname]), but denied due to the Security-level:[dmlist_secure_level]") + log_admin("[key_name(src)] attempted to edit a special list ([dmlist_varname]), but denied due to the Security-level:[dmlist_secure_level]") + message_admins("[key_name_admin(src)] attempted to edit a special list ([dmlist_varname]), but denied due to the Security-level:[dmlist_secure_level]. Bonk this guy.") + return if(target_index) if(href_list[VV_HK_LIST_EDIT]) mod_list(target, null, "list", "contents", target_index, autodetect_class = TRUE) diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm index a330c337b7fa1..accf08eef4c98 100644 --- a/code/modules/admin/view_variables/view_variables.dm +++ b/code/modules/admin/view_variables/view_variables.dm @@ -1,6 +1,13 @@ #define ICON_STATE_CHECKED 1 /// this dmi is checked. We don't check this one anymore. #define ICON_STATE_NULL 2 /// this dmi has null-named icon_state, allowing it to show a sprite on vv editor. +// defines of hints for how a proc should build output data +#define STYLE_DATUM (1) +#define STYLE_APPEARANCE (2) +#define STYLE_READ_ONLY_LIST (3) +#define STYLE_LIST (4) +#define STYLE_SPECIAL_LIST (5) + /client/proc/debug_variables(datum/thing in world) set category = "Debug" set name = "View Variables" @@ -17,17 +24,95 @@ var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/simple/vv) asset_cache_datum.send(usr) - var/isappearance = isappearance(thing) - var/islist = islist(thing) || (!isdatum(thing) && hascall(thing, "Cut")) // Some special lists dont count as lists, but can be detected by if they have list procs + + // -------------------------------------------------------------- + // ------------ Preparation part ------------ + // -------------------------------------------------------------- + + // vv_ghost part. exotique abyss code. + var/static/datum/vv_ghost/vv_spectre = new() /// internal purpose + var/special_list_secure_level /// secure level of a special list + if(thing == GLOB.vv_ghost) + if(GLOB.vv_ghost.dmlist_origin_ref) + thing = vv_spectre.deliver_special() + special_list_secure_level = GLOB.vv_special_lists[vv_spectre.dmlist_varname] + if(special_list_secure_level == VV_LIST_PROTECTED) // investigating this is not recommended. force return. + vv_spectre.reset() + return + else if(GLOB.vv_ghost.list_holder) + thing = vv_spectre.deliver_list() + else + return // vv_ghost is not meant to be vv'ed + + + // Prepares often-used-values into variables + var/isappearance = isappearance(thing) // TG has a version of handling /appearance stuff, by mirroring the appearance. Our version is accessing /appearance directly. Just be noted. + var/islist = islist(thing) || special_list_secure_level // dm internal special list isn't detectable by 'islist()', but having 'secure_level' means it's detected + if(!islist && !isdatum(thing) && !isappearance) return - - var/title = "" + var/refid = REF(thing) - var/icon/sprite - var/hash - var/type = islist? /list : (isappearance ? "/appearance" : thing.type) + // Prepares '/fake_type' for better readibility. + var/type = \ + isappearance ? "/appearance" \ + : vv_spectre.dmlist_varname ? "/special_list ([vv_spectre.dmlist_varname])" \ + : islist ? /list \ + : thing.type + + // special_list flag + var/read_only_special_list = (special_list_secure_level && (special_list_secure_level <= VV_LIST_READ_ONLY)) + + // Hints how this debug proc will write output data + var/debug_output_style = \ + isappearance ? STYLE_APPEARANCE \ + : read_only_special_list ? STYLE_READ_ONLY_LIST \ + : special_list_secure_level ? STYLE_SPECIAL_LIST \ + : islist ? STYLE_LIST \ + : STYLE_DATUM + + + // ------------------------------------------------------ + // ------------ Building output data --------------- + // ------------------------------------------------------ + + // Builds text: basic info + var/title = "[thing] ([refid]) = [type]" + var/formatted_type = replacetext("[type]", "/", "/") + var/ref_line = "@[copytext(refid, 2, -1)]" // get rid of the brackets, add a @ prefix for copy pasting in asay + + var/list/header + switch(debug_output_style) + if(STYLE_DATUM) + header = thing.vv_get_header() + if(STYLE_APPEARANCE) + header = vv_get_header_appearance(thing) + if(STYLE_LIST, STYLE_SPECIAL_LIST, STYLE_READ_ONLY_LIST) + header = list("/list") + + // Builds text: tells if a datum we're editing has some flags + var/marked_line + var/tagged_line + if(holder) + if(holder.marked_datum && holder.marked_datum == thing) + marked_line = VV_MSG_MARKED + if(LAZYFIND(holder.tagged_datums, thing)) + var/tag_index = LAZYFIND(holder.tagged_datums, thing) + tagged_line = VV_MSG_TAGGED(tag_index) + + var/varedited_line + var/deleted_line + if(!islist) + if(thing.datum_flags & DF_VAR_EDITED) + varedited_line = VV_MSG_EDITED + if(thing.gc_destroyed) + deleted_line = VV_MSG_DELETED + + + // ------------------------------------------------------ + // Builds icon info: shows icon image on vv window + var/icon/sprite var/no_icon = FALSE if(isatom(thing)) @@ -40,6 +125,7 @@ // This list remembers which dmi has null icon_state, to determine if icon_state=null should display a sprite // (NOTE: icon_state="" is correct, but saying null is obvious) var/static/list/dmi_nullstate_checklist = list() + var/image/image_object = thing var/icon_filename_text = "[image_object.icon]" // "icon(null)" type can exist. textifying filters it. if(icon_filename_text) @@ -56,83 +142,110 @@ if(dmi_nullstate_checklist[icon_filename_text] == ICON_STATE_NULL) sprite = icon(image_object.icon, image_object.icon_state) + var/sprite_hash var/sprite_text if(sprite) - hash = md5(sprite) - src << browse_rsc(sprite, "vv[hash].png") - sprite_text = no_icon ? "\[NO ICON\]" : "" - - title = "[thing] ([REF(thing)]) = [type]" - var/formatted_type = replacetext("[type]", "/", "/") - - var/list/header = islist ? list("/list") : (isappearance ? vv_get_header_appearance(thing) : thing.vv_get_header()) - - var/ref_line = "@[copytext(refid, 2, -1)]" // get rid of the brackets, add a @ prefix for copy pasting in asay - - var/marked_line - if(holder && holder.marked_datum && holder.marked_datum == thing) - marked_line = VV_MSG_MARKED - var/tagged_line - if(holder && LAZYFIND(holder.tagged_datums, thing)) - var/tag_index = LAZYFIND(holder.tagged_datums, thing) - tagged_line = VV_MSG_TAGGED(tag_index) - var/varedited_line - if(!islist && (thing.datum_flags & DF_VAR_EDITED)) - varedited_line = VV_MSG_EDITED - var/deleted_line - if(!islist && thing.gc_destroyed) - deleted_line = VV_MSG_DELETED - - var/list/dropdownoptions - if (islist) - dropdownoptions = list( - "---", - "Add Item" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ADD), - "Remove Nulls" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ERASE_NULLS), - "Remove Dupes" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ERASE_DUPES), - "Set len" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SET_LENGTH), - "Shuffle" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SHUFFLE), - "Show VV To Player" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_EXPOSE), - "---" + sprite_hash = md5(sprite) + src << browse_rsc(sprite, "vv[sprite_hash].png") + sprite_text = no_icon ? "\[NO ICON\]" : "" + + + // ------------------------------------------------------ + // Builds dropdown-options + var/list/dropdown_options + switch(debug_output_style) + if(STYLE_DATUM) + dropdown_options = thing.vv_get_dropdown() + if(STYLE_APPEARANCE) + dropdown_options = vv_get_dropdown_appearance(thing) + if(STYLE_READ_ONLY_LIST) + dropdown_options = list( + "---", + "Show VV To Player" = VV_HREF_SPECIAL_MENU(vv_spectre.dmlist_origin_ref, VV_HK_EXPOSE, vv_spectre.dmlist_varname), + "---" + ) + if(STYLE_SPECIAL_LIST) + dropdown_options = list( + "---", + "Add Item" = VV_HREF_SPECIAL_MENU(vv_spectre.dmlist_origin_ref, VV_HK_LIST_ADD, vv_spectre.dmlist_varname), + "Remove Nulls" = VV_HREF_SPECIAL_MENU(vv_spectre.dmlist_origin_ref, VV_HK_LIST_ERASE_NULLS, vv_spectre.dmlist_varname), + "Show VV To Player" = VV_HREF_SPECIAL_MENU(vv_spectre.dmlist_origin_ref, VV_HK_EXPOSE, vv_spectre.dmlist_varname), + "---" ) - for(var/i in 1 to length(dropdownoptions)) - var/name = dropdownoptions[i] - var/link = dropdownoptions[name] - dropdownoptions[i] = "" - else if(isappearance) - dropdownoptions = vv_get_dropdown_appearance(thing) - else - dropdownoptions = thing.vv_get_dropdown() - - var/list/names = list() - if(isappearance) - var/static/list/virtual_appearance_vars = build_virtual_appearance_vars() - names = virtual_appearance_vars.Copy() - else if(!islist) - for(var/varname in thing.vars) - names += varname + if(STYLE_LIST) + dropdown_options = list( + "---", + "Add Item" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ADD), + "Remove Nulls" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ERASE_NULLS), + "Remove Dupes" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_ERASE_DUPES), + "Set len" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SET_LENGTH), + "Shuffle" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_LIST_SHUFFLE), + "Show VV To Player" = VV_HREF_TARGETREF_INTERNAL(refid, VV_HK_EXPOSE), + "---" + ) + // Finalize dropdown-options for /list + if(islist) + for(var/idx in 1 to length(dropdown_options)) + var/assoc_key = dropdown_options[idx] + var/assoc_val = dropdown_options[assoc_key] + var/href_string = assoc_val ? "value='[assoc_val]'" : null + dropdown_options[idx] = "" + + + // ------------------------------------------------------ + // Builds var-name list: gathers names of each variable in the thing you're editing. + var/list/varname_list = list() + switch(debug_output_style) + if(STYLE_DATUM) + for(var/each_varname in thing.vars) + varname_list += each_varname + if(STYLE_APPEARANCE) + var/static/list/virtual_appearance_vars = build_virtual_appearance_vars() + varname_list = virtual_appearance_vars.Copy() + // Does nothing to LIST STYLE defines sleep(1 TICKS) var/list/variable_html = list() - if(islist) - var/list/list_value = thing - for(var/i in 1 to list_value.len) - var/key = list_value[i] - var/value - if(IS_NORMAL_LIST(list_value) && IS_VALID_ASSOC_KEY(key)) - value = list_value[key] - variable_html += debug_variable(i, value, 0, list_value) - else if(isappearance) - names = sort_list(names) - for(var/varname in names) - variable_html += debug_variable_appearance(varname, thing) - else - names = sort_list(names) - for(var/varname in names) - if(thing.can_vv_get(varname)) - variable_html += thing.vv_get_var(varname) - + switch(debug_output_style) + if(STYLE_DATUM) + varname_list = sort_list(varname_list) + for(var/each_varname in varname_list) + if(thing.can_vv_get(each_varname)) + variable_html += thing.vv_get_var(each_varname) + if(STYLE_APPEARANCE) + varname_list = sort_list(varname_list) + for(var/each_varname in varname_list) + variable_html += debug_variable_appearance(each_varname, thing) + if(STYLE_LIST, STYLE_SPECIAL_LIST, STYLE_READ_ONLY_LIST) + // There is only VV_READ_ONLY for now + var/list_flags = (read_only_special_list ? VV_READ_ONLY : null) + // If TRUE, instead of sending actual '/special_list' instance, we send 'vv_spectre' which delegates that /special_list + var/should_delegate_list = (special_list_secure_level ? TRUE : FALSE) + + var/list/list_value = thing + for(var/i in 1 to list_value.len) + var/key = list_value[i] + var/value + if(IS_NORMAL_LIST(list_value) && IS_VALID_ASSOC_KEY(key)) + value = list_value[key] + variable_html += debug_variable(i, value, 0, (should_delegate_list ? vv_spectre : thing), display_flags = list_flags) + + // ------------------------------------------------------ + // Builds text: 'href string' based on the existence of 'vv_spectre' (which remembers actual refID of a special list) + var/href_reference_string = \ + vv_spectre.dmlist_varname \ + ? "dmlist_origin_ref=[vv_spectre.dmlist_origin_ref];dmlist_varname=[vv_spectre.dmlist_varname]" \ + : "Vars=[refid]" + /* + href key "Vars" only does refreshing. I hate that name because it's contextless. + "dmlist_origin_ref" and "dmlist_varname" must exist at the same time, to access a special list directly, because such special list is not possible to be accessed through 'locate(refID)' + You can't access /client/images (internal variable) by 'locate(that_client_images_list_ref)'. Yes, This sucks + */ + + + // ------------------------------------------------------ + // Builds html text - finalization var/html = {" @@ -261,14 +374,13 @@
    - Refresh + Refresh
    @@ -307,6 +419,9 @@ datumrefresh=[refid];[HrefToken()]'>Refresh "} + + // Resets vv_spectre, and shows it to user + vv_spectre.reset() src << browse(html, "window=variables[refid];size=475x650") /client/proc/vv_update_display(datum/thing, span, content) @@ -314,3 +429,9 @@ datumrefresh=[refid];[HrefToken()]'>Refresh #undef ICON_STATE_CHECKED #undef ICON_STATE_NULL + +#undef STYLE_DATUM +#undef STYLE_APPEARANCE +#undef STYLE_READ_ONLY_LIST +#undef STYLE_LIST +#undef STYLE_SPECIAL_LIST diff --git a/code/modules/admin/view_variables/vv_ghost.dm b/code/modules/admin/view_variables/vv_ghost.dm new file mode 100644 index 0000000000000..580b3cb07756b --- /dev/null +++ b/code/modules/admin/view_variables/vv_ghost.dm @@ -0,0 +1,104 @@ +GLOBAL_PROTECT(vv_ghost) +GLOBAL_DATUM_INIT(vv_ghost, /datum/vv_ghost, new) // Fake datum for vv debug_variables() proc. Am I real? + +/* + < What the hell is this vv_ghost? > + Our view-variables client proc doesn't like to investigate list() instances. + This means `debug_variables(list_reference)` won't work, because it only wants /datum - but /list is not /datum + vv_ghost exists to trick the proc, by storing values to bypass /datum restriction of the proc. + but also, it exists to deliever some special list that isn't possible to get through locate(). + < Can you just do `locate([0x0_list_ref_id])`? > + Only an ordinary /list is possible to be located through locate() + First, vv takes values from 'href' texts. + Of course, we can get ref_id of a special list, + BUT `locate(ref_id_of_special_list)` returns null. (only ordinary /list works this) + This is why we need to store 'dmlist_origin_ref', and 'dmlist_varname' + We locate(dmlist_origin_ref), then access their special list from their var list. + => dmlist_holder.vars[dmlist_varname] + < Summary > + Two usages exist: + 1. Store a list_ref into this datum, to deliver the list into the vv debugging system. + 2. Store a datum's ref_id with target varname, to deliver the special list into the vv debugging system. +*/ + +/datum/vv_ghost + // --- variables for vv special list --- + /// Ref ID of a thing. + var/dmlist_origin_ref + /// which var of the reference you're s eeing + var/dmlist_varname + /// instance holder for special list + var/datum/dmlist_holder + + // --- variable for ordinary lists --- + /// instance holder for normal list + var/list_holder + + // --- variable for internal use only --- + /// a failsafe variable + var/ready_to_del + +/datum/vv_ghost/New() + var/static/creation_count = 2 // to prevent something bullshit + + if(creation_count) + creation_count-- + ..() + return + + else + stack_trace("vv_ghost is not meant to be created more than 2 in the current logic. One for GLOB, one for vv internal") + ready_to_del = TRUE + qdel(src) + +/datum/vv_ghost/Destroy(force = FALSE) + if(ready_to_del || force) + reset() + return ..() + + stack_trace("Something breaks view-variables debugging tool... Check something.") + return QDEL_HINT_LETMELIVE + +/datum/vv_ghost/proc/mark_special(origin_ref, varname) + if(dmlist_origin_ref) + CRASH("vv_ghost has dmlist_origin_ref already: [dmlist_origin_ref]. It can be async issue.") + if(dmlist_varname) + CRASH("vv_ghost has dmlist_varname already: [dmlist_varname]. It can be async issue.") + dmlist_origin_ref = origin_ref + dmlist_varname = varname + +/datum/vv_ghost/proc/mark_list(actual_list) + if(list_holder) + CRASH("vv_ghost has list_ref already: [list_holder]. It can be async issue.") + list_holder = actual_list + +/// a proc that delivers values to vv_spectre (internal static one). +/// vv_spectre exists to prevent async error, just in case +/datum/vv_ghost/proc/deliver_special() + if(GLOB.vv_ghost == src) + CRASH("This proc isn't meant be called from GLOB one.") + + dmlist_origin_ref = GLOB.vv_ghost.dmlist_origin_ref // = [0x123456] + dmlist_varname = GLOB.vv_ghost.dmlist_varname // = "vis_contents" + GLOB.vv_ghost.dmlist_origin_ref = null + GLOB.vv_ghost.dmlist_varname = null + + var/datum/located = locate(dmlist_origin_ref) // = Clown [0x123456] + dmlist_holder = located.vars[dmlist_varname] // = Clown.vis_contents + return dmlist_holder + +/// a proc that delivers values to vv_spectre (internal static one). +/// vv_spectre exists to prevent async error, just in case +/datum/vv_ghost/proc/deliver_list() + if(GLOB.vv_ghost == src) + CRASH("This proc isn't meant be called from GLOB one.") + + var/return_target = GLOB.vv_ghost.list_holder + GLOB.vv_ghost.list_holder = null + return return_target + +/datum/vv_ghost/proc/reset() + dmlist_origin_ref = null + dmlist_varname = null + dmlist_holder = null + list_holder = null