Bun, Inferno->React migration (#22529)

Re-creation of https://github.com/Aurorastation/Aurora.3/pull/21046 to
skip merge conflict hell. Brings us modern TGUI.

**ALTERNATE TITLE: TGUI HELLSCAPE PR
ABANDON ALL HOPE YE WHO ENTER HERE**

- [x] Migrate build tools (javascript -> typescript, bun for package
management).
- [x] Upgrade all TGUI dependencies and associated root files to
TG-congruent versions (axios, babel, dompurify, eslint, highlight,
marked, prettier, sass, source-map, stacktrace-parser, typescript).
- [x] InfernoJS -> React migrations
- [x] React cleanup and polish (migrate all remaining .js files to
appropriate .ts or .tsx filetype, all remaining hooks, linting, error
corrections, etc.)
- [ ] Test all remaining TGUI interfaces
This commit is contained in:
Batrachophreno
2026-06-05 15:55:22 +02:00
committed by GitHub
parent a52729c105
commit 0d92359da7
930 changed files with 23130 additions and 50520 deletions
+72 -21
View File
@@ -10,31 +10,82 @@ SUBSYSTEM_DEF(chat)
flags = SS_TICKER | SS_NO_INIT
wait = 1
priority = FIRE_PRIORITY_CHAT
init_order = INIT_ORDER_CHAT
init_order = INITSTAGE_MAX
var/list/payload_by_client = list()
/// Assosciates a ckey with a list of messages to send to them.
var/list/list/datum/chat_payload/client_to_payloads = list()
/// Associates a ckey with an assosciative list of their last CHAT_RELIABILITY_HISTORY_SIZE messages.
var/list/list/datum/chat_payload/client_to_reliability_history = list()
/// Assosciates a ckey with their next sequence number.
var/list/client_to_sequence_number = list()
/datum/controller/subsystem/chat/proc/generate_payload(client/target, message_data)
var/sequence = client_to_sequence_number[target.ckey]
client_to_sequence_number[target.ckey] += 1
var/datum/chat_payload/payload = new
payload.sequence = sequence
payload.content = message_data
if(!(target.ckey in client_to_reliability_history))
client_to_reliability_history[target.ckey] = list()
var/list/client_history = client_to_reliability_history[target.ckey]
client_history["[sequence]"] = payload
if(length(client_history) > 5)
var/oldest = text2num(client_history[1])
for(var/index in 2 to length(client_history))
var/test = text2num(client_history[index])
if(test < oldest)
oldest = test
client_history -= "[oldest]"
return payload
/datum/controller/subsystem/chat/proc/send_payload_to_client(client/target, datum/chat_payload/payload)
target.tgui_panel.window.send_message("chat/message", payload.into_message())
SEND_TEXT(target, payload.get_content_as_html())
/datum/controller/subsystem/chat/fire()
for(var/key in payload_by_client)
var/client/client = key
var/payload = payload_by_client[key]
payload_by_client -= key
if(client)
// Send to tgchat
client.tgui_panel?.window.send_message("chat/message", payload)
// Send to old chat
for(var/message in payload)
to_target(client, message_to_html(message))
for(var/ckey in client_to_payloads)
var/client/target = GLOB.directory[ckey]
if(isnull(target)) // verify client still exists
LAZYREMOVE(client_to_payloads, ckey)
continue
for(var/datum/chat_payload/payload as anything in client_to_payloads[ckey])
send_payload_to_client(target, payload)
LAZYREMOVE(client_to_payloads, ckey)
if(MC_TICK_CHECK)
return
/datum/controller/subsystem/chat/proc/queue(target, message)
if(islist(target))
for(var/_target in target)
var/client/client = CLIENT_FROM_VAR(_target)
if(client)
LAZYADD(payload_by_client[client], list(message))
/datum/controller/subsystem/chat/proc/queue(queue_target, list/message_data)
var/list/targets = islist(queue_target) ? queue_target : list(queue_target)
for(var/target in targets)
var/client/client = CLIENT_FROM_VAR(target)
if(isnull(client))
continue
LAZYADDASSOCLIST(client_to_payloads, client.ckey, generate_payload(client, message_data))
/datum/controller/subsystem/chat/proc/send_immediate(send_target, list/message_data)
var/list/targets = islist(send_target) ? send_target : list(send_target)
for(var/target in targets)
var/client/client = CLIENT_FROM_VAR(target)
if(isnull(client))
continue
send_payload_to_client(client, generate_payload(client, message_data))
/datum/controller/subsystem/chat/proc/handle_resend(client/client, sequence)
var/list/client_history = client_to_reliability_history[client.ckey]
sequence = "[sequence]"
if(isnull(client_history) || !(sequence in client_history))
return
var/client/client = CLIENT_FROM_VAR(target)
if(client)
LAZYADD(payload_by_client[client], list(message))
var/datum/chat_payload/payload = client_history[sequence]
if(payload.resends > 3)
return // we tried but byond said no
payload.resends += 1
send_payload_to_client(client, client_history[sequence])
@@ -50,12 +50,16 @@ SUBSYSTEM_DEF(nightlight)
/datum/controller/subsystem/nightlight/proc/get_apc_list(var/whitelisted_only = 1)
var/list/obj/structure/machinery/power/apc/lighting_apcs = list()
for (var/A in get_sorted_areas())
var/area/B = A
if (B.no_light_control || (!(B.allow_nightmode) && whitelisted_only))
for (var/obj/structure/machinery/power/apc/APC in SSmachinery.apc_units)
if (QDELETED(APC) || APC.aidisabled)
continue
if (B.apc && !B.apc.aidisabled)
lighting_apcs += B.apc
var/area/apc_area = APC.area || get_area(APC)
if (!apc_area)
continue
if (apc_area.no_light_control || (!(apc_area.allow_nightmode) && whitelisted_only))
continue
lighting_apcs += APC
CHECK_TICK
@@ -1,241 +0,0 @@
PROCESSING_SUBSYSTEM_DEF(nanoui)
// Subsystem stuff.
name = "NanoUI"
flags = SS_NO_INIT
priority = SS_PRIORITY_NANOUI
stat_tag = "A"
// NanoUI stuff.
var/list/open_nanouis = list()
/**
* Get an open /nanoui ui for the current user, src_object and ui_key and try to update it with data
*
* * user - /mob The mob who opened/owns the ui
* * src_object - /obj|/mob The obj or mob which the ui belongs to
* * ui_key - A string key used for the ui
* * data - List, the data to be passed to the ui, if it exists
* * force_open - Boolean, the ui is being forced to (re)open, so close ui if it exists (instead of updating)
*
* Returns the `/nanoui` found ui, for null if none exists
*/
/datum/controller/subsystem/processing/nanoui/proc/try_update_ui(mob/user, src_object, ui_key, datum/nanoui/ui, data, force_open = FALSE)
if (!ui) // no ui has been passed, so we'll search for one
ui = get_open_ui(user, src_object, ui_key)
if (ui)
// The UI is already open
if (!force_open)
ui.push_data(data)
return ui
else
ui.reinitialise(new_initial_data=data)
return ui
return null
/**
* Get an open /nanoui ui for the current user, src_object and ui_key
*
* * user - The `/mob` who opened/owns the ui
* * src_object - The `/obj` or `/mob` which the ui belongs to
* * ui_key - A string key used for the ui
*/
/datum/controller/subsystem/processing/nanoui/proc/get_open_ui(mob/user, src_object, ui_key)
var/src_object_key = REF(src_object)
if (!LAZYLEN(open_nanouis[src_object_key]) || !LAZYLEN(open_nanouis[src_object_key][ui_key]))
return null
for (var/datum/nanoui/ui in open_nanouis[src_object_key][ui_key])
if (ui.user == user)
return ui
//testing("nanomanager/get_open_ui mob [user.name] [src_object:name] [ui_key] - ui not found")
return null
/**
* Update all `/nanoui` uis attached to src_object
*
* * src_object - The `/obj` or `/mob` which the uis are attached to
*
* Returns the number of UIs updated
*/
/datum/controller/subsystem/processing/nanoui/proc/update_uis(src_object)
var/src_object_key = REF(src_object)
if (!LAZYLEN(open_nanouis[src_object_key]))
return 0
. = 0
var/list/obj_uis = open_nanouis[src_object_key]
for (var/ui_key in obj_uis)
for (var/thing in obj_uis[ui_key])
var/datum/nanoui/ui = thing
if(ui && ui.src_object && ui.user && ui.src_object.ui_host())
ui.process(1)
.++
/**
* Close all `/nanoui` uis attached to src_object
*
* * src_object - The `/obj` or `/mob` which the uis are attached to
*
* Returns the number of UIs closed
*/
/datum/controller/subsystem/processing/nanoui/proc/close_uis(src_object)
var/src_object_key = REF(src_object)
if (!open_nanouis[src_object_key] || !islist(open_nanouis[src_object_key]))
return 0
. = 0
var/list/obj_uis = open_nanouis[src_object_key]
for (var/ui_key in obj_uis)
for (var/thing in obj_uis[ui_key])
var/datum/nanoui/ui = thing
if(ui && ui.src_object && ui.user && ui.src_object.ui_host())
ui.close()
.++
/**
* Update /nanoui uis belonging to user
*
* * user - The `/mob` who owns the uis
* * src_object - An `/obj` or `/mob` that, if is provided, only update uis which are attached to it (optional)
* * ui_key - A string, if ui_key is provided, only update uis with a matching ui_key (optional)
*
* Returns the number of UIs updated
*/
/datum/controller/subsystem/processing/nanoui/proc/update_user_uis(mob/user, src_object, ui_key)
if (!LAZYLEN(user.open_nanouis))
return 0 // has no open uis
. = 0
for (var/thing in user.open_nanouis)
var/datum/nanoui/ui = thing
if (NULL_OR_EQUAL(src_object, ui.src_object) && NULL_OR_EQUAL(ui_key, ui.ui_key))
ui.process(1)
.++
/datum/controller/subsystem/processing/nanoui/proc/close_user_uis(mob/user, src_object, ui_key)
if (!LAZYLEN(user.open_nanouis))
return 0
for (var/thing in user.open_nanouis)
var/datum/nanoui/ui = thing
if (NULL_OR_EQUAL(src_object, ui.src_object) && NULL_OR_EQUAL(ui_key, ui.ui_key))
ui.close()
.++
//testing("nanomanager/close_user_uis mob [user.name] closed [open_nanouis.len] of [.] uis")
/**
* Add a /nanoui ui to the list of open uis
* This is called by the /nanoui open() proc
*
* * ui - The `/nanoui` ui to add
*/
/datum/controller/subsystem/processing/nanoui/proc/ui_opened(datum/nanoui/ui)
var/src_object_key = REF(ui.src_object)
LAZYINITLIST(open_nanouis[src_object_key])
LAZYADD(ui.user.open_nanouis, ui)
LAZYADD(open_nanouis[src_object_key][ui.ui_key], ui)
START_PROCESSING(SSnanoui, ui)
//testing("nanomanager/ui_opened mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.open_nanouis [ui.user.open_nanouis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
/**
* Remove a /nanoui ui from the list of open uis
* This is called by the /nanoui close() proc
*
* * ui - A `/nanoui` to remove
*
* Returns FALSE if no ui was removed, TRUE if removed successfully
*/
/datum/controller/subsystem/processing/nanoui/proc/ui_closed(datum/nanoui/ui)
var/src_object_key = REF(ui.src_object)
var/ui_key = ui.ui_key
var/list/obj_uis = open_nanouis[src_object_key]
if (!LAZYLEN(obj_uis) || !obj_uis[ui_key])
return 0 // Wasn't open.
STOP_PROCESSING(SSnanoui, ui)
if(ui.user) // Sanity check in case a user has been deleted (say a blown up borg watching the alarm interface)
LAZYREMOVE(ui.user.open_nanouis, ui)
obj_uis[ui_key] -= ui
if (!LAZYLEN(obj_uis[ui_key]))
obj_uis -= ui_key
if (!LAZYLEN(obj_uis))
open_nanouis -= src_object_key
//testing("nanomanager/ui_closed mob [ui.user.name] [ui.src_object:name] [ui.ui_key] - user.open_nanouis [ui.user.open_nanouis.len] | uis [uis.len] | processing_uis [processing_uis.len]")
return 1
/**
* This is called on user logout
* Closes/clears all uis attached to the user's `/mob`
*
* * user - The user's `/mob`
*/
/datum/controller/subsystem/processing/nanoui/proc/user_logout(mob/user)
return close_user_uis(user)
/**
* This is called when a player transfers from one mob to another
* Transfers all open UIs to the new mob
*
* * oldMob - The user's old `/mob`
* * newMob - The user's new `/mob`
*/
/datum/controller/subsystem/processing/nanoui/proc/user_transferred(mob/oldMob, mob/newMob)
//testing("nanomanager/user_transferred from mob [oldMob.name] to mob [newMob.name]")
if (!oldMob || !LAZYLEN(oldMob.open_nanouis) || !LAZYLEN(open_nanouis))
//testing("nanomanager/user_transferred mob [oldMob.name] has no open uis")
return 0 // has no open uis
for (var/thing in oldMob.open_nanouis)
var/datum/nanoui/ui = thing
ui.user = newMob
LAZYADD(newMob.open_nanouis, ui)
oldMob.open_nanouis = null
return 1 // success
/datum/asset/nanoui
var/list/common = list()
var/list/common_dirs = list(
"nano/css/",
"nano/js/",
"nano/images/",
"nano/images/status_icons/",
"nano/templates/"
)
var/list/uncommon_dirs = list()
/datum/asset/nanoui/register()
// Crawl the directories to find files.
for (var/path in common_dirs)
var/list/filenames = flist(path)
for(var/filename in filenames)
if(copytext(filename, length(filename)) != "/") // Ignore directories.
if(fexists(path + filename))
common[filename] = fcopy_rsc(path + filename)
SSassets.transport.register_asset(filename, common[filename])
for (var/path in uncommon_dirs)
var/list/filenames = flist(path)
for(var/filename in filenames)
if(copytext(filename, length(filename)) != "/") // Ignore directories.
if(fexists(path + filename))
SSassets.transport.register_asset(filename, fcopy_rsc(path + filename))
/datum/asset/nanoui/send(client, uncommon)
if(!islist(uncommon))
uncommon = list(uncommon)
SSassets.transport.send_assets(client, uncommon)
SSassets.transport.send_assets(client, common)
@@ -10,11 +10,11 @@
*
*/
PROCESSING_SUBSYSTEM_DEF(tgui)
SUBSYSTEM_DEF(tgui)
name = "tgui"
wait = 9
flags = SS_NO_INIT
priority = SS_PRIORITY_NANOUI
priority = FIRE_PRIORITY_TGUI
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
/// A list of UIs scheduled to process
@@ -24,54 +24,39 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
/// The HTML base used for all UIs.
var/basehtml
/datum/controller/subsystem/processing/tgui/PreInit()
basehtml = file2text('tgui/public/tgui.html')
// Inject inline polyfills
var/polyfill = file2text('tgui/public/tgui-polyfill.min.js')
polyfill = "<script>\n[polyfill]\n</script>"
basehtml = replacetextEx(basehtml, "<!-- tgui:inline-polyfill -->", polyfill)
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "NanoTrasen © 2457-[text2num(time2text(world.realtime, "YYYY")) + 442]")
/datum/controller/subsystem/tgui/PreInit()
basehtml = file2text("tgui/public/tgui.html")
basehtml = replacetext(basehtml, "tgui:stylesheet", MAP_STYLESHEET)
// Inject inline helper functions
var/helpers = file2text("tgui/public/helpers.min.js")
helpers = "<script type='text/javascript'>\n[helpers]\n</script>"
basehtml = replacetextEx(basehtml, "<!-- tgui:helpers -->", helpers)
/datum/controller/subsystem/processing/tgui/OnConfigLoad()
var/storage_iframe = GLOB.config.storage_cdn_iframe
if(storage_iframe && storage_iframe != /datum/configuration::storage_cdn_iframe)
basehtml = replacetextEx(basehtml, "tgui:storagecdn", storage_iframe)
return
// Inject inline ntos-error styles
var/ntos_error = file2text("tgui/public/ntos-error.min.css")
ntos_error = "<style type='text/css'>\n[ntos_error]\n</style>"
basehtml = replacetextEx(basehtml, "<!-- tgui:ntos-error -->", ntos_error)
if(GLOB.config.asset_transport == "webroot")
var/datum/asset_transport/webroot/webroot = SSassets.transport
basehtml = replacetextEx(basehtml, "<!-- tgui:nt-copyright -->", "Nanotrasen (c) 2395-[num2text(GLOB.game_year)]")
var/datum/asset_cache_item/item = webroot.register_asset("iframe.html", file("tgui/public/iframe.html"))
basehtml = replacetext(basehtml, "tgui:storagecdn", webroot.get_asset_url("iframe.html", item))
return
if(!storage_iframe)
return
basehtml = replacetextEx(basehtml, "tgui:storagecdn", storage_iframe)
/datum/controller/subsystem/processing/tgui/Shutdown()
/datum/controller/subsystem/tgui/Shutdown()
close_all_uis()
/datum/controller/subsystem/processing/tgui/stat_entry(msg)
/datum/controller/subsystem/tgui/stat_entry(msg)
msg = "P:[length(all_uis)]"
return ..()
/datum/controller/subsystem/processing/tgui/fire(resumed = FALSE)
CAN_BE_REDEFINED(TRUE)
/datum/controller/subsystem/tgui/fire(resumed = FALSE)
if(!resumed)
src.current_run = all_uis.Copy()
// Cache for sanic speed (lists are references anyways)
var/list/current_run = src.current_run
var/seconds_per_tick = wait * 0.1
while(current_run.len)
var/datum/tgui/ui = current_run[current_run.len]
current_run.len--
// TODO: Move user/src_object check to process()
if(ui?.user && ui.src_object)
ui.process(seconds_per_tick)
ui.process(wait * 0.1)
else
ui.close(0)
if(MC_TICK_CHECK)
@@ -84,9 +69,9 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
* Returns null if pool was exhausted.
*
* required user mob
* return datum/tgui
* return datum/tgui_window
*/
/datum/controller/subsystem/processing/tgui/proc/request_pooled_window(mob/user)
/datum/controller/subsystem/tgui/proc/request_pooled_window(mob/user)
if(!user.client)
return null
var/list/windows = user.client.tgui_windows
@@ -122,7 +107,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* required user mob
*/
/datum/controller/subsystem/processing/tgui/proc/force_close_all_windows(mob/user)
/datum/controller/subsystem/tgui/proc/force_close_all_windows(mob/user)
log_tgui(user, context = "SStgui/force_close_all_windows")
if(user.client)
user.client.tgui_windows = list()
@@ -138,14 +123,12 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
* required user mob
* required window_id string
*/
/datum/controller/subsystem/processing/tgui/proc/force_close_window(mob/user, window_id)
/datum/controller/subsystem/tgui/proc/force_close_window(mob/user, window_id)
log_tgui(user, context = "SStgui/force_close_window")
// Close all tgui datums based on window_id.
for(var/datum/tgui/ui in user.tgui_open_uis)
if(ui.window && ui.window.id == window_id)
ui.close(can_be_suspended = FALSE)
// Unset machine just to be sure.
user.unset_machine()
// Close window directly just to be sure.
user << browse(null, "window=[window_id]")
@@ -161,7 +144,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return datum/tgui The found UI.
*/
/datum/controller/subsystem/processing/tgui/proc/try_update_ui(
/datum/controller/subsystem/tgui/proc/try_update_ui(
mob/user,
datum/src_object,
datum/tgui/ui)
@@ -191,7 +174,8 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return datum/tgui The found UI.
*/
/datum/controller/subsystem/processing/tgui/proc/get_open_ui(mob/user, datum/src_object)
/datum/controller/subsystem/tgui/proc/get_open_ui(mob/user, datum/src_object)
// No UIs opened for this src_object
if(!LAZYLEN(src_object?.open_uis))
return null
for(var/datum/tgui/ui in src_object.open_uis)
@@ -209,15 +193,15 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs updated.
*/
/datum/controller/subsystem/processing/tgui/proc/update_uis(datum/src_object)
/datum/controller/subsystem/tgui/proc/update_uis(datum/src_object)
// No UIs opened for this src_object
if(!LAZYLEN(src_object?.open_uis))
return 0
var/count = 0
var/seconds_per_tick = wait * 0.1
for(var/datum/tgui/ui in src_object.open_uis)
// Check if UI is valid.
if(ui?.src_object && ui.user && ui.src_object.ui_host(ui.user))
INVOKE_ASYNC(ui, TYPE_PROC_REF(/datum/tgui, process), seconds_per_tick, TRUE)
INVOKE_ASYNC(ui, TYPE_PROC_REF(/datum/tgui, process), wait * 0.1, TRUE)
count++
return count
@@ -230,7 +214,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/close_uis(datum/src_object)
/datum/controller/subsystem/tgui/proc/close_uis(datum/src_object)
// No UIs opened for this src_object
if(!LAZYLEN(src_object?.open_uis))
return 0
@@ -249,7 +233,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/close_all_uis()
/datum/controller/subsystem/tgui/proc/close_all_uis()
var/count = 0
for(var/datum/tgui/ui in all_uis)
// Check if UI is valid.
@@ -268,14 +252,13 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs updated.
*/
/datum/controller/subsystem/processing/tgui/proc/update_user_uis(mob/user, datum/src_object)
/datum/controller/subsystem/tgui/proc/update_user_uis(mob/user, datum/src_object)
var/count = 0
if(length(user?.tgui_open_uis) == 0)
return count
var/seconds_per_tick = wait * 0.1
for(var/datum/tgui/ui in user.tgui_open_uis)
if(isnull(src_object) || ui.src_object == src_object)
ui.process(seconds_per_tick, force = 1)
ui.process(wait * 0.1, force = 1)
count++
return count
@@ -289,7 +272,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/close_user_uis(mob/user, datum/src_object)
/datum/controller/subsystem/tgui/proc/close_user_uis(mob/user, datum/src_object)
var/count = 0
if(length(user?.tgui_open_uis) == 0)
return count
@@ -306,7 +289,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* required ui datum/tgui The UI to be added.
*/
/datum/controller/subsystem/processing/tgui/proc/on_open(datum/tgui/ui)
/datum/controller/subsystem/tgui/proc/on_open(datum/tgui/ui)
ui.user?.tgui_open_uis |= ui
LAZYOR(ui.src_object.open_uis, ui)
all_uis |= ui
@@ -320,7 +303,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return bool If the UI was removed or not.
*/
/datum/controller/subsystem/processing/tgui/proc/on_close(datum/tgui/ui)
/datum/controller/subsystem/tgui/proc/on_close(datum/tgui/ui)
// Remove it from the list of processing UIs.
all_uis -= ui
current_run -= ui
@@ -340,7 +323,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return int The number of UIs closed.
*/
/datum/controller/subsystem/processing/tgui/proc/on_logout(mob/user)
/datum/controller/subsystem/tgui/proc/on_logout(mob/user)
close_user_uis(user)
/**
@@ -353,7 +336,7 @@ PROCESSING_SUBSYSTEM_DEF(tgui)
*
* return bool If the UIs were transferred.
*/
/datum/controller/subsystem/processing/tgui/proc/on_transfer(mob/source, mob/target)
/datum/controller/subsystem/tgui/proc/on_transfer(mob/source, mob/target)
// The old mob had no open UIs.
if(length(source?.tgui_open_uis) == 0)
return FALSE