mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-31 07:57:47 +01:00
Browser/TGUI Stat Panels (#24065)
* initial commit (broken)
* load the html
* fix this
* Fix various issues with browser statpanel
* Fix Alt Clicking opening up a window and Add back some object verbs to the browser stat panel
* Optimize stat panel and fix guardian verbs
* Restyles Stat Panel, Adds Subpanel Sub-Categories
* Use better layout for verbs in stat panel
* Updates statpanel verb widths to be more dynamic at higher screen resolutions.
* Adjust stat panel grid item widths and breakpoints
* refactors statpanel to use tgui API
* CI moment
* more CI
* this stupid thing
* Apply suggestions from code review
Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
* Update code/modules/client/client_procs.dm
* ci fix
* emergency mc debug view
* temp revert some code change suggestions due to massive runtiming
* proper atom click topic implementation
* optimise
* mob clicking in stat panels work
* yeet spell tab thingy
* yeet simple stat panel pref
* allow insertion of html into MC tab content
* tidy up status tab
* Apply suggestions from code review
* fix this
* fix CI
* oops
* fix index runtime
* fixes MC tab showing up for mentors, fixes runtime
* safeties!
* Return of theme support
* more fixes
* fix view range pref, tidy prefs tab
* Remove old stat panel from themes
* fixes
* make sure verbs don't go missing
* fix ooc/looc breaking
* Revert "make sure verbs don't go missing"
This reverts commit 7d07ad45ed.
* fix this properly
* fix stat panel hitting rate limiters
* fix borg status tab
* Object Window Niceties
* Adds file cycling for icon2base64
* optimizes icon2html() for icon files known to be in the rsc at compile time
* CI moment
* remove dupe emergency shuttle timers
* more robust verb updates
* statpanel tweaks
* zip archived changelog to avoid search results
* optimise
* fix mentor chat wonkyness when disabled
* debug log moment
* i am very smart
* reintroduce this because it was needed
* better time listings
* less jank
* stops telling admins they arent mentors
* returns MC tab pref for admins
* Update code/controllers/subsystem/SSstatpanel.dm
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
* lewcc
* OD typemaker prep
---------
Co-authored-by: GDN <96800819+GDNgit@users.noreply.github.com>
Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com>
Co-authored-by: Aylong <alexanderkitsa@gmail.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
This commit is contained in:
co-authored by
GDN
Burzah
Luc
Aylong
parent
19a3f768c8
commit
91660824fa
@@ -87,7 +87,7 @@ SUBSYSTEM_DEF(debugview)
|
||||
if((SS.flags & SS_NO_FIRE) || !SS.can_fire)
|
||||
continue
|
||||
|
||||
html += "[SS.state_colour()]\[[SS.state_letter()]][SS.ss_id]</font>\t[round(SS.cost, 1)]ms | [round(SS.tick_usage, 1)]% | [SS.get_stat_details()]"
|
||||
html += "[SS.state_colour()]\[[SS.state_letter()]][SS.ss_id]</font>\t[round(SS.cost, 1)]ms | [round(SS.tick_usage, 1)]% [SS.get_stat_details() ? "| [SS.get_stat_details()] " : ""]| <a href=?_src_=vars;Vars=[SS.UID()]>VV Edit</a>"
|
||||
|
||||
popup.set_content(html.Join("<br>"))
|
||||
popup.open(FALSE)
|
||||
|
||||
@@ -14,8 +14,9 @@ SUBSYSTEM_DEF(ping)
|
||||
/// List used each time SS fires to track which clients have been processed so far
|
||||
var/list/current_run = list()
|
||||
|
||||
/datum/controller/subsystem/ping/stat_entry()
|
||||
..("P:[length(GLOB.clients)]")
|
||||
/datum/controller/subsystem/ping/stat_entry(msg)
|
||||
msg = "P:[length(GLOB.clients)]"
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/ping/fire(resumed = FALSE)
|
||||
// Prepare the new batch of clients
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
SUBSYSTEM_DEF(statpanels)
|
||||
name = "Stat Panels"
|
||||
wait = 4
|
||||
init_order = INIT_ORDER_STATPANELS
|
||||
priority = FIRE_PRIORITY_STATPANEL
|
||||
flags = SS_NO_INIT
|
||||
runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
|
||||
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 = 6
|
||||
/// 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/cached = SSmapping.next_map
|
||||
var/round_time = world.time - SSticker.time_game_started
|
||||
global_data = list(
|
||||
list("Map:", SSmapping.map_datum?.fluff_name ? SSmapping.map_datum?.fluff_name : "Loading..."),
|
||||
cached ? list("Next Map:", "[cached.fluff_name]") : null,
|
||||
list("Round ID:", "[GLOB.round_id ? GLOB.round_id : "NULL"]"),
|
||||
list("Server Time:", "[time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")]"),
|
||||
list("[SSticker.time_game_started ? "Round Time" : "Lobby Time"]:", "[round_time > MIDNIGHT_ROLLOVER ? "[round(round_time / MIDNIGHT_ROLLOVER)]:[roundtime2text()]" : roundtime2text()]"),
|
||||
list("Station Time:", "[station_time_timestamp()]"),
|
||||
list("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)]%)"),
|
||||
list("Players Connected:", "[length(GLOB.clients)]"),
|
||||
list("Players in Lobby:", "[length(GLOB.new_player_mobs)]")
|
||||
)
|
||||
|
||||
if(SSshuttle.emergency)
|
||||
var/eta = SSshuttle.emergency.getModeStr()
|
||||
if(eta)
|
||||
global_data[++global_data.len] = list("[eta]", "[SSshuttle.emergency.getTimerStr()]")
|
||||
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.prefs?.toggles2 & PREFTOGGLE_2_MC_TAB))
|
||||
target.stat_panel.send_message("remove_mc_tab", !target.holder ? TRUE : FALSE)
|
||||
|
||||
else if(target.mob && check_rights(R_DEBUG | R_VIEWRUNTIMES, FALSE, target.mob) && target.prefs?.toggles2 & PREFTOGGLE_2_MC_TAB)
|
||||
if(!("MC" in target.panel_tabs))
|
||||
target.stat_panel.send_message("add_mc_tab", target.holder.href_token)
|
||||
|
||||
if(target.stat_tab == "MC" && ((num_fires % mc_wait == 0)))
|
||||
set_MC_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,
|
||||
mob_specific_data = 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_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]", turf_item.UID(), 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_PARENT_QDELETING, TYPE_PROC_REF(/datum/object_window_info, viewing_atom_deleted), override = TRUE) // 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:", Master.formatcpu(world.cpu)),
|
||||
list("Map CPU:", Master.formatcpu(world.map_cpu)),
|
||||
list("Instances:", "[num2text(length(world.contents), 10)]"),
|
||||
list("World Time:", "[world.time]"),
|
||||
list("Server Time:", time_stamp()),
|
||||
list("Globals:", GLOB.stat_entry(), "[GLOB.UID()]"),
|
||||
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(), "[Master.UID()]"),
|
||||
list("Failsafe Controller:", Failsafe.stat_entry(), "[Failsafe.UID()]"),
|
||||
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(), "[sub_system.UID()]")
|
||||
mc_data[++mc_data.len] = list("Camera Net", "Cameras: [length(GLOB.cameranet.cameras)] | Chunks: [length(GLOB.cameranet.chunks)]", "[GLOB.cameranet.UID()]")
|
||||
|
||||
///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
|
||||
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
|
||||
|
||||
/// Stat panel window declaration, we don't usually allow this but tgui windows/panels are exceptions
|
||||
/* check_grep:ignore */ /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(delta_time)
|
||||
// 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()
|
||||
@@ -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(length(current_run))
|
||||
var/datum/thing = current_run[length(current_run)]
|
||||
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--
|
||||
Reference in New Issue
Block a user