diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm
index a04f02bd6a..86a3bd4c31 100644
--- a/code/__DEFINES/vv.dm
+++ b/code/__DEFINES/vv.dm
@@ -18,6 +18,7 @@
#define VV_NULL "NULL"
#define VV_RESTORE_DEFAULT "Restore to Default"
#define VV_MARKED_DATUM "Marked Datum"
+#define VV_TAGGED_DATUM "Tagged Datum"
#define VV_BITFIELD "Bitfield"
#define VV_TEXT_LOCATE "Custom Reference Locate"
#define VV_PROCCALL_RETVAL "Return Value of Proccall"
@@ -73,6 +74,7 @@
#define VV_HK_EXPOSE "expose"
#define VV_HK_CALLPROC "proc_call"
#define VV_HK_MARK "mark"
+#define VV_HK_TAG "tag"
#define VV_HK_ADDCOMPONENT "addcomponent"
#define VV_HK_MODIFY_TRAITS "modtraits"
#define VV_HK_VIEW_REFERENCES "viewreferences"
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index 6e8d1de955..9a75660b7a 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -58,7 +58,10 @@
if(modifiers["shift"] && modifiers["ctrl"])
return CtrlShiftClickOn(A)
if(modifiers["middle"])
- return MiddleClickOn(A)
+ if(modifiers["ctrl"])
+ return CtrlMiddleClickOn(A)
+ else
+ return MiddleClickOn(A)
if(modifiers["shift"] && (client && client.show_popup_menus || modifiers["right"])) //CIT CHANGE - makes shift-click examine use right click instead of left click in combat mode
return ShiftClickOn(A)
if(modifiers["alt"]) // alt and alt-gr (rightalt)
@@ -356,6 +359,14 @@
return TRUE
else
return ..()
+
+/mob/proc/CtrlMiddleClickOn(atom/A)
+ if(check_rights_for(client, R_ADMIN))
+ client.toggle_tag_datum(A)
+ else
+ A.CtrlClick(src)
+ return
+
/*
Alt click
Used as an alternate way to interact with things.
diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm
index 78fc32480f..dfab3b5160 100644
--- a/code/_onclick/observer.dm
+++ b/code/_onclick/observer.dm
@@ -24,7 +24,10 @@
CtrlShiftClickOn(A)
return
if(modifiers["middle"])
- MiddleClickOn(A)
+ if(modifiers["ctrl"])
+ CtrlMiddleClickOn(A)
+ else
+ MiddleClickOn(A)
return
if(modifiers["shift"])
ShiftClickOn(A)
diff --git a/code/datums/datumvars.dm b/code/datums/datumvars.dm
index 0bb803ddb3..976ccb6b6c 100644
--- a/code/datums/datumvars.dm
+++ b/code/datums/datumvars.dm
@@ -27,6 +27,7 @@
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")
VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 05c68bfdcf..47be7353fe 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -18,6 +18,7 @@ GLOBAL_PROTECT(admin_verbs_default)
/client/proc/cmd_admin_pm_panel, /*admin-pm list*/
/client/proc/stop_sounds,
/client/proc/mark_datum_mapview,
+ /client/proc/tag_datum_mapview,
/client/proc/debugstatpanel
// /client/proc/fix_air /*resets air in designated radius to its default atmos composition*/
)
@@ -88,7 +89,8 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/addbunkerbypass, //CIT
/client/proc/revokebunkerbypass, //CIT
/datum/admins/proc/open_borgopanel,
- /datum/admins/proc/change_laws //change AI laws
+ /datum/admins/proc/change_laws, //change AI laws
+ /datum/admins/proc/display_tags,
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/DB_ban_panel, /client/proc/stickybanpanel))
GLOBAL_PROTECT(admin_verbs_ban)
diff --git a/code/modules/admin/holder2.dm b/code/modules/admin/holder2.dm
index 1430d5d77a..4e97c6fd59 100644
--- a/code/modules/admin/holder2.dm
+++ b/code/modules/admin/holder2.dm
@@ -30,6 +30,9 @@ GLOBAL_PROTECT(href_token)
var/datum/filter_editor/filteriffic
+ /// A lazylist of tagged datums, for quick reference with the View Tags verb
+ var/list/tagged_datums
+
/datum/admins/CanProcCall(procname)
. = ..()
if(!check_rights(R_SENSITIVE))
diff --git a/code/modules/admin/tag.dm b/code/modules/admin/tag.dm
new file mode 100644
index 0000000000..b7fc297cd1
--- /dev/null
+++ b/code/modules/admin/tag.dm
@@ -0,0 +1,132 @@
+/**
+ * Inserts the target_datum into [/datum/admins/var/tagged_datums], for later reference.
+ *
+ * Arguments:
+ * * target_datum - The datum you want to create a tag for
+ */
+/datum/admins/proc/add_tagged_datum(datum/target_datum)
+ if(LAZYFIND(tagged_datums, target_datum))
+ to_chat(owner, span_warning("[target_datum] is already tagged!"))
+ return
+
+ LAZYADD(tagged_datums, target_datum)
+ RegisterSignal(target_datum, COMSIG_PARENT_QDELETING, .proc/handle_tagged_del, override = TRUE)
+ to_chat(owner, span_notice("[target_datum] has been tagged."))
+
+/// Get ahead of the curve with deleting
+/datum/admins/proc/handle_tagged_del(datum/source)
+ SIGNAL_HANDLER
+
+ // No point, we don't even care about it anymore.
+ if(!LAZYFIND(tagged_datums, source))
+ return
+
+ if(owner)
+ to_chat(owner, span_boldnotice("Tagged datum [source] ([source.type]) has been deleted."))
+ remove_tagged_datum(source, silent = TRUE)
+
+/**
+ * Attempts to remove the specified datum from [/datum/admins/var/tagged_datums] if it exists
+ *
+ * Arguments:
+ * * target_datum - The datum you want to remove from the tagged_datums list
+ * * silent - If TRUE, won't print messages to the owner's chat
+ */
+/datum/admins/proc/remove_tagged_datum(datum/target_datum, silent=FALSE)
+ if(!istype(target_datum))
+ return
+
+ if(LAZYFIND(tagged_datums, target_datum))
+ LAZYREMOVE(tagged_datums, target_datum)
+ if(!silent)
+ to_chat(owner, span_notice("[target_datum] has been untagged."))
+ else if(!silent)
+ to_chat(owner, span_warning("[target_datum] was not already tagged."))
+
+/// Quick define for readability
+#define TAGS_REFRESH "Refresh"
+#define TAGS_CLEAR "Untag all"
+
+#define TAG_DEL(X) "(UNTAG)"
+#define TAG_MARK(X) "(MARK)"
+#define TAG_SIMPLE_HEALTH(X) "Health: [X.health]"
+#define TAG_CARBON_HEALTH(X) "Health: [X.health] (\
+ [X.getBruteLoss()] \
+ [X.getFireLoss()] \
+ [X.getToxLoss()] \
+ [X.getOxyLoss()]\
+ [X.getCloneLoss() ? " [X.getCloneLoss()]" : ""])"
+
+/// Display all of the tagged datums
+/datum/admins/proc/display_tags()
+ set category = "Admin.Game"
+ set name = "View Tags"
+
+ if (!istype(src, /datum/admins))
+ src = usr.client.holder
+ if (!istype(src, /datum/admins))
+ to_chat(usr, "Error: you are not an admin!", confidential = TRUE)
+ return
+
+ var/index = 0
+ var/list/dat = list("
Tag Menu
")
+
+ dat += "
[TAGS_REFRESH] | [TAGS_CLEAR]
"
+ if(LAZYLEN(tagged_datums))
+ for(var/datum/iter_datum as anything in tagged_datums)
+ index++
+ var/specific_info
+
+ if(isnull(iter_datum))
+ dat += "\t[index]: Null reference - Check runtime logs!"
+ stack_trace("Null datum found in tagged datum menu! User: [usr]")
+ continue
+ else if(iscarbon(iter_datum))
+ var/mob/living/carbon/resolved_carbon = iter_datum
+ specific_info = "[TAG_CARBON_HEALTH(resolved_carbon)] | [AREACOORD(resolved_carbon)] [ADMIN_PP(iter_datum)] [ADMIN_FLW(iter_datum)]"
+ else if(isliving(iter_datum))
+ var/mob/living/resolved_living = iter_datum
+ specific_info = "[TAG_SIMPLE_HEALTH(resolved_living)] | [AREACOORD(resolved_living)] [ADMIN_PP(iter_datum)] [ADMIN_FLW(iter_datum)]"
+ else if(ismob(iter_datum))
+ var/atom/resolved_atom = iter_datum // needed for ADMIN_JMP
+ specific_info = "[AREACOORD(resolved_atom)] [ADMIN_PP(iter_datum)] [ADMIN_FLW(iter_datum)]"
+ else if(ismovable(iter_datum))
+ var/atom/resolved_atom = iter_datum // needed for ADMIN_JMP
+ specific_info = "[AREACOORD(resolved_atom)] [ADMIN_FLW(iter_datum)]"
+ else if(isatom(iter_datum))
+ var/atom/resolved_atom = iter_datum // needed for ADMIN_JMP
+ specific_info = "[AREACOORD(resolved_atom)] [ADMIN_JMP(resolved_atom)]"
+ else if(istype(iter_datum, /datum/controller/subsystem))
+ var/datum/controller/subsystem/resolved_subsystem = iter_datum
+ specific_info = "[resolved_subsystem.stat_entry()]"
+ // else, it's just a /datum
+
+ dat += "\t[index]: [iter_datum] | [specific_info] | [ADMIN_VV(iter_datum)]| [TAG_DEL(iter_datum)] | [iter_datum == marked_datum ? "Marked" : TAG_MARK(iter_datum)] "
+ dat += "\t([iter_datum.type])"
+ else
+ dat += "No datums tagged :("
+
+ dat = dat.Join("
")
+ usr << browse(dat, "window=tag;size=800x480")
+
+/**
+ * Clears tagged datums
+ */
+/datum/admins/proc/clear_tags()
+ var/amount_of_tags = LAZYLEN(tagged_datums)
+ if(!amount_of_tags)
+ to_chat(owner, span_warning("There are no tagged datums to clear."))
+ return
+ var/confirmation = alert(usr, "Clear your tagged datums? ([amount_of_tags])", "Confirmation", "Yes", "No")
+ if(confirmation != "Yes")
+ return
+ LAZYNULL(tagged_datums)
+ to_chat(owner, span_notice("Tagged datums have been cleared, [amount_of_tags] item\s removed."))
+
+#undef TAGS_REFRESH
+#undef TAGS_CLEAR
+
+#undef TAG_DEL
+#undef TAG_MARK
+#undef TAG_SIMPLE_HEALTH
+#undef TAG_CARBON_HEALTH
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index cc143dcad0..c6b2437da4 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2705,6 +2705,31 @@
log_query_debug("[usr.key] | [response]")
else if(answer == "no")
log_query_debug("[usr.key] | Reported no server hang")
+ else if(href_list["del_tag"])
+ if(!check_rights(R_ADMIN))
+ return
+ var/datum/datum_to_remove = locate(href_list["del_tag"])
+ if(!datum_to_remove)
+ return
+ return remove_tagged_datum(datum_to_remove)
+
+ else if(href_list["show_tags"])
+ if(!check_rights(R_ADMIN))
+ return
+ return display_tags()
+
+ else if(href_list["clear_tags"])
+ if(!check_rights(R_ADMIN))
+ return
+ return clear_tags()
+
+ else if(href_list["mark_datum"])
+ if(!check_rights(R_ADMIN))
+ return
+ var/datum/datum_to_mark = locate(href_list["mark_datum"])
+ if(!datum_to_mark)
+ return
+ return usr.client?.mark_datum(datum_to_mark)
/datum/admins/proc/HandleCMode()
if(!check_rights(R_ADMIN))
diff --git a/code/modules/admin/view_variables/get_variables.dm b/code/modules/admin/view_variables/get_variables.dm
index 3f90002edc..1457731ee1 100644
--- a/code/modules/admin/view_variables/get_variables.dm
+++ b/code/modules/admin/view_variables/get_variables.dm
@@ -77,6 +77,15 @@
markstring = "[VV_MARKED_DATUM] (CURRENT: [(istype(holder) && istype(holder.marked_datum))? holder.marked_datum.type : "NULL"])"
classes += markstring
+ var/list/tagstrings = new
+ if(!(VV_TAGGED_DATUM in restricted_classes) && holder && LAZYLEN(holder.tagged_datums))
+ var/i = 0
+ for(var/datum/iter_tagged_datum as anything in holder.tagged_datums)
+ i++
+ var/new_tagstring = "[VV_TAGGED_DATUM] #[i]: [iter_tagged_datum.type])"
+ tagstrings[new_tagstring] = iter_tagged_datum
+ classes += new_tagstring
+
if(restricted_classes)
classes -= restricted_classes
@@ -87,6 +96,11 @@
if(holder && holder.marked_datum && .["class"] == markstring)
.["class"] = VV_MARKED_DATUM
+ if(holder && tagstrings[.["class"]])
+ var/datum/chosen_datum = tagstrings[.["class"]]
+ .["value"] = chosen_datum
+ .["class"] = VV_TAGGED_DATUM
+
switch(.["class"])
if(VV_TEXT)
.["value"] = input("Enter new text:", "Text", current_value) as null|text
@@ -202,6 +216,11 @@
.["class"] = null
return
+ if(VV_TAGGED_DATUM)
+ if(.["value"] == null)
+ .["class"] = null
+ return
+
if(VV_PROCCALL_RETVAL)
var/list/get_retval = list()
callproc_blocking(get_retval)
diff --git a/code/modules/admin/view_variables/tag_datum.dm b/code/modules/admin/view_variables/tag_datum.dm
new file mode 100644
index 0000000000..3b611e3cdf
--- /dev/null
+++ b/code/modules/admin/view_variables/tag_datum.dm
@@ -0,0 +1,18 @@
+/client/proc/tag_datum(datum/target_datum)
+ if(!holder || QDELETED(target_datum))
+ return
+ holder.add_tagged_datum(target_datum)
+
+/client/proc/toggle_tag_datum(datum/target_datum)
+ if(!holder || !target_datum)
+ return
+
+ if(LAZYFIND(holder.tagged_datums, target_datum))
+ holder.remove_tagged_datum(target_datum)
+ else
+ holder.add_tagged_datum(target_datum)
+
+/client/proc/tag_datum_mapview(datum/target_datum as mob|obj|turf|area in view(view))
+ set category = "Debug"
+ set name = "Tag Datum"
+ tag_datum(target_datum)
diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm
index 043c50173d..230c86dd2d 100644
--- a/code/modules/admin/view_variables/topic_basic.dm
+++ b/code/modules/admin/view_variables/topic_basic.dm
@@ -59,6 +59,8 @@
if(href_list[VV_HK_MARK])
usr.client.mark_datum(target)
+ if(href_list[VV_HK_TAG])
+ usr.client.tag_datum(target)
if(href_list[VV_HK_ADDCOMPONENT])
if(!check_rights(NONE))
return
diff --git a/code/modules/keybindings/keybind/admin.dm b/code/modules/keybindings/keybind/admin.dm
index b63221e97a..74b8431f30 100644
--- a/code/modules/keybindings/keybind/admin.dm
+++ b/code/modules/keybindings/keybind/admin.dm
@@ -94,3 +94,16 @@
/datum/keybinding/admin/readmin/down(client/user)
user.readmin()
return TRUE
+
+/datum/keybinding/admin/view_tags
+ hotkey_keys = list("F9")
+ name = "view_tags"
+ full_name = "View Tags"
+ description = "Open the View-Tags menu"
+
+/datum/keybinding/admin/view_tags/down(client/user)
+ . = ..()
+ if(.)
+ return
+ user.holder?.display_tags()
+ return TRUE
diff --git a/tgstation.dme b/tgstation.dme
index bb585d8b07..a279fc4a0c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1499,6 +1499,7 @@
#include "code\modules\admin\sound_emitter.dm"
#include "code\modules\admin\sql_message_system.dm"
#include "code\modules\admin\stickyban.dm"
+#include "code\modules\admin\tag.dm"
#include "code\modules\admin\topic.dm"
#include "code\modules\admin\whitelist.dm"
#include "code\modules\admin\callproc\callproc.dm"
@@ -1549,6 +1550,7 @@
#include "code\modules\admin\view_variables\mass_edit_variables.dm"
#include "code\modules\admin\view_variables\modify_variables.dm"
#include "code\modules\admin\view_variables\reference_tracking.dm"
+#include "code\modules\admin\view_variables\tag_datum.dm"
#include "code\modules\admin\view_variables\topic.dm"
#include "code\modules\admin\view_variables\topic_basic.dm"
#include "code\modules\admin\view_variables\topic_list.dm"