mirror of
https://github.com/PolarisSS13/Polaris.git
synced 2026-08-27 15:17:21 +01:00
TGUI 4.0 & Camera Console
This commit is contained in:
@@ -11,14 +11,10 @@
|
||||
* If this proc is not implemented properly, the UI will not update correctly.
|
||||
*
|
||||
* required user mob The mob who opened/is using the UI.
|
||||
* optional ui_key string The ui_key of the UI.
|
||||
* optional ui datum/tgui The UI to be updated, if it exists.
|
||||
* optional force_open bool If the UI should be re-opened instead of updated.
|
||||
* optional master_ui datum/tgui The parent UI.
|
||||
* optional state datum/ui_state The state used to determine status.
|
||||
*/
|
||||
|
||||
/datum/proc/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
|
||||
/datum/proc/tgui_interact(mob/user, datum/tgui/ui = null)
|
||||
return FALSE // Not implemented.
|
||||
|
||||
/**
|
||||
@@ -38,10 +34,10 @@
|
||||
* public
|
||||
*
|
||||
* Static Data to be sent to the UI.
|
||||
* Static data differs from normal data in that it's large data that should be sent infrequently
|
||||
* This is implemented optionally for heavy uis that would be sending a lot of redundant data
|
||||
* frequently.
|
||||
* Gets squished into one object on the frontend side, but the static part is cached.
|
||||
* Static data differs from normal data in that it's large data that should be
|
||||
* sent infrequently. This is implemented optionally for heavy uis that would
|
||||
* be sending a lot of redundant data frequently. Gets squished into one
|
||||
* object on the frontend side, but the static part is cached.
|
||||
*
|
||||
* required user mob The mob interacting with the UI.
|
||||
*
|
||||
@@ -53,18 +49,19 @@
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Forces an update on static data. Should be done manually whenever something happens to change static data.
|
||||
* Forces an update on static data. Should be done manually whenever something
|
||||
* happens to change static data.
|
||||
*
|
||||
* required user the mob currently interacting with the ui
|
||||
* optional ui ui to be updated
|
||||
* optional ui_key ui key of ui to be updated
|
||||
*/
|
||||
/datum/proc/update_tgui_static_data(mob/user, datum/tgui/ui, ui_key = "main")
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui)
|
||||
/datum/proc/update_tgui_static_data(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
// If there was no ui to update, there's no static data to update either.
|
||||
if(!ui)
|
||||
return
|
||||
ui.push_data(null, tgui_static_data(), TRUE)
|
||||
ui = SStgui.get_open_ui(user, src)
|
||||
if(ui)
|
||||
ui.send_full_update()
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -86,17 +83,12 @@
|
||||
* public
|
||||
*
|
||||
* Called on an object when a tgui object is being created, allowing you to
|
||||
* customise the html
|
||||
* For example: inserting a custom stylesheet that you need in the head
|
||||
* push various assets to tgui, for examples spritesheets.
|
||||
*
|
||||
* For this purpose, some tags are available in the html, to be parsed out
|
||||
^ with replacetext
|
||||
* (customheadhtml) - Additions to the head tag
|
||||
*
|
||||
* required html the html base text
|
||||
* return list List of asset datums or file paths.
|
||||
*/
|
||||
/datum/proc/tgui_base_html(html)
|
||||
return html
|
||||
/datum/proc/ui_assets(mob/user)
|
||||
return list()
|
||||
|
||||
/**
|
||||
* private
|
||||
@@ -108,6 +100,15 @@
|
||||
/datum/proc/tgui_host(mob/user)
|
||||
return src // Default src.
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* The UI's state controller to be used for created uis
|
||||
* This is a proc over a var for memory reasons
|
||||
*/
|
||||
/datum/proc/tgui_state(mob/user)
|
||||
return GLOB.tgui_default_state
|
||||
|
||||
/**
|
||||
* global
|
||||
*
|
||||
@@ -120,9 +121,17 @@
|
||||
/**
|
||||
* global
|
||||
*
|
||||
* Used to track UIs for a mob.
|
||||
* Tracks open UIs for a user.
|
||||
*/
|
||||
/mob/var/list/open_tguis = list()
|
||||
/mob/var/list/tgui_open_uis = list()
|
||||
|
||||
/**
|
||||
* global
|
||||
*
|
||||
* Tracks open windows for a user.
|
||||
*/
|
||||
/client/var/list/tgui_windows = list()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
@@ -139,17 +148,44 @@
|
||||
*
|
||||
* required uiref ref The UI that was closed.
|
||||
*/
|
||||
/client/verb/tguiclose(ref as text)
|
||||
/client/verb/tguiclose(window_id as text)
|
||||
// Name the verb, and hide it from the user panel.
|
||||
set name = "uiclose"
|
||||
set hidden = TRUE
|
||||
|
||||
// Get the UI based on the ref.
|
||||
var/datum/tgui/ui = locate(ref)
|
||||
var/mob/user = src && src.mob
|
||||
if(!user)
|
||||
return
|
||||
// Close all tgui datums based on window_id.
|
||||
SStgui.force_close_window(user, window_id)
|
||||
|
||||
// If we found the UI, close it.
|
||||
if(istype(ui))
|
||||
ui.close()
|
||||
// Unset machine just to be sure.
|
||||
if(src && src.mob)
|
||||
src.mob.unset_machine()
|
||||
/**
|
||||
* Middleware for /client/Topic.
|
||||
*
|
||||
* return bool Whether the topic is passed (TRUE), or cancelled (FALSE).
|
||||
*/
|
||||
/proc/tgui_Topic(href_list)
|
||||
// Skip non-tgui topics
|
||||
if(!href_list["tgui"])
|
||||
return TRUE
|
||||
var/type = href_list["type"]
|
||||
// Unconditionally collect tgui logs
|
||||
if(type == "log")
|
||||
log_tgui(usr, href_list["message"])
|
||||
// Locate window
|
||||
var/window_id = href_list["window_id"]
|
||||
var/datum/tgui_window/window
|
||||
if(window_id)
|
||||
window = usr.client.tgui_windows[window_id]
|
||||
if(!window)
|
||||
log_tgui(usr, "Error: Couldn't find the window datum, force closing.")
|
||||
SStgui.force_close_window(usr, window_id)
|
||||
return FALSE
|
||||
// Decode payload
|
||||
var/payload
|
||||
if(href_list["payload"])
|
||||
payload = json_decode(href_list["payload"])
|
||||
// Pass message to window
|
||||
if(window)
|
||||
window.on_message(type, payload, href_list)
|
||||
return FALSE
|
||||
|
||||
@@ -9,6 +9,9 @@ Code is pretty much ripped verbatim from nano modules, but with un-needed stuff
|
||||
/datum/tgui_module
|
||||
var/name
|
||||
var/datum/host
|
||||
var/list/using_access
|
||||
|
||||
var/tgui_id
|
||||
|
||||
/datum/tgui_module/New(var/host)
|
||||
src.host = host
|
||||
@@ -19,3 +22,25 @@ Code is pretty much ripped verbatim from nano modules, but with un-needed stuff
|
||||
/datum/tgui_module/tgui_close(mob/user)
|
||||
if(host)
|
||||
host.tgui_close(user)
|
||||
|
||||
/datum/tgui_module/proc/check_access(mob/user, access)
|
||||
if(!access)
|
||||
return 1
|
||||
|
||||
if(using_access)
|
||||
if(access in using_access)
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
|
||||
if(!istype(user))
|
||||
return 0
|
||||
|
||||
var/obj/item/weapon/card/id/I = user.GetIdCard()
|
||||
if(!I)
|
||||
return 0
|
||||
|
||||
if(access in I.access)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
@@ -0,0 +1,325 @@
|
||||
/datum/tgui_module/camera
|
||||
name = "Security Cameras"
|
||||
tgui_id = "CameraConsole"
|
||||
|
||||
var/access_based = FALSE
|
||||
var/list/network = list()
|
||||
var/list/additional_networks = list()
|
||||
|
||||
var/obj/machinery/camera/active_camera
|
||||
var/list/concurrent_users = list()
|
||||
|
||||
// Stuff needed to render the map
|
||||
var/map_name
|
||||
var/const/default_map_size = 15
|
||||
var/obj/screen/map_view/cam_screen
|
||||
/// All the plane masters that need to be applied.
|
||||
var/list/cam_plane_masters
|
||||
var/obj/screen/background/cam_background
|
||||
var/obj/screen/background/cam_foreground
|
||||
var/obj/screen/skybox/local_skybox
|
||||
// Needed for moving camera support
|
||||
var/camera_diff_x = -1
|
||||
var/camera_diff_y = -1
|
||||
var/camera_diff_z = -1
|
||||
|
||||
/datum/tgui_module/camera/New(host, list/network_computer)
|
||||
. = ..()
|
||||
if(!LAZYLEN(network_computer))
|
||||
access_based = TRUE
|
||||
else
|
||||
network = network_computer
|
||||
map_name = "camera_console_[REF(src)]_map"
|
||||
// Initialize map objects
|
||||
cam_screen = new
|
||||
cam_screen.name = "screen"
|
||||
cam_screen.assigned_map = map_name
|
||||
cam_screen.del_on_map_removal = FALSE
|
||||
cam_screen.screen_loc = "[map_name]:1,1"
|
||||
|
||||
cam_plane_masters = list()
|
||||
|
||||
// 'Utility' planes
|
||||
cam_plane_masters += new /obj/screen/plane_master/fullbright //Lighting system (lighting_overlay objects)
|
||||
cam_plane_masters += new /obj/screen/plane_master/lighting //Lighting system (but different!)
|
||||
cam_plane_masters += new /obj/screen/plane_master/ghosts //Ghosts!
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_AI_EYE} //AI Eye!
|
||||
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_STATUS} //Status is the synth/human icon left side of medhuds
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_HEALTH} //Health bar
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_LIFE} //Alive-or-not icon
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_ID} //Job ID icon
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_WANTED} //Wanted status
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_IMPLOYAL} //Loyalty implants
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_IMPTRACK} //Tracking implants
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_IMPCHEM} //Chemical implants
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_SPECIAL} //"Special" role stuff
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_CH_STATUS_OOC} //OOC status HUD
|
||||
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_ADMIN1} //For admin use
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_ADMIN2} //For admin use
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_ADMIN3} //For admin use
|
||||
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_MESONS} //Meson-specific things like open ceilings.
|
||||
cam_plane_masters += new /obj/screen/plane_master{plane = PLANE_BUILDMODE} //Things that only show up while in build mode
|
||||
|
||||
// Real tangible stuff planes
|
||||
cam_plane_masters += new /obj/screen/plane_master/main{plane = TURF_PLANE}
|
||||
cam_plane_masters += new /obj/screen/plane_master/main{plane = OBJ_PLANE}
|
||||
cam_plane_masters += new /obj/screen/plane_master/main{plane = MOB_PLANE}
|
||||
cam_plane_masters += new /obj/screen/plane_master/cloaked //Cloaked atoms!
|
||||
|
||||
for(var/plane in cam_plane_masters)
|
||||
var/obj/screen/instance = plane
|
||||
instance.assigned_map = map_name
|
||||
instance.del_on_map_removal = FALSE
|
||||
instance.screen_loc = "[map_name]:CENTER"
|
||||
|
||||
local_skybox = new()
|
||||
local_skybox.assigned_map = map_name
|
||||
local_skybox.del_on_map_removal = FALSE
|
||||
local_skybox.screen_loc = "[map_name]:CENTER,CENTER"
|
||||
cam_plane_masters += local_skybox
|
||||
|
||||
cam_background = new
|
||||
cam_background.assigned_map = map_name
|
||||
cam_background.del_on_map_removal = FALSE
|
||||
|
||||
var/mutable_appearance/scanlines = mutable_appearance('icons/effects/static.dmi', "scanlines")
|
||||
scanlines.alpha = 50
|
||||
scanlines.layer = FULLSCREEN_LAYER
|
||||
|
||||
var/mutable_appearance/noise = mutable_appearance('icons/effects/static.dmi', "1 light")
|
||||
noise.layer = FULLSCREEN_LAYER
|
||||
|
||||
cam_foreground = new
|
||||
cam_foreground.assigned_map = map_name
|
||||
cam_foreground.del_on_map_removal = FALSE
|
||||
cam_foreground.plane = PLANE_FULLSCREEN
|
||||
cam_foreground.add_overlay(scanlines)
|
||||
cam_foreground.add_overlay(noise)
|
||||
|
||||
/datum/tgui_module/camera/Destroy()
|
||||
qdel(cam_screen)
|
||||
QDEL_LIST(cam_plane_masters)
|
||||
qdel(cam_background)
|
||||
qdel(cam_foreground)
|
||||
return ..()
|
||||
|
||||
/datum/tgui_module/camera/tgui_interact(mob/user, datum/tgui/ui = null)
|
||||
// Update UI
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
// Show static if can't use the camera
|
||||
if(!active_camera?.can_use())
|
||||
show_camera_static()
|
||||
if(!ui)
|
||||
var/user_ref = REF(user)
|
||||
var/is_living = isliving(user)
|
||||
// Ghosts shouldn't count towards concurrent users, which produces
|
||||
// an audible terminal_on click.
|
||||
if(is_living)
|
||||
concurrent_users += user_ref
|
||||
// Turn on the console
|
||||
if(length(concurrent_users) == 1 && is_living)
|
||||
playsound(tgui_host(), 'sound/machines/terminal_on.ogg', 25, FALSE)
|
||||
// Register map objects
|
||||
user.client.register_map_obj(cam_screen)
|
||||
for(var/plane in cam_plane_masters)
|
||||
user.client.register_map_obj(plane)
|
||||
user.client.register_map_obj(cam_background)
|
||||
user.client.register_map_obj(cam_foreground)
|
||||
// Open UI
|
||||
ui = new(user, src, tgui_id, name)
|
||||
ui.open()
|
||||
|
||||
/datum/tgui_module/camera/tgui_data()
|
||||
var/list/data = list()
|
||||
data["activeCamera"] = null
|
||||
if(active_camera)
|
||||
differential_check()
|
||||
data["activeCamera"] = list(
|
||||
name = active_camera.c_tag,
|
||||
status = active_camera.status,
|
||||
)
|
||||
return data
|
||||
|
||||
/datum/tgui_module/camera/tgui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["mapRef"] = map_name
|
||||
var/list/cameras = get_available_cameras(user)
|
||||
data["cameras"] = list()
|
||||
data["allNetworks"] = list()
|
||||
for(var/i in cameras)
|
||||
var/obj/machinery/camera/C = cameras[i]
|
||||
data["cameras"] += list(list(
|
||||
name = C.c_tag,
|
||||
networks = C.network
|
||||
))
|
||||
data["allNetworks"] |= C.network
|
||||
return data
|
||||
|
||||
/datum/tgui_module/camera/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
|
||||
if(action == "switch_camera")
|
||||
var/c_tag = params["name"]
|
||||
var/list/cameras = get_available_cameras(usr)
|
||||
var/obj/machinery/camera/C = cameras["[ckey(c_tag)]"]
|
||||
active_camera = C
|
||||
playsound(tgui_host(), get_sfx("terminal_type"), 25, FALSE)
|
||||
|
||||
reload_cameraview()
|
||||
|
||||
return TRUE
|
||||
|
||||
/datum/tgui_module/camera/proc/differential_check()
|
||||
var/turf/T = get_turf(active_camera)
|
||||
if(T)
|
||||
var/new_x = T.x
|
||||
var/new_y = T.y
|
||||
var/new_z = T.z
|
||||
if((new_x != camera_diff_x) || (new_y != camera_diff_y) || (new_z != camera_diff_z))
|
||||
reload_cameraview()
|
||||
|
||||
/datum/tgui_module/camera/proc/reload_cameraview()
|
||||
// Show static if can't use the camera
|
||||
if(!active_camera?.can_use())
|
||||
show_camera_static()
|
||||
return TRUE
|
||||
|
||||
var/turf/camTurf = get_turf(active_camera)
|
||||
|
||||
camera_diff_x = camTurf.x
|
||||
camera_diff_y = camTurf.y
|
||||
camera_diff_z = camTurf.z
|
||||
|
||||
var/list/visible_turfs = list()
|
||||
for(var/turf/T in (active_camera.isXRay() \
|
||||
? range(active_camera.view_range, camTurf) \
|
||||
: view(active_camera.view_range, camTurf)))
|
||||
visible_turfs += T
|
||||
|
||||
var/list/bbox = get_bbox_of_atoms(visible_turfs)
|
||||
var/size_x = bbox[3] - bbox[1] + 1
|
||||
var/size_y = bbox[4] - bbox[2] + 1
|
||||
|
||||
cam_screen.vis_contents = visible_turfs
|
||||
cam_background.icon_state = "clear"
|
||||
cam_background.fill_rect(1, 1, size_x, size_y)
|
||||
|
||||
cam_foreground.fill_rect(1, 1, size_x, size_y)
|
||||
|
||||
local_skybox.cut_overlays()
|
||||
local_skybox.add_overlay(SSskybox.get_skybox(get_z(camTurf)))
|
||||
local_skybox.scale_to_view(size_x)
|
||||
local_skybox.set_position("CENTER", "CENTER", (world.maxx>>1) - camTurf.x, (world.maxy>>1) - camTurf.y)
|
||||
|
||||
// Returns the list of cameras accessible from this computer
|
||||
// This proc operates in two distinct ways depending on the context in which the module is created.
|
||||
// It can either return a list of cameras sharing the same the internal `network` variable, or
|
||||
// It can scan all station networks and determine what cameras to show based on the access of the user.
|
||||
/datum/tgui_module/camera/proc/get_available_cameras(mob/user)
|
||||
var/list/all_networks = list()
|
||||
// Access Based
|
||||
if(access_based)
|
||||
for(var/network in using_map.station_networks)
|
||||
if(can_access_network(user, get_camera_access(network), 1))
|
||||
all_networks.Add(network)
|
||||
for(var/network in using_map.secondary_networks)
|
||||
if(can_access_network(user, get_camera_access(network), 0))
|
||||
all_networks.Add(network)
|
||||
// Network Based
|
||||
else
|
||||
all_networks = network.Copy()
|
||||
|
||||
if(additional_networks)
|
||||
all_networks += additional_networks
|
||||
|
||||
var/list/D = list()
|
||||
for(var/obj/machinery/camera/C in cameranet.cameras)
|
||||
if(!C.network)
|
||||
stack_trace("Camera in a cameranet has no camera network")
|
||||
continue
|
||||
if(!(islist(C.network)))
|
||||
stack_trace("Camera in a cameranet has a non-list camera network")
|
||||
continue
|
||||
var/list/tempnetwork = C.network & all_networks
|
||||
if(tempnetwork.len)
|
||||
D["[ckey(C.c_tag)]"] = C
|
||||
return D
|
||||
|
||||
/datum/tgui_module/camera/proc/can_access_network(mob/user, network_access, station_network = 0)
|
||||
// No access passed, or 0 which is considered no access requirement. Allow it.
|
||||
if(!network_access)
|
||||
return 1
|
||||
|
||||
if(station_network)
|
||||
return check_access(user, network_access) || check_access(user, access_security) || check_access(user, access_heads)
|
||||
else
|
||||
return check_access(user, network_access)
|
||||
|
||||
/datum/tgui_module/camera/proc/show_camera_static()
|
||||
cam_screen.vis_contents.Cut()
|
||||
cam_background.icon_state = "scanline2"
|
||||
cam_background.fill_rect(1, 1, default_map_size, default_map_size)
|
||||
local_skybox.cut_overlays()
|
||||
|
||||
/datum/tgui_module/camera/tgui_close(mob/user)
|
||||
. = ..()
|
||||
var/user_ref = REF(user)
|
||||
var/is_living = isliving(user)
|
||||
// living creature or not, we remove you anyway.
|
||||
concurrent_users -= user_ref
|
||||
// Unregister map objects
|
||||
if(user.client)
|
||||
user.client.clear_map(map_name)
|
||||
// Turn off the console
|
||||
if(length(concurrent_users) == 0 && is_living)
|
||||
active_camera = null
|
||||
playsound(tgui_host(), 'sound/machines/terminal_off.ogg', 25, FALSE)
|
||||
|
||||
// NTOS Version
|
||||
// Please note, this isn't a very good replacement for converting modular computers 100% to TGUI
|
||||
// If/when that is done, just move all the PC_ specific data and stuff to the modular computers themselves
|
||||
// instead of copying this approach here.
|
||||
/datum/tgui_module/camera/ntos
|
||||
tgui_id = "NtosCameraConsole"
|
||||
|
||||
/datum/tgui_module/camera/ntos/tgui_state()
|
||||
return GLOB.tgui_ntos_state
|
||||
|
||||
/datum/tgui_module/camera/ntos/tgui_static_data()
|
||||
. = ..()
|
||||
|
||||
var/datum/computer_file/program/host = tgui_host()
|
||||
if(istype(host) && host.computer)
|
||||
. += host.computer.get_header_data()
|
||||
|
||||
/datum/tgui_module/camera/ntos/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
|
||||
var/datum/computer_file/program/host = tgui_host()
|
||||
if(istype(host) && host.computer)
|
||||
if(action == "PC_exit")
|
||||
host.computer.kill_program()
|
||||
return TRUE
|
||||
if(action == "PC_shutdown")
|
||||
host.computer.shutdown_computer()
|
||||
return TRUE
|
||||
if(action == "PC_minimize")
|
||||
host.computer.minimize_program(usr)
|
||||
return TRUE
|
||||
|
||||
// ERT Version provides some additional networks.
|
||||
/datum/tgui_module/camera/ntos/ert
|
||||
additional_networks = list(NETWORK_ERT, NETWORK_CRESCENT)
|
||||
|
||||
// Hacked version also provides some additional networks,
|
||||
// but we want it to show *all* the networks 24/7, so we convert it into a non-access-based UI.
|
||||
/datum/tgui_module/camera/ntos/hacked
|
||||
additional_networks = list(NETWORK_MERCENARY, NETWORK_ERT, NETWORK_CRESCENT)
|
||||
|
||||
/datum/tgui_module/camera/ntos/hacked/New(host)
|
||||
. = ..(host, using_map.station_networks.Copy())
|
||||
@@ -1,11 +1,12 @@
|
||||
/datum/tgui_module/crew_monitor
|
||||
name = "Crew monitor"
|
||||
tgui_id = "CrewMonitor"
|
||||
|
||||
/datum/tgui_module/crew_monitor/tgui_act(action, params)
|
||||
/datum/tgui_module/crew_monitor/tgui_act(action, params, datum/tgui/ui)
|
||||
if(..())
|
||||
return TRUE
|
||||
|
||||
var/turf/T = get_turf(tgui_host())
|
||||
var/turf/T = get_turf(usr)
|
||||
if(!T || !(T.z in using_map.player_levels))
|
||||
to_chat(usr, "<span class='warning'><b>Unable to establish a connection</b>: You're too far away from the station!</span>")
|
||||
return FALSE
|
||||
@@ -18,9 +19,12 @@
|
||||
if(hassensorlevel(H, SUIT_SENSOR_TRACKING))
|
||||
AI.ai_actual_track(H)
|
||||
return TRUE
|
||||
if("setZLevel")
|
||||
ui.set_map_z_level(params["mapZLevel"])
|
||||
SStgui.update_uis(src)
|
||||
|
||||
/datum/tgui_module/crew_monitor/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = 0, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
|
||||
var/z = get_z(tgui_host())
|
||||
/datum/tgui_module/crew_monitor/tgui_interact(mob/user, datum/tgui/ui = null)
|
||||
var/z = get_z(user)
|
||||
var/list/map_levels = using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)
|
||||
|
||||
if(!map_levels.len)
|
||||
@@ -29,19 +33,19 @@
|
||||
ui.close()
|
||||
return null
|
||||
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "CrewMonitor", name, 800, 600, master_ui, state)
|
||||
ui = new(user, src, tgui_id, name)
|
||||
ui.autoupdate = TRUE
|
||||
ui.open()
|
||||
|
||||
|
||||
/datum/tgui_module/crew_monitor/tgui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.tgui_default_state)
|
||||
/datum/tgui_module/crew_monitor/tgui_data(mob/user, ui_key = "main", datum/tgui_state/state = GLOB.tgui_default_state)
|
||||
var/data[0]
|
||||
|
||||
data["isAI"] = isAI(user)
|
||||
|
||||
var/z = get_z(tgui_host())
|
||||
var/z = get_z(user)
|
||||
var/list/map_levels = uniquelist(using_map.get_map_levels(z, TRUE, om_range = DEFAULT_OVERMAP_RANGE))
|
||||
data["map_levels"] = map_levels
|
||||
|
||||
@@ -50,3 +54,37 @@
|
||||
data["crewmembers"] += crew_repository.health_data(zlevel)
|
||||
|
||||
return data
|
||||
|
||||
/datum/tgui_module/crew_monitor/ntos
|
||||
tgui_id = "NtosCrewMonitor"
|
||||
|
||||
/datum/tgui_module/crew_monitor/ntos/tgui_state(mob/user)
|
||||
return GLOB.tgui_ntos_state
|
||||
|
||||
/datum/tgui_module/crew_monitor/ntos/tgui_static_data()
|
||||
. = ..()
|
||||
|
||||
var/datum/computer_file/program/host = tgui_host()
|
||||
if(istype(host) && host.computer)
|
||||
. += host.computer.get_header_data()
|
||||
|
||||
/datum/tgui_module/crew_monitor/ntos/tgui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
|
||||
var/datum/computer_file/program/host = tgui_host()
|
||||
if(istype(host) && host.computer)
|
||||
if(action == "PC_exit")
|
||||
host.computer.kill_program()
|
||||
return TRUE
|
||||
if(action == "PC_shutdown")
|
||||
host.computer.shutdown_computer()
|
||||
return TRUE
|
||||
if(action == "PC_minimize")
|
||||
host.computer.minimize_program(usr)
|
||||
return TRUE
|
||||
|
||||
// Subtype for self_state
|
||||
/datum/tgui_module/crew_monitor/robot
|
||||
/datum/tgui_module/crew_monitor/robot/tgui_state(mob/user)
|
||||
return GLOB.tgui_self_state
|
||||
|
||||
+22
-14
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* tgui states
|
||||
*
|
||||
* Base state and helpers for states. Just does some sanity checks, implement a state for in-depth checks.
|
||||
* Base state and helpers for states. Just does some sanity checks,
|
||||
* implement a proper state for in-depth checks.
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -26,9 +25,10 @@
|
||||
// . = max(., STATUS_INTERACTIVE)
|
||||
|
||||
// Regular ghosts can always at least view if in range.
|
||||
var/clientviewlist = getviewsize(user.client.view)
|
||||
if(get_dist(src_object, user) < max(clientviewlist[1],clientviewlist[2]))
|
||||
. = max(., STATUS_UPDATE)
|
||||
if(user.client)
|
||||
var/clientviewlist = getviewsize(user.client.view)
|
||||
if(get_dist(src_object, user) < max(clientviewlist[1], clientviewlist[2]))
|
||||
. = max(., STATUS_UPDATE)
|
||||
|
||||
// Check if the state allows interaction
|
||||
var/result = state.can_use_topic(src_object, user)
|
||||
@@ -46,7 +46,8 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/datum/tgui_state/proc/can_use_topic(src_object, mob/user)
|
||||
return STATUS_CLOSE // Don't allow interaction by default.
|
||||
// Don't allow interaction by default.
|
||||
return STATUS_CLOSE
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -56,21 +57,26 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/mob/proc/shared_tgui_interaction(src_object)
|
||||
if(!client) // Close UIs if mindless.
|
||||
// Close UIs if mindless.
|
||||
if(!client)
|
||||
return STATUS_CLOSE
|
||||
else if(stat) // Disable UIs if unconcious.
|
||||
// Disable UIs if unconcious.
|
||||
else if(stat)
|
||||
return STATUS_DISABLED
|
||||
else if(incapacitated()) // Update UIs if incapicitated but concious.
|
||||
// Update UIs if incapicitated but concious.
|
||||
else if(incapacitated())
|
||||
return STATUS_UPDATE
|
||||
return STATUS_INTERACTIVE
|
||||
|
||||
/mob/living/silicon/ai/shared_tgui_interaction(src_object)
|
||||
if(lacks_power()) // Disable UIs if the AI is unpowered.
|
||||
// Disable UIs if the AI is unpowered.
|
||||
if(lacks_power())
|
||||
return STATUS_DISABLED
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/robot/shared_tgui_interaction(src_object)
|
||||
if(!cell || cell.charge <= 0 || lockcharge) // Disable UIs if the Borg is unpowered or locked.
|
||||
// Disable UIs if the Borg is unpowered or locked.
|
||||
if(!cell || cell.charge <= 0 || lockcharge)
|
||||
return STATUS_DISABLED
|
||||
return ..()
|
||||
|
||||
@@ -87,7 +93,8 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/atom/proc/contents_tgui_distance(src_object, mob/living/user)
|
||||
return user.shared_living_tgui_distance(src_object) // Just call this mob's check.
|
||||
// Just call this mob's check.
|
||||
return user.shared_living_tgui_distance(src_object)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -99,7 +106,8 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/mob/living/proc/shared_living_tgui_distance(atom/movable/src_object, viewcheck = TRUE)
|
||||
if(viewcheck && !(src_object in view(src))) // If the object is obscured, close it.
|
||||
// If the object is obscured, close it.
|
||||
if(viewcheck && !(src_object in view(src)))
|
||||
return STATUS_CLOSE
|
||||
|
||||
var/dist = get_dist(src_object, src)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* tgui state: ntos_state
|
||||
*
|
||||
* Checks a number of things -- mostly physical distance for humans and view for robots.
|
||||
* This is basically the same as default, except instead of src_object, it uses the computer
|
||||
* it's attached to.
|
||||
**/
|
||||
|
||||
GLOBAL_DATUM_INIT(tgui_ntos_state, /datum/tgui_state/ntos, new)
|
||||
|
||||
/datum/tgui_state/ntos/can_use_topic(src_object, mob/user)
|
||||
var/datum/computer_file/program/P = src_object
|
||||
if(!istype(P) || !P.computer)
|
||||
return FALSE
|
||||
return user.default_can_use_tgui_topic(P.computer) // Call the individual mob-overridden procs.
|
||||
+202
-271
@@ -14,34 +14,26 @@
|
||||
var/datum/src_object
|
||||
/// The title of te UI.
|
||||
var/title
|
||||
/// The ui_key of the UI. This allows multiple UIs for one src_object.
|
||||
var/ui_key
|
||||
/// The window_id for browse() and onclose().
|
||||
var/window_id
|
||||
/// The window width.
|
||||
var/width = 0
|
||||
/// The window height
|
||||
var/height = 0
|
||||
var/datum/tgui_window/window
|
||||
/// Key that is used for remembering the window geometry.
|
||||
var/window_key
|
||||
/// Deprecated: Window size.
|
||||
var/window_size
|
||||
/// The interface (template) to be used for this UI.
|
||||
var/interface
|
||||
/// Update the UI every MC tick.
|
||||
var/autoupdate = TRUE
|
||||
/// If the UI has been initialized yet.
|
||||
var/initialized = FALSE
|
||||
/// The data (and datastructure) used to initialize the UI.
|
||||
var/list/initial_data
|
||||
/// The static data used to initialize the UI.
|
||||
var/list/initial_static_data
|
||||
/// Holder for the json string, that is sent during the initial update
|
||||
var/_initial_update
|
||||
/// Time of opening the window.
|
||||
var/opened_at
|
||||
/// Stops further updates when close() was called.
|
||||
var/closing = FALSE
|
||||
/// The status/visibility of the UI.
|
||||
var/status = STATUS_INTERACTIVE
|
||||
/// Topic state used to determine status/interactability.
|
||||
var/datum/tgui_state/state = null
|
||||
/// The parent UI.
|
||||
var/datum/tgui/master_ui
|
||||
/// Children of this UI.
|
||||
var/list/datum/tgui/children = list()
|
||||
// The map z-level to display.
|
||||
var/map_z_level = 1
|
||||
|
||||
@@ -52,39 +44,24 @@
|
||||
*
|
||||
* required user mob The mob who opened/is using the UI.
|
||||
* required src_object datum The object or datum which owns the UI.
|
||||
* required ui_key string The ui_key of the UI.
|
||||
* required interface string The interface used to render the UI.
|
||||
* optional title string The title of the UI.
|
||||
* optional width int The window width.
|
||||
* optional height int The window height.
|
||||
* optional master_ui datum/tgui The parent UI.
|
||||
* optional state datum/ui_state The state used to determine status.
|
||||
* 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, ui_key, interface, title, width = 0, height = 0, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_default_state)
|
||||
/datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y)
|
||||
src.user = user
|
||||
src.src_object = src_object
|
||||
src.ui_key = ui_key
|
||||
src.window_id = "\ref[src_object]-[ui_key]"
|
||||
src.window_key = "[REF(src_object)]-main"
|
||||
src.interface = interface
|
||||
|
||||
if(title)
|
||||
src.title = sanitize(title)
|
||||
if(width)
|
||||
src.width = width
|
||||
if(height)
|
||||
src.height = height
|
||||
|
||||
src.master_ui = master_ui
|
||||
if(master_ui)
|
||||
master_ui.children += src
|
||||
src.state = state
|
||||
|
||||
var/datum/asset/tgui_assets = get_asset_datum(/datum/asset/simple/tgui)
|
||||
var/datum/asset/fa = get_asset_datum(/datum/asset/simple/fontawesome)
|
||||
tgui_assets.send(user)
|
||||
fa.send(user)
|
||||
src.title = title
|
||||
src.state = src_object.tgui_state()
|
||||
// Deprecated
|
||||
if(ui_x && ui_y)
|
||||
src.window_size = list(ui_x, ui_y)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -93,84 +70,51 @@
|
||||
*/
|
||||
/datum/tgui/proc/open()
|
||||
if(!user.client)
|
||||
return // Bail if there is no client.
|
||||
|
||||
update_status(push = FALSE) // Update the window status.
|
||||
return null
|
||||
if(window)
|
||||
return null
|
||||
process_status()
|
||||
if(status < STATUS_UPDATE)
|
||||
return // Bail if we're not supposed to open.
|
||||
|
||||
|
||||
|
||||
// Build window options
|
||||
var/window_options = "can_minimize=0;auto_format=0;"
|
||||
// If we have a width and height, use them.
|
||||
if(width && height)
|
||||
window_options += "size=[width]x[height];"
|
||||
|
||||
// Remove titlebar and resize handles for a fancy window
|
||||
// if(user.client.prefs.nanoui_fancy)
|
||||
// window_options += "titlebar=0;can_resize=0;"
|
||||
// else
|
||||
window_options += "titlebar=1;can_resize=1;"
|
||||
|
||||
// Generate page html
|
||||
var/html
|
||||
html = SStgui.basehtml
|
||||
// Allow the src object to override the html if needed
|
||||
html = src_object.tgui_base_html(html)
|
||||
// Replace template tokens with important UI data
|
||||
html = replacetextEx(html, "\[tgui:ref]", "\ref[src]")
|
||||
|
||||
// Open the window.
|
||||
user << browse(html, "window=[window_id];[window_options]")
|
||||
|
||||
// Instruct the client to signal UI when the window is closed.
|
||||
// NOTE: Intentional \ref usage; tgui datums can't/shouldn't
|
||||
// be tagged, so this is an effective unwrap
|
||||
winset(user, window_id, "on-close=\"uiclose \ref[src]\"")
|
||||
|
||||
// Pre-fetch initial state while browser is still loading in
|
||||
// another thread
|
||||
if(!initial_data)
|
||||
initial_data = src_object.tgui_data(user)
|
||||
if(!initial_static_data)
|
||||
initial_static_data = src_object.tgui_static_data(user)
|
||||
_initial_update = url_encode(get_json(initial_data, initial_static_data))
|
||||
|
||||
return null
|
||||
window = SStgui.request_pooled_window(user)
|
||||
if(!window)
|
||||
return null
|
||||
opened_at = world.time
|
||||
window.acquire_lock(src)
|
||||
if(!window.is_ready())
|
||||
window.initialize(inline_assets = list(
|
||||
get_asset_datum(/datum/asset/simple/tgui)
|
||||
))
|
||||
else
|
||||
window.send_message("ping")
|
||||
window.send_asset(get_asset_datum(/datum/asset/simple/fontawesome))
|
||||
for(var/datum/asset/asset in src_object.ui_assets(user))
|
||||
window.send_asset(asset)
|
||||
window.send_message("update", get_payload(
|
||||
with_data = TRUE,
|
||||
with_static_data = TRUE))
|
||||
SStgui.on_open(src)
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Reinitialize the UI.
|
||||
* (Possibly with a new interface and/or data).
|
||||
*
|
||||
* optional template string The name of the new interface.
|
||||
* optional data list The new initial data.
|
||||
*/
|
||||
/datum/tgui/proc/reinitialize(interface, list/data, list/static_data)
|
||||
if(interface)
|
||||
src.interface = interface
|
||||
if(data)
|
||||
initial_data = data
|
||||
if(static_data)
|
||||
initial_static_data = static_data
|
||||
open()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Close the UI, and all its children.
|
||||
*/
|
||||
/datum/tgui/proc/close()
|
||||
user << browse(null, "window=[window_id]") // Close the window.
|
||||
src_object.tgui_close(user)
|
||||
SStgui.on_close(src)
|
||||
for(var/datum/tgui/child in children) // Loop through and close all children.
|
||||
child.close()
|
||||
children.Cut()
|
||||
/datum/tgui/proc/close(can_be_suspended = TRUE)
|
||||
if(closing)
|
||||
return
|
||||
closing = TRUE
|
||||
// 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)
|
||||
// Windows you want to keep are usually blue screens of death
|
||||
// and we want to keep them around, to allow user to read
|
||||
// the error message properly.
|
||||
window.release_lock()
|
||||
window.close(can_be_suspended)
|
||||
src_object.tgui_close(user)
|
||||
SStgui.on_close(src)
|
||||
state = null
|
||||
master_ui = null
|
||||
qdel(src)
|
||||
|
||||
/**
|
||||
@@ -178,50 +122,153 @@
|
||||
*
|
||||
* Enable/disable auto-updating of the UI.
|
||||
*
|
||||
* required state bool Enable/disable auto-updating.
|
||||
* required autoupdate bool Enable/disable auto-updating.
|
||||
*/
|
||||
/datum/tgui/proc/set_autoupdate(state = TRUE)
|
||||
autoupdate = state
|
||||
/datum/tgui/proc/set_autoupdate(autoupdate)
|
||||
src.autoupdate = autoupdate
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Replace current ui.state with a new one.
|
||||
*
|
||||
* required state datum/ui_state/state Next state
|
||||
*/
|
||||
/datum/tgui/proc/set_state(datum/tgui_state/state)
|
||||
src.state = state
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Makes an asset available to use in tgui.
|
||||
*
|
||||
* required asset datum/asset
|
||||
*/
|
||||
/datum/tgui/proc/send_asset(datum/asset/asset)
|
||||
if(!window)
|
||||
CRASH("send_asset() can only be called after open().")
|
||||
window.send_asset(asset)
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Send a full update to the client (includes static data).
|
||||
*
|
||||
* optional custom_data list Custom data to send instead of ui_data.
|
||||
* optional force bool Send an update even if UI is not interactive.
|
||||
*/
|
||||
/datum/tgui/proc/send_full_update(custom_data, force)
|
||||
if(!user.client || !initialized || closing)
|
||||
return
|
||||
var/should_update_data = force || status >= STATUS_UPDATE
|
||||
window.send_message("update", get_payload(
|
||||
custom_data,
|
||||
with_data = should_update_data,
|
||||
with_static_data = TRUE))
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Send a partial update to the client (excludes static data).
|
||||
*
|
||||
* optional custom_data list Custom data to send instead of ui_data.
|
||||
* optional force bool Send an update even if UI is not interactive.
|
||||
*/
|
||||
/datum/tgui/proc/send_update(custom_data, force)
|
||||
if(!user.client || !initialized || closing)
|
||||
return
|
||||
var/should_update_data = force || status >= STATUS_UPDATE
|
||||
window.send_message("update", get_payload(
|
||||
custom_data,
|
||||
with_data = should_update_data))
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Package the data to send to the UI, as JSON.
|
||||
* This includes the UI data and config_data.
|
||||
*
|
||||
* return string The packaged JSON.
|
||||
* return list
|
||||
*/
|
||||
/datum/tgui/proc/get_json(list/data, list/static_data)
|
||||
/datum/tgui/proc/get_payload(custom_data, with_data, with_static_data)
|
||||
var/list/json_data = list()
|
||||
|
||||
json_data["config"] = list(
|
||||
"title" = title,
|
||||
"status" = status,
|
||||
"interface" = interface,
|
||||
// "fancy" = user.client.prefs.nanoui_fancy,
|
||||
"observer" = isobserver(user),
|
||||
"window" = window_id,
|
||||
"map" = (using_map && using_map.path) ? using_map.path : "Unknown",
|
||||
"mapZLevel" = map_z_level,
|
||||
|
||||
"ref" = "\ref[src]"
|
||||
"window" = list(
|
||||
"key" = window_key,
|
||||
"size" = window_size,
|
||||
"fancy" = user.client.prefs.tgui_fancy,
|
||||
"locked" = user.client.prefs.tgui_lock,
|
||||
),
|
||||
"user" = list(
|
||||
"name" = "[user]",
|
||||
"ckey" = "[user.ckey]",
|
||||
"observer" = isobserver(user),
|
||||
),
|
||||
)
|
||||
|
||||
if(!isnull(data))
|
||||
var/data = custom_data || with_data && src_object.tgui_data(user)
|
||||
if(data)
|
||||
json_data["data"] = data
|
||||
if(!isnull(static_data))
|
||||
var/static_data = with_static_data && src_object.tgui_static_data(user)
|
||||
if(static_data)
|
||||
json_data["static_data"] = static_data
|
||||
|
||||
// Send shared states
|
||||
if(src_object.tgui_shared_states)
|
||||
json_data["shared"] = src_object.tgui_shared_states
|
||||
return json_data
|
||||
|
||||
// Generate the JSON.
|
||||
var/json = json_encode(json_data)
|
||||
// Strip #255/improper.
|
||||
json = replacetext(json, "\proper", "")
|
||||
json = replacetext(json, "\improper", "")
|
||||
return json
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Run an update cycle for this UI. Called internally by SStgui
|
||||
* every second or so.
|
||||
*/
|
||||
/datum/tgui/process(force = FALSE)
|
||||
if(closing)
|
||||
return
|
||||
var/datum/host = src_object.tgui_host(user)
|
||||
// If the object or user died (or something else), abort.
|
||||
if(!src_object || !host || !user || !window)
|
||||
close(can_be_suspended = FALSE)
|
||||
return
|
||||
// Validate ping
|
||||
if(!initialized && world.time - opened_at > TGUI_PING_TIMEOUT)
|
||||
log_tgui(user, \
|
||||
"Error: Zombie window detected, killing it with fire.\n" \
|
||||
+ "window_id: [window.id]\n" \
|
||||
+ "opened_at: [opened_at]\n" \
|
||||
+ "world.time: [world.time]")
|
||||
close(can_be_suspended = FALSE)
|
||||
return
|
||||
// Update through a normal call to ui_interact
|
||||
if(status != STATUS_DISABLED && (autoupdate || force))
|
||||
src_object.tgui_interact(user, src)
|
||||
return
|
||||
// Update status only
|
||||
var/needs_update = process_status()
|
||||
if(status <= STATUS_CLOSE)
|
||||
close()
|
||||
return
|
||||
if(needs_update)
|
||||
window.send_message("update", get_payload())
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Updates the status, and returns TRUE if status has changed.
|
||||
*/
|
||||
/datum/tgui/proc/process_status()
|
||||
var/prev_status = status
|
||||
status = src_object.tgui_status(user, state)
|
||||
return prev_status != status
|
||||
|
||||
/datum/tgui/proc/log_message(message)
|
||||
log_tgui("[user] ([user.ckey]) using \"[title]\":\n[message]")
|
||||
|
||||
/datum/tgui/proc/set_map_z_level(nz)
|
||||
map_z_level = nz
|
||||
|
||||
/**
|
||||
* private
|
||||
@@ -230,144 +277,28 @@
|
||||
* Call the src_object's ui_act() if status is UI_INTERACTIVE.
|
||||
* If the src_object's ui_act() returns 1, update all UIs attacked to it.
|
||||
*/
|
||||
/datum/tgui/Topic(href, href_list)
|
||||
if(user != usr)
|
||||
return // Something is not right here.
|
||||
|
||||
var/action = href_list["action"]
|
||||
var/params = href_list; params -= "action"
|
||||
|
||||
switch(action)
|
||||
if("tgui:initialize")
|
||||
user << output(_initial_update, "[window_id].browser:update")
|
||||
/datum/tgui/proc/on_message(type, list/payload, list/href_list)
|
||||
// Pass act type messages to tgui_act
|
||||
if(type && copytext(type, 1, 5) == "act/")
|
||||
process_status()
|
||||
if(src_object.tgui_act(copytext(type, 5), payload, src, state))
|
||||
SStgui.update_uis(src_object)
|
||||
return FALSE
|
||||
switch(type)
|
||||
if("ready")
|
||||
initialized = TRUE
|
||||
if("tgui:setSharedState")
|
||||
// Update the window state.
|
||||
update_status(push = FALSE)
|
||||
// Bail if UI is not interactive or usr calling Topic
|
||||
// is not the UI user.
|
||||
if("pingReply")
|
||||
initialized = TRUE
|
||||
if("suspend")
|
||||
close(can_be_suspended = TRUE)
|
||||
if("close")
|
||||
close(can_be_suspended = FALSE)
|
||||
if("log")
|
||||
if(href_list["fatal"])
|
||||
close(can_be_suspended = FALSE)
|
||||
if("setSharedState")
|
||||
if(status != STATUS_INTERACTIVE)
|
||||
return
|
||||
var/key = params["key"]
|
||||
var/value = params["value"]
|
||||
if(!src_object.tgui_shared_states)
|
||||
src_object.tgui_shared_states = list()
|
||||
src_object.tgui_shared_states[key] = value
|
||||
SStgui.update_uis(src_object)
|
||||
// if("tgui:setFancy")
|
||||
// var/value = text2num(params["value"])
|
||||
// user.client.prefs.nanoui_fancy = value
|
||||
if("tgui:log")
|
||||
// Force window to show frills on fatal errors
|
||||
if(params["fatal"])
|
||||
winset(user, window_id, "titlebar=1;can-resize=1;size=600x600")
|
||||
log_message(params["log"])
|
||||
if("tgui:link")
|
||||
user << link(params["url"])
|
||||
if("tgui:setZLevel")
|
||||
set_map_z_level(params["mapZLevel"])
|
||||
// Update the window state.
|
||||
update_status(push = FALSE)
|
||||
|
||||
else
|
||||
// Update the window state.
|
||||
update_status(push = FALSE)
|
||||
// Call tgui_act() on the src_object.
|
||||
if(src_object.tgui_act(action, params, src, state))
|
||||
// Update if the object requested it.
|
||||
SStgui.update_uis(src_object)
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Update the UI.
|
||||
* Only updates the data if update is true, otherwise only updates the status.
|
||||
*
|
||||
* optional force bool If the UI should be forced to update.
|
||||
*/
|
||||
/datum/tgui/process(force = FALSE)
|
||||
var/datum/host = src_object.tgui_host(user)
|
||||
if(!src_object || !host || !user) // If the object or user died (or something else), abort.
|
||||
close()
|
||||
return
|
||||
|
||||
if(status && (force || autoupdate))
|
||||
update() // Update the UI if the status and update settings allow it.
|
||||
else
|
||||
update_status(push = TRUE) // Otherwise only update status.
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Push data to an already open UI.
|
||||
*
|
||||
* required data list The data to send.
|
||||
* optional force bool If the update should be sent regardless of state.
|
||||
*/
|
||||
/datum/tgui/proc/push_data(data, static_data, force = FALSE)
|
||||
// Update the window state.
|
||||
update_status(push = FALSE)
|
||||
// Cannot update UI if it is not set up yet.
|
||||
if(!initialized)
|
||||
return
|
||||
// Cannot update UI, we have no visibility.
|
||||
if(status <= STATUS_DISABLED && !force)
|
||||
return
|
||||
// Send the new JSON to the update() Javascript function.
|
||||
user << output(
|
||||
url_encode(get_json(data, static_data)),
|
||||
"[window_id].browser:update")
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Updates the UI by interacting with the src_object again, which will hopefully
|
||||
* call try_ui_update on it.
|
||||
*
|
||||
* optional force_open bool If force_open should be passed to ui_interact.
|
||||
*/
|
||||
/datum/tgui/proc/update(force_open = FALSE)
|
||||
src_object.tgui_interact(user, ui_key, src, force_open, master_ui, state)
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Update the status/visibility of the UI for its user.
|
||||
*
|
||||
* optional push bool Push an update to the UI (an update is always sent for UI_DISABLED).
|
||||
*/
|
||||
/datum/tgui/proc/update_status(push = FALSE)
|
||||
var/status = src_object.tgui_status(user, state)
|
||||
if(master_ui)
|
||||
status = min(status, master_ui.status)
|
||||
|
||||
set_status(status, push)
|
||||
if(status == STATUS_CLOSE)
|
||||
close()
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Set the status/visibility of the UI.
|
||||
*
|
||||
* required status int The status to set (UI_CLOSE/UI_DISABLED/UI_UPDATE/UI_INTERACTIVE).
|
||||
* optional push bool Push an update to the UI (an update is always sent for UI_DISABLED).
|
||||
*/
|
||||
/datum/tgui/proc/set_status(status, push = FALSE)
|
||||
// Only update if status has changed.
|
||||
if(src.status != status)
|
||||
if(src.status == STATUS_DISABLED)
|
||||
src.status = status
|
||||
if(push)
|
||||
update()
|
||||
else
|
||||
src.status = status
|
||||
// Update if the UI just because disabled, or a push is requested.
|
||||
if(status == STATUS_DISABLED || push)
|
||||
push_data(null, force = TRUE)
|
||||
|
||||
/datum/tgui/proc/log_message(message)
|
||||
log_tgui("[user] ([user.ckey]) using \"[title]\":\n[message]")
|
||||
|
||||
/datum/tgui/proc/set_map_z_level(nz)
|
||||
map_z_level = nz
|
||||
LAZYINITLIST(src_object.tgui_shared_states)
|
||||
src_object.tgui_shared_states[href_list["key"]] = href_list["value"]
|
||||
SStgui.update_uis(src_object)
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/datum/tgui_window
|
||||
var/id
|
||||
var/client/client
|
||||
var/pooled
|
||||
var/pool_index
|
||||
var/status = TGUI_WINDOW_CLOSED
|
||||
var/locked = FALSE
|
||||
var/datum/tgui/locked_by
|
||||
var/fatally_errored = FALSE
|
||||
var/message_queue
|
||||
var/sent_assets = list()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Create a new tgui window.
|
||||
*
|
||||
* required client /client
|
||||
* required id string A unique window identifier.
|
||||
*/
|
||||
/datum/tgui_window/New(client/client, id, pooled = FALSE)
|
||||
src.id = id
|
||||
src.client = client
|
||||
src.pooled = pooled
|
||||
if(pooled)
|
||||
client.tgui_windows[id] = src
|
||||
src.pool_index = TGUI_WINDOW_INDEX(id)
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Initializes the window with a fresh page. Puts window into the "loading"
|
||||
* state. You can begin sending messages right after initializing. Messages
|
||||
* will be put into the queue until the window finishes loading.
|
||||
*
|
||||
* optional inline_assets list List of assets to inline into the html.
|
||||
*/
|
||||
/datum/tgui_window/proc/initialize(inline_assets = list())
|
||||
log_tgui(client, "[id]/initialize")
|
||||
if(!client)
|
||||
return
|
||||
status = TGUI_WINDOW_LOADING
|
||||
fatally_errored = FALSE
|
||||
message_queue = null
|
||||
// Build window options
|
||||
var/options = "file=[id].html;can_minimize=0;auto_format=0;"
|
||||
// Remove titlebar and resize handles for a fancy window
|
||||
if(client.prefs.tgui_fancy)
|
||||
options += "titlebar=0;can_resize=0;"
|
||||
else
|
||||
options += "titlebar=1;can_resize=1;"
|
||||
// Generate page html
|
||||
var/html = SStgui.basehtml
|
||||
html = replacetextEx(html, "\[tgui:windowId]", id)
|
||||
// Process inline assets
|
||||
var/inline_styles = ""
|
||||
var/inline_scripts = ""
|
||||
for(var/datum/asset/asset in inline_assets)
|
||||
var/mappings = asset.get_url_mappings()
|
||||
for(var/name in mappings)
|
||||
var/url = mappings[name]
|
||||
// Not urlencoding since asset strings are considered safe
|
||||
if(copytext(name, -4) == ".css")
|
||||
inline_styles += "<link rel=\"stylesheet\" type=\"text/css\" href=\"[url]\">\n"
|
||||
else if(copytext(name, -3) == ".js")
|
||||
inline_scripts += "<script type=\"text/javascript\" defer src=\"[url]\"></script>\n"
|
||||
asset.send()
|
||||
html = replacetextEx(html, "<!-- tgui:styles -->\n", inline_styles)
|
||||
html = replacetextEx(html, "<!-- tgui:scripts -->\n", inline_scripts)
|
||||
// Open the window
|
||||
client << browse(html, "window=[id];[options]")
|
||||
// Instruct the client to signal UI when the window is closed.
|
||||
winset(client, id, "on-close=\"uiclose [id]\"")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Checks if the window is ready to receive data.
|
||||
*
|
||||
* return bool
|
||||
*/
|
||||
/datum/tgui_window/proc/is_ready()
|
||||
return status == TGUI_WINDOW_READY
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Checks if the window can be sanely suspended.
|
||||
*
|
||||
* return bool
|
||||
*/
|
||||
/datum/tgui_window/proc/can_be_suspended()
|
||||
return !fatally_errored \
|
||||
&& pooled \
|
||||
&& pool_index > 0 \
|
||||
&& pool_index <= TGUI_WINDOW_SOFT_LIMIT \
|
||||
&& status == TGUI_WINDOW_READY
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Acquire the window lock. Pool will not be able to provide this window
|
||||
* to other UIs for the duration of the lock.
|
||||
*
|
||||
* Can be given an optional tgui datum, which will hook its on_message
|
||||
* callback into the message stream.
|
||||
*
|
||||
* optional ui /datum/tgui
|
||||
*/
|
||||
/datum/tgui_window/proc/acquire_lock(datum/tgui/ui)
|
||||
locked = TRUE
|
||||
locked_by = ui
|
||||
|
||||
/**
|
||||
* Release the window lock.
|
||||
*/
|
||||
/datum/tgui_window/proc/release_lock()
|
||||
// Clean up assets sent by tgui datum which requested the lock
|
||||
if(locked)
|
||||
sent_assets = list()
|
||||
locked = FALSE
|
||||
locked_by = null
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Close the UI.
|
||||
*
|
||||
* optional can_be_suspended bool
|
||||
*/
|
||||
/datum/tgui_window/proc/close(can_be_suspended = TRUE)
|
||||
if(!client)
|
||||
return
|
||||
if(can_be_suspended && can_be_suspended())
|
||||
log_tgui(client, "[id]/close: suspending")
|
||||
status = TGUI_WINDOW_READY
|
||||
send_message("suspend")
|
||||
return
|
||||
log_tgui(client, "[id]/close")
|
||||
release_lock()
|
||||
status = TGUI_WINDOW_CLOSED
|
||||
message_queue = null
|
||||
// Do not close the window to give user some time
|
||||
// to read the error message.
|
||||
if(!fatally_errored)
|
||||
client << browse(null, "window=[id]")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Sends a message to tgui window.
|
||||
*
|
||||
* required type string Message type
|
||||
* required payload list Message payload
|
||||
* optional force bool Send regardless of the ready status.
|
||||
*/
|
||||
/datum/tgui_window/proc/send_message(type, list/payload, force)
|
||||
if(!client)
|
||||
return
|
||||
var/message = json_encode(list(
|
||||
"type" = type,
|
||||
"payload" = payload,
|
||||
))
|
||||
// Strip #255/improper.
|
||||
message = replacetext(message, "\proper", "")
|
||||
message = replacetext(message, "\improper", "")
|
||||
// Pack for sending via output()
|
||||
message = url_encode(message)
|
||||
// Place into queue if window is still loading
|
||||
if(!force && status != TGUI_WINDOW_READY)
|
||||
if(!message_queue)
|
||||
message_queue = list()
|
||||
message_queue += list(message)
|
||||
return
|
||||
client << output(message, "[id].browser:update")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Makes an asset available to use in tgui.
|
||||
*
|
||||
* required asset datum/asset
|
||||
*/
|
||||
/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())
|
||||
send_message("asset/mappings", asset.get_url_mappings())
|
||||
sent_assets += list(asset)
|
||||
asset.send(client)
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Sends queued messages if the queue wasn't empty.
|
||||
*/
|
||||
/datum/tgui_window/proc/flush_message_queue()
|
||||
if(!client || !message_queue)
|
||||
return
|
||||
for(var/message in message_queue)
|
||||
client << output(message, "[id].browser:update")
|
||||
message_queue = null
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Callback for handling incoming tgui messages.
|
||||
*/
|
||||
/datum/tgui_window/proc/on_message(type, list/payload, list/href_list)
|
||||
switch(type)
|
||||
if("ready")
|
||||
// Status can be READY if user has refreshed the window.
|
||||
if(status == TGUI_WINDOW_READY)
|
||||
// Resend the assets
|
||||
for(var/asset in sent_assets)
|
||||
send_asset(asset)
|
||||
status = TGUI_WINDOW_READY
|
||||
if("log")
|
||||
if(href_list["fatal"])
|
||||
fatally_errored = TRUE
|
||||
// Pass message to UI that requested the lock
|
||||
if(locked && locked_by)
|
||||
locked_by.on_message(type, payload, href_list)
|
||||
flush_message_queue()
|
||||
return
|
||||
// If not locked, handle these message types
|
||||
switch(type)
|
||||
if("suspend")
|
||||
close(can_be_suspended = TRUE)
|
||||
if("close")
|
||||
close(can_be_suspended = FALSE)
|
||||
Reference in New Issue
Block a user