diff --git a/code/modules/tgui/external.dm b/code/modules/tgui/external.dm index 7e75f9343c..bb39c721d0 100644 --- a/code/modules/tgui/external.dm +++ b/code/modules/tgui/external.dm @@ -12,9 +12,10 @@ * * required user mob The mob who opened/is using the UI. * optional ui datum/tgui The UI to be updated, if it exists. + * optional parent_ui datum/tgui A parent UI that, when closed, closes this UI as well. */ -/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null) +/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null, datum/tgui/parent_ui = null) return FALSE // Not implemented. /** @@ -27,7 +28,7 @@ * * return list Data to be sent to the UI. */ -/datum/proc/tgui_data(mob/user) +/datum/proc/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) return list() // Not implemented. /** @@ -140,6 +141,26 @@ */ /datum/proc/tgui_close(mob/user) +/** + * verb + * + * Used by a client to fix broken TGUI windows caused by opening a UI window before assets load. + * Probably not very performant and forcibly destroys a bunch of windows, so it has some warnings attached. + * Conveniently, also allows devs to force a dev server reattach without relogging, since it yeets windows. + */ +/client/verb/tgui_fix_white() + set desc = "Only use this if you have a broken TGUI window occupying your screen!" + set name = "Fix TGUI" + set category = "OOC" + + if(alert(src, "Only use this verb if you have a white TGUI window stuck on your screen.", "Fix TGUI", "Continue", "Nevermind") != "Continue") + return + + SStgui.close_user_uis(mob) + if(alert(src, "Did that fix the problem?", "Fix TGUI", "Yes", "No") == "No") + SStgui.force_close_all_windows(mob) + alert(src, "UIs should be fixed now. If not, please cry to your nearest coder.", "Fix TGUI") + /** * verb * diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm index 9ccc9c751e..ba8d132e0b 100644 --- a/code/modules/tgui/states/default.dm +++ b/code/modules/tgui/states/default.dm @@ -36,13 +36,29 @@ GLOBAL_DATUM_INIT(tgui_default_state, /datum/tgui_state/default, new) return STATUS_DISABLED // Otherwise they can keep the UI open. /mob/living/silicon/ai/default_can_use_tgui_topic(src_object) - . = shared_tgui_interaction(src_object) - if(. < STATUS_INTERACTIVE) + . = shared_tgui_interaction() + if(. != STATUS_INTERACTIVE) return - // The AI can interact with anything it can see nearby, or with cameras while wireless control is enabled. - if(!control_disabled && can_see(src_object)) + // Prevents the AI from using Topic on admin levels (by for example viewing through the court/thunderdome cameras) + // unless it's on the same level as the object it's interacting with. + var/turf/T = get_turf(src_object) + if(!T || !(z == T.z || (T.z in using_map.player_levels))) + return STATUS_CLOSE + + // If an object is in view then we can interact with it + if(src_object in view(client.view, src)) return STATUS_INTERACTIVE + + // If we're installed in a chassi, rather than transfered to an inteliCard or other container, then check if we have camera view + if(is_in_chassis()) + //stop AIs from leaving windows open and using then after they lose vision + if(cameranet && !cameranet.checkTurfVis(get_turf(src_object))) + return STATUS_CLOSE + return STATUS_INTERACTIVE + else if(get_dist(src_object, src) <= client.view) // View does not return what one would expect while installed in an inteliCard + return STATUS_INTERACTIVE + return STATUS_CLOSE /mob/living/simple_animal/default_can_use_tgui_topic(src_object) diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 9af1296ab0..2f928ae6fa 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -34,8 +34,12 @@ var/status = STATUS_INTERACTIVE /// Topic state used to determine status/interactability. var/datum/tgui_state/state = null - // The map z-level to display. + /// The map z-level to display. var/map_z_level = 1 + /// The Parent UI + var/datum/tgui/parent_ui + /// Children of this UI + var/list/children = list() /** * public @@ -46,12 +50,13 @@ * required src_object datum The object or datum which owns the UI. * required interface string The interface used to render the UI. * optional title string The title of the UI. + * optional parent_ui datum/tgui The parent of this UI. * optional ui_x int Deprecated: Window width. * optional ui_y int Deprecated: Window height. * * return datum/tgui The requested UI. */ -/datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y) +/datum/tgui/New(mob/user, datum/src_object, interface, title, datum/tgui/parent_ui, ui_x, ui_y) src.user = user src.src_object = src_object src.window_key = "[REF(src_object)]-main" @@ -59,6 +64,9 @@ if(title) src.title = title src.state = src_object.tgui_state() + src.parent_ui = parent_ui + if(parent_ui) + parent_ui.children += src // Deprecated if(ui_x && ui_y) src.window_size = list(ui_x, ui_y) @@ -100,10 +108,13 @@ * * Close the UI, and all its children. */ -/datum/tgui/proc/close(can_be_suspended = TRUE) +/datum/tgui/proc/close(can_be_suspended = TRUE, logout = FALSE) if(closing) return closing = TRUE + for(var/datum/tgui/child in children) + child.close(can_be_suspended, logout) + children.Cut() // If we don't have window_id, open proc did not have the opportunity // to finish, therefore it's safe to skip this whole block. if(window) @@ -111,10 +122,11 @@ // and we want to keep them around, to allow user to read // the error message properly. window.release_lock() - window.close(can_be_suspended) + window.close(can_be_suspended, logout) src_object.tgui_close(user) SStgui.on_close(src) state = null + parent_ui = null qdel(src) /** @@ -209,7 +221,7 @@ "observer" = isobserver(user), ), ) - var/data = custom_data || with_data && src_object.tgui_data(user) + var/data = custom_data || with_data && src_object.tgui_data(user, src, state) if(data) json_data["data"] = data var/static_data = with_static_data && src_object.tgui_static_data(user) @@ -244,7 +256,7 @@ return // Update through a normal call to ui_interact if(status != STATUS_DISABLED && (autoupdate || force)) - src_object.tgui_interact(user, src) + src_object.tgui_interact(user, src, parent_ui) return // Update status only var/needs_update = process_status() @@ -262,6 +274,8 @@ /datum/tgui/proc/process_status() var/prev_status = status status = src_object.tgui_status(user, state) + if(parent_ui) + status = min(status, parent_ui.status) return prev_status != status /datum/tgui/proc/log_message(message) diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index 18cf715368..c78eda86d3 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -133,13 +133,19 @@ * * optional can_be_suspended bool */ -/datum/tgui_window/proc/close(can_be_suspended = TRUE) +/datum/tgui_window/proc/close(can_be_suspended = TRUE, logout = FALSE) if(!client) return if(can_be_suspended && can_be_suspended()) log_tgui(client, "[id]/close: suspending") status = TGUI_WINDOW_READY send_message("suspend") + // You would think that BYOND would null out client or make it stop passing istypes or, y'know, ANYTHING during + // logout, but nope! It appears to be perfectly valid to call winset by every means we can measure in Logout, + // and yet it causes a bad client runtime. To avoid that happening, we just have to know if we're in Logout or + // not. + if(!logout && client) + winset(client, null, "mapwindow.map.focus=true") return log_tgui(client, "[id]/close") release_lock() @@ -149,7 +155,8 @@ // to read the error message. if(!fatally_errored) client << browse(null, "window=[id]") - + if(!logout && client) + winset(client, null, "mapwindow.map.focus=true") /** * public * @@ -189,9 +196,9 @@ /datum/tgui_window/proc/send_asset(datum/asset/asset) if(!client || !asset) return - // if(istype(asset, /datum/asset/spritesheet)) - // var/datum/asset/spritesheet/spritesheet = asset - // send_message("asset/stylesheet", spritesheet.css_filename()) + if(istype(asset, /datum/asset/spritesheet)) + var/datum/asset/spritesheet/spritesheet = asset + send_message("asset/stylesheet", spritesheet.css_filename()) send_message("asset/mappings", asset.get_url_mappings()) sent_assets += list(asset) asset.send(client)