mirror of
https://github.com/Citadel-Station-13/Citadel-Station-13-RP.git
synced 2026-08-22 00:26:36 +01:00
343 lines
9.3 KiB
Plaintext
343 lines
9.3 KiB
Plaintext
/**
|
|
* citadel RP stat system
|
|
*
|
|
* we use a hybrid approach
|
|
* most stat goes to the statpanel, which is default for users;
|
|
* this is a browser system that uses .js and is relatively fast for serialization/whatnot
|
|
* the listed turf, however, gets sent to native Stat() so it supports native mouse
|
|
* handling among other things.
|
|
*
|
|
* todo: use /datum/tgui_window, have a JS bootstrap instead of the current system.
|
|
*/
|
|
/datum/client_statpanel
|
|
var/client/client
|
|
var/window_id
|
|
// var/datum/tgui_window/window
|
|
|
|
//* Browser Stat *//
|
|
/// is the statpanel ready?
|
|
var/ready = FALSE
|
|
/// current tab
|
|
var/current_tab
|
|
/// currently loaded tabs
|
|
var/list/current_tabs
|
|
/// i don't know what this does
|
|
var/list/spell_last
|
|
|
|
//* BYOND Stat *//
|
|
/// the turf being listed on byond stat
|
|
var/turf/byond_stat_turf
|
|
/// are we currently tabbed to byond stat?
|
|
var/byond_stat_active = FALSE
|
|
/// did we get autoswitched to byond stat when the player alt clicked?
|
|
/// determines if we switch back when the turf is no longer listed.
|
|
var/byond_stat_ephemeral = FALSE
|
|
|
|
/datum/client_statpanel/New(client/client, window_id)
|
|
src.client = client
|
|
src.window_id = window_id
|
|
// src.window = new(client, window_id)
|
|
// src.window.subscribe(src, PROC_REF(on_message))
|
|
|
|
/datum/client_statpanel/Destroy()
|
|
client << output(null, "statbrowser:byond_cleanup")
|
|
client = null
|
|
return ..()
|
|
|
|
/datum/client_statpanel/proc/initialize()
|
|
if(!client.initialized)
|
|
// todo: this should be a timer, but current MC doesn't really support that until we have
|
|
// MC init stages
|
|
spawn(1 SECONDS)
|
|
UNTIL(!client || client.initialized)
|
|
if(!client)
|
|
return
|
|
boot()
|
|
else
|
|
spawn(0)
|
|
boot()
|
|
|
|
/datum/client_statpanel/proc/boot()
|
|
PRIVATE_PROC(TRUE)
|
|
// loads statbrowser if it isn't there
|
|
client << browse(file('html/statbrowser.html'), "window=[window_id]")
|
|
// if it is there and we can't tell because byond is byond, send it a signal to reload
|
|
client << output(null, "statbrowser:byond_reconnect")
|
|
// check for it incase it breaks
|
|
addtimer(CALLBACK(src, PROC_REF(check_initialized)), 5 SECONDS)
|
|
|
|
/datum/client_statpanel/proc/check_initialized()
|
|
if(ready)
|
|
return
|
|
to_chat(client, SPAN_USERDANGER("Statpanel failed to load, click <a href='?src=[REF(src)];statpanel=reload'>here</a> to reload the panel "))
|
|
|
|
//* Internal API *//
|
|
|
|
/**
|
|
* Resends the client's current verbs.
|
|
*
|
|
* @params
|
|
* * reset - if TRUE, we will obliterate all current tabs and entirely redraw the browser. Otherwise, we
|
|
* only change mutated verbs.
|
|
*/
|
|
/datum/client_statpanel/proc/init_verbs(reset = FALSE)
|
|
PRIVATE_PROC(TRUE)
|
|
var/list/verblist = list()
|
|
var/list/verbstoprocess = client.verbs.Copy()
|
|
if(client.mob)
|
|
verbstoprocess += client.mob.verbs
|
|
for(var/atom/movable/AM as anything in client.mob)
|
|
verbstoprocess += AM.verbs
|
|
for(var/thing in verbstoprocess)
|
|
var/procpath/verb_to_init = thing
|
|
if(verb_to_init.hidden)
|
|
continue
|
|
if(!istext(verb_to_init.category) || !verb_to_init.name)
|
|
continue
|
|
if(verb_to_init.name[1] == ".")
|
|
continue
|
|
LAZYINITLIST(verblist[verb_to_init.category])
|
|
verblist[verb_to_init.category] |= verb_to_init.name
|
|
client << output("[url_encode(json_encode(verblist))];[reset]", "statbrowser:byond_init_verbs")
|
|
|
|
//* External API *//
|
|
|
|
/datum/client_statpanel/proc/request_verb_update(reset)
|
|
if(!ready)
|
|
return
|
|
init_verbs(reset)
|
|
|
|
/**
|
|
* resets state and reloads everything
|
|
*/
|
|
/datum/client_statpanel/proc/request_reload()
|
|
current_tabs = list()
|
|
current_tab = null
|
|
spell_last = list()
|
|
request_verb_update(TRUE)
|
|
|
|
/**
|
|
* called to set/dispose admin token
|
|
*/
|
|
/datum/client_statpanel/proc/set_admin_token(token)
|
|
if(!token)
|
|
client << output(null, "statbrowser:byond_dispose_token")
|
|
return
|
|
client << output(token, "statbrowser:byond_grant_token")
|
|
|
|
/**
|
|
* sent from a client verb called by statbrowser when it's ready
|
|
*/
|
|
/datum/client_statpanel/proc/ready_received()
|
|
ready = TRUE
|
|
client << output(null, "statbrowser:byond_init")
|
|
request_reload()
|
|
|
|
//! verb hooks - js stat
|
|
// todo: replace with tgui window messaging
|
|
|
|
/client/verb/hook_statpanel_ready()
|
|
set name = ".statpanel_ready"
|
|
set hidden = TRUE
|
|
set instant = TRUE
|
|
|
|
tgui_stat.ready_received()
|
|
|
|
/client/verb/hook_statpanel_add_tab(tab as text)
|
|
set name = ".statpanel_tab_add"
|
|
set hidden = TRUE
|
|
set instant = TRUE
|
|
|
|
if(length(tgui_stat.current_tabs) > 50)
|
|
return // bail
|
|
tgui_stat.current_tabs |= tab
|
|
if(!tgui_stat.current_tab)
|
|
tgui_stat.current_tab = tgui_stat.current_tabs[1]
|
|
|
|
/client/verb/hook_statpanel_remove_tab(tab as text)
|
|
set name = ".statpanel_tab_remove"
|
|
set hidden = TRUE
|
|
set instant = TRUE
|
|
|
|
tgui_stat.current_tabs -= tab
|
|
|
|
/client/verb/hook_statpanel_wipe_tabs()
|
|
set name = ".statpanel_tab_reset"
|
|
set hidden = TRUE
|
|
set instant = TRUE
|
|
|
|
tgui_stat.current_tabs = list()
|
|
|
|
/client/verb/hook_statpanel_set_tab(tab as text)
|
|
set name = ".statpanel_tab"
|
|
set hidden = TRUE
|
|
set instant = TRUE
|
|
|
|
tgui_stat.current_tab = tab
|
|
|
|
/**
|
|
* This cleanly and gracefully attempts to go to a specific tab via verbs, for the hyperspecific purpose of interacting with the statpanel from other HTML UI
|
|
*/
|
|
/client/verb/hook_statpanel_goto_tab(tab as text)
|
|
set name = ".statpanel_goto_tab"
|
|
set hidden = TRUE
|
|
set instant = TRUE
|
|
src << output(tab, "statbrowser:change_tab")
|
|
|
|
//* verb hooks - tab switcher *//
|
|
|
|
/client/verb/hook_statswitcher_set_tab(tab as text)
|
|
set name = ".statswitcher"
|
|
set hidden = TRUE
|
|
set instant = TRUE
|
|
|
|
tgui_stat.byond_stat_active = (tab == "stat_pane_byond")
|
|
|
|
// todo: legacy stuff below
|
|
|
|
/// for legacy shit like rigsuits that didn't get the hint about not using verbs
|
|
/client/proc/queue_legacy_verb_update()
|
|
if(HAS_TRAIT(src, "VERB_UPDATE_QUEUED"))
|
|
return
|
|
ADD_TRAIT(src, "VERB_UPDATE_QUEUED", "FUCK")
|
|
addtimer(CALLBACK(src, PROC_REF(legacy_verb_update)), 1 SECONDS)
|
|
|
|
/// -_-
|
|
/client/proc/legacy_verb_update()
|
|
REMOVE_TRAIT(src, "VERB_UPDATE_QUEUED", "FUCK")
|
|
tgui_stat?.request_verb_update()
|
|
|
|
/**
|
|
* returns TRUE if the tab should exist and we are on the tab
|
|
*
|
|
* @params
|
|
* - status: use to set if the panel should exist
|
|
*/
|
|
/client/proc/statpanel_tab(tab, status)
|
|
. = (tgui_stat.current_tab == tab)
|
|
if(isnull(status))
|
|
return
|
|
if(tab in tgui_stat.current_tabs)
|
|
if(!status)
|
|
src << output(url_encode(tab), "statbrowser:byond_remove_tab")
|
|
return FALSE // removing
|
|
else
|
|
if(status)
|
|
src << output(url_encode(tab), "statbrowser:byond_add_tab")
|
|
return FALSE // don't add yet, this is unfortunate but we'll add one tick of update delay to let it add first
|
|
|
|
/client/proc/list_turf(turf/T)
|
|
if(tgui_stat.byond_stat_turf)
|
|
unlist_turf()
|
|
if(!T || !list_turf_check(T))
|
|
return
|
|
tgui_stat.byond_stat_turf = T
|
|
// using byond atm
|
|
if(!tgui_stat.byond_stat_active)
|
|
tgui_stat.byond_stat_ephemeral = TRUE
|
|
tgui_stat.byond_stat_active = TRUE
|
|
winset(src, SKIN_TAB_ID_STAT, "current-tab=[SKIN_PANE_ID_BYONDSTAT]")
|
|
|
|
|
|
/client/proc/unlist_turf()
|
|
// using byond atm
|
|
if(tgui_stat.byond_stat_ephemeral)
|
|
tgui_stat.byond_stat_ephemeral = FALSE
|
|
tgui_stat.byond_stat_active = FALSE
|
|
winset(src, SKIN_TAB_ID_STAT, "current-tab=[SKIN_PANE_ID_BROWSERSTAT]")
|
|
|
|
tgui_stat.byond_stat_turf = null
|
|
|
|
/client/proc/list_turf_check(turf/T)
|
|
return mob.snowflake_ai_vision_adjacency(T)
|
|
|
|
/**
|
|
* must return list(name, icon, ref).
|
|
*/
|
|
/client/proc/statpanel_encode_atom(atom/movable/AM)
|
|
RETURN_TYPE(/list)
|
|
if(AM.mouse_opacity == MOUSE_OPACITY_TRANSPARENT)
|
|
return
|
|
if(AM.invisibility > mob.see_invisible)
|
|
return
|
|
return list(
|
|
"[AM.name]",
|
|
REF(AM),
|
|
ismob(AM) ||(length(AM.overlays) > 2)? costly_icon2html(AM, src, sourceonly = TRUE) : icon2html(AM, src, sourceonly = TRUE)
|
|
)
|
|
|
|
/client/proc/__stat_hook_turf_enter(datum/source, atom/movable/AM)
|
|
var/list/got = statpanel_encode_atom(AM)
|
|
if(!got)
|
|
return
|
|
src << output("[url_encode(json_encode(got))]", "statbrowser:byond_turf_add")
|
|
|
|
/client/proc/__stat_hook_turf_exit(datum/source, atom/movable/AM)
|
|
src << output("[url_encode(REF(AM))]", "statbrowser:byond_turf_del")
|
|
|
|
/**
|
|
* the big, bad, Citadel Station in house stat proc.
|
|
* returns a list of "panel name" : list(entry, ...)
|
|
* where entry is either:
|
|
* 1. a text string
|
|
* 2. a text string associated to a value
|
|
*/
|
|
/datum/proc/statpanel_data(client/C)
|
|
return list()
|
|
|
|
/client/statpanel_data(client/C)
|
|
var/datum/D = statobj
|
|
return istype(D)? D.statpanel_data(C) : list()
|
|
|
|
/**
|
|
* acts on a statpanel action / press
|
|
*
|
|
* @params
|
|
* * C - who pressed us
|
|
* * action - (optional) action; in some cases there is none.
|
|
* * auth - successful admin authentication. obviously, non admins will always fail this.
|
|
*/
|
|
/datum/proc/statpanel_click(client/C, action, auth)
|
|
return
|
|
|
|
/**
|
|
* routes actions from statpanel
|
|
*
|
|
* todo: do we move this to /datum/statpanel?
|
|
*/
|
|
/client/proc/_statpanel_act(action, list/params)
|
|
switch(action)
|
|
if("reload")
|
|
tgui_stat.request_reload()
|
|
return
|
|
if("ready")
|
|
tgui_stat.ready_received()
|
|
return
|
|
if("stat_click")
|
|
var/datum/D = locate(params["ref"])
|
|
if(!istype(D))
|
|
return
|
|
// todo: admin token implementation
|
|
D.statpanel_click(src, null, FALSE)
|
|
return
|
|
if("atom_click")
|
|
var/atom/A = locate(params["ref"])
|
|
if(!istype(A))
|
|
return
|
|
// assert: it's within a *VERY* generous range
|
|
if(get_dist(A, mob) > 5)
|
|
return
|
|
// todo: admin token implementation
|
|
var/clickparams = params["params"]
|
|
mob.click_on(A, null, null, clickparams, CLICKCHAIN_FROM_HREF)
|
|
return
|
|
// todo: mousedrag event
|
|
|
|
/**
|
|
* grabs statpanel data to send on tick
|
|
*/
|
|
/client/proc/_statpanel_data()
|
|
. = statpanel_data(src)
|
|
if(!islist(.))
|
|
CRASH("[.] was not list.")
|