-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8722 from Spookerton/spkrtn/sys/sstyping
Adds SSTyping
- Loading branch information
Showing
9 changed files
with
251 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,12 @@ | ||
// Consider these images/atoms as part of the UI/HUD | ||
#define APPEARANCE_UI_IGNORE_ALPHA RESET_COLOR|RESET_TRANSFORM|NO_CLIENT_COLOR|RESET_ALPHA | ||
#define APPEARANCE_UI RESET_COLOR|RESET_TRANSFORM|NO_CLIENT_COLOR | ||
|
||
/// The atom is not clickable | ||
#define XMOUSE_OPACITY_NEVER 0 | ||
|
||
/// The atom is clickable normally | ||
#define XMOUSE_OPACITY_DEFAULT 1 | ||
|
||
/// The atom steals clicks from other clickables | ||
#define XMOUSE_OPACITY_ALWAYS 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
SUBSYSTEM_DEF(typing) | ||
name = "Typing" | ||
flags = SS_BACKGROUND | SS_NO_INIT | ||
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME | ||
wait = 0.5 SECONDS | ||
|
||
/// The skin control to poll for TYPING_STATE_INPUT status. | ||
var/const/INPUT_HANDLE = "mainwindow.input" | ||
|
||
var/const/INFLIGHT_TIMEOUT = 5 SECONDS | ||
|
||
/// The status entry index of the related client's typing indicator visibility preference. | ||
var/const/INDEX_PREFERENCE = 1 | ||
|
||
/// The status entry index of the inflight state. | ||
var/const/INDEX_INFLIGHT = 2 | ||
|
||
/// The status entry index of the timeout threshold. | ||
var/const/INDEX_TIMEOUT = 3 | ||
|
||
/// The status entry index of the input bar typing state. | ||
var/const/INDEX_INPUT_STATE = 4 | ||
|
||
/// The status entry index of the verb input typing state. | ||
var/const/INDEX_VERB_STATE = 5 | ||
|
||
/// The highest index in a status entry. | ||
var/const/MAX_INDEX = 5 | ||
|
||
/* | ||
* A list of (ckey = list( | ||
preference = 0|1, | ||
inflight = 0|1, | ||
timeout = num, | ||
istyping_input = 0|1, | ||
istyping_hotkey = 0|1 | ||
), ...) | ||
See .proc/GetEntry for details. | ||
*/ | ||
var/static/list/status = list() | ||
|
||
/// Matches input bar verbs that should set TYPING_STATE_INPUT. | ||
var/static/regex/match_verbs = regex("^(Me|Say) +\"?\\w+") | ||
|
||
/// A list of clients waiting to be polled for input state. | ||
var/static/list/client/queue = list() | ||
|
||
|
||
/datum/controller/subsystem/typing/Recover() | ||
status = list() | ||
queue = list() | ||
|
||
|
||
/datum/controller/subsystem/typing/fire(resumed, no_mc_tick) | ||
if (!resumed) | ||
queue = list() | ||
for (var/client/client as anything in GLOB.clients) | ||
queue += client | ||
if (!length(queue)) | ||
return | ||
var/cut_until = 1 | ||
var/list/entry | ||
for (var/client/client as anything in queue) | ||
++cut_until | ||
if (QDELETED(client)) | ||
continue | ||
entry = GetEntry(client) | ||
if (!entry[INDEX_PREFERENCE]) | ||
continue | ||
if (!entry[INDEX_INFLIGHT]) | ||
UpdateInputState(client, entry) | ||
else if (world.time < entry[INDEX_TIMEOUT]) | ||
entry[INDEX_INFLIGHT] = FALSE | ||
if (no_mc_tick) | ||
CHECK_TICK | ||
else if (MC_TICK_CHECK) | ||
queue.Cut(1, cut_until) | ||
return | ||
queue.Cut() | ||
|
||
|
||
/// Return, generating if necessary, a ckey-indexed list holding typing status. | ||
/datum/controller/subsystem/typing/proc/GetEntry(client/client) | ||
PRIVATE_PROC(TRUE) | ||
var/ckey | ||
if (istext(client)) | ||
ckey = client | ||
else if (istype(client)) | ||
ckey = client.ckey | ||
else | ||
return | ||
var/list/entry = status[ckey] | ||
if (!entry) | ||
entry = new (MAX_INDEX) | ||
entry[INDEX_PREFERENCE] = client.is_preference_enabled(/datum/client_preference/show_typing_indicator) | ||
entry[INDEX_INFLIGHT] = FALSE | ||
entry[INDEX_TIMEOUT] = world.time | ||
entry[INDEX_INPUT_STATE] = FALSE | ||
entry[INDEX_VERB_STATE] = FALSE | ||
status[ckey] = entry | ||
return entry | ||
|
||
|
||
/// Updates client's preference bool for whether typing indicators should be shown. | ||
/datum/controller/subsystem/typing/proc/UpdatePreference(client/client, preference) | ||
var/list/entry = GetEntry(client) | ||
entry[INDEX_PREFERENCE] = preference | ||
UpdateIndicator(client, entry) | ||
|
||
|
||
/// Updates client|ckey's verb typing state to new_state. | ||
/datum/controller/subsystem/typing/proc/UpdateVerbState(client/client, state) | ||
var/list/entry = GetEntry(client) | ||
entry[INDEX_VERB_STATE] = state | ||
UpdateIndicator(client, entry) | ||
|
||
|
||
/// Request client's input bar state using winget and updating entry accordingly. | ||
/datum/controller/subsystem/typing/proc/UpdateInputState(client/client, list/entry) | ||
PRIVATE_PROC(TRUE) | ||
set waitfor = FALSE | ||
var/timeout = world.time + INFLIGHT_TIMEOUT | ||
entry[INDEX_INFLIGHT] = TRUE | ||
entry[INDEX_TIMEOUT] = timeout | ||
var/content = winget(client, INPUT_HANDLE, "text") | ||
if (timeout != entry[INDEX_TIMEOUT]) // We're stale. Touch nothing. | ||
return | ||
entry[INDEX_INFLIGHT] = FALSE | ||
if (QDELETED(client) || !isliving(client.mob)) | ||
return | ||
entry[INDEX_INPUT_STATE] = match_verbs.Find(content) != 0 | ||
UpdateIndicator(client, entry) | ||
|
||
|
||
/// Attempt to update the mob's typing state and indicator according to new state. | ||
/datum/controller/subsystem/typing/proc/UpdateIndicator(client/client, list/entry) | ||
PRIVATE_PROC(TRUE) | ||
var/mob/target = client.mob | ||
var/display = target.stat == CONSCIOUS && entry[INDEX_PREFERENCE] && (entry[INDEX_INPUT_STATE] || entry[INDEX_VERB_STATE]) | ||
if (display == target.is_typing) | ||
return | ||
if (display) | ||
if (!target.typing_indicator) | ||
target.typing_indicator = new (null, target) | ||
target.typing_indicator.icon_state = "[target.speech_bubble_appearance()]_typing" | ||
target.typing_indicator.pixel_y = target.icon_expected_height - 32 | ||
target.vis_contents += target.typing_indicator | ||
target.is_typing = TRUE | ||
if (target.shadow) | ||
target.vis_contents += target.typing_indicator | ||
else | ||
if (target.typing_indicator) | ||
target.vis_contents -= target.typing_indicator | ||
target.is_typing = FALSE | ||
if (target.shadow) | ||
target.vis_contents -= target.typing_indicator | ||
|
||
|
||
/atom/movable/typing_indicator | ||
icon = 'icons/mob/talk.dmi' | ||
icon_state = "typing" | ||
plane = PLANE_LIGHTING_ABOVE | ||
mouse_opacity = XMOUSE_OPACITY_NEVER | ||
simulated = FALSE | ||
anchored = TRUE | ||
|
||
var/mob/owner | ||
|
||
|
||
/atom/movable/typing_indicator/Destroy() | ||
if (owner) | ||
owner.vis_contents -= src | ||
owner.typing_indicator = null | ||
owner = null | ||
return ..() | ||
|
||
|
||
/atom/movable/typing_indicator/Initialize(mapload, mob/_owner) | ||
. = ..() | ||
if (!istype(_owner)) | ||
return INITIALIZE_HINT_QDEL | ||
owner = _owner | ||
|
||
|
||
/// If this mob is or was piloted by a player with typing indicators enabled, an instance of one. | ||
/mob/var/atom/movable/typing_indicator/typing_indicator | ||
|
||
|
||
/// Whether this mob is currently typing, if piloted by a player. | ||
/mob/var/is_typing | ||
|
||
|
||
/mob/Destroy() | ||
QDEL_NULL(typing_indicator) | ||
return ..() | ||
|
||
|
||
/mob/Logout() | ||
if (typing_indicator) | ||
vis_contents -= typing_indicator | ||
is_typing = FALSE | ||
..() | ||
|
||
|
||
/mob/verb/say_wrapper() | ||
set name = ".Say" | ||
set hidden = TRUE | ||
SStyping.UpdateVerbState(client, TRUE) | ||
var/message = input("","say (text)") as null | text | ||
SStyping.UpdateVerbState(client, FALSE) | ||
if (message) | ||
say_verb(message) | ||
|
||
|
||
/mob/verb/me_wrapper() | ||
set name = ".Me" | ||
set hidden = TRUE | ||
SStyping.UpdateVerbState(client, TRUE) | ||
var/message = input("","me (text)") as null | text | ||
SStyping.UpdateVerbState(client, FALSE) | ||
if (message) | ||
me_verb(message) | ||
|
||
|
||
// Retained for uses by non-player assets | ||
/proc/generate_speech_bubble(atom/movable/loc, icon_state, layer = FLOAT_LAYER) | ||
var/image/image = image('icons/mob/talk.dmi', loc, icon_state, layer) | ||
image.appearance_flags |= KEEP_APART | RESET_COLOR | PIXEL_SCALE | ||
if (ismovable(loc)) | ||
var/x_scale = loc.get_icon_scale_x() | ||
if (abs(x_scale) < 2) // reset transform on bubbles, except for the Very Large | ||
image.pixel_z = (loc.icon_expected_height * (x_scale - 1)) | ||
image.appearance_flags |= RESET_TRANSFORM | ||
return image |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.