From 6eebc1d6490f54cf496e32f75b5d461a460aa1ba Mon Sep 17 00:00:00 2001 From: Dip Date: Tue, 10 Nov 2020 01:52:06 -0300 Subject: [PATCH] Typing Indicators --- code/__DEFINES/layers.dm | 5 +- code/__DEFINES/mobs.dm | 2 + code/controllers/subsystem/input.dm | 7 +- code/modules/keybindings/bindings_living.dm | 2 +- .../mob/living/carbon/human/species.dm | 3 + .../living/carbon/human/typing_indicator.dm | 2 + code/modules/mob/living/living_defines.dm | 2 + code/modules/mob/living/living_movement.dm | 2 + code/modules/mob/mob_defines.dm | 15 ++++- code/modules/mob/say.dm | 63 +++++++++++++----- code/modules/mob/say_vr.dm | 3 - code/modules/mob/typing_indicator.dm | 56 ++++++++++++++++ icons/mob/talk.dmi | Bin 5989 -> 7918 bytes tgstation.dme | 4 +- 14 files changed, 140 insertions(+), 26 deletions(-) create mode 100644 code/modules/mob/living/carbon/human/typing_indicator.dm create mode 100644 code/modules/mob/typing_indicator.dm diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 884a1f31..4598932b 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -66,8 +66,9 @@ #define SPACEVINE_LAYER 4.8 #define SPACEVINE_MOB_LAYER 4.9 //#define FLY_LAYER 5 //For easy recordkeeping; this is a byond define -#define GASFIRE_LAYER 5.05 -#define RIPPLE_LAYER 5.1 +#define ABOVE_FLY_LAYER 5.1 +#define GASFIRE_LAYER 5.2 +#define RIPPLE_LAYER 5.3 #define GHOST_LAYER 6 #define LOW_LANDMARK_LAYER 9 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 8d50a151..49f2307c 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -272,3 +272,5 @@ #define PULL_PRONE_SLOWDOWN 0.6 #define HUMAN_CARRY_SLOWDOWN 0 + +#define TYPING_INDICATOR_TIMEOUT 10 MINUTES diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm index 221d04da..163d9475 100644 --- a/code/controllers/subsystem/input.dm +++ b/code/controllers/subsystem/input.dm @@ -34,9 +34,10 @@ SUBSYSTEM_DEF(input) "O" = "ooc", "Ctrl+O" = "looc", "T" = "say", - "Ctrl+T" = "whisper", + "Ctrl+T" = "say_indicator", + "Y" = "whisper", "M" = "me", - "Ctrl+M" = "subtle", + "Ctrl+M" = "me_indicator", "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs "Any" = "\"KeyDown \[\[*\]\]\"", "Any+UP" = "\"KeyUp \[\[*\]\]\"", @@ -50,7 +51,9 @@ SUBSYSTEM_DEF(input) "Tab" = "\".winset \\\"mainwindow.macro=old_default input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"", "O" = "ooc", "T" = "say", + "Ctrl+T" = "say_indicator", "M" = "me", + "Ctrl+M" = "me_indicator", "Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs "Any" = "\"KeyDown \[\[*\]\]\"", "Any+UP" = "\"KeyUp \[\[*\]\]\"", diff --git a/code/modules/keybindings/bindings_living.dm b/code/modules/keybindings/bindings_living.dm index 241bc15b..7ae8b996 100644 --- a/code/modules/keybindings/bindings_living.dm +++ b/code/modules/keybindings/bindings_living.dm @@ -4,4 +4,4 @@ resist() return - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index ccd5ac5d..90cf2a9e 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -85,6 +85,9 @@ GLOBAL_LIST_EMPTY(roundstart_races) var/icon_limbs //Overrides the icon used for the limbs of this species. Mainly for downstream, and also because hardcoded icons disgust me. Implemented and maintained as a favor in return for a downstream's implementation of synths. + /// Our default override for typing indicator state + var/typing_indicator_state + /////////// // PROCS // /////////// diff --git a/code/modules/mob/living/carbon/human/typing_indicator.dm b/code/modules/mob/living/carbon/human/typing_indicator.dm new file mode 100644 index 00000000..16ed9579 --- /dev/null +++ b/code/modules/mob/living/carbon/human/typing_indicator.dm @@ -0,0 +1,2 @@ +/mob/living/carbon/human/get_typing_indicator_icon_state() + return dna?.species?.typing_indicator_state || ..() diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 9d346a44..7a6ea95e 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -5,6 +5,8 @@ hud_possible = list(HEALTH_HUD,STATUS_HUD,ANTAG_HUD,NANITE_HUD,DIAG_NANITE_FULL_HUD) pressure_resistance = 10 + typing_indicator_enabled = TRUE + var/resize = 1 //Badminnery resize var/lastattacker = null var/lastattackerckey = null diff --git a/code/modules/mob/living/living_movement.dm b/code/modules/mob/living/living_movement.dm index 37769ba5..5600764e 100644 --- a/code/modules/mob/living/living_movement.dm +++ b/code/modules/mob/living/living_movement.dm @@ -1,6 +1,8 @@ /mob/living/Moved() . = ..() update_turf_movespeed(loc) + //Hide typing indicator if we move. + clear_typing_indicator() if(is_shifted) is_shifted = FALSE pixel_x = get_standard_pixel_x_offset(lying) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 61b8da65..a5edbb0d 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -126,4 +126,17 @@ ///Whether the mob is updating glide size when movespeed updates or not - var/updating_glide_size = TRUE \ No newline at end of file + var/updating_glide_size = TRUE + + var/flavor_text = "" + var/flavor_text_2 = "" //version of the above that only lasts for the current round. + + ///////TYPING INDICATORS/////// + /// Set to true if we want to show typing indicators. + var/typing_indicator_enabled = FALSE + /// Default icon_state of our typing indicator. Currently only supports paths (because anything else is, as of time of typing this, unnecesary. + var/typing_indicator_state = /obj/effect/overlay/typing_indicator + /// The timer that will remove our indicator for early aborts (like when an user finishes their message) + var/typing_indicator_timerid + /// Current state of our typing indicator. Used for cut overlay, DO NOT RUNTIME ASSIGN OTHER THAN FROM SHOW/CLEAR. Used to absolutely ensure we do not get stuck overlays. + var/mutable_appearance/typing_indicator_current diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 4d338dd3..bb51e514 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -1,17 +1,60 @@ //Speech verbs. -/mob/verb/say_verb(message as text) - set name = "Say" +// the _keybind verbs uses "as text" versus "as text|null" to force a popup when pressed by a keybind. +/mob/verb/say_typing_indicator() + set name = "say_indicator" + set hidden = TRUE set category = "IC" + display_typing_indicator() + var/message = input(usr, "", "say") as text|null + // If they don't type anything just drop the message. + clear_typing_indicator() // clear it immediately! + if(!length(message)) + return + return say_verb(message) + +/mob/verb/say_verb(message as text) + set name = "say" + set category = "IC" + if(!length(message)) + return if(GLOB.say_disabled) //This is here to try to identify lag problems to_chat(usr, "Speech is currently admin-disabled.") return - if(message) - say(message) + clear_typing_indicator() // clear it immediately! + say(message) + +/mob/verb/me_typing_indicator() + set name = "me_indicator" + set hidden = TRUE + set category = "IC" + display_typing_indicator() + var/message = input(usr, "", "me") as message|null + // If they don't type anything just drop the message. + clear_typing_indicator() // clear it immediately! + if(!length(message)) + return + return me_verb(message) + +/mob/verb/me_verb(message as message) + set name = "me" + set category = "IC" + if(!length(message)) + return + if(GLOB.say_disabled) //This is here to try to identify lag problems + to_chat(usr, "Speech is currently admin-disabled.") + return + + message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) + clear_typing_indicator() // clear it immediately! + + usr.emote("me",1,message,TRUE) /mob/verb/whisper_verb(message as text) set name = "Whisper" set category = "IC" + if(!length(message)) + return if(GLOB.say_disabled) //This is here to try to identify lag problems to_chat(usr, "Speech is currently admin-disabled.") return @@ -20,18 +63,6 @@ /mob/proc/whisper(message, datum/language/language=null) say(message, language) //only living mobs actually whisper, everything else just talks -/mob/verb/me_verb(message as message) - set name = "Me" - set category = "IC" - - if(GLOB.say_disabled) //This is here to try to identify lag problems - to_chat(usr, "Speech is currently admin-disabled.") - return - - message = trim(copytext(sanitize(message), 1, MAX_MESSAGE_LEN)) - - usr.emote("me",1,message,TRUE) - /mob/proc/say_dead(var/message) var/name = real_name var/alt_name = "" diff --git a/code/modules/mob/say_vr.dm b/code/modules/mob/say_vr.dm index 3772dae5..957ea6c8 100644 --- a/code/modules/mob/say_vr.dm +++ b/code/modules/mob/say_vr.dm @@ -1,8 +1,6 @@ ////////////////////////////////////////////////////// ////////////////////SUBTLE COMMAND//////////////////// ////////////////////////////////////////////////////// -/mob - var/flavor_text = "" //tired of fucking double checking this /mob/proc/update_flavor_text() set src in usr @@ -54,7 +52,6 @@ proc/get_top_level_mob(var/mob/S) message = null mob_type_blacklist_typecache = list(/mob/living/brain) - /datum/emote/living/subtle/proc/check_invalid(mob/user, input) . = TRUE if(copytext(input,1,5) == "says") diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm new file mode 100644 index 00000000..0558ed0b --- /dev/null +++ b/code/modules/mob/typing_indicator.dm @@ -0,0 +1,56 @@ +/// state = overlay/image/object/type/whatever add_overlay will accept +GLOBAL_LIST_EMPTY(typing_indicator_overlays) + +/// Fetches the typing indicator we'll use from GLOB.typing_indicator_overlays +/mob/proc/get_indicator_overlay(state) + . = GLOB.typing_indicator_overlays[state] + if(.) + return + // doesn't exist, make it and cache it + if(ispath(state)) + . = GLOB.typing_indicator_overlays[state] = state + // We only support paths for now because anything else isn't necessary yet. + +/// Gets the state we will use for typing indicators. Defaults to src.typing_indicator_state +/mob/proc/get_typing_indicator_icon_state() + return typing_indicator_state + +/// Generates the mutable appearance for typing indicator. Should prevent stuck overlays. +/mob/proc/generate_typing_indicator() + var/state = get_typing_indicator_icon_state() + if(ispath(state)) + var/atom/thing = new state(null) + var/mutable_appearance/generated = new(thing) + return generated + else + CRASH("Unsupported typing indicator state: [state]") + +/** + * Displays typing indicator. + * @param timeout_override - Sets how long until this will disappear on its own without the user finishing their message or logging out. Defaults to src.typing_indicator_timeout + * @param state_override - Sets the state that we will fetch. Defaults to src.get_typing_indicator_icon_state() + * @param force - shows even if src.typing_indcator_enabled is FALSE. + */ +/mob/proc/display_typing_indicator(timeout_override = TYPING_INDICATOR_TIMEOUT, state_override = generate_typing_indicator(), force = FALSE) + if((!typing_indicator_enabled && !force) || typing_indicator_current) + return + typing_indicator_current = state_override + add_overlay(state_override) + typing_indicator_timerid = addtimer(CALLBACK(src, .proc/clear_typing_indicator), timeout_override, TIMER_STOPPABLE) +/** + * Removes typing indicator. + */ +/mob/proc/clear_typing_indicator() + cut_overlay(typing_indicator_current) + typing_indicator_current = null + if(typing_indicator_timerid) + deltimer(typing_indicator_timerid) + typing_indicator_timerid = null + +/// Default typing indicator +/obj/effect/overlay/typing_indicator + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + icon = 'icons/mob/talk.dmi' + icon_state = "normal_typing" + appearance_flags = RESET_COLOR | TILE_BOUND | PIXEL_SCALE + layer = ABOVE_FLY_LAYER diff --git a/icons/mob/talk.dmi b/icons/mob/talk.dmi index c85d9cef6dbfdea474c12cb2d3411580e1376dbc..4b66afcc86c12b39954995f31338724833ef8229 100644 GIT binary patch literal 7918 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 La=84^`Ro4y-pfum literal 5989 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