TGUI say: Upgrades chat input with modern features (#23907)

* initial stuff (broken)

* initial stuff (works)

* fix most other things

* fix thinking indicator

* file name moment

* contra review

* better logs

* snowball feedback

* expanded drag handles

* Update code/modules/tgui_input/say_modal/tgui_say_modal.dm

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>

* Update code/modules/tgui_input/say_modal/tgui_say_modal.dm

Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>

* Drag handle

* fix missing colours

* TGUI rebuild (im clever)

* Make msay useable by admins again (i hope)

* fixed blacklist channel leaks due to lack of user eyes

* clear old TGUIsay macros on keybind update

* TGUI say tweaks

* Update code/modules/tgui_input/say_modal/tgui_say_modal.dm

* whisper is now a channel

* remove placeholder, shorten width

* tidies prefs, fix lightmode, refactor typing/thinking indicators

* clarify more prefs

* TG Typing Indicators and increace layer

* modifiers?

* makes the TGUI say macros serverside

* add checks for muted stuff

* dgamer review

* fix talk and type issue

* Update code/datums/keybindings/communication_keybinds.dm

Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>

* review and binary stuff

* Removes force say from TGUI say

* updated

* Apply suggestions from code review

Co-authored-by: Aylong <69762909+AyIong@users.noreply.github.com>

* remove verb

* le rebuild

* better behaviour on illegal entries

---------

Co-authored-by: Aylong <alexanderkitsa@gmail.com>
Co-authored-by: Contrabang <91113370+Contrabang@users.noreply.github.com>
Co-authored-by: GDN <Roanrichards1@Gmail.com>
Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com>
Co-authored-by: Aylong <69762909+AyIong@users.noreply.github.com>
This commit is contained in:
S34N
2024-02-15 19:31:29 +01:00
committed by GitHub
co-authored by Contrabang Luc Aylong Aylong GDN
parent c120721cde
commit 3be2d5f8b6
60 changed files with 1932 additions and 401 deletions
+2
View File
@@ -660,6 +660,7 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
GLOB.de_mentors += ckey
deadmin()
verbs += /client/proc/readmin
update_active_keybindings()
to_chat(src, "<span class='interface'>You are now a normal player.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "De-admin") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
@@ -749,6 +750,7 @@ GLOBAL_LIST_INIT(view_runtimes_verbs, list(
var/client/C = GLOB.directory[ckey]
D.associate(C)
update_active_keybindings()
message_admins("[key_name_admin(usr)] re-adminned themselves.")
log_admin("[key_name(usr)] re-adminned themselves.")
GLOB.de_admins -= ckey
+4 -1
View File
@@ -124,12 +124,15 @@
/// The client's movement keybindings to directions, which work regardless of modifiers.
var/list/movement_kb_dirs = list()
///A lazy list of atoms we've examined in the last RECENT_EXAMINE_MAX_WINDOW (default 2) seconds, so that we will call [/atom/proc/examine_more] instead of [/atom/proc/examine] on them when examining
/// A lazy list of atoms we've examined in the last RECENT_EXAMINE_MAX_WINDOW (default 2) seconds, so that we will call [/atom/proc/examine_more] instead of [/atom/proc/examine] on them when examining
var/list/recent_examines
/// When to next alert admins that mouse macro use was attempted
var/next_mouse_macro_warning
/// Assigned say modal of the client
var/datum/tgui_say/tgui_say
/client/vv_edit_var(var_name, var_value)
switch(var_name)
// I know we will never be in a world where admins are editing client vars to let people bypass TOS
+7 -26
View File
@@ -254,7 +254,10 @@
///////////
/client/New(TopicData)
var/tdata = TopicData //save this for later use
tgui_panel = new(src)
tgui_panel = new(src, "browseroutput")
tgui_say = new(src, "tgui_say")
TopicData = null //Prevent calls to client.Topic from connect
if(connection != "seeker") //Invalid connection type.
@@ -392,6 +395,9 @@
// Initialize tgui panel
tgui_panel.initialize()
// Initialize tgui say
tgui_say.initialize()
check_ip_intel()
send_resources()
SSchangelog.UpdatePlayerChangelogButton(src)
@@ -955,31 +961,6 @@
#undef SSD_WARNING_TIMER
//
/client/verb/resend_ui_resources()
set name = "Reload UI Resources"
set desc = "Reload your UI assets if they are not working"
set category = "Special Verbs"
if(last_ui_resource_send > world.time)
to_chat(usr, "<span class='warning'>You requested your UI resource files too quickly. Please try again in [(last_ui_resource_send - world.time)/10] seconds.</span>")
return
var/choice = alert(usr, "This will reload your TGUI resources. If you have any open UIs, they will be closed. Are you sure?", "Resource Reloading", "Yes", "No")
if(choice == "Yes")
// 600 deciseconds = 1 minute
last_ui_resource_send = world.time + 60 SECONDS
// Close their open UIs
SStgui.close_user_uis(usr)
// Clear the user's cache so they get resent.
// This is not fully clearing their BYOND cache, just their assets sent from the server this round
sent_assets = list()
to_chat(usr, "<span class='notice'>UI resource files resent successfully. If you are still having issues, please try manually clearing your BYOND cache. <b>This can be achieved by opening your BYOND launcher, pressing the cog in the top right, selecting preferences, going to the Games tab, and pressing 'Clear Cache'.</b></span>")
/**
* Retrieves the BYOND accounts data from the BYOND servers
*
@@ -946,6 +946,10 @@
if("input_lists")
toggles2 ^= PREFTOGGLE_2_DISABLE_TGUI_LISTS
if("tgui_say_light_mode")
toggles2 ^= PREFTOGGLE_2_ENABLE_TGUI_SAY_LIGHT_MODE
user?.client?.tgui_say?.load()
if("ghost_att_anim")
toggles2 ^= PREFTOGGLE_2_ITEMATTACK
@@ -439,7 +439,7 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
dat += "<h2>Interface Settings</h2>"
dat += "<b>Set screentip mode:</b> <a href='?_src_=prefs;preference=screentip_mode'>[(screentip_mode == 0) ? "Disabled" : "[screentip_mode]px"]</a><br>"
dat += "<b>Screentip color:</b> <span style='border: 1px solid #161616; background-color: [screentip_color];'>&nbsp;&nbsp;&nbsp;</span> <a href='?_src_=prefs;preference=screentip_color'><b>Change</b></a><br>"
dat += "<b>Thought Bubble:</b> <a href='?_src_=prefs;preference=thought_bubble'>[(toggles2 & PREFTOGGLE_2_THOUGHT_BUBBLE) ? "Yes" : "No"]</a><br>"
dat += "<b>Thought Bubble when pointing:</b> <a href='?_src_=prefs;preference=thought_bubble'>[(toggles2 & PREFTOGGLE_2_THOUGHT_BUBBLE) ? "Yes" : "No"]</a><br>"
dat += "<b>Custom UI settings:</b><br>"
dat += " - <b>Alpha (transparency):</b> <a href='?_src_=prefs;preference=UIalpha'><b>[UI_style_alpha]</b></a><br>"
dat += " - <b>Color:</b> <a href='?_src_=prefs;preference=UIcolor'><b>[UI_style_color]</b></a> <span style='border: 1px solid #161616; background-color: [UI_style_color];'>&nbsp;&nbsp;&nbsp;</span><br>"
@@ -447,6 +447,7 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
dat += "<b>TGUI settings:</b><br>"
dat += "<b> - Fancy TGUI:</b> <a href='?_src_=prefs;preference=tgui'>[(toggles2 & PREFTOGGLE_2_FANCYUI) ? "Yes" : "No"]</a><br>"
dat += "<b> - Input Lists:</b> <a href='?_src_=prefs;preference=input_lists'>[(toggles2 & PREFTOGGLE_2_DISABLE_TGUI_LISTS) ? "Default" : "TGUI"]</a><br>"
dat += "<b> - TGUI Say Theme:</b> <a href='?_src_=prefs;preference=tgui_say_light_mode'>[(toggles2 & PREFTOGGLE_2_ENABLE_TGUI_SAY_LIGHT_MODE) ? "Light" : "Dark"]</a><br>"
dat += "</td></tr></table>"
if(TAB_ANTAG) // Antagonist's Preferences
@@ -254,18 +254,6 @@
prefs.current_tab = 1
prefs.ShowChoices(usr)
/client/verb/toggle_popup_limiter()
set name = "Toggle Text Popup Limiter"
set category = "Preferences"
set desc = "Will let you limit the text input popups to one at a time."
prefs.toggles ^= PREFTOGGLE_TYPING_ONCE
prefs.save_preferences(src)
if(prefs.toggles & PREFTOGGLE_TYPING_ONCE)
to_chat(usr, "<span class='notice'>You have enabled text popup limiting.")
else
to_chat(usr, "<span class='notice'>You have disabled text popup limiting.")
return
/client/verb/toggle_ghost_pda()
set name = "Show/Hide GhostPDA"
set category = "Preferences"
@@ -1031,6 +1031,7 @@ It'll return null if the organ doesn't correspond, so include null checks when u
return
/datum/species/proc/spec_attacked_by(obj/item/I, mob/living/user, obj/item/organ/external/affecting, intent, mob/living/carbon/human/H)
return
/proc/get_random_species(species_name = FALSE) // Returns a random non black-listed or hazardous species, either as a string or datum
var/static/list/random_species = list()
+2
View File
@@ -1611,6 +1611,8 @@ GLOBAL_LIST_INIT(holy_areas, typecacheof(list(
. = stat
stat = new_stat
SEND_SIGNAL(src, COMSIG_MOB_STATCHANGE, new_stat, .)
if(.)
set_typing_indicator(FALSE)
///Makes a call in the context of a different usr. Use sparingly
/world/proc/invoke_callback_with_usr(mob/user_mob, datum/callback/invoked_callback, ...)
+1
View File
@@ -1,4 +1,5 @@
/mob/Logout()
set_typing_indicator(FALSE)
SStgui.on_logout(src) // Cleanup any TGUIs the user has open
unset_machine()
GLOB.player_list -= src
+2 -2
View File
@@ -227,10 +227,10 @@
var/blood_state = BLOOD_STATE_NOT_BLOODY
/// Assoc list for tracking how "bloody" a mobs feet are, used for creating bloody foot/shoeprints on turfs when moving
var/list/bloody_feet = list(BLOOD_STATE_HUMAN = 0, BLOOD_STATE_XENO = 0, BLOOD_STATE_NOT_BLOODY = 0, BLOOD_BASE_ALPHA = BLOODY_FOOTPRINT_BASE_ALPHA)
/// set when typing in an input window instead of chatline, this var could probably be removed soon enough
var/hud_typing = 0
/// Affects if you have a typing indicator
var/typing
/// Affects if you have a thinking indicator
var/thinking
/// Last thing we typed in to the typing indicator, probably does not need to exist
var/last_typed
/// Last time we typed something in to the typing popup
+60 -60
View File
@@ -1,6 +1,5 @@
#define TYPING_INDICATOR_LIFETIME 30 * 10 //grace period after which typing indicator disappears regardless of text in chatbar
GLOBAL_LIST_EMPTY(typing_indicator)
GLOBAL_LIST_EMPTY(thinking_indicator)
/**
* Toggles the floating chat bubble above a players head.
@@ -10,9 +9,8 @@ GLOBAL_LIST_EMPTY(typing_indicator)
* * me - Is the bubble being caused by the 'me' emote command
*/
/mob/proc/set_typing_indicator(state, me)
if(!GLOB.typing_indicator[bubble_icon])
GLOB.typing_indicator[bubble_icon] = image('icons/mob/talk.dmi', null, "[bubble_icon]typing", FLY_LAYER)
GLOB.typing_indicator[bubble_icon] = image('icons/mob/talk.dmi', null, "[bubble_icon]_typing", ABOVE_HUD_LAYER)
var/image/I = GLOB.typing_indicator[bubble_icon]
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
@@ -20,91 +18,93 @@ GLOBAL_LIST_EMPTY(typing_indicator)
var/mob/living/carbon/human/H = src
if(HAS_TRAIT(H, TRAIT_MUTE))
overlays -= GLOB.typing_indicator[bubble_icon]
return
typing = FALSE
return FALSE
if(client)
if(stat != CONSCIOUS || is_muzzled() || (client.prefs.toggles & PREFTOGGLE_SHOW_TYPING) || (me && (client.prefs.toggles2 & PREFTOGGLE_2_EMOTE_BUBBLE)))
overlays -= GLOB.typing_indicator[bubble_icon]
else
if(state)
if(!typing)
overlays += GLOB.typing_indicator[bubble_icon]
typing = TRUE
else
if(typing)
overlays -= GLOB.typing_indicator[bubble_icon]
typing = FALSE
return state
if(!client)
return FALSE
if(stat != CONSCIOUS || is_muzzled() || (client.prefs.toggles & PREFTOGGLE_SHOW_TYPING))
overlays -= GLOB.typing_indicator[bubble_icon]
typing = FALSE
return FALSE
if(state && !typing)
overlays += GLOB.typing_indicator[bubble_icon]
typing = TRUE
if(!state && typing)
overlays -= GLOB.typing_indicator[bubble_icon]
typing = FALSE
return state
/**
* Toggles the floating thought bubble above a players head.
*
* Arguments:
* * state - Should a thought bubble be shown or hidden
*/
/mob/proc/set_thinking_indicator(state)
if(!GLOB.thinking_indicator[bubble_icon])
GLOB.thinking_indicator[bubble_icon] = image('icons/mob/talk.dmi', null, "[bubble_icon]_thinking", ABOVE_HUD_LAYER)
var/image/I = GLOB.thinking_indicator[bubble_icon]
I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
if(!client && !isliving(src))
return FALSE
if(stat != CONSCIOUS || (client.prefs.toggles & PREFTOGGLE_SHOW_TYPING))
overlays -= GLOB.thinking_indicator[bubble_icon]
thinking = FALSE
return FALSE
if(!state && thinking)
overlays -= GLOB.thinking_indicator[bubble_icon]
thinking = FALSE
if(state && !thinking)
overlays += GLOB.thinking_indicator[bubble_icon]
thinking = TRUE
return state
/mob/verb/say_wrapper()
set name = ".Say"
set hidden = 1
set hidden = TRUE
set_typing_indicator(TRUE)
hud_typing = 1
typing = TRUE
var/message = typing_input(src, "", "say (text)")
hud_typing = 0
typing = FALSE
set_typing_indicator(FALSE)
if(message)
say_verb(message)
/mob/verb/me_wrapper()
set name = ".Me"
set hidden = 1
set hidden = TRUE
set_typing_indicator(TRUE, TRUE)
hud_typing = 1
typing = TRUE
var/message = typing_input(src, "", "me (text)")
hud_typing = 0
typing = FALSE
set_typing_indicator(FALSE)
if(message)
me_verb(message)
/mob/proc/handle_typing_indicator()
if(client)
if(!(client.prefs.toggles & PREFTOGGLE_SHOW_TYPING) && !hud_typing)
var/temp = winget(client, "input", "text")
if(temp != last_typed)
last_typed = temp
last_typed_time = world.time
if(world.time > last_typed_time + TYPING_INDICATOR_LIFETIME)
set_typing_indicator(FALSE)
return
if(length(temp) > 5 && findtext(temp, "Say \"", 1, 7))
set_typing_indicator(TRUE)
else if(length(temp) > 3 && findtext(temp, "Me ", 1, 5))
set_typing_indicator(TRUE, TRUE)
else
set_typing_indicator(FALSE)
/client/verb/typing_indicator()
set name = "Show/Hide Typing Indicator"
set category = "Preferences"
set desc = "Toggles showing an indicator when you are typing a message."
set desc = "Toggles showing a typing/thought indicator when you have TGUIsay open."
prefs.toggles ^= PREFTOGGLE_SHOW_TYPING
prefs.save_preferences(src)
to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_SHOW_TYPING) ? "no longer" : "now"] display a typing indicator.")
to_chat(src, "You will [(prefs.toggles & PREFTOGGLE_SHOW_TYPING) ? "no longer" : "now"] display a typing/thought indicator when you have TGUIsay open.")
// Clear out any existing typing indicator.
if(prefs.toggles & PREFTOGGLE_SHOW_TYPING)
if(istype(mob))
mob.set_typing_indicator(FALSE)
mob.set_thinking_indicator(FALSE)
SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Typing Indicator (Speech)") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/verb/emote_indicator()
set name = "Show/Hide Emote Typing Indicator"
set category = "Preferences"
set desc = "Toggles showing an indicator when you are typing an emote."
prefs.toggles2 ^= PREFTOGGLE_2_EMOTE_BUBBLE
prefs.save_preferences(src)
to_chat(src, "You will [(prefs.toggles2 & PREFTOGGLE_2_EMOTE_BUBBLE) ? "no longer" : "now"] display a typing indicator for emotes.")
SSblackbox.record_feedback("tally", "toggle_verbs", 1, "Toggle Typing Indicator (Emote)") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
#undef TYPING_INDICATOR_LIFETIME
+1
View File
@@ -87,6 +87,7 @@
window.acquire_lock(src)
if(!window.is_ready())
window.initialize(
strict_mode = TRUE,
fancy = (user.client.prefs.toggles2 & PREFTOGGLE_2_FANCYUI),
assets = list(
get_asset_datum(/datum/asset/simple/tgui),
+10 -5
View File
@@ -13,9 +13,12 @@
var/broken = FALSE
var/initialized_at
/datum/tgui_panel/New(client/client)
/datum/tgui_panel/New(client/client, id)
if(!id)
qdel(src)
CRASH("New TGUI panel created for [client] but no id supplied, deleting.")
src.client = client
window = new(client, "browseroutput")
window = new(client, id)
window.subscribe(src, PROC_REF(on_message))
/datum/tgui_panel/Del()
@@ -42,9 +45,11 @@
sleep(1)
initialized_at = world.time
// Perform a clean initialization
window.initialize(assets = list(
get_asset_datum(/datum/asset/simple/tgui_panel),
))
window.initialize(
strict_mode = TRUE,
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)
@@ -34,3 +34,11 @@
// 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")
/client/verb/refresh_tgui()
set name = "Refresh TGUI"
set category = "Special Verbs"
for(var/window_id in tgui_windows)
var/datum/tgui_window/window = tgui_windows[window_id]
window.reinitialize()
+42 -16
View File
@@ -18,6 +18,7 @@
var/message_queue
var/sent_assets = list()
// Vars passed to initialize proc (and saved for later)
var/initial_strict_mode
var/initial_fancy
var/initial_assets
var/initial_inline_html
@@ -47,11 +48,15 @@
* 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.
* optional strict_mode bool - Enables strict error handling and BSOD.
* optional fancy bool - If TRUE and if this is NOT a panel, will hide the window titlebar.
* optional assets list - List of assets to load during initialization.
* optional inline_html string - Custom HTML to inject.
* optional inline_js string - Custom JS to inject.
* optional inline_css string - Custom CSS to inject.
*/
/datum/tgui_window/proc/initialize(
strict_mode = FALSE,
fancy = FALSE,
assets = list(),
inline_html = "",
@@ -77,6 +82,7 @@
// Generate page html
var/html = SStgui.basehtml
html = replacetextEx(html, "\[tgui:windowId]", id)
html = replacetextEx(html, "\[tgui:strictMode]", strict_mode)
// Inject assets
var/inline_assets_str = ""
for(var/datum/asset/asset in assets)
@@ -94,14 +100,14 @@
html = replacetextEx(html, "<!-- tgui:assets -->\n", inline_assets_str)
// Inject inline HTML
if(inline_html)
html = replacetextEx(html, "<!-- tgui:inline-html -->", inline_html)
html = replacetextEx(html, "<!-- tgui:inline-html -->", isfile(inline_html) ? file2text(inline_html) : inline_html)
// Inject inline JS
if(inline_js)
inline_js = "<script>\n[inline_js]\n</script>"
inline_js = "<script>\n'use strict';\n[isfile(inline_js) ? file2text(inline_js) : 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>"
inline_css = "<style>\n[isfile(inline_css) ? file2text(inline_css) : inline_css]\n</style>"
html = replacetextEx(html, "<!-- tgui:inline-css -->", inline_css)
// Open the window
client << browse(html, "window=[id];[options]")
@@ -111,6 +117,23 @@
if(!is_browser)
winset(client, id, "on-close=\"uiclose [id]\"")
/**
* public
*
* Reinitializes the panel with previous data used for initialization.
*/
/datum/tgui_window/proc/reinitialize()
initialize(
strict_mode = initial_strict_mode,
fancy = initial_fancy,
assets = initial_assets,
inline_html = initial_inline_html,
inline_js = initial_inline_js,
inline_css = initial_inline_css)
// Resend assets
for(var/datum/asset/asset in sent_assets)
send_asset(asset)
/**
* public
*
@@ -287,6 +310,18 @@
: "[id].browser:update")
message_queue = null
/**
* public
*
* Replaces the inline HTML content.
*
* required inline_html string HTML to inject
*/
/datum/tgui_window/proc/replace_html(inline_html = "")
client << output(url_encode(inline_html), is_browser \
? "[id]:replaceHtml" \
: "[id].browser:replaceHtml")
/**
* private
*
@@ -329,15 +364,6 @@
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)
reinitialize()
if("chat/resend")
SSchat.handle_resend(client, payload)
@@ -0,0 +1,123 @@
/**
* Creates a JSON encoded message to open TGUI say modals properly.
*
* Arguments:
* channel - The channel to open the modal in.
* Returns:
* string - A JSON encoded message to open the modal.
*/
/client/proc/tgui_say_create_open_command(channel)
var/message = TGUI_CREATE_MESSAGE("open", list(
channel = channel,
))
return "\".output tgui_say.browser:update [message]\""
/**
* The tgui say modal. This initializes an input window which hides until
* the user presses one of the speech hotkeys. Once something is entered, it will
* delegate the speech to the proper channel.
*/
/datum/tgui_say
/// The user who opened the window
var/client/client
/// The modal window
var/datum/tgui_window/window
/// Boolean for whether the tgui_say was opened by the user.
var/window_open
/** Creates the new input window to exist in the background. */
/datum/tgui_say/New(client/client, id)
src.client = client
window = new(client, id)
winset(client, "tgui_say", "size=1,1;is-visible=0;")
window.subscribe(src, PROC_REF(on_message))
window.is_browser = TRUE
/**
* After a brief period, injects the scripts into
* the window to listen for open commands.
*/
/datum/tgui_say/proc/initialize()
set waitfor = FALSE
// Sleep to defer initialization to after client constructor
sleep(3)
window.initialize(
strict_mode = TRUE,
fancy = TRUE,
inline_css = file("tgui/public/tgui-say.bundle.css"),
inline_js = file("tgui/public/tgui-say.bundle.js"),
);
/**
* Ensures nothing funny is going on window load.
* Minimizes the window, sets max length, closes all
* typing and thinking indicators. This is triggered
* as soon as the window sends the "ready" message.
*/
/datum/tgui_say/proc/load()
window_open = FALSE
winset(client, "tgui_say", "pos=848,500;size=275,30;is-visible=0;")
window.send_message("props", list(
"lightMode" = (client.prefs.toggles2 & PREFTOGGLE_2_ENABLE_TGUI_SAY_LIGHT_MODE),
"maxLength" = MAX_MESSAGE_LEN,
))
stop_thinking()
return TRUE
/**
* Sets the window as "opened" server side, though it is already
* visible to the user. We do this to set local vars &
* start typing (if enabled and in an IC channel).
*
* Arguments:
* payload - A list containing the channel the window was opened in.
*/
/datum/tgui_say/proc/open(payload)
if(!payload?["channel"])
CRASH("No channel provided to an open TGUI-Say")
window_open = TRUE
switch(payload["channel"])
if(ME_CHANNEL, RADIO_CHANNEL, SAY_CHANNEL, WHISPER_CHANNEL)
start_thinking()
return TRUE
/**
* Closes the window serverside. Closes any open chat bubbles
* regardless of preference.
*/
/datum/tgui_say/proc/close()
window_open = FALSE
stop_thinking()
stop_typing()
/**
* The equivalent of ui_act, this waits on messages from the window
* and delegates actions.
*/
/datum/tgui_say/proc/on_message(type, payload)
switch(type)
if("ready")
load()
return TRUE
if("open")
open(payload)
return TRUE
if("close")
close()
return TRUE
if("thinking")
if(payload["visible"] == TRUE)
start_thinking()
return TRUE
if(payload["visible"] == FALSE)
stop_thinking()
return TRUE
return FALSE
if("typing")
start_typing(payload["isMeChannel"])
return TRUE
if("entry")
handle_entry(payload)
return TRUE
return FALSE
@@ -0,0 +1,61 @@
/**
* Delegates the speech to the proper channel.
*
* Arguments:
* entry - the text to broadcast
* channel - the channel to broadcast in
* Returns:
* boolean - on success or failure
*/
/datum/tgui_say/proc/delegate_speech(entry, channel)
switch(channel)
if(SAY_CHANNEL)
client.mob.say_verb(entry)
return TRUE
if(RADIO_CHANNEL)
client.mob.say_verb((isliving(client.mob) ? ";" : "") + entry)
return TRUE
if(WHISPER_CHANNEL)
client.mob.whisper(entry)
return TRUE
if(ME_CHANNEL)
client.mob.me_verb(entry)
return TRUE
if(OOC_CHANNEL)
client.ooc(entry)
return TRUE
if(LOOC_CHANNEL)
client.looc(entry)
return TRUE
if(ADMIN_CHANNEL)
client.cmd_admin_say(entry)
return TRUE
if(MENTOR_CHANNEL)
client.cmd_mentor_say(entry)
return TRUE
if(DSAY_CHANNEL)
client.dsay(entry)
return TRUE
return FALSE
/**
* Handles text entry and forced speech.
*
* Arguments:
* payload - a string list containing entry & channel
* Returns:
* boolean - success or failure
*/
/datum/tgui_say/proc/handle_entry(payload)
if(!payload?["channel"] || !payload["entry"])
var/hacker_man_ckey = usr.client.ckey
qdel(usr.client)
message_admins("[hacker_man_ckey] was kicked for attemping to send a null message to TGUI-say.")
CRASH("[hacker_man_ckey] entered in a null payload to the chat window.")
if(length(payload["entry"]) > MAX_MESSAGE_LEN)
var/hacker_man_ckey = usr.client.ckey
qdel(usr.client)
message_admins("[hacker_man_ckey] was kicked for attemping to bypass TGUI-say character limits.")
CRASH("[hacker_man_ckey] has entered more characters than allowed into a TGUI-Say.")
delegate_speech(payload["entry"], payload["channel"])
return TRUE
@@ -0,0 +1,41 @@
/** Sets the mob as "thinking" - with indicator */
/datum/tgui_say/proc/start_thinking()
if(!client?.mob || !window_open)
return FALSE
/// Special exemptions
if(isabductor(client.mob))
return FALSE
client.mob.set_thinking_indicator(TRUE)
addtimer(CALLBACK(src, PROC_REF(stop_thinking)), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE)
/** Removes typing/thinking indicators and flags the mob as not thinking */
/datum/tgui_say/proc/stop_thinking()
if(!client?.mob)
return FALSE
client.mob.set_thinking_indicator(FALSE)
/**
* Handles the user typing. After a brief period of inactivity,
* signals the client mob to revert to the "thinking" icon.
*/
/datum/tgui_say/proc/start_typing(me = FALSE)
if(!client?.mob)
return FALSE
client.mob.set_typing_indicator(FALSE)
client.mob.set_thinking_indicator(FALSE)
if(!window_open)
return FALSE
client.mob.set_typing_indicator(TRUE, me)
addtimer(CALLBACK(src, PROC_REF(stop_typing)), 5 SECONDS, TIMER_UNIQUE | TIMER_OVERRIDE | TIMER_STOPPABLE)
/**
* Callback to remove the typing indicator after a brief period of inactivity.
* If the user was typing IC, the thinking indicator is shown.
*/
/datum/tgui_say/proc/stop_typing()
if(!client?.mob)
return FALSE
client.mob.set_typing_indicator(FALSE)
if(!window_open)
return FALSE
client.mob.set_thinking_indicator(TRUE)