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 c85d9cef..4b66afcc 100644
Binary files a/icons/mob/talk.dmi and b/icons/mob/talk.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 9a5d6c13..df8afbd6 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1775,7 +1775,6 @@
#include "code\modules\holiday\easter.dm"
#include "code\modules\holiday\holidays.dm"
#include "code\modules\holiday\halloween\bartholomew.dm"
-//#include "code\modules\holiday\halloween\halloween.dm" //Time to go ahead and disable this. See you next year.
#include "code\modules\holiday\halloween\jacqueen.dm"
#include "code\modules\holodeck\area_copy.dm"
#include "code\modules\holodeck\computer.dm"
@@ -1990,6 +1989,7 @@
#include "code\modules\mob\say_vr.dm"
#include "code\modules\mob\status_procs.dm"
#include "code\modules\mob\transform_procs.dm"
+#include "code\modules\mob\typing_indicator.dm"
#include "code\modules\mob\update_icons.dm"
#include "code\modules\mob\camera\camera.dm"
#include "code\modules\mob\dead\dead.dm"
@@ -2112,6 +2112,7 @@
#include "code\modules\mob\living\carbon\human\say.dm"
#include "code\modules\mob\living\carbon\human\species.dm"
#include "code\modules\mob\living\carbon\human\status_procs.dm"
+#include "code\modules\mob\living\carbon\human\typing_indicator.dm"
#include "code\modules\mob\living\carbon\human\update_icons.dm"
#include "code\modules\mob\living\carbon\human\species_types\abductors.dm"
#include "code\modules\mob\living\carbon\human\species_types\android.dm"
@@ -3009,6 +3010,7 @@
#include "interface\interface.dm"
#include "interface\menu.dm"
#include "interface\stylesheet.dm"
+#include "interface\skin.dmf"
#include "modular_citadel\code\init.dm"
#include "modular_citadel\code\__HELPERS\list2list.dm"
#include "modular_citadel\code\__HELPERS\lists.dm"