From b6c0db48ff310c7b78fbb9cb8587b364dead0778 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:28:09 -0700 Subject: [PATCH] Client Initializations Updates (#6822) --- citadel.dme | 6 +- code/__DEFINES/interface/skin.dm | 2 +- code/controllers/subsystem/server_maint.dm | 4 + code/controllers/subsystem/statpanel.dm | 10 +- code/game/click/click.dm | 2 +- code/game/world.dm | 15 +- code/modules/asset_cache/packs/spritesheet.dm | 6 + code/modules/client/client-stat.dm | 21 + code/modules/client/client.dm | 28 +- code/modules/client/client_procs.dm | 106 ++--- .../{statpanel.dm => client_statpanel.dm} | 424 ++++++++---------- code/modules/client/cutscene.dm | 6 + code/modules/client/data/client_data.dm | 3 +- code/modules/client/data/player_data.dm | 9 +- .../game_preference_toggle.dm | 2 +- .../game_preferences/game_preferences.dm | 40 +- code/modules/client/verbs/fix_statpanel.dm | 7 + code/modules/mob/login.dm | 2 +- code/modules/mob/{client.dm => mob-client.dm} | 2 +- code/modules/spells/spells.dm | 12 +- code/modules/tgui/tgui.dm | 39 +- code/modules/tgui/tgui_window.dm | 13 + code/modules/tgui_panel/external.dm | 4 +- code/modules/tgui_panel/tgui_panel.dm | 28 +- code/modules/tooltip/tooltip.dm | 37 +- 25 files changed, 450 insertions(+), 378 deletions(-) create mode 100644 code/modules/client/client-stat.dm rename code/modules/client/{statpanel.dm => client_statpanel.dm} (55%) create mode 100644 code/modules/client/verbs/fix_statpanel.dm rename code/modules/mob/{client.dm => mob-client.dm} (94%) diff --git a/citadel.dme b/citadel.dme index 79c90a8763f..54792161116 100644 --- a/citadel.dme +++ b/citadel.dme @@ -2472,14 +2472,15 @@ #include "code\modules\catalogue\cataloguer_visuals.dm" #include "code\modules\catalogue\cataloguer_vr.dm" #include "code\modules\client\client-admin.dm" +#include "code\modules\client\client-stat.dm" #include "code\modules\client\client.dm" #include "code\modules\client\client_procs.dm" +#include "code\modules\client\client_statpanel.dm" #include "code\modules\client\connection.dm" #include "code\modules\client\cutscene.dm" #include "code\modules\client\perspective.dm" #include "code\modules\client\security.dm" #include "code\modules\client\spam_prevention.dm" -#include "code\modules\client\statpanel.dm" #include "code\modules\client\throttling.dm" #include "code\modules\client\ui_style.dm" #include "code\modules\client\viewport.dm" @@ -2498,6 +2499,7 @@ #include "code\modules\client\onboarding\age_verification.dm" #include "code\modules\client\onboarding\panic_bunker.dm" #include "code\modules\client\onboarding\security_checks.dm" +#include "code\modules\client\verbs\fix_statpanel.dm" #include "code\modules\client\verbs\minimap.dm" #include "code\modules\client\verbs\ooc.dm" #include "code\modules\client\verbs\system.dm" @@ -3514,7 +3516,6 @@ #include "code\modules\mining\tools\vertibore.dm" #include "code\modules\mob\action_feedback.dm" #include "code\modules\mob\animations.dm" -#include "code\modules\mob\client.dm" #include "code\modules\mob\death.dm" #include "code\modules\mob\emote.dm" #include "code\modules\mob\floating_message.dm" @@ -3527,6 +3528,7 @@ #include "code\modules\mob\life.dm" #include "code\modules\mob\login.dm" #include "code\modules\mob\logout.dm" +#include "code\modules\mob\mob-client.dm" #include "code\modules\mob\mob-damage.dm" #include "code\modules\mob\mob-defense.dm" #include "code\modules\mob\mob-iff.dm" diff --git a/code/__DEFINES/interface/skin.dm b/code/__DEFINES/interface/skin.dm index f4c2eeea2f8..4ba912e61de 100644 --- a/code/__DEFINES/interface/skin.dm +++ b/code/__DEFINES/interface/skin.dm @@ -22,7 +22,7 @@ //* Chat System #define SKIN_OUTPUT_ID_LEGACY_CHAT "output" -#define SKIN_BROWSER_ID_CHAT "outputbrowser" +#define SKIN_BROWSER_ID_CHAT "browseroutput" //* Cutscene System #define SKIN_BROWSER_ID_CUTSCENE "cutscenebrowser" diff --git a/code/controllers/subsystem/server_maint.dm b/code/controllers/subsystem/server_maint.dm index 742a14393e2..c8fe6fa4ba7 100644 --- a/code/controllers/subsystem/server_maint.dm +++ b/code/controllers/subsystem/server_maint.dm @@ -96,3 +96,7 @@ SUBSYSTEM_DEF(server_maint) world.update_hub_visibility(TRUE) #undef PING_BUFFER_TIME + +// todo: SShub_manager +/datum/controller/subsystem/server_maint/proc/queue_hub_update() + addtimer(CALLBACK(src, PROC_REF(UpdateHubStatus)), 0) diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm index 300da509d8b..5c5ea3e4dfa 100644 --- a/code/controllers/subsystem/statpanel.dm +++ b/code/controllers/subsystem/statpanel.dm @@ -43,15 +43,15 @@ SUBSYSTEM_DEF(statpanels) // grab victim var/client/player = currentrun[length(currentrun)] --currentrun.len - // check listed turf - if(player.statpanel_turf && !player.list_turf_check(player.statpanel_turf)) - player.unlist_turf() // check if we're even on the js one - if(player.statpanel_on_byond) + if(player.tgui_stat.byond_stat_active) continue // check if ready - if(!player.statpanel_ready) + if(!player.tgui_stat.ready) continue + // check listed turf + if(player.tgui_stat.byond_stat_turf && !player.list_turf_check(player.tgui_stat.byond_stat_turf)) + player.unlist_turf() // are they an admin? var/is_admin = !!player.holder // grab their mob data diff --git a/code/game/click/click.dm b/code/game/click/click.dm index b7770f36037..540d3c6a68a 100644 --- a/code/game/click/click.dm +++ b/code/game/click/click.dm @@ -282,7 +282,7 @@ return if(!client) return - if(T == client.statpanel_turf) + if(T == client.tgui_stat?.byond_stat_turf) client.unlist_turf() return client.list_turf(T) diff --git a/code/game/world.dm b/code/game/world.dm index 799cb479cf9..05ff88358b2 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -75,13 +75,6 @@ GLOBAL_LIST(topic_status_cache) SSdbcore.SetRoundID() SetupLogs() -// #ifndef USE_CUSTOM_ERROR_HANDLER -// world.log = file("[GLOB.log_directory]/dd.log") -// #else -// if (TgsAvailable()) -// world.log = file("[GLOB.log_directory]/dd.log") //not all runtimes trigger world/Error, so this is the only way to ensure we can see all of them. -// #endif - // shunt redirected world log from Master's init back into world log proper, now that logging has been set up. shunt_redirected_log() @@ -525,6 +518,9 @@ GLOBAL_LIST(topic_status_cache) // we already know, we don't care if(global.world_log_redirected) return + // we're not running in tgs, do not redirect world.log + if(!world.params["server_service_version"]) + return global.world_log_redirected = TRUE if(fexists("data/logs/world_init_temporary.log")) fdel("data/logs/world_init_temporary.log") @@ -538,8 +534,13 @@ GLOBAL_LIST(topic_status_cache) /world/proc/shunt_redirected_log() // if we're unit testing do not ever redirect world.log or the test won't show output. #ifndef UNIT_TESTS + // we're not running in tgs, do not redirect world.log + if(!world.params["server_service_version"]) + return + // if logs are to be redirected, send it to that folder if(!(OVERRIDE_LOG_DIRECTORY_PARAMETER in params)) world.log = file("[GLOB.log_directory]/dd.log") + // handle pre-init log redirection if(!world_log_redirected) log_world("World log shunt never happened. Something has gone wrong!") return diff --git a/code/modules/asset_cache/packs/spritesheet.dm b/code/modules/asset_cache/packs/spritesheet.dm index 7b83ff7c6a4..0e0834d4544 100644 --- a/code/modules/asset_cache/packs/spritesheet.dm +++ b/code/modules/asset_cache/packs/spritesheet.dm @@ -40,6 +40,12 @@ /// "foo_bar" -> list("32x32", 5) var/list/sprites = list() +/datum/asset_pack/spritesheet/unload() + ..() + to_generate = list() + sizes = list() + sprites = list() + /datum/asset_pack/spritesheet/register(generation) return construct() diff --git a/code/modules/client/client-stat.dm b/code/modules/client/client-stat.dm new file mode 100644 index 00000000000..f1d1055eeeb --- /dev/null +++ b/code/modules/client/client-stat.dm @@ -0,0 +1,21 @@ +/** + * Client Stat() + * + * We only run this if the statpanel is on BYOND mode right now. + */ +/client/Stat() + if(!tgui_stat?.byond_stat_active) + return + ..() // hit mob.Stat() + if(!statpanel("Turf")) + return + if(!tgui_stat.byond_stat_turf) + stat("No turf listed ; Alt click on an adjacent turf to view contents.") + return + stat(tgui_stat.byond_stat_turf.name, tgui_stat.byond_stat_turf) + for(var/atom/movable/AM as anything in tgui_stat.byond_stat_turf) + if(!AM.mouse_opacity) + continue + if(AM.invisibility > using_perspective.see_invisible) + continue + stat(null, AM) diff --git a/code/modules/client/client.dm b/code/modules/client/client.dm index eb77daa6618..7c36b27ebb5 100644 --- a/code/modules/client/client.dm +++ b/code/modules/client/client.dm @@ -108,22 +108,6 @@ /// client preferences var/datum/game_preferences/preferences - //? Statpanel - /// statpanel tab ; can be null (e.g. we're looking at verb tabs) - var/statpanel_tab - /// statpanel initialized - var/statpanel_ready = FALSE - /// turf being listed - var/turf/statpanel_turf - /// tabs the panel has - var/list/statpanel_tabs - /// statpanel variable tabs: spells / other "simple" action button frameworks - var/list/statpanel_spell_last - /// are we on byond stat? if so we can just skip the js one in data transmit (and vice versa) - var/statpanel_on_byond = FALSE - /// did we get autoswitched to byond stat for turf? if so we'll switch back when we un-list - var/statpanel_for_turf = FALSE - //? Throttling /// block re-execution of expensive verbs var/verb_throttle = 0 @@ -141,11 +125,17 @@ /// since byond is deranged and will send winsets and browse calls out of order sometimes. var/cutscene_lockout = FALSE - //* UI *// - /// Our action drawer - var/datum/action_drawer/action_drawer + //* UI - Client *// /// our tooltips system var/datum/tooltip/tooltips + /// chat panel + var/datum/tgui_panel/tgui_panel + /// statpanel + var/datum/client_statpanel/tgui_stat + + //* UI - Map *// + /// Our action drawer + var/datum/action_drawer/action_drawer //////////////// diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index c6158d2fc08..138744a62a7 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -171,15 +171,17 @@ C.New() /client/New(TopicData) - //* pre-connect-ish - // set appadmin for profiling or it might not work (?) (this is old code we just assume it's here for a reason) + //* pre-connect-ish *// + + // Byond only populates whether or not you can profile at connect. You have to give someone this + // before their client loads/whatever. This cannot be behind a spawn(). We will remove it from non-admins later. world.SetConfig("APP/admin", ckey, "role=admin") - // block client.Topic() calls from connect + // Block client.Topic() calls from connect. TopicData = null - // kick out invalid connections + // Kick invalid connections. if(connection != "seeker" && connection != "web") return null - // kick out guests + //! legacy: kick out guests !// if(!config_legacy.guests_allowed && is_guest() && !is_localhost()) security_kick( message = "This server doesn't allow guest accounts to play. Please go to http://www.byond.com/ and register for a key.", @@ -187,9 +189,10 @@ immediate = TRUE, ) return null - // pre-connect greeting - to_chat(src, "If the title screen is black, resources are still downloading. Please be patient until the title screen appears.") - // register in globals + // Queue pre-connect greeting + spawn(0.5 SECONDS) + to_chat(src, "If the title screen is black, resources are still downloading. Please be patient until the title screen appears.") + // Register in globals. GLOB.clients += src GLOB.directory[ckey] = src @@ -212,26 +215,18 @@ //* Resolve preferences preferences = SSpreferences.resolve_game_preferences(key, ckey) //? WARNING: SHITCODE ALERT ?// - // We allow a client/New sleep because preferences is currently required for - // everything else to work - // todo: maybe don't do this? - if(!UNLINT(preferences.block_on_initialized(5 SECONDS))) - security_kick("A fatal error occurred while attempting to load: preferences not initialized. Please notify a coder.") - stack_trace("we just kicked a client due to prefs not loading; something is horribly wrong!") - return // we wait until it inits to do this // todo: is there a better way this is kind of awful preferences.active = src preferences.on_reconnect() //? END ?// - //* Setup user interface + //* Create UI *// // todo: move top level menu here, for now it has to be under prefs. // Instantiate statpanel - spawn(1) - statpanel_boot() + tgui_stat = new(src, SKIN_BROWSER_ID_STAT) // Instantiate tgui panel - tgui_panel = new(src, "browseroutput") + tgui_panel = new(src, SKIN_BROWSER_ID_CHAT) // Instantiate cutscene system spawn(1) init_cutscene_system() @@ -298,48 +293,36 @@ //* therefore, DO NOT PUT ANYTHING YOU WILL RELY ON LATER IN THIS PROC IN LOGIN! . = ..() //calls mob.Login() - //* Connection Security + //* Connection Security *// // start caching it immediately INVOKE_ASYNC(SSipintel, TYPE_PROC_REF(/datum/controller/subsystem/ipintel, vpn_connection_check), address, ckey) // run onboarding gauntlet INVOKE_ASYNC(src, PROC_REF(onboarding)) - //* Initialize Input + //* Initialize Input *// if(SSinput.initialized) set_macros() update_movement_keys() - //* Initialize UI + //* Initialize UI *// // initialize statbrowser - // (we don't, the JS does it for us. by signalling statpanel_ready().) + tgui_stat.initialize() // Initialize tgui panel - INVOKE_ASYNC(tgui_panel, TYPE_PROC_REF(/datum/tgui_panel, initialize)) + tgui_panel.initialize() // initialize cutscene browser - // (we don't, the JS does it for us.) - - //if(alert_mob_dupe_login) - // spawn() - // alert(mob, "You have logged in already with another key this round, please log out of this one NOW or risk being banned!") + // - (we don't, the JS does it for us.) - + // Initialize tooltips + tooltips.initialize() connection_time = world.time connection_realtime = world.realtime connection_timeofday = world.timeofday - winset(src, null, "command=\".configure graphics-hwmode on\"") - /* - if (connection == "web" && !connecting_admin) - if (!CONFIG_GET(flag/allow_webclient)) - to_chat(src, "Web client is disabled") - qdel(src) - return 0 - if (CONFIG_GET(flag/webclient_only_byond_members) && !IsByondMember()) - to_chat(src, "Sorry, but the web client is restricted to byond members only.") - qdel(src) - return 0 - if( (world.address == address || !address) && !GLOB.host ) - GLOB.host = key - world.update_status() - */ + //* Misc *// + // force hardware graphics on + spawn(5) + winset(src, null, "command=\".configure graphics-hwmode on\"") + if(holder) add_admin_verbs() admin_memo_show() @@ -352,7 +335,10 @@ to_chat(src, "[custom_event_msg]") to_chat(src, "
") - send_resources() + // Preload resources. + // todo: re-evaluate this + spawn(0) + send_resources() //? Startup rendering pre_init_viewport() @@ -366,10 +352,13 @@ // changelog_async() // run post-init 'lint'-like checks - on_new_hook_stability_checks() + // this is on a spawn() to force a separate call chain + spawn(0) + on_new_hook_stability_checks() // todo: fuck you voreprefs - prefs_vr = new /datum/vore_preferences(src) + spawn(0) + prefs_vr = new /datum/vore_preferences(src) if(config_legacy.paranoia_logging) if(isnum(player.player_age) && player.player_age == -1) @@ -377,16 +366,18 @@ if(isnum(persistent.account_age) && persistent.account_age <= 2) log_and_message_admins("PARANOIA: [key_name(src)] has a very new BYOND account ([persistent.account_age] days).") - //? We are done + //* Finalize *// // set initialized if we're not queued for a security kick if(!queued_security_kick || panic_bunker_pending) initialized = TRUE else addtimer(CALLBACK(src, PROC_REF(deferred_initialization_block)), 0) // show any migration errors + // todo: this shouldn't be here prefs.auto_flush_errors() // update our hub label - SSserver_maint.UpdateHubStatus() + // todo: this should be a global signal that the subsystem hooks + SSserver_maint.queue_hub_update() /** * Called in the middle of new, after everything critical @@ -450,25 +441,22 @@ // clear HUDs clear_atom_hud_providers() - //* cleanup UI - // cleanup statbrowser - statpanel_dispose() - // cleanup cutscene system + //* cleanup client UI *// + QDEL_NULL(tgui_stat) cleanup_cutscene_system() - // cleanup tgui panel QDEL_NULL(tgui_panel) - // cleanup tooltips QDEL_NULL(tooltips) - // cleanup actions + + //* cleanup map UI *// QDEL_NULL(action_holder) QDEL_NULL(action_drawer) - //* logout + //* logout *// mob?.pre_logout(src) - //* cleanup from SSinput + //* cleanup from SSinput *// SSinput.currentrun?.Remove(src) - //* cleanup from SSping + //* cleanup from SSping *// SSping.currentrun?.Remove(src) . = ..() //Even though we're going to be hard deleted there are still some things that want to know the destroy is happening diff --git a/code/modules/client/statpanel.dm b/code/modules/client/client_statpanel.dm similarity index 55% rename from code/modules/client/statpanel.dm rename to code/modules/client/client_statpanel.dm index 6703ccc3bde..98f178c3394 100644 --- a/code/modules/client/statpanel.dm +++ b/code/modules/client/client_statpanel.dm @@ -1,6 +1,3 @@ -// todo: if byond ever gets threaded browsers, we're standardizing tgui.html and tgui_window.dm -// because manually reimplementing this shit is driving me nuts. - /** * citadel RP stat system * @@ -9,81 +6,206 @@ * 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. */ -//! external - state +/datum/client_statpanel + var/client/client + var/window_id + // var/datum/tgui_window/window -/client/proc/statpanel_init() - src << output(null, "statbrowser:byond_init") - init_verbs() + //* 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 -/client/proc/statpanel_check() - if(statpanel_ready) - return - to_chat(src, SPAN_USERDANGER("Statpanel failed to load, click here to reload the panel ")) + //* 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 -/** - * boots statpanel up during connect - */ -/client/proc/statpanel_boot() - set waitfor = FALSE - statpanel_boot_impl() +/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)) -/client/proc/statpanel_boot_impl() +/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) - // give client a second to load - sleep(world.tick_lag) // loads statbrowser if it isn't there - src << browse(file('html/statbrowser.html'), "window=statbrowser") + 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 - src << output(null, "statbrowser:byond_reconnect") + client << output(null, "statbrowser:byond_reconnect") // check for it incase it breaks - addtimer(CALLBACK(src, TYPE_PROC_REF(/client, statpanel_check)), 30 SECONDS) + 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 here to reload the panel ")) + +//* Internal API *// /** - * cleans up statpanel stuff during disconnect + * 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. */ -/client/proc/statpanel_dispose() - statpanel_ready = FALSE - statpanel_tab = null - statpanel_tabs = null - statpanel_spell_last = null - unlist_turf() - src << output(null, "statbrowser:byond_cleanup") +/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) /** - * instructs statpanel to reload + * resets state and reloads everything */ -/client/proc/statpanel_reload() - // this is janky as shit tbh - init_verbs shouldn't be our reset call too. - init_verbs(TRUE) - -/** - * only called for debug; fully reset statbrowser. - */ -/client/proc/statpanel_reset() - statpanel_dispose() - sleep(1) - src << browse("RELOADING", "window=statbrowser") - src << browse(file('html/statbrowser.html'), "window=statbrowser") - sleep(1) - statpanel_boot() - -/** - * called by statbrowser when it's ready - */ -/client/proc/statpanel_ready() - statpanel_tabs = list() - statpanel_init() - statpanel_ready = TRUE +/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 */ -/client/proc/statpanel_token(token) +/datum/client_statpanel/proc/set_admin_token(token) if(!token) - src << output(null, "statbrowser:byond_dispose_token") + client << output(null, "statbrowser:byond_dispose_token") return - src << output(token, "statbrowser:byond_grant_token") + 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 @@ -92,10 +214,10 @@ * - status: use to set if the panel should exist */ /client/proc/statpanel_tab(tab, status) - . = (statpanel_tab == tab) + . = (tgui_stat.current_tab == tab) if(isnull(status)) return - if(tab in statpanel_tabs) + if(tab in tgui_stat.current_tabs) if(!status) src << output(url_encode(tab), "statbrowser:byond_remove_tab") return FALSE // removing @@ -105,40 +227,26 @@ 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(statpanel_turf) + if(tgui_stat.byond_stat_turf) unlist_turf() if(!T || !list_turf_check(T)) return - statpanel_turf = T + tgui_stat.byond_stat_turf = T // using byond atm - if(!statpanel_on_byond) - statpanel_for_turf = TRUE - statpanel_on_byond = TRUE + 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]") -/* not using js - var/list/data = list() - for(var/atom/movable/AM as anything in T) - var/list/got = statpanel_encode_atom(AM) - if(!got) - continue - data[++data.len] = got - src << output("[url_encode(T.name)];[url_encode(REF(T))];[icon2html(T, src, sourceonly = TRUE)];[url_encode(json_encode(data))]", "statbrowser:byond_turf_set") -*/ + /client/proc/unlist_turf() // using byond atm - if(statpanel_for_turf) - statpanel_for_turf = FALSE - statpanel_on_byond = FALSE + 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]") -/* not using js - src << output(null, "statbrowser:byond_turf_unset") - UnregisterSignal(statpanel_turf, list( - COMSIG_ATOM_ENTERED, - COMSIG_ATOM_EXITED, - )) -*/ - statpanel_turf = null + + tgui_stat.byond_stat_turf = null /client/proc/list_turf_check(turf/T) return mob.TurfAdjacent(T) @@ -152,12 +260,6 @@ return if(AM.invisibility > mob.see_invisible) return - // not gonna bother for now - this is to prevent alt clicking to see past override image - // if(AM in overrides) - // return - // meanwhile this is just a shit check in a proc because it's N^2, need proper turf obscure flags. - // if(AM.IsObscued()) - // return return list( "[AM.name]", REF(AM), @@ -173,77 +275,6 @@ /client/proc/__stat_hook_turf_exit(datum/source, atom/movable/AM) src << output("[url_encode(REF(AM))]", "statbrowser:byond_turf_del") -//! external - load -/// 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") - init_verbs(FALSE) - -/// compiles a full list of verbs and sends it to the browser -/client/proc/init_verbs(reset = FALSE) - var/list/verblist = list() - var/list/verbstoprocess = verbs.Copy() - if(mob) - verbstoprocess += mob.verbs - for(var/atom/movable/AM as anything in 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 - pass() - src << output("[url_encode(json_encode(verblist))];[reset]", "statbrowser:byond_init_verbs") - -//! native - -/client/Stat() - if(!statpanel_on_byond) - return - ..() // hit mob.Stat() - if(!statpanel("Turf")) - return - if(!statpanel_turf) - stat("No turf listed ; Alt click on an adjacent turf to view contents.") - return - stat(statpanel_turf.name, statpanel_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/movable/AM as anything in statpanel_turf) - if(!AM.mouse_opacity) - continue - if(AM.invisibility > using_perspective.see_invisible) - continue -// too expensive rn -/* - if(overrides.len && (A in overrides)) - continue -*/ -// not needed rn -/* - if(A.IsObscured()) - continue -*/ - stat(null, AM) - -//! data - /** * the big, bad, Citadel Station in house stat proc. * returns a list of "panel name" : list(entry, ...) @@ -277,10 +308,10 @@ /client/proc/_statpanel_act(action, list/params) switch(action) if("reload") - statpanel_reload() + tgui_stat.request_reload() return if("ready") - statpanel_ready() + tgui_stat.ready_received() return if("stat_click") var/datum/D = locate(params["ref"]) @@ -309,70 +340,3 @@ . = statpanel_data(src) if(!islist(.)) CRASH("[.] was not list.") - -//! verb hooks - js stat - -/client/verb/hook_statpanel_ready() - set name = ".statpanel_ready" - set hidden = TRUE - set instant = TRUE - - statpanel_ready() - -/client/verb/hook_statpanel_add_tab(tab as text) - set name = ".statpanel_tab_add" - set hidden = TRUE - set instant = TRUE - - if(length(statpanel_tabs) > 50) - return // bail - statpanel_tabs |= tab - -/client/verb/hook_statpanel_remove_tab(tab as text) - set name = ".statpanel_tab_remove" - set hidden = TRUE - set instant = TRUE - - statpanel_tabs -= tab - -/client/verb/hook_statpanel_wipe_tabs() - set name = ".statpanel_tab_reset" - set hidden = TRUE - set instant = TRUE - - statpanel_tabs = list() - -/client/verb/hook_statpanel_set_tab(tab as text) - set name = ".statpanel_tab" - set hidden = TRUE - set instant = TRUE - - statpanel_tab = tab - -/** - * This cleanly and gracefully attempts to go to a specific tab via , 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 - byond stat - -//! verb hooks - tab switcher - -/client/verb/hook_statswitcher_set_tab(tab as text) - set name = ".statswitcher" - set hidden = TRUE - set instant = TRUE - - statpanel_on_byond = (tab == "stat_pane_byond") - -//? Verbs - For Players - -/client/verb/fix_stat_panel() - set name = "Fix Stat Panel" - set category = VERB_CATEGORY_OOC - - statpanel_reset() diff --git a/code/modules/client/cutscene.dm b/code/modules/client/cutscene.dm index 2c94f33ec50..dda4e467597 100644 --- a/code/modules/client/cutscene.dm +++ b/code/modules/client/cutscene.dm @@ -1,5 +1,6 @@ // todo: if byond ever gets threaded browsers, we're standardizing tgui.html and tgui_window.dm // because manually reimplementing this shit is driving me nuts. +// todo: the cutscene system is kinda jank; we should just do lobbybrowser instead of using it for that. /client/proc/init_cutscene_system() set waitfor = FALSE @@ -146,6 +147,11 @@ push_dispose(C) C.cutscene_browser = FALSE winset(C, SKIN_BROWSER_ID_CUTSCENE, "is-visible=0") + // just in case + spawn(2 SECONDS) + // no QDELETED check because we'll already be deleted + if(winget(C, "[SKIN_BROWSER_ID_CUTSCENE]", "is-visible") == "true") + winset(C, SKIN_BROWSER_ID_CUTSCENE, "is-visible=0") /datum/cutscene/browser/proc/push_build(client/C, raw_html = build_inner_html()) C << output("[url_encode(json_encode(list("raw_html" = raw_html)))]", "[SKIN_BROWSER_ID_CUTSCENE]:build") diff --git a/code/modules/client/data/client_data.dm b/code/modules/client/data/client_data.dm index 7660a0a5e12..8b74a28b368 100644 --- a/code/modules/client/data/client_data.dm +++ b/code/modules/client/data/client_data.dm @@ -50,7 +50,8 @@ GLOBAL_LIST_EMPTY(client_data) is_guest = IsGuestKey(key) - INVOKE_ASYNC(src, PROC_REF(load_account_age)) + spawn(0) + load_account_age() var/list/the_cheese_touch = CONFIG_GET(keyed_list/shadowban) var/client/C = GLOB.directory[src.ckey] diff --git a/code/modules/client/data/player_data.dm b/code/modules/client/data/player_data.dm index 08ef61a6f19..dfbc4c6faa9 100644 --- a/code/modules/client/data/player_data.dm +++ b/code/modules/client/data/player_data.dm @@ -63,7 +63,9 @@ GLOBAL_LIST_EMPTY(player_data) if(isnull(available)) available = FALSE return FALSE - INVOKE_ASYNC(src, PROC_REF(load_blocking)) + // make separate call chain; do not mess with client init + spawn(0) + load_blocking() return TRUE /datum/player_data/proc/load_blocking() @@ -221,8 +223,9 @@ GLOBAL_LIST_EMPTY(player_data) * async */ /datum/player_data/proc/log_connect() - set waitfor = FALSE - update_last_seen() + // make separate call chain; do not mess with client init + spawn(0) + update_last_seen() /datum/player_data/proc/update_last_seen() // don't interrupt diff --git a/code/modules/client/game_preferences/game_preference_toggle.dm b/code/modules/client/game_preferences/game_preference_toggle.dm index db431f3d985..c4fa3ec8aa9 100644 --- a/code/modules/client/game_preferences/game_preference_toggle.dm +++ b/code/modules/client/game_preferences/game_preference_toggle.dm @@ -38,7 +38,7 @@ abstract_type = /datum/game_preference_toggle/admin category = "Admin" -/datum/game_preference_toggle/admin/is_visible(client/user, silent) +/datum/game_preference_toggle/admin/is_visible(client/user) return check_rights(C = user, show_msg = FALSE) /datum/game_preference_toggle/admin/global_looc diff --git a/code/modules/client/game_preferences/game_preferences.dm b/code/modules/client/game_preferences/game_preferences.dm index 3c8da9b9403..c54d71592dc 100644 --- a/code/modules/client/game_preferences/game_preferences.dm +++ b/code/modules/client/game_preferences/game_preferences.dm @@ -1,6 +1,14 @@ //* This file is explicitly licensed under the MIT license. *// //* Copyright (c) 2024 silicons *// +/client/on_new_hook_stability_checks() + // preferences are critical; if they can't load, kick them + spawn(0) + if(!preferences.block_on_initialized(5 SECONDS)) + disconnection_message("A fatal error occurred while attempting to load: preferences not initialized. Please notify a coder.") + stack_trace("we just kicked a client due to prefs not loading; something is horribly wrong!") + qdel(src) + return ..() /** * Game preferences * @@ -61,13 +69,16 @@ //* Init *// /datum/game_preferences/proc/initialize() - perform_initial_load() - initialized = TRUE + // do not mess with client init; start a new call chain + spawn(0) + perform_initial_load() + initialized = TRUE /datum/game_preferences/proc/on_reconnect() - if(!initialized) - return - initialize_client() + // do not mess with client init; start a new call chain + spawn(0) + block_on_initialized() + initialize_client() /datum/game_preferences/proc/block_on_initialized(timeout = 10 SECONDS) var/wait_until = world.time + timeout @@ -133,12 +144,12 @@ var/list/old_toggles legacy_savefile["preferences"] >> old_toggles - - for(var/key in SSpreferences.toggles_by_key) - var/datum/game_preference_toggle/toggle = SSpreferences.toggles_by_key[key] - if(!toggle.legacy_key) - continue - toggles_by_key[key] = (toggle.legacy_key in old_toggles) + if(islist(old_toggles)) + for(var/key in SSpreferences.toggles_by_key) + var/datum/game_preference_toggle/toggle = SSpreferences.toggles_by_key[key] + if(!toggle.legacy_key) + continue + toggles_by_key[key] = (toggle.legacy_key in old_toggles) var/list/old_keybinds legacy_savefile["key_bindings"] >> old_keybinds @@ -151,6 +162,7 @@ return TRUE /datum/game_preferences/proc/perform_initial_load() + sleep(2 SECONDS) if(!is_guest) // only if not guest if(SSdbcore.IsConnected()) @@ -253,9 +265,6 @@ CRASH("invalid fetch") if(!initialized) return FALSE - // we don't check is visible, as it's checked on 'get' - // if(!toggle.is_visible(active)) - // return FALSE toggles_by_key[toggle.key] = value if(active) toggle.toggled(active, value) @@ -268,9 +277,6 @@ CRASH("invalid fetch") if(!initialized) return FALSE - // we don't check is visible, as it's checked on 'get' - // if(!toggle.is_visible(active)) - // return FALSE toggles_by_key[toggle.key] = !toggles_by_key[toggle.key] if(active) toggle.toggled(active, toggles_by_key[toggle.key]) diff --git a/code/modules/client/verbs/fix_statpanel.dm b/code/modules/client/verbs/fix_statpanel.dm new file mode 100644 index 00000000000..1d63aac05a0 --- /dev/null +++ b/code/modules/client/verbs/fix_statpanel.dm @@ -0,0 +1,7 @@ +/client/verb/fix_stat_panel() + set name = "Fix Stat Panel" + set category = VERB_CATEGORY_OOC + + if(!istype(tgui_stat)) + tgui_stat = new(src, SKIN_BROWSER_ID_STAT) + tgui_stat.initialize() diff --git a/code/modules/mob/login.dm b/code/modules/mob/login.dm index 256211c38fb..d781b7a82aa 100644 --- a/code/modules/mob/login.dm +++ b/code/modules/mob/login.dm @@ -84,7 +84,7 @@ // we really hate that this is needed but it is until the screens/images reset isn't there client.action_drawer.reassert_screen() // reset statpanel of any verbs/whatnot - client.statpanel_reload() + client.tgui_stat?.request_reload() // update ssd overlay addtimer(CALLBACK(src, TYPE_PROC_REF(/mob, update_ssd_overlay)), 0) // reset cutscene to default; this is a proc for new players. diff --git a/code/modules/mob/client.dm b/code/modules/mob/mob-client.dm similarity index 94% rename from code/modules/mob/client.dm rename to code/modules/mob/mob-client.dm index 7968f977f51..1c69bc97884 100644 --- a/code/modules/mob/client.dm +++ b/code/modules/mob/mob-client.dm @@ -1,5 +1,5 @@ //* This file is explicitly licensed under the MIT license. *// -//* Copyright (c) 2024 silicons *// +//* Copyright (c) 2024 Citadel Station Developers *// /** * **never directly set ckey on a mob** diff --git a/code/modules/spells/spells.dm b/code/modules/spells/spells.dm index 3adb922981a..c7e219ef87a 100644 --- a/code/modules/spells/spells.dm +++ b/code/modules/spells/spells.dm @@ -17,13 +17,13 @@ // i'm going to trust people aren't stupid and won't put the name of a regular panel in spells. if(!length(spell_list)) - if(C.statpanel_spell_last) + if(C.tgui_stat.spell_last) // dispose - for(var/tab in C.statpanel_spell_last) + for(var/tab in C.tgui_stat.spell_last) C.statpanel_tab(tab, FALSE) - C.statpanel_spell_last = null + C.tgui_stat.spell_last = null return - LAZYINITLIST(C.statpanel_spell_last) + LAZYINITLIST(C.tgui_stat.spell_last) var/list/collected = list() for(var/spell/S in spell_list) if(!S.panel || !S.connected_button) @@ -39,8 +39,8 @@ if(Sp_HOLDVAR) STATPANEL_DATA_CLICK("[S.holder_var_type] [S.holder_var_amount]", "[S.connected_button]", "\ref[S.connected_button]") // process tabs - var/list/removing = C.statpanel_spell_last - collected - var/list/adding = collected - C.statpanel_spell_last + var/list/removing = C.tgui_stat.spell_last - collected + var/list/adding = collected - C.tgui_stat.spell_last for(var/tab in adding) C.statpanel_tab(adding, TRUE) for(var/tab in removing) diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 698529d3954..533d3ecb9df 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -50,11 +50,21 @@ /// processed modules var/list/datum/modules_processed +/** + * Linter check, do not call. + */ +/proc/lint__check_tgui_new_doesnt_sleep() + SHOULD_NOT_SLEEP(TRUE) + var/datum/tgui/tgui + tgui.New() + /** * public * * Create a new UI. * + * * Does not block. + * * required user mob The mob who opened/is using the UI. * required src_object datum The object or datum which owns the UI. * required interface string The interface used to render the UI. @@ -91,6 +101,8 @@ * * Open this UI (and initialize it with data). * + * This proc does not block. + * * @params * * data - force certain data sends * * modules - force certain module sends @@ -98,6 +110,7 @@ * return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists. */ /datum/tgui/proc/open(data, modules) + SHOULD_NOT_SLEEP(TRUE) if(!user.client) return FALSE if(window) @@ -108,8 +121,20 @@ window = SStgui.request_pooled_window(user) if(!window) return FALSE + // point of no return; call initialize() asynchronously. opened_at = world.time window.acquire_lock(src) + // defer initialize() to after current call chain. + spawn(0) + initialize(data, modules) + return TRUE + +/** + * Initializes the window. + * + * * Separate from open() so that open() can be non-blocking. + */ +/datum/tgui/proc/initialize(data, modules) if(!window.is_ready()) window.initialize( strict_mode = TRUE, @@ -440,13 +465,16 @@ * * required data The data to send * optional force bool Send an update even if UI is not interactive. + * + * @return TRUE if data was sent, FALSE otherwise. */ /datum/tgui/proc/push_data(data, force) if(!user.client || !initialized || closing) - return + return FALSE if(!force && status < UI_UPDATE) - return + return FALSE window.send_message("data", data) + return TRUE /** * public @@ -461,13 +489,16 @@ * @params * * updates - list(id = list(data...), ...) of modules to update. * * force - (optional) send update even if UI is not interactive + * + * @return TRUE if data was sent, FALSE otherwise. */ /datum/tgui/proc/push_modules(list/updates, force) if(isnull(user.client) || !initialized || closing) - return + return FALSE if(!force && status < UI_UPDATE) - return + return FALSE window.send_message("modules", updates) + return TRUE //* Module System *// diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 97d3ac21648..035eba008f7 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -11,7 +11,9 @@ var/is_browser = FALSE var/status = TGUI_WINDOW_CLOSED var/locked = FALSE + // todo: combine with subscriber_object var/datum/tgui/locked_by + // todo: combine with locked_by var/datum/subscriber_object var/subscriber_delegate var/fatally_errored = FALSE @@ -29,15 +31,26 @@ var/initial_inline_css var/mouse_event_macro_set = FALSE +/** + * Linter check, do not call. + */ +/proc/lint__check_tgui_window_new_doesnt_sleep() + SHOULD_NOT_SLEEP(TRUE) + var/datum/tgui_window/window + window.New() + /** * public * * Create a new tgui window. * + * * Does not block. + * * required client /client * required id string A unique window identifier. */ /datum/tgui_window/New(client/client, id, pooled = FALSE) + ASSERT(!client.tgui_windows[id]) src.id = id src.client = client src.client.tgui_windows[id] = src diff --git a/code/modules/tgui_panel/external.dm b/code/modules/tgui_panel/external.dm index c112a8c147e..4c8e5174478 100644 --- a/code/modules/tgui_panel/external.dm +++ b/code/modules/tgui_panel/external.dm @@ -3,8 +3,6 @@ *! SPDX-License-Identifier: MIT */ -/client/var/datum/tgui_panel/tgui_panel - /** * tgui panel / chat troubleshooting verb */ @@ -31,7 +29,7 @@ log_tgui(src, "tgui_panel datum is missing", context = "verb/fix_tgui_panel") tgui_panel = new(src) - tgui_panel.initialize(force = TRUE) + tgui_panel.initialize() // Force show the panel to see if there are any errors winset(src, "output", "is-disabled=1&is-visible=0") winset(src, "browseroutput", "is-disabled=0;is-visible=1") diff --git a/code/modules/tgui_panel/tgui_panel.dm b/code/modules/tgui_panel/tgui_panel.dm index 806b5e6b052..79f9b0d5d4a 100644 --- a/code/modules/tgui_panel/tgui_panel.dm +++ b/code/modules/tgui_panel/tgui_panel.dm @@ -34,14 +34,30 @@ return !broken && window.is_ready() /** - * public + * Initializes the chat panel. * - * Initializes tgui panel. + * * This is asynchronous and will yield for an appropriate duration for the client's current state. */ -/datum/tgui_panel/proc/initialize(force = FALSE) - set waitfor = FALSE - // Minimal sleep to defer initialization to after client constructor - sleep(1) +/datum/tgui_panel/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() + +/** + * Boots the panel. + * + * * This is a blocking proc. + */ +/datum/tgui_panel/proc/boot() + PRIVATE_PROC(TRUE) initialized_at = world.time // Perform a clean initialization window.initialize( diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm index 9803d4fb96a..0b5ff331feb 100644 --- a/code/modules/tooltip/tooltip.dm +++ b/code/modules/tooltip/tooltip.dm @@ -32,30 +32,45 @@ Notes: /datum/tooltip - var/client/owner + var/client/client var/control = "mainwindow.tooltip" var/showing = 0 var/queueHide = 0 var/init = 0 - /datum/tooltip/New(client/C) if(!C) return - owner = C - addtimer(CALLBACK(src, PROC_REF(init)), 0) + client = C -/datum/tooltip/proc/init() - SSassets.send_asset_pack(owner, /datum/asset_pack/simple/jquery) - owner << browse(file2text('code/modules/tooltip/tooltip.html'), "window=[control]") +/datum/tooltip/Destroy() + client = null + return ..() + +/datum/tooltip/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/tooltip/proc/boot() + SSassets.send_asset_pack(client, /datum/asset_pack/simple/jquery) + client << browse(file2text('code/modules/tooltip/tooltip.html'), "window=[control]") /datum/tooltip/proc/show(atom/movable/thing, params = null, title = null, content = null, theme = "default", special = "none") - if (!thing || !params || (!title && !content) || !owner || !isnum(world.icon_size)) + if (!thing || !params || (!title && !content) || !client || !isnum(world.icon_size)) return FALSE if (!init) //Initialize some vars init = 1 - owner << output(list2params(list(world.icon_size, control)), "[control]:tooltip.init") + client << output(list2params(list(world.icon_size, control)), "[control]:tooltip.init") showing = 1 @@ -75,7 +90,7 @@ Notes: params = {"{ "cursor": "[params]", "screenLoc": "[thing.screen_loc]" }"} //Send stuff to the tooltip - owner << output(list2params(list(params, owner.current_viewport_width, owner.current_viewport_height, "[title][content]", theme, special)), "[control]:tooltip.update") + client << output(list2params(list(params, client.current_viewport_width, client.current_viewport_height, "[title][content]", theme, special)), "[control]:tooltip.update") //If a hide() was hit while we were showing, run hide() again to avoid stuck tooltips showing = 0 @@ -95,7 +110,7 @@ Notes: return TRUE /datum/tooltip/proc/do_hide() - winshow(owner, control, FALSE) + winshow(client, control, FALSE) /* TG SPECIFIC CODE */