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]
- . = "
@@ -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