diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index 46cfc8415b..368535228f 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -673,6 +673,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): "FORCEDENDLINE": validate_bool, "LOCALECHO": validate_bool, "TRUECOLOR": validate_bool, + "ISTYPING": validate_bool, } name = self.lhs.upper() diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 79b240d30f..701eceab7a 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -25,8 +25,9 @@ from codecs import lookup as codecs_lookup from django.conf import settings -from evennia.accounts.models import AccountDB +from evennia import ObjectDB, DefaultCharacter from evennia.commands.cmdhandler import cmdhandler +from evennia.commands.default.general import CmdSay from evennia.utils.logger import log_err from evennia.utils.utils import to_str @@ -181,6 +182,7 @@ _CLIENT_OPTIONS = ( "NOCOLOR", "NOGOAHEAD", "LOCALECHO", + "ISTYPING", ) @@ -207,6 +209,7 @@ def client_options(session, *args, **kwargs): nocolor (bool): Strip color raw (bool): Turn off parsing localecho (bool): Turn on server-side echo (for clients not supporting it) + istyping (bool): Toggle notifications for whether relevant players are typing """ old_flags = session.protocol_flags @@ -270,6 +273,8 @@ def client_options(session, *args, **kwargs): flags["NOGOAHEAD"] = validate_bool(value) elif key == "localecho": flags["LOCALECHO"] = validate_bool(value) + elif key == "istyping": + flags["ISTYPING"] = validate_bool(value) elif key in ( "Char 1", "Char.Skills 1", diff --git a/evennia/server/is_typing.py b/evennia/server/is_typing.py new file mode 100644 index 0000000000..ca06c174df --- /dev/null +++ b/evennia/server/is_typing.py @@ -0,0 +1,71 @@ +""" +This module allows users based on a given condition (defaults to same location) to see +whether applicable users are typing or not. Currently, only the webclient is supported. +""" + +from evennia import DefaultCharacter +from evennia.commands.default.general import CmdSay + +# Notification timeout in milliseconds + <=100ms polling interval in the client. +_IS_TYPING_TIMEOUT = 1000 * 5 + + +def is_typing_setup(session, *args, **kwargs): + """ + This fetches any aliases for the "say" command and the + specified notification timeout in milliseconds. + + Args: + session: The player's current session. + """ + + options = session.protocol_flags + is_typing = options.get("ISTYPING", True) + + if not is_typing: + return + + session.msg( + is_typing={ + "type": "setup", + "payload": {"say_aliases": CmdSay.aliases, "talking_timeout": _IS_TYPING_TIMEOUT}, + } + ) + + +def is_typing_state(session, *args, **kwargs): + """ + Broadcasts a typing state update from the session's puppet + to all other characters meeting the configured conditions + (defaults to same location). + + Args: + session (Session): The player's current session. + **kwargs: + - state (bool): The typing state to broadcast. + """ + options = session.protocol_flags + is_typing = options.get("ISTYPING", True) + + if not is_typing: + return + + state = kwargs.get("state") + + audience = DefaultCharacter.objects.filter_family(db_location=session.puppet.location).exclude( + db_key=session.puppet.key + ) + + for puppet in audience: + + for puppet_session in puppet.sessions.all(): + puppet_session_options = puppet_session.protocol_flags + puppet_session_is_typing = puppet_session_options.get("ISTYPING", True) + + if puppet_session_is_typing: + puppet_session.msg( + is_typing={ + "type": "typing", + "payload": {"name": session.puppet.name, "state": state}, + } + ) diff --git a/evennia/server/portal/webclient.py b/evennia/server/portal/webclient.py index 1f35d473ee..e2380bcce4 100644 --- a/evennia/server/portal/webclient.py +++ b/evennia/server/portal/webclient.py @@ -138,6 +138,7 @@ class WebSocketClient(WebSocketServerProtocol, _BASE_SESSION_CLASS): self.protocol_flags["TRUECOLOR"] = True self.protocol_flags["XTERM256"] = True self.protocol_flags["ANSI"] = True + self.protocol_flags["ISTYPING"] = True # watch for dead links self.transport.setTcpKeepAlive(1) diff --git a/evennia/settings_default.py b/evennia/settings_default.py index 0371b4bd07..ad423c8685 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -460,7 +460,11 @@ LOCK_FUNC_MODULES = ("evennia.locks.lockfuncs", "server.conf.lockfuncs") # Module holding handlers for managing incoming data from the client. These # will be loaded in order, meaning functions in later modules may overload # previous ones if having the same name. -INPUT_FUNC_MODULES = ["evennia.server.inputfuncs", "server.conf.inputfuncs"] +INPUT_FUNC_MODULES = [ + "evennia.server.inputfuncs", + "server.conf.inputfuncs", + "evennia.server.is_typing", +] # Modules that contain prototypes for use with the spawner mechanism. PROTOTYPE_MODULES = ["world.prototypes"] # Modules containining Prototype functions able to be embedded in prototype diff --git a/evennia/web/static/webclient/css/webclient.css b/evennia/web/static/webclient/css/webclient.css index 55135acc60..6ae9d929c6 100644 --- a/evennia/web/static/webclient/css/webclient.css +++ b/evennia/web/static/webclient/css/webclient.css @@ -346,6 +346,38 @@ div {margin:0px;} text-decoration: underline; } +#istyping { + position: fixed; + top: 25px; + right: 25px; + width: auto; + height: auto; + display: none; + color: white; +} + +#istypingdivider { + color: white; + border-color: white; +} + +.player-is-typing { + animation: isTyping 2s ease 0s infinite normal forwards; +} +@keyframes isTyping { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.2; + } + + 100% { + opacity: 1; + } +} + /* XTERM256 colors */ diff --git a/evennia/web/static/webclient/js/plugins/is_typing.js b/evennia/web/static/webclient/js/plugins/is_typing.js new file mode 100644 index 0000000000..44ac96131c --- /dev/null +++ b/evennia/web/static/webclient/js/plugins/is_typing.js @@ -0,0 +1,264 @@ +let is_typing = (function (){ + let Evennia; + let timeout = 0 + const state = { + timeout: null, + callback: null, + is_typing: false, + typing_players: [], + cleanup_callback: null, + } + + const sayCommands = ['say'] + + /** + * Create the containers that house our typing users + */ + const createDialog = function() { + const ele =[ + '