From f394368b06bde5867156ffae534a6da88791d985 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Fri, 21 Jul 2023 20:12:17 +0100
Subject: [PATCH 01/12] Port tg statpanel
---
code/__DEFINES/_subsystems.dm | 2 +
code/__DEFINES/dcs/signals.dm | 6 +
code/__DEFINES/procpath.dm | 10 +-
code/__HELPERS/icons.dm | 4 +-
code/__HELPERS/verbs.dm | 98 ++
code/_onclick/click.dm | 7 +-
.../configuration/configuration.dm | 7 +-
code/controllers/controller.dm | 2 +-
code/controllers/failsafe.dm | 8 +-
code/controllers/globals.dm | 8 +-
code/controllers/master.dm | 8 +-
code/controllers/subsystem.dm | 12 +-
.../subsystem/advanced_pathfinding.dm | 6 +-
code/controllers/subsystem/autofire.dm | 3 +-
code/controllers/subsystem/events.dm | 4 +-
code/controllers/subsystem/explosions.dm | 2 +-
code/controllers/subsystem/garbage.dm | 2 +-
code/controllers/subsystem/idlenpcpool.dm | 5 +-
code/controllers/subsystem/lighting.dm | 5 +-
code/controllers/subsystem/machines.dm | 5 +-
code/controllers/subsystem/mobs.dm | 5 +-
code/controllers/subsystem/npcpool.dm | 5 +-
.../subsystem/processing/obj_tab_items.dm | 24 +
.../subsystem/processing/processing.dm | 5 +-
code/controllers/subsystem/statpanel.dm | 335 ++++++
code/datums/components/connect_mob_behalf.dm | 63 ++
code/datums/gamemodes/_game_mode.dm | 6 +-
code/datums/mind.dm | 1 +
code/game/atoms.dm | 26 +
code/game/objects/items/embedding.dm | 6 +-
code/game/verbs/ooc.dm | 6 +
code/modules/admin/admin_verbs.dm | 6 +-
code/modules/admin/debug_verbs.dm | 6 +
code/modules/admin/fun_verbs.dm | 66 +-
code/modules/admin/holder.dm | 42 +-
code/modules/admin/verbs/SDQL2/SDQL_2.dm | 12 +-
code/modules/admin/verbs/adminhelp.dm | 28 +-
code/modules/admin/verbs/datumvars.dm | 12 +-
code/modules/admin/verbs/faxes.dm | 2 +-
code/modules/admin/verbs/podlauncher.dm | 2 +-
code/modules/client/client_defines.dm | 12 +
code/modules/client/client_procs.dm | 66 +-
code/modules/client/preferences.dm | 5 +
code/modules/client/preferences_savefile.dm | 13 +
code/modules/client/preferences_ui.dm | 9 +
code/modules/mob/dead/observer/observer.dm | 88 +-
code/modules/mob/living/carbon/human/human.dm | 63 +-
.../mob/living/carbon/human/species.dm | 6 +-
.../xenomorph/castes/carrier/carrier.dm | 5 +-
.../xenomorph/castes/hivelord/hivelord.dm | 6 +-
.../mob/living/carbon/xenomorph/xenoprocs.dm | 36 +-
code/modules/mob/living/silicon/ai/ai.dm | 40 +-
.../living/simple_animal/friendly/parrot.dm | 5 +-
.../mob/living/simple_animal/simple_animal.dm | 6 +-
code/modules/mob/login.dm | 1 +
code/modules/mob/mob.dm | 70 +-
code/modules/mob/new_player/new_player.dm | 27 +-
html/statbrowser.css | 227 ++++
html/statbrowser.html | 3 +
html/statbrowser.js | 1003 +++++++++++++++++
interface/skin.dmf | 10 +-
tgmc.dme | 4 +
.../PlayerPreferences/GameSettings.tsx | 34 +-
.../interfaces/PlayerPreferences/Types.tsx | 1 +
64 files changed, 2233 insertions(+), 369 deletions(-)
create mode 100644 code/__HELPERS/verbs.dm
create mode 100644 code/controllers/subsystem/processing/obj_tab_items.dm
create mode 100644 code/controllers/subsystem/statpanel.dm
create mode 100644 code/datums/components/connect_mob_behalf.dm
create mode 100644 html/statbrowser.css
create mode 100644 html/statbrowser.html
create mode 100644 html/statbrowser.js
diff --git a/code/__DEFINES/_subsystems.dm b/code/__DEFINES/_subsystems.dm
index da73dede763f8..99d7ee1143da6 100755
--- a/code/__DEFINES/_subsystems.dm
+++ b/code/__DEFINES/_subsystems.dm
@@ -110,6 +110,7 @@
#define INIT_ORDER_PATH -50
#define INIT_ORDER_EXPLOSIONS -69
#define INIT_ORDER_EXCAVATION -78
+#define INIT_ORDER_STATPANELS -97
#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init.
// Subsystem fire priority, from lowest to highest priority
@@ -140,6 +141,7 @@
#define FIRE_PRIORITY_MOBS 100
#define FIRE_PRIORITY_TGUI 110
#define FIRE_PRIORITY_TICKER 200
+#define FIRE_PRIORITY_STATPANEL 390
#define FIRE_PRIORITY_CHAT 400
#define FIRE_PRIORITY_LOOPINGSOUND 405
#define FIRE_PRIORITY_RUNECHAT 410
diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 644e5bec93637..c0c8d41486d5b 100755
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -155,6 +155,10 @@
#define COMSIG_CLIENT_MOUSEDRAG "client_mousedrag" //from base of client/MouseUp(): (/client, object, location, control, params)
#define COMSIG_CLIENT_DISCONNECTED "client_disconnecred" //from base of /client/Destroy(): (/client)
#define COMSIG_CLIENT_PREFERENCES_UIACTED "client_preferences_uiacted" //called after preferences have been updated for this client after /datum/preferences/ui_act has completed
+/// Called after one or more verbs are added: (list of verbs added)
+#define COMSIG_CLIENT_VERB_ADDED "client_verb_added"
+/// Called after one or more verbs are removed: (list of verbs added)
+#define COMSIG_CLIENT_VERB_REMOVED "client_verb_removed"
// /atom signals
#define COMSIG_ATOM_ATTACKBY "atom_attackby" //from base of atom/attackby(): (/obj/item, /mob/living)
@@ -451,6 +455,8 @@
#define COMSIG_RANGED_SCATTER_MOD_CHANGED "ranged_scatter_mod_changed"
#define COMSIG_MOB_SKILLS_CHANGED "mob_skills_changed"
#define COMSIG_MOB_SHOCK_STAGE_CHANGED "mob_shock_stage_changed"
+/// from mob/get_status_tab_items(): (list/items)
+#define COMSIG_MOB_GET_STATUS_TAB_ITEMS "mob_get_status_tab_items"
//mob/dead/observer
#define COMSIG_OBSERVER_CLICKON "observer_clickon" //from mob/dead/observer/ClickOn(): (atom/A, params)
diff --git a/code/__DEFINES/procpath.dm b/code/__DEFINES/procpath.dm
index 81087555e9806..642ca3eab6cc8 100644
--- a/code/__DEFINES/procpath.dm
+++ b/code/__DEFINES/procpath.dm
@@ -15,10 +15,12 @@
// below, their accesses are optimized away.
/// A text string of the verb's name.
- var/name
+ var/name as text
/// The verb's help text or description.
- var/desc
+ var/desc as text
/// The category or tab the verb will appear in.
- var/category
+ var/category as text
/// Only clients/mobs with `see_invisibility` higher can use the verb.
- var/invisibility
+ var/invisibility as num
+ /// Whether or not the verb appears in statpanel and commandbar when you press space
+ var/hidden as num
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index b175474bce61c..2b086c5ff90fc 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1055,7 +1055,7 @@ ColorTone(rgb, tone)
//Costlier version of icon2html() that uses getFlatIcon() to account for overlays, underlays, etc. Use with extreme moderation, ESPECIALLY on mobs.
-/proc/costly_icon2html(thing, target)
+/proc/costly_icon2html(thing, target, sourceonly = FALSE)
if(!thing)
return
@@ -1063,7 +1063,7 @@ ColorTone(rgb, tone)
return icon2html(thing, target)
var/icon/I = getFlatIcon(thing)
- return icon2html(I, target)
+ return icon2html(I, target, sourceonly = sourceonly)
//For creating consistent icons for human looking simple animals
diff --git a/code/__HELPERS/verbs.dm b/code/__HELPERS/verbs.dm
new file mode 100644
index 0000000000000..badd3c6727ae1
--- /dev/null
+++ b/code/__HELPERS/verbs.dm
@@ -0,0 +1,98 @@
+/**
+ * handles adding verbs and updating the stat panel browser
+ *
+ * pass the verb type path to this instead of adding it directly to verbs so the statpanel can update
+ * Arguments:
+ * * target - Who the verb is being added to, client or mob typepath
+ * * verb - typepath to a verb, or a list of verbs, supports lists of lists
+ */
+/proc/add_verb(client/target, verb_or_list_to_add)
+ if(!target)
+ CRASH("add_verb called without a target")
+ if(IsAdminAdvancedProcCall())
+ return
+ var/mob/mob_target = null
+
+ if(ismob(target))
+ mob_target = target
+ target = mob_target.client
+ else if(!istype(target, /client))
+ CRASH("add_verb called on a non-mob and non-client")
+ var/list/verbs_list = list()
+ if(!islist(verb_or_list_to_add))
+ verbs_list += verb_or_list_to_add
+ else
+ var/list/verb_listref = verb_or_list_to_add
+ var/list/elements_to_process = verb_listref.Copy()
+ while(length(elements_to_process))
+ var/element_or_list = elements_to_process[length(elements_to_process)] //Last element
+ elements_to_process.len--
+ if(islist(element_or_list))
+ elements_to_process += element_or_list //list/a += list/b adds the contents of b into a, not the reference to the list itself
+ else
+ verbs_list += element_or_list
+
+ if(mob_target)
+ mob_target.verbs += verbs_list
+ if(!target)
+ return //Our work is done.
+ else
+ target.verbs += verbs_list
+
+ var/list/output_list = list()
+ for(var/thing in verbs_list)
+ var/procpath/verb_to_add = thing
+ output_list[++output_list.len] = list(verb_to_add.category, verb_to_add.name)
+
+ target.stat_panel.send_message("add_verb_list", output_list)
+
+ SEND_SIGNAL(target, COMSIG_CLIENT_VERB_ADDED, verbs_list)
+
+/**
+ * handles removing verb and sending it to browser to update, use this for removing verbs
+ *
+ * pass the verb type path to this instead of removing it from verbs so the statpanel can update
+ * Arguments:
+ * * target - Who the verb is being removed from, client or mob typepath
+ * * verb - typepath to a verb, or a list of verbs, supports lists of lists
+ */
+/proc/remove_verb(client/target, verb_or_list_to_remove)
+ if(IsAdminAdvancedProcCall())
+ return
+
+ var/mob/mob_target = null
+ if(ismob(target))
+ mob_target = target
+ target = mob_target.client
+ else if(!istype(target, /client))
+ CRASH("remove_verb called on a non-mob and non-client")
+
+ var/list/verbs_list = list()
+ if(!islist(verb_or_list_to_remove))
+ verbs_list += verb_or_list_to_remove
+ else
+ var/list/verb_listref = verb_or_list_to_remove
+ var/list/elements_to_process = verb_listref.Copy()
+ while(length(elements_to_process))
+ var/element_or_list = elements_to_process[length(elements_to_process)] //Last element
+ elements_to_process.len--
+ if(islist(element_or_list))
+ elements_to_process += element_or_list //list/a += list/b adds the contents of b into a, not the reference to the list itself
+ else
+ verbs_list += element_or_list
+
+ if(mob_target)
+ mob_target.verbs -= verbs_list
+ if(!target)
+ return //Our work is done.
+ else
+ target.verbs -= verbs_list
+
+ var/list/output_list = list()
+ for(var/thing in verbs_list)
+ var/procpath/verb_to_remove = thing
+ output_list[++output_list.len] = list(verb_to_remove.category, verb_to_remove.name)
+
+ target.stat_panel.send_message("remove_verb_list", output_list)
+
+ SEND_SIGNAL(target, COMSIG_CLIENT_VERB_REMOVED, verbs_list)
diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm
index ce34ebd0b95a2..4ca49e2fa4e86 100644
--- a/code/_onclick/click.dm
+++ b/code/_onclick/click.dm
@@ -465,10 +465,9 @@ if(selected_ability.target_flags & flagname && !istype(A, typepath)){\
/atom/proc/AltClick(mob/user)
SEND_SIGNAL(src, COMSIG_CLICK_ALT, user)
- var/turf/examined_turf = get_turf(src)
- if(examined_turf && user.TurfAdjacent(examined_turf))
- user.listed_turf = examined_turf
- user.client.statpanel = examined_turf.name
+ var/turf/T = get_turf(src)
+ if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T))
+ user.set_listed_turf(T)
return TRUE
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index a6d3b92ccea14..767ce41b7ba99 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -231,10 +231,9 @@
return !(var_name in banned_edits) && ..()
-/datum/controller/configuration/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Debug", src)
- stat("[name]:", statclick)
+/datum/controller/configuration/stat_entry(msg)
+ msg = "Edit"
+ return msg
/datum/controller/configuration/proc/Get(entry_type)
diff --git a/code/controllers/controller.dm b/code/controllers/controller.dm
index c9d5f1e5650c6..1f0a01a072cc5 100644
--- a/code/controllers/controller.dm
+++ b/code/controllers/controller.dm
@@ -16,4 +16,4 @@
/datum/controller/proc/Recover()
-/datum/controller/proc/stat_entry()
+/datum/controller/proc/stat_entry(msg)
diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm
index 45896b1546e4e..07b5650486809 100644
--- a/code/controllers/failsafe.dm
+++ b/code/controllers/failsafe.dm
@@ -176,8 +176,6 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/datum/controller/failsafe/proc/defcon_pretty()
return defcon
-/datum/controller/failsafe/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))
+/datum/controller/failsafe/stat_entry(msg)
+ msg = "Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"
+ return msg
diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm
index 756f8f04cbde9..3108a9ce30169 100644
--- a/code/controllers/globals.dm
+++ b/code/controllers/globals.dm
@@ -25,11 +25,9 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
SHOULD_CALL_PARENT(FALSE)
return QDEL_HINT_IWILLGC
-/datum/controller/global_vars/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- stat("Globals:", statclick.update("Debug"))
+/datum/controller/global_vars/stat_entry(msg)
+ msg = "Edit"
+ return msg
/datum/controller/global_vars/vv_edit_var(var_name, var_value)
if(gvars_datum_protected_varlist[var_name])
diff --git a/code/controllers/master.dm b/code/controllers/master.dm
index 56e96a466cbde..4827cb30cede1 100644
--- a/code/controllers/master.dm
+++ b/code/controllers/master.dm
@@ -784,11 +784,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new
/datum/controller/master/stat_entry()
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
- stat("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)")
- stat("Master Controller:", statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])"))
+/datum/controller/master/stat_entry(msg)
+ msg = "(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])"
+ return msg
/datum/controller/master/StartLoadingMap()
diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm
index 5b75e1377555e..8c458073221eb 100644
--- a/code/controllers/subsystem.dm
+++ b/code/controllers/subsystem.dm
@@ -198,21 +198,11 @@
//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc.
/datum/controller/subsystem/stat_entry(msg)
- if(!statclick)
- statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
-
-
-
if(can_fire && !(SS_NO_FIRE & flags) && init_stage <= Master.init_stage_completed)
msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]"
else
msg = "OFFLINE\t[msg]"
-
- var/title = name
- if (can_fire)
- title = "\[[state_letter()]][title]"
-
- stat(title, statclick.update(msg))
+ return msg
/datum/controller/subsystem/proc/state_letter()
switch (state)
diff --git a/code/controllers/subsystem/advanced_pathfinding.dm b/code/controllers/subsystem/advanced_pathfinding.dm
index 79b611f15e419..0564b0650b076 100644
--- a/code/controllers/subsystem/advanced_pathfinding.dm
+++ b/code/controllers/subsystem/advanced_pathfinding.dm
@@ -60,8 +60,10 @@ SUBSYSTEM_DEF(advanced_pathfinding)
if (MC_TICK_CHECK)
return
-/datum/controller/subsystem/advanced_pathfinding/stat_entry()
- ..("Node pathfinding : [length(node_pathfinding_to_do)] || Tile pathfinding : [length(tile_pathfinding_to_do)]")
+/datum/controller/subsystem/advanced_pathfinding/stat_entry(msg)
+ msg = "Node pathfinding : [length(node_pathfinding_to_do)] || Tile pathfinding : [length(tile_pathfinding_to_do)]"
+ return ..()
+
#define NODE_PATHING "node_pathing" //Looking through the network of nodes the best node path
#define TILE_PATHING "tile_pathing" //Looking the best tile path
diff --git a/code/controllers/subsystem/autofire.dm b/code/controllers/subsystem/autofire.dm
index 30e0c03bee14b..f4afb5cb4cd63 100644
--- a/code/controllers/subsystem/autofire.dm
+++ b/code/controllers/subsystem/autofire.dm
@@ -39,7 +39,8 @@ SUBSYSTEM_DEF(automatedfire)
head_offset = world.time
bucket_resolution = world.tick_lag
-/datum/controller/subsystem/automatedfire/stat_entry(msg = "ActShooters:[shooter_count]")
+/datum/controller/subsystem/automatedfire/stat_entry(msg)
+ msg = "ActShooters:[shooter_count]"
return ..()
/datum/controller/subsystem/automatedfire/fire(resumed = FALSE)
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index a584409f73e2f..a4d76f8e2f73e 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -97,7 +97,7 @@ SUBSYSTEM_DEF(events)
//aka Badmin Central
/client/proc/force_event()
set name = "Trigger Event"
- set category = "Fun"
+ set category = "Admin.Fun"
if(!holder ||!check_rights(R_FUN))
return
@@ -120,7 +120,7 @@ SUBSYSTEM_DEF(events)
/client/proc/toggle_events()
set name = "Toggle Events Subsystem"
- set category = "Fun"
+ set category = "Admin.Fun"
if(!holder ||!check_rights(R_FUN))
return
diff --git a/code/controllers/subsystem/explosions.dm b/code/controllers/subsystem/explosions.dm
index 549c15267849c..af289141309c1 100644
--- a/code/controllers/subsystem/explosions.dm
+++ b/code/controllers/subsystem/explosions.dm
@@ -63,7 +63,7 @@ SUBSYSTEM_DEF(explosions)
msg += "TO:[length(throwTurf)]"
msg += "} "
- ..(msg)
+ return ..()
#define SSEX_TURF "turf"
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index 2219b3593b041..42d66084d2200 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -77,7 +77,7 @@ SUBSYSTEM_DEF(garbage)
msg += "TGR:[round((totalgcs/(totaldels+totalgcs))*100, 0.01)]%"
msg += " P:[pass_counts.Join(",")]"
msg += "|F:[fail_counts.Join(",")]"
- ..(msg)
+ return ..()
/datum/controller/subsystem/garbage/Shutdown()
//Adds the del() log to the qdel log file
diff --git a/code/controllers/subsystem/idlenpcpool.dm b/code/controllers/subsystem/idlenpcpool.dm
index 8fa8dc4dc73ac..2a36583d81d50 100644
--- a/code/controllers/subsystem/idlenpcpool.dm
+++ b/code/controllers/subsystem/idlenpcpool.dm
@@ -9,10 +9,11 @@ SUBSYSTEM_DEF(idlenpcpool)
var/static/list/idle_mobs_by_zlevel[][]
-/datum/controller/subsystem/idlenpcpool/stat_entry()
+/datum/controller/subsystem/idlenpcpool/stat_entry(msg)
var/list/idlelist = GLOB.simple_animals[AI_IDLE]
var/list/zlist = GLOB.simple_animals[AI_Z_OFF]
- ..("IdleNPCS:[length(idlelist)]|Z:[length(zlist)]")
+ msg = "IdleNPCS:[length(idlelist)]|Z:[length(zlist)]"
+ return ..()
/datum/controller/subsystem/idlenpcpool/proc/MaxZChanged()
diff --git a/code/controllers/subsystem/lighting.dm b/code/controllers/subsystem/lighting.dm
index 900d9f444b810..60f17bac1842c 100644
--- a/code/controllers/subsystem/lighting.dm
+++ b/code/controllers/subsystem/lighting.dm
@@ -26,8 +26,9 @@ SUBSYSTEM_DEF(lighting)
return SS_INIT_SUCCESS
-/datum/controller/subsystem/lighting/stat_entry()
- . = ..("ShCalcs:[total_shadow_calculations]|SourcQ:[length(static_sources_queue)]|CcornQ:[length(corners_queue)]|ObjQ:[length(objects_queue)]|HybrQ:[length(mask_queue)]")
+/datum/controller/subsystem/lighting/stat_entry(msg)
+ msg = "ShCalcs:[total_shadow_calculations]|SourcQ:[length(static_sources_queue)]|CcornQ:[length(corners_queue)]|ObjQ:[length(objects_queue)]|HybrQ:[length(mask_queue)]"
+ return ..()
/datum/controller/subsystem/lighting/fire(resumed, init_tick_checks)
MC_SPLIT_TICK_INIT(3)
diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm
index 506d74f5cb7b9..2a3ac60cb664b 100644
--- a/code/controllers/subsystem/machines.dm
+++ b/code/controllers/subsystem/machines.dm
@@ -25,8 +25,9 @@ SUBSYSTEM_DEF(machines)
NewPN.add_cable(PC)
propagate_network(PC,PC.powernet)
-/datum/controller/subsystem/machines/stat_entry()
- ..("PN:[length(powernets)]|PM:[length(processing)]")
+/datum/controller/subsystem/machines/stat_entry(msg)
+ msg = "PM:[length(processing)]|PN:[length(powernets)]"
+ return ..()
/datum/controller/subsystem/machines/fire(resumed = FALSE)
if (!resumed)
diff --git a/code/controllers/subsystem/mobs.dm b/code/controllers/subsystem/mobs.dm
index e5d9f81320038..1b5b616344997 100644
--- a/code/controllers/subsystem/mobs.dm
+++ b/code/controllers/subsystem/mobs.dm
@@ -15,8 +15,9 @@ SUBSYSTEM_DEF(mobs)
var/list/list/crates = list(list(), list(), list(), list())
var/crate = 1
-/datum/controller/subsystem/mobs/stat_entry()
- ..("P:[length(processing)]")
+/datum/controller/subsystem/mobs/stat_entry(msg)
+ msg = "P:[length(GLOB.mob_living_list)]"
+ return ..()
/datum/controller/subsystem/mobs/proc/stop_processing(mob/living/L)
if(!CHECK_BITFIELD(L.datum_flags, DF_ISPROCESSING))
diff --git a/code/controllers/subsystem/npcpool.dm b/code/controllers/subsystem/npcpool.dm
index da298bccea747..06078e17b4d66 100644
--- a/code/controllers/subsystem/npcpool.dm
+++ b/code/controllers/subsystem/npcpool.dm
@@ -7,9 +7,10 @@ SUBSYSTEM_DEF(npcpool)
var/list/currentrun = list()
-/datum/controller/subsystem/npcpool/stat_entry()
+/datum/controller/subsystem/npcpool/stat_entry(msg)
var/list/activelist = GLOB.simple_animals[AI_ON]
- ..("NPCS:[length(activelist)]")
+ msg = "NPCS:[length(activelist)]"
+ return ..()
/datum/controller/subsystem/npcpool/fire(resumed = FALSE)
diff --git a/code/controllers/subsystem/processing/obj_tab_items.dm b/code/controllers/subsystem/processing/obj_tab_items.dm
new file mode 100644
index 0000000000000..53786daf0117e
--- /dev/null
+++ b/code/controllers/subsystem/processing/obj_tab_items.dm
@@ -0,0 +1,24 @@
+PROCESSING_SUBSYSTEM_DEF(obj_tab_items)
+ name = "Obj Tab Items"
+ flags = SS_NO_INIT
+ runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
+ wait = 0.1 SECONDS
+
+// I know this is mostly copypasta, but I want to change the processing logic
+// Sorry bestie :(
+/datum/controller/subsystem/processing/obj_tab_items/fire(resumed = FALSE)
+ if (!resumed)
+ currentrun = processing.Copy()
+ //cache for sanic speed (lists are references anyways)
+ var/list/current_run = currentrun
+
+ while(current_run.len)
+ var/datum/thing = current_run[current_run.len]
+ if(QDELETED(thing))
+ processing -= thing
+ else if(thing.process(wait * 0.1) == PROCESS_KILL)
+ // fully stop so that a future START_PROCESSING will work
+ STOP_PROCESSING(src, thing)
+ if (MC_TICK_CHECK)
+ return
+ current_run.len--
diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm
index 79605cc57ea39..1744f21517367 100644
--- a/code/controllers/subsystem/processing/processing.dm
+++ b/code/controllers/subsystem/processing/processing.dm
@@ -10,8 +10,9 @@ SUBSYSTEM_DEF(processing)
var/list/processing = list()
var/list/currentrun = list()
-/datum/controller/subsystem/processing/stat_entry()
- ..("[stat_tag]:[length(processing)]")
+/datum/controller/subsystem/processing/stat_entry(msg)
+ msg = "[stat_tag]:[length(processing)]"
+ return ..()
/datum/controller/subsystem/processing/fire(resumed = 0)
if (!resumed)
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
new file mode 100644
index 0000000000000..fdfc577677c79
--- /dev/null
+++ b/code/controllers/subsystem/statpanel.dm
@@ -0,0 +1,335 @@
+SUBSYSTEM_DEF(statpanels)
+ name = "Stat Panels"
+ wait = 4
+ init_order = INIT_ORDER_STATPANELS
+ init_stage = INITSTAGE_EARLY
+ priority = FIRE_PRIORITY_STATPANEL
+ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ flags = SS_NO_INIT
+ var/list/currentrun = list()
+ var/list/global_data
+ var/list/mc_data
+
+ ///how many subsystem fires between most tab updates
+ var/default_wait = 10
+ ///how many subsystem fires between updates of the status tab
+ var/status_wait = 2
+ ///how many subsystem fires between updates of the MC tab
+ var/mc_wait = 5
+ ///how many full runs this subsystem has completed. used for variable rate refreshes.
+ var/num_fires = 0
+
+/datum/controller/subsystem/statpanels/fire(resumed = FALSE)
+ if (!resumed)
+ num_fires++
+ global_data = list(
+ "Ground Map: [length(SSmapping.configs) ? SSmapping.configs[GROUND_MAP].map_name : "Loading..."]",
+ "Ship Map: [length(SSmapping.configs) ? SSmapping.configs[SHIP_MAP].map_name : "Loading..."]",
+ "Game Mode: [GLOB.master_mode]",
+ "Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]",
+ "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]",
+ "Operation Time: [stationTimestamp("hh:mm")]",
+ "Time Dilation: [round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)"
+ )
+
+ src.currentrun = GLOB.clients.Copy()
+ mc_data = null
+
+ var/list/currentrun = src.currentrun
+ while(length(currentrun))
+ var/client/target = currentrun[length(currentrun)]
+ currentrun.len--
+
+ if(!target.stat_panel.is_ready())
+ continue
+
+ if(target.stat_tab == "Status" && num_fires % status_wait == 0)
+ set_status_tab(target)
+
+ if(!target.holder)
+ target.stat_panel.send_message("remove_admin_tabs")
+ else
+ target.stat_panel.send_message("update_split_admin_tabs", target?.prefs.split_admin_tabs)
+
+ if(!("MC" in target.panel_tabs) || !("Tickets" in target.panel_tabs))
+ target.stat_panel.send_message("add_admin_tabs", target.holder.href_token)
+
+ if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || target?.prefs.fast_mc_refresh))
+ set_MC_tab(target)
+
+ if(target.stat_tab == "Tickets" && num_fires % default_wait == 0)
+ set_tickets_tab(target)
+
+ if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
+ target.stat_panel.send_message("remove_sdql2")
+
+ else if(length(GLOB.sdql2_queries) && (target.stat_tab == "SDQL2" || !("SDQL2" in target.panel_tabs)) && num_fires % default_wait == 0)
+ set_SDQL2_tab(target)
+
+ if(target.mob)
+ var/mob/target_mob = target.mob
+
+ // Handle the examined turf of the stat panel, if it's been long enough, or if we've generated new images for it
+ var/turf/listed_turf = target_mob?.listed_turf
+ if(listed_turf && num_fires % default_wait == 0)
+ if(target.stat_tab == listed_turf.name || !(listed_turf.name in target.panel_tabs))
+ set_turf_examine_tab(target, target_mob)
+
+ if(MC_TICK_CHECK)
+ return
+
+/datum/controller/subsystem/statpanels/proc/set_status_tab(client/target)
+ if(!global_data)//statbrowser hasnt fired yet and we were called from immediate_send_stat_data()
+ return
+
+ target.stat_panel.send_message("update_stat", list(
+ "global_data" = global_data,
+ "ping_str" = "Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)",
+ "other_str" = target.mob?.get_status_tab_items(),
+ ))
+
+/datum/controller/subsystem/statpanels/proc/set_MC_tab(client/target)
+ var/turf/eye_turf = get_turf(target.eye)
+ var/coord_entry = COORD(eye_turf)
+ if(!mc_data)
+ generate_mc_data()
+ target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry))
+
+/datum/controller/subsystem/statpanels/proc/set_tickets_tab(client/target)
+ var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry()
+ target.stat_panel.send_message("update_tickets", ahelp_tickets)
+
+/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target)
+ var/list/sdql2A = list()
+ sdql2A[++sdql2A.len] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj))
+ var/list/sdql2B = list()
+ for(var/datum/SDQL2_query/query as anything in GLOB.sdql2_queries)
+ sdql2B = query.generate_stat()
+
+ sdql2A += sdql2B
+ target.stat_panel.send_message("update_sdql2", sdql2A)
+
+/datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob)
+ var/list/overrides = list()
+ for(var/image/target_image as anything in target.images)
+ if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override)
+ continue
+ overrides += target_image.loc
+
+ var/list/atoms_to_display = list(target_mob.listed_turf)
+ for(var/atom/movable/turf_content as anything in target_mob.listed_turf)
+ if(turf_content.mouse_opacity == MOUSE_OPACITY_TRANSPARENT)
+ continue
+ if(turf_content.invisibility > target_mob.see_invisible)
+ continue
+ if(turf_content in overrides)
+ continue
+ if(turf_content.IsObscured())
+ continue
+ atoms_to_display += turf_content
+
+ /// Set the atoms we're meant to display
+ var/datum/object_window_info/obj_window = target.obj_window
+ obj_window.atoms_to_show = atoms_to_display
+ START_PROCESSING(SSobj_tab_items, obj_window)
+ refresh_client_obj_view(target)
+
+/datum/controller/subsystem/statpanels/proc/refresh_client_obj_view(client/refresh)
+ var/list/turf_items = return_object_images(refresh)
+ if(!length(turf_items) || !refresh.mob?.listed_turf)
+ return
+ refresh.stat_panel.send_message("update_listedturf", turf_items)
+
+#define OBJ_IMAGE_LOADING "statpanels obj loading temporary"
+/// Returns all our ready object tab images
+/// Returns a list in the form list(list(object_name, object_ref, loaded_image), ...)
+/datum/controller/subsystem/statpanels/proc/return_object_images(client/load_from)
+ // You might be inclined to think that this is a waste of cpu time, since we
+ // A: Double iterate over atoms in the build case, or
+ // B: Generate these lists over and over in the refresh case
+ // It's really not very hot. The hot portion of this code is genuinely mostly in the image generation
+ // So it's ok to pay a performance cost for cleanliness here
+
+ // No turf? go away
+ if(!load_from.mob?.listed_turf)
+ return list()
+ var/datum/object_window_info/obj_window = load_from.obj_window
+ var/list/already_seen = obj_window.atoms_to_images
+ var/list/to_make = obj_window.atoms_to_imagify
+ var/list/turf_items = list()
+ for(var/atom/turf_item as anything in obj_window.atoms_to_show)
+ // First, we fill up the list of refs to display
+ // If we already have one, just use that
+ var/existing_image = already_seen[turf_item]
+ if(existing_image == OBJ_IMAGE_LOADING)
+ continue
+ // We already have it. Success!
+ if(existing_image)
+ turf_items[++turf_items.len] = list("[turf_item.name]", REF(turf_item), existing_image)
+ continue
+ // Now, we're gonna queue image generation out of those refs
+ to_make += turf_item
+ already_seen[turf_item] = OBJ_IMAGE_LOADING
+ obj_window.RegisterSignal(turf_item, COMSIG_QDELETING, TYPE_PROC_REF(/datum/object_window_info,viewing_atom_deleted)) // we reset cache if anything in it gets deleted
+ return turf_items
+
+#undef OBJ_IMAGE_LOADING
+
+/datum/controller/subsystem/statpanels/proc/generate_mc_data()
+ mc_data = list(
+ list("CPU:", world.cpu),
+ list("Instances:", "[num2text(world.contents.len, 10)]"),
+ list("World Time:", "[world.time]"),
+ list("Globals:", GLOB.stat_entry(), ref(GLOB)),
+ list("[config]:", config.stat_entry(), ref(config)),
+ list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"),
+ list("Master Controller:", Master.stat_entry(), ref(Master)),
+ list("Failsafe Controller:", Failsafe.stat_entry(), ref(Failsafe)),
+ list("","")
+ )
+ for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems)
+ mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), ref(sub_system))
+ mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", ref(GLOB.cameranet))
+
+///immediately update the active statpanel tab of the target client
+/datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target)
+ if(!target.stat_panel.is_ready())
+ return FALSE
+
+ if(target.stat_tab == "Status")
+ set_status_tab(target)
+ return TRUE
+
+ var/mob/target_mob = target.mob
+
+ // Handle turfs
+
+ if(target_mob?.listed_turf)
+ if(!target_mob.TurfAdjacent(target_mob.listed_turf))
+ target_mob.set_listed_turf(null)
+
+ else if(target.stat_tab == target_mob?.listed_turf.name || !(target_mob?.listed_turf.name in target.panel_tabs))
+ set_turf_examine_tab(target, target_mob)
+ return TRUE
+
+ if(!target.holder)
+ return FALSE
+
+ if(target.stat_tab == "MC")
+ set_MC_tab(target)
+ return TRUE
+
+ if(target.stat_tab == "Tickets")
+ set_tickets_tab(target)
+ return TRUE
+
+ if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
+ target.stat_panel.send_message("remove_sdql2")
+
+ else if(length(GLOB.sdql2_queries) && target.stat_tab == "SDQL2")
+ set_SDQL2_tab(target)
+
+/// Stat panel window declaration
+/client/var/datum/tgui_window/stat_panel
+
+/// Datum that holds and tracks info about a client's object window
+/// Really only exists because I want to be able to do logic with signals
+/// And need a safe place to do the registration
+/datum/object_window_info
+ /// list of atoms to show to our client via the object tab, at least currently
+ var/list/atoms_to_show = list()
+ /// list of atom -> image string for objects we have had in the right click tab
+ /// this is our caching
+ var/list/atoms_to_images = list()
+ /// list of atoms to turn into images for the object tab
+ var/list/atoms_to_imagify = list()
+ /// Our owner client
+ var/client/parent
+ /// Are we currently tracking a turf?
+ var/actively_tracking = FALSE
+
+/datum/object_window_info/New(client/parent)
+ . = ..()
+ src.parent = parent
+
+/datum/object_window_info/Destroy(force, ...)
+ atoms_to_show = null
+ atoms_to_images = null
+ atoms_to_imagify = null
+ parent.obj_window = null
+ parent = null
+ STOP_PROCESSING(SSobj_tab_items, src)
+ return ..()
+
+/// Takes a client, attempts to generate object images for it
+/// We will update the client with any improvements we make when we're done
+/datum/object_window_info/process(seconds_per_tick)
+ // Cache the datum access for sonic speed
+ var/list/to_make = atoms_to_imagify
+ var/list/newly_seen = atoms_to_images
+ var/index = 0
+ for(index in 1 to length(to_make))
+ var/atom/thing = to_make[index]
+
+ var/generated_string
+ if(ismob(thing) || length(thing.overlays) > 2)
+ generated_string = costly_icon2html(thing, parent, sourceonly=TRUE)
+ else
+ generated_string = icon2html(thing, parent, sourceonly=TRUE)
+
+ newly_seen[thing] = generated_string
+ if(TICK_CHECK)
+ to_make.Cut(1, index + 1)
+ index = 0
+ break
+ // If we've not cut yet, do it now
+ if(index)
+ to_make.Cut(1, index + 1)
+ SSstatpanels.refresh_client_obj_view(parent)
+ if(!length(to_make))
+ return PROCESS_KILL
+
+/datum/object_window_info/proc/start_turf_tracking()
+ if(actively_tracking)
+ stop_turf_tracking()
+ var/static/list/connections = list(
+ COMSIG_MOVABLE_MOVED = PROC_REF(on_mob_move),
+ COMSIG_MOB_LOGOUT = PROC_REF(on_mob_logout),
+ )
+ AddComponent(/datum/component/connect_mob_behalf, parent, connections)
+ actively_tracking = TRUE
+
+/datum/object_window_info/proc/stop_turf_tracking()
+ qdel(GetComponent(/datum/component/connect_mob_behalf))
+ actively_tracking = FALSE
+
+/datum/object_window_info/proc/on_mob_move(mob/source)
+ SIGNAL_HANDLER
+ var/turf/listed = source.listed_turf
+ if(!listed || !source.TurfAdjacent(listed))
+ source.set_listed_turf(null)
+
+/datum/object_window_info/proc/on_mob_logout(mob/source)
+ SIGNAL_HANDLER
+ on_mob_move(parent.mob)
+
+/// Clears any cached object window stuff
+/// We use hard refs cause we'd need a signal for this anyway. Cleaner this way
+/datum/object_window_info/proc/viewing_atom_deleted(atom/deleted)
+ SIGNAL_HANDLER
+ atoms_to_show -= deleted
+ atoms_to_imagify -= deleted
+ atoms_to_images -= deleted
+
+/mob/proc/set_listed_turf(turf/new_turf)
+ listed_turf = new_turf
+ if(!client)
+ return
+ if(!client.obj_window)
+ client.obj_window = new(client)
+ if(listed_turf)
+ client.stat_panel.send_message("create_listedturf", listed_turf.name)
+ client.obj_window.start_turf_tracking()
+ else
+ client.stat_panel.send_message("remove_listedturf")
+ client.obj_window.stop_turf_tracking()
diff --git a/code/datums/components/connect_mob_behalf.dm b/code/datums/components/connect_mob_behalf.dm
new file mode 100644
index 0000000000000..f7989a83982fc
--- /dev/null
+++ b/code/datums/components/connect_mob_behalf.dm
@@ -0,0 +1,63 @@
+/// This component behaves similar to connect_loc_behalf, but working off clients and mobs instead of loc
+/// To be clear, we hook into a signal on a tracked client's mob
+/// We retain the ability to react to that signal on a seperate listener, which makes this quite powerful
+/datum/component/connect_mob_behalf
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+
+ /// An assoc list of signal -> procpath to register to the mob our client "owns"
+ var/list/connections
+ /// The master client we're working with
+ var/client/tracked
+ /// The mob we're currently tracking
+ var/mob/tracked_mob
+
+/datum/component/connect_mob_behalf/Initialize(client/tracked, list/connections)
+ . = ..()
+ if (!istype(tracked))
+ return COMPONENT_INCOMPATIBLE
+ src.connections = connections
+ src.tracked = tracked
+
+/datum/component/connect_mob_behalf/RegisterWithParent()
+ RegisterSignal(tracked, COMSIG_QDELETING, PROC_REF(handle_tracked_qdel))
+ update_signals()
+
+/datum/component/connect_mob_behalf/UnregisterFromParent()
+ unregister_signals()
+ UnregisterSignal(tracked, COMSIG_QDELETING)
+
+ tracked = null
+ tracked_mob = null
+
+/// Delete ourselves when our tracked client is deleted
+/datum/component/connect_mob_behalf/proc/handle_tracked_qdel()
+ SIGNAL_HANDLER
+ qdel(src)
+
+/// Re-register signals on tracked mob
+/datum/component/connect_mob_behalf/proc/update_signals()
+ unregister_signals()
+ // Yes this is a runtime silencer
+ // We could be in a position where logout is sent to two things, one thing intercepts it, then deletes the client's new mob
+ // It's rare, and the same check in connect_loc_behalf is more fruitful, but it's still worth doing
+ if(QDELETED(tracked?.mob))
+ return
+ tracked_mob = tracked.mob
+ RegisterSignal(tracked_mob, COMSIG_MOB_LOGOUT, PROC_REF(on_logout))
+ for (var/signal in connections)
+ parent.RegisterSignal(tracked_mob, signal, connections[signal])
+
+/// Unregister signals on tracked mob
+/datum/component/connect_mob_behalf/proc/unregister_signals()
+ if(isnull(tracked_mob))
+ return
+
+ parent.UnregisterSignal(tracked_mob, connections)
+ UnregisterSignal(tracked_mob, COMSIG_MOB_LOGOUT)
+
+ tracked_mob = null
+
+/// update_signals on tracked mob logout
+/datum/component/connect_mob_behalf/proc/on_logout(mob/source)
+ SIGNAL_HANDLER
+ update_signals()
diff --git a/code/datums/gamemodes/_game_mode.dm b/code/datums/gamemodes/_game_mode.dm
index 05bb5bdf3a54f..9cab1a0017d3b 100644
--- a/code/datums/gamemodes/_game_mode.dm
+++ b/code/datums/gamemodes/_game_mode.dm
@@ -164,6 +164,7 @@ GLOBAL_VAR(common_report) //Contains common part of roundend report
continue
qdel(player)
+ living.client.init_verbs()
living.notransform = TRUE
livings += living
@@ -269,7 +270,7 @@ GLOBAL_LIST_INIT(bioscan_locations, list(
/datum/game_mode/proc/grant_eord_respawn(datum/dcs, mob/source)
SIGNAL_HANDLER
- source.verbs |= /mob/proc/eord_respawn
+ add_verb(source, /mob/proc/eord_respawn)
/datum/game_mode/proc/end_of_round_deathmatch()
RegisterSignal(SSdcs, COMSIG_GLOB_MOB_LOGIN, PROC_REF(grant_eord_respawn)) // New mobs can now respawn into EORD
@@ -283,7 +284,7 @@ GLOBAL_LIST_INIT(bioscan_locations, list(
for(var/i in GLOB.player_list)
var/mob/M = i
- M.verbs |= /mob/proc/eord_respawn
+ add_verb(M, /mob/proc/eord_respawn)
if(isnewplayer(M))
continue
if(!(M.client?.prefs?.be_special & BE_DEATHMATCH))
@@ -574,6 +575,7 @@ GLOBAL_LIST_INIT(bioscan_locations, list(
player.mind.transfer_to(player.new_character)
var/datum/job/job = player.assigned_role
job.on_late_spawn(player.new_character)
+ player.new_character.client?.init_verbs()
var/area/A = get_area(player.new_character)
deadchat_broadcast(span_game(" has woken at [span_name("[A?.name]")]."), span_game("[span_name("[player.new_character.real_name]")] ([job.title])"), follow_target = player.new_character, message_type = DEADCHAT_ARRIVALRATTLE)
qdel(player)
diff --git a/code/datums/mind.dm b/code/datums/mind.dm
index 1f73d2c342a68..21c2d5f9a3811 100644
--- a/code/datums/mind.dm
+++ b/code/datums/mind.dm
@@ -74,6 +74,7 @@
if(active || force_key_move)
new_character.key = key //now transfer the key to link the client to our new body
+ new_character.client.init_verbs()
/datum/mind/proc/set_death_time()
last_death = world.time
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index dcac16c585c0a..9f5c0c4083dc0 100755
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -925,6 +925,32 @@ Proc for attack log creation, because really why not
. = ..()
if(.)
return
+ if(!usr?.client)
+ return
+ var/client/usr_client = usr.client
+ var/list/paramslist = list()
+
+ if(href_list["statpanel_item_click"])
+ switch(href_list["statpanel_item_click"])
+ if("left")
+ paramslist[LEFT_CLICK] = "1"
+ if("right")
+ paramslist[RIGHT_CLICK] = "1"
+ if("middle")
+ paramslist[MIDDLE_CLICK] = "1"
+ else
+ return
+
+ if(href_list["statpanel_item_shiftclick"])
+ paramslist[SHIFT_CLICK] = "1"
+ if(href_list["statpanel_item_ctrlclick"])
+ paramslist[CTRL_CLICK] = "1"
+ if(href_list["statpanel_item_altclick"])
+ paramslist[ALT_CLICK] = "1"
+
+ var/mouseparams = list2params(paramslist)
+ usr_client.Click(src, loc, null, mouseparams)
+ . = TRUE
add_fingerprint(usr, "topic")
diff --git a/code/game/objects/items/embedding.dm b/code/game/objects/items/embedding.dm
index 8c46fe9411f80..1b4199f2d64a5 100644
--- a/code/game/objects/items/embedding.dm
+++ b/code/game/objects/items/embedding.dm
@@ -37,7 +37,7 @@
yankable_embedded = TRUE
break
if(!yankable_embedded)
- verbs -= /mob/living/proc/yank_out_object
+ remove_verb(src, /mob/living/proc/yank_out_object)
/mob/living/carbon/human/unembed_item(obj/item/embedding)
@@ -52,7 +52,7 @@
yankable_embedded = TRUE
break
if(!yankable_embedded)
- verbs -= /mob/living/proc/yank_out_object
+ remove_verb(src, /mob/living/proc/yank_out_object)
/datum/limb/proc/unembed(obj/item/embedding)
@@ -88,7 +88,7 @@
owner.visible_message(span_danger("\The [embedding] sticks in the wound!"))
implants += embedding
if(embedding.embedding.embedded_flags & EMBEDDED_CAN_BE_YANKED_OUT)
- owner.verbs += /mob/living/proc/yank_out_object
+ add_verb(owner, /mob/living/proc/yank_out_object)
embedding.add_mob_blood(owner)
embedding.forceMove(owner)
embedding.RegisterSignal(src, COMSIG_LIMB_DESTROYED, TYPE_PROC_REF(/obj/item, embedded_on_limb_destruction))
diff --git a/code/game/verbs/ooc.dm b/code/game/verbs/ooc.dm
index c4e8dd9a348de..7e7ec84f1f3e0 100644
--- a/code/game/verbs/ooc.dm
+++ b/code/game/verbs/ooc.dm
@@ -583,3 +583,9 @@
set name = "Ping"
set category = "OOC"
winset(src, null, "command=.display_ping+[world.time + world.tick_lag * TICK_USAGE_REAL / 100]")
+
+/client/verb/fix_stat_panel()
+ set name = "Fix Stat Panel"
+ set hidden = TRUE
+
+ init_verbs()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 03bf2d4606a3d..c162c0020e8a5 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -17,6 +17,7 @@
return
var/mob/dead/observer/ghost = M.ghostize(TRUE, TRUE)
+ owner.init_verbs()
log_admin("[key_name(ghost)] admin ghosted at [AREACOORD(ghost)].")
if(M.stat != DEAD)
@@ -1299,7 +1300,7 @@
/client/proc/get_togglebuildmode()
set name = "Toggle Build Mode"
- set category = "Fun"
+ set category = "Admin.Fun"
if(!check_rights(R_SPAWN))
return
togglebuildmode(mob)
@@ -1340,13 +1341,14 @@
SSblackbox.record_feedback("tally", "admin_verb", 1, "Ghost Drag Control")
tomob.ckey = frommob.ckey
+ tomob.client?.init_verbs()
qdel(frommob)
return TRUE
/client/proc/mass_replace()
set name = "Mass replace atom"
- set category = "Fun"
+ set category = "Admin.Fun"
if(!check_rights(R_SPAWN))
return
var/to_replace = pick_closest_path(input("Pick a movable atom path to be replaced", "Enter path as text") as text)
diff --git a/code/modules/admin/debug_verbs.dm b/code/modules/admin/debug_verbs.dm
index 828cc4c8c49ed..15bae5e6768e8 100644
--- a/code/modules/admin/debug_verbs.dm
+++ b/code/modules/admin/debug_verbs.dm
@@ -696,3 +696,9 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
dellog += ""
usr << browse(dellog.Join(), "window=dellog")
+
+/client/proc/debugstatpanel()
+ set name = "Debug Stat Panel"
+ set category = "Debug"
+
+ src.stat_panel.send_message("create_debug")
diff --git a/code/modules/admin/fun_verbs.dm b/code/modules/admin/fun_verbs.dm
index 34c9194a283fd..47af429d18734 100644
--- a/code/modules/admin/fun_verbs.dm
+++ b/code/modules/admin/fun_verbs.dm
@@ -1,5 +1,5 @@
/datum/admins/proc/set_view_range()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Set View Range"
if(!check_rights(R_FUN))
@@ -24,7 +24,7 @@
/datum/admins/proc/emp()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "EM Pulse"
if(!check_rights(R_FUN))
@@ -48,7 +48,7 @@
/datum/admins/proc/queen_report()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Queen Mother Report"
if(!check_rights(R_FUN))
@@ -70,7 +70,7 @@
/datum/admins/proc/rouny_all()
set name = "Toggle Glob Xeno Rouny"
- set category = "Fun"
+ set category = "Admin.Fun"
set desc = "Toggle all living xenos into rouny versions of themselves"
if(!check_rights(R_FUN))
@@ -83,7 +83,7 @@
/datum/admins/proc/hive_status()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Hive Status"
set desc = "Check the status of the hive."
@@ -99,7 +99,7 @@
/datum/admins/proc/ai_report()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "AI Report"
if(!check_rights(R_FUN))
@@ -129,7 +129,7 @@
/datum/admins/proc/command_report()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Command Report"
if(!check_rights(R_FUN))
@@ -158,7 +158,7 @@
/datum/admins/proc/narrate_global()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Global Narrate"
if(!check_rights(R_FUN))
@@ -217,7 +217,7 @@
/datum/admins/proc/subtle_message_panel()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Subtle Message Mob"
if(!check_rights(R_FUN|R_MENTOR))
@@ -256,7 +256,7 @@
/datum/admins/proc/award_medal()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Award a Medal"
if(!check_rights(R_FUN))
@@ -266,7 +266,7 @@
/datum/admins/proc/custom_info()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Change Custom Info"
if(!check_rights(R_FUN))
@@ -304,7 +304,7 @@
/datum/admins/proc/sound_file(S as sound)
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Play Imported Sound"
set desc = "Play a sound imported from anywhere on your computer."
@@ -336,7 +336,7 @@
/datum/admins/proc/sound_web()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Play Internet Sound"
if(!check_rights(R_SOUND))
@@ -428,7 +428,7 @@
/datum/admins/proc/sound_stop()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Stop Regular Sounds"
if(!check_rights(R_SOUND))
@@ -443,7 +443,7 @@
/datum/admins/proc/music_stop()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Stop Playing Music"
if(!check_rights(R_SOUND))
@@ -459,7 +459,7 @@
/datum/admins/proc/announce()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Admin Announce"
if(!check_rights(R_FUN))
@@ -478,7 +478,7 @@
/datum/admins/proc/force_distress()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Distress Beacon"
set desc = "Call a distress beacon manually."
@@ -567,7 +567,7 @@
/datum/admins/proc/drop_bomb()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Drop Bomb"
set desc = "Cause an explosion of varying strength at your location."
@@ -656,7 +656,7 @@
/datum/admins/proc/drop_dynex_bomb()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Drop DynEx Bomb"
set desc = "Cause an explosion of varying strength at your location."
@@ -669,7 +669,7 @@
/datum/admins/proc/change_security_level()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Set Security Level"
if(!check_rights(R_FUN))
@@ -689,7 +689,7 @@
/datum/admins/proc/rank_and_equipment(mob/living/carbon/human/H in GLOB.human_mob_list)
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Rank and Equipment"
if(!check_rights(R_FUN))
@@ -731,7 +731,7 @@
/datum/admins/proc/edit_appearance(mob/living/carbon/human/H in GLOB.human_mob_list)
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Edit Appearance"
if(!check_rights(R_FUN))
@@ -766,7 +766,7 @@
/datum/admins/proc/offer(mob/living/L in GLOB.mob_living_list)
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Offer Mob"
if(!check_rights(R_FUN))
@@ -801,7 +801,7 @@
/datum/admins/proc/xeno_panel(mob/living/carbon/xenomorph/X in GLOB.xeno_mob_list)
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Xeno Panel"
if(!check_rights(R_FUN))
@@ -873,7 +873,7 @@
/client/proc/toggle_buildmode()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Toggle Build Mode"
if(!check_rights(R_FUN))
@@ -883,7 +883,7 @@
/datum/admins/proc/imaginary_friend()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Imaginary Friend"
if(!check_rights(R_FUN|R_MENTOR))
@@ -925,7 +925,7 @@
message_admins("[ADMIN_TPMONTY(IF)] started being imaginary friend of [ADMIN_TPMONTY(friend_owner)].")
/datum/admins/proc/force_dropship()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Force Dropship"
if(!check_rights(R_FUN))
@@ -999,7 +999,7 @@
/datum/admins/proc/play_cinematic()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Play Cinematic"
if(!check_rights(R_FUN))
@@ -1016,7 +1016,7 @@
/datum/admins/proc/set_tip()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Set Tip"
if(!check_rights(R_FUN))
@@ -1037,7 +1037,7 @@
/datum/admins/proc/ghost_interact()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Ghost Interact"
if(!check_rights(R_FUN))
@@ -1049,7 +1049,7 @@
message_admins("[ADMIN_TPMONTY(usr)] has [usr.client.holder.ghost_interact ? "enabled" : "disabled"] ghost interact.")
/client/proc/run_weather()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Run Weather"
set desc = "Triggers a weather on the z-level you choose."
@@ -1073,7 +1073,7 @@
///client verb to set round end sound
/client/proc/set_round_end_sound(S as sound)
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Set Round End Sound"
if(!check_rights(R_SOUND))
return
@@ -1086,7 +1086,7 @@
///Adjusts gravity, modifying the jump component for all mobs
/datum/admins/proc/adjust_gravity()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Adjust Gravity"
if(!check_rights(R_FUN))
diff --git a/code/modules/admin/holder.dm b/code/modules/admin/holder.dm
index c61557b2c8594..f34ac59188518 100644
--- a/code/modules/admin/holder.dm
+++ b/code/modules/admin/holder.dm
@@ -84,7 +84,7 @@
var/client/C
if((C = owner) || (C = GLOB.directory[target]))
disassociate()
- C.verbs += /client/proc/readmin
+ add_verb(C, /client/proc/readmin)
/datum/admins/proc/associate(client/C)
@@ -105,7 +105,8 @@
owner = C
owner.holder = src
owner.add_admin_verbs()
- owner.verbs -= /client/proc/readmin
+ remove_verb(owner, /client/proc/readmin)
+ owner.init_verbs()
GLOB.admins |= C
@@ -349,6 +350,7 @@ GLOBAL_PROTECT(admin_verbs_asay)
/datum/admins/proc/delete_all,
/datum/admins/proc/generate_powernets,
/datum/admins/proc/debug_mob_lists,
+ /client/proc/debugstatpanel,
/datum/admins/proc/delete_atom,
/datum/admins/proc/restart_controller,
/datum/admins/proc/check_contents,
@@ -499,41 +501,41 @@ GLOBAL_PROTECT(admin_verbs_log)
/client/proc/add_admin_verbs()
if(holder)
var/rights = holder.rank.rights
- verbs += GLOB.admin_verbs_default
+ add_verb(src, GLOB.admin_verbs_default)
if(rights & R_ADMIN)
- verbs += GLOB.admin_verbs_admin
+ add_verb(src, GLOB.admin_verbs_admin)
if(rights & R_MENTOR)
- verbs += GLOB.admin_verbs_mentor
+ add_verb(src, GLOB.admin_verbs_mentor)
if(rights & R_BAN)
- verbs += GLOB.admin_verbs_ban
+ add_verb(src, GLOB.admin_verbs_ban)
if(rights & R_ASAY)
- verbs += GLOB.admin_verbs_asay
+ add_verb(src, GLOB.admin_verbs_asay)
if(rights & R_FUN)
- verbs += GLOB.admin_verbs_fun
+ add_verb(src, GLOB.admin_verbs_fun)
if(rights & R_SERVER)
- verbs += GLOB.admin_verbs_server
+ add_verb(src, GLOB.admin_verbs_server)
if(rights & R_DEBUG)
- verbs += GLOB.admin_verbs_debug
+ add_verb(src, GLOB.admin_verbs_debug)
if(rights & R_RUNTIME)
- verbs += GLOB.admin_verbs_runtimes
+ add_verb(src, GLOB.admin_verbs_runtimes)
if(rights & R_PERMISSIONS)
- verbs += GLOB.admin_verbs_permissions
+ add_verb(src, GLOB.admin_verbs_permissions)
if(rights & R_DBRANKS)
- verbs += GLOB.admin_verbs_permissions
+ add_verb(src, GLOB.admin_verbs_permissions)
if(rights & R_SOUND)
- verbs += GLOB.admin_verbs_sound
+ add_verb(src, GLOB.admin_verbs_sound)
if(rights & R_COLOR)
- verbs += GLOB.admin_verbs_color
+ add_verb(src, GLOB.admin_verbs_color)
if(rights & R_VAREDIT)
- verbs += GLOB.admin_verbs_varedit
+ add_verb(src, GLOB.admin_verbs_varedit)
if(rights & R_SPAWN)
- verbs += GLOB.admin_verbs_spawn
+ add_verb(src, GLOB.admin_verbs_spawn)
if(rights & R_LOG)
- verbs += GLOB.admin_verbs_log
+ add_verb(src, GLOB.admin_verbs_log)
/client/proc/remove_admin_verbs()
- verbs.Remove(
+ remove_verb(src, list(
GLOB.admin_verbs_default,
GLOB.admin_verbs_admin,
GLOB.admin_verbs_mentor,
@@ -548,7 +550,7 @@ GLOBAL_PROTECT(admin_verbs_log)
GLOB.admin_verbs_varedit,
GLOB.admin_verbs_spawn,
GLOB.admin_verbs_log,
- )
+ ))
/proc/is_mentor(client/C)
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
index 5f7122d6e3b3b..8b6f97a2eb783 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
@@ -231,7 +231,7 @@
return
var/list/datum/SDQL2_query/running = list()
var/list/datum/SDQL2_query/waiting_queue = list() //Sequential queries queue.
-
+
for(var/list/query_tree in querys)
var/datum/SDQL2_query/query = new /datum/SDQL2_query(query_tree)
if(QDELETED(query))
@@ -258,7 +258,7 @@
to_chat(usr, span_admin("[msg]"))
log_admin(msg)
query.ARun()
-
+
var/finished = FALSE
var/objs_all = 0
var/objs_eligible = 0
@@ -429,11 +429,13 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
delete_click = new(null, "INITIALIZING", src)
if(!action_click)
action_click = new(null, "INITIALIZNG", src)
- stat("[id] ", delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \
+ var/list/L = list()
+ L[++L.len] = list("[id] ", "[delete_click.update("DELETE QUERY | STATE : [text_state()] | ALL/ELIG/FIN \
[islist(obj_count_all)? length(obj_count_all) : (isnull(obj_count_all)? "0" : obj_count_all)]/\
[islist(obj_count_eligible)? length(obj_count_eligible) : (isnull(obj_count_eligible)? "0" : obj_count_eligible)]/\
- [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]"))
- stat(" ", action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]"))
+ [islist(obj_count_finished)? length(obj_count_finished) : (isnull(obj_count_finished)? "0" : obj_count_finished)] - [get_query_text()]")]", REF(delete_click))
+ L[++L.len] = list(" ", "[action_click.update("[SDQL2_IS_RUNNING? "HALT" : "RUN"]")]", REF(action_click))
+ return L
/datum/SDQL2_query/proc/delete_click()
admin_del(usr)
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 532dee00fe20e..f20544487ff84 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -109,6 +109,9 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
//Tickets statpanel
/datum/admin_help_tickets/proc/stat_entry()
+ SHOULD_CALL_PARENT(TRUE)
+ SHOULD_NOT_SLEEP(TRUE)
+ var/list/L = list()
var/num_mentors_active = 0
var/num_admins_active = 0
var/num_mentors_closed = 0
@@ -138,32 +141,35 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
num_admins_resolved++
if(check_rights(R_ADMINTICKET, FALSE))
- stat("Active Tickets:", astatclick.update("[num_mentors_active + num_admins_active]"))
+ L[++L.len] = list("Active Tickets:", "[astatclick.update("[num_mentors_active + num_admins_active]")]", null, REF(astatclick))
else if(check_rights(R_MENTOR, FALSE))
- stat("Active Tickets:", astatclick.update("[num_mentors_active]"))
+ L[++L.len] = list("Active Tickets:", "[astatclick.update("[num_mentors_active]")]", null, REF(astatclick))
for(var/I in active_tickets)
var/datum/admin_help/AH = I
+ var/obj/effect/statclick/updated = AH.statclick.update()
if(AH.tier == TICKET_MENTOR && check_rights(R_ADMINTICKET|R_MENTOR, FALSE))
if(AH.initiator)
- stat("\[[AH.marked ? "X" : " "]\] #[AH.id]. Mentor. [AH.initiator_key_name]:", AH.statclick.update())
+ L[++L.len] = list("\[[AH.marked ? "X" : " "]\] #[AH.id]. Mentor. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
else
- stat("\[D\] #[AH.id]. Mentor. [AH.initiator_key_name]:", AH.statclick.update())
+ L[++L.len] = list("\[D\] #[AH.id]. Mentor. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
else if(AH.tier == TICKET_ADMIN && check_rights(R_ADMINTICKET, FALSE))
if(AH.initiator)
- stat("\[[AH.marked ? "X" : " "]\] #[AH.id]. Admin. [AH.initiator_key_name]:", AH.statclick.update())
+ L[++L.len] = list("\[[AH.marked ? "X" : " "]\] #[AH.id]. Admin. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
else
- stat("\[D\] #[AH.id]. Admin. [AH.initiator_key_name]:", AH.statclick.update())
+ L[++L.len] = list("\[D\] #[AH.id]. Admin. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
if(check_rights(R_ADMINTICKET, FALSE))
- stat("Closed Tickets:", cstatclick.update("[num_mentors_closed + num_admins_closed]"))
+ L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[num_mentors_closed + num_admins_closed]")]", null, REF(cstatclick))
else if(check_rights(R_MENTOR, FALSE))
- stat("Closed Tickets:", cstatclick.update("[num_mentors_closed]"))
+ L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[num_mentors_closed]")]", null, REF(cstatclick))
if(check_rights(R_ADMINTICKET, FALSE))
- stat("Resolved Tickets:", rstatclick.update("[num_mentors_resolved + num_admins_resolved]"))
+ L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[num_mentors_resolved + num_admins_resolved]")]", null, REF(rstatclick))
else if(check_rights(R_MENTOR, FALSE))
- stat("Resolved Tickets:", rstatclick.update("[num_mentors_resolved]"))
+ L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[num_mentors_resolved]")]", null, REF(rstatclick))
+
+ return L
//Reassociate still open ticket if one exists
@@ -761,7 +767,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
//
/client/proc/giveadminhelpverb()
- verbs |= /client/verb/adminhelp
+ add_verb(src, /client/verb/adminhelp)
deltimer(adminhelptimerid)
adminhelptimerid = 0
diff --git a/code/modules/admin/verbs/datumvars.dm b/code/modules/admin/verbs/datumvars.dm
index d43b5ce8e73fe..d0ffd44fd005b 100644
--- a/code/modules/admin/verbs/datumvars.dm
+++ b/code/modules/admin/verbs/datumvars.dm
@@ -563,9 +563,9 @@
else if(istype(value, /datum))
var/datum/D = value
if("[D]" != "[D.type]") //if the thing as a name var, lets use it.
- item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D] [D.type]"
+ item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D] [D.type]"
else
- item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D.type]"
+ item = "[VV_HTML_ENCODE(name)] [REF(value)] = [D.type]"
else if(islist(value))
var/list/L = value
@@ -585,9 +585,9 @@
items += debug_variable(key, val, level + 1, sanitize = sanitize)
- item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])
"
+ item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])"
else
- item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])"
+ item = "[VV_HTML_ENCODE(name)] = /list ([length(L)])"
else if(name in GLOB.bitfields)
var/list/flags = list()
@@ -608,8 +608,8 @@
return
- if(href_list["vars"])
- debug_variables(locate(href_list["vars"]))
+ if(href_list["Vars"])
+ debug_variables(locate(href_list["Vars"]))
else if(href_list["datumrefresh"])
diff --git a/code/modules/admin/verbs/faxes.dm b/code/modules/admin/verbs/faxes.dm
index 95324baa6bb1a..34c3539b61e26 100644
--- a/code/modules/admin/verbs/faxes.dm
+++ b/code/modules/admin/verbs/faxes.dm
@@ -74,7 +74,7 @@ GLOBAL_LIST_EMPTY(faxes)
/datum/admins/proc/view_faxes()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "View Faxes"
if(!check_rights(R_ADMIN, FALSE) && !is_mentor(usr.client))
diff --git a/code/modules/admin/verbs/podlauncher.dm b/code/modules/admin/verbs/podlauncher.dm
index 396cc39e0eb5a..42e1495fb52bd 100644
--- a/code/modules/admin/verbs/podlauncher.dm
+++ b/code/modules/admin/verbs/podlauncher.dm
@@ -1,5 +1,5 @@
/datum/admins/proc/launch_pod()
- set category = "Fun"
+ set category = "Admin.Fun"
set name = "Launch Supply Pod"
if(!check_rights(R_FUN))
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index ba366bf186e45..8c34fe22ec510 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -51,6 +51,18 @@
/// datum wrapper for client view
var/datum/view_data/view_size
+ /// our current tab
+ var/stat_tab
+
+ /// list of all tabs
+ var/list/panel_tabs = list()
+ /// list of tabs containing spells and abilities
+ var/list/spell_tabs = list()
+ ///A lazy list of atoms we've examined in the last RECENT_EXAMINE_MAX_WINDOW (default 2) seconds, so that we will call [/atom/proc/examine_more] instead of [/atom/proc/examine] on them when examining
+ var/list/recent_examines
+ ///Our object window datum. It stores info about and handles behavior for the object tab
+ var/datum/object_window_info/obj_window
+
//Database related
var/player_age = -1 //Used to determine how old the account is - in days.
var/player_join_date = null //Date that this account was first seen in the server
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 2f06fb73d26ef..a43d4402c14f5 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -64,7 +64,7 @@
to_chat(src, span_danger("[msg]"))
return
var/stl = CONFIG_GET(number/second_topic_limit)
- if (!holder && stl)
+ if (!holder && stl && href_list["window_id"] != "statbrowser")
var/second = round(world.time, 10)
if (!topiclimiter)
topiclimiter = new(LIMITER_SIZE)
@@ -80,6 +80,8 @@
return
if(href_list["reload_tguipanel"])
nuke_chat()
+ if(href_list["reload_statbrowser"])
+ stat_panel.reinitialize()
//Logs all hrefs.
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
@@ -141,6 +143,10 @@
GLOB.clients += src
GLOB.directory[ckey] = src
+ // Instantiate stat panel
+ stat_panel = new(src, "statbrowser")
+ stat_panel.subscribe(src, PROC_REF(on_stat_panel_message))
+
// Instantiate tgui panel
tgui_panel = new(src, "browseroutput")
@@ -161,7 +167,7 @@
holder.owner = src
holder.activate()
else if(GLOB.deadmins[ckey])
- verbs += /client/proc/readmin
+ add_verb(src, /client/proc/readmin)
//preferences datum - also holds some persistant data for the client (because we may as well keep these datums to a minimum)
prefs = GLOB.preferences_datums[ckey]
@@ -222,7 +228,13 @@
tgui_say.initialize()
-
+ // Initialize stat panel
+ stat_panel.initialize(
+ inline_html = file("html/statbrowser.html"),
+ inline_js = file("html/statbrowser.js"),
+ inline_css = file("html/statbrowser.css"),
+ )
+ addtimer(CALLBACK(src, PROC_REF(check_panel_loaded)), 30 SECONDS)
if(byond_version < REQUIRED_CLIENT_MAJOR || (byond_build && byond_build < REQUIRED_CLIENT_MINOR))
//to_chat(src, span_userdanger("Your version of byond is severely out of date."))
@@ -368,6 +380,8 @@
/client/Destroy()
SEND_SIGNAL(src, COMSIG_CLIENT_DISCONNECTED)
log_access("Logout: [key_name(src)]")
+ if(obj_window)
+ QDEL_NULL(obj_window)
if(holder)
if(check_rights(R_ADMIN, FALSE))
message_admins("Admin logout: [key_name(src)].")
@@ -407,8 +421,6 @@
return QDEL_HINT_HARDDEL_NOW
/client/Click(atom/object, atom/location, control, params)
- if(!control)
- return
if(click_intercepted)
if(click_intercepted >= world.time)
click_intercepted = 0 //Reset and return. Next click should work, but not this one.
@@ -977,3 +989,47 @@ GLOBAL_VAR_INIT(automute_on, null)
SSambience.ambience_listening_clients[src] = world.time + 10 SECONDS //Just wait 10 seconds before the next one aight mate? cheers.
else
SSambience.ambience_listening_clients -= src
+
+/// compiles a full list of verbs and sends it to the browser
+/client/proc/init_verbs()
+ if(IsAdminAdvancedProcCall())
+ return
+ var/list/verblist = list()
+ var/list/verbstoprocess = verbs.Copy()
+ if(mob)
+ verbstoprocess += mob.verbs
+ for(var/atom/movable/thing as anything in mob.contents)
+ verbstoprocess += thing.verbs
+ panel_tabs.Cut() // panel_tabs get reset in init_verbs on JS side anyway
+ for(var/procpath/verb_to_init as anything in verbstoprocess)
+ if(!verb_to_init)
+ continue
+ if(verb_to_init.hidden)
+ continue
+ if(!istext(verb_to_init.category))
+ continue
+ panel_tabs |= verb_to_init.category
+ verblist[++verblist.len] = list(verb_to_init.category, verb_to_init.name)
+ src.stat_panel.send_message("init_verbs", list(panel_tabs = panel_tabs, verblist = verblist))
+
+/client/proc/check_panel_loaded()
+ if(stat_panel.is_ready())
+ return
+ to_chat(src, span_userdanger("Statpanel failed to load, click here to reload the panel "))
+
+/**
+ * Handles incoming messages from the stat-panel TGUI.
+ */
+/client/proc/on_stat_panel_message(type, payload)
+ switch(type)
+ if("Update-Verbs")
+ init_verbs()
+ if("Remove-Tabs")
+ panel_tabs -= payload["tab"]
+ if("Send-Tabs")
+ panel_tabs |= payload["tab"]
+ if("Reset-Tabs")
+ panel_tabs = list()
+ if("Set-Tab")
+ stat_tab = payload["tab"]
+ SSstatpanels.immediate_send_stat_data(src)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index d6ecea2edd905..1f3b7c66d0c16 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -169,6 +169,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
///If the game is in fullscreen mode
var/fullscreen_mode = FALSE
+ ///Whether or not the MC tab of the Stat Panel refreshes fast. This is expensive so make sure you need it.
+ var/fast_mc_refresh = FALSE
+ ///When enabled, will split the 'Admin' panel into several tabs.
+ var/split_admin_tabs = FALSE
+
/// New TGUI Preference preview
var/map_name = "player_pref_map"
var/atom/movable/screen/map_view/screen_main
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index afbf559d72147..dea42a098e9d6 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -152,6 +152,8 @@
READ_FILE(S["sound_tts"], sound_tts)
READ_FILE(S["volume_tts"], volume_tts)
READ_FILE(S["sound_tts_blips"], sound_tts_blips)
+ READ_FILE(S["fast_mc_refresh"], fast_mc_refresh)
+ READ_FILE(S["split_admin_tabs"], split_admin_tabs)
READ_FILE(S["key_bindings"], key_bindings)
READ_FILE(S["custom_emotes"], custom_emotes)
@@ -228,6 +230,9 @@
tgui_input = sanitize_integer(tgui_input, FALSE, TRUE, initial(tgui_input))
tgui_input_big_buttons = sanitize_integer(tgui_input_big_buttons, FALSE, TRUE, initial(tgui_input_big_buttons))
tgui_input_buttons_swap = sanitize_integer(tgui_input_buttons_swap, FALSE, TRUE, initial(tgui_input_buttons_swap))
+
+ fast_mc_refresh = sanitize_integer(fast_mc_refresh, FALSE, TRUE, initial(fast_mc_refresh))
+ split_admin_tabs = sanitize_integer(split_admin_tabs, FALSE, TRUE, initial(split_admin_tabs))
return TRUE
@@ -294,6 +299,10 @@
tgui_input_big_buttons = sanitize_integer(tgui_input_big_buttons, FALSE, TRUE, initial(tgui_input_big_buttons))
tgui_input_buttons_swap = sanitize_integer(tgui_input_buttons_swap, FALSE, TRUE, initial(tgui_input_buttons_swap))
+ // Admin
+ fast_mc_refresh = sanitize_integer(fast_mc_refresh, FALSE, TRUE, initial(fast_mc_refresh))
+ split_admin_tabs = sanitize_integer(split_admin_tabs, FALSE, TRUE, initial(split_admin_tabs))
+
WRITE_FILE(S["default_slot"], default_slot)
WRITE_FILE(S["lastchangelog"], lastchangelog)
WRITE_FILE(S["ooccolor"], ooccolor)
@@ -343,6 +352,10 @@
WRITE_FILE(S["tgui_input_big_buttons"], tgui_input_big_buttons)
WRITE_FILE(S["tgui_input_buttons_swap"], tgui_input_buttons_swap)
+ // Admin options
+ WRITE_FILE(S["fast_mc_refresh"], fast_mc_refresh)
+ WRITE_FILE(S["split_admin_tabs"], split_admin_tabs)
+
return TRUE
/datum/preferences/proc/save_keybinds()
diff --git a/code/modules/client/preferences_ui.dm b/code/modules/client/preferences_ui.dm
index 599f39456cded..6f6480f9f4afb 100644
--- a/code/modules/client/preferences_ui.dm
+++ b/code/modules/client/preferences_ui.dm
@@ -96,6 +96,7 @@
data["alternate_option"] = alternate_option
data["special_occupation"] = be_special
if(GAME_SETTINGS)
+ data["is_admin"] = user.client?.holder ? TRUE : FALSE
data["ui_style_color"] = ui_style_color
data["ui_style"] = ui_style
data["ui_style_alpha"] = ui_style_alpha
@@ -130,6 +131,8 @@
data["quick_equip"] = list()
for(var/quick_equip_slots in quick_equip)
data["quick_equip"] += slot_flag_to_fluff(quick_equip_slots)
+ data["fast_mc_refresh"] = fast_mc_refresh
+ data["split_admin_tabs"] = split_admin_tabs
if(KEYBIND_SETTINGS)
data["is_admin"] = user.client?.holder ? TRUE : FALSE
data["key_bindings"] = list()
@@ -801,6 +804,12 @@
if("unique_action_use_active_hand")
unique_action_use_active_hand = !unique_action_use_active_hand
+ if("fast_mc_refresh")
+ fast_mc_refresh = !fast_mc_refresh
+
+ if("split_admin_tabs")
+ split_admin_tabs = !split_admin_tabs
+
else // Handle the unhandled cases
return
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index f88eb2768f52e..40f15d50d8f37 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -270,6 +270,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
ghost.mind = mind
mind = null
ghost.key = key
+ ghost.client?.init_verbs()
ghost.mind?.current = ghost
ghost.faction = faction
@@ -318,51 +319,51 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
return FALSE
-/mob/dead/observer/Stat()
+/mob/dead/observer/get_status_tab_items()
. = ..()
- if(statpanel("Status"))
- if(SSticker.current_state == GAME_STATE_PREGAME)
- stat("Time To Start:", "[SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]")
- stat("Players: [length(GLOB.player_list)]", "Players Ready: [length(GLOB.ready_players)]")
- for(var/i in GLOB.player_list)
- if(isnewplayer(i))
- var/mob/new_player/N = i
- stat("[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key]", N.ready ? "Playing" : "")
- else if(isobserver(i))
- var/mob/dead/observer/O = i
- stat("[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key]", "Observing")
- var/status_value = SSevacuation?.get_status_panel_eta()
- if(status_value)
- stat("Evacuation in:", status_value)
- if(SSticker.mode)
- var/rulerless_countdown = SSticker.mode.get_hivemind_collapse_countdown()
- if(rulerless_countdown)
- stat("Orphan hivemind collapse timer:", rulerless_countdown)
- if(GLOB.respawn_allowed)
- status_value = (GLOB.key_to_time_of_role_death[key] + SSticker.mode?.respawn_time - world.time) * 0.1
- if(status_value <= 0)
- stat("Respawn timer:", "READY")
- else
- stat("Respawn timer:", "[(status_value / 60) % 60]:[add_leading(num2text(status_value % 60), 2, "0")]")
- if(SSticker.mode?.flags_round_type & MODE_INFESTATION)
- if(larva_position)
- stat("Position in larva candidate queue: ", "[larva_position]")
- var/datum/job/xeno_job = SSjob.GetJobType(/datum/job/xenomorph)
- var/stored_larva = xeno_job.total_positions - xeno_job.current_positions
- if(stored_larva)
- stat("Burrowed larva:", stored_larva)
- //game end timer for patrol and sensor capture
- var/patrol_end_countdown = SSticker.mode?.game_end_countdown()
- if(patrol_end_countdown)
- stat("Round End timer:", patrol_end_countdown)
- //respawn wave timer
- var/patrol_wave_countdown = SSticker.mode?.wave_countdown()
- if(patrol_wave_countdown)
- stat("Respawn wave timer:", patrol_wave_countdown)
- var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode
- if(issensorcapturegamemode(SSticker.mode))
- stat("Activated Sensor Towers:", sensor_mode.sensors_activated)
+ if(SSticker.current_state == GAME_STATE_PREGAME)
+ . += "Time To Start: [SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]"
+ . += "Players: [length(GLOB.player_list)]"
+ . += "Players Ready: [length(GLOB.ready_players)]"
+ for(var/i in GLOB.player_list)
+ if(isnewplayer(i))
+ var/mob/new_player/N = i
+ . += "[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key][N.ready ? " Playing" : ""]"
+ else if(isobserver(i))
+ var/mob/dead/observer/O = i
+ . += "[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key] Observing"
+ var/status_value = SSevacuation?.get_status_panel_eta()
+ if(status_value)
+ . += "Evacuation in: [status_value]"
+ if(SSticker.mode)
+ var/rulerless_countdown = SSticker.mode.get_hivemind_collapse_countdown()
+ if(rulerless_countdown)
+ . += "Orphan hivemind collapse timer: [rulerless_countdown]"
+ if(GLOB.respawn_allowed)
+ status_value = (GLOB.key_to_time_of_role_death[key] + SSticker.mode?.respawn_time - world.time) * 0.1
+ if(status_value <= 0)
+ . += "Respawn timer: READY"
+ else
+ . += "Respawn timer: [(status_value / 60) % 60]:[add_leading(num2text(status_value % 60), 2, "0")]"
+ if(SSticker.mode?.flags_round_type & MODE_INFESTATION)
+ if(larva_position)
+ . += "Position in larva candidate queue: [larva_position]"
+ var/datum/job/xeno_job = SSjob.GetJobType(/datum/job/xenomorph)
+ var/stored_larva = xeno_job.total_positions - xeno_job.current_positions
+ if(stored_larva)
+ . += "Burrowed larva: [stored_larva]"
+ //game end timer for patrol and sensor capture
+ var/patrol_end_countdown = SSticker.mode?.game_end_countdown()
+ if(patrol_end_countdown)
+ . += "Round End timer: [patrol_end_countdown]"
+ //respawn wave timer
+ var/patrol_wave_countdown = SSticker.mode?.wave_countdown()
+ if(patrol_wave_countdown)
+ . += "Respawn wave timer: [patrol_wave_countdown]"
+ var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode
+ if(issensorcapturegamemode(SSticker.mode))
+ . += "Activated Sensor Towers: [sensor_mode.sensors_activated]"
/mob/dead/observer/verb/reenter_corpse()
set category = "Ghost"
@@ -387,6 +388,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
client.view_size.set_default(get_screen_size(client.prefs.widescreenpref))//Let's reset so people can't become allseeing gods
mind.transfer_to(old_mob, TRUE)
+ client.init_verbs()
return TRUE
/mob/dead/observer/verb/toggle_HUDs()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index fa1cf9a88fcf4..b81209831bb52 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1,5 +1,5 @@
/mob/living/carbon/human/Initialize(mapload)
- verbs += /mob/living/proc/lay_down
+ add_verb(src, /mob/living/proc/lay_down)
b_type = pick(7;"O-", 38;"O+", 6;"A-", 34;"A+", 2;"B-", 9;"B+", 1;"AB-", 3;"AB+")
blood_type = b_type
@@ -89,39 +89,38 @@
GLOB.dead_human_list -= src
return ..()
-/mob/living/carbon/human/Stat()
+/mob/living/carbon/human/get_status_tab_items()
. = ..()
- if(statpanel("Game"))
- var/eta_status = SSevacuation?.get_status_panel_eta()
- if(eta_status)
- stat("Evacuation in:", eta_status)
-
- //combat patrol timer
- var/patrol_end_countdown = SSticker.mode?.game_end_countdown()
- if(patrol_end_countdown)
- stat("Round End timer:", patrol_end_countdown)
-
- if(internal)
- stat("Internal Atmosphere Info", internal.name)
- stat("Tank Pressure", internal.pressure)
- stat("Distribution Pressure", internal.distribute_pressure)
-
- if(assigned_squad)
- if(assigned_squad.primary_objective)
- stat("Primary Objective: ", assigned_squad.primary_objective)
- if(assigned_squad.secondary_objective)
- stat("Secondary Objective: ", assigned_squad.secondary_objective)
-
- if(mobility_aura)
- stat(null, "You are affected by a MOVE order.")
- if(protection_aura)
- stat(null, "You are affected by a HOLD order.")
- if(marksman_aura)
- stat(null, "You are affected by a FOCUS order.")
- var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode
- if(issensorcapturegamemode(SSticker.mode))
- stat("Activated Sensor Towers:", sensor_mode.sensors_activated)
+ var/eta_status = SSevacuation?.get_status_panel_eta()
+ if(eta_status)
+ . += "Evacuation in: [eta_status]"
+
+ //combat patrol timer
+ var/patrol_end_countdown = SSticker.mode?.game_end_countdown()
+ if(patrol_end_countdown)
+ . += "Round End timer: [patrol_end_countdown]"
+
+ if(internal)
+ . += "Internal Atmosphere Info [internal.name]"
+ . += "Tank Pressure [internal.pressure]"
+ . += "Distribution Pressure [internal.distribute_pressure]"
+
+ if(assigned_squad)
+ if(assigned_squad.primary_objective)
+ . += "Primary Objective: [assigned_squad.primary_objective]"
+ if(assigned_squad.secondary_objective)
+ . += "Secondary Objective: [assigned_squad.secondary_objective]"
+
+ if(mobility_aura)
+ . += "You are affected by a MOVE order."
+ if(protection_aura)
+ . += "You are affected by a HOLD order."
+ if(marksman_aura)
+ . += "You are affected by a FOCUS order."
+ var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode
+ if(issensorcapturegamemode(SSticker.mode))
+ . += "Activated Sensor Towers: [sensor_mode.sensors_activated]"
/mob/living/carbon/human/ex_act(severity)
if(status_flags & GODMODE)
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 2efe4f674c632..c9941aa061a3c 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -249,8 +249,7 @@
/// Removes all species-specific verbs and actions
/datum/species/proc/remove_inherent_abilities(mob/living/carbon/human/H)
if(inherent_verbs)
- for(var/verb_path in inherent_verbs)
- H.verbs -= verb_path
+ remove_verb(H, inherent_verbs)
if(inherent_actions)
for(var/action_path in inherent_actions)
var/datum/action/old_species_action = H.actions_by_path[action_path]
@@ -260,8 +259,7 @@
/// Adds all species-specific verbs and actions
/datum/species/proc/add_inherent_abilities(mob/living/carbon/human/H)
if(inherent_verbs)
- for(var/verb_path in inherent_verbs)
- H.verbs |= verb_path
+ add_verb(H, inherent_verbs)
if(inherent_actions)
for(var/action_path in inherent_actions)
var/datum/action/new_species_action = new action_path(H)
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm
index d4003fedd9d10..aee9da70a6b39 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/carrier/carrier.dm
@@ -27,10 +27,9 @@
. = ..()
hugger_overlays_icon = mutable_appearance('icons/Xeno/2x2_Xenos.dmi',"empty")
-/mob/living/carbon/xenomorph/carrier/Stat()
+/mob/living/carbon/xenomorph/carrier/get_status_tab_items()
. = ..()
- if(statpanel("Game"))
- stat("Stored Huggers:", "[huggers] / [xeno_caste.huggers_max]")
+ . += "Stored Huggers: [huggers] / [xeno_caste.huggers_max]"
/mob/living/carbon/xenomorph/carrier/update_icons()
. = ..()
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm b/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm
index f8ed8d097f1a5..5455834c84a82 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/hivelord/hivelord.dm
@@ -24,8 +24,6 @@
update_spits()
-/mob/living/carbon/xenomorph/hivelord/Stat()
+/mob/living/carbon/xenomorph/hivelord/get_status_tab_items()
. = ..()
-
- if(statpanel("Game"))
- stat("Active Tunnel Sets:", "[LAZYLEN(tunnels)] / [HIVELORD_TUNNEL_SET_LIMIT]")
+ . += "Active Tunnel Sets: [LAZYLEN(tunnels)] / [HIVELORD_TUNNEL_SET_LIMIT]"
diff --git a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm
index 0129c9d1e8515..620aeb9012ca6 100644
--- a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm
+++ b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm
@@ -98,30 +98,27 @@
//Adds stuff to your "Status" pane -- Specific castes can have their own, like carrier hugger count
//Those are dealt with in their caste files.
-/mob/living/carbon/xenomorph/Stat()
+/mob/living/carbon/xenomorph/get_status_tab_items()
. = ..()
- if(!statpanel("Game"))
- return
-
if(!(xeno_caste.caste_flags & CASTE_EVOLUTION_ALLOWED))
- stat("Evolve Progress:", "(FINISHED)")
+ . += "Evolve Progress: (FINISHED)"
else if(!hive.check_ruler())
- stat("Evolve Progress:", "(HALTED - NO RULER)")
+ . += "Evolve Progress: (HALTED - NO RULER)"
else
- stat("Evolve Progress:", "[evolution_stored]/[xeno_caste.evolution_threshold]")
+ . += "Evolve Progress: [evolution_stored]/[xeno_caste.evolution_threshold]"
if(upgrade_possible())
- stat("Upgrade Progress:", "[upgrade_stored]/[xeno_caste.upgrade_threshold]")
+ . += "Upgrade Progress: [upgrade_stored]/[xeno_caste.upgrade_threshold]"
else //Upgrade process finished or impossible
- stat("Upgrade Progress:", "(FINISHED)")
+ . += "Upgrade Progress: (FINISHED)"
- stat("Health:", "[overheal ? "[overheal] + ": ""][health]/[maxHealth]") //Changes with balance scalar, can't just use the caste
+ . += "Health: [overheal ? "[overheal] + ": ""][health]/[maxHealth]" //Changes with balance scalar, can't just use the caste
if(xeno_caste.plasma_max > 0)
- stat("Plasma:", "[plasma_stored]/[xeno_caste.plasma_max]")
+ . += "Plasma: [plasma_stored]/[xeno_caste.plasma_max]"
- stat("Sunder:", "[100-sunder]% armor left")
+ . += "Sunder: [100-sunder]% armor left"
//Very weak <= 1.0, weak <= 2.0, no modifier 2-3, strong <= 3.5, very strong <= 4.5
var/msg_holder = ""
@@ -137,7 +134,7 @@
msg_holder = "Strong"
if(4.0 to INFINITY)
msg_holder = "Very strong"
- stat("[AURA_XENO_FRENZY] pheromone strength:", msg_holder)
+ . += "[AURA_XENO_FRENZY] pheromone strength: [msg_holder]"
if(warding_aura)
switch(warding_aura)
if(-INFINITY to 1.0)
@@ -150,7 +147,7 @@
msg_holder = "Strong"
if(4.0 to INFINITY)
msg_holder = "Very strong"
- stat("[AURA_XENO_WARDING] pheromone strength:", msg_holder)
+ . += "[AURA_XENO_WARDING] pheromone strength: [msg_holder]"
if(recovery_aura)
switch(recovery_aura)
if(-INFINITY to 1.0)
@@ -163,12 +160,12 @@
msg_holder = "Strong"
if(4.0 to INFINITY)
msg_holder = "Very strong"
- stat("[AURA_XENO_RECOVERY] pheromone strength:", msg_holder)
+ . += "[AURA_XENO_RECOVERY] pheromone strength: [msg_holder]"
if(hivenumber == XENO_HIVE_NORMAL)
var/hivemind_countdown = SSticker.mode?.get_hivemind_collapse_countdown()
if(hivemind_countdown)
- stat("Orphan hivemind collapse timer:", hivemind_countdown)
+ . += "Orphan hivemind collapse timer: [hivemind_countdown]"
//A simple handler for checking your state. Used in pretty much all the procs.
/mob/living/carbon/xenomorph/proc/check_state()
@@ -199,13 +196,12 @@
/mob/living/carbon/xenomorph/proc/remove_inherent_verbs()
if(inherent_verbs)
for(var/verb_path in inherent_verbs)
- verbs -= verb_path
+ remove_verb(verbs, verb_path)
//Add all your inherent caste verbs and procs. Used in evolution.
/mob/living/carbon/xenomorph/proc/add_inherent_verbs()
if(inherent_verbs)
- for(var/verb_path in inherent_verbs)
- verbs |= verb_path
+ add_verb(src, inherent_verbs)
//Adds or removes a delay to movement based on your caste. If speed = 0 then it shouldn't do much.
@@ -494,7 +490,7 @@
return FALSE
/mob/living/carbon/xenomorph/proc/setup_verbs()
- verbs += /mob/living/proc/lay_down
+ add_verb(src, /mob/living/proc/lay_down)
/mob/living/carbon/xenomorph/hivemind/setup_verbs()
return
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 2ed55c44e8771..52d9904f00028 100755
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -350,36 +350,34 @@
lighting_alpha = initial(lighting_alpha) // yes you really have to change both the eye and the ai vars
-/mob/living/silicon/ai/Stat()
+/mob/living/silicon/ai/get_status_tab_items()
. = ..()
- if(statpanel("Game"))
-
- if(stat != CONSCIOUS)
- stat("System status:", "Nonfunctional")
- return
+ if(stat != CONSCIOUS)
+ . += "System status: Nonfunctional"
+ return
- stat("System integrity:", "[(health + 100) / 2]%")
- stat("
- Operation information -
")
- stat("Current orbit:", "[GLOB.current_orbit]")
+ . += "System integrity: [(health + 100) / 2]%"
+ . += "
- Operation information -
"
+ . += "Current orbit: [GLOB.current_orbit]"
- if(!GLOB.marine_main_ship?.orbital_cannon?.chambered_tray)
- stat("Orbital bombardment status:", "No ammo chambered in the cannon.
")
- else
- stat("Orbital bombardment warhead:", "[GLOB.marine_main_ship.orbital_cannon.tray.warhead.name] Detected
")
+ if(!GLOB.marine_main_ship?.orbital_cannon?.chambered_tray)
+ . += "Orbital bombardment status: No ammo chambered in the cannon.
"
+ else
+ . += "Orbital bombardment warhead: [GLOB.marine_main_ship.orbital_cannon.tray.warhead.name] Detected
"
- stat("Current supply points:", "[round(SSpoints.supply_points[FACTION_TERRAGOV])]")
+ . += "Current supply points: [round(SSpoints.supply_points[FACTION_TERRAGOV])]"
- stat("Current dropship points:", "[round(SSpoints.dropship_points)]")
+ . += "Current dropship points: [round(SSpoints.dropship_points)]"
- stat("Current alert level:", "[GLOB.marine_main_ship.get_security_level()]")
+ . += "Current alert level: [GLOB.marine_main_ship.get_security_level()]"
- stat("Number of living marines:", "[SSticker.mode.count_humans_and_xenos()[1]]")
+ . += "Number of living marines: [SSticker.mode.count_humans_and_xenos()[1]]"
- if(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE > world.time)
- stat("Railgun status:", "Cooling down, next fire in [(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE - world.time)/10] seconds.")
- else
- stat("Railgun status:", "Railgun is ready to fire.")
+ if(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE > world.time)
+ . += "Railgun status: Cooling down, next fire in [(GLOB.marine_main_ship?.rail_gun?.last_firing_ai + COOLDOWN_RAILGUN_FIRE - world.time)/10] seconds."
+ else
+ . += "Railgun status: Railgun is ready to fire."
/mob/living/silicon/ai/fully_replace_character_name(oldname, newname)
. = ..()
diff --git a/code/modules/mob/living/simple_animal/friendly/parrot.dm b/code/modules/mob/living/simple_animal/friendly/parrot.dm
index afa0a662c0ec3..f30a6ab2f1d59 100644
--- a/code/modules/mob/living/simple_animal/friendly/parrot.dm
+++ b/code/modules/mob/living/simple_animal/friendly/parrot.dm
@@ -95,10 +95,9 @@ GLOBAL_LIST_INIT(strippable_parrot_items, create_strippable_list(list(
return ..()
-/mob/living/simple_animal/parrot/Stat()
+/mob/living/simple_animal/parrot/get_status_tab_items()
. = ..()
- if(statpanel("Game"))
- stat("Held Item", held_item)
+ . += "Held Item: [held_item]"
/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode)
diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm
index 8bc0aca07c4a4..44b35686f276b 100644
--- a/code/modules/mob/living/simple_animal/simple_animal.dm
+++ b/code/modules/mob/living/simple_animal/simple_animal.dm
@@ -216,11 +216,9 @@
return TRUE
-/mob/living/simple_animal/Stat()
+/mob/living/simple_animal/get_status_tab_items()
. = ..()
-
- if(statpanel("Game"))
- stat("Health:", "[round((health / maxHealth) * 100)]%")
+ . += "Health: [round((health / maxHealth) * 100)]%"
/mob/living/simple_animal/ex_act(severity)
diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm
index 40b530a618aad..1a2f8c86a778e 100644
--- a/code/modules/mob/login.dm
+++ b/code/modules/mob/login.dm
@@ -65,3 +65,4 @@
log_mob_tag("\[[tag]\] NEW OWNER: [key_name(src)]")
SEND_SIGNAL(src, COMSIG_MOB_LOGIN)
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_MOB_LOGIN, src)
+ client.init_verbs()
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index fdbc3dfd8b010..14bce229952a9 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -40,72 +40,6 @@
log_mob_tag("\[[tag]\] CREATED: [key_name(src)]")
become_hearing_sensitive()
-
-/mob/Stat()
- . = ..()
- if(statpanel("Status"))
- if(GLOB.round_id)
- stat("Round ID:", GLOB.round_id)
- stat("Operation Time:", stationTimestamp("hh:mm"))
- stat("Current Map:", length(SSmapping.configs) ? SSmapping.configs[GROUND_MAP].map_name : "Loading...")
- stat("Current Ship:", length(SSmapping.configs) ? SSmapping.configs[SHIP_MAP].map_name : "Loading...")
- stat("Game Mode:", "[GLOB.master_mode]")
-
- if(statpanel("Game"))
- if(client)
- stat("Ping:", "[round(client.lastping, 1)]ms (Average: [round(client.avgping, 1)]ms)")
- stat("Time Dilation:", "[round(SStime_track.time_dilation_current,1)]% AVG:([round(SStime_track.time_dilation_avg_fast,1)]%, [round(SStime_track.time_dilation_avg,1)]%, [round(SStime_track.time_dilation_avg_slow,1)]%)")
-
- if(client?.holder?.rank?.rights)
- if(client.holder.rank.rights & (R_DEBUG))
- if(statpanel("MC"))
- stat("CPU:", "[world.cpu]")
- stat("Instances:", "[num2text(length(world.contents), 10)]")
- stat("World Time:", "[world.time]")
- GLOB.stat_entry()
- config.stat_entry()
- GLOB.cameranet.stat_entry()
- stat(null)
- if(Master)
- Master.stat_entry()
- else
- stat("Master Controller:", "ERROR")
- if(Failsafe)
- Failsafe.stat_entry()
- else
- stat("Failsafe Controller:", "ERROR")
- if(Master)
- stat(null)
- for(var/datum/controller/subsystem/SS in Master.subsystems)
- SS.stat_entry()
- if(client.holder.rank.rights & (R_ADMIN|R_MENTOR))
- if(statpanel("Tickets"))
- GLOB.ahelp_tickets.stat_entry()
- if(length(GLOB.sdql2_queries))
- if(statpanel("SDQL2"))
- stat("Access Global SDQL2 List", GLOB.sdql2_vv_statobj)
- for(var/i in GLOB.sdql2_queries)
- var/datum/SDQL2_query/Q = i
- Q.generate_stat()
-
- if(listed_turf && client)
- if(!TurfAdjacent(listed_turf))
- listed_turf = null
- else
- statpanel(listed_turf.name, null, listed_turf)
- var/list/overrides = list()
- for(var/image/I in client.images)
- if(I.loc && I.loc.loc == listed_turf && I.override)
- overrides += I.loc
- for(var/atom/A in listed_turf)
- if(!A.mouse_opacity)
- continue
- if(A.invisibility > see_invisible)
- continue
- if(length(overrides) && (A in overrides))
- continue
- statpanel(listed_turf.name, null, A)
-
/mob/proc/show_message(msg, type, alt_msg, alt_type, avoid_highlight)
if(!client)
return FALSE
@@ -712,6 +646,10 @@
/mob/proc/is_muzzled()
return FALSE
+/// Adds this list to the output to the stat browser
+/mob/proc/get_status_tab_items()
+ . = list("") //we want to offset unique stuff from standard stuff
+ SEND_SIGNAL(src, COMSIG_MOB_GET_STATUS_TAB_ITEMS, .)
// reset_perspective(thing) set the eye to the thing (if it's equal to current default reset to mob perspective)
// reset_perspective(null) set eye to common default : mob on turf, loc otherwise
diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm
index 7a49f326816e5..8ac11ae5aa67c 100644
--- a/code/modules/mob/new_player/new_player.dm
+++ b/code/modules/mob/new_player/new_player.dm
@@ -69,24 +69,23 @@
return null
return output
-/mob/new_player/Stat()
+/mob/new_player/get_status_tab_items()
. = ..()
if(!SSticker)
return
- if(statpanel("Status"))
-
- if(SSticker.current_state == GAME_STATE_PREGAME)
- stat("Time To Start:", "[SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]")
- stat("Players: [length(GLOB.player_list)]", "Players Ready: [length(GLOB.ready_players)]")
- for(var/i in GLOB.player_list)
- if(isnewplayer(i))
- var/mob/new_player/N = i
- stat("[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key]", N.ready ? "Playing" : "")
- else if(isobserver(i))
- var/mob/dead/observer/O = i
- stat("[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key]", "Observing")
+ if(SSticker.current_state == GAME_STATE_PREGAME)
+ . += "Time To Start: [SSticker.time_left > 0 ? SSticker.GetTimeLeft() : "(DELAYED)"]"
+ . += "Players: [length(GLOB.player_list)]"
+ . += "Players Ready: [length(GLOB.ready_players)]"
+ for(var/i in GLOB.player_list)
+ if(isnewplayer(i))
+ var/mob/new_player/N = i
+ . += "[N.client?.holder?.fakekey ? N.client.holder.fakekey : N.key][N.ready ? " Playing" : ""]"
+ else if(isobserver(i))
+ var/mob/dead/observer/O = i
+ . += "[O.client?.holder?.fakekey ? O.client.holder.fakekey : O.key] Observing"
/mob/new_player/Topic(href, href_list[])
@@ -295,6 +294,7 @@
var/spawn_type = assigned_role.return_spawn_type(client.prefs)
var/mob/living/spawning_living = new spawn_type()
GLOB.joined_player_list += ckey
+ client.init_verbs()
spawning_living.on_spawn(src)
@@ -394,6 +394,7 @@
observer.name = observer.real_name
mind.transfer_to(observer, TRUE)
+ observer.client?.init_verbs()
qdel(src)
///Toggles the new players ready state
diff --git a/html/statbrowser.css b/html/statbrowser.css
new file mode 100644
index 0000000000000..dc693f42f756b
--- /dev/null
+++ b/html/statbrowser.css
@@ -0,0 +1,227 @@
+body {
+ font-family: Verdana, Geneva, Tahoma, sans-serif;
+ font-size: 12px !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+body.dark {
+ background-color: #131313;
+ color: #b2c4dd;
+ scrollbar-base-color: #1c1c1c;
+ scrollbar-face-color: #3b3b3b;
+ scrollbar-3dlight-color: #252525;
+ scrollbar-highlight-color: #252525;
+ scrollbar-track-color: #1c1c1c;
+ scrollbar-arrow-color: #929292;
+ scrollbar-shadow-color: #3b3b3b;
+}
+
+#menu {
+ background-color: #F0F0F0;
+ position: fixed;
+ width: 100%;
+ z-index: 100;
+}
+
+.dark #menu {
+ background-color: #202020;
+}
+
+#statcontent {
+ padding: 7px 7px 7px 7px;
+}
+
+a {
+ color: black;
+ text-decoration: none
+}
+
+.dark a {
+ color: #b2c4dd;
+}
+
+a:hover,
+.dark a:hover {
+ text-decoration: underline;
+}
+
+ul {
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+ background-color: #333;
+}
+
+li {
+ float: left;
+}
+
+li a {
+ display: block;
+ color: white;
+ text-align: center;
+ padding: 14px 16px;
+ text-decoration: none;
+}
+
+li a:hover:not(.active) {
+ background-color: #111;
+}
+
+.button-container {
+ display: inline-flex;
+ flex-wrap: wrap-reverse;
+ flex-direction: row;
+ align-items: flex-start;
+ overflow-x: hidden;
+ white-space: pre-wrap;
+ padding: 0 4px;
+}
+
+.button {
+ background-color: #dfdfdf;
+ border: 1px solid #cecece;
+ border-bottom-width: 2px;
+ color: rgba(0, 0, 0, 0.7);
+ padding: 6px 4px 4px;
+ text-align: center;
+ text-decoration: none;
+ font-size: 12px;
+ margin: 0;
+ cursor: pointer;
+ transition-duration: 100ms;
+ order: 3;
+ min-width: 40px;
+}
+
+.dark button {
+ background-color: #222222;
+ border-color: #343434;
+ color: rgba(255, 255, 255, 0.5);
+}
+
+.button:hover {
+ background-color: #ececec;
+ transition-duration: 0;
+}
+
+.dark button:hover {
+ background-color: #2e2e2e;
+}
+
+.button:active,
+.button.active {
+ background-color: #ffffff;
+ color: black;
+ border-top-color: #cecece;
+ border-left-color: #cecece;
+ border-right-color: #cecece;
+ border-bottom-color: #ffffff;
+}
+
+.dark .button:active,
+.dark .button.active {
+ background-color: #444444;
+ color: white;
+ border-top-color: #343434;
+ border-left-color: #343434;
+ border-right-color: #343434;
+ border-bottom-color: #ffffff;
+}
+
+.grid-container {
+ margin: -2px;
+ margin-right: -15px;
+}
+
+.grid-item {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+ box-sizing: border-box;
+ overflow: visible;
+ padding: 3px 2px;
+ text-decoration: none;
+}
+
+@media only screen and (min-width: 300px) {
+ .grid-item {
+ width: 50%;
+ }
+}
+
+@media only screen and (min-width: 430px) {
+ .grid-item {
+ width: 33%;
+ }
+}
+
+@media only screen and (min-width: 560px) {
+ .grid-item {
+ width: 25%;
+ }
+}
+
+@media only screen and (min-width: 770px) {
+ .grid-item {
+ width: 20%;
+ }
+}
+
+.grid-item:hover {
+ z-index: 1;
+}
+
+.grid-item:hover .grid-item-text {
+ width: auto;
+ text-decoration: underline;
+}
+
+.grid-item-text {
+ display: inline-block;
+ width: 100%;
+ background-color: #ffffff;
+ margin: 0 -6px;
+ padding: 0 6px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ pointer-events: none;
+}
+
+.dark .grid-item-text {
+ background-color: #131313;
+}
+
+.link {
+ display: inline;
+ background: none;
+ border: none;
+ padding: 7px 14px;
+ color: black;
+ text-decoration: none;
+ cursor: pointer;
+ font-size: 13px;
+ margin: 2px 2px;
+}
+
+.dark .link {
+ color: #abc6ec;
+}
+
+.link:hover {
+ text-decoration: underline;
+}
+
+img {
+ -ms-interpolation-mode: nearest-neighbor;
+ image-rendering: pixelated;
+}
+
+.interview_panel_controls,
+.interview_panel_stats {
+ margin-bottom: 10px;
+}
diff --git a/html/statbrowser.html b/html/statbrowser.html
new file mode 100644
index 0000000000000..1aea8811d58a0
--- /dev/null
+++ b/html/statbrowser.html
@@ -0,0 +1,3 @@
+
+
+
diff --git a/html/statbrowser.js b/html/statbrowser.js
new file mode 100644
index 0000000000000..1a8fba254ad61
--- /dev/null
+++ b/html/statbrowser.js
@@ -0,0 +1,1003 @@
+// Polyfills and compatibility ------------------------------------------------
+var decoder = decodeURIComponent || unescape;
+if (!Array.prototype.includes) {
+ Array.prototype.includes = function (thing) {
+ for (var i = 0; i < this.length; i++) {
+ if (this[i] == thing) return true;
+ }
+ return false;
+ }
+}
+if (!String.prototype.trim) {
+ String.prototype.trim = function () {
+ return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
+ };
+}
+
+// Status panel implementation ------------------------------------------------
+var status_tab_parts = ["Loading..."];
+var current_tab = null;
+var mc_tab_parts = [["Loading...", ""]];
+var href_token = null;
+var spells = [];
+var spell_tabs = [];
+var verb_tabs = [];
+var verbs = [["", ""]]; // list with a list inside
+var tickets = [];
+var interviewManager = { status: "", interviews: [] };
+var sdql2 = [];
+var permanent_tabs = []; // tabs that won't be cleared by wipes
+var turfcontents = [];
+var turfname = "";
+var imageRetryDelay = 500;
+var imageRetryLimit = 50;
+var menu = document.getElementById('menu');
+var under_menu = document.getElementById('under_menu');
+var statcontentdiv = document.getElementById('statcontent');
+var storedimages = [];
+var split_admin_tabs = false;
+
+// Any BYOND commands that could result in the client's focus changing go through this
+// to ensure that when we relinquish our focus, we don't do it after the result of
+// a command has already taken focus for itself.
+function run_after_focus(callback) {
+ setTimeout(callback, 0);
+}
+
+function createStatusTab(name) {
+ if (name.indexOf(".") != -1) {
+ var splitName = name.split(".");
+ if (split_admin_tabs && splitName[0] === "Admin")
+ name = splitName[1];
+ else
+ name = splitName[0];
+ }
+ if (document.getElementById(name) || name.trim() == "") {
+ return;
+ }
+ if (!verb_tabs.includes(name) && !permanent_tabs.includes(name)) {
+ return;
+ }
+ var B = document.createElement("BUTTON");
+ B.onclick = function () {
+ tab_change(name);
+ this.blur();
+ };
+ B.id = name;
+ B.textContent = name;
+ B.className = "button";
+ //ORDERING ALPHABETICALLY
+ B.style.order = name.charCodeAt(0);
+ if (name == "Status" || name == "MC") {
+ B.style.order = name == "Status" ? 1 : 2;
+ }
+ //END ORDERING
+ menu.appendChild(B);
+ SendTabToByond(name);
+ under_menu.style.height = menu.clientHeight + 'px';
+}
+
+function removeStatusTab(name) {
+ if (!document.getElementById(name) || permanent_tabs.includes(name)) {
+ return;
+ }
+ for (var i = verb_tabs.length - 1; i >= 0; --i) {
+ if (verb_tabs[i] == name) {
+ verb_tabs.splice(i, 1);
+ }
+ }
+ menu.removeChild(document.getElementById(name));
+ TakeTabFromByond(name);
+ under_menu.style.height = menu.clientHeight + 'px';
+}
+
+function sortVerbs() {
+ verbs.sort(function (a, b) {
+ var selector = a[0] == b[0] ? 1 : 0;
+ if (a[selector].toUpperCase() < b[selector].toUpperCase()) {
+ return 1;
+ }
+ else if (a[selector].toUpperCase() > b[selector].toUpperCase()) {
+ return -1;
+ }
+ return 0;
+ })
+}
+
+window.onresize = function () {
+ under_menu.style.height = menu.clientHeight + 'px';
+}
+
+function addPermanentTab(name) {
+ if (!permanent_tabs.includes(name)) {
+ permanent_tabs.push(name);
+ }
+ createStatusTab(name);
+}
+
+function removePermanentTab(name) {
+ for (var i = permanent_tabs.length - 1; i >= 0; --i) {
+ if (permanent_tabs[i] == name) {
+ permanent_tabs.splice(i, 1);
+ }
+ }
+ removeStatusTab(name);
+}
+
+function checkStatusTab() {
+ for (var i = 0; i < menu.children.length; i++) {
+ if (!verb_tabs.includes(menu.children[i].id) && !permanent_tabs.includes(menu.children[i].id)) {
+ menu.removeChild(menu.children[i]);
+ }
+ }
+}
+
+function remove_verb(v) {
+ var verb_to_remove = v; // to_remove = [verb:category, verb:name]
+ for (var i = verbs.length - 1; i >= 0; i--) {
+ var part_to_remove = verbs[i];
+ if (part_to_remove[1] == verb_to_remove[1]) {
+ verbs.splice(i, 1)
+ }
+ }
+}
+
+function check_verbs() {
+ for (var v = verb_tabs.length - 1; v >= 0; v--) {
+ verbs_cat_check(verb_tabs[v]);
+ }
+}
+
+function verbs_cat_check(cat) {
+ var tabCat = cat;
+ if (cat.indexOf(".") != -1) {
+ var splitName = cat.split(".");
+ if (split_admin_tabs && splitName[0] === "Admin")
+ tabCat = splitName[1];
+ else
+ tabCat = splitName[0];
+ }
+ var verbs_in_cat = 0;
+ var verbcat = "";
+ if (!verb_tabs.includes(tabCat)) {
+ removeStatusTab(tabCat);
+ return;
+ }
+ for (var v = 0; v < verbs.length; v++) {
+ var part = verbs[v];
+ verbcat = part[0];
+ if (verbcat.indexOf(".") != -1) {
+ var splitName = verbcat.split(".");
+ if (split_admin_tabs && splitName[0] === "Admin")
+ verbcat = splitName[1];
+ else
+ verbcat = splitName[0];
+ }
+ if (verbcat != tabCat || verbcat.trim() == "") {
+ continue;
+ }
+ else {
+ verbs_in_cat = 1;
+ break; // we only need one
+ }
+ }
+ if (verbs_in_cat != 1) {
+ removeStatusTab(tabCat);
+ if (current_tab == tabCat)
+ tab_change("Status");
+ }
+}
+
+function findVerbindex(name, verblist) {
+ for (var i = 0; i < verblist.length; i++) {
+ var part = verblist[i];
+ if (part[1] == name)
+ return i;
+ }
+}
+function wipe_verbs() {
+ verbs = [["", ""]];
+ verb_tabs = [];
+ checkStatusTab(); // remove all empty verb tabs
+}
+
+function update_verbs() {
+ wipe_verbs();
+ Byond.sendMessage("Update-Verbs");
+}
+
+function SendTabsToByond() {
+ var tabstosend = [];
+ tabstosend = tabstosend.concat(permanent_tabs, verb_tabs);
+ for (var i = 0; i < tabstosend.length; i++) {
+ SendTabToByond(tabstosend[i]);
+ }
+}
+
+function SendTabToByond(tab) {
+ Byond.sendMessage("Send-Tabs", {tab: tab});
+}
+
+//Byond can't have this tab anymore since we're removing it
+function TakeTabFromByond(tab) {
+ Byond.sendMessage("Remove-Tabs", {tab: tab});
+}
+
+function spell_cat_check(cat) {
+ var spells_in_cat = 0;
+ var spellcat = "";
+ for (var s = 0; s < spells.length; s++) {
+ var spell = spells[s];
+ spellcat = spell[0];
+ if (spellcat == cat) {
+ spells_in_cat++;
+ }
+ }
+ if (spells_in_cat < 1) {
+ removeStatusTab(cat);
+ }
+}
+
+function tab_change(tab) {
+ if (tab == current_tab) return;
+ if (document.getElementById(current_tab))
+ document.getElementById(current_tab).className = "button"; // disable active on last button
+ current_tab = tab;
+ set_byond_tab(tab);
+ if (document.getElementById(tab))
+ document.getElementById(tab).className = "button active"; // make current button active
+ var spell_tabs_thingy = (spell_tabs.includes(tab));
+ var verb_tabs_thingy = (verb_tabs.includes(tab));
+ if (tab == "Status") {
+ draw_status();
+ } else if (tab == "MC") {
+ draw_mc();
+ } else if (spell_tabs_thingy) {
+ draw_spells(tab);
+ } else if (verb_tabs_thingy) {
+ draw_verbs(tab);
+ } else if (tab == "Debug Stat Panel") {
+ draw_debug();
+ } else if (tab == "Tickets") {
+ draw_tickets();
+ //draw_interviews();
+ } else if (tab == "SDQL2") {
+ draw_sdql2();
+ } else if (tab == turfname) {
+ draw_listedturf();
+ } else {
+ statcontentdiv.textContext = "Loading...";
+ }
+ Byond.winset(Byond.windowId, {
+ 'is-visible': true,
+ });
+}
+
+function set_byond_tab(tab) {
+ Byond.sendMessage("Set-Tab", {tab: tab});
+}
+
+function draw_debug() {
+ statcontentdiv.textContent = "";
+ var wipeverbstabs = document.createElement("div");
+ var link = document.createElement("a");
+ link.onclick = function () { wipe_verbs() };
+ link.textContent = "Wipe All Verbs";
+ wipeverbstabs.appendChild(link);
+ document.getElementById("statcontent").appendChild(wipeverbstabs);
+ var wipeUpdateVerbsTabs = document.createElement("div");
+ var updateLink = document.createElement("a");
+ updateLink.onclick = function () { update_verbs() };
+ updateLink.textContent = "Wipe and Update All Verbs";
+ wipeUpdateVerbsTabs.appendChild(updateLink);
+ document.getElementById("statcontent").appendChild(wipeUpdateVerbsTabs);
+ var text = document.createElement("div");
+ text.textContent = "Verb Tabs:";
+ document.getElementById("statcontent").appendChild(text);
+ var table1 = document.createElement("table");
+ for (var i = 0; i < verb_tabs.length; i++) {
+ var part = verb_tabs[i];
+ // Hide subgroups except admin subgroups if they are split
+ if (verb_tabs[i].lastIndexOf(".") != -1) {
+ var splitName = verb_tabs[i].split(".");
+ if (split_admin_tabs && splitName[0] === "Admin")
+ part = splitName[1];
+ else
+ continue;
+ }
+ var tr = document.createElement("tr");
+ var td1 = document.createElement("td");
+ td1.textContent = part;
+ var a = document.createElement("a");
+ a.onclick = function (part) {
+ return function () { removeStatusTab(part) };
+ }(part);
+ a.textContent = " Delete Tab " + part;
+ td1.appendChild(a);
+ tr.appendChild(td1);
+ table1.appendChild(tr);
+ }
+ document.getElementById("statcontent").appendChild(table1);
+ var header2 = document.createElement("div");
+ header2.textContent = "Verbs:";
+ document.getElementById("statcontent").appendChild(header2);
+ var table2 = document.createElement("table");
+ for (var v = 0; v < verbs.length; v++) {
+ var part2 = verbs[v];
+ var trr = document.createElement("tr");
+ var tdd1 = document.createElement("td");
+ tdd1.textContent = part2[0];
+ var tdd2 = document.createElement("td");
+ tdd2.textContent = part2[1];
+ trr.appendChild(tdd1);
+ trr.appendChild(tdd2);
+ table2.appendChild(trr);
+ }
+ document.getElementById("statcontent").appendChild(table2);
+ var text3 = document.createElement("div");
+ text3.textContent = "Permanent Tabs:";
+ document.getElementById("statcontent").appendChild(text3);
+ var table3 = document.createElement("table");
+ for (var i = 0; i < permanent_tabs.length; i++) {
+ var part3 = permanent_tabs[i];
+ var trrr = document.createElement("tr");
+ var tddd1 = document.createElement("td");
+ tddd1.textContent = part3;
+ trrr.appendChild(tddd1);
+ table3.appendChild(trrr);
+ }
+ document.getElementById("statcontent").appendChild(table3);
+
+}
+function draw_status() {
+ if (!document.getElementById("Status")) {
+ createStatusTab("Status");
+ current_tab = "Status";
+ }
+ statcontentdiv.textContent = '';
+ for (var i = 0; i < status_tab_parts.length; i++) {
+ if (status_tab_parts[i].trim() == "") {
+ document.getElementById("statcontent").appendChild(document.createElement("br"));
+ } else {
+ var div = document.createElement("div");
+ div.textContent = status_tab_parts[i];
+ document.getElementById("statcontent").appendChild(div);
+ }
+ }
+ if (verb_tabs.length == 0 || !verbs) {
+ Byond.command("Fix-Stat-Panel");
+ }
+}
+
+function draw_mc() {
+ statcontentdiv.textContent = "";
+ var table = document.createElement("table");
+ for (var i = 0; i < mc_tab_parts.length; i++) {
+ var part = mc_tab_parts[i];
+ var tr = document.createElement("tr");
+ var td1 = document.createElement("td");
+ td1.textContent = part[0];
+ var td2 = document.createElement("td");
+ if (part[2]) {
+ var a = document.createElement("a");
+ a.href = "?_src_=vars;admin_token=" + href_token + ";Vars=" + part[2];
+ a.textContent = part[1];
+ td2.appendChild(a);
+ } else {
+ td2.textContent = part[1];
+ }
+ tr.appendChild(td1);
+ tr.appendChild(td2);
+ table.appendChild(tr);
+ }
+ document.getElementById("statcontent").appendChild(table);
+}
+
+function remove_tickets() {
+ if (tickets) {
+ tickets = [];
+ removePermanentTab("Tickets");
+ if (current_tab == "Tickets")
+ tab_change("Status");
+ }
+ checkStatusTab();
+}
+
+function remove_sdql2() {
+ if (sdql2) {
+ sdql2 = [];
+ removePermanentTab("SDQL2");
+ if (current_tab == "SDQL2")
+ tab_change("Status");
+ }
+ checkStatusTab();
+}
+
+function remove_interviews() {
+ if (tickets) {
+ tickets = [];
+ }
+ checkStatusTab();
+}
+
+function iconError(e) {
+ if(current_tab != turfname) {
+ return;
+ }
+ setTimeout(function () {
+ var node = e.target;
+ var current_attempts = Number(node.getAttribute("data-attempts")) || 0
+ if (current_attempts > imageRetryLimit) {
+ return;
+ }
+ var src = node.src;
+ node.src = null;
+ node.src = src + '#' + current_attempts;
+ node.setAttribute("data-attempts", current_attempts + 1)
+ draw_listedturf();
+ }, imageRetryDelay);
+}
+
+function draw_listedturf() {
+ statcontentdiv.textContent = "";
+ var table = document.createElement("table");
+ for (var i = 0; i < turfcontents.length; i++) {
+ var part = turfcontents[i];
+ if (storedimages[part[1]] == null && part[2]) {
+ var img = document.createElement("img");
+ img.src = part[2];
+ img.id = part[1];
+ storedimages[part[1]] = part[2];
+ img.onerror = iconError;
+ table.appendChild(img);
+ } else {
+ var img = document.createElement("img");
+ img.onerror = iconError;
+ img.src = storedimages[part[1]];
+ img.id = part[1];
+ table.appendChild(img);
+ }
+ var b = document.createElement("div");
+ var clickcatcher = "";
+ b.className = "link";
+ b.onmousedown = function (part) {
+ // The outer function is used to close over a fresh "part" variable,
+ // rather than every onmousedown getting the "part" of the last entry.
+ return function (e) {
+ e.preventDefault();
+ clickcatcher = "?src=" + part[1];
+ switch (e.button) {
+ case 1:
+ clickcatcher += ";statpanel_item_click=middle"
+ break;
+ case 2:
+ clickcatcher += ";statpanel_item_click=right"
+ break;
+ default:
+ clickcatcher += ";statpanel_item_click=left"
+ }
+ if (e.shiftKey) {
+ clickcatcher += ";statpanel_item_shiftclick=1";
+ }
+ if (e.ctrlKey) {
+ clickcatcher += ";statpanel_item_ctrlclick=1";
+ }
+ if (e.altKey) {
+ clickcatcher += ";statpanel_item_altclick=1";
+ }
+ window.location.href = clickcatcher;
+ }
+ }(part);
+ b.textContent = part[0];
+ table.appendChild(b);
+ table.appendChild(document.createElement("br"));
+ }
+ document.getElementById("statcontent").appendChild(table);
+}
+
+function remove_listedturf() {
+ removePermanentTab(turfname);
+ checkStatusTab();
+ if (current_tab == turfname) {
+ tab_change("Status");
+ }
+}
+
+function remove_mc() {
+ removePermanentTab("MC");
+ if (current_tab == "MC") {
+ tab_change("Status");
+ }
+};
+
+function draw_sdql2() {
+ statcontentdiv.textContent = "";
+ var table = document.createElement("table");
+ for (var i = 0; i < sdql2.length; i++) {
+ var part = sdql2[i];
+ var tr = document.createElement("tr");
+ var td1 = document.createElement("td");
+ td1.textContent = part[0];
+ var td2 = document.createElement("td");
+ if (part[2]) {
+ var a = document.createElement("a");
+ a.href = "?src=" + part[2] + ";statpanel_item_click=left";
+ a.textContent = part[1];
+ td2.appendChild(a);
+ } else {
+ td2.textContent = part[1];
+ }
+ tr.appendChild(td1);
+ tr.appendChild(td2);
+ table.appendChild(tr);
+ }
+ document.getElementById("statcontent").appendChild(table);
+}
+
+function draw_tickets() {
+ statcontentdiv.textContent = "";
+ var table = document.createElement("table");
+ if (!tickets) {
+ return;
+ }
+ for (var i = 0; i < tickets.length; i++) {
+ var part = tickets[i];
+ var tr = document.createElement("tr");
+ var td1 = document.createElement("td");
+ td1.textContent = part[0];
+ var td2 = document.createElement("td");
+ if (part[2]) {
+ var a = document.createElement("a");
+ a.href = "?_src_=holder;admin_token=" + href_token + ";ahelp=" + part[2] + ";ahelp_action=ticket;statpanel_item_click=left;action=ticket";
+ a.textContent = part[1];
+ td2.appendChild(a);
+ } else if (part[3]) {
+ var a = document.createElement("a");
+ a.href = "?src=" + part[3] + ";statpanel_item_click=left";
+ a.textContent = part[1];
+ td2.appendChild(a);
+ } else {
+ td2.textContent = part[1];
+ }
+ tr.appendChild(td1);
+ tr.appendChild(td2);
+ table.appendChild(tr);
+ }
+ document.getElementById("statcontent").appendChild(table);
+}
+
+function draw_interviews() {
+ var body = document.createElement("div");
+ var header = document.createElement("h3");
+ header.textContent = "Interviews";
+ body.appendChild(header);
+ var manDiv = document.createElement("div");
+ manDiv.className = "interview_panel_controls"
+ var manLink = document.createElement("a");
+ manLink.textContent = "Open Interview Manager Panel";
+ manLink.href = "?_src_=holder;admin_token=" + href_token + ";interview_man=1;statpanel_item_click=left";
+ manDiv.appendChild(manLink);
+ body.appendChild(manDiv);
+
+ // List interview stats
+ var statsDiv = document.createElement("table");
+ statsDiv.className = "interview_panel_stats";
+ for (var key in interviewManager.status) {
+ var d = document.createElement("div");
+ var tr = document.createElement("tr");
+ var stat_name = document.createElement("td");
+ var stat_text = document.createElement("td");
+ stat_name.textContent = key;
+ stat_text.textContent = interviewManager.status[key];
+ tr.appendChild(stat_name);
+ tr.appendChild(stat_text);
+ statsDiv.appendChild(tr);
+ }
+ body.appendChild(statsDiv);
+ document.getElementById("statcontent").appendChild(body);
+
+ // List interviews if any are open
+ var table = document.createElement("table");
+ table.className = "interview_panel_table";
+ if (!interviewManager) {
+ return;
+ }
+ for (var i = 0; i < interviewManager.interviews.length; i++) {
+ var part = interviewManager.interviews[i];
+ var tr = document.createElement("tr");
+ var td = document.createElement("td");
+ var a = document.createElement("a");
+ a.textContent = part["status"];
+ a.href = "?_src_=holder;admin_token=" + href_token + ";interview=" + part["ref"] + ";statpanel_item_click=left";
+ td.appendChild(a);
+ tr.appendChild(td);
+ table.appendChild(tr);
+ }
+ document.getElementById("statcontent").appendChild(table);
+}
+
+function draw_spells(cat) {
+ statcontentdiv.textContent = "";
+ var table = document.createElement("table");
+ for (var i = 0; i < spells.length; i++) {
+ var part = spells[i];
+ if (part[0] != cat) continue;
+ var tr = document.createElement("tr");
+ var td1 = document.createElement("td");
+ td1.textContent = part[1];
+ var td2 = document.createElement("td");
+ if (part[3]) {
+ var a = document.createElement("a");
+ a.href = "?src=" + part[3] + ";statpanel_item_click=left";
+ a.textContent = part[2];
+ td2.appendChild(a);
+ } else {
+ td2.textContent = part[2];
+ }
+ tr.appendChild(td1);
+ tr.appendChild(td2);
+ table.appendChild(tr);
+ }
+ document.getElementById("statcontent").appendChild(table);
+}
+
+function make_verb_onclick(command) {
+ return function () {
+ run_after_focus(function () {
+ Byond.command(command);
+ });
+ };
+}
+
+function draw_verbs(cat) {
+ statcontentdiv.textContent = "";
+ var table = document.createElement("div");
+ var additions = {}; // additional sub-categories to be rendered
+ table.className = "grid-container";
+ sortVerbs();
+ if (split_admin_tabs && cat.lastIndexOf(".") != -1) {
+ var splitName = cat.split(".");
+ if (splitName[0] === "Admin")
+ cat = splitName[1];
+ }
+ verbs.reverse(); // sort verbs backwards before we draw
+ for (var i = 0; i < verbs.length; ++i) {
+ var part = verbs[i];
+ var name = part[0];
+ if (split_admin_tabs && name.lastIndexOf(".") != -1) {
+ var splitName = name.split(".");
+ if (splitName[0] === "Admin")
+ name = splitName[1];
+ }
+ var command = part[1];
+
+ if (command && name.lastIndexOf(cat, 0) != -1 && (name.length == cat.length || name.charAt(cat.length) == ".")) {
+ var subCat = name.lastIndexOf(".") != -1 ? name.split(".")[1] : null;
+ if (subCat && !additions[subCat]) {
+ var newTable = document.createElement("div");
+ newTable.className = "grid-container";
+ additions[subCat] = newTable;
+ }
+
+ var a = document.createElement("a");
+ a.href = "#";
+ a.onclick = make_verb_onclick(command.replace(/\s/g, "-"));
+ a.className = "grid-item";
+ var t = document.createElement("span");
+ t.textContent = command;
+ t.className = "grid-item-text";
+ a.appendChild(t);
+ (subCat ? additions[subCat] : table).appendChild(a);
+ }
+ }
+
+ // Append base table to view
+ var content = document.getElementById("statcontent");
+ content.appendChild(table);
+
+ // Append additional sub-categories if relevant
+ for (var cat in additions) {
+ if (additions.hasOwnProperty(cat)) {
+ // do addition here
+ var header = document.createElement("h3");
+ header.textContent = cat;
+ content.appendChild(header);
+ content.appendChild(additions[cat]);
+ }
+ }
+}
+
+function set_theme(which) {
+ if (which == "light") {
+ document.body.className = "";
+ set_style_sheet("browserOutput_white");
+ } else if (which == "dark") {
+ document.body.className = "dark";
+ set_style_sheet("browserOutput");
+ }
+}
+
+function set_style_sheet(sheet) {
+ if (document.getElementById("goonStyle")) {
+ var currentSheet = document.getElementById("goonStyle");
+ currentSheet.parentElement.removeChild(currentSheet);
+ }
+ var head = document.getElementsByTagName('head')[0];
+ var sheetElement = document.createElement("link");
+ sheetElement.id = "goonStyle";
+ sheetElement.rel = "stylesheet";
+ sheetElement.type = "text/css";
+ sheetElement.href = sheet + ".css";
+ sheetElement.media = 'all';
+ head.appendChild(sheetElement);
+}
+
+function restoreFocus() {
+ run_after_focus(function () {
+ Byond.winset('map', {
+ focus: true,
+ });
+ });
+}
+
+function getCookie(cname) {
+ var name = cname + '=';
+ var ca = document.cookie.split(';');
+ for (var i = 0; i < ca.length; i++) {
+ var c = ca[i];
+ while (c.charAt(0) == ' ') c = c.substring(1);
+ if (c.indexOf(name) === 0) {
+ return decoder(c.substring(name.length, c.length));
+ }
+ }
+ return '';
+}
+
+function add_verb_list(payload) {
+ var to_add = payload; // list of a list with category and verb inside it
+ to_add.sort(); // sort what we're adding
+ for (var i = 0; i < to_add.length; i++) {
+ var part = to_add[i];
+ if (!part[0])
+ continue;
+ var category = part[0];
+ if (category.indexOf(".") != -1) {
+ var splitName = category.split(".");
+ if (split_admin_tabs && splitName[0] === "Admin")
+ category = splitName[1];
+ else
+ category = splitName[0];
+ }
+ if (findVerbindex(part[1], verbs))
+ continue;
+ if (verb_tabs.includes(category)) {
+ verbs.push(part);
+ if (current_tab == category) {
+ draw_verbs(category); // redraw if we added a verb to the tab we're currently in
+ }
+ } else if (category) {
+ verb_tabs.push(category);
+ verbs.push(part);
+ createStatusTab(category);
+ }
+ }
+};
+
+function init_spells() {
+ var cat = "";
+ for (var i = 0; i < spell_tabs.length; i++) {
+ cat = spell_tabs[i];
+ if (cat.length > 0) {
+ verb_tabs.push(cat);
+ createStatusTab(cat);
+ }
+ }
+}
+
+document.addEventListener("mouseup", restoreFocus);
+document.addEventListener("keyup", restoreFocus);
+
+if (!current_tab) {
+ addPermanentTab("Status");
+ tab_change("Status");
+}
+
+window.onload = function () {
+ Byond.sendMessage("Update-Verbs");
+};
+
+Byond.subscribeTo('update_spells', function (payload) {
+ spell_tabs = payload.spell_tabs;
+ var do_update = false;
+ if (spell_tabs.includes(current_tab)) {
+ do_update = true;
+ }
+ init_spells();
+ if (payload.actions) {
+ spells = payload.actions;
+ if (do_update) {
+ draw_spells(current_tab);
+ }
+ } else {
+ remove_spells();
+ }
+});
+
+Byond.subscribeTo('remove_verb_list', function (v) {
+ var to_remove = v;
+ for (var i = 0; i < to_remove.length; i++) {
+ remove_verb(to_remove[i]);
+ }
+ check_verbs();
+ sortVerbs();
+ if (verb_tabs.includes(current_tab))
+ draw_verbs(current_tab);
+});
+
+// passes a 2D list of (verbcategory, verbname) creates tabs and adds verbs to respective list
+// example (IC, Say)
+Byond.subscribeTo('init_verbs', function (payload) {
+ wipe_verbs(); // remove all verb categories so we can replace them
+ checkStatusTab(); // remove all status tabs
+ verb_tabs = payload.panel_tabs;
+ verb_tabs.sort(); // sort it
+ var do_update = false;
+ var cat = "";
+ for (var i = 0; i < verb_tabs.length; i++) {
+ cat = verb_tabs[i];
+ createStatusTab(cat); // create a category if the verb doesn't exist yet
+ }
+ if (verb_tabs.includes(current_tab)) {
+ do_update = true;
+ }
+ if (payload.verblist) {
+ add_verb_list(payload.verblist);
+ sortVerbs(); // sort them
+ if (do_update) {
+ draw_verbs(current_tab);
+ }
+ }
+ SendTabsToByond();
+});
+
+Byond.subscribeTo('update_stat', function (payload) {
+ status_tab_parts = [payload.ping_str];
+ var parsed = payload.global_data;
+
+ for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]);
+
+ parsed = payload.other_str;
+
+ for (var i = 0; i < parsed.length; i++) if (parsed[i] != null) status_tab_parts.push(parsed[i]);
+
+ if (current_tab == "Status") {
+ draw_status();
+ } else if (current_tab == "Debug Stat Panel") {
+ draw_debug();
+ }
+});
+
+Byond.subscribeTo('update_mc', function (payload) {
+ mc_tab_parts = payload.mc_data;
+ mc_tab_parts.splice(0, 0, ["Location:", payload.coord_entry]);
+
+ if (!verb_tabs.includes("MC")) {
+ verb_tabs.push("MC");
+ }
+
+ createStatusTab("MC");
+
+ if (current_tab == "MC") {
+ draw_mc();
+ }
+});
+
+Byond.subscribeTo('remove_spells', function () {
+ for (var s = 0; s < spell_tabs.length; s++) {
+ removeStatusTab(spell_tabs[s]);
+ }
+});
+
+Byond.subscribeTo('init_spells', function () {
+ var cat = "";
+ for (var i = 0; i < spell_tabs.length; i++) {
+ cat = spell_tabs[i];
+ if (cat.length > 0) {
+ verb_tabs.push(cat);
+ createStatusTab(cat);
+ }
+ }
+});
+
+Byond.subscribeTo('check_spells', function () {
+ for (var v = 0; v < spell_tabs.length; v++) {
+ spell_cat_check(spell_tabs[v]);
+ }
+});
+
+Byond.subscribeTo('create_debug', function () {
+ if (!document.getElementById("Debug Stat Panel")) {
+ addPermanentTab("Debug Stat Panel");
+ } else {
+ removePermanentTab("Debug Stat Panel");
+ }
+});
+
+Byond.subscribeTo('create_listedturf', function (TN) {
+ remove_listedturf(); // remove the last one if we had one
+ turfname = TN;
+ addPermanentTab(turfname);
+ tab_change(turfname);
+});
+
+Byond.subscribeTo('remove_admin_tabs', function () {
+ href_token = null;
+ remove_mc();
+ remove_tickets();
+ remove_sdql2();
+ remove_interviews();
+});
+
+Byond.subscribeTo('update_listedturf', function (TC) {
+ turfcontents = TC;
+ if (current_tab == turfname) {
+ draw_listedturf();
+ }
+});
+
+Byond.subscribeTo('update_interviews', function (I) {
+ interviewManager = I;
+ if (current_tab == "Tickets") {
+ draw_interviews();
+ }
+});
+
+Byond.subscribeTo('update_split_admin_tabs', function (status) {
+ status = (status == true);
+
+ if (split_admin_tabs !== status) {
+ if (split_admin_tabs === true) {
+ removeStatusTab("Events");
+ removeStatusTab("Fun");
+ removeStatusTab("Game");
+ }
+ update_verbs();
+ }
+ split_admin_tabs = status;
+});
+
+Byond.subscribeTo('add_admin_tabs', function (ht) {
+ href_token = ht;
+ addPermanentTab("MC");
+ addPermanentTab("Tickets");
+});
+
+Byond.subscribeTo('update_sdql2', function (S) {
+ sdql2 = S;
+ if (sdql2.length > 0 && !verb_tabs.includes("SDQL2")) {
+ verb_tabs.push("SDQL2");
+ addPermanentTab("SDQL2");
+ }
+ if (current_tab == "SDQL2") {
+ draw_sdql2();
+ }
+});
+
+Byond.subscribeTo('update_tickets', function (T) {
+ tickets = T;
+ if (!verb_tabs.includes("Tickets")) {
+ verb_tabs.push("Tickets");
+ addPermanentTab("Tickets");
+ }
+ if (current_tab == "Tickets") {
+ draw_tickets();
+ }
+});
+
+Byond.subscribeTo('remove_listedturf', remove_listedturf);
+
+Byond.subscribeTo('remove_sdql2', remove_sdql2);
+
+Byond.subscribeTo('remove_mc', remove_mc);
+
+Byond.subscribeTo('add_verb_list', add_verb_list);
diff --git a/interface/skin.dmf b/interface/skin.dmf
index 6a916397352f3..cb0db3bef49d7 100644
--- a/interface/skin.dmf
+++ b/interface/skin.dmf
@@ -331,17 +331,17 @@ window "statwindow"
type = MAIN
pos = 281,0
size = 640x480
- anchor1 = none
- anchor2 = none
+ anchor1 = -1,-1
+ anchor2 = -1,-1
saved-params = "pos;size;is-minimized;is-maximized"
is-pane = true
- elem "stat"
- type = INFO
+ elem "statbrowser"
+ type = BROWSER
pos = 0,0
size = 640x480
anchor1 = 0,0
anchor2 = 100,100
- is-default = true
+ is-visible = false
saved-params = ""
window "tgui_say"
diff --git a/tgmc.dme b/tgmc.dme
index 54fd5336be454..76e09f021e5a8 100755
--- a/tgmc.dme
+++ b/tgmc.dme
@@ -176,6 +176,7 @@
#include "code\__HELPERS\tts.dm"
#include "code\__HELPERS\type2type.dm"
#include "code\__HELPERS\unsorted.dm"
+#include "code\__HELPERS\verbs.dm"
#include "code\__HELPERS\view.dm"
#include "code\__HELPERS\sorts\__main.dm"
#include "code\__HELPERS\sorts\InsertSort.dm"
@@ -293,6 +294,7 @@
#include "code\controllers\subsystem\spatial_gridmap.dm"
#include "code\controllers\subsystem\spawning.dm"
#include "code\controllers\subsystem\speech_controller.dm"
+#include "code\controllers\subsystem\statpanel.dm"
#include "code\controllers\subsystem\stickyban.dm"
#include "code\controllers\subsystem\strings.dm"
#include "code\controllers\subsystem\tgui.dm"
@@ -307,6 +309,7 @@
#include "code\controllers\subsystem\processing\fastprocess.dm"
#include "code\controllers\subsystem\processing\instruments.dm"
#include "code\controllers\subsystem\processing\obj.dm"
+#include "code\controllers\subsystem\processing\obj_tab_items.dm"
#include "code\controllers\subsystem\processing\processing.dm"
#include "code\controllers\subsystem\processing\projectiles.dm"
#include "code\controllers\subsystem\processing\resinshaping.dm"
@@ -370,6 +373,7 @@
#include "code\datums\components\clothing_tint.dm"
#include "code\datums\components\companion.dm"
#include "code\datums\components\connect_loc_behalf.dm"
+#include "code\datums\components\connect_mob_behalf.dm"
#include "code\datums\components\deployable_item.dm"
#include "code\datums\components\dripping.dm"
#include "code\datums\components\grillable.dm"
diff --git a/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx b/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx
index ae67bf639c46e..347db2455e225 100644
--- a/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx
+++ b/tgui/packages/tgui/interfaces/PlayerPreferences/GameSettings.tsx
@@ -28,8 +28,14 @@ const ParallaxNumToString = (integer) => {
export const GameSettings = (props, context) => {
const { act, data } = useBackend(context);
- const { ui_style_color, scaling_method, pixel_size, parallax, quick_equip } =
- data;
+ const {
+ ui_style_color,
+ scaling_method,
+ pixel_size,
+ parallax,
+ quick_equip,
+ is_admin,
+ } = data;
return (
@@ -292,6 +298,30 @@ export const GameSettings = (props, context) => {
+ {is_admin && (
+
+
+
+
+
+ )}
);
};
diff --git a/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx b/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx
index 91270dd100fe3..4d0b3b7bd8f70 100644
--- a/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx
+++ b/tgui/packages/tgui/interfaces/PlayerPreferences/Types.tsx
@@ -36,6 +36,7 @@ type GameSettingData = {
pixel_size: number;
parallax: number;
quick_equip: string[];
+ is_admin: number;
};
type GearCustomizationData = {
From 347b3e1b4bb62be312966f46924e8db6c1ee778d Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Wed, 26 Jul 2023 19:12:58 +0100
Subject: [PATCH 02/12] Remove HTML from status items
---
code/modules/mob/dead/observer/observer.dm | 10 +++++-----
code/modules/mob/living/carbon/human/human.dm | 4 ++--
code/modules/mob/living/carbon/xenomorph/xenoprocs.dm | 2 +-
code/modules/mob/living/silicon/ai/ai.dm | 7 ++++---
4 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 40f15d50d8f37..a0be82e9aaadb 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -339,11 +339,11 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
if(SSticker.mode)
var/rulerless_countdown = SSticker.mode.get_hivemind_collapse_countdown()
if(rulerless_countdown)
- . += "Orphan hivemind collapse timer: [rulerless_countdown]"
+ . += "Orphan hivemind collapse timer: [rulerless_countdown]"
if(GLOB.respawn_allowed)
status_value = (GLOB.key_to_time_of_role_death[key] + SSticker.mode?.respawn_time - world.time) * 0.1
if(status_value <= 0)
- . += "Respawn timer: READY"
+ . += "Respawn timer: READY"
else
. += "Respawn timer: [(status_value / 60) % 60]:[add_leading(num2text(status_value % 60), 2, "0")]"
if(SSticker.mode?.flags_round_type & MODE_INFESTATION)
@@ -356,14 +356,14 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
//game end timer for patrol and sensor capture
var/patrol_end_countdown = SSticker.mode?.game_end_countdown()
if(patrol_end_countdown)
- . += "Round End timer: [patrol_end_countdown]"
+ . += "Round End timer: [patrol_end_countdown]"
//respawn wave timer
var/patrol_wave_countdown = SSticker.mode?.wave_countdown()
if(patrol_wave_countdown)
- . += "Respawn wave timer: [patrol_wave_countdown]"
+ . += "Respawn wave timer: [patrol_wave_countdown]"
var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode
if(issensorcapturegamemode(SSticker.mode))
- . += "Activated Sensor Towers: [sensor_mode.sensors_activated]"
+ . += "Activated Sensor Towers: [sensor_mode.sensors_activated]"
/mob/dead/observer/verb/reenter_corpse()
set category = "Ghost"
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index b81209831bb52..07c7c9562bf76 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -99,7 +99,7 @@
//combat patrol timer
var/patrol_end_countdown = SSticker.mode?.game_end_countdown()
if(patrol_end_countdown)
- . += "Round End timer: [patrol_end_countdown]"
+ . += "Round End timer: [patrol_end_countdown]"
if(internal)
. += "Internal Atmosphere Info [internal.name]"
@@ -120,7 +120,7 @@
. += "You are affected by a FOCUS order."
var/datum/game_mode/combat_patrol/sensor_capture/sensor_mode = SSticker.mode
if(issensorcapturegamemode(SSticker.mode))
- . += "Activated Sensor Towers: [sensor_mode.sensors_activated]"
+ . += "Activated Sensor Towers: [sensor_mode.sensors_activated]"
/mob/living/carbon/human/ex_act(severity)
if(status_flags & GODMODE)
diff --git a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm
index 620aeb9012ca6..a0fafc3d0328f 100644
--- a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm
+++ b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm
@@ -165,7 +165,7 @@
if(hivenumber == XENO_HIVE_NORMAL)
var/hivemind_countdown = SSticker.mode?.get_hivemind_collapse_countdown()
if(hivemind_countdown)
- . += "Orphan hivemind collapse timer: [hivemind_countdown]"
+ . += "Orphan hivemind collapse timer: [hivemind_countdown]"
//A simple handler for checking your state. Used in pretty much all the procs.
/mob/living/carbon/xenomorph/proc/check_state()
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 52d9904f00028..ed9d5bb340c1f 100755
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -358,13 +358,14 @@
return
. += "System integrity: [(health + 100) / 2]%"
- . += "
- Operation information -
"
+ . += ""
+ . += "- Operation information -"
. += "Current orbit: [GLOB.current_orbit]"
if(!GLOB.marine_main_ship?.orbital_cannon?.chambered_tray)
- . += "Orbital bombardment status: No ammo chambered in the cannon.
"
+ . += "Orbital bombardment status: No ammo chambered in the cannon."
else
- . += "Orbital bombardment warhead: [GLOB.marine_main_ship.orbital_cannon.tray.warhead.name] Detected
"
+ . += "Orbital bombardment warhead: [GLOB.marine_main_ship.orbital_cannon.tray.warhead.name] Detected"
. += "Current supply points: [round(SSpoints.supply_points[FACTION_TERRAGOV])]"
From f060b090da5f3e8f161886ad54cde0820e3df198 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Wed, 26 Jul 2023 19:26:43 +0100
Subject: [PATCH 03/12] Fix order for tickets tab
---
html/statbrowser.js | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/html/statbrowser.js b/html/statbrowser.js
index 1a8fba254ad61..9d9d88554c126 100644
--- a/html/statbrowser.js
+++ b/html/statbrowser.js
@@ -67,10 +67,7 @@ function createStatusTab(name) {
B.textContent = name;
B.className = "button";
//ORDERING ALPHABETICALLY
- B.style.order = name.charCodeAt(0);
- if (name == "Status" || name == "MC") {
- B.style.order = name == "Status" ? 1 : 2;
- }
+ B.style.order = ({"Status": 1, "MC": 2, "Tickets": 3})[name] || name.charCodeAt(0);
//END ORDERING
menu.appendChild(B);
SendTabToByond(name);
From 1dd0dd4c928ad3d915b8d6054f5c833b5aa61081 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Wed, 26 Jul 2023 19:39:12 +0100
Subject: [PATCH 04/12] Fix MC tab showing for mentors
---
code/controllers/subsystem/statpanel.dm | 13 ++++++++-----
html/statbrowser.js | 5 ++++-
2 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index fdfc577677c79..6b9803c4901ed 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -49,13 +49,16 @@ SUBSYSTEM_DEF(statpanels)
if(!target.holder)
target.stat_panel.send_message("remove_admin_tabs")
else
- target.stat_panel.send_message("update_split_admin_tabs", target?.prefs.split_admin_tabs)
+ target.stat_panel.send_message("update_split_admin_tabs", target.prefs.split_admin_tabs)
- if(!("MC" in target.panel_tabs) || !("Tickets" in target.panel_tabs))
+ if(!("Tickets" in target.panel_tabs))
target.stat_panel.send_message("add_admin_tabs", target.holder.href_token)
- if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || target?.prefs.fast_mc_refresh))
- set_MC_tab(target)
+ if(check_rights_for(target, R_DEBUG))
+ if(!("MC" in target.panel_tabs))
+ target.stat_panel.send_message("add_mc_tab")
+ if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0) || target.prefs.fast_mc_refresh))
+ set_MC_tab(target)
if(target.stat_tab == "Tickets" && num_fires % default_wait == 0)
set_tickets_tab(target)
@@ -215,7 +218,7 @@ SUBSYSTEM_DEF(statpanels)
if(!target.holder)
return FALSE
- if(target.stat_tab == "MC")
+ if(target.stat_tab == "MC" && check_rights_for(target, R_DEBUG))
set_MC_tab(target)
return TRUE
diff --git a/html/statbrowser.js b/html/statbrowser.js
index 9d9d88554c126..996b2f444e860 100644
--- a/html/statbrowser.js
+++ b/html/statbrowser.js
@@ -965,10 +965,13 @@ Byond.subscribeTo('update_split_admin_tabs', function (status) {
Byond.subscribeTo('add_admin_tabs', function (ht) {
href_token = ht;
- addPermanentTab("MC");
addPermanentTab("Tickets");
});
+Byond.subscribeTo('add_mc_tab', function () {
+ addPermanentTab("MC");
+});
+
Byond.subscribeTo('update_sdql2', function (S) {
sdql2 = S;
if (sdql2.length > 0 && !verb_tabs.includes("SDQL2")) {
From c67e71b59a1db132519761a01d9bdf90cf535e0d Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Wed, 26 Jul 2023 19:39:54 +0100
Subject: [PATCH 05/12] Fix admin tabs splitting by default
---
code/modules/client/preferences.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 21136ea74ac0b..5259dd95f1c6d 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -172,7 +172,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
///Whether or not the MC tab of the Stat Panel refreshes fast. This is expensive so make sure you need it.
var/fast_mc_refresh = FALSE
///When enabled, will split the 'Admin' panel into several tabs.
- var/split_admin_tabs = FALSE
+ var/split_admin_tabs = TRUE
/// New TGUI Preference preview
var/map_name = "player_pref_map"
From db08a8f1efda019d13fdcf2b109661a37a3f6643 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Wed, 26 Jul 2023 21:22:13 +0100
Subject: [PATCH 06/12] Fix tickets tab disappearing
---
code/controllers/subsystem/statpanel.dm | 2 +-
code/modules/admin/verbs/adminhelp.dm | 18 +++++++++---------
2 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index 6b9803c4901ed..67240ccb7c38b 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -99,7 +99,7 @@ SUBSYSTEM_DEF(statpanels)
target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry))
/datum/controller/subsystem/statpanels/proc/set_tickets_tab(client/target)
- var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry()
+ var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry(target)
target.stat_panel.send_message("update_tickets", ahelp_tickets)
/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target)
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index f20544487ff84..50e5942dd49c7 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -108,7 +108,7 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
//Tickets statpanel
-/datum/admin_help_tickets/proc/stat_entry()
+/datum/admin_help_tickets/proc/stat_entry(client/target)
SHOULD_CALL_PARENT(TRUE)
SHOULD_NOT_SLEEP(TRUE)
var/list/L = list()
@@ -140,33 +140,33 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
else if(AH.tier == TICKET_ADMIN)
num_admins_resolved++
- if(check_rights(R_ADMINTICKET, FALSE))
+ if(check_rights_for(target, R_ADMINTICKET))
L[++L.len] = list("Active Tickets:", "[astatclick.update("[num_mentors_active + num_admins_active]")]", null, REF(astatclick))
- else if(check_rights(R_MENTOR, FALSE))
+ else if(check_rights_for(target, R_MENTOR))
L[++L.len] = list("Active Tickets:", "[astatclick.update("[num_mentors_active]")]", null, REF(astatclick))
for(var/I in active_tickets)
var/datum/admin_help/AH = I
var/obj/effect/statclick/updated = AH.statclick.update()
- if(AH.tier == TICKET_MENTOR && check_rights(R_ADMINTICKET|R_MENTOR, FALSE))
+ if(AH.tier == TICKET_MENTOR && check_rights_for(target, R_ADMINTICKET|R_MENTOR))
if(AH.initiator)
L[++L.len] = list("\[[AH.marked ? "X" : " "]\] #[AH.id]. Mentor. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
else
L[++L.len] = list("\[D\] #[AH.id]. Mentor. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
- else if(AH.tier == TICKET_ADMIN && check_rights(R_ADMINTICKET, FALSE))
+ else if(AH.tier == TICKET_ADMIN && check_rights_for(target, R_ADMINTICKET))
if(AH.initiator)
L[++L.len] = list("\[[AH.marked ? "X" : " "]\] #[AH.id]. Admin. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
else
L[++L.len] = list("\[D\] #[AH.id]. Admin. [AH.initiator_key_name]:", "[updated.name]", REF(AH))
- if(check_rights(R_ADMINTICKET, FALSE))
+ if(check_rights_for(target, R_ADMINTICKET))
L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[num_mentors_closed + num_admins_closed]")]", null, REF(cstatclick))
- else if(check_rights(R_MENTOR, FALSE))
+ else if(check_rights_for(target, R_MENTOR))
L[++L.len] = list("Closed Tickets:", "[cstatclick.update("[num_mentors_closed]")]", null, REF(cstatclick))
- if(check_rights(R_ADMINTICKET, FALSE))
+ if(check_rights_for(target, R_ADMINTICKET))
L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[num_mentors_resolved + num_admins_resolved]")]", null, REF(rstatclick))
- else if(check_rights(R_MENTOR, FALSE))
+ else if(check_rights_for(target, R_MENTOR))
L[++L.len] = list("Resolved Tickets:", "[rstatclick.update("[num_mentors_resolved]")]", null, REF(rstatclick))
return L
From 820326f7dcbdbbf604bb15128045ff0b788eea3d Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Thu, 27 Jul 2023 05:33:27 +0100
Subject: [PATCH 07/12] Fix runtimes
These were never necessary
---
code/modules/admin/admin_verbs.dm | 1 -
code/modules/mob/dead/observer/observer.dm | 1 -
2 files changed, 2 deletions(-)
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index c162c0020e8a5..f9e29dbd53247 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -17,7 +17,6 @@
return
var/mob/dead/observer/ghost = M.ghostize(TRUE, TRUE)
- owner.init_verbs()
log_admin("[key_name(ghost)] admin ghosted at [AREACOORD(ghost)].")
if(M.stat != DEAD)
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index a0be82e9aaadb..a2599172668dd 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -388,7 +388,6 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
client.view_size.set_default(get_screen_size(client.prefs.widescreenpref))//Let's reset so people can't become allseeing gods
mind.transfer_to(old_mob, TRUE)
- client.init_verbs()
return TRUE
/mob/dead/observer/verb/toggle_HUDs()
From 602696b156da00e3f51441ba2cbec9a58267dcc6 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Thu, 27 Jul 2023 06:08:26 +0100
Subject: [PATCH 08/12] Rename duplicate hive status verb
---
code/modules/admin/fun_verbs.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/admin/fun_verbs.dm b/code/modules/admin/fun_verbs.dm
index 47af429d18734..d8eac7da3dcb6 100644
--- a/code/modules/admin/fun_verbs.dm
+++ b/code/modules/admin/fun_verbs.dm
@@ -84,7 +84,7 @@
/datum/admins/proc/hive_status()
set category = "Admin.Fun"
- set name = "Hive Status"
+ set name = "Check Hive Status"
set desc = "Check the status of the hive."
if(!check_rights(R_FUN))
From ffae0d6186e497619a6c6e1719b1b26a8acb7220 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Thu, 27 Jul 2023 06:39:33 +0100
Subject: [PATCH 09/12] Remove duplicate build mode verb
---
code/modules/admin/fun_verbs.dm | 10 ----------
code/modules/admin/holder.dm | 1 -
2 files changed, 11 deletions(-)
diff --git a/code/modules/admin/fun_verbs.dm b/code/modules/admin/fun_verbs.dm
index d8eac7da3dcb6..1c8175536d8e6 100644
--- a/code/modules/admin/fun_verbs.dm
+++ b/code/modules/admin/fun_verbs.dm
@@ -872,16 +872,6 @@
message_admins("[ADMIN_TPMONTY(usr)] has possessed [O] ([O.type]).")
-/client/proc/toggle_buildmode()
- set category = "Admin.Fun"
- set name = "Toggle Build Mode"
-
- if(!check_rights(R_FUN))
- return
-
- togglebuildmode(usr)
-
-
/datum/admins/proc/imaginary_friend()
set category = "Admin.Fun"
set name = "Imaginary Friend"
diff --git a/code/modules/admin/holder.dm b/code/modules/admin/holder.dm
index f34ac59188518..dd70031bf6c34 100644
--- a/code/modules/admin/holder.dm
+++ b/code/modules/admin/holder.dm
@@ -411,7 +411,6 @@ GLOBAL_PROTECT(admin_verbs_varedit)
/datum/admins/proc/play_cinematic,
/datum/admins/proc/set_tip,
/datum/admins/proc/ghost_interact,
- /client/proc/toggle_buildmode,
/client/proc/force_event,
/client/proc/toggle_events,
/client/proc/run_weather,
From 12643528fad70e659de8fcd8bdc653ac8f8d2b82 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Fri, 28 Jul 2023 20:11:22 +0000
Subject: [PATCH 10/12] Fix statclicks not working
---
code/modules/admin/verbs/adminhelp.dm | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/code/modules/admin/verbs/adminhelp.dm b/code/modules/admin/verbs/adminhelp.dm
index 50e5942dd49c7..ec19378458eed 100644
--- a/code/modules/admin/verbs/adminhelp.dm
+++ b/code/modules/admin/verbs/adminhelp.dm
@@ -203,15 +203,22 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
/obj/effect/statclick/ticket_list
var/current_state
-
/obj/effect/statclick/ticket_list/Initialize(mapload, name, state)
. = ..()
current_state = state
-
/obj/effect/statclick/ticket_list/Click()
+ if (!usr.client?.holder)
+ message_admins("[key_name_admin(usr)] non-holder clicked on a ticket list statclick! ([src])")
+ usr.log_message("non-holder clicked on a ticket list statclick! ([src])", LOG_ADMIN)
+ return
+
GLOB.ahelp_tickets.BrowseTickets(current_state)
+//called by admin topic
+/obj/effect/statclick/ticket_list/proc/Action()
+ Click()
+
//
//TICKET DATUM
From 5c9bdd0b48bf9cbef2dcb782600f407c96cb5b54 Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Mon, 31 Jul 2023 00:30:11 +0100
Subject: [PATCH 11/12] Fix runtime
---
code/controllers/subsystem/statpanel.dm | 3 +++
1 file changed, 3 insertions(+)
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index 67240ccb7c38b..39be8077eaf78 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -113,6 +113,9 @@ SUBSYSTEM_DEF(statpanels)
target.stat_panel.send_message("update_sdql2", sdql2A)
/datum/controller/subsystem/statpanels/proc/set_turf_examine_tab(client/target, mob/target_mob)
+ if(!target.obj_window)
+ target_mob.set_listed_turf(null)
+ return
var/list/overrides = list()
for(var/image/target_image as anything in target.images)
if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override)
From 9cb28c470cf1da557b60eaa264c7e6ea34f08a7a Mon Sep 17 00:00:00 2001
From: forest <136390975+forestbbbbbbb@users.noreply.github.com>
Date: Wed, 9 Aug 2023 07:48:26 +0100
Subject: [PATCH 12/12] Fix admin woes
Yeah, not the ideal way to do it but this was done because we check for can_interact in /datum/Topic and I cba to refactor everything
---
code/game/atoms.dm | 52 +++++++++++++++++++++++-----------------------
1 file changed, 26 insertions(+), 26 deletions(-)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 9f5c0c4083dc0..2f1ff60793b60 100755
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -922,35 +922,35 @@ Proc for attack log creation, because really why not
/atom/Topic(href, href_list)
+ if(usr?.client)
+ var/client/usr_client = usr.client
+ var/list/paramslist = list()
+
+ if(href_list["statpanel_item_click"])
+ switch(href_list["statpanel_item_click"])
+ if("left")
+ paramslist[LEFT_CLICK] = "1"
+ if("right")
+ paramslist[RIGHT_CLICK] = "1"
+ if("middle")
+ paramslist[MIDDLE_CLICK] = "1"
+ else
+ return
+
+ if(href_list["statpanel_item_shiftclick"])
+ paramslist[SHIFT_CLICK] = "1"
+ if(href_list["statpanel_item_ctrlclick"])
+ paramslist[CTRL_CLICK] = "1"
+ if(href_list["statpanel_item_altclick"])
+ paramslist[ALT_CLICK] = "1"
+
+ var/mouseparams = list2params(paramslist)
+ usr_client.Click(src, loc, null, mouseparams)
+ . = TRUE
+
. = ..()
if(.)
return
- if(!usr?.client)
- return
- var/client/usr_client = usr.client
- var/list/paramslist = list()
-
- if(href_list["statpanel_item_click"])
- switch(href_list["statpanel_item_click"])
- if("left")
- paramslist[LEFT_CLICK] = "1"
- if("right")
- paramslist[RIGHT_CLICK] = "1"
- if("middle")
- paramslist[MIDDLE_CLICK] = "1"
- else
- return
-
- if(href_list["statpanel_item_shiftclick"])
- paramslist[SHIFT_CLICK] = "1"
- if(href_list["statpanel_item_ctrlclick"])
- paramslist[CTRL_CLICK] = "1"
- if(href_list["statpanel_item_altclick"])
- paramslist[ALT_CLICK] = "1"
-
- var/mouseparams = list2params(paramslist)
- usr_client.Click(src, loc, null, mouseparams)
- . = TRUE
add_fingerprint(usr, "topic")