mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2025-12-09 09:56:45 +00:00
refactors admin narrate (#7319)
This commit is contained in:
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -29,5 +29,6 @@
|
||||
},
|
||||
"[javascript][typescript][typescriptreact][javascriptreact]": {
|
||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||
}
|
||||
},
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
|
||||
23
citadel.dme
23
citadel.dme
@@ -34,7 +34,6 @@
|
||||
#include "code\__DEFINES\ability.dm"
|
||||
#include "code\__DEFINES\access.dm"
|
||||
#include "code\__DEFINES\actionspeed_modification.dm"
|
||||
#include "code\__DEFINES\admin_verb.dm"
|
||||
#include "code\__DEFINES\appearance.dm"
|
||||
#include "code\__DEFINES\assert.dm"
|
||||
#include "code\__DEFINES\automata.dm"
|
||||
@@ -126,6 +125,7 @@
|
||||
#include "code\__DEFINES\admin\admin.dm"
|
||||
#include "code\__DEFINES\admin\bans.dm"
|
||||
#include "code\__DEFINES\admin\keybindings.dm"
|
||||
#include "code\__DEFINES\admin\verbs.dm"
|
||||
#include "code\__DEFINES\ai\ai_holder.dm"
|
||||
#include "code\__DEFINES\ai\debug.dm"
|
||||
#include "code\__DEFINES\assets\medical.dm"
|
||||
@@ -480,9 +480,9 @@
|
||||
#include "code\__HELPERS\matrices\color_matrix.dm"
|
||||
#include "code\__HELPERS\matrices\transform_matrix.dm"
|
||||
#include "code\__HELPERS\misc\sonar.dm"
|
||||
#include "code\__HELPERS\pathfinding\astar.dm"
|
||||
#include "code\__HELPERS\pathfinding\common.dm"
|
||||
#include "code\__HELPERS\pathfinding\jps.dm"
|
||||
#include "code\__HELPERS\pathfinding\pathfinding.dm"
|
||||
#include "code\__HELPERS\pathfinding\algorithms\astar.dm"
|
||||
#include "code\__HELPERS\pathfinding\algorithms\jps.dm"
|
||||
#include "code\__HELPERS\rendering\positioning.dm"
|
||||
#include "code\__HELPERS\rendering\screen_loc.dm"
|
||||
#include "code\__HELPERS\rendering\view.dm"
|
||||
@@ -1126,6 +1126,7 @@
|
||||
#include "code\game\atoms\defense_old.dm"
|
||||
#include "code\game\atoms\movement.dm"
|
||||
#include "code\game\atoms\say.dm"
|
||||
#include "code\game\atoms\movable\movable-admin.dm"
|
||||
#include "code\game\atoms\movable\movable-logging.dm"
|
||||
#include "code\game\atoms\movable\movable-movement.dm"
|
||||
#include "code\game\atoms\movable\movable-proto_kinetic.dm"
|
||||
@@ -2090,6 +2091,7 @@
|
||||
#include "code\game\objects\structures\window_spawner.dm"
|
||||
#include "code\game\objects\structures\crates_lockers\__closet.dm"
|
||||
#include "code\game\objects\structures\crates_lockers\_closet_appearance_definitions.dm"
|
||||
#include "code\game\objects\structures\crates_lockers\closet-admin.dm"
|
||||
#include "code\game\objects\structures\crates_lockers\crates.dm"
|
||||
#include "code\game\objects\structures\crates_lockers\largecrate.dm"
|
||||
#include "code\game\objects\structures\crates_lockers\vehiclecage.dm"
|
||||
@@ -2296,10 +2298,13 @@
|
||||
#include "code\modules\actionspeed\modifiers\base.dm"
|
||||
#include "code\modules\admin\admin.dm"
|
||||
#include "code\modules\admin\admin_attack_log.dm"
|
||||
#include "code\modules\admin\admin_holder-legacy.dm"
|
||||
#include "code\modules\admin\admin_holder.dm"
|
||||
#include "code\modules\admin\admin_memo.dm"
|
||||
#include "code\modules\admin\admin_ranks.dm"
|
||||
#include "code\modules\admin\admin_secrets.dm"
|
||||
#include "code\modules\admin\admin_tools.dm"
|
||||
#include "code\modules\admin\admin_verb_descriptor.dm"
|
||||
#include "code\modules\admin\admin_verbs.dm"
|
||||
#include "code\modules\admin\banjob.dm"
|
||||
#include "code\modules\admin\chat_commands.dm"
|
||||
@@ -2307,7 +2312,6 @@
|
||||
#include "code\modules\admin\create_mob.dm"
|
||||
#include "code\modules\admin\create_object.dm"
|
||||
#include "code\modules\admin\create_turf.dm"
|
||||
#include "code\modules\admin\holder2.dm"
|
||||
#include "code\modules\admin\investigate.dm"
|
||||
#include "code\modules\admin\IsBanned.dm"
|
||||
#include "code\modules\admin\map_capture.dm"
|
||||
@@ -2317,6 +2321,8 @@
|
||||
#include "code\modules\admin\player_panel.dm"
|
||||
#include "code\modules\admin\topic.dm"
|
||||
#include "code\modules\admin\ToRban.dm"
|
||||
#include "code\modules\admin\admin_modal\admin_modal.dm"
|
||||
#include "code\modules\admin\admin_modal\modals\admin_narrate.dm"
|
||||
#include "code\modules\admin\ban\ban_system.dm"
|
||||
#include "code\modules\admin\ban\role_ban.dm"
|
||||
#include "code\modules\admin\ban\server_ban.dm"
|
||||
@@ -2392,11 +2398,14 @@
|
||||
#include "code\modules\admin\verbs\striketeam.dm"
|
||||
#include "code\modules\admin\verbs\ticklag.dm"
|
||||
#include "code\modules\admin\verbs\tripAI.dm"
|
||||
#include "code\modules\admin\verbs\admin\delete.dm"
|
||||
#include "code\modules\admin\verbs\debug\fucky_wucky.dm"
|
||||
#include "code\modules\admin\verbs\debug\profiling.dm"
|
||||
#include "code\modules\admin\verbs\debug\reestablish_db_connection.dm"
|
||||
#include "code\modules\admin\verbs\debug\reload_configuration.dm"
|
||||
#include "code\modules\admin\verbs\debug\spawn.dm"
|
||||
#include "code\modules\admin\verbs\game\narrate.dm"
|
||||
#include "code\modules\admin\verbs\game\narrate_quick.dm"
|
||||
#include "code\modules\admin\verbs\SDQL2\SDQL_2.dm"
|
||||
#include "code\modules\admin\verbs\SDQL2\SDQL_2_parser.dm"
|
||||
#include "code\modules\admin\verbs\SDQL2\SDQL_2_wrappers.dm"
|
||||
@@ -3766,6 +3775,7 @@
|
||||
#include "code\modules\metrics\api\spatial_series.dm"
|
||||
#include "code\modules\metrics\api\string_set.dm"
|
||||
#include "code\modules\metrics\api\time_series.dm"
|
||||
#include "code\modules\metrics\metrics\admin.dm"
|
||||
#include "code\modules\metrics\metrics\controllers.dm"
|
||||
#include "code\modules\mining\mine_turfs-defense.dm"
|
||||
#include "code\modules\mining\mine_turfs.dm"
|
||||
@@ -3807,6 +3817,7 @@
|
||||
#include "code\modules\mob\holder.dm"
|
||||
#include "code\modules\mob\inventory_legacy.dm"
|
||||
#include "code\modules\mob\life.dm"
|
||||
#include "code\modules\mob\mob-admin.dm"
|
||||
#include "code\modules\mob\mob-client.dm"
|
||||
#include "code\modules\mob\mob-damage.dm"
|
||||
#include "code\modules\mob\mob-defense.dm"
|
||||
@@ -5433,6 +5444,7 @@
|
||||
#include "code\modules\tgui\modules\specific\lathe_control.dm"
|
||||
#include "code\modules\tgui\modules\specific\prosfab_control.dm"
|
||||
#include "code\modules\tgui\states\admin.dm"
|
||||
#include "code\modules\tgui\states\admin_modal_state.dm"
|
||||
#include "code\modules\tgui\states\always.dm"
|
||||
#include "code\modules\tgui\states\conscious.dm"
|
||||
#include "code\modules\tgui\states\contained.dm"
|
||||
@@ -5475,6 +5487,7 @@
|
||||
#include "code\modules\vehicles\actions.dm"
|
||||
#include "code\modules\vehicles\ridden.dm"
|
||||
#include "code\modules\vehicles\sealed.dm"
|
||||
#include "code\modules\vehicles\vehicle-admin.dm"
|
||||
#include "code\modules\vehicles\vehicle-input.dm"
|
||||
#include "code\modules\vehicles\vehicle-physics.dm"
|
||||
#include "code\modules\vehicles\vehicle.dm"
|
||||
|
||||
59
code/__DEFINES/admin/verbs.dm
Normal file
59
code/__DEFINES/admin/verbs.dm
Normal file
@@ -0,0 +1,59 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/**
|
||||
* Declares an admin verb.
|
||||
*
|
||||
* * Verbs declared in this way will have the caller as `client/caller`. Do not define your
|
||||
* own client / usr calls.
|
||||
* * You may safely assume that the verb is only accessible by them if they have the right permissions.
|
||||
* * Set `CATEGORY` to null to not have it show up in verb panel.
|
||||
*/
|
||||
#define ADMIN_VERB_DEF(PATH_SUFFIX, REQUIRED_RIGHTS, NAME, DESC, CATEGORY, HEADER...) \
|
||||
ADMIN_VERB_DEF_INTERNAL(PATH_SUFFIX, REQUIRED_RIGHTS, NAME, DESC, CATEGORY, TRUE, HEADER)
|
||||
|
||||
/**
|
||||
* Declares an admin verb that does not show up in the popup menu.
|
||||
*
|
||||
* * Verbs declared in this way will have the caller as `client/caller`. Do not define your
|
||||
* own client / usr calls.
|
||||
* * You may safely assume that the verb is only accessible by them if they have the right permissions.
|
||||
* * Set `CATEGORY` to null to not have it show up in verb panel.
|
||||
*/
|
||||
#define ADMIN_VERB_DEF_PANEL_ONLY(PATH_SUFFIX, REQUIRED_RIGHTS, NAME, DESC, CATEGORY, HEADER...) \
|
||||
ADMIN_VERB_DEF_INTERNAL(PATH_SUFFIX, REQUIRED_RIGHTS, NAME, DESC, CATEGORY, FALSE, HEADER)
|
||||
|
||||
/**
|
||||
* Do not use.
|
||||
*/
|
||||
#define ADMIN_VERB_DEF_INTERNAL(PATH_SUFFIX, REQUIRED_RIGHTS, NAME, DESC, CATEGORY, POPUP_MENU, HEADER...) \
|
||||
/datum/admin_verb_descriptor/##PATH_SUFFIX { \
|
||||
id = #PATH_SUFFIX; \
|
||||
required_rights = ##REQUIRED_RIGHTS; \
|
||||
verb_path = /datum/admin_verb_abstraction/proc/verb__##PATH_SUFFIX; \
|
||||
reflection_path = /datum/admin_verb_abstraction/proc/verb__invoke_##PATH_SUFFIX; \
|
||||
}; \
|
||||
/datum/admin_verb_abstraction/proc/verb__##PATH_SUFFIX(##HEADER) { \
|
||||
set name = NAME; \
|
||||
set desc = DESC; \
|
||||
set category = CATEGORY; \
|
||||
set hidden = FALSE; \
|
||||
set popup_menu = POPUP_MENU; \
|
||||
if(!((usr?.client?.holder?.rights & ##REQUIRED_RIGHTS) == ##REQUIRED_RIGHTS)) {\
|
||||
CRASH("attempted invocation with insufficient rights."); \
|
||||
}; \
|
||||
do { \
|
||||
metric_increment_nested_numerical(/datum/metric/nested_numerical/admin_verb_invocation, #PATH_SUFFIX, 1); \
|
||||
}; \
|
||||
while(FALSE); \
|
||||
call(usr.client, /datum/admin_verb_abstraction::verb__invoke_##PATH_SUFFIX())(arglist(list(usr.client) + args)); \
|
||||
}; \
|
||||
/datum/admin_verb_abstraction/proc/verb__invoke_##PATH_SUFFIX(client/caller, ##HEADER)
|
||||
|
||||
/**
|
||||
* Abstract datum with no variables. Used to hold the proc definitions for admin verbs.
|
||||
*
|
||||
* * The way this works is black magic, so, uh, no touchy I guess.
|
||||
*/
|
||||
/datum/admin_verb_abstraction
|
||||
abstract_type = /datum/admin_verb_abstraction
|
||||
@@ -1,24 +0,0 @@
|
||||
// This port is currently half-assed, we do not include the entire avd stuff
|
||||
|
||||
/// Use this to mark your verb as not having a description. Should ONLY be used if you are also hiding the verb!
|
||||
#define ADMIN_VERB_NO_DESCRIPTION ""
|
||||
/// Used to verbs you do not want to show up in the master verb panel.
|
||||
#define ADMIN_CATEGORY_HIDDEN null
|
||||
|
||||
// Admin verb categories
|
||||
#define ADMIN_CATEGORY_MAIN "Admin"
|
||||
#define ADMIN_CATEGORY_EVENTS "Admin.Events"
|
||||
#define ADMIN_CATEGORY_FUN "Admin.Fun"
|
||||
#define ADMIN_CATEGORY_GAME "Admin.Game"
|
||||
#define ADMIN_CATEGORY_SHUTTLE "Admin.Shuttle"
|
||||
|
||||
// Special categories that are separated
|
||||
#define ADMIN_CATEGORY_DEBUG "Debug"
|
||||
#define ADMIN_CATEGORY_SERVER "Server"
|
||||
#define ADMIN_CATEGORY_OBJECT "Object"
|
||||
#define ADMIN_CATEGORY_MAPPING "Mapping"
|
||||
#define ADMIN_CATEGORY_PROFILE "Profile"
|
||||
#define ADMIN_CATEGORY_IPINTEL "Admin.IPIntel"
|
||||
|
||||
// Visibility flags
|
||||
#define ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG "Map-Debug"
|
||||
@@ -7,3 +7,8 @@
|
||||
#define VERB_CATEGORY_SYSTEM "System"
|
||||
|
||||
// todo: admin verb categories too
|
||||
|
||||
#define VERB_CATEGORY_ADMIN "Admin"
|
||||
#define VERB_CATEGORY_DEBUG "Debug"
|
||||
#define VERB_CATEGORY_GAME "Game"
|
||||
#define VERB_CATEGORY_SERVER "Server"
|
||||
|
||||
@@ -3,4 +3,5 @@
|
||||
|
||||
//* Metric Categories *//
|
||||
|
||||
#define METRIC_CATEGORY_ADMIN "admin"
|
||||
#define METRIC_CATEGORY_SERVER "server"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// todo: DO NOT FUCKING USE THIS
|
||||
// it is *EXTREMELY* inefficient, and scales up quadratically in time complexity
|
||||
// DO NOT USE THIS UNTIL IT IS REWRITTEN
|
||||
// notably that "bad node trimming" is actually horrifying.
|
||||
// TODO: optimize this for use on actual graphs; make it efficient and generic.
|
||||
// while we do not currently have a use for generic graph-search, we sure as hell
|
||||
// will eventually.
|
||||
|
||||
/**
|
||||
* A Star pathfinding algorithm
|
||||
|
||||
8
code/game/atoms/movable/movable-admin.dm
Normal file
8
code/game/atoms/movable/movable-admin.dm
Normal file
@@ -0,0 +1,8 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2025 Citadel Station Developers *//
|
||||
|
||||
/**
|
||||
* @return null if not supported, or list of mobs to target
|
||||
*/
|
||||
/atom/movable/proc/admin_resolve_narrate() as /list
|
||||
return null
|
||||
@@ -836,19 +836,6 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out.
|
||||
if(isturf(loc) && I.obj_storage?.allow_mass_gather && I.obj_storage.allow_mass_gather_via_click)
|
||||
I.obj_storage.auto_handle_interacted_mass_pickup(new /datum/event_args/actor(user), src)
|
||||
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
|
||||
if(istype(I, /obj/item/cell) && !isnull(obj_cell_slot) && isnull(obj_cell_slot.cell) && obj_cell_slot.interaction_active(user))
|
||||
if(!user.transfer_item_to_loc(I, src))
|
||||
user.action_feedback(SPAN_WARNING("[I] is stuck to your hand!"), src)
|
||||
return CLICKCHAIN_DO_NOT_PROPAGATE
|
||||
user.visible_action_feedback(
|
||||
target = src,
|
||||
hard_range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION,
|
||||
visible_hard = SPAN_NOTICE("[user] inserts [I] into [src]."),
|
||||
audible_hard = SPAN_NOTICE("You hear something being slotted in."),
|
||||
visible_self = SPAN_NOTICE("You insert [I] into [src]."),
|
||||
)
|
||||
obj_cell_slot.insert_cell(I)
|
||||
return CLICKCHAIN_DO_NOT_PROPAGATE | CLICKCHAIN_DID_SOMETHING
|
||||
return ..()
|
||||
|
||||
/**
|
||||
|
||||
@@ -404,6 +404,12 @@
|
||||
if(!user.transfer_item_to_loc(I, src))
|
||||
user.action_feedback(SPAN_WARNING("[I] is stuck to your hand!"), src)
|
||||
return CLICKCHAIN_DO_NOT_PROPAGATE
|
||||
if(!obj_cell_slot.accepts_cell(I))
|
||||
user.action_feedback(
|
||||
SPAN_WARNING("[src] do,es not accept [I]."),
|
||||
target = src,
|
||||
)
|
||||
return CLICKCHAIN_DO_NOT_PROPAGATE
|
||||
user.visible_action_feedback(
|
||||
target = src,
|
||||
hard_range = obj_cell_slot.remove_is_discrete? 0 : MESSAGE_RANGE_CONSTRUCTION,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2025 Citadel Station Developers *//
|
||||
|
||||
/obj/structure/closet/admin_resolve_narrate()
|
||||
. = list()
|
||||
for(var/mob/target in .)
|
||||
. += target
|
||||
@@ -48,6 +48,9 @@
|
||||
/datum/object_system/cell_slot/proc/accepts_cell(obj/item/cell/cell)
|
||||
return legacy_use_device_cells? istype(cell, /obj/item/cell/device) : TRUE
|
||||
|
||||
// TODO: user_remove_cell && user_insert_cell
|
||||
// TODO: play sound please & visible message
|
||||
|
||||
/**
|
||||
* removes cell from the system and drops it at new_loc
|
||||
*/
|
||||
|
||||
@@ -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
code/modules/admin/admin_holder.dm
Normal file
76
code/modules/admin/admin_holder.dm
Normal 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 ..()
|
||||
70
code/modules/admin/admin_modal/admin_modal.dm
Normal file
70
code/modules/admin/admin_modal/admin_modal.dm
Normal file
@@ -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>")
|
||||
|
||||
361
code/modules/admin/admin_modal/modals/admin_narrate.dm
Normal file
361
code/modules/admin/admin_modal/modals/admin_narrate.dm
Normal file
@@ -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
|
||||
45
code/modules/admin/admin_verb_descriptor.dm
Normal file
45
code/modules/admin/admin_verb_descriptor.dm
Normal file
@@ -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
|
||||
@@ -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>")
|
||||
|
||||
@@ -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
code/modules/admin/verbs/admin/delete.dm
Normal file
8
code/modules/admin/verbs/admin/delete.dm
Normal 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)
|
||||
@@ -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
code/modules/admin/verbs/game/narrate.dm
Normal file
5
code/modules/admin/verbs/game/narrate.dm
Normal 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)
|
||||
95
code/modules/admin/verbs/game/narrate_quick.dm
Normal file
95
code/modules/admin/verbs/game/narrate_quick.dm
Normal file
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
@@ -173,9 +173,6 @@
|
||||
/client/New(TopicData)
|
||||
//* pre-connect-ish *//
|
||||
|
||||
// Byond only populates whether or not you can profile at connect. You have to give someone this
|
||||
// before their client loads/whatever. This cannot be behind a spawn(). We will remove it from non-admins later.
|
||||
world.SetConfig("APP/admin", ckey, "role=admin")
|
||||
// Block client.Topic() calls from connect.
|
||||
TopicData = null
|
||||
// Kick invalid connections.
|
||||
@@ -238,39 +235,20 @@
|
||||
action_holder = new /datum/action_holder/client_actor(src)
|
||||
action_drawer.register_holder(action_holder)
|
||||
|
||||
//* Setup admin tooling
|
||||
//* Setup admin tooling *//
|
||||
// Notify tickets they logged in
|
||||
GLOB.ahelp_tickets.ClientLogin(src)
|
||||
// var/connecting_admin = FALSE //because de-admined admins connecting should be treated like admins.
|
||||
//Admin Authorisation
|
||||
holder = admin_datums[ckey]
|
||||
var/debug_tools_allowed = FALSE
|
||||
if(holder)
|
||||
GLOB.admins |= src
|
||||
holder.owner = src
|
||||
// connecting_admin = TRUE
|
||||
//if(check_rights_for(src, R_DEBUG))
|
||||
if(R_DEBUG & holder?.rights) //same wiht this, check_rights when?
|
||||
debug_tools_allowed = TRUE
|
||||
/*
|
||||
else if(GLOB.deadmins[ckey])
|
||||
add_verb(src, /client/proc/readmin)
|
||||
connecting_admin = TRUE
|
||||
*/
|
||||
// if(CONFIG_GET(flag/enable_localhost_rank) && !connecting_admin)
|
||||
if(is_localhost() && CONFIG_GET(flag/enable_localhost_rank))
|
||||
// Give them admin if they're an admin
|
||||
// TODO: Maybe don't do it if they're deadminned..
|
||||
var/datum/admins/maybe_holder = admin_datums[ckey]
|
||||
maybe_holder?.associate(src)
|
||||
|
||||
//! TODO: This is shitcode, fix it.
|
||||
if(is_localhost() && CONFIG_GET(flag/enable_localhost_rank) && !holder)
|
||||
holder = new /datum/admins("!localhost!", ALL, ckey)
|
||||
holder.owner = src
|
||||
GLOB.admins |= src
|
||||
//admins |= src // this makes them not have admin. what the fuck??
|
||||
// holder.associate(ckey)
|
||||
// connecting_admin = TRUE
|
||||
//CITADEL EDIT
|
||||
//if(check_rights_for(src, R_DEBUG)) //check if autoadmin gave us it
|
||||
if(R_DEBUG & holder?.rights) //this is absolutely horrid
|
||||
debug_tools_allowed = TRUE
|
||||
if(!debug_tools_allowed)
|
||||
world.SetConfig("APP/admin", ckey, null)
|
||||
//END CITADEL EDIT
|
||||
holder.associate(src)
|
||||
//! END
|
||||
|
||||
// todo: refactor and hoist
|
||||
//preferences datum - also holds some persistent data for the client (because we may as well keep these datums to a minimum)
|
||||
prefs = GLOB.preferences_datums[ckey]
|
||||
@@ -417,9 +395,8 @@
|
||||
prefs.client = null
|
||||
prefs = null
|
||||
SSserver_maint.UpdateHubStatus()
|
||||
if(holder)
|
||||
holder.owner = null
|
||||
GLOB.admins -= src //delete them on the managed one too
|
||||
|
||||
holder?.disassociate()
|
||||
|
||||
//* Cleanup rendering *//
|
||||
if(using_perspective)
|
||||
|
||||
7
code/modules/metrics/metrics/admin.dm
Normal file
7
code/modules/metrics/metrics/admin.dm
Normal file
@@ -0,0 +1,7 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/datum/metric/nested_numerical/admin_verb_invocation
|
||||
id = "admin-verb-invocation"
|
||||
name = "Admin - Verb Invocation"
|
||||
category = METRIC_CATEGORY_ADMIN
|
||||
@@ -1,3 +1,6 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
/datum/metric/nested_numerical/subsystem_init_time
|
||||
id = "subsystem-init-time"
|
||||
name = "Init Time - Subsystem"
|
||||
|
||||
5
code/modules/mob/mob-admin.dm
Normal file
5
code/modules/mob/mob-admin.dm
Normal file
@@ -0,0 +1,5 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2025 Citadel Station Developers *//
|
||||
|
||||
/mob/admin_resolve_narrate()
|
||||
return list(src)
|
||||
@@ -114,6 +114,24 @@
|
||||
return 0
|
||||
return y - overmap.lower_left_y + 1
|
||||
|
||||
/**
|
||||
* Get floating point tile X where being on a tile is considered being at the center of it.
|
||||
*/
|
||||
/obj/overmap/entity/proc/get_tile_x_f()
|
||||
if(!overmap)
|
||||
return 0
|
||||
var/center = bound_x + bound_width * 0.5
|
||||
return x - overmap.lower_left_x + 1 + (center - (WORLD_ICON_SIZE * 0.5)) / WORLD_ICON_SIZE
|
||||
|
||||
/**
|
||||
* Get floating point tile Y where being on a tile is considered being at the center of it.
|
||||
*/
|
||||
/obj/overmap/entity/proc/get_tile_y_f()
|
||||
if(!overmap)
|
||||
return 0
|
||||
var/center = bound_y + bound_height * 0.5
|
||||
return y - overmap.lower_left_y + 1 + (center - (WORLD_ICON_SIZE * 0.5)) / WORLD_ICON_SIZE
|
||||
|
||||
/**
|
||||
* gets our movement (non-angular) speed in overmaps units per second
|
||||
*/
|
||||
|
||||
15
code/modules/tgui/states/admin_modal_state.dm
Normal file
15
code/modules/tgui/states/admin_modal_state.dm
Normal file
@@ -0,0 +1,15 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2024 Citadel Station Developers *//
|
||||
|
||||
//* UI State *//
|
||||
|
||||
GLOBAL_DATUM_INIT(ui_admin_modal_state, /datum/ui_state/admin_modal_state, new)
|
||||
|
||||
/**
|
||||
* Admin modal state. **Only valid on /datum/admin_modal.**
|
||||
*/
|
||||
VV_PROTECT_READONLY(/datum/admin_modal_state)
|
||||
/datum/ui_state/admin_modal_state
|
||||
|
||||
/datum/ui_state/admin_modal_state/can_use_topic(datum/admin_modal/src_object, mob/user)
|
||||
return src_object.owner.owner == user.client ? UI_INTERACTIVE : UI_CLOSE
|
||||
5
code/modules/vehicles/vehicle-admin.dm
Normal file
5
code/modules/vehicles/vehicle-admin.dm
Normal file
@@ -0,0 +1,5 @@
|
||||
//* This file is explicitly licensed under the MIT license. *//
|
||||
//* Copyright (c) 2025 Citadel Station Developers *//
|
||||
|
||||
/obj/vehicle/admin_resolve_narrate()
|
||||
return occupants
|
||||
266
tgui/packages/tgui/interfaces/admin_modal/AdminNarrate.tsx
Normal file
266
tgui/packages/tgui/interfaces/admin_modal/AdminNarrate.tsx
Normal file
@@ -0,0 +1,266 @@
|
||||
/**
|
||||
* @file
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Component, Fragment } from "react";
|
||||
import { Button, LabeledList, NumberInput, Section, Stack, TextArea } from "tgui-core/components";
|
||||
import { BooleanLike } from "tgui-core/react";
|
||||
|
||||
import { useBackend } from "../../backend";
|
||||
import { Window } from "../../layouts";
|
||||
import { ByondColorString } from "../common/Color";
|
||||
|
||||
interface AdminNarrateData {
|
||||
visualColor: ByondColorString;
|
||||
possibleModes: AdminNarrateMode[];
|
||||
mode: AdminNarrateMode;
|
||||
rawHtml: string;
|
||||
target: AdminNarrateTarget | null;
|
||||
useLos: BooleanLike;
|
||||
useRange: number;
|
||||
maxRangeLos: number;
|
||||
maxRangeAny: number;
|
||||
}
|
||||
|
||||
interface AdminNarrateTarget {
|
||||
coords: [number, number, number] | null;
|
||||
level: {
|
||||
index: number;
|
||||
name: string;
|
||||
} | null;
|
||||
sector: {
|
||||
name: string;
|
||||
} | null;
|
||||
overmap: {
|
||||
name: string;
|
||||
x: number;
|
||||
y: number;
|
||||
map: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
enum AdminNarrateMode {
|
||||
Global = "global",
|
||||
Sector = "sector",
|
||||
Overmap = "overmap",
|
||||
Level = "level",
|
||||
Range = "range",
|
||||
Direct = "direct",
|
||||
Lobby = "lobby",
|
||||
}
|
||||
|
||||
const MODES_REQUIRING_TARGET: AdminNarrateMode[] = [
|
||||
AdminNarrateMode.Direct,
|
||||
AdminNarrateMode.Range,
|
||||
AdminNarrateMode.Overmap,
|
||||
AdminNarrateMode.Sector,
|
||||
AdminNarrateMode.Level,
|
||||
];
|
||||
|
||||
const MODES_REQUIRING_RANGE: AdminNarrateMode[] = [
|
||||
AdminNarrateMode.Range,
|
||||
AdminNarrateMode.Overmap,
|
||||
];
|
||||
|
||||
const ADMIN_NARRATE_MODE_NAMES: Record<AdminNarrateMode, string> = {
|
||||
[AdminNarrateMode.Global]: "Global",
|
||||
[AdminNarrateMode.Sector]: "Sector",
|
||||
[AdminNarrateMode.Overmap]: "Overmap",
|
||||
[AdminNarrateMode.Level]: "Level",
|
||||
[AdminNarrateMode.Range]: "Range",
|
||||
[AdminNarrateMode.Direct]: "Direct",
|
||||
[AdminNarrateMode.Lobby]: "Lobby",
|
||||
};
|
||||
|
||||
const ADMIN_NARRATE_MODE_DESCS: Record<AdminNarrateMode, string> = {
|
||||
[AdminNarrateMode.Global]: "Send to everyone in the server, including those sitting in the lobby.",
|
||||
[AdminNarrateMode.Sector]: "Send to everyone in the logical map, whether that map may be a planet or a ship. This includes anyone sitting in its z-levels but not on the map, e.g. visiting shuttles.",
|
||||
[AdminNarrateMode.Overmap]: "Send to target and nearby overmap entities. Range is measured in <b>tiles</b>, not in overmap distance!",
|
||||
[AdminNarrateMode.Level]: "Send to everyone in the z-level, regardless of line of sight.",
|
||||
[AdminNarrateMode.Range]: "Send to everyone within range. If 'LoS' is enabled, this will check for viewers. Viewer check is done via BYOND 'viewers()', entirely ignoring things like blindness, darksight, etc.",
|
||||
[AdminNarrateMode.Direct]: "Send directly to an entity. What this does depends on the entity; it generally will target the mob itself if it's a mob, everyone inside a vehicle, the overmap level if it's an overmap entity, etc. This is a logical 'contains'. Note: Vore bellies are excluded from this (I hate that I have to specify it).",
|
||||
[AdminNarrateMode.Lobby]: "Send to everyone in the lobby.",
|
||||
};
|
||||
|
||||
interface AdminNarrateState {
|
||||
emitHtml: string | null;
|
||||
edited: boolean;
|
||||
}
|
||||
|
||||
export class AdminNarrate extends Component<{}, AdminNarrateState> {
|
||||
timeoutRef: any;
|
||||
state: AdminNarrateState = {
|
||||
emitHtml: null,
|
||||
edited: false,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.timeoutRef = setInterval(() => {
|
||||
if (this.state.edited) {
|
||||
this.setState((old) => ({ ...old, edited: false }));
|
||||
const { act } = useBackend<AdminNarrateData>();
|
||||
act("setOutput", { target: this.state.emitHtml });
|
||||
}
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
clearTimeout(this.timeoutRef);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { act, data } = useBackend<AdminNarrateData>();
|
||||
|
||||
return (
|
||||
<Window width={900} height={500} title="Narrate">
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow={1}>
|
||||
<Stack fill>
|
||||
<Stack.Item width="70%">
|
||||
<Section title={(
|
||||
// eslint-disable-next-line react/no-danger
|
||||
<div dangerouslySetInnerHTML={{ __html: "Content - <b>Shift-Enter</b> to insert a Line-Break!" }} />
|
||||
)} fill>
|
||||
<TextArea width="100%" height="100%"
|
||||
value={this.state.emitHtml || ""}
|
||||
onChange={(val) =>
|
||||
this.setState((old) =>
|
||||
({ ...old, edited: true, emitHtml: val }))} />
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item width="30%">
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow={1}>
|
||||
<Section title="Mode" fill>
|
||||
<Stack fill vertical>
|
||||
{data.possibleModes.map((mode) => {
|
||||
let selected = mode === data.mode;
|
||||
return (
|
||||
<Stack.Item key={mode}>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button icon="question" tooltip={ADMIN_NARRATE_MODE_DESCS[mode]} />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
style={{ textAlign: "left" }}
|
||||
fluid color={selected ? "" : "transparent"} selected={selected}
|
||||
onClick={() => act('setMode', { mode: mode })}>
|
||||
{ADMIN_NARRATE_MODE_NAMES[mode]}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<Section title="Settings" fill>
|
||||
<Stack vertical fill>
|
||||
{MODES_REQUIRING_RANGE.includes(data.mode) && (
|
||||
<>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow={1}>
|
||||
Use LoS
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button icon="question" tooltip="Whether the narrate will check line of sight. If enabled, only people who can see the entity can view it; otherwise everyone in Chebyshev (square-radius) distance can." />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<Stack fill>
|
||||
<Stack.Item grow={1}>
|
||||
<Button fluid selected={data.useLos} onClick={() => act('setLos', { target: true })}>Yes</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<Button fluid selected={!data.useLos} onClick={() => act('setLos', { target: false })}>No</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow={1}>
|
||||
Range
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button icon="question" tooltip="Distance, in tiles, to broadcast. Fractional tiles are allowed in overmaps mode." />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<NumberInput width="100%"
|
||||
step={data.mode === AdminNarrateMode.Overmap ? 0.01 : 1}
|
||||
value={data.useRange}
|
||||
minValue={0}
|
||||
maxValue={data.maxRangeAny}
|
||||
onChange={(val) => act('setRange', { target: val })} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<Section title="Target" fill>
|
||||
<LabeledList>
|
||||
{data.target?.coords && (
|
||||
<LabeledList.Item label="Coords">{data.target.coords[0]}, {data.target.coords[1]}, {data.target.coords[2]}</LabeledList.Item>
|
||||
)}
|
||||
{data.target?.level && (
|
||||
<LabeledList.Item label="Level">{data.target.level.name} - Z{data.target.level.index}</LabeledList.Item>
|
||||
)}
|
||||
{data.target?.sector && (
|
||||
<LabeledList.Item label="Sector">{data.target.sector.name}</LabeledList.Item>
|
||||
)}
|
||||
{data.target?.overmap && (
|
||||
<LabeledList.Item label="Overmap">{data.target.overmap.name} @ {data.target.overmap.x} - {data.target.overmap.y} ({data.target.overmap.map})</LabeledList.Item>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section fill>
|
||||
<Stack fill>
|
||||
<Stack.Item grow={1}>
|
||||
<Button.Confirm fluid
|
||||
style={{ textAlign: "center" }}
|
||||
color="transparent"
|
||||
onClick={() => act("cancel")}>
|
||||
Cancel
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<Button.Confirm fluid
|
||||
style={{ textAlign: "center" }}
|
||||
color="transparent"
|
||||
onClick={() => act("preview", { html: this.state.emitHtml })}>
|
||||
Preview
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={1}>
|
||||
<Button.Confirm fluid
|
||||
style={{ textAlign: "center" }}
|
||||
color="transparent"
|
||||
onClick={() => act("narrate", { html: this.state.emitHtml })}>
|
||||
Send
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content >
|
||||
</Window >
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user