refactors admin narrate (#7319)

This commit is contained in:
silicons
2025-10-08 23:51:38 +02:00
committed by GitHub
parent 8fedefc9d3
commit f76aeb8f33
35 changed files with 1207 additions and 224 deletions
@@ -3,6 +3,7 @@ var/list/admin_datums = list()
GLOBAL_VAR_INIT(href_token, GenerateToken())
GLOBAL_PROTECT(href_token)
// todo: /datum/admin_holder
/datum/admins
var/rank = "Temporary Admin"
var/client/owner = null
@@ -36,27 +37,97 @@ GLOBAL_PROTECT(href_token)
spawn(-1)
UNTIL(SSmapping.loaded_station)
admincaster_signature = "[(LEGACY_MAP_DATUM).company_name] Officer #[rand(0,9)][rand(0,9)][rand(0,9)]"
..()
// todo: assertions on this are too weak
/datum/admins/proc/associate(client/C)
if(istype(C))
owner = C
owner.holder = src
owner.add_admin_verbs() //TODO
GLOB.admins |= C
if(owner == C)
return
if(owner)
disassociate()
if(!istype(C))
return
owner = C
owner.holder = src
GLOB.admins |= C
// add verbs
add_admin_verbs()
// todo: assertions on this are too weak
/datum/admins/proc/disassociate()
if(owner)
GLOB.admins -= owner
owner.remove_admin_verbs()
owner.deadmin_holder = owner.holder
owner.holder = null
if(!owner)
return
// for now, destroy all modals
QDEL_LIST(admin_modals)
// obliterate verbs
remove_admin_verbs()
GLOB.admins -= owner
owner.deadmin_holder = owner.holder
owner.holder = null
owner = null
/datum/admins/proc/reassociate()
if(owner)
GLOB.admins |= owner
owner.holder = src
owner.deadmin_holder = null
owner.add_admin_verbs()
/datum/admins/add_admin_verbs()
..()
if(!owner)
return
var/list/verbs_to_add = list()
verbs_to_add += admin_verbs_default
if(rights & R_BUILDMODE)
verbs_to_add += /client/proc/togglebuildmodeself
if(rights & R_ADMIN)
verbs_to_add += admin_verbs_admin
if(rights & R_BAN)
verbs_to_add += admin_verbs_ban
if(rights & R_FUN)
verbs_to_add += admin_verbs_fun
if(rights & R_SERVER)
verbs_to_add += admin_verbs_server
if(rights & R_DEBUG)
verbs_to_add += admin_verbs_debug
if(rights & R_POSSESS)
verbs_to_add += admin_verbs_possess
if(rights & R_PERMISSIONS)
verbs_to_add += admin_verbs_permissions
if(rights & R_STEALTH)
verbs_to_add += /client/proc/stealth
if(rights & R_REJUVINATE)
verbs_to_add += admin_verbs_rejuv
if(rights & R_SOUNDS)
verbs_to_add += admin_verbs_sounds
if(rights & R_SPAWN)
verbs_to_add += admin_verbs_spawn
if(rights & R_MOD)
verbs_to_add += admin_verbs_mod
if(rights & R_EVENT)
verbs_to_add += admin_verbs_event_manager
add_verb(
owner,
verbs_to_add,
)
/datum/admins/remove_admin_verbs()
..()
if(!owner)
return
remove_verb(
owner,
list(
admin_verbs_default,
/client/proc/togglebuildmodeself,
admin_verbs_admin,
admin_verbs_ban,
admin_verbs_fun,
admin_verbs_server,
admin_verbs_debug,
admin_verbs_possess,
admin_verbs_permissions,
/client/proc/stealth,
admin_verbs_rejuv,
admin_verbs_sounds,
admin_verbs_spawn,
debug_verbs,
),
)
/*
checks if usr is an admin with at least ONE of the flags in rights_required. (Note, they don't need all the flags)
@@ -119,7 +190,6 @@ NOTE: It checks usr by default. Supply the "user" argument if you wish to check
/client/proc/deadmin()
if(holder)
holder.disassociate()
//qdel(holder)
return 1
/proc/GenerateToken()
@@ -159,12 +229,3 @@ NOTE: It checks usr by default. Supply the "user" argument if you wish to check
return TRUE
log_admin_private("[key_name(usr)] clicked an href with [msg] authorization key! [href]")
*/
/datum/admins/vv_edit_var(var_name, var_value)
#ifdef TESTING
return ..()
#else
if(var_name == NAMEOF(src, rank) || var_name == NAMEOF(src, rights))
return FALSE
return ..()
#endif
+76
View File
@@ -0,0 +1,76 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2025 Citadel Station Developers *//
/datum/admins
/// lazy list of active admin modals
///
/// todo: re-open these on reconnect.
var/list/datum/admin_modal/admin_modals
/// owning ckey
var/ckey
/datum/admins/Destroy()
QDEL_LIST(admin_modals)
return ..()
/datum/admins/New(initial_rank, initial_rights, ckey)
src.ckey = ckey
..()
/datum/admins/proc/add_admin_verbs()
if(!owner)
return
var/list/verbs_to_add = list()
for(var/datum/admin_verb_descriptor/descriptor in global.admin_verb_descriptors)
if((rights & descriptor.required_rights) != descriptor.required_rights)
continue
verbs_to_add += descriptor.verb_path
add_verb(
owner,
verbs_to_add,
)
world.SetConfig("APP/admin", ckey, "role=admin")
/datum/admins/proc/remove_admin_verbs()
var/list/verbs_to_remove = list()
for(var/datum/admin_verb_descriptor/descriptor in global.admin_verb_descriptors)
verbs_to_remove += descriptor.verb_path
remove_verb(
owner,
verbs_to_remove,
)
world.SetConfig("APP/admin", ckey, "null")
//* Admin Modals *//
/datum/admins/proc/open_admin_modal(path, ...)
ASSERT(ispath(path, /datum/admin_modal))
var/datum/admin_modal/modal = new path(src)
if(!modal.Initialize(arglist(args.Copy(2))))
qdel(modal)
message_admins("Failed to initialize an admin modal. Check runtimes for more details.")
stack_trace("failed to initialize an admin modal; this means someone passed in bad args.")
return null
modal.open()
return modal
//* -- SECURITY -- *//
//* Do not touch this section unless you know what you are doing. *//
/datum/admins/vv_edit_var(var_name, var_value)
#ifdef TESTING
return ..()
#else
if(var_name == NAMEOF(src, rank) || var_name == NAMEOF(src, rights))
return FALSE
return ..()
#endif
/datum/admins/CanProcCall(procname)
switch(procname)
if(NAMEOF_PROC(src, open_admin_modal))
return FALSE
if(findtext(procname, "verb__") == 1)
return FALSE
return ..()
@@ -0,0 +1,70 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 Citadel Station Developers *//
//* Modal *//
/**
* Base type of admin modals, which tend to be standalone panels.
*/
VV_PROTECT_READONLY(/datum/admin_modal)
/datum/admin_modal
/// Our name, for UI / output purposes
var/name = "Unknown Modal"
/// The admin datum that opened us
var/datum/admins/owner
/// TGUI ID; this will always be loaded from `tgui/interfaces/admin_modal` if possible.
var/tgui_interface
/// Do we autoupdate?
var/tgui_update = TRUE
/// are we initialized?
var/initialized = FALSE
/datum/admin_modal/New(datum/admins/for_owner)
owner = for_owner
LAZYADD(owner.admin_modals, src)
/datum/admin_modal/Destroy()
LAZYREMOVE(owner.admin_modals, src)
return ..()
/datum/admin_modal/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "admin_modal/[tgui_interface]")
ui.set_autoupdate(tgui_update)
ui.open()
/datum/admin_modal/ui_state()
return GLOB.ui_admin_modal_state
// TODO: don't destroy if it's part of a disconnect, restore after?
/datum/admin_modal/on_ui_close(mob/user, datum/tgui/ui, embedded)
..()
qdel(src)
/**
* Just a wrapper for opening our UI. Ensures everything is initialized.
*/
/datum/admin_modal/proc/open()
if(!initialized)
CRASH("attempted to open an uninitialized admin modal")
if(!owner?.owner?.mob)
return
ui_interact(owner.owner.mob)
/**
* Called with args to open_admin_modal().
*/
/datum/admin_modal/proc/Initialize(...)
initialized = TRUE
return TRUE
/**
* Call to complain about a failure in doing something.
*
* * Failures should generally be done UI-side, this is for when something is seriously wrong.
*/
/datum/admin_modal/proc/loud_rejection(message)
// todo: make this fancier
to_chat(owner.owner, "<font color='red'><b>[name]</b>: [message]</font>")
@@ -0,0 +1,361 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2025 Citadel Station Developers *//
/datum/admin_modal/admin_narrate
name = "Admin Narrate"
/**
* Target. Also determines what modes we can change to.
* ### in global, lobby mode
* Ignored.
* ### in overmap mode
* Can be atom, map_level, map, or overmap entity
* ### in sector mode
* Can be atom, map_level, map
* ### in level mode
* Can be atom, map_level
* ### in range mode
* Can be atom
* ### in direct mode
* Can be movable that is a or can hold mob(s), or client
*/
var/atom/target
//* i'm so sorry lohikar but i don't believe in define spam for this *//
var/const/M_GLOBAL = "global"
var/const/M_SECTOR = "sector"
var/const/M_OVERMAP = "overmap"
var/const/M_LEVEL = "level"
var/const/M_RANGE = "range"
var/const/M_DIRECT = "direct"
var/const/M_LOBBY = "lobby"
/// text to send (raw html)
var/unsafe_raw_html_to_send
/// narrate mode
var/mode
/// use line of sight when doing in range?
var/use_los = FALSE
/// use range in tiles; in overmaps, this is multiples of WORLD_ICON_SIZE
var/use_range = 14
var/const/MAX_LOS_RANGE = 35
var/const/MAX_ANY_RANGE = 1024
/// our identifying color
var/narrate_visual_color
/// our target image
// TODO: visualize it
// var/image/narrate_target_image
/datum/admin_modal/admin_narrate/Initialize(atom/target)
. = ..()
if(!.)
return
set_target(target)
src.narrate_visual_color = rgb(arglist(hsl2rgb(rand(0, 360), rand(0, 360), rand(125, 360))))
var/list/possible_modes = resolve_modes()
if(length(possible_modes))
src.mode = possible_modes[1]
else
return FALSE
/datum/admin_modal/admin_narrate/Destroy()
set_target(null)
return ..()
/datum/admin_modal/admin_narrate/proc/set_target(atom/target)
if(src.target)
on_unset_target(src.target)
src.target = target
if(src.target)
on_set_target(src.target)
/datum/admin_modal/admin_narrate/proc/on_unset_target(atom/new_target)
// TODO: visualize it
UnregisterSignal(target, COMSIG_PARENT_QDELETING)
/datum/admin_modal/admin_narrate/proc/on_set_target(atom/new_target)
// TODO: visualize it
RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(on_target_del))
/datum/admin_modal/admin_narrate/proc/on_target_del(datum/source)
if(source != target)
CRASH("how was target del signal invoked by something that is not our target?")
set_target(null)
/**
* @return list of M_ modes
*/
/datum/admin_modal/admin_narrate/proc/resolve_modes()
var/datum/resolved = target
if(!resolved)
return list(
/datum/admin_modal/admin_narrate::M_GLOBAL,
/datum/admin_modal/admin_narrate::M_LOBBY,
)
. = list()
if(istype(resolved, /obj/overmap/entity))
. += /datum/admin_modal/admin_narrate::M_DIRECT
. += /datum/admin_modal/admin_narrate::M_OVERMAP
else if(isatom(resolved))
if(ismovable(resolved))
var/atom/movable/casted = resolved
if(casted.admin_resolve_narrate())
// compatible with direct-narrate
. += /datum/admin_modal/admin_narrate::M_DIRECT
. += /datum/admin_modal/admin_narrate::M_RANGE
. += /datum/admin_modal/admin_narrate::M_LEVEL
. += /datum/admin_modal/admin_narrate::M_SECTOR
. += /datum/admin_modal/admin_narrate::M_OVERMAP
else if(istype(resolved, /datum/map_level))
var/datum/map_level/casted = resolved
. += /datum/admin_modal/admin_narrate::M_LEVEL
. += /datum/admin_modal/admin_narrate::M_SECTOR
if(get_overmap_sector(locate(1, 1, casted.z_index)))
. += /datum/admin_modal/admin_narrate::M_OVERMAP
else if(istype(resolved, /datum/map))
. += /datum/admin_modal/admin_narrate::M_SECTOR
// TODO: check for overmap binding & add overmap if it's there
/**
* @return list of mobs, or null if invalid
*/
/datum/admin_modal/admin_narrate/proc/resolve_hearers()
// Clients are not allowed in output list; we want mobs, not clients.
. = list()
switch(mode)
if(M_GLOBAL)
// do not use client refs directly to prevent gc issues if delayed
for(var/client/C as anything in GLOB.clients)
. += C.mob
if(M_SECTOR)
var/atom/resolved = target
var/turf/maybe_target_turf = get_turf(resolved)
var/datum/map_level/maybe_target_map_level
if(maybe_target_turf)
maybe_target_map_level = SSmapping.ordered_levels[maybe_target_turf.z]
if(!maybe_target_map_level)
return null
for(var/client/C as anything in GLOB.clients)
var/turf/maybe_turf = get_turf(C.mob)
if(maybe_turf)
var/datum/map_level/maybe_map_level = SSmapping.ordered_levels[maybe_turf.z]
if(maybe_map_level?.parent_map == maybe_target_map_level.parent_map)
. += C.mob
if(M_OVERMAP)
var/datum/resolved = target
var/obj/overmap/entity/resolved_entity
if(istype(resolved, /obj/overmap/entity))
resolved_entity = resolved
else if(istype(resolved, /atom))
resolved_entity = get_overmap_sector(get_turf(resolved))
else if(istype(resolved, /datum/map_level))
var/datum/map_level/casted = resolved
resolved_entity = get_overmap_sector(locate(1, 1, casted.z_index))
// TODO: do something
else if(istype(resolved, /datum/map))
pass()
// TODO: do something
if(!resolved_entity)
return null
for(var/client/C as anything in GLOB.clients)
var/turf/maybe_turf = get_turf(C.mob)
var/obj/overmap/entity/maybe_entity = get_overmap_sector(maybe_turf)
if(maybe_entity == resolved_entity)
. += C.mob
if(M_LEVEL)
var/datum/resolved = target
var/datum/map_level/resolved_level
if(istype(resolved, /atom))
var/turf/maybe_turf = get_turf(resolved)
if(maybe_turf)
resolved_level = SSmapping.ordered_levels[maybe_turf.z]
else if(istype(resolved, /datum/map_level))
resolved_level = resolved
for(var/client/C as anything in GLOB.clients)
var/turf/maybe_turf = get_turf(C.mob)
if(maybe_turf?.z == resolved_level.z_index)
. += C.mob
if(M_RANGE)
var/atom/resolved = target
if(!istype(resolved))
return null
else
if(use_los)
for(var/mob/maybe_viewing in viewers(min(MAX_LOS_RANGE, use_range), resolved))
if(!maybe_viewing.client)
continue
. += maybe_viewing
else
var/our_z = get_z(resolved)
for(var/client/C as anything in GLOB.clients)
var/mob/maybe_viewing = C.mob
if((get_z(maybe_viewing) != our_z) || (get_dist(maybe_viewing, resolved) > use_range))
continue
if(!maybe_viewing.client)
continue
. += maybe_viewing
if(M_DIRECT)
var/atom/movable/resolved = target
if(!istype(resolved))
return null
. += resolved.admin_resolve_narrate()
if(M_LOBBY)
// do not use client refs directly to prevent gc issues if delayed
for(var/client/C as anything in GLOB.clients)
if(isnewplayer(C.mob))
. += C.mob
/datum/admin_modal/admin_narrate/proc/get_target_data()
var/datum/resolved = target
if(!is_target_valid(resolved, mode))
return null
var/turf/maybe_turf
var/datum/map_level/maybe_level
var/datum/map/maybe_map
var/obj/overmap/entity/maybe_entity
if(istype(resolved, /obj/overmap/entity))
maybe_entity = resolved
else if(isatom(resolved))
maybe_turf = get_turf(resolved)
else if(istype(resolved, /datum/map_level))
maybe_level = resolved
else if(istype(resolved, /datum/map))
maybe_map = resolved
if(maybe_turf)
maybe_level = SSmapping.ordered_levels[maybe_turf.z]
if(maybe_level)
maybe_map = maybe_level.parent_map
maybe_entity = get_overmap_sector(maybe_turf)
. = list()
if(maybe_turf)
.["coords"] = list(maybe_turf.x, maybe_turf.y, maybe_turf.z)
if(maybe_level)
.["level"] = list("index" = maybe_level.z_index, "name" = maybe_level.name)
if(maybe_map)
.["sector"] = list("name" = maybe_map.name)
if(maybe_entity)
.["overmap"] = list(
"name" = maybe_entity.name,
"x" = maybe_entity.x,
"y" = maybe_entity.y,
"map" = maybe_entity.overmap?.name,
)
/datum/admin_modal/admin_narrate/proc/is_target_valid(datum/target, mode)
switch(mode)
if(M_GLOBAL)
return TRUE
if(M_LOBBY)
return TRUE
if(M_SECTOR)
return isturf(target) || ismovable(target) || istype(target, /datum/map_level) || istype(target, /datum/map)
if(M_OVERMAP)
return isturf(target) || ismovable(target) || istype(target, /datum/map_level) || istype(target, /datum/map)
if(M_LEVEL)
return isturf(target) || ismovable(target) || istype(target, /datum/map_level)
if(M_RANGE)
return isturf(target) || ismovable(target)
if(M_DIRECT)
var/atom/movable/casted = target
return istype(casted) && casted.admin_resolve_narrate()
/datum/admin_modal/admin_narrate/proc/narrate()
if(!length(unsafe_raw_html_to_send))
return
var/list/mob/targets = resolve_hearers()
var/emit = unsafe_raw_html_to_send
var/list/view_target_to_list = list()
for(var/mob/viewing in targets)
view_target_to_list += "[key_name(viewing)]"
tim_sort(view_target_to_list, /proc/cmp_text_asc)
var/view_target_list = jointext(view_target_to_list, ", ")
message_admins("[key_name(owner.owner.mob)] sent a [SPAN_TOOLTIP("[html_encode(emit)]", "global narrate")] to [SPAN_TOOLTIP("[view_target_list]", "[length(targets)] target(s)")].")
log_admin("[key_name(owner.owner.mob)] sent a global narrate to [length(targets)] targets; VIEWERS: '[view_target_list]'', TEXT: '[emit]'")
for(var/mob/viewing in targets)
to_chat(viewing, emit)
/datum/admin_modal/admin_narrate/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
switch(action)
if("setOutput")
unsafe_raw_html_to_send = params["target"]
return TRUE
if("setRange")
use_range = params["target"]
if(use_los)
use_range = clamp(use_range, 0, MAX_LOS_RANGE)
else
use_range = clamp(use_range, 0, MAX_ANY_RANGE)
return TRUE
if("setMode")
mode = params["mode"]
return TRUE
if("setLos")
use_los = !!params["target"]
if(use_los)
use_range = clamp(use_range, 0, MAX_LOS_RANGE)
else
use_range = clamp(use_range, 0, MAX_ANY_RANGE)
return TRUE
if("narrate")
if(params["html"])
unsafe_raw_html_to_send = params["html"]
narrate()
qdel(src)
return TRUE
if("cancel")
qdel(src)
return TRUE
if("preview")
var/emit = params["html"]
var/list/mob/targets = resolve_hearers()
var/list/rendered_viewers_list = list()
for(var/mob/target as anything in targets)
rendered_viewers_list += "[target.name][target.real_name != target.name ? " ([target.real_name])" : ""]"
tim_sort(rendered_viewers_list, /proc/cmp_text_asc)
var/rendered_viewers = jointext(rendered_viewers_list, "<br>")
var/list/html = list(
"<hr>",
SPAN_BLOCKQUOTE(emit, null),
"<hr>",
"<center>[SPAN_ADMIN("^^^ Narrate Preview; [length(rendered_viewers) ? SPAN_TOOLTIP(rendered_viewers, "Viewers..."): "No Viewers!"] ^^^")]</center>",
)
to_chat(owner.owner, jointext(html, ""))
return TRUE
/datum/admin_modal/admin_narrate/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "admin_modal/AdminNarrate")
ui.open()
/datum/admin_modal/admin_narrate/ui_static_data(mob/user, datum/tgui/ui)
. = ..()
// UI-authoritative, so this is only even sent to server
// to keep the UI persistent across reconnects.
.["rawHtml"] = unsafe_raw_html_to_send
.["visualColor"] = narrate_visual_color
.["possibleModes"] = resolve_modes()
/datum/admin_modal/admin_narrate/ui_data(mob/user, datum/tgui/ui)
. = ..()
// This however needs to update
.["target"] = get_target_data()
.["mode"] = mode
.["useLos"] = use_los
.["useRange"] = use_range
.["maxRangeLos"] = MAX_LOS_RANGE
.["maxRangeAny"] = MAX_ANY_RANGE
@@ -0,0 +1,45 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2024 Citadel Station Developers *//
GLOBAL_REAL_PROTECT(admin_verbs)
GLOBAL_REAL_LIST(admin_verb_descriptors) = zz__prepare_admin_verb_descriptors()
/proc/zz__prepare_admin_verb_descriptors()
. = list()
for(var/datum/admin_verb_descriptor/path as anything in subtypesof(/datum/admin_verb_descriptor))
. += new path
/**
* Describes an admin verb.
*
* When admin verbs should be used:
* * Core debug functions (VV, SDQL, similar). These should **never** be abstracted to complex
* systems like TGUI, as we need thse to debug said complex systems!
* * Anything that invokes on right click
* * Anything that is very simple to invoke ('spawn') and is used enough that removing it
* for a panel makes no sense as it would make the lives of people using the in-game
* command line worse!
*/
/datum/admin_verb_descriptor
/// our unique ID; this is based on verb path!
var/id
/// our verb path
var/verb_path
/// the reflection path to read from
var/reflection_path
/// required rights flags
var/required_rights
//* detected from verb *//
/// our name
var/name
/// our desc
var/desc
/datum/admin_verb_descriptor/New()
if(!verb_path)
return
var/procpath/cast_verb = reflection_path
name = cast_verb.name
desc = cast_verb.desc
+1 -11
View File
@@ -51,10 +51,6 @@ var/list/admin_verbs_admin = list(
/client/proc/jumptoturf, //allows us to jump to a specific turf,
/client/proc/admin_call_shuttle, //allows us to call the emergency shuttle,
/client/proc/admin_cancel_shuttle, //allows us to cancel the emergency shuttle, sending it back to CentCom,
/client/proc/cmd_admin_direct_narrate, //send text directly to a player with no padding. Useful for narratives and fluff-text,
/client/proc/cmd_admin_local_narrate,
/client/proc/cmd_admin_world_narrate,
/client/proc/cmd_admin_z_narrate, //sends text to all players on a z-level.When Global is too much
/client/proc/cmd_admin_create_centcom_report,
/client/proc/check_words, //displays cult-words,
/client/proc/check_ai_laws, //shows AI and borg laws,
@@ -282,10 +278,6 @@ var/list/admin_verbs_hideable = list(
/datum/admins/proc/access_news_network,
/client/proc/admin_call_shuttle,
/client/proc/admin_cancel_shuttle,
/client/proc/cmd_admin_direct_narrate,
/client/proc/cmd_admin_local_narrate,
/client/proc/cmd_admin_world_narrate,
/client/proc/cmd_admin_z_narrate,
/client/proc/check_words,
/client/proc/play_local_sound,
/client/proc/play_sound,
@@ -364,7 +356,6 @@ var/list/admin_verbs_mod = list(
/client/proc/cmd_admin_subtle_message, //send an message to somebody as a 'voice in their head',
/client/proc/cmd_admin_icsubtle_message,
/datum/admins/proc/paralyze_mob,
/client/proc/cmd_admin_direct_narrate,
/client/proc/allow_character_respawn, // Allows a ghost to respawn ,
/datum/admins/proc/sendFax,
/client/proc/addbunkerbypass,
@@ -386,7 +377,6 @@ var/list/admin_verbs_event_manager = list(
/client/proc/aooc,
/client/proc/cmd_admin_clear_mobs,
/datum/admins/proc/paralyze_mob,
/client/proc/cmd_admin_direct_narrate,
/client/proc/allow_character_respawn,
/datum/admins/proc/sendFax,
/client/proc/respawn_character,
@@ -776,7 +766,7 @@ var/list/admin_verbs_event_manager = list(
set category = "Admin"
if(deadmin_holder)
deadmin_holder.reassociate()
deadmin_holder.associate(src)
log_admin("[src] re-admined themself.")
message_admins("[src] re-admined themself.", 1)
to_chat(src, "<span class='interface'>You now have the keys to control the planet, or atleast a small space station</span>")
+1 -1
View File
@@ -1574,7 +1574,7 @@
if(!check_rights(R_ADMIN)) return
var/mob/M = locate(href_list["narrateto"])
usr.client.cmd_admin_direct_narrate(M)
usr.client.holder.open_admin_modal(/datum/admin_modal/admin_narrate, M)
else if(href_list["subtlemessage"])
if(!check_rights(R_MOD,0) && !check_rights(R_ADMIN)) return
+8
View File
@@ -0,0 +1,8 @@
/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in world)
set category = "Admin"
set name = "Delete"
if(!check_rights(R_SPAWN|R_DEBUG|R_ADMIN))
return
admin_delete(A)
+13 -13
View File
@@ -1,5 +1,5 @@
/client/proc/Debug2()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Debug-Game"
set desc = "Toggles game debugging."
@@ -118,7 +118,7 @@
return types[key]
/client/proc/cmd_del_all(object as text)
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Del-All"
set desc = "Delete all datums with the specified type."
@@ -138,7 +138,7 @@
message_admins("[key_name_admin(src)] has deleted all ([counter]) instances of [type_to_del].")
/client/proc/cmd_del_all_force(object as text)
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Force-Del-All"
set desc = "Forcibly delete all datums with the specified type."
@@ -158,7 +158,7 @@
message_admins("[key_name_admin(src)] has force-deleted all ([counter]) instances of [type_to_del].")
/client/proc/cmd_del_all_hard(object as text)
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Hard-Del-All"
set desc = "Hard delete all datums with the specified type."
@@ -203,7 +203,7 @@
message_admins("[key_name_admin(src)] has hard deleted all ([counter]) instances of [type_to_del].")
/client/proc/cmd_debug_make_powernets()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Make Powernets"
set desc = "Regenerates all powernets for all cables."
@@ -215,7 +215,7 @@
feedback_add_details("admin_verb","MPWN") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_grantfullaccess(var/mob/M in GLOB.mob_list)
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Grant Full Access"
set desc = "Grant full access to a mob."
@@ -250,7 +250,7 @@
message_admins(SPAN_ADMINNOTICE("[key_name_admin(usr)] has granted [M.key] full access."))
/client/proc/cmd_debug_mob_lists()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Debug Mob Lists"
set desc = "For when you just gotta know"
@@ -274,7 +274,7 @@
to_chat(usr, jointext(GLOB.clients,","), confidential = TRUE)
/client/proc/cmd_display_del_log()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Display del() Log"
set desc = "Display del's log of everything that's passed through it."
@@ -307,7 +307,7 @@
browser.open()
/client/proc/cmd_display_overlay_log()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Display overlay Log"
set desc = "Display SSoverlays log of everything that's passed through it."
@@ -316,7 +316,7 @@
render_stats(SSoverlays.stats, src)
/client/proc/cmd_display_init_log()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Display Initialize() Log"
set desc = "Displays a list of things that didn't handle Initialize() properly"
@@ -327,7 +327,7 @@
browser.open()
/datum/admins/proc/view_runtimes()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "View Runtimes"
set desc = "Open the Runtime Viewer"
@@ -345,7 +345,7 @@
alert(src, "[warning]. Proceed with caution. If you really need to see the runtimes, download the runtime log and view it in a text editor.", "HEED THIS WARNING CAREFULLY MORTAL")
/client/proc/check_timer_sources()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Check Timer Sources"
set desc = "Checks the sources of running timers."
@@ -365,7 +365,7 @@
browser.open()
/client/proc/toggle_browser_inspect()
set category = ADMIN_CATEGORY_DEBUG
set category = VERB_CATEGORY_DEBUG
set name = "Toggle Browser Inspect"
set desc = "Toggle browser debugging via inspect"
+5
View File
@@ -0,0 +1,5 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2025 Citadel Station Developers *//
ADMIN_VERB_DEF(narrate, R_ADMIN, "Narrate", "Perform narration.", VERB_CATEGORY_GAME, atom/target as null|obj|mob|turf in world)
caller.holder.open_admin_modal(/datum/admin_modal/admin_narrate, target)
@@ -0,0 +1,95 @@
//* This file is explicitly licensed under the MIT license. *//
//* Copyright (c) 2025 Citadel Station Developers *//
ADMIN_VERB_DEF(narrate_quick, R_ADMIN, "Narrate (Quick)", "Perform narration.", VERB_CATEGORY_GAME, atom/target as null|obj|mob|turf in world)
var/use_global
var/datum/weakref/use_viewers
var/datum/weakref/use_overmap
var/datum/weakref/use_direct
var/target_name_descriptor = "ERROR"
var/target_long_descriptor = "ERROR"
if(isnull(target))
use_global = TRUE
target_name_descriptor = "(Everyone)"
target_long_descriptor = "Narrate to the entire server, even those in lobby."
else if(istype(target, /obj/overmap/entity))
use_overmap = WEAKREF(target)
target_name_descriptor = "([target])"
target_long_descriptor = "Narrate to everyone aboard / present on \the [target]."
else if(isturf(target))
use_viewers = WEAKREF(target)
target_name_descriptor = "(viewing [target] @ [target.x], [target.y], [target.y])"
target_long_descriptor = "Narrate to everyone who can view the turf ([target]) at [target.x], [target.y], [target.z]."
else if(ismovable(target))
var/atom/movable/target_movable = target
if(target_movable.admin_resolve_narrate())
use_direct = WEAKREF(target_movable)
target_name_descriptor = "([target])"
target_long_descriptor = "Narrate to everyone inside / the person at / the target of '[target]' (currently at \the [get_area(target)])."
else
use_viewers = WEAKREF(target_movable)
target_name_descriptor = "(viewing [target])"
target_long_descriptor = "Narrate to everyone who can see '[target]' (currently at \the [get_area(target)])."
var/emit = tgui_input_text(caller, target_long_descriptor, "Narrate to [target_name_descriptor]", "", 65535, TRUE, FALSE)
if(!emit)
return
var/list/mob/targets = list()
var/reject
if(use_global)
for(var/client/C as anything in GLOB.clients)
targets += C.mob
else if(use_viewers)
var/atom/resolved = use_viewers.resolve()
if(!istype(resolved))
reject = "use_viewers failed atom resolution; this is a bug."
else
for(var/mob/maybe_viewing in viewers(35, resolved))
if(!maybe_viewing.client)
continue
targets += maybe_viewing
else if(use_overmap)
var/obj/overmap/entity/resolved = use_viewers.resolve()
if(!istype(resolved))
reject = "use_overmap failed entity resolution; this is a bug."
else
// sigh make this better later
for(var/client/C as anything in GLOB.clients)
if(get_overmap_sector(C.mob) == resolved)
targets += C.mob
else if(use_direct)
var/atom/movable/resolved = use_viewers.resolve()
if(!istype(resolved))
reject = "use_direct failed target resolution; this is a bug."
else
targets = resolved.admin_resolve_narrate()
else
reject = "no narrate mode resolved; this is a bug."
if(!reject && !length(targets))
reject = "no targets in range; skipping narrate sequence."
if(reject)
var/list/html = list(
"<hr>",
SPAN_BLOCKQUOTE(emit, null),
"<hr>",
"<center><span style='font-weight: bold; color: red;'>^^^ ERROR: The above was not sent; [reject] ^^^</span></center>",
)
to_chat(caller, jointext(html, ""))
return
var/list/view_target_to_list = list()
for(var/mob/viewing in targets)
view_target_to_list += "[key_name(viewing)]"
var/view_target_list = jointext(view_target_to_list, ", ")
message_admins("[key_name(caller)] sent a [SPAN_TOOLTIP("[html_encode(emit)]", "global narrate")] to [SPAN_TOOLTIP("[view_target_list]", "[length(targets)] target(s)")].")
log_admin("[key_name(caller)] sent a global narrate to [length(targets)] targets; VIEWERS: '[view_target_list]'', TEXT: '[emit]'")
for(var/mob/viewing in targets)
to_chat(viewing, emit)
-89
View File
@@ -101,86 +101,6 @@
admin_ticket_log(M, msg)
feedback_add_details("admin_verb","SMS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_world_narrate()
set category = "Special Verbs"
set name = "Global Narrate"
if(!check_rights(R_ADMIN))
return
var/msg = input("Message:", "Enter the text you wish to appear to everyone:") as text|null
if (!msg)
return
to_chat(world, "[msg]")
log_admin("GlobalNarrate: [key_name(usr)] : [msg]")
message_admins("<span class='adminnotice'>[key_name_admin(usr)] Sent a global narrate</span>")
// SSblackbox.record_feedback("tally", "admin_verb", 1, "Global Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_direct_narrate(mob/M)
set category = "Special Verbs"
set name = "Direct Narrate"
if(!check_rights(R_ADMIN))
return
if(!M)
M = input("Direct narrate to whom?", "Active Players") as null|anything in GLOB.player_list
if(!M)
return
var/msg = input("Message:", "Enter the text you wish to appear to your target:") as text|null
if( !msg )
return
to_chat(M, msg)
log_admin("DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]): [msg]")
msg = "<span class='adminnotice'><b> DirectNarrate: [key_name(usr)] to ([M.name]/[M.key]):</b> [msg]<BR></span>"
message_admins(msg)
admin_ticket_log(M, msg)
// SSblackbox.record_feedback("tally", "admin_verb", 1, "Direct Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_local_narrate(atom/A)
set category = "Special Verbs"
set name = "Local Narrate"
if(!check_rights(R_ADMIN))
return
if(!A)
return
var/range = input("Range:", "Narrate to mobs within how many tiles:", 7) as num|null
if(!range)
return
var/msg = input("Message:", "Enter the text you wish to appear to everyone within view:") as text|null
if (!msg)
return
for(var/mob/M in view(range,A))
to_chat(M, msg)
log_admin("LocalNarrate: [key_name(usr)] at [AREACOORD(A)]: [msg]")
message_admins("<span class='adminnotice'><b> LocalNarrate: [key_name_admin(usr)] at [ADMIN_COORDJMP(A)]:</b> [msg]<BR></span>")
// SSblackbox.record_feedback("tally", "admin_verb", 1, "Local Narrate") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_z_narrate()
set category = "Special Verbs"
set name = "Z Narrate"
if(!check_rights(R_ADMIN))
return
var/msg = input("Enter the text you wish to show an entire Z level:", "Z Narrate:") as text|null
if (!msg)
return
for(var/mob/M in range(192)) //Yes this is lazy
to_chat(M, msg)
log_admin("ZNarrate: [key_name(usr)] at [ADMIN_COORDJMP(usr)]: [msg]")
message_admins("<span class='adminnotice'><b> ZNarrate: [key_name_admin(usr)] at [ADMIN_COORDJMP(usr)]:</b> [msg]<BR></span>")
/client/proc/cmd_admin_godmode(mob/M as mob in GLOB.mob_list)
set category = "Special Verbs"
@@ -632,15 +552,6 @@ Traitors and the like can also be revived with the previous role mostly intact.
message_admins("[key_name_admin(src)] has created a command report", 1)
feedback_add_details("admin_verb","CCR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_delete(atom/A as obj|mob|turf in world)
set category = "Admin"
set name = "Delete"
if(!check_rights(R_SPAWN|R_DEBUG|R_ADMIN))
return
admin_delete(A)
/client/proc/cmd_admin_list_open_jobs()
set category = "Admin"
set name = "List free slots"