port ADMIN_VERB and friends (#30646)

* port ADMIN_VERB and friends

* some renaming

* dumb

* one more rename

* never search and replace this codebase

* fix TM issues, more renaming

* add a static analysis to shore up user verbs

* fix double message on roundstart

* remove macro we're not using yet

* convert remaining playsounds verbs

* convert more verbs i missed somehow

* why is this a completely different signature than everything else

* fix ui_interact arg

* fix logging view and others

* buncha issues caught in TM

* fix mentor tickets ui

* fix bug report viewing

* moron
This commit is contained in:
warriorstar-orion
2025-12-12 19:18:22 +00:00
committed by GitHub
parent f89b05ee88
commit 2a842644d5
92 changed files with 1751 additions and 3113 deletions
+1 -1
View File
@@ -202,7 +202,7 @@ SUBSYSTEM_DEF(blackbox)
*
* Arguments:
* * key_type - Type of key. Either "text", "amount", "tally", "nested tally", "associative"
* * key - Key of the data to be used (EG: "admin_verb")
* * key - Key of the data to be used (EG: "user_verb")
* * stat - Either a number accumulated via "amount", "tally", or "nested tally"; or a number/string collected by "ledger" or "nested ledger".
* * data - The actual data to logged
* * overwrite - Do we want to overwrite the existing key
+9 -11
View File
@@ -481,28 +481,26 @@ SUBSYSTEM_DEF(dbcore)
item = null
// Verb that lets admins force reconnect the DB
/client/proc/reestablish_db_connection()
set category = "Debug"
set name = "Reestablish DB Connection"
USER_VERB(reestablish_db_connection, R_ADMIN, "Reestablish DB Connection", "Force a reconnection to the database.", VERB_CATEGORY_DEBUG)
if(!GLOB.configuration.database.enabled)
to_chat(usr, "<span class='warning'>The Database is not enabled in the server configuration!</span>")
to_chat(client, "<span class='warning'>The Database is not enabled in the server configuration!</span>")
return
if(SSdbcore.IsConnected())
if(!check_rights(R_DEBUG, FALSE))
to_chat(usr, "<span class='warning'>The database is already connected! (Only those with +DEBUG can force a reconnection)</span>")
if(!check_rights_client(R_DEBUG, FALSE, client))
to_chat(client, "<span class='warning'>The database is already connected! (Only those with +DEBUG can force a reconnection)</span>")
return
var/reconnect = alert("The database is already connected! If you *KNOW* that this is incorrect, you can force a reconnection", "The database is already connected!", "Force Reconnect", "Cancel")
var/reconnect = alert(client, "The database is already connected! If you *KNOW* that this is incorrect, you can force a reconnection", "The database is already connected!", "Force Reconnect", "Cancel")
if(reconnect != "Force Reconnect")
return
SSdbcore.Disconnect()
log_admin("[key_name(usr)] has forced the database to disconnect")
message_admins("[key_name_admin(usr)] has <b>forced</b> the database to disconnect!!!")
log_admin("[key_name(client)] has forced the database to disconnect")
message_admins("[key_name_admin(client)] has <b>forced</b> the database to disconnect!!!")
log_admin("[key_name(usr)] is attempting to re-establish the DB Connection")
message_admins("[key_name_admin(usr)] is attempting to re-establish the DB Connection")
log_admin("[key_name(client)] is attempting to re-establish the DB Connection")
message_admins("[key_name_admin(client)] is attempting to re-establish the DB Connection")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Force Reconnect DB") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
SSdbcore.failed_connections = 0 // Reset this
+2 -8
View File
@@ -70,14 +70,8 @@ SUBSYSTEM_DEF(debugview)
maptext_width = viewsizes[1] * world.icon_size
// Make a verb for dumping full SS stats
/client/proc/ss_breakdown()
set name = "SS Info Breakdown"
set category = "Debug"
if(!check_rights(R_DEBUG|R_VIEWRUNTIMES))
return
var/datum/browser/popup = new(usr, "ss_breakdown", "Subsystem Breakdown", 1100, 850)
USER_VERB(ss_breakdown, R_DEBUG|R_VIEWRUNTIMES, "SS Info Breakdown", "Dump stats of all subsystems", VERB_CATEGORY_DEBUG)
var/datum/browser/popup = new(client, "ss_breakdown", "Subsystem Breakdown", 1100, 850)
var/list/html = list()
html += "CPU: [round(world.cpu, 1)] | MCPU: [round(world.map_cpu, 1)] | FPS/TPS: [world.fps] | Clients: [length(GLOB.clients)] | BYOND: [world.byond_version].[world.byond_build]"
+5 -20
View File
@@ -383,12 +383,7 @@ SUBSYSTEM_DEF(garbage)
#ifdef REFERENCE_TRACKING
/datum/proc/find_refs()
set category = "Debug"
set name = "Find References"
if(!check_rights(R_DEBUG))
return
USER_VERB(find_refs, R_DEBUG, "Find References", "Find references.", VERB_CATEGORY_DEBUG)
find_references(FALSE)
/datum/proc/find_references(skip_alert)
@@ -446,23 +441,13 @@ SUBSYSTEM_DEF(garbage)
SSgarbage.can_fire = 1
SSgarbage.next_fire = world.time + world.tick_lag
/datum/proc/qdel_then_find_references()
set category = "Debug"
set name = "qdel() then Find References"
if(!check_rights(R_DEBUG))
return
qdel(src, TRUE) //force a qdel
USER_VERB(qdel_then_find_references, R_DEBUG, "qdel() then Find References", "qdel() then Find References", VERB_CATEGORY_DEBUG)
qdel(user, TRUE) //force a qdel
if(!running_find_references)
find_references(TRUE)
/datum/proc/qdel_then_if_fail_find_references()
set category = "Debug"
set name = "qdel() then Find References if GC failure"
if(!check_rights(R_DEBUG))
return
qdel_and_find_ref_if_fail(src, TRUE)
USER_VERB(qdel_then_if_fail_find_references, R_DEBUG, "qdel() then Find References if GC failure", "qdel() then Find References if GC failure", VERB_CATEGORY_DEBUG)
qdel_and_find_ref_if_fail(user, TRUE)
/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time)
if((usr?.client && !usr.client.running_find_references) || SSgarbage.ref_search_stop)
+4 -18
View File
@@ -590,30 +590,16 @@ GLOBAL_LIST_EMPTY(timers_by_proc)
*
* In-round ability to view what has created a timer, and how many times a timer for that path has been created
*/
/client/proc/timer_log()
set name = "View Timer Log"
set category = "Debug"
set desc = "Shows the log of what types created timers this round"
if(!check_rights(R_DEBUG | R_VIEWRUNTIMES))
return
USER_VERB(timer_log, R_DEBUG|R_VIEWRUNTIMES, "View Timer Log", "Shows the log of what types created timers this round", VERB_CATEGORY_DEBUG)
var/list/sorted = sortTim(GLOB.timers_by_proc, GLOBAL_PROC_REF(cmp_numeric_dsc), TRUE)
var/list/text = list("<h1>Timer Log</h1>", "<ul>")
for(var/key in sorted)
text += "<li>[key] - [sorted[key]]</li>"
text += "</ul>"
usr << browse(text.Join(), "window=timerlog")
/client/proc/debug_timers()
set name = "Debug Timers"
set category = "Debug"
set desc = "Shows currently active timers, grouped by callback"
if(!check_rights(R_DEBUG | R_VIEWRUNTIMES))
return
client << browse(text.Join(), "window=timerlog")
USER_VERB(debug_timers, R_DEBUG|R_VIEWRUNTIMES, "Debug Timers", "Shows currently active timers, grouped by callback", VERB_CATEGORY_DEBUG)
var/list/timers = list()
for(var/id in SStimer.timer_id_dict)
var/datum/timedevent/T = SStimer.timer_id_dict[id]
@@ -646,7 +632,7 @@ GLOBAL_LIST_EMPTY(timers_by_proc)
text += "<li>[key] - [sorted2[key]]</li>"
text += "</ul>"
usr << browse(text.Join(), "window=timerdebug")
client << browse(text.Join(), "window=timerdebug")
/**
+3 -9
View File
@@ -162,13 +162,7 @@ SUBSYSTEM_DEF(verb_manager)
/datum/controller/subsystem/verb_manager/Recover()
verb_queue = SSverb_manager.verb_queue
/client/proc/force_verb_bypass()
set category = "Debug"
set name = "Enable Forced Verb Execution"
if(!check_rights(R_DEBUG))
return
if(alert(src,"This will make all verbs bypass the queueing system, creating more lag. Are you absolutely sure?","Verb Manager","Yes","No") == "Yes")
USER_VERB(force_verb_bypass, R_DEBUG, "Enable Forced Verb Execution", "Enable Forced Verb Execution", VERB_CATEGORY_DEBUG)
if(alert(client, "This will make all verbs bypass the queueing system, creating more lag. Are you absolutely sure?","Verb Manager","Yes","No") == "Yes")
SSverb_manager.FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs = TRUE
message_admins("Admin [key_name_admin(usr)] has forced verbs to bypass the verb queue subsystem.")
message_admins("Admin [key_name_admin(client)] has forced verbs to bypass the verb queue subsystem.")
@@ -114,16 +114,7 @@ SUBSYSTEM_DEF(atoms)
old_initialized = SSatoms.old_initialized
BadInitializeCalls = SSatoms.BadInitializeCalls
/client/proc/debug_atom_init()
set name = "Atom Init Log"
set category = "Debug"
set desc = "Shows what failed to init this round"
if(!check_rights(R_DEBUG | R_VIEWRUNTIMES))
return
USER_VERB(debug_atom_init, R_DEBUG|R_VIEWRUNTIMES, "Atom Init Log", "Shows what failed to init this round", VERB_CATEGORY_DEBUG)
var/list/html_data = list()
html_data += "<h1>Bad Initialize() Calls</h1><table border='1'><tr><th scope='col'>Type</th><th scope='col'>Qdeleted before init</th><th scope='col'>Did not init</th><th scope='col'>Slept during init</th><th scope='col'>No init hint</th></tr>"
@@ -134,5 +125,5 @@ SUBSYSTEM_DEF(atoms)
html_data += "</table>"
usr << browse(html_data.Join(), "window=initdebug")
client << browse(html_data.Join(), "window=initdebug")
@@ -0,0 +1,154 @@
GENERAL_PROTECT_DATUM(/datum/controller/subsystem/user_verbs)
SUBSYSTEM_DEF(user_verbs)
name = "User Verbs"
flags = SS_NO_FIRE
init_order = INIT_ORDER_USER_VERBS
/// A list of all user verbs indexed by their type.
var/list/user_verbs_by_type = list()
/// A list of all user verbs indexed by their visibility flag.
var/list/user_verbs_by_visibility_flag = list()
/// A map of all associated admins and their visibility flags.
var/list/admin_visibility_flags = list()
/// A list of all admins that are pending initialization of this SS.
var/list/admins_pending_subsytem_init = list()
/datum/controller/subsystem/user_verbs/Initialize()
setup_verb_list()
process_pending_admins()
/datum/controller/subsystem/user_verbs/Recover()
user_verbs_by_type = SSuser_verbs.user_verbs_by_type
/datum/controller/subsystem/user_verbs/stat_entry(msg)
msg = "V:[length(user_verbs_by_type)]"
return ..()
/datum/controller/subsystem/user_verbs/proc/process_pending_admins()
var/list/pending_admins = admins_pending_subsytem_init
admins_pending_subsytem_init = null
for(var/admin_ckey in pending_admins)
associate(GLOB.directory[admin_ckey])
/datum/controller/subsystem/user_verbs/proc/setup_verb_list()
if(length(user_verbs_by_type))
CRASH("Attempting to set up user verbs twice!")
for(var/datum/user_verb/verb_type as anything in subtypesof(/datum/user_verb))
var/datum/user_verb/verb_singleton = new verb_type
if(!verb_singleton.__avd_check_should_exist())
qdel(verb_singleton, force = TRUE)
continue
user_verbs_by_type[verb_type] = verb_singleton
if(verb_singleton.visibility_flag)
if(!(verb_singleton.visibility_flag in user_verbs_by_visibility_flag))
user_verbs_by_visibility_flag[verb_singleton.visibility_flag] = list()
user_verbs_by_visibility_flag[verb_singleton.visibility_flag] |= list(verb_singleton)
/datum/controller/subsystem/user_verbs/proc/get_valid_verbs_for(client/admin)
if(isnull(admin.holder))
CRASH("Tried to get valid verbs for client without permissions")
var/list/has_permission = list()
for(var/permission_flag in GLOB.more_bitflags)
if(check_rights_client(permission_flag, FALSE, admin))
has_permission["[permission_flag]"] = TRUE
var/list/valid_verbs = list()
for(var/datum/user_verb/verb_type as anything in user_verbs_by_type)
var/datum/user_verb/verb_singleton = user_verbs_by_type[verb_type]
if(!verify_visibility(admin, verb_singleton))
continue
var/verb_permissions = verb_singleton.permissions
if(verb_permissions == R_NONE)
valid_verbs |= list(verb_singleton)
else for(var/permission_flag in bitfield2list(verb_permissions))
if(!has_permission["[permission_flag]"])
continue
valid_verbs |= list(verb_singleton)
return valid_verbs
/datum/controller/subsystem/user_verbs/proc/verify_visibility(client/admin, datum/user_verb/verb_singleton)
var/needed_flag = verb_singleton.visibility_flag
return !needed_flag || (needed_flag in admin_visibility_flags[admin.ckey])
/datum/controller/subsystem/user_verbs/proc/update_visibility_flag(client/admin, flag, state)
if(state)
admin_visibility_flags[admin.ckey] |= list(flag)
associate(admin)
return
admin_visibility_flags[admin.ckey] -= list(flag)
// they lost the flag, iterate over verbs with that flag and yoink em
for(var/datum/user_verb/verb_singleton as anything in user_verbs_by_visibility_flag[flag])
verb_singleton.unassign_from_client(admin)
admin.init_verbs()
/datum/controller/subsystem/user_verbs/proc/invoke_verb(client/admin, datum/user_verb/verb_type, ...)
if(IsAdminAdvancedProcCall())
message_admins("PERMISSION ELEVATION: [key_name_admin(admin)] attempted to dynamically invoke user verb '[verb_type]'.")
return
if(ismob(admin))
var/mob/mob = admin
admin = mob.client
if(!ispath(verb_type, /datum/user_verb) || verb_type == /datum/user_verb)
CRASH("Attempted to dynamically invoke user verb with invalid typepath '[verb_type]'.")
if(isnull(admin.holder))
CRASH("Attempted to dynamically invoke user verb '[verb_type]' with a non-admin.")
var/list/verb_args = args.Copy()
verb_args.Cut(2, 3)
var/datum/user_verb/verb_singleton = user_verbs_by_type[verb_type] // this cannot be typed because we need to use `:`
if(isnull(verb_singleton))
CRASH("Attempted to dynamically invoke user verb '[verb_type]' that doesn't exist.")
if(!check_rights_client(verb_singleton.permissions, FALSE, admin))
to_chat(admin, "<span class='span_adminnotice'>You lack the permissions to do this.</span>")
return
var/old_usr = usr
usr = admin.mob
// THE MACRO ENSURES THIS EXISTS. IF IT EVER DOESNT EXIST SOMEONE DIDNT USE THE DAMN MACRO!
verb_singleton.__avd_do_verb(arglist(verb_args))
usr = old_usr
SSblackbox.record_feedback("tally", "dynamic_user_verb_invocation", 1, "[verb_type]")
/**
* Assosciates and/or resyncs an admin with their accessible user verbs.
*/
/datum/controller/subsystem/user_verbs/proc/associate(client/user)
if(IsAdminAdvancedProcCall())
return
if(!isnull(admins_pending_subsytem_init)) // if the list exists we are still initializing
if(!(user.ckey in admins_pending_subsytem_init))
to_chat(user, "<span class='big green'>User Verbs are still initializing. Please wait and you will be automatically assigned your verbs when it is complete.</span>")
admins_pending_subsytem_init |= list(user.ckey)
return
// refresh their verbs
admin_visibility_flags[user.ckey] ||= list()
if(user.holder.is_localhost_autoadmin)
admin_visibility_flags[user.ckey] |= list(VERB_VISIBILITY_FLAG_LOCALHOST)
for(var/datum/user_verb/verb_singleton as anything in get_valid_verbs_for(user))
verb_singleton.assign_to_client(user)
user.init_verbs()
/**
* Unassociates an admin from their user verbs.
* Goes over all user verbs because we don't know which ones are assigned to the admin's mob without a bunch of extra bookkeeping.
* This might be a performance issue in the future if we have a lot of user verbs.
*/
/datum/controller/subsystem/user_verbs/proc/deassociate(client/user)
if(IsAdminAdvancedProcCall())
return
UnregisterSignal(user, COMSIG_MOB_CLIENT_LOGIN)
for(var/verb_type in user_verbs_by_type)
var/datum/user_verb/user_verb = user_verbs_by_type[verb_type]
user_verb.unassign_from_client(user)
admin_visibility_flags -= list(user.ckey)
@@ -305,7 +305,7 @@ SUBSYSTEM_DEF(tickets)
if(!closeTicket(N))
to_chat(C, "Unable to close ticket")
if("Man Up")
C.man_up(returnClient(N))
SSuser_verbs.invoke_verb(C, /datum/user_verb/man_up, returnClient(N))
T.lastStaffResponse = "Autoresponse: [message_key]"
resolveTicket(N)
message_staff("[C] has auto responded to [ticket_owner]\'s adminhelp with:<span class='adminticketalt'> [message_key]</span>")
@@ -747,9 +747,9 @@ UI STUFF
if(href_list["resolveall"])
if(ticket_system_name == "Mentor Tickets")
usr.client.resolveAllMentorTickets()
SSuser_verbs.invoke_verb(usr, /datum/user_verb/resolve_all_mentor_tickets)
else
usr.client.resolveAllAdminTickets()
SSuser_verbs.invoke_verb(usr, /datum/user_verb/resolve_all_admin_tickets)
if(href_list["close"])
onCloseDetailUI(usr)