diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index a3fdf762c2..9deddd4b46 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -122,6 +122,7 @@
#define FIRE_PRIORITY_PROJECTILES 200
#define FIRE_PRIORITY_TICKER 200
#define FIRE_PRIORITY_ATMOS_ADJACENCY 300
+#define FIRE_PRIORITY_STATPANEL 390
#define FIRE_PRIORITY_CHAT 400
#define FIRE_PRIORITY_OVERLAYS 500
#define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost.
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 2bd477d98c..4723577fad 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -1223,7 +1223,7 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico
return "
"
//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
@@ -1231,4 +1231,5 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico
return icon2html(thing, target)
var/icon/I = getFlatIcon(thing)
- return icon2html(I, target)
+ return icon2html(I, target, sourceonly = sourceonly)
+
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index 4731940f41..cf7d4ba533 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -1,7 +1,8 @@
SUBSYSTEM_DEF(statpanels)
name = "Stat Panels"
wait = 4
- init_order = INIT_ORDER_STATPANELS //Really early so we can debug MC roundstart
+ init_order = INIT_ORDER_STATPANELS
+ priority = FIRE_PRIORITY_STATPANEL
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
var/list/currentrun = list()
var/encoded_global_data
@@ -9,13 +10,15 @@ SUBSYSTEM_DEF(statpanels)
var/list/cached_images = list()
/datum/controller/subsystem/statpanels/fire(resumed = FALSE)
- if(!resumed)
+ if (!resumed)
var/datum/map_config/cached = SSmapping.next_map_config
+ var/round_time = world.time - SSticker.round_start_time
var/list/global_data = list(
"Map: [SSmapping.config?.map_name || "Loading..."]",
cached ? "Next Map: [cached.map_name]" : null,
"Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]",
- "[SStime_track.stat_time_text]",
+ "Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]",
+ "Round Time: [round_time > MIDNIGHT_ROLLOVER ? "[round(round_time/MIDNIGHT_ROLLOVER)]:[worldtime2text()]" : worldtime2text()]",
"Station Time: [STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)]",
"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)]%)"
)
@@ -24,96 +27,169 @@ SUBSYSTEM_DEF(statpanels)
var/ETA = SSshuttle.emergency.getModeStr()
if(ETA)
global_data += "[ETA] [SSshuttle.emergency.getTimerStr()]"
+/* Please fix
+ if(SSvote.mode)
+ var/static/list/supported = list(PLURALITY_VOTING, APPROVAL_VOTING)
+ global_data += "Vote active!, There is currently a vote running. Question: [SSvote.question]"
+ if(!(SSvote.vote_system in supported))
+ global_data += ", The current vote system is not supported by statpanel rendering. Please vote manually by opening the vote popup using the action button or chat link."
+ return
+ global_data += "Time Left:, [round(SSvote.end_time - world.time)] seconds"
+
+ global_data += "Choices:"
+ for(var/i in 1 to SSvote.choice_statclicks.len)
+ var/choice = SSvote.choice_statclicks[i]
+ var/ivotedforthis = FALSE
+ if(usr.ckey)
+ switch(SSvote.vote_system)
+ if(APPROVAL_VOTING)
+ ivotedforthis = SSvote.voted[usr.ckey] && (i in SSvote.voted[usr.ckey])
+ if(PLURALITY_VOTING)
+ ivotedforthis = SSvote.voted[usr.ckey] == i
+
+ global_data += (ivotedforthis? "\[X\]" : "\[ \]", SSvote.choice_statclicks[choice])
+*/
+
encoded_global_data = url_encode(json_encode(global_data))
-
- var/list/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/ss in Master.subsystems)
- var/datum/controller/subsystem/sub_system = ss
- 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]")
- mc_data_encoded = url_encode(json_encode(mc_data))
src.currentrun = GLOB.clients.Copy()
-
+ mc_data_encoded = null
var/list/currentrun = src.currentrun
while(length(currentrun))
var/client/target = currentrun[length(currentrun)]
currentrun.len--
- var/ping_str = url_encode("Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)")
- var/other_str = url_encode(json_encode(target.mob.get_status_tab_items()))
- target << output("[encoded_global_data];[ping_str];[other_str]", "statbrowser:update")
+ if(!target.statbrowser_ready)
+ continue
+ if(target.stat_tab == "Status")
+ var/ping_str = url_encode("Ping: [round(target.lastping, 1)]ms (Average: [round(target.avgping, 1)]ms)")
+ var/other_str = url_encode(json_encode(target.mob.get_status_tab_items()))
+ target << output("[encoded_global_data];[ping_str];[other_str]", "statbrowser:update")
if(!target.holder)
target << output("", "statbrowser:remove_admin_tabs")
else
- var/turf/eye_turf = get_turf(target.eye)
- var/coord_entry = url_encode(COORD(eye_turf))
- target << output("[mc_data_encoded];[coord_entry];[url_encode(target.holder.href_token)]", "statbrowser:update_mc")
- var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry()
- target << output("[url_encode(json_encode(ahelp_tickets))];", "statbrowser:update_tickets")
- if(!length(GLOB.sdql2_queries))
+ if(!("MC" in target.panel_tabs) || !("Tickets" in target.panel_tabs))
+ target << output("[url_encode(target.holder.href_token)]", "statbrowser:add_admin_tabs")
+ if(target.stat_tab == "MC")
+ var/turf/eye_turf = get_turf(target.eye)
+ var/coord_entry = url_encode(COORD(eye_turf))
+ if(!mc_data_encoded)
+ generate_mc_data()
+ target << output("[mc_data_encoded];[coord_entry]", "statbrowser:update_mc")
+ if(target.stat_tab == "Tickets")
+ var/list/ahelp_tickets = GLOB.ahelp_tickets.stat_entry()
+ target << output("[url_encode(json_encode(ahelp_tickets))];", "statbrowser:update_tickets")
+ if(!length(GLOB.sdql2_queries) && ("SDQL2" in target.panel_tabs))
target << output("", "statbrowser:remove_sdql2")
- else
- var/list/sqdl2A = list()
- sqdl2A[++sqdl2A.len] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj))
- var/list/sqdl2B = list()
+ else if(length(GLOB.sdql2_queries) && (target.stat_tab == "SDQL2" || !("SDQL2" in target.panel_tabs)))
+ var/list/sdql2A = list()
+ sdql2A[++sdql2A.len] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj))
+ var/list/sdql2B = list()
for(var/i in GLOB.sdql2_queries)
var/datum/SDQL2_query/Q = i
- sqdl2B = Q.generate_stat()
- sqdl2A += sqdl2B
- target << output(url_encode(json_encode(sqdl2A)), "statbrowser:update_sdql2")
- var/list/proc_holders = target.mob.get_proc_holders()
- target.spell_tabs.Cut()
- for(var/phl in proc_holders)
- var/list/proc_holder_list = phl
- target.spell_tabs |= proc_holder_list[1]
- var/proc_holders_encoded = ""
- if(length(proc_holders))
- proc_holders_encoded = url_encode(json_encode(proc_holders))
- target << output("[url_encode(json_encode(target.spell_tabs))];[proc_holders_encoded]", "statbrowser:update_spells")
- if(target.mob?.listed_turf)
- var/mob/target_mob = target.mob
- if(!target_mob.TurfAdjacent(target_mob.listed_turf))
- target << output("", "statbrowser:remove_listedturf")
- target_mob.listed_turf = null
- else
- var/list/overrides = list()
- var/list/turfitems = list()
- for(var/img in target.images)
- var/image/target_image = img
- if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override)
- continue
- overrides += target_image.loc
- if(!(REF(target_mob.listed_turf) in cached_images))
- target << browse_rsc(getFlatIcon(target_mob.listed_turf, no_anim = TRUE), "[REF(target_mob.listed_turf)].png")
- cached_images += REF(target_mob.listed_turf)
- turfitems[++turfitems.len] = list("[target_mob.listed_turf]", REF(target_mob.listed_turf), "[REF(target_mob.listed_turf)].png")
- for(var/tc in target_mob.listed_turf)
- var/atom/movable/turf_content = tc
- 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
- if(length(turfitems) < 30) // only create images for the first 30 items on the turf, for performance reasons
- if(!(REF(turf_content) in cached_images))
- target << browse_rsc(getFlatIcon(turf_content, no_anim = TRUE), "[REF(turf_content)].png")
- cached_images += REF(turf_content)
- turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content), "[REF(turf_content)].png")
- else
- turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content))
- turfitems = url_encode(json_encode(turfitems))
- target << output("[turfitems];", "statbrowser:update_listedturf")
+ sdql2B = Q.generate_stat()
+ sdql2A += sdql2B
+ target << output(url_encode(json_encode(sdql2A)), "statbrowser:update_sdql2")
+ if(target.mob)
+ var/mob/M = target.mob
+ if((target.stat_tab in target.spell_tabs) || !length(target.spell_tabs) && (length(M.mob_spell_list) || length(M.mind?.spell_list)))
+ var/list/proc_holders = M.get_proc_holders()
+ target.spell_tabs.Cut()
+ for(var/phl in proc_holders)
+ var/list/proc_holder_list = phl
+ target.spell_tabs |= proc_holder_list[1]
+ var/proc_holders_encoded = ""
+ if(length(proc_holders))
+ proc_holders_encoded = url_encode(json_encode(proc_holders))
+ target << output("[url_encode(json_encode(target.spell_tabs))];[proc_holders_encoded]", "statbrowser:update_spells")
+ if(M?.listed_turf)
+ var/mob/target_mob = M
+ if(!target_mob.TurfAdjacent(target_mob.listed_turf))
+ target << output("", "statbrowser:remove_listedturf")
+ target_mob.listed_turf = null
+ else if(target.stat_tab == M?.listed_turf.name || !(M?.listed_turf.name in target.panel_tabs))
+ var/list/overrides = list()
+ var/list/turfitems = list()
+ for(var/img in target.images)
+ var/image/target_image = img
+ if(!target_image.loc || target_image.loc.loc != target_mob.listed_turf || !target_image.override)
+ continue
+ overrides += target_image.loc
+ turfitems[++turfitems.len] = list("[target_mob.listed_turf]", REF(target_mob.listed_turf), icon2html(target_mob.listed_turf, target, sourceonly=TRUE))
+ for(var/tc in target_mob.listed_turf)
+ var/atom/movable/turf_content = tc
+ 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
+ if(length(turfitems) < 30) // only create images for the first 30 items on the turf, for performance reasons
+ if(!(REF(turf_content) in cached_images))
+ cached_images += REF(turf_content)
+ turf_content.RegisterSignal(turf_content, COMSIG_PARENT_QDELETING, /atom/.proc/remove_from_cache) // we reset cache if anything in it gets deleted
+ if(ismob(turf_content) || length(turf_content.overlays) > 2)
+ turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content), costly_icon2html(turf_content, target, sourceonly=TRUE))
+ else
+ turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content), icon2html(turf_content, target, sourceonly=TRUE))
+ else
+ turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content))
+ else
+ turfitems[++turfitems.len] = list("[turf_content.name]", REF(turf_content))
+ turfitems = url_encode(json_encode(turfitems))
+ target << output("[turfitems];", "statbrowser:update_listedturf")
if(MC_TICK_CHECK)
return
+
+
+/datum/controller/subsystem/statpanels/proc/generate_mc_data()
+ var/list/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/ss in Master.subsystems)
+ var/datum/controller/subsystem/sub_system = ss
+ 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]")
+ mc_data_encoded = url_encode(json_encode(mc_data))
+
+/atom/proc/remove_from_cache()
+ SSstatpanels.cached_images -= REF(src)
+
+/// verbs that send information from the browser UI
+/client/verb/set_tab(tab as text|null)
+ set name = "Set Tab"
+ set hidden = TRUE
+
+ stat_tab = tab
+
+/client/verb/send_tabs(tabs as text|null)
+ set name = "Send Tabs"
+ set hidden = TRUE
+
+ panel_tabs |= tabs
+
+/client/verb/remove_tabs(tabs as text|null)
+ set name = "Remove Tabs"
+ set hidden = TRUE
+
+ panel_tabs -= tabs
+
+/client/verb/reset_tabs()
+ set name = "Reset Tabs"
+ set hidden = TRUE
+
+ panel_tabs = list()
+
+/client/verb/panel_ready()
+ set name = "Panel Ready"
+ set hidden = TRUE
+
+ statbrowser_ready = TRUE
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index d4f8547c34..755665583a 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -130,6 +130,15 @@
/// datum wrapper for client view
var/datum/view_data/view_size
+ /// our current tab
+ var/stat_tab
+
+ /// whether our browser is ready or not yet
+ var/statbrowser_ready = FALSE
+
+ /// list of all tabs
+ var/list/panel_tabs = list()
+
/// list of tabs containing spells and abilities
var/list/spell_tabs = list()
/// list of tabs containing verbs