mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-24 12:37:26 +01:00
TGUIv4 - Core backend updates, TGchat, Asset cache updates (#23726)
* TGUI upgrade project: Port initial TGUI 4.0 changes from TG (#23440) * tgui4.0 * bugfix for un-interaction * fix for input not resetting on close * NTOS restore * fix all interfaces having scrollbars, fix colours * bundle update * dep bumps * bumps the deps as much as possible * button regression fix * TGUI test map rev 1 * fix theme and some component regression * fix login screen regression * fixes regression with uplink cart * bundle * fix regressions * fix the input issue, again * regression fixes, stylesheet edition, hash restore * fixes GPS BSOD * draggable control regression * dev server dep regression * byondUI regression fix * section regression fix * fix secure storage weirdness * Fixed mining vendor scrolling * Arthri review pass 1 * mining vendor double-scroll * fix for RPD, AirAlarm, and Radio * arthri review pass 2 * arthri review pass 3 * sanitize var setting, fix colours, delete unused file * adds CI to branch * god damn AI making random changes * I should remember to compile when changes are made * fixes scrollbar issues * fix camera console, fix flexGrow for sections * CI fix * nanomap fix * Update code/modules/atmospherics/machinery/airalarm.dm * restore margin * style semicolon * TGUIv4 - Moves TGUI Window Sizing from DM to JS (#23524) * Initial commit * Transferring the remaining windows to JS * Resizing part 1 * Resizing part 2 * Some reverts and polish * Forgot that * undeployed nuke window size --------- Co-authored-by: Aylong <alexanderkitsa@gmail.com> * TGUIv4: Remove force_open (#23537) * First state fixes * Removes "force_open" * TGUIv4 - TGUI Version 4.1 (#23547) * Reduced Logging Changes * setSharedState early return for non-interactive UI * TGUI version bump to 4.1 * Fix sending updates for non-interactive UIs * Scalablue UI Stylesheet Changes * CSS Tgui Bundle * JavaScript TGUI Style Changes * Update tgui/packages/tgui/styles/base.scss Co-authored-by: Aylong <69762909+Aylong220@users.noreply.github.com> * Additional changes to Input and Button scalability * TGUI logging proc update * Indentation fix for logging proc * Update code/__HELPERS/_logging.dm Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * Additional log_tgui changes --------- Co-authored-by: Aylong <69762909+Aylong220@users.noreply.github.com> Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * dev server hotfix (#23584) * ui_interact object constructor argument check (#23594) * feat: add smart asset cache, add CDN support for assets, properly group assets (#23585) * feat: add smart asset cache, add CDN support for assets, properly group assets * fix: make tgui actually work * fix: keeping local name for `tgui.bundle.js` and `tgui.bundle.css` for debug and dev server functioning * fix: make `fontawesome` assets finally work * fix: make proper identation for `if` * fix: add `resolveAsset` to `NanoMap.js` * refactor: update `claw_game` and `chess` to new asset framework * refactor/tweak: don't use string concatenation for browser `content` and `head_content`, use list instead. Don't use `common.css` for paper UI, keep local name for stamp image assets, replace hard ref of browser to `atom` (rare case) to `UID` * refactor: remove redundant debug logs * refactor: remove space betwee `if` and `(` * refactor: remove one more redundant log, properly reload UI resources * rafactor: change names of asset files * fix: adjust existing UIs to properly use assets * fix: properly pass args to to `onclose` proc * Update code/modules/asset_cache/transports/asset_transport.dm Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * Update code/modules/asset_cache/transports/asset_transport.dm Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * TGUI 4.2: TGchat + Refactor Asset Delivery + Yarn Berry (#23643) * tgchat * Little cleanup * Refactor Asset Delivery (Part 2) * Little tweaks * fix code styling issues * fix file name duplications * Browser window options fix @gaxeer * transfer valve fixes * yarn berry initial * vsc tasks * yarn berry working * node 20 * fix dev server (lol wrong yarn) * bloody regressions * fixes that damn scrolltracking bug * Some tweaks and flexGrow deletions It still doesn't work as it should, which makes me nervous * remove unneeded deps * 514 regression fix * change stuff to not conflict with other servers * name it as requested --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * revert test_TGUI map addition * TGUI 4.3: Stack, Webpack 5 & UI Tweaks, Yarn 3.6.4, TS support, Jest (#23677) * Stack & UI's Refactor Almost final, polish required Stack & UI's Refactor (Part 1) Stack & UI's Refactor (Part 2) Stack & UI's Refactor (Part 3) * Prettier Maybe bad * ClearChat & some tweaks * Adds a Chat Reliability Layer https://github.com/tgstation/tgstation/pull/79479 * Fix chat BSOD https://github.com/tgstation/tgstation/pull/79821 * WebPack 5 (Didn't work) I hate this shit * Yarn 3.6.4 * make it all work * revert snowflake fix * Stories * adds TS support * re-enable test and prettier * update yarn sdk's * Fix some box regression * ping/reply * Fixes regressions and some things * Zebra and Fix chat button transfer +rebuild * make VSC use the proper local typescript lib * Popper Tooltips https://github.com/tgstation/tgstation/pull/58980 * Popper Tooltips performancy fixes * Dropdown v2 https://github.com/tgstation/tgstation/pull/75164 Without Icon.tsx * BB test map * run build --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * we moved these * change test values * ignore scss files * change hash function * TGUIv4: TGchat fix traitor codeword highlighting (#23720) * Codeword highlighting for TGUIv4 * null safeties * Apply suggestions from code review Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> --------- Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> * TGUIv4 - updates standalone build tooling (#23721) * swap `xxhash64` for `md4` * remove hash function * remove old file * TGUI 4.3+4.4: Newscaster fix, `inline-block` replacements and other fixes (#23722) * TGUI 4.3 fixes Newscaster fix + inline-blocks * Be gone Box * Mod Chat -> Mentor Chat * Some warning filters * Rebuild * Delete tgui-common.bundle.js * please work * try it like this * 4.3.1 * 4.3.2 * fix * i forgot to build * 4.4.0 * give me more diff details please so I can fix you * its not binary pls git * test without svgs * removes an un-needed SVG file * inlined SVGs test * fix code styling * comment these --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * TGUIv4 - Removes resizable and improves drag code (#23719) * Initial commit * bundle rebuild * Fixes Radio Sizing * Updates Radio sizing to fix wrapping issues * fix dev server --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * fix hotkeys not being passed to BYOND * Apply suggestions from code review * bump deps * forgot these deps * ci fix * ChemDispenser fixes and HoloControl cooldown * Station Traits TGUI +Rebuild * CI * fix CI for real this time * I have no idea * fix station trait panel * Update code/controllers/subsystem/SSping.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> * Update code/controllers/subsystem/SSticker.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> * Update code/controllers/subsystem/SSping.dm Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> * deterministic module IDs * TGUI fixes. Again... (#23762) * TGUI Fixes: Part 1 ORM icon for reinforced glass now showing correctly Mining vendor moved to Stack Exofab UI buttons descended from the heavens to the earth * TGUI Fixes: Part 2 DestTagger now works, forgot import LabeledList PowerMonitor graph displays correct Request Console updated to 2.0, BUT, problem with message priority, it was there before the changes, maybe even before the project, I'll have to check it out * TGUI Fixes: Part 2 Fixed `onEnter` input Fixed Cloning Console storage (Im dumbass) Some windows resizing Fixed bad picture in security and medical records console Fixed scrollable dropdown Added translucent color for buttons * Fucking uplink DONE, and Button.Input fix * NT Recruiter and Uplink polish * Del LabeledList from dest. tagger * ChemMaster and OreRedemption log deletion * Update test_tiny.dmm * Translucent button story * Filter for SQL error * del: `log_debug` in `hear_say.dm` * Some little fixes and SpecMenu Stack * Panel.js: mx -> mr * fix ghost spawners * null check --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * fix material name in protolathe * fix various UI interacts * card machine runtime fix * remove legacy folders * TGUIv4 - TGchat Theme Adjustments (#23772) * Initial commit * Changes rule grouping * Robot class style changes and webpack config * Reverts webpack/font changes and subs robot font * revert a state changed in error * fix uninteractable UIs due to chunky fingers * TGUIv4: More fixes, tweaks and fixes for tweaks (#23781) * I fuckin hate PDA * Vending * Add `|` to highlight splitters and some light mode tweaks * Teleporter rewrite * APC and AiAirlock (God forgive me) * clarify highlight syntax --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * TGUIv4 - Abductor Experiment Machine TGUI (#23776) * Experiment Console TGUI * Update for TGUIv4 * UI Size adjustment * ui state and ghost attack update * remove this --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> * fix * TGUIv4 - Adjustments, Fixes, and Tweaks (#23785) * Photocopier layout adjustments * Autolathe Text and Button spacing adjustment * Hotkey fix for moving bug * Removes rule sets for visited links * Fixes ordering multiples on cargo console * fix possible wonky json payloads * fix admin log input list stuff * TGUIv4: Reconnect fix and some other fixes + tweaks (#23790) * Fix reconnecting and transfer it to TSX * RPD polish * RCD, AccessList, GuestPass * Focus reset fix when camera switched. God forgive me again... * Purple box and identation fixes For votes and health scanner * ChemMaster and ChemDispenser ChemMaster - translucent pills buttons and no grow produce section ChemDuspenser - dynamic height * ShuttleManipulator Tabs * RCD buttons * make input bar mode more prominent * fix chem master icons * tab fix * Properly sanitizes loaded messages in tgui chat * TGUIv4 - Adjustments, Fixes, and Tweaks v2 (#23795) * Fixes icon spacing for nanobank tabs * Tweaks Dropdown styling * Fixes sending stuff to old chat * OpenDream TGUI fix * Resizes Station Traits window * Adjusts health analyzer messages for chat tabs * Revert "Properly sanitizes loaded messages in tgui chat" This reverts commit4c32a7094a. * Revert "Revert "Properly sanitizes loaded messages in tgui chat"" This reverts commit24afa55922. * Changes how tgui handles static data * Reenable no-undef rule. Enable Format On Save for VSCode (#23803) * Enable Format On Save for VSCode * Re-enable and fix no-undef --------- Co-authored-by: Arthri <41360489+a@users.noreply.github.com> * pda scanner message improvements * fix mentor/admin PMs being sent to the "unsorted" category * fix garbled custom vote messages * moves cyborg analyser to use chatboxes * fixes regression * Prevent F5 reload * TGUIv4: Themes polish and some new ones + misc fixes (#23814) * Little themes tweaks * NTOS chat theme * Update tgui-panel.bundle.js * Capitalize themes * Paradise theme v1.0 * Little StationTraitsPanel cleanup * Update member_content.dmi * NoCapitalize * Rebuild * Delete Paradise.scss * Create paradise.scss * Forgot * Rewritten Security Records Console For Jesus fucking christ... * Vending and Wires sizes tweaks * ThermoMachine * Syndicate Theme * Little darker * Rewritten Medical Records Console * PDA Nanobank fix * Syndicate Theme Darker * Rebuild TGUI * Wires section grow fix * fixes the enshittified paradise icon * Sirryan+Warrior reviews * bloody define comments * comment correction * unprivate these * errant . begone (how did this not cause a compiler error) * TGUIv4 - Adjustments, Fixes, and Tweaks v3 (#23807) * Fixes ERT Manager silenced message function * Adjusts progress bar on ExosuitFabricator * Sets Dropdown selected to align left * Removes the use of self_state select interfaces * Exosuit Fabricator style adjustments * fix sextractor UI * remove empty tochat string * fix human air alarm interactions * fix modals * add F12/IEChooser --------- Co-authored-by: S34N <12197162+S34NW@users.noreply.github.com> --------- Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Co-authored-by: Aylong <alexanderkitsa@gmail.com> Co-authored-by: Aylong <69762909+Aylong220@users.noreply.github.com> Co-authored-by: Gaxeer <44334376+Gaxeer@users.noreply.github.com> Co-authored-by: DGamerL <108773801+DGamerL@users.noreply.github.com> Co-authored-by: Arthri <41360489+Arthri@users.noreply.github.com> Co-authored-by: Arthri <41360489+a@users.noreply.github.com>
This commit is contained in:
co-authored by
Arthri
Burzah
Aylong
Aylong
Gaxeer
DGamerL
Arthri
parent
98638f1d38
commit
22fbd9aced
+118
-48
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* tgui external
|
||||
* External tgui definitions, such as src_object APIs.
|
||||
*
|
||||
* Contains all external tgui declarations.
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -10,14 +11,10 @@
|
||||
* Used to open and update UIs.
|
||||
* If this proc is not implemented properly, the UI will not update correctly.
|
||||
*
|
||||
* * mob/user - The mob who opened/is using the UI. (REQUIRED)
|
||||
* * ui_key - The ui_key of the UI. (OPTIONAL)
|
||||
* * datum/tgui/ui - The UI to be updated, if it exists. (OPTIONAL)
|
||||
* * force_open - If the UI should be re-opened instead of updated. (OPTIONAL)
|
||||
* * datum/tgui/master_ui - The parent UI. (OPTIONAL)
|
||||
* * datum/ui_state/state - The state used to determine status. (OPTIONAL)
|
||||
* required user mob The mob who opened/is using the UI.
|
||||
* optional ui datum/tgui The UI to be updated, if it exists.
|
||||
*/
|
||||
/datum/proc/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
/datum/proc/ui_interact(mob/user, datum/tgui/ui)
|
||||
return FALSE // Not implemented.
|
||||
|
||||
/**
|
||||
@@ -26,10 +23,11 @@
|
||||
* Data to be sent to the UI.
|
||||
* This must be implemented for a UI to work.
|
||||
*
|
||||
* * mob/user - The mob interacting with the UI.
|
||||
* required user mob The mob interacting with the UI.
|
||||
*
|
||||
* return list Data to be sent to the UI.
|
||||
*/
|
||||
/datum/proc/ui_data(mob/user)
|
||||
// Two good reasons why it shouldn't:
|
||||
// 1) this is polled several times a second, so sleeping means more running threads, needlessly tanking performance
|
||||
// 2) if you try to sleep, you get fun bugs ranging from BSOD to uninteractable white windows, to windows straight up vanishing.
|
||||
// Just don't.
|
||||
@@ -40,12 +38,15 @@
|
||||
* 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.
|
||||
*
|
||||
* * mob/user - The mob interacting with 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.
|
||||
*
|
||||
* required user mob The mob interacting with the UI.
|
||||
*
|
||||
* return list Statuic Data to be sent to the UI.
|
||||
*/
|
||||
/datum/proc/ui_static_data(mob/user)
|
||||
SHOULD_NOT_SLEEP(TRUE)
|
||||
@@ -54,49 +55,44 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* * mob/user - The mob currently interacting with the UI. (REQUIRED)
|
||||
* * datum/tgui/ui - UI to be updated (OPTIONAL)
|
||||
* * ui_key - Key of the UI to be updated. (OPTIONAL)
|
||||
* required user the mob currently interacting with the ui
|
||||
* optional ui ui to be updated
|
||||
*/
|
||||
/datum/proc/update_static_data(mob/user, datum/tgui/ui, ui_key = "main")
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui)
|
||||
// If there was no ui to update, there's no static data to update either.
|
||||
/datum/proc/update_static_data(mob/user, datum/tgui/ui)
|
||||
if(!ui)
|
||||
return
|
||||
ui.push_data(null, ui_static_data(user), TRUE)
|
||||
ui = SStgui.get_open_ui(user, src)
|
||||
if(ui)
|
||||
ui.send_full_update()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Called on a UI when the UI receieves a href.
|
||||
* Think of this as Topic().
|
||||
* Returns TRUE if the UI should be updated, and FALSE if not.
|
||||
*
|
||||
* * action - The action/button that has been invoked by the user.
|
||||
* * list/params - A list of parameters attached to the button.
|
||||
* required action string The action/button that has been invoked by the user.
|
||||
* required params list A list of parameters attached to the button.
|
||||
*
|
||||
* return bool If the UI should be updated or not.
|
||||
*/
|
||||
/datum/proc/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
// If UI is not interactive or usr calling Topic is not the UI user, bail.
|
||||
if(!ui || ui.status != STATUS_INTERACTIVE)
|
||||
if(!ui || ui.status != UI_INTERACTIVE)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* * html - The html base text.
|
||||
* return list List of asset datums or file paths.
|
||||
*/
|
||||
/datum/proc/ui_base_html(html)
|
||||
return html
|
||||
/datum/proc/ui_assets(mob/user)
|
||||
return list()
|
||||
|
||||
/**
|
||||
* private
|
||||
@@ -108,6 +104,15 @@
|
||||
/datum/proc/ui_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/ui_state(mob/user)
|
||||
return GLOB.default_state
|
||||
|
||||
/**
|
||||
* global
|
||||
*
|
||||
@@ -116,6 +121,27 @@
|
||||
*/
|
||||
/datum/var/list/tgui_shared_states
|
||||
|
||||
/**
|
||||
* global
|
||||
*
|
||||
* Tracks open UIs for a user.
|
||||
*/
|
||||
/mob/var/list/tgui_open_uis = list()
|
||||
|
||||
/**
|
||||
* global
|
||||
*
|
||||
* Tracks open windows for a user.
|
||||
*/
|
||||
/client/var/list/tgui_windows = list()
|
||||
|
||||
/**
|
||||
* global
|
||||
*
|
||||
* TRUE if cache was reloaded by tgui dev server at least once.
|
||||
*/
|
||||
/client/var/tgui_cache_reloaded = FALSE
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
@@ -130,19 +156,63 @@
|
||||
* Called by UIs when they are closed.
|
||||
* Must be a verb so winset() can call it.
|
||||
*
|
||||
* * uid - The UI that was closed.
|
||||
* required uiref ref The UI that was closed.
|
||||
*/
|
||||
/client/verb/uiclose(uid as text)
|
||||
/client/verb/uiclose(window_id as text)
|
||||
// Name the verb, and hide it from the user panel.
|
||||
set name = "uiclose"
|
||||
set hidden = TRUE
|
||||
var/mob/user = src && src.mob
|
||||
if(!user)
|
||||
return
|
||||
// Close all tgui datums based on window_id.
|
||||
SStgui.force_close_window(user, window_id)
|
||||
|
||||
// Get the UI based on the UID.
|
||||
var/datum/tgui/ui = locateUID(uid)
|
||||
/**
|
||||
* Middleware for /client/Topic.
|
||||
*
|
||||
* return bool If TRUE, prevents propagation of the topic call.
|
||||
*/
|
||||
/proc/tgui_Topic(href_list)
|
||||
// Skip non-tgui topics
|
||||
if(!href_list["tgui"])
|
||||
return FALSE
|
||||
var/type = href_list["type"]
|
||||
// Unconditionally collect tgui logs
|
||||
if(type == "log")
|
||||
log_tgui(usr, href_list["message"])
|
||||
// Reload all tgui windows
|
||||
if(type == "cacheReloaded")
|
||||
if(!check_rights(R_ADMIN) || usr.client.tgui_cache_reloaded)
|
||||
return TRUE
|
||||
// Mark as reloaded
|
||||
usr.client.tgui_cache_reloaded = TRUE
|
||||
// Notify windows
|
||||
var/list/windows = usr.client.tgui_windows
|
||||
for(var/window_id in windows)
|
||||
var/datum/tgui_window/window = windows[window_id]
|
||||
if(window.status == TGUI_WINDOW_READY)
|
||||
window.on_message(type, null, href_list)
|
||||
return TRUE
|
||||
// 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 TRUE
|
||||
// Decode payload
|
||||
var/payload
|
||||
if(href_list["payload"])
|
||||
var/payload_text = href_list["payload"]
|
||||
if(!rustg_json_is_valid(payload_text))
|
||||
log_tgui(usr, "Error: Invalid JSON")
|
||||
return TRUE
|
||||
|
||||
// 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()
|
||||
payload = json_decode(payload_text)
|
||||
// Pass message to window
|
||||
if(window)
|
||||
window.on_message(type, payload, href_list)
|
||||
return TRUE
|
||||
|
||||
@@ -199,10 +199,13 @@
|
||||
cut_and_generate_data()
|
||||
|
||||
|
||||
/datum/ui_module/appearance_changer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/appearance_changer/ui_state(mob/user)
|
||||
return GLOB.default_state
|
||||
|
||||
/datum/ui_module/appearance_changer/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "AppearanceChanger", name, 800, 450, master_ui, state)
|
||||
ui = new(user, src, "AppearanceChanger", name)
|
||||
ui.open()
|
||||
|
||||
/datum/ui_module/appearance_changer/ui_data(mob/user)
|
||||
|
||||
@@ -9,19 +9,24 @@
|
||||
if("open_alarm")
|
||||
var/obj/machinery/alarm/alarm = locate(params["aref"]) in GLOB.air_alarms
|
||||
if(alarm)
|
||||
alarm.ui_interact(usr, master_ui = ui, state = GLOB.always_state) // ALWAYS is intentional here, as the master_ui pass will prevent fuckery
|
||||
alarm.ui_interact(usr)
|
||||
|
||||
/datum/ui_module/atmos_control/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/atmos_control/ui_state(mob/user)
|
||||
if(isliving(usr))
|
||||
return GLOB.human_adjacent_state
|
||||
return GLOB.default_state
|
||||
|
||||
/datum/ui_module/atmos_control/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "AtmosControl", name, 800, 600, master_ui, state)
|
||||
|
||||
// Send nanomaps
|
||||
var/datum/asset/nanomaps = get_asset_datum(/datum/asset/simple/nanomaps)
|
||||
nanomaps.send(user)
|
||||
|
||||
ui = new(user, src, "AtmosControl", name)
|
||||
ui.open()
|
||||
|
||||
/datum/ui_module/atmos_control/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/simple/nanomaps)
|
||||
)
|
||||
|
||||
/datum/ui_module/atmos_control/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["alarms"] = GLOB.air_alarm_repository.air_alarm_data(GLOB.air_alarms, target_z=level_name_to_num(MAIN_STATION))
|
||||
|
||||
@@ -19,10 +19,13 @@
|
||||
|
||||
target_datum = target
|
||||
|
||||
/datum/ui_module/colour_matrix_tester/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/colour_matrix_tester/ui_state(mob/user)
|
||||
return GLOB.admin_state
|
||||
|
||||
/datum/ui_module/colour_matrix_tester/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "ColourMatrixTester", name, 350, 170, master_ui, state)
|
||||
ui = new(user, src, "ColourMatrixTester", name)
|
||||
ui.autoupdate = TRUE
|
||||
ui.open()
|
||||
|
||||
|
||||
@@ -27,19 +27,19 @@
|
||||
return
|
||||
viewing_current_z_level = text2num(params["new_level"])
|
||||
|
||||
/datum/ui_module/crew_monitor/ui_state(mob/user)
|
||||
return GLOB.default_state
|
||||
|
||||
|
||||
/datum/ui_module/crew_monitor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/crew_monitor/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "CrewMonitor", name, 800, 600, master_ui, state)
|
||||
|
||||
// Send nanomaps
|
||||
var/datum/asset/nanomaps = get_asset_datum(/datum/asset/simple/nanomaps)
|
||||
nanomaps.send(user)
|
||||
|
||||
ui = new(user, src, "CrewMonitor", name)
|
||||
ui.open()
|
||||
|
||||
/datum/ui_module/crew_monitor/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/simple/nanomaps)
|
||||
)
|
||||
|
||||
/datum/ui_module/crew_monitor/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
name = "Destination Tagger"
|
||||
var/my_tag = 1
|
||||
|
||||
/datum/ui_module/destination_tagger/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/destination_tagger/ui_state(mob/user)
|
||||
return GLOB.default_state
|
||||
|
||||
/datum/ui_module/destination_tagger/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "DestinationTagger", name, 400, 350, master_ui, state)
|
||||
ui = new(user, src, "DestinationTagger", name)
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
/// The below is a toggle for if sec cyborgs are enabled or not
|
||||
var/cyborg_security = FALSE
|
||||
|
||||
/datum/ui_module/ert_manager/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.admin_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/ert_manager/ui_state(mob/user)
|
||||
return GLOB.admin_state
|
||||
|
||||
/datum/ui_module/ert_manager/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "ERTManager", name, 350, 540, master_ui, state)
|
||||
ui = new(user, src, "ERTManager", name)
|
||||
ui.autoupdate = TRUE
|
||||
ui.open()
|
||||
|
||||
@@ -96,7 +99,7 @@
|
||||
if(cyborg_slots > 0)
|
||||
slots_list += "cyborg: [cyborg_slots]"
|
||||
|
||||
var/silenced = text2bool(params["silent"])
|
||||
var/silenced = (params["silent"])
|
||||
D.silent = silenced
|
||||
|
||||
var/slot_text = english_list(slots_list)
|
||||
|
||||
@@ -3,10 +3,18 @@ GLOBAL_DATUM_INIT(generic_crew_manifest, /datum/ui_module/generic_crew_manifest,
|
||||
/datum/ui_module/generic_crew_manifest
|
||||
name = "Crew Manifest"
|
||||
|
||||
/datum/ui_module/generic_crew_manifest/ui_interact(user, ui_key = "GenericCrewManifest", datum/tgui/ui = null, datum/tgui/master_ui = null, state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui)
|
||||
/datum/ui_module/generic_crew_manifest/ui_state(mob/user)
|
||||
if(isnewplayer(user))
|
||||
return GLOB.always_state
|
||||
if(isobserver(user))
|
||||
return GLOB.observer_state
|
||||
if(issilicon(user))
|
||||
return GLOB.not_incapacitated_state
|
||||
|
||||
/datum/ui_module/generic_crew_manifest/ui_interact(user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "GenericCrewManifest", name, 588, 510, master_ui, state)
|
||||
ui = new(user, src, "GenericCrewManifest", name)
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
|
||||
@@ -16,10 +16,13 @@ GLOBAL_DATUM_INIT(ghost_hud_panel, /datum/ui_module/ghost_hud_panel, new)
|
||||
"diagnostic" = DATA_HUD_DIAGNOSTIC_ADVANCED
|
||||
)
|
||||
|
||||
/datum/ui_module/ghost_hud_panel/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.observer_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui)
|
||||
/datum/ui_module/ghost_hud_panel/ui_state(mob/user)
|
||||
return GLOB.observer_state
|
||||
|
||||
/datum/ui_module/ghost_hud_panel/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "GhostHudPanel", name, 250, 207, master_ui, state)
|
||||
ui = new(user, src, "GhostHudPanel", name)
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
|
||||
@@ -149,10 +149,17 @@
|
||||
to_chat(usr, "<span class='notice'>Laws displayed.</span>")
|
||||
|
||||
|
||||
/datum/ui_module/law_manager/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/law_manager/ui_state(mob/user)
|
||||
if(check_rights(R_ADMIN, FALSE))
|
||||
return GLOB.admin_state
|
||||
if(issilicon(user))
|
||||
return GLOB.conscious_state
|
||||
return GLOB.default_state
|
||||
|
||||
/datum/ui_module/law_manager/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "LawManager", sanitize("[src] - [owner.name]"), 800, is_malf(user) ? 600 : 400, master_ui, state)
|
||||
ui = new(user, src, "LawManager", sanitize("[src] - [owner.name]"))
|
||||
ui.open()
|
||||
|
||||
/datum/ui_module/law_manager/ui_data(mob/user)
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
if(!select_monitor)
|
||||
powermonitor = ui_host()
|
||||
|
||||
/datum/ui_module/power_monitor/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/power_monitor/ui_state(mob/user)
|
||||
return GLOB.default_state
|
||||
|
||||
/datum/ui_module/power_monitor/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "PowerMonitor", name, 600, 650, master_ui, state)
|
||||
ui = new(user, src, "PowerMonitor", name)
|
||||
ui.open()
|
||||
|
||||
/datum/ui_module/power_monitor/ui_data(mob/user)
|
||||
|
||||
@@ -7,10 +7,13 @@
|
||||
CRASH("A [S.type] was passed to /datum/ui_module/robot_self_diagnosis/New().")
|
||||
owner = S
|
||||
|
||||
/datum/ui_module/robot_self_diagnosis/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/robot_self_diagnosis/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/ui_module/robot_self_diagnosis/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "RobotSelfDiagnosis", "Component Self Diagnosis", 280, 480, master_ui, state)
|
||||
ui = new(user, src, "RobotSelfDiagnosis", "Component Self Diagnosis")
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
|
||||
@@ -114,10 +114,13 @@
|
||||
while(!choice && !closed)
|
||||
stoplag(1)
|
||||
|
||||
/datum/tgui_list_input/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/tgui_list_input/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/tgui_list_input/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "ListInput", title, 325, 355, master_ui, state)
|
||||
ui = new(user, src, "ListInput", title)
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
/datum/ui_module/volume_mixer
|
||||
name = "Volume Mixer"
|
||||
|
||||
/datum/ui_module/volume_mixer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state)
|
||||
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
|
||||
/datum/ui_module/volume_mixer/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/ui_module/volume_mixer/ui_interact(mob/user, datum/tgui/ui = null)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, ui_key, "VolumeMixer", name, 400, clamp(80 + 50 * length(user.client.prefs.volume_mixer), 300, 600), master_ui, state)
|
||||
ui = new(user, src, "VolumeMixer", name)
|
||||
ui.set_autoupdate(FALSE)
|
||||
ui.open()
|
||||
|
||||
|
||||
+47
-34
@@ -1,10 +1,11 @@
|
||||
/**
|
||||
* tgui states
|
||||
* Base state and helpers for states. Just does some sanity checks,
|
||||
* implement a proper state for in-depth checks.
|
||||
*
|
||||
* Base state and helpers for states. Just does some sanity checks, implement a state for in-depth checks.
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
@@ -17,19 +18,20 @@
|
||||
*/
|
||||
/datum/proc/ui_status(mob/user, datum/ui_state/state)
|
||||
var/src_object = ui_host(user)
|
||||
. = STATUS_CLOSE
|
||||
. = UI_CLOSE
|
||||
if(!state)
|
||||
return
|
||||
|
||||
if(isobserver(user))
|
||||
// If they turn on ghost AI control, admins can always interact.
|
||||
if(user.client.advanced_admin_interaction)
|
||||
. = max(., STATUS_INTERACTIVE)
|
||||
if(user.can_admin_interact())
|
||||
. = max(., UI_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(., UI_UPDATE)
|
||||
|
||||
// Check if the state allows interaction
|
||||
var/result = state.can_use_topic(src_object, user)
|
||||
@@ -47,7 +49,8 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/datum/ui_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 UI_CLOSE
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -57,22 +60,27 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/mob/proc/shared_ui_interaction(src_object)
|
||||
if(!client) // Close UIs if mindless.
|
||||
return STATUS_CLOSE
|
||||
else if(stat) // Disable UIs if unconcious.
|
||||
return STATUS_DISABLED
|
||||
else if(incapacitated()) // Update UIs if incapicitated but concious.
|
||||
return STATUS_UPDATE
|
||||
return STATUS_INTERACTIVE
|
||||
// Close UIs if mindless.
|
||||
if(!client)
|
||||
return UI_CLOSE
|
||||
// Disable UIs if unconcious.
|
||||
else if(stat)
|
||||
return UI_DISABLED
|
||||
// Update UIs if incapicitated but concious.
|
||||
else if(incapacitated())
|
||||
return UI_UPDATE
|
||||
return UI_INTERACTIVE
|
||||
|
||||
/mob/living/silicon/ai/shared_ui_interaction(src_object)
|
||||
if(lacks_power() && !apc_override) // Disable UIs if the AI is unpowered.
|
||||
return STATUS_DISABLED
|
||||
// Disable UIs if the AI is unpowered.
|
||||
if(lacks_power() && !apc_override)
|
||||
return UI_DISABLED
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/robot/shared_ui_interaction(src_object)
|
||||
if(!cell || cell.charge <= 0 || lockcharge) // Disable UIs if the Borg is unpowered or locked.
|
||||
return STATUS_DISABLED
|
||||
// Disable UIs if the Borg is unpowered or locked.
|
||||
if(!cell || cell.charge <= 0 || lockcharge)
|
||||
return UI_DISABLED
|
||||
return ..()
|
||||
|
||||
/**
|
||||
@@ -88,7 +96,8 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/atom/proc/contents_ui_distance(src_object, mob/living/user)
|
||||
return user.shared_living_ui_distance(src_object) // Just call this mob's check.
|
||||
// Just call this mob's check.
|
||||
return user.shared_living_ui_distance(src_object)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -100,19 +109,23 @@
|
||||
* return UI_state The state of the UI.
|
||||
*/
|
||||
/mob/living/proc/shared_living_ui_distance(atom/movable/src_object, viewcheck = TRUE)
|
||||
if(viewcheck && !(src_object in view(src))) // If the object is obscured, close it.
|
||||
return STATUS_CLOSE
|
||||
|
||||
// If the object is obscured, close it.
|
||||
if(viewcheck && !(src_object in view(src)))
|
||||
return UI_CLOSE
|
||||
var/dist = get_dist(src_object, src)
|
||||
if(dist <= 1) // Open and interact if 1-0 tiles away.
|
||||
return STATUS_INTERACTIVE
|
||||
else if(dist <= 2) // View only if 2-3 tiles away.
|
||||
return STATUS_UPDATE
|
||||
else if(dist <= 5) // Disable if 5 tiles away.
|
||||
return STATUS_DISABLED
|
||||
return STATUS_CLOSE // Otherwise, we got nothing.
|
||||
// Open and interact if 1-0 tiles away.
|
||||
if(dist <= 1)
|
||||
return UI_INTERACTIVE
|
||||
// View only if 2-3 tiles away.
|
||||
else if(dist <= 2)
|
||||
return UI_UPDATE
|
||||
// Disable if 5 tiles away.
|
||||
else if(dist <= 5)
|
||||
return UI_DISABLED
|
||||
// Otherwise, we got nothing.
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/carbon/human/shared_living_ui_distance(atom/movable/src_object)
|
||||
/mob/living/carbon/human/shared_living_ui_distance(atom/movable/src_object, viewcheck)
|
||||
if(HAS_TRAIT(src, TRAIT_TELEKINESIS) && (get_dist(src, src_object) <= 2))
|
||||
return STATUS_INTERACTIVE
|
||||
return UI_INTERACTIVE
|
||||
return ..()
|
||||
|
||||
@@ -8,5 +8,5 @@ GLOBAL_DATUM_INIT(admin_state, /datum/ui_state/admin_state, new)
|
||||
|
||||
/datum/ui_state/admin_state/can_use_topic(src_object, mob/user)
|
||||
if(check_rights_for(user.client, R_ADMIN))
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: always_state
|
||||
*
|
||||
* Always grants the user STATUS_INTERACTIVE. Period.
|
||||
* Always grants the user UI_INTERACTIVE. Period.
|
||||
*/
|
||||
|
||||
GLOBAL_DATUM_INIT(always_state, /datum/ui_state/always_state, new)
|
||||
|
||||
/datum/ui_state/always_state/can_use_topic(src_object, mob/user)
|
||||
return STATUS_INTERACTIVE
|
||||
return UI_INTERACTIVE
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: conscious_state
|
||||
*
|
||||
@@ -8,5 +13,5 @@ GLOBAL_DATUM_INIT(conscious_state, /datum/ui_state/conscious_state, new)
|
||||
|
||||
/datum/ui_state/conscious_state/can_use_topic(src_object, mob/user)
|
||||
if(user.stat == CONSCIOUS)
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: contained_state
|
||||
*
|
||||
@@ -9,6 +14,6 @@ GLOBAL_DATUM_INIT(contained_state, /datum/ui_state/contained_state, new)
|
||||
|
||||
/datum/ui_state/contained_state/can_use_topic(atom/src_object, mob/user)
|
||||
if(!src_object.contains(user))
|
||||
return STATUS_CLOSE
|
||||
return UI_CLOSE
|
||||
return user.shared_ui_interaction(src_object)
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: deep_inventory_state
|
||||
*
|
||||
* Checks that the src_object is in the user's deep (backpack, box, toolbox, etc) inventory.
|
||||
* Checks that the src_object is in the user's deep
|
||||
* (backpack, box, toolbox, etc) inventory.
|
||||
*/
|
||||
|
||||
GLOBAL_DATUM_INIT(deep_inventory_state, /datum/ui_state/deep_inventory_state, new)
|
||||
|
||||
/datum/ui_state/deep_inventory_state/can_use_topic(src_object, mob/user)
|
||||
if(!user.contains(src_object))
|
||||
return STATUS_CLOSE
|
||||
return UI_CLOSE
|
||||
return user.shared_ui_interaction(src_object)
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
/**
|
||||
* tgui state: default_state
|
||||
*
|
||||
* Checks a number of things -- mostly physical distance for humans and view for robots.
|
||||
* Checks a number of things -- mostly physical distance for humans
|
||||
* and view for robots.
|
||||
*
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
|
||||
@@ -10,67 +14,67 @@ GLOBAL_DATUM_INIT(default_state, /datum/ui_state/default, new)
|
||||
return user.default_can_use_topic(src_object) // Call the individual mob-overridden procs.
|
||||
|
||||
/mob/proc/default_can_use_topic(src_object)
|
||||
return STATUS_CLOSE // Don't allow interaction by default.
|
||||
return UI_CLOSE // Don't allow interaction by default.
|
||||
|
||||
/mob/living/default_can_use_topic(src_object)
|
||||
. = shared_ui_interaction(src_object)
|
||||
if(. > STATUS_CLOSE && loc)
|
||||
if(. > UI_CLOSE && loc) //must not be in nullspace.
|
||||
. = min(., loc.contents_ui_distance(src_object, src)) // Check the distance...
|
||||
if(. == STATUS_INTERACTIVE) // Non-human living mobs can only look, not touch.
|
||||
return STATUS_UPDATE
|
||||
if(HAS_TRAIT(src, TRAIT_HANDS_BLOCKED) && . == UI_INTERACTIVE) // Non-human living mobs and mobs with blocked hands can only look, not touch.
|
||||
return UI_UPDATE
|
||||
|
||||
/mob/living/carbon/human/default_can_use_topic(src_object)
|
||||
. = shared_ui_interaction(src_object)
|
||||
if(. > STATUS_CLOSE)
|
||||
if(. > UI_CLOSE)
|
||||
. = min(., shared_living_ui_distance(src_object)) // Check the distance...
|
||||
|
||||
/mob/living/silicon/robot/default_can_use_topic(src_object)
|
||||
. = shared_ui_interaction(src_object)
|
||||
if(. <= STATUS_DISABLED)
|
||||
if(. <= UI_DISABLED)
|
||||
return
|
||||
|
||||
// Robots can interact with anything they can see.
|
||||
var/list/clientviewlist = getviewsize(client.view)
|
||||
if((src_object in view(src)) && (get_dist(src, src_object) <= max(clientviewlist[1], clientviewlist[2])))
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_DISABLED // Otherwise they can keep the UI open.
|
||||
if((src_object in view(src)) && get_dist(src, src_object) <= max(clientviewlist[1], clientviewlist[2]))
|
||||
return UI_INTERACTIVE
|
||||
return UI_DISABLED // Otherwise they can keep the UI open.
|
||||
|
||||
/mob/living/silicon/ai/default_can_use_topic(src_object)
|
||||
. = shared_ui_interaction(src_object)
|
||||
if(. < STATUS_INTERACTIVE)
|
||||
if(. < UI_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))
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/simple_animal/revenant/default_can_use_topic(src_object)
|
||||
return STATUS_UPDATE
|
||||
return UI_UPDATE
|
||||
|
||||
/mob/living/simple_animal/demon/pulse_demon/default_can_use_topic(src_object)
|
||||
. = shared_ui_interaction(src_object)
|
||||
if(. < STATUS_INTERACTIVE)
|
||||
if(. < UI_INTERACTIVE)
|
||||
return
|
||||
|
||||
// anything in its APC's area
|
||||
if(get_area(src_object) == controlling_area)
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/simple_animal/default_can_use_topic(src_object)
|
||||
. = shared_ui_interaction(src_object)
|
||||
if(. > STATUS_CLOSE)
|
||||
if(. > UI_CLOSE)
|
||||
. = min(., shared_living_ui_distance(src_object)) //simple animals can only use things they're near.
|
||||
|
||||
/mob/living/silicon/pai/default_can_use_topic(src_object)
|
||||
// pAIs can only use themselves and the owner's radio.
|
||||
if((src_object == src || src_object == radio) && !stat)
|
||||
return STATUS_INTERACTIVE
|
||||
return UI_INTERACTIVE
|
||||
else
|
||||
return ..()
|
||||
|
||||
/mob/dead/observer/default_can_use_topic()
|
||||
/mob/dead/observer/default_can_use_topic(src_object)
|
||||
if(can_admin_interact())
|
||||
return STATUS_INTERACTIVE // Admins are more equal
|
||||
return STATUS_UPDATE // Ghosts can view updates
|
||||
return UI_INTERACTIVE // Admins are more equal
|
||||
return UI_UPDATE // Ghosts can view updates
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: hands_state
|
||||
*
|
||||
@@ -8,21 +13,21 @@ GLOBAL_DATUM_INIT(hands_state, /datum/ui_state/hands_state, new)
|
||||
|
||||
/datum/ui_state/hands_state/can_use_topic(src_object, mob/user)
|
||||
. = user.shared_ui_interaction(src_object)
|
||||
if(. > STATUS_CLOSE)
|
||||
if(. > UI_CLOSE)
|
||||
return min(., user.hands_can_use_topic(src_object))
|
||||
|
||||
/mob/proc/hands_can_use_topic(src_object)
|
||||
return STATUS_CLOSE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/hands_can_use_topic(src_object)
|
||||
if(is_in_active_hand(src_object) || is_in_inactive_hand(src_object))
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
|
||||
/mob/living/simple_animal/revenant/hands_can_use_topic(src_object)
|
||||
return STATUS_UPDATE
|
||||
if(is_holding(src_object))
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/silicon/robot/hands_can_use_topic(src_object)
|
||||
if(activated(src_object))
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/simple_animal/revenant/hands_can_use_topic(src_object)
|
||||
return UI_UPDATE
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: human_adjacent_state
|
||||
*
|
||||
@@ -13,4 +18,4 @@ GLOBAL_DATUM_INIT(human_adjacent_state, /datum/ui_state/human_adjacent_state, ne
|
||||
var/dist = get_dist(src_object, user)
|
||||
if((dist > 1) || (!ishuman(user)))
|
||||
// Can't be used unless adjacent and human, even with TK
|
||||
. = min(., STATUS_UPDATE)
|
||||
. = min(., UI_UPDATE)
|
||||
|
||||
@@ -12,5 +12,5 @@ GLOBAL_DATUM_INIT(inventory_state, /datum/ui_state/inventory_state, new)
|
||||
var/mob/living/silicon/robot/R = user
|
||||
if(src_object in R.module_active) // Magnetic grippers
|
||||
return user.shared_ui_interaction(src_object)
|
||||
return STATUS_CLOSE
|
||||
return UI_CLOSE
|
||||
return user.shared_ui_interaction(src_object)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: not_incapacitated_state
|
||||
*
|
||||
@@ -22,8 +27,8 @@ GLOBAL_DATUM_INIT(not_incapacitated_turf_state, /datum/ui_state/not_incapacitate
|
||||
turf_check = no_turfs
|
||||
|
||||
/datum/ui_state/not_incapacitated_state/can_use_topic(src_object, mob/user)
|
||||
if(user.stat)
|
||||
return STATUS_CLOSE
|
||||
if(user.incapacitated() || (turf_check && !isturf(user.loc)))
|
||||
return STATUS_DISABLED
|
||||
return STATUS_INTERACTIVE
|
||||
if(user.stat != CONSCIOUS)
|
||||
return UI_CLOSE
|
||||
if(HAS_TRAIT(src, TRAIT_UI_BLOCKED) || user.incapacitated() || (turf_check && !isturf(user.loc)))
|
||||
return UI_DISABLED
|
||||
return UI_INTERACTIVE
|
||||
|
||||
@@ -1,25 +1,33 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: notcontained_state
|
||||
*
|
||||
* Checks that the user is not inside src_object, and then makes the default checks.
|
||||
* Checks that the user is not inside src_object, and then makes the
|
||||
* default checks.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
GLOBAL_DATUM_INIT(notcontained_state, /datum/ui_state/notcontained_state, new)
|
||||
|
||||
/datum/ui_state/notcontained_state/can_use_topic(atom/src_object, mob/user)
|
||||
. = user.shared_ui_interaction(src_object)
|
||||
if(. > STATUS_CLOSE)
|
||||
if(. > UI_CLOSE)
|
||||
return min(., user.notcontained_can_use_topic(src_object))
|
||||
|
||||
/mob/proc/notcontained_can_use_topic(src_object)
|
||||
return STATUS_CLOSE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/notcontained_can_use_topic(atom/src_object)
|
||||
if(src_object.contains(src))
|
||||
return STATUS_CLOSE // Close if we're inside it.
|
||||
return UI_CLOSE // Close if we're inside it.
|
||||
return default_can_use_topic(src_object)
|
||||
|
||||
/mob/living/silicon/notcontained_can_use_topic(src_object)
|
||||
return default_can_use_topic(src_object) // Silicons use default bevhavior.
|
||||
*/
|
||||
return default_can_use_topic(src_object) // Silicons use default behavior.
|
||||
|
||||
/mob/living/simple_animal/drone/notcontained_can_use_topic(src_object)
|
||||
return default_can_use_topic(src_object) // Drones use default behavior.
|
||||
|
||||
@@ -8,8 +8,8 @@ GLOBAL_DATUM_INIT(observer_state, /datum/ui_state/observer_state, new)
|
||||
|
||||
/datum/ui_state/observer_state/can_use_topic(src_object, mob/user)
|
||||
if(isobserver(user))
|
||||
return STATUS_INTERACTIVE
|
||||
return UI_INTERACTIVE
|
||||
if(check_rights(R_ADMIN, 0, src))
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
return UI_INTERACTIVE
|
||||
return UI_CLOSE
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: physical_state
|
||||
*
|
||||
@@ -8,23 +13,49 @@ GLOBAL_DATUM_INIT(physical_state, /datum/ui_state/physical, new)
|
||||
|
||||
/datum/ui_state/physical/can_use_topic(src_object, mob/user)
|
||||
. = user.shared_ui_interaction(src_object)
|
||||
if(. > STATUS_CLOSE)
|
||||
if(. > UI_CLOSE)
|
||||
return min(., user.physical_can_use_topic(src_object))
|
||||
|
||||
/mob/proc/physical_can_use_topic(src_object)
|
||||
return STATUS_CLOSE
|
||||
|
||||
/mob/living/simple_animal/revenant/physical_can_use_topic(src_object)
|
||||
return STATUS_UPDATE
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/simple_animal/demon/pulse_demon/physical_can_use_topic(src_object)
|
||||
return STATUS_UPDATE
|
||||
return UI_UPDATE
|
||||
|
||||
/mob/living/physical_can_use_topic(src_object)
|
||||
return shared_living_ui_distance(src_object)
|
||||
|
||||
/mob/living/silicon/physical_can_use_topic(src_object)
|
||||
return max(STATUS_UPDATE, shared_living_ui_distance(src_object)) // Silicons can always see.
|
||||
return max(UI_UPDATE, shared_living_ui_distance(src_object)) // Silicons can always see.
|
||||
|
||||
/mob/living/silicon/ai/physical_can_use_topic(src_object)
|
||||
return STATUS_UPDATE // AIs are not physical.
|
||||
return UI_UPDATE // AIs are not physical.
|
||||
|
||||
/mob/living/simple_animal/revenant/physical_can_use_topic(src_object)
|
||||
return UI_UPDATE
|
||||
|
||||
/**
|
||||
* tgui state: physical_obscured_state
|
||||
*
|
||||
* Short-circuits the default state to only check physical distance, being in view doesn't matter
|
||||
*/
|
||||
|
||||
GLOBAL_DATUM_INIT(physical_obscured_state, /datum/ui_state/physical_obscured_state, new)
|
||||
|
||||
/datum/ui_state/physical_obscured_state/can_use_topic(src_object, mob/user)
|
||||
. = user.shared_ui_interaction(src_object)
|
||||
if(. > UI_CLOSE)
|
||||
return min(., user.physical_obscured_can_use_topic(src_object))
|
||||
|
||||
/mob/proc/physical_obscured_can_use_topic(src_object)
|
||||
return UI_CLOSE
|
||||
|
||||
/mob/living/physical_obscured_can_use_topic(src_object)
|
||||
return shared_living_ui_distance(src_object, viewcheck = FALSE)
|
||||
|
||||
/mob/living/silicon/physical_obscured_can_use_topic(src_object)
|
||||
return max(UI_UPDATE, shared_living_ui_distance(src_object, viewcheck = FALSE)) // Silicons can always see.
|
||||
|
||||
/mob/living/silicon/ai/physical_obscured_can_use_topic(src_object)
|
||||
return UI_UPDATE // AIs are not physical.
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui state: self_state
|
||||
*
|
||||
@@ -8,5 +13,5 @@ GLOBAL_DATUM_INIT(self_state, /datum/ui_state/self_state, new)
|
||||
|
||||
/datum/ui_state/self_state/can_use_topic(src_object, mob/user)
|
||||
if(src_object != user)
|
||||
return STATUS_CLOSE
|
||||
return UI_CLOSE
|
||||
return user.shared_ui_interaction(src_object)
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
GLOBAL_DATUM_INIT(viewer_state, /datum/ui_state/viewer_state, new)
|
||||
|
||||
/datum/ui_state/viewer_state/can_use_topic(src_object, mob/user)
|
||||
return STATUS_UPDATE
|
||||
return UI_UPDATE
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* tgui state: z_state
|
||||
*
|
||||
* Only checks that the Z-level of the user and src_object are the same.
|
||||
*/
|
||||
|
||||
/*
|
||||
GLOBAL_DATUM_INIT(z_state, /datum/ui_state/z_state, new)
|
||||
|
||||
/datum/ui_state/z_state/can_use_topic(src_object, mob/user)
|
||||
var/turf/turf_obj = get_turf(src_object)
|
||||
var/turf/turf_usr = get_turf(user)
|
||||
if(turf_obj && turf_usr && turf_obj.z == turf_usr.z)
|
||||
return STATUS_INTERACTIVE
|
||||
return STATUS_CLOSE
|
||||
*/
|
||||
+221
-254
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* tgui
|
||||
*
|
||||
* /tg/station user interface library
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -14,34 +13,32 @@
|
||||
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
|
||||
var/status = UI_INTERACTIVE
|
||||
/// Timed refreshing state
|
||||
var/refreshing = FALSE
|
||||
/// Topic state used to determine status/interactability.
|
||||
var/datum/ui_state/state = null
|
||||
/// The parent UI.
|
||||
var/datum/tgui/master_ui
|
||||
/// Children of this UI.
|
||||
var/list/datum/tgui/children = list()
|
||||
/// Asset data to be sent with every update
|
||||
var/list/asset_data
|
||||
/// Rate limit client refreshes to prevent DoS.
|
||||
COOLDOWN_DECLARE(refresh_cooldown)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -50,39 +47,25 @@
|
||||
*
|
||||
* 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/ui_state/state = GLOB.default_state)
|
||||
/datum/tgui/New(mob/user, datum/src_object, interface, title, ui_x, ui_y)
|
||||
log_tgui(user, "new [interface]")
|
||||
src.user = user
|
||||
src.src_object = src_object
|
||||
src.ui_key = ui_key
|
||||
src.window_id = "[src_object.UID()]-[ui_key]"
|
||||
src.window_key = "[src_object.UID()]-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.ui_state(user)
|
||||
// Deprecated
|
||||
if(ui_x && ui_y)
|
||||
src.window_size = list(ui_x, ui_y)
|
||||
|
||||
/**
|
||||
* public
|
||||
@@ -91,82 +74,61 @@
|
||||
*/
|
||||
/datum/tgui/proc/open()
|
||||
if(!user.client)
|
||||
return // Bail if there is no client.
|
||||
|
||||
update_status(push = FALSE) // Update the window 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.toggles2 & PREFTOGGLE_2_FANCYUI)
|
||||
window_options += "titlebar=0;can_resize=0;"
|
||||
return null
|
||||
if(window)
|
||||
return null
|
||||
process_status()
|
||||
if(status < UI_UPDATE)
|
||||
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(
|
||||
fancy = (user.client.prefs.toggles2 & PREFTOGGLE_2_FANCYUI),
|
||||
assets = list(
|
||||
get_asset_datum(/datum/asset/simple/tgui),
|
||||
))
|
||||
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.ui_base_html(html)
|
||||
// Replace template tokens with important UI data
|
||||
html = replacetextEx(html, "\[tgui:ref]", "[src.UID()]")
|
||||
|
||||
// Open the window.
|
||||
user << browse(html, "window=[window_id];[window_options]")
|
||||
|
||||
// Instruct the client to signal UI when the window is closed.
|
||||
winset(user, window_id, "on-close=\"uiclose [src.UID()]\"")
|
||||
|
||||
// Pre-fetch initial state while browser is still loading in
|
||||
// another thread
|
||||
if(!initial_data)
|
||||
initial_data = src_object.ui_data(user)
|
||||
if(!initial_static_data)
|
||||
initial_static_data = src_object.ui_static_data(user)
|
||||
_initial_update = url_encode(get_json(initial_data, initial_static_data))
|
||||
|
||||
window.send_message("ping")
|
||||
send_assets()
|
||||
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()
|
||||
/datum/tgui/proc/send_assets()
|
||||
var/flushqueue = window.send_asset(get_asset_datum(
|
||||
/datum/asset/simple/namespaced/fontawesome))
|
||||
for(var/datum/asset/asset in src_object.ui_assets(user))
|
||||
flushqueue |= window.send_asset(asset)
|
||||
if(flushqueue)
|
||||
user.client.browse_queue_flush()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Close the UI, and all its children.
|
||||
* Close the UI.
|
||||
*
|
||||
* optional can_be_suspended bool
|
||||
*/
|
||||
/datum/tgui/proc/close()
|
||||
user << browse(null, "window=[window_id]") // Close the window.
|
||||
src_object.ui_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.ui_close(user)
|
||||
SStgui.on_close(src)
|
||||
state = null
|
||||
master_ui = null
|
||||
qdel(src)
|
||||
|
||||
/**
|
||||
@@ -174,184 +136,189 @@
|
||||
*
|
||||
* Enable/disable auto-updating of the UI.
|
||||
*
|
||||
* required state bool Enable/disable auto-updating.
|
||||
* required value 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/ui_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().")
|
||||
return 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
|
||||
if(!COOLDOWN_FINISHED(src, refresh_cooldown))
|
||||
refreshing = TRUE
|
||||
addtimer(CALLBACK(src, PROC_REF(send_full_update)), TGUI_REFRESH_FULL_UPDATE_COOLDOWN, TIMER_UNIQUE)
|
||||
return
|
||||
refreshing = FALSE
|
||||
var/should_update_data = force || status >= UI_UPDATE
|
||||
window.send_message("update", get_payload(
|
||||
custom_data,
|
||||
with_data = should_update_data,
|
||||
with_static_data = TRUE))
|
||||
COOLDOWN_START(src, refresh_cooldown, TGUI_REFRESH_FULL_UPDATE_COOLDOWN)
|
||||
|
||||
/**
|
||||
* 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 >= UI_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.toggles2 & PREFTOGGLE_2_FANCYUI,
|
||||
"observer" = isobserver(user),
|
||||
"window" = window_id,
|
||||
"refreshing" = refreshing,
|
||||
"window" = list(
|
||||
"key" = window_key,
|
||||
"size" = window_size,
|
||||
"fancy" = (user.client?.prefs?.toggles2 & PREFTOGGLE_2_FANCYUI),
|
||||
"locked" = FALSE,
|
||||
),
|
||||
"client" = list(
|
||||
"ckey" = user.client.ckey,
|
||||
"address" = user.client.address,
|
||||
"computer_id" = user.client.computer_id,
|
||||
),
|
||||
"user" = list(
|
||||
"name" = "[user]",
|
||||
"observer" = isobserver(user),
|
||||
),
|
||||
"map" = SSmapping.map_datum.technical_name,
|
||||
"ref" = "[src.UID()]"
|
||||
)
|
||||
|
||||
if(!isnull(data))
|
||||
var/data = custom_data || with_data && src_object.ui_data(user)
|
||||
if(data)
|
||||
json_data["data"] = data
|
||||
if(!isnull(static_data))
|
||||
var/static_data = with_static_data && src_object.ui_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
|
||||
|
||||
// Generate the JSON.
|
||||
var/json = json_encode(json_data)
|
||||
// Strip #255/improper.
|
||||
json = replacetext(json, "\proper", "")
|
||||
json = replacetext(json, "\improper", "")
|
||||
return json
|
||||
return json_data
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Handle clicks from the UI.
|
||||
* 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")
|
||||
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(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")
|
||||
user.client.prefs.toggles2 ^= PREFTOGGLE_2_FANCYUI
|
||||
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"])
|
||||
else
|
||||
// Update the window state.
|
||||
update_status(push = FALSE)
|
||||
// Call ui_act() on the src_object.
|
||||
if(src_object.ui_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.
|
||||
* 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.ui_host(user)
|
||||
if(!src_object || !host || !user) // If the object or user died (or something else), abort.
|
||||
// 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 != UI_DISABLED && (autoupdate || force))
|
||||
src_object.ui_interact(user, src)
|
||||
return
|
||||
// Update status only
|
||||
var/needs_update = process_status()
|
||||
if(status <= UI_CLOSE)
|
||||
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.
|
||||
if(needs_update)
|
||||
window.send_message("update", get_payload())
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Updates the status, and returns TRUE if status has changed.
|
||||
*/
|
||||
/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")
|
||||
/datum/tgui/proc/process_status()
|
||||
var/prev_status = status
|
||||
status = src_object.ui_status(user, state)
|
||||
return prev_status != status
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Callback for handling incoming tgui messages.
|
||||
*/
|
||||
/datum/tgui/proc/update(force_open = FALSE)
|
||||
src_object.ui_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.ui_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/on_message(type, list/payload, list/href_list)
|
||||
// Pass act type messages to ui_act
|
||||
if(type && copytext(type, 1, 5) == "act/")
|
||||
process_status()
|
||||
if(src_object.ui_act(copytext(type, 5), payload, src, state))
|
||||
SStgui.update_uis(src_object)
|
||||
return FALSE
|
||||
switch(type)
|
||||
if("ready")
|
||||
// Send a full update when the user manually refreshes the UI
|
||||
if(initialized)
|
||||
send_full_update()
|
||||
initialized = TRUE
|
||||
if("ping/reply")
|
||||
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 != UI_INTERACTIVE)
|
||||
return
|
||||
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,30 @@
|
||||
## /TG/ Chat
|
||||
|
||||
/TG/ Chat, which will be referred to as TgChat from this point onwards, is a system in which we can send messages to clients in a controlled and semi-reliable manner. The standard way of sending messages to BYOND clients simply dumps whatever you output to them directly into their chat window, however BYOND allows us to load our own code on the client to change this behaviour in a way that allows us to do some pretty neat things.
|
||||
|
||||
### Message Format
|
||||
|
||||
TgChat handles sending messages from the server to the client through the use of JSON payloads, of which the format will change depending on the type of message and the intended client endpoint. An example of the payload for chat messages is as follows:
|
||||
```json
|
||||
{
|
||||
"sequence": 0,
|
||||
"content": {
|
||||
"type": ". . .", // ?optional
|
||||
"text": ". . .", // ?optional !atleast-one
|
||||
"html": ". . .", // ?optional !atleast-one
|
||||
"avoidHighlighting": 0 // ?optional
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Reliability
|
||||
|
||||
In the past there have been issues where BYOND will silently and without reason lose a message we sent to the client, to detect this and recover from it seamlessly TgChat also has a baked in reliability layer. This reliability layer is very primitive, and simply keeps track of recieved sequence numbers. Should the client recieve an unexpected sequence number TgChat asks the server to resend any missing packets.
|
||||
|
||||
### Ping System
|
||||
|
||||
TgChat supports a round trip time ping measurement, which is displayed to the client so they can know how long it takes for their commands and inputs to reach the server. This is done by sending the client a ping request, `ping/soft`, which tells the client to send a ping to the server. When the server recieves said ping it sends a reply, `ping/reply`, to the client with a payload containing the current DateTime which the client can reference against the initial ping request.
|
||||
|
||||
### Chat Tabs, Local Storage, and Highlighting
|
||||
|
||||
To make organizing and managing chat easier and more functional for both players and admins, TgChat has the ability to filter out messages based on their primary tag, such as individual departmental radios, to a dedicated chat tab for easier reading and comprehension. These tabs can also be configured to highlist messages based on a simple keyword search. You can set a multitude of different keywords to search for and they will be highlighting for instant alerting of the client. Said tabs, highlighting rules, and your chat history will persist thanks to use of local storage on the client. Using local storage TgChat can ensure that your preferences are saved and maintained between client restarts and switching between other /TG/ servers. Local Storage is also used to keep your chat history aswell, should you need to scroll through your chat logs.
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/// Admin music volume, from 0 to 1.
|
||||
/client/var/admin_music_volume = 1
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Sends music data to the browser.
|
||||
*
|
||||
* Optional settings:
|
||||
* - pitch: the playback rate
|
||||
* - start: the start time of the sound
|
||||
* - end: when the musics stops playing
|
||||
*
|
||||
* required url string Must be an https URL.
|
||||
* optional extra_data list Optional settings.
|
||||
*/
|
||||
/datum/tgui_panel/proc/play_music(url, extra_data)
|
||||
if(!is_ready())
|
||||
return
|
||||
var/list/payload = list()
|
||||
if(length(extra_data) > 0)
|
||||
for(var/key in extra_data)
|
||||
payload[key] = extra_data[key]
|
||||
payload["url"] = url
|
||||
window.send_message("audio/playMusic", payload)
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Stops playing music through the browser.
|
||||
*/
|
||||
/datum/tgui_panel/proc/stop_music()
|
||||
if(!is_ready())
|
||||
return
|
||||
window.send_message("audio/stopMusic")
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Maximum number of connection records allowed to analyze.
|
||||
* Should match the value set in the browser.
|
||||
*/
|
||||
#define TGUI_TELEMETRY_MAX_CONNECTIONS 5
|
||||
|
||||
/**
|
||||
* Maximum time allocated for sending a telemetry packet.
|
||||
*/
|
||||
#define TGUI_TELEMETRY_RESPONSE_WINDOW 30 SECONDS
|
||||
|
||||
/// Time of telemetry request
|
||||
/datum/tgui_panel/var/telemetry_requested_at
|
||||
/// Time of telemetry analysis completion
|
||||
/datum/tgui_panel/var/telemetry_analyzed_at
|
||||
/// List of previous client connections
|
||||
/datum/tgui_panel/var/list/telemetry_connections
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Requests some telemetry from the client.
|
||||
*/
|
||||
/datum/tgui_panel/proc/request_telemetry()
|
||||
telemetry_requested_at = world.time
|
||||
telemetry_analyzed_at = null
|
||||
window.send_message("telemetry/request", list(
|
||||
"limits" = list(
|
||||
"connections" = TGUI_TELEMETRY_MAX_CONNECTIONS,
|
||||
),
|
||||
))
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Analyzes a telemetry packet.
|
||||
*
|
||||
* Is currently only useful for detecting ban evasion attempts.
|
||||
*/
|
||||
/datum/tgui_panel/proc/analyze_telemetry(payload)
|
||||
if(world.time > telemetry_requested_at + TGUI_TELEMETRY_RESPONSE_WINDOW)
|
||||
message_admins("[key_name(client)] sent telemetry outside of the allocated time window.")
|
||||
return
|
||||
if(telemetry_analyzed_at)
|
||||
message_admins("[key_name(client)] sent telemetry more than once.")
|
||||
return
|
||||
telemetry_analyzed_at = world.time
|
||||
if(!payload)
|
||||
return
|
||||
telemetry_connections = payload["connections"]
|
||||
var/len = length(telemetry_connections)
|
||||
if(len == 0)
|
||||
return
|
||||
if(len > TGUI_TELEMETRY_MAX_CONNECTIONS)
|
||||
message_admins("[key_name(client)] was kicked for sending a huge telemetry payload")
|
||||
qdel(client)
|
||||
return
|
||||
var/list/found
|
||||
for(var/i in 1 to len)
|
||||
if(QDELETED(client))
|
||||
// He got cleaned up before we were done
|
||||
return
|
||||
var/list/row = telemetry_connections[i]
|
||||
// Check for a malformed history object
|
||||
if(!row || row.len < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"]))
|
||||
return
|
||||
if(world.IsBanned(row["ckey"], row["address"], row["computer_id"]))
|
||||
found = row
|
||||
break
|
||||
CHECK_TICK
|
||||
// This fucker has a history of playing on a banned account.
|
||||
if(found)
|
||||
var/msg = "[key_name(client)] has a banned account in connection history! (Matched: [found["ckey"]], [found["address"]], [found["computer_id"]])"
|
||||
message_admins(msg)
|
||||
log_admin(msg)
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* tgui_panel datum
|
||||
* Hosts tgchat and other nice features.
|
||||
*/
|
||||
/datum/tgui_panel
|
||||
var/client/client
|
||||
var/datum/tgui_window/window
|
||||
var/broken = FALSE
|
||||
var/initialized_at
|
||||
|
||||
/datum/tgui_panel/New(client/client)
|
||||
src.client = client
|
||||
window = new(client, "browseroutput")
|
||||
window.subscribe(src, PROC_REF(on_message))
|
||||
|
||||
/datum/tgui_panel/Del()
|
||||
window.unsubscribe(src)
|
||||
window.close()
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* TRUE if panel is initialized and ready to receive messages.
|
||||
*/
|
||||
/datum/tgui_panel/proc/is_ready()
|
||||
return !broken && window.is_ready()
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Initializes tgui panel.
|
||||
*/
|
||||
/datum/tgui_panel/proc/initialize(force = FALSE)
|
||||
set waitfor = FALSE
|
||||
// Minimal sleep to defer initialization to after client constructor
|
||||
sleep(1)
|
||||
initialized_at = world.time
|
||||
// Perform a clean initialization
|
||||
window.initialize(assets = list(
|
||||
get_asset_datum(/datum/asset/simple/tgui_panel),
|
||||
))
|
||||
window.send_asset(get_asset_datum(/datum/asset/simple/namespaced/fontawesome))
|
||||
request_telemetry()
|
||||
addtimer(CALLBACK(src, PROC_REF(on_initialize_timed_out)), 5 SECONDS)
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Called when initialization has timed out.
|
||||
*/
|
||||
/datum/tgui_panel/proc/on_initialize_timed_out()
|
||||
// Currently does nothing but sending a message to old chat.
|
||||
SEND_TEXT(client, "<span class=\"userdanger\">Failed to load fancy chat, click <a href='?src=[UID()];reload_tguipanel=1'>HERE</a> to attempt to reload it.</span>")
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Callback for handling incoming tgui messages.
|
||||
*/
|
||||
/datum/tgui_panel/proc/on_message(type, payload)
|
||||
if(type == "ready")
|
||||
broken = FALSE
|
||||
window.send_message("update", list(
|
||||
"config" = list(
|
||||
"client" = list(
|
||||
"ckey" = client.ckey,
|
||||
"address" = client.address,
|
||||
"computer_id" = client.computer_id,
|
||||
),
|
||||
"window" = list(
|
||||
"fancy" = FALSE,
|
||||
"locked" = FALSE,
|
||||
),
|
||||
),
|
||||
))
|
||||
return TRUE
|
||||
if(type == "audio/setAdminMusicVolume")
|
||||
client.admin_music_volume = payload["volume"]
|
||||
return TRUE
|
||||
if(type == "telemetry")
|
||||
analyze_telemetry(payload)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Sends a round restart notification.
|
||||
*/
|
||||
/datum/tgui_panel/proc/send_roundrestart()
|
||||
window.send_message("roundrestart")
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/client/var/datum/tgui_panel/tgui_panel
|
||||
|
||||
/**
|
||||
* tgui panel / chat troubleshooting verb
|
||||
*/
|
||||
/client/verb/fix_tgui_panel()
|
||||
set name = "Fix chat"
|
||||
set category = "Special Verbs"
|
||||
var/action
|
||||
log_tgui(src, "Started fixing.")
|
||||
|
||||
nuke_chat()
|
||||
|
||||
// Failed to fix
|
||||
action = alert(src, "Did that work?", "", "Yes", "No, switch to old ui")
|
||||
if(action == "No, switch to old ui")
|
||||
winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
|
||||
winset(src, "browseroutput", "is-disabled=1;is-visible=0")
|
||||
log_tgui(src, "Failed to fix.")
|
||||
|
||||
/client/proc/nuke_chat()
|
||||
// Catch all solution (kick the whole thing in the pants)
|
||||
winset(src, "output", "on-show=&is-disabled=0&is-visible=1")
|
||||
winset(src, "browseroutput", "is-disabled=1;is-visible=0")
|
||||
if(!tgui_panel || !istype(tgui_panel))
|
||||
log_tgui(src, "tgui_panel datum is missing")
|
||||
tgui_panel = new(src)
|
||||
tgui_panel.initialize(force = TRUE)
|
||||
// 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")
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Message-related procs
|
||||
*
|
||||
* Message format (/list):
|
||||
* - type - Message type, must be one of defines in `code/__DEFINES/chat.dm`
|
||||
* - text - Plain message text
|
||||
* - html - HTML message text
|
||||
* - Optional metadata, can be any key/value pair.
|
||||
*
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/proc/message_to_html(message)
|
||||
// Here it is possible to add a switch statement
|
||||
// to custom-handle various message types.
|
||||
return message["html"] || message["text"]
|
||||
@@ -0,0 +1,92 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Circumvents the message queue and sends the message
|
||||
* to the recipient (target) as soon as possible.
|
||||
*/
|
||||
/proc/to_chat_immediate(
|
||||
target,
|
||||
html,
|
||||
type = null,
|
||||
text = null,
|
||||
avoid_highlighting = FALSE,
|
||||
// FIXME: These flags are now pointless and have no effect
|
||||
handle_whitespace = TRUE,
|
||||
trailing_newline = TRUE,
|
||||
confidential = FALSE
|
||||
)
|
||||
// Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
|
||||
html = "[html]"
|
||||
text = "[text]"
|
||||
|
||||
if(!target)
|
||||
return
|
||||
if(!html && !text)
|
||||
CRASH("Empty or null string in to_chat proc call.")
|
||||
if(target == world)
|
||||
target = GLOB.clients
|
||||
|
||||
// Build a message
|
||||
var/message = list()
|
||||
if(type)
|
||||
message["type"] = type
|
||||
if(text)
|
||||
message["text"] = text
|
||||
if(html)
|
||||
message["html"] = html
|
||||
if(avoid_highlighting)
|
||||
message["avoidHighlighting"] = avoid_highlighting
|
||||
|
||||
// send it immediately
|
||||
SSchat.send_immediate(target, message)
|
||||
|
||||
/**
|
||||
* Sends the message to the recipient (target).
|
||||
*
|
||||
* Recommended way to write to_chat calls:
|
||||
* ```
|
||||
* to_chat(client,
|
||||
* type = MESSAGE_TYPE_INFO,
|
||||
* html = "You have found <strong>[object]</strong>")
|
||||
* ```
|
||||
*/
|
||||
/proc/to_chat(
|
||||
target,
|
||||
html,
|
||||
type = null,
|
||||
text = null,
|
||||
avoid_highlighting = FALSE,
|
||||
// FIXME: These flags are now pointless and have no effect
|
||||
handle_whitespace = TRUE,
|
||||
trailing_newline = TRUE,
|
||||
confidential = FALSE
|
||||
)
|
||||
if(Master.current_runlevel == RUNLEVEL_INIT || !SSchat?.initialized)
|
||||
to_chat_immediate(target, html, type, text)
|
||||
return
|
||||
|
||||
// Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
|
||||
html = "[html]"
|
||||
text = "[text]"
|
||||
|
||||
if(!target)
|
||||
return
|
||||
if(!html && !text)
|
||||
CRASH("Empty or null string in to_chat proc call.")
|
||||
if(target == world)
|
||||
target = GLOB.clients
|
||||
|
||||
// Build a message
|
||||
var/message = list()
|
||||
if(type)
|
||||
message["type"] = type
|
||||
if(text)
|
||||
message["text"] = text
|
||||
if(html)
|
||||
message["html"] = html
|
||||
if(avoid_highlighting)
|
||||
message["avoidHighlighting"] = avoid_highlighting
|
||||
SSchat.queue(target, message)
|
||||
@@ -0,0 +1,343 @@
|
||||
/**
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/datum/tgui_window
|
||||
var/id
|
||||
var/client/client
|
||||
var/pooled
|
||||
var/pool_index
|
||||
var/is_browser = FALSE
|
||||
var/status = TGUI_WINDOW_CLOSED
|
||||
var/locked = FALSE
|
||||
var/datum/tgui/locked_by
|
||||
var/datum/subscriber_object
|
||||
var/subscriber_delegate
|
||||
var/fatally_errored = FALSE
|
||||
var/message_queue
|
||||
var/sent_assets = list()
|
||||
// Vars passed to initialize proc (and saved for later)
|
||||
var/initial_fancy
|
||||
var/initial_assets
|
||||
var/initial_inline_html
|
||||
var/initial_inline_js
|
||||
var/initial_inline_css
|
||||
|
||||
/**
|
||||
* 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.client.tgui_windows[id] = src
|
||||
src.pooled = pooled
|
||||
if(pooled)
|
||||
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 assets list List of assets to inline into the html.
|
||||
* optional inline_html string Custom HTML to inject.
|
||||
* optional fancy bool If TRUE, will hide the window titlebar.
|
||||
*/
|
||||
/datum/tgui_window/proc/initialize(
|
||||
fancy = FALSE,
|
||||
assets = list(),
|
||||
inline_html = "",
|
||||
inline_js = "",
|
||||
inline_css = "")
|
||||
log_tgui(client, "[id]/initialize")
|
||||
if(!client)
|
||||
return
|
||||
src.initial_fancy = fancy
|
||||
src.initial_assets = assets
|
||||
src.initial_inline_html = inline_html
|
||||
src.initial_inline_js = inline_js
|
||||
src.initial_inline_css = inline_css
|
||||
status = TGUI_WINDOW_LOADING
|
||||
fatally_errored = FALSE
|
||||
// Build window options
|
||||
var/options = "file=[id].html;can_minimize=0;auto_format=0;"
|
||||
// Remove titlebar and resize handles for a fancy window
|
||||
if(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)
|
||||
// Inject assets
|
||||
var/inline_assets_str = ""
|
||||
for(var/datum/asset/asset in assets)
|
||||
var/mappings = asset.get_url_mappings()
|
||||
for(var/name in mappings)
|
||||
var/url = mappings[name]
|
||||
// Not encoding since asset strings are considered safe
|
||||
if(copytext(name, -4) == ".css")
|
||||
inline_assets_str += "Byond.loadCss('[url]', true);\n"
|
||||
else if(copytext(name, -3) == ".js")
|
||||
inline_assets_str += "Byond.loadJs('[url]', true);\n"
|
||||
asset.send(client)
|
||||
if(length(inline_assets_str))
|
||||
inline_assets_str = "<script>\n" + inline_assets_str + "</script>\n"
|
||||
html = replacetextEx(html, "<!-- tgui:assets -->\n", inline_assets_str)
|
||||
// Inject inline HTML
|
||||
if(inline_html)
|
||||
html = replacetextEx(html, "<!-- tgui:inline-html -->", inline_html)
|
||||
// Inject inline JS
|
||||
if(inline_js)
|
||||
inline_js = "<script>\n[inline_js]\n</script>"
|
||||
html = replacetextEx(html, "<!-- tgui:inline-js -->", inline_js)
|
||||
// Inject inline CSS
|
||||
if(inline_css)
|
||||
inline_css = "<style>\n[inline_css]\n</style>"
|
||||
html = replacetextEx(html, "<!-- tgui:inline-css -->", inline_css)
|
||||
// Open the window
|
||||
client << browse(html, "window=[id];[options]")
|
||||
// Detect whether the control is a browser
|
||||
is_browser = winexists(client, id) == "BROWSER"
|
||||
// Instruct the client to signal UI when the window is closed.
|
||||
if(!is_browser)
|
||||
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 be automatically
|
||||
* subscribed to incoming messages via the on_message proc.
|
||||
*
|
||||
* optional ui /datum/tgui
|
||||
*/
|
||||
/datum/tgui_window/proc/acquire_lock(datum/tgui/ui)
|
||||
locked = TRUE
|
||||
locked_by = ui
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Subscribes the datum to consume window messages on a specified proc.
|
||||
*
|
||||
* Note, that this supports only one subscriber, because code for that
|
||||
* is simpler and therefore faster. If necessary, this can be rewritten
|
||||
* to support multiple subscribers.
|
||||
*/
|
||||
/datum/tgui_window/proc/subscribe(datum/object, delegate)
|
||||
subscriber_object = object
|
||||
subscriber_delegate = delegate
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Unsubscribes the datum. Do not forget to call this when cleaning up.
|
||||
*/
|
||||
/datum/tgui_window/proc/unsubscribe(datum/object)
|
||||
subscriber_object = null
|
||||
subscriber_delegate = 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, payload, force)
|
||||
if(!client)
|
||||
return
|
||||
var/message = TGUI_CREATE_MESSAGE(type, payload)
|
||||
// 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, is_browser \
|
||||
? "[id]:update" \
|
||||
: "[id].browser:update")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Sends a raw payload to tgui window.
|
||||
*
|
||||
* required message string JSON+urlencoded blob to send.
|
||||
* optional force bool Send regardless of the ready status.
|
||||
*/
|
||||
/datum/tgui_window/proc/send_raw_message(message, force)
|
||||
if(!client)
|
||||
return
|
||||
// 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, is_browser \
|
||||
? "[id]:update" \
|
||||
: "[id].browser:update")
|
||||
|
||||
/**
|
||||
* public
|
||||
*
|
||||
* Makes an asset available to use in tgui.
|
||||
*
|
||||
* required asset datum/asset
|
||||
*
|
||||
* return bool - TRUE if any assets had to be sent to the client
|
||||
*/
|
||||
/datum/tgui_window/proc/send_asset(datum/asset/asset)
|
||||
if(!client || !asset)
|
||||
return
|
||||
|
||||
sent_assets |= list(asset)
|
||||
. = asset.send(client)
|
||||
if(istype(asset, /datum/asset/spritesheet))
|
||||
var/datum/asset/spritesheet/spritesheet = asset
|
||||
send_message("asset/stylesheet", spritesheet.css_filename())
|
||||
send_raw_message(asset.get_serialized_url_mappings())
|
||||
|
||||
/**
|
||||
* 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, is_browser \
|
||||
? "[id]:update" \
|
||||
: "[id].browser:update")
|
||||
message_queue = null
|
||||
|
||||
/**
|
||||
* private
|
||||
*
|
||||
* Callback for handling incoming tgui messages.
|
||||
*/
|
||||
/datum/tgui_window/proc/on_message(type, payload, href_list)
|
||||
// Status can be READY if user has refreshed the window.
|
||||
if(type == "ready" && status == TGUI_WINDOW_READY)
|
||||
// Resend the assets
|
||||
for(var/asset in sent_assets)
|
||||
send_asset(asset)
|
||||
// Mark this window as fatally errored which prevents it from
|
||||
// being suspended.
|
||||
if(type == "log" && href_list["fatal"])
|
||||
fatally_errored = TRUE
|
||||
// Mark window as ready since we received this message from somewhere
|
||||
if(status != TGUI_WINDOW_READY)
|
||||
status = TGUI_WINDOW_READY
|
||||
flush_message_queue()
|
||||
// Pass message to UI that requested the lock
|
||||
if(locked && locked_by)
|
||||
var/prevent_default = locked_by.on_message(type, payload, href_list)
|
||||
if(prevent_default)
|
||||
return
|
||||
// Pass message to the subscriber
|
||||
else if(subscriber_object)
|
||||
var/prevent_default = call(
|
||||
subscriber_object,
|
||||
subscriber_delegate)(type, payload, href_list)
|
||||
if(prevent_default)
|
||||
return
|
||||
// If not locked, handle these message types
|
||||
switch(type)
|
||||
if("ping")
|
||||
send_message("ping/reply", payload)
|
||||
if("suspend")
|
||||
close(can_be_suspended = TRUE)
|
||||
if("close")
|
||||
close(can_be_suspended = FALSE)
|
||||
if("openLink")
|
||||
client << link(href_list["url"])
|
||||
if("cacheReloaded")
|
||||
// Reinitialize
|
||||
initialize(
|
||||
fancy = initial_fancy,
|
||||
assets = initial_assets,
|
||||
inline_html = initial_inline_html,
|
||||
inline_js = initial_inline_js,
|
||||
inline_css = initial_inline_css)
|
||||
// Resend the assets
|
||||
for(var/asset in sent_assets)
|
||||
send_asset(asset)
|
||||
if("chat/resend")
|
||||
SSchat.handle_resend(client, payload)
|
||||
Reference in New Issue
Block a user