diff --git a/code/__DEFINES/_helpers.dm b/code/__DEFINES/_helpers.dm
new file mode 100644
index 0000000000..384f6c064e
--- /dev/null
+++ b/code/__DEFINES/_helpers.dm
@@ -0,0 +1,38 @@
+// Stuff that is relatively "core" and is used in other defines/helpers
+
+//Returns the hex value of a decimal number
+//len == length of returned string
+// #define num2hex(X, len) num2text(X, len, 16) -- NOT YET
+
+//Returns an integer given a hex input, supports negative values "-ff"
+//skips preceding invalid characters
+// #define hex2num(X) text2num(X, 16) -- NO
+
+/// Stringifies whatever you put into it.
+#define STRINGIFY(argument) #argument
+
+/// subtypesof(), typesof() without the parent path
+#define subtypesof(typepath) ( typesof(typepath) - typepath )
+
+/// Until a condition is true, sleep
+#define UNTIL(X) while(!(X)) stoplag()
+
+/// Sleep if we haven't been deleted
+/// Otherwise, return
+#define SLEEP_NOT_DEL(time) \
+ if(QDELETED(src)) { \
+ return; \
+ } \
+ sleep(time);
+
+/// Takes a datum as input, returns its ref string
+#define text_ref(datum) ref(datum)
+
+// Refs contain a type id within their string that can be used to identify byond types.
+// Custom types that we define don't get a unique id, but this is useful for identifying
+// types that don't normally have a way to run istype() on them.
+#define TYPEID(thing) copytext(REF(thing), 4, 6)
+
+/// A null statement to guard against EmptyBlock lint without necessitating the use of pass()
+/// Used to avoid proc-call overhead. But use sparingly. Probably pointless in most places.
+#define EMPTY_BLOCK_GUARD ;
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 640079e9b8..fb8ebd3d08 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -7,8 +7,21 @@
#define isatom(A) (isloc(A))
+#define isdatum(thing) (istype(thing, /datum))
+
#define isweakref(D) (istype(D, /datum/weakref))
+#define isimage(thing) (istype(thing, /image))
+
+GLOBAL_VAR_INIT(magic_appearance_detecting_image, new /image) // appearances are awful to detect safely, but this seems to be the best way ~ninjanomnom
+#define isappearance(thing) (!isimage(thing) && !ispath(thing) && istype(GLOB.magic_appearance_detecting_image, thing))
+
+// 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 isgenerator(A) (istype(A, /generator))
+
//Turfs
//#define isturf(A) (istype(A, /turf)) This is actually a byond built-in. Added here for completeness sake.
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index edd9149fa3..0b051af156 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -211,10 +211,6 @@ GLOBAL_LIST_EMPTY(bloody_footprints_cache)
#define POLLTYPE_IRV "IRV"
-
-//subtypesof(), typesof() without the parent path
-#define subtypesof(typepath) ( typesof(typepath) - typepath )
-
//Gets the turf this atom inhabits
#define get_turf(A) (get_step(A, 0))
@@ -550,8 +546,6 @@ GLOBAL_LIST_INIT(pda_reskins, list(
#define NIGHTSHIFT_AREA_DEPARTMENT_HALLS 3 //interior hallways, etc
#define NIGHTSHIFT_AREA_NONE 4 //default/highest.
-#define UNTIL(X) while(!(X)) stoplag()
-
//Scavenging element defines for special loot "events".
#define SCAVENGING_FOUND_NOTHING "found_nothing"
diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index 86a3bd4c31..198265b041 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -24,6 +24,7 @@
#define VV_PROCCALL_RETVAL "Return Value of Proccall"
#define VV_MSG_MARKED " Marked Object"
+#define VV_MSG_TAGGED(num) " Tagged Datum #[num]"
#define VV_MSG_EDITED " Var Edited"
#define VV_MSG_DELETED " Deleted"
diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm
index e4ac2fe140..381ddc7d4a 100644
--- a/code/_globalvars/bitfields.dm
+++ b/code/_globalvars/bitfields.dm
@@ -471,6 +471,19 @@ DEFINE_BITFIELD(vis_flags, list(
"VIS_UNDERLAY" = VIS_UNDERLAY,
))
+// I am so sorry. Required because vis_flags is both undefinable and unreadable on mutable_appearance
+// But we need to display them anyway. See /mutable_appearance/appearance_mirror
+DEFINE_BITFIELD(_vis_flags, list(
+ "VIS_HIDE" = VIS_HIDE,
+ "VIS_INHERIT_DIR" = VIS_INHERIT_DIR,
+ "VIS_INHERIT_ICON" = VIS_INHERIT_ICON,
+ "VIS_INHERIT_ICON_STATE" = VIS_INHERIT_ICON_STATE,
+ "VIS_INHERIT_ID" = VIS_INHERIT_ID,
+ "VIS_INHERIT_LAYER" = VIS_INHERIT_LAYER,
+ "VIS_INHERIT_PLANE" = VIS_INHERIT_PLANE,
+ "VIS_UNDERLAY" = VIS_UNDERLAY,
+))
+
DEFINE_BITFIELD(visor_flags, list(
"ALLOWINTERNALS" = ALLOWINTERNALS,
"BLOCK_GAS_SMOKE_EFFECT" = BLOCK_GAS_SMOKE_EFFECT,
diff --git a/code/modules/admin/view_variables/debug_variable_appearance.dm b/code/modules/admin/view_variables/debug_variable_appearance.dm
new file mode 100644
index 0000000000..3ceaf2a50b
--- /dev/null
+++ b/code/modules/admin/view_variables/debug_variable_appearance.dm
@@ -0,0 +1,101 @@
+/// Shows a header name on top when you investigate an appearance/image
+/image/vv_get_header()
+ . = list()
+ var/icon_name = icon ? copytext("[icon]", findlasttext("[icon]", "/") + 1) : "null"
+ . += "[icon_name] "
+ if(icon)
+ . += icon_state ? "\"[icon_state]\"" : "(icon_state = null)"
+
+/// Makes nice short vv names for images
+/image/debug_variable_value(name, level, datum/owner, sanitize, display_flags)
+ var/display_name = "[type]"
+ if("[src]" != "[type]") // If we have a name var, let's use it.
+ display_name = "[src] [type]"
+
+ var/display_value
+ var/list/icon_file_name = splittext("[icon]", "/")
+ if(length(icon_file_name))
+ display_value = icon_file_name[length(icon_file_name)]
+ else
+ display_value = "null"
+
+ if(icon_state)
+ display_value = "[display_value]:[icon_state]"
+
+ var/display_ref = get_vv_link_ref()
+ return "[display_name] ([display_value]) [display_ref]"
+
+/// Returns the ref string to use when displaying this image in the vv menu of something else
+/image/proc/get_vv_link_ref()
+ return REF(src)
+
+// It is endlessly annoying to display /appearance directly for stupid byond reasons, so we copy everything we care about into a holder datum
+// That we can override procs on and store other vars on and such.
+/mutable_appearance/appearance_mirror
+ // So people can see where it came from
+ var/appearance_ref
+ // vis flags can't be displayed by mutable appearances cause it don't make sense as overlays, but appearances do carry them
+ // can't use the name either for byond reasons
+ var/_vis_flags
+
+// all alone at the end of the universe
+GLOBAL_DATUM_INIT(pluto, /atom/movable, new /atom/movable(null))
+
+// arg is actually an appearance, typed as mutable_appearance as closest mirror
+/mutable_appearance/appearance_mirror/New(mutable_appearance/appearance_father)
+ . = ..() // /mutable_appearance/New() copies over all the appearance vars MAs care about by default
+ // We copy over our appearance onto an atom. This is done so we can read vars carried by but not accessible on appearances
+ GLOB.pluto.appearance = appearance_father
+ _vis_flags = GLOB.pluto.vis_flags
+ appearance_ref = REF(appearance_father)
+
+// This means if the appearance loses refs before a click it's gone, but that's consistent to other datums so it's fine
+// Need to ref the APPEARANCE because we just free on our own, which sorta fucks this operation up you know?
+/mutable_appearance/appearance_mirror/get_vv_link_ref()
+ return appearance_ref
+
+/mutable_appearance/appearance_mirror/can_vv_get(var_name)
+ var/static/datum/beloved = new()
+ if(beloved.vars.Find(var_name)) // If datums have it, get out
+ return FALSE
+ // If it is one of the two args on /image, yeet (I am sorry)
+ if(var_name == NAMEOF(src, realized_overlays))
+ return FALSE
+ if(var_name == NAMEOF(src, realized_underlays))
+ return FALSE
+ // Filtering out the stuff I know we don't care about
+ if(var_name == NAMEOF(src, x))
+ return FALSE
+ if(var_name == NAMEOF(src, y))
+ return FALSE
+ if(var_name == NAMEOF(src, z))
+ return FALSE
+ // Could make an argument for these but I think they will just confuse people, so yeeet
+#ifndef SPACEMAN_DMM // Spaceman doesn't believe in contents on appearances, sorry lads
+ if(var_name == NAMEOF(src, contents))
+ return FALSE
+#endif
+ if(var_name == NAMEOF(src, loc))
+ return FALSE
+ if(var_name == NAMEOF(src, vis_contents))
+ return FALSE
+ return ..()
+
+/mutable_appearance/appearance_mirror/vv_get_var(var_name)
+ // No editing for you
+ var/value = vars[var_name]
+ return "
"
+
+/mutable_appearance/appearance_mirror/vv_get_dropdown()
+ SHOULD_CALL_PARENT(FALSE)
+
+ . = list()
+ VV_DROPDOWN_OPTION("", "---")
+ VV_DROPDOWN_OPTION(VV_HK_CALLPROC, "Call Proc")
+ VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object")
+ VV_DROPDOWN_OPTION(VV_HK_TAG, "Tag Datum")
+ VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete")
+ VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
+
+/proc/get_vv_appearance(mutable_appearance/appearance) // actually appearance yadeeyada
+ return new /mutable_appearance/appearance_mirror(appearance)
diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm
index a7c191de58..ddb372551a 100644
--- a/code/modules/admin/view_variables/debug_variables.dm
+++ b/code/modules/admin/view_variables/debug_variables.dm
@@ -1,96 +1,125 @@
#define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing )
/// Get displayed variable in VV variable list
-/proc/debug_variable(name, value, level, datum/D, sanitize = TRUE) //if D is a list, name will be index, and value will be assoc value.
- var/header
- if(D)
- if(islist(D))
+/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.
+ if(owner)
+ if(islist(owner))
+ var/list/list_owner = owner
var/index = name
if (value)
- name = D[name] //name is really the index until this line
+ name = list_owner[name] //name is really the index until this line
else
- value = D[name]
- header = "
"
+
+// 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)
+ if(isappearance(value))
+ value = get_vv_appearance(value)
+
+ . = "DISPLAY_ERROR: ([value] [REF(value)])" // Make sure this line can never runtime
+
+ if(isnull(value))
+ return "null"
+
+ if(istext(value))
+ return "\"[VV_HTML_ENCODE(value)]\""
+
+ if(isicon(value))
#ifdef VARSICON
- var/icon/I = icon(value)
+ var/icon/icon_value = icon(value)
var/rnd = rand(1,10000)
- var/rname = "tmp[REF(I)][rnd].png"
- usr << browse_rsc(I, rname)
- item = "[name_part] = ([value]) "
+ var/rname = "tmp[REF(icon_value)][rnd].png"
+ usr << browse_rsc(icon_value, rname)
+ return "([value]) "
#else
- item = "[name_part] = /icon ([value])"
+ return "/icon ([value])"
#endif
- else if (isfile(value))
- item = "[name_part] = '[value]'"
+ if(isfilter(value))
+ var/datum/filter_value = value
+ return "/filter ([filter_value.type] [REF(filter_value)])"
- else if(istype(value,/matrix)) // Needs to be before datum
- var/matrix/M = value
- item = {"[name_part] =
-
-
-
-
[M.a]
[M.d]
0
-
[M.b]
[M.e]
0
-
[M.c]
[M.f]
1
-
-
"} //TODO link to modify_transform wrapper for all matrices
- else if (istype(value, /datum))
- var/datum/DV = value
- if ("[DV]" != "[DV.type]") //if the thing as a name var, lets use it.
- item = "[name_part] = [DV] [DV.type] [REF(value)]"
- else
- item = "[name_part] = [DV.type] [REF(value)]"
+ if(isfile(value))
+ return "'[value]'"
- else if (islist(value))
- var/list/L = value
- var/list/items = list()
+ if(isdatum(value))
+ var/datum/datum_value = value
+ return datum_value.debug_variable_value(name, level, owner, sanitize, display_flags)
- if (L.len > 0 && !(name == "underlays" || name == "overlays" || L.len > (IS_NORMAL_LIST(L) ? VV_NORMAL_LIST_NO_EXPAND_THRESHOLD : VV_SPECIAL_LIST_NO_EXPAND_THRESHOLD)))
- for (var/i in 1 to L.len)
- var/key = L[i]
+ if(islist(value)) // || (name in GLOB.vv_special_lists)) // Some special lists arent detectable as a list through istype
+ 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/key = list_value[i]
var/val
- if (IS_NORMAL_LIST(L) && !isnum(key))
- val = L[key]
- if (isnull(val)) // we still want to display non-null false values, such as 0 or ""
+ if (IS_NORMAL_LIST(list_value) && !isnum(key))
+ val = list_value[key]
+ if (isnull(val)) // we still want to display non-null false values, such as 0 or ""
val = key
key = i
items += debug_variable(key, val, level + 1, sanitize = sanitize)
- item = "[name_part] = /list ([L.len])
"
+ else */
+ return "/list ([list_value.len])"
- else if (name in GLOB.bitfields)
+ if(name in GLOB.bitfields)
var/list/flags = list()
for (var/i in GLOB.bitfields[name])
if (value & GLOB.bitfields[name][i])
flags += i
- item = "[name_part] = [VV_HTML_ENCODE(jointext(flags, ", "))]"
+ if(length(flags))
+ return "[VV_HTML_ENCODE(jointext(flags, ", "))]"
+ else
+ return "NONE"
else
- item = "[name_part] = [VV_HTML_ENCODE(value)]"
+ return "[VV_HTML_ENCODE(value)]"
- return "[header][item]"
+/datum/proc/debug_variable_value(name, level, datum/owner, sanitize, display_flags)
+ if("[src]" != "[type]") // If we have a name var, let's use it.
+ return "[src] [type] [REF(src)]"
+ else
+ return "[type] [REF(src)]"
+
+/datum/weakref/debug_variable_value(name, level, datum/owner, sanitize, display_flags)
+ . = ..()
+ return "[.] (Resolve)"
+
+/matrix/debug_variable_value(name, level, datum/owner, sanitize, display_flags)
+ return {"
+
+
+
+
[a]
[d]
0
+
[b]
[e]
0
+
[c]
[f]
1
+
+
"} //TODO link to modify_transform wrapper for all matrices
#undef VV_HTML_ENCODE
diff --git a/code/modules/admin/view_variables/view_variables.dm b/code/modules/admin/view_variables/view_variables.dm
index a7faea2553..0ddec6f147 100644
--- a/code/modules/admin/view_variables/view_variables.dm
+++ b/code/modules/admin/view_variables/view_variables.dm
@@ -1,55 +1,87 @@
-/client/proc/debug_variables(datum/D in world)
+#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.
+
+/client/proc/debug_variables(datum/thing in world)
set category = "Debug"
set name = "View Variables"
//set src in world
var/static/cookieoffset = rand(1, 9999) //to force cookies to reset after the round.
- if(!usr.client || !usr.client.holder) //This is usr because admins can call the proc on other clients, even if they're not admins, to show them VVs.
- to_chat(usr, "You need to be an administrator to access this.", confidential = TRUE)
+ if(!usr.client || !usr.client.holder) //This is usr because admins can call the proc on other clients, even if they're not admins, to show them VVs.
+ to_chat(usr, span_danger("You need to be an administrator to access this."), confidential = TRUE)
return
- if(!D)
+ if(!thing)
return
var/datum/asset/asset_cache_datum = get_asset_datum(/datum/asset/simple/vv)
asset_cache_datum.send(usr)
- var/islist = islist(D)
- if(!islist && !istype(D))
+ if(isappearance(thing))
+ thing = get_vv_appearance(thing) // this is /mutable_appearance/our_bs_subtype
+ 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
+ if(!islist && !isdatum(thing))
return
var/title = ""
- var/refid = REF(D)
+ var/refid = REF(thing)
var/icon/sprite
var/hash
- var/type = islist? /list : D.type
+ var/type = islist ? /list : thing.type
var/no_icon = FALSE
- if(istype(D, /atom))
- sprite = getFlatIcon(D)
- if(sprite)
- hash = md5(sprite)
- src << browse_rsc(sprite, "vv[hash].png")
- else
+ if(isatom(thing))
+ sprite = getFlatIcon(thing)
+ if(!sprite)
no_icon = TRUE
- title = "[D] ([REF(D)]) = [type]"
- var/formatted_type = replacetext("[type]", "/", "/")
+ else if(isimage(thing))
+ // icon_state=null shows first image even if dmi has no icon_state for null name.
+ // 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)
+ if(image_object.icon_state)
+ sprite = icon(image_object.icon, image_object.icon_state)
+
+ else // it means: icon_state=""
+ if(!dmi_nullstate_checklist[icon_filename_text])
+ dmi_nullstate_checklist[icon_filename_text] = ICON_STATE_CHECKED
+ if("" in icon_states(image_object.icon))
+ // this dmi has nullstate. We'll allow "icon_state=null" to show image.
+ dmi_nullstate_checklist[icon_filename_text] = ICON_STATE_NULL
+
+ if(dmi_nullstate_checklist[icon_filename_text] == ICON_STATE_NULL)
+ sprite = icon(image_object.icon, image_object.icon_state)
var/sprite_text
if(sprite)
- sprite_text = no_icon? "\[NO ICON\]" : "