diff --git a/evennia/commands/default/account.py b/evennia/commands/default/account.py index c836a51dc1..368535228f 100644 --- a/evennia/commands/default/account.py +++ b/evennia/commands/default/account.py @@ -656,6 +656,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): "ENCODING": validate_encoding, "MCCP": validate_bool, "NOGOAHEAD": validate_bool, + "NOPROMPTGOAHEAD": validate_bool, "MXP": validate_bool, "NOCOLOR": validate_bool, "NOPKEEPALIVE": validate_bool, @@ -672,7 +673,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS): "FORCEDENDLINE": validate_bool, "LOCALECHO": validate_bool, "TRUECOLOR": validate_bool, - "ISTYPING": validate_bool + "ISTYPING": validate_bool, } name = self.lhs.upper() @@ -815,8 +816,8 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): # the slices of the ANSI_PARSER lists to use for retrieving the # relevant color tags to display. Replace if using another schema. # This command can only show one set of markup. - slice_bright_fg = slice(7, 15) # from ANSI_PARSER.ansi_map - slice_dark_fg = slice(15, 23) # from ANSI_PARSER.ansi_map + slice_bright_fg = slice(13, 21) # from ANSI_PARSER.ansi_map + slice_dark_fg = slice(21, 29) # from ANSI_PARSER.ansi_map slice_dark_bg = slice(-8, None) # from ANSI_PARSER.ansi_map slice_bright_bg = slice(None, None) # from ANSI_PARSER.ansi_xterm256_bright_bg_map @@ -840,10 +841,10 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): ) return ftable - def make_hex_color_from_column(self, column_number): - r = 255 - column_number * 255 / 76 - g = column_number * 510 / 76 - b = column_number * 255 / 76 + def make_hex_color_from_column(self, column_number, count): + r = 255 - column_number * 255 / count + g = column_number * 510 / count + b = column_number * 255 / count if g > 255: g = 510 - g @@ -933,14 +934,25 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS): elif self.args.startswith("t"): # show abbreviated truecolor sample (16.7 million colors in truecolor) - string = "" - for i in range(76): - string += f"|[{self.make_hex_color_from_column(i)} |n" + string = ( + "\n" + "True Colors (if this is not a smooth rainbow transition, your client might not " + "report that it can handle truecolor): \n" + ) + display_width = self.client_width() + num_colors = display_width * 1 + color_block = [ + f"|[{self.make_hex_color_from_column(i, num_colors)} " for i in range(num_colors) + ] + color_block = [ + "".join(color_block[iline : iline + display_width]) + for iline in range(0, num_colors, display_width) + ] + string += "\n".join(color_block) string += ( - "\n" - + "some of the truecolor colors (if not all hues show, your client might not report that it can" - " handle trucolor.):" + "\n|nfg: |#FF0000||#FF0000|n (|#F00||#F00|n) to |#0000FF||#0000FF|n (|#00F||#00F|n)" + "\n|nbg: |[#FF0000||[#FF0000|n (|[#F00||[#F00|n) to |n|[#0000FF||[#0000FF |n(|[#00F||[#00F|n)" ) self.msg(string) diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index aab89c690c..08c9ef9af5 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -25,12 +25,11 @@ from codecs import lookup as codecs_lookup from django.conf import settings -from evennia import DefaultCharacter +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, delay -from evennia.scripts.tickerhandler import TICKER_HANDLER +from evennia.utils.utils import to_str BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore @@ -45,8 +44,6 @@ _SA = object.__setattr__ _STRIP_INCOMING_MXP = settings.MXP_ENABLED and settings.MXP_OUTGOING_ONLY _STRIP_MXP = None -_IS_TYPING_PARTICIPANTS = {} - def _NA(o): return "N/A" @@ -145,12 +142,10 @@ def echo(session, *args, **kwargs): """ Echo test function """ - # txt = kwargs.get("txt") - # - # if _STRIP_INCOMING_MXP and txt: - # txt = strip_mxp(txt) + if _STRIP_INCOMING_MXP: + args = [_maybe_strip_incoming_mxp(str(arg)) for arg in args] - session.data_out(text="Echo returns: %s" % args) + session.data_out(text=f"Echo returns: {args}, {kwargs}") def default(session, cmdname, *args, **kwargs): @@ -187,7 +182,7 @@ _CLIENT_OPTIONS = ( "NOCOLOR", "NOGOAHEAD", "LOCALECHO", - "ISTYPING" + "ISTYPING", ) @@ -214,7 +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): Turn off OOB typing notifications (currently only used by the webclient) + istyping (bool): Toggle notifications for whether relevant players are typing """ old_flags = session.protocol_flags @@ -394,6 +389,8 @@ def repeat(session, *args, **kwargs): the above settings. """ + from evennia.scripts.tickerhandler import TICKER_HANDLER + name = kwargs.get("callback", "") interval = max(5, int(kwargs.get("interval", 60))) @@ -662,119 +659,6 @@ def msdp_send(session, *args, **kwargs): session.msg(send=((), out)) -def is_typing_send_update(): - """ - Send relevant updates to participants - - """ - participants = list(_IS_TYPING_PARTICIPANTS.keys()) - - for participant in participants: - if _IS_TYPING_PARTICIPANTS[participant]["session"].puppet is not None: - - payload = [] - # Get potentials - potentials = (DefaultCharacter.objects.filter_family(db_location=_IS_TYPING_PARTICIPANTS[participant]["session"].puppet.location) - .exclude(db_key=_IS_TYPING_PARTICIPANTS[participant]["session"].puppet.key)) - - for puppet in potentials: - - # We're only interested in sending updates if they're capable of receiving them - if str(puppet.sessid) in participants: - payload.append({ - "name": puppet.name, - "state": _IS_TYPING_PARTICIPANTS[str(puppet.sessid)]['state'], - }) - - _IS_TYPING_PARTICIPANTS[participant]['session'].msg(is_typing={ - 'type': 'typing', - 'payload': payload - }) - delay(5, is_typing_send_update) - else: - del _IS_TYPING_PARTICIPANTS[str(participant)] - - if len(_IS_TYPING_PARTICIPANTS.keys()) > 0: - delay(5, is_typing_send_update) - - -def is_typing_get_aliases(session, *args, **kwargs): - """ - Used in setting up clients. Fetch list of possible "talking" triggers - - Args: - session: - *args: - **kwargs: - - Returns: - - """ - options = session.protocol_flags - istyping = options.get("ISTYPING", True) - - if not istyping: - return - - # Add the participant to the list. - _IS_TYPING_PARTICIPANTS[str(session.sessid)] = { - "state": False, - "session": session, - } - - session.msg(is_typing={'type': 'aliases', 'payload': CmdSay.aliases}) - - if len(_IS_TYPING_PARTICIPANTS.keys()) == 1: - delay(5, is_typing_send_update) - - -def is_typing_update_participant(session, *args, **kwargs): - """ - Update a participant session's typing status - - Args: - session: - *args: First argument is a boolean indicating their typing state - **kwargs: - - Returns: - - """ - options = session.protocol_flags - istyping = options.get("ISTYPING", True) - - if not istyping: - is_typing_remove_participant(session) - return - - # If the session isn't found then server restarted - if _IS_TYPING_PARTICIPANTS.get(session.sessid) is None: - _IS_TYPING_PARTICIPANTS[str(session.sessid)] = { - "session": session, - "state": args[0], - } - - if len(_IS_TYPING_PARTICIPANTS.keys()) == 1: - delay(5, is_typing_send_update) - else: - _IS_TYPING_PARTICIPANTS[str(session.sessid)]['state'] = args[0] - -def is_typing_remove_participant(session, *args, **kwargs): - """ - Handle logging out/ending a session - - Args: - session: - *args: - **kwargs: - - Returns: - - """ - if _IS_TYPING_PARTICIPANTS.get(str(session.sessid)) is not None: - del _IS_TYPING_PARTICIPANTS[str(session.sessid)] - - # client specific 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/settings_default.py b/evennia/settings_default.py index ccd3bcccc2..79e11c8ebc 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -441,7 +441,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 66433c4b9b..6ae9d929c6 100644 --- a/evennia/web/static/webclient/css/webclient.css +++ b/evennia/web/static/webclient/css/webclient.css @@ -356,6 +356,11 @@ div {margin:0px;} color: white; } +#istypingdivider { + color: white; + border-color: white; +} + .player-is-typing { animation: isTyping 2s ease 0s infinite normal forwards; } diff --git a/evennia/web/static/webclient/js/plugins/is_typing.js b/evennia/web/static/webclient/js/plugins/is_typing.js index 02bebc0176..44ac96131c 100644 --- a/evennia/web/static/webclient/js/plugins/is_typing.js +++ b/evennia/web/static/webclient/js/plugins/is_typing.js @@ -1,227 +1,264 @@ -let is_typing = (function () { - let Evennia; - const timeout = 2 * 1000; - const state = { - timeout: null, - callback: null, - is_typing: false, - typing_players: [], - cleanup_callback: null, - }; - - const sayCommands = ["say"]; - - const createDialog = function () { - const ele = [ - '