Files
Bubberstation/code/controllers/subsystem/statpanel.dm
Roxy 3d49b8913e Update stat panel override to work with new map features (#3908)
## About The Pull Request

Pull `code/controllers/subsystem/statpanel.dm` fresh from tg and reapply
our changes, this will let us use the webmap and feedbacklink options in
maps.txt

## Why It's Good For The Game

Cool button

## Proof Of Testing
<details>
<summary>Screenshots/Videos</summary>


![image](https://github.com/user-attachments/assets/14278e78-5d2f-4eb8-a1cd-2252eaae52fa)

</details>

## Changelog

No player facing changes
2025-05-26 23:37:26 -04:00

279 lines
11 KiB
Plaintext

SUBSYSTEM_DEF(statpanels)
name = "Stat Panels"
wait = 4
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++
var/datum/map_config/cached = SSmap_vote.next_map_config
if(isnull(SSmapping.current_map))
global_data = list("Loading")
else if(SSmapping.current_map.feedback_link)
global_data = list(list("Map: [SSmapping.current_map.map_name]", " (Feedback)", "action=openLink&link=[SSmapping.current_map.feedback_link]"))
else
global_data = list("Map: [SSmapping.current_map?.map_name]")
if(SSmapping.current_map?.mapping_url)
global_data += list(list("same_line", " | (View in Browser)", "action=openWebMap"))
if(cached)
global_data += "Next Map: [cached.map_name]"
// BUBBER EDIT ADDITION BEGIN - Extra stat panel info
var/real_round_time = world.timeofday - SSticker.real_round_start_time
var/active_players = get_active_player_count(alive_check = FALSE, afk_check = TRUE, human_check = FALSE) //This is a list of all active players, including players who are dead
var/observing_players = length(GLOB.current_observers_list) //This is a list of all players that started as an observer-- dead and lobby players are not included.
var/current_date = "[time2text(world.realtime, "DDD Month DD")], [CURRENT_STATION_YEAR]"
// BUBBER EDIT ADDITION END - Extra stat panel info
global_data += list(
"Round ID: [GLOB.round_id ? GLOB.round_id : "NULL"]",
"Connected: [GLOB.clients.len] | Active: [active_players]/[CONFIG_GET(number/hard_popcap)] | Observing: [observing_players]", // BUBBER EDIT ADDITION - Extra stat panel info
"OOC: [GLOB.ooc_allowed ? "Enabled" : "Disabled"]", // BUBBER EDIT ADDITION - Extra stat panel info
" ", // BUBBER EDIT ADDITION - Extra stat panel info
"Storyteller: [SSgamemode.storyteller ? SSgamemode.storyteller.name : "N/A"]", // BUBBER EDIT ADDITION - Extra stat panel info
"Station Time: [station_time_timestamp(format = "hh:mm")], [current_date]", // BUBBER EDIT CHANGE - Extra stat panel info - ORIGINAL: "Station Time: [station_time_timestamp()]"
"Round Time: [time2text(real_round_time, "hh:mm:ss", 0)]", // BUBBER EDIT CHANGE - Extra stat panel info - ORIGINAL: "Round Time: [ROUND_TIME()]"
"Server Time: [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss", world.timezone)]",
"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(SSshuttle.emergency)
var/ETA = SSshuttle.emergency.getModeStr()
if(ETA)
global_data += "[ETA] [SSshuttle.emergency.getTimerStr()]"
if(SSticker.reboot_timer)
var/reboot_time = timeleft(SSticker.reboot_timer)
if(reboot_time)
global_data += "Reboot: [DisplayTimeText(reboot_time, 1)]"
// admin must have delayed round end
else if(SSticker.ready_for_reboot)
global_data += "Reboot: DELAYED"
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.toggles & 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.read_preference(/datum/preference/toggle/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 action panels of the stat panel
var/update_actions = FALSE
// We're on a spell tab, update the tab so we can see cooldowns progressing and such
if(target.stat_tab in target.spell_tabs)
update_actions = TRUE
// We're not on a spell tab per se, but we have cooldown actions, and we've yet to
// set up our spell tabs at all
if(!length(target.spell_tabs) && locate(/datum/action/cooldown) in target_mob.actions)
update_actions = TRUE
if(update_actions && num_fires % default_wait == 0)
set_action_tabs(target, target_mob)
if(MC_TICK_CHECK)
return
/*
* send_message for the stat panel can be sent 1 of 4 things:
* 1- A string entry, to show up as plain text.
* 2- An empty string (""), which will translate to a new line, to for a break between lines.
* 3- a list, in which the first entry is plain text, the second entry is highlighted text, and the third entry is a link
* that clicking the second entry will take you to.
* 4- a list with "same_line" as the first entry, which will automatically put it on the line above it,
* with the second/third entry matching #3 (text & url), allowing you to have 2 clickable links on one line.
*/
/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)
var/datum/interview_manager/m = GLOB.interviews
// get open interview count
var/dc = 0
for (var/ckey in m.open_interviews)
var/datum/interview/current_interview = m.open_interviews[ckey]
if (current_interview && !current_interview.owner)
dc++
var/stat_string = "([m.open_interviews.len - dc] online / [dc] disconnected)"
// Prepare each queued interview
var/list/queued = list()
for (var/datum/interview/queued_interview in m.interview_queue)
queued += list(list(
"ref" = REF(queued_interview),
"status" = "\[[queued_interview.pos_in_queue]\]: [queued_interview.owner_ckey][!queued_interview.owner ? " (DC)": ""] \[INT-[queued_interview.id]\]"
))
var/list/data = list(
"status" = list(
"Active:" = "[m.open_interviews.len] [stat_string]",
"Queued:" = "[m.interview_queue.len]",
"Closed:" = "[m.closed_interviews.len]"),
"interviews" = queued
)
// Push update
target.stat_panel.send_message("update_interviews", data)
/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)
/// Set up the various action tabs.
/datum/controller/subsystem/statpanels/proc/set_action_tabs(client/target, mob/target_mob)
var/list/actions = target_mob.get_actions_for_statpanel()
target.spell_tabs.Cut()
for(var/action_data in actions)
target.spell_tabs |= action_data[1]
target.stat_panel.send_message("update_spells", list(spell_tabs = target.spell_tabs, actions = actions))
/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(), text_ref(GLOB)),
list("[config]:", config.stat_entry(), text_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(), text_ref(Master)),
list("Failsafe Controller:", Failsafe.stat_entry(), text_ref(Failsafe)),
list("","")
)
#if defined(MC_TAB_TRACY_INFO) || defined(SPACEMAN_DMM)
var/static/tracy_dll
var/static/tracy_present
if(isnull(tracy_dll))
tracy_dll = TRACY_DLL_PATH
tracy_present = fexists(tracy_dll)
if(tracy_present)
if(Tracy.enabled)
mc_data.Insert(2, list(list("byond-tracy:", "Active (reason: [Tracy.init_reason || "N/A"])")))
else if(Tracy.error)
mc_data.Insert(2, list(list("byond-tracy:", "Errored ([Tracy.error])")))
else if(fexists(TRACY_ENABLE_PATH))
mc_data.Insert(2, list(list("byond-tracy:", "Queued for next round")))
else
mc_data.Insert(2, list(list("byond-tracy:", "Inactive")))
else
mc_data.Insert(2, list(list("byond-tracy:", "[tracy_dll] not present")))
#endif
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(), text_ref(sub_system))
mc_data[++mc_data.len] = list("Camera Net", "Cameras: [GLOB.cameranet.cameras.len] | Chunks: [GLOB.cameranet.chunks.len]", text_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 actions
var/update_actions = FALSE
if(target.stat_tab in target.spell_tabs)
update_actions = TRUE
if(!length(target.spell_tabs) && locate(/datum/action/cooldown) in target_mob.actions)
update_actions = TRUE
if(update_actions)
set_action_tabs(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