mirror of
https://github.com/evennia/evennia.git
synced 2026-03-24 00:36:30 +01:00
Rolled it back to just relay typing states. Refactored to be a separate module, refined the notification container, and added in the ability to turn off notifications (and their sending) to the webclient.
This commit is contained in:
parent
6b782ee3c6
commit
91a4ac1c81
6 changed files with 358 additions and 345 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
71
evennia/server/is_typing.py
Normal file
71
evennia/server/is_typing.py
Normal file
|
|
@ -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},
|
||||
}
|
||||
)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
'<div id="istyping" class="content">',
|
||||
'<div id="typingplayers"></div>',
|
||||
"</div>",
|
||||
].join("\n");
|
||||
|
||||
$("body").append(ele);
|
||||
};
|
||||
|
||||
const playerElement = (name) =>
|
||||
`<div id="istyping-${name}" class="player-is-typing">${name} is typing...</div>`;
|
||||
|
||||
const startedTyping = function () {
|
||||
state.is_typing = true;
|
||||
state.timeout = Date.now() + timeout;
|
||||
state.callback = setTimeout(stoppedTyping, timeout);
|
||||
|
||||
sendIsTyping();
|
||||
};
|
||||
|
||||
const stillTyping = function () {
|
||||
state.timeout = Date.now() + timeout;
|
||||
clearTimeout(state.callback);
|
||||
state.callback = setTimeout(stoppedTyping, timeout);
|
||||
|
||||
sendIsTyping();
|
||||
};
|
||||
|
||||
const stoppedTyping = function () {
|
||||
state.is_typing = false;
|
||||
clearTimeout(state.callback);
|
||||
state.callback = null;
|
||||
state.timeout = null;
|
||||
|
||||
sendIsTyping();
|
||||
};
|
||||
|
||||
// Make our commands array regex safe
|
||||
const escapeRegExp = function (text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
};
|
||||
|
||||
// Get the say command's aliases
|
||||
const requestSayAliases = function () {
|
||||
Evennia.msg("is_typing_get_aliases");
|
||||
};
|
||||
|
||||
const setSayAliases = function (aliases) {
|
||||
aliases.forEach((alias) => sayCommands.push(escapeRegExp(alias)));
|
||||
};
|
||||
|
||||
// Update server
|
||||
const sendIsTyping = function () {
|
||||
Evennia.msg("is_typing_update_participant", [state.is_typing]);
|
||||
};
|
||||
|
||||
const onLoggedIn = function () {
|
||||
requestSayAliases();
|
||||
};
|
||||
|
||||
// Listen for talk commands
|
||||
const onKeydown = function (event) {
|
||||
const regex = new RegExp(
|
||||
`^\W*(${sayCommands.reduce((acc, cur) => acc + "|" + cur, "").substring(1)})`,
|
||||
);
|
||||
const inputfield = $(".inputfield:focus");
|
||||
|
||||
// A 'say' command is being used.
|
||||
if (
|
||||
Evennia.isConnected() &&
|
||||
inputfield.length === 1 &&
|
||||
event.key.length === 1 &&
|
||||
inputfield.val().match(regex)
|
||||
) {
|
||||
if (event.which === 13) {
|
||||
// Enter. Message sent. Reset.
|
||||
stoppedTyping();
|
||||
} else if (!state.is_typing) {
|
||||
// Speaking just started. Set is_talking and timeout.
|
||||
startedTyping();
|
||||
} else if (Date.now() > state.timeout) {
|
||||
// Expiration is nearing. Update timeout.
|
||||
stillTyping();
|
||||
}
|
||||
// Not talking anymore but state hasn't been updated yet.
|
||||
} else if (state.is_typing) {
|
||||
stoppedTyping();
|
||||
}
|
||||
};
|
||||
|
||||
// Reset everything
|
||||
const onConnectionClose = function () {
|
||||
state.is_typing = false;
|
||||
state.timeout = null;
|
||||
state.typing_players = [];
|
||||
|
||||
if (state.callback) {
|
||||
clearTimeout(state.callback);
|
||||
let is_typing = (function (){
|
||||
let Evennia;
|
||||
let timeout = 0
|
||||
const state = {
|
||||
timeout: null,
|
||||
callback: null,
|
||||
is_typing: false,
|
||||
typing_players: [],
|
||||
cleanup_callback: null,
|
||||
}
|
||||
|
||||
if (state.cleanup_callback) {
|
||||
clearTimeout(state.cleanup_callback);
|
||||
const sayCommands = ['say']
|
||||
|
||||
/**
|
||||
* Create the containers that house our typing users
|
||||
*/
|
||||
const createDialog = function() {
|
||||
const ele =[
|
||||
'<div id="istyping" class="content">',
|
||||
'<h5>Who\'s typing?</h4>',
|
||||
'<hr id="istypingdivider" />',
|
||||
'<div id="typingplayers"></div>',
|
||||
'</div>'
|
||||
].join('\n')
|
||||
|
||||
$('body').append(ele)
|
||||
}
|
||||
|
||||
Evennia.msg("is_typing_remove_participant");
|
||||
};
|
||||
const playerElement =(name)=> `<div id="istyping-${name}" class="player-is-typing">${name}</div>`
|
||||
|
||||
const cleanupTimedOutPlayers = function () {
|
||||
const now = Date.now();
|
||||
const timedOut = [];
|
||||
/**
|
||||
* The user has just started typing--set our flag, start our timeout callback, and
|
||||
* let the server know
|
||||
*/
|
||||
const startedTyping = function () {
|
||||
state.is_typing = true;
|
||||
state.timeout = Date.now() + timeout;
|
||||
state.callback = setTimeout(stoppedTyping, timeout);
|
||||
|
||||
state.typing_players.forEach((player, index) => {
|
||||
if (player.timeout < now) {
|
||||
timedOut.push(index);
|
||||
$(`#istyping-${player.name}`).remove();
|
||||
}
|
||||
});
|
||||
|
||||
timedOut
|
||||
.reverse()
|
||||
.forEach((index) => state.typing_players.splice(index, 1));
|
||||
|
||||
if (state.typing_players.length === 0) {
|
||||
clearTimeout(state.cleanup_callback);
|
||||
$("#istyping").hide();
|
||||
sendIsTyping()
|
||||
}
|
||||
};
|
||||
|
||||
const is_typing = function (args, kwargs) {
|
||||
if ("type" in kwargs) {
|
||||
switch (kwargs.type) {
|
||||
case "aliases":
|
||||
setSayAliases(kwargs.payload);
|
||||
break;
|
||||
/**
|
||||
* The user is *still* typing--update our timeout and let the server know
|
||||
*/
|
||||
const stillTyping = function () {
|
||||
state.timeout = Date.now() + timeout
|
||||
clearTimeout(state.callback)
|
||||
state.callback = setTimeout(stoppedTyping, timeout)
|
||||
|
||||
case "typing":
|
||||
const updated_players = kwargs.payload;
|
||||
let player = null;
|
||||
sendIsTyping()
|
||||
}
|
||||
|
||||
updated_players.forEach((updated) => {
|
||||
player = state.typing_players.filter(
|
||||
(player) => player.name === updated.name,
|
||||
);
|
||||
/**
|
||||
* The user has stopped typing--clean things up and tell the server
|
||||
*/
|
||||
const stoppedTyping = function () {
|
||||
state.is_typing = false;
|
||||
clearTimeout(state.callback);
|
||||
state.callback = null;
|
||||
state.timeout = null;
|
||||
|
||||
// New talker
|
||||
if (updated.state && player.length === 0) {
|
||||
state.typing_players.push({
|
||||
name: updated.name,
|
||||
timeout: Date.now() + timeout,
|
||||
});
|
||||
$("#typingplayers").append(playerElement(updated.name));
|
||||
sendIsTyping()
|
||||
}
|
||||
|
||||
// Existing talker is still going
|
||||
} else if (updated.state && player.length > 0) {
|
||||
if (Date.now() - 1000 >= player.timeout) {
|
||||
stillTyping();
|
||||
}
|
||||
/**
|
||||
* Make our commands array regex safe
|
||||
*
|
||||
* @param {string} - The contents of the user's command input
|
||||
*/
|
||||
const escapeRegExp = function (text) {
|
||||
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
|
||||
}
|
||||
|
||||
// They're done talking
|
||||
} else {
|
||||
state.typing_players = state.typing_players.filter(
|
||||
(player) => player.name !== updated.name,
|
||||
);
|
||||
$(`#istyping-${updated.name}`).remove();
|
||||
/**
|
||||
* Fetch the "say" command's aliases and the notification timeout
|
||||
*/
|
||||
const setup = function() {
|
||||
Evennia.msg('is_typing_setup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided aliases to the things we listen for
|
||||
*
|
||||
* @param {string[]} aliases - Array of "say" commands
|
||||
*/
|
||||
const setSayAliases = function (aliases) {
|
||||
aliases.forEach(alias=>{
|
||||
const cmd = escapeRegExp(alias);
|
||||
|
||||
// Is it already present?
|
||||
if (sayCommands.indexOf(cmd) === -1){
|
||||
sayCommands.push(escapeRegExp(alias)); // Nope!
|
||||
}
|
||||
});
|
||||
|
||||
if (state.typing_players.length > 0 && !state.cleanup_callback) {
|
||||
state.cleanup_callback = setTimeout(cleanupTimedOutPlayers, 100);
|
||||
$("#istyping").show();
|
||||
} else if (
|
||||
state.typing_players.length === 0 &&
|
||||
state.cleanup_callback
|
||||
) {
|
||||
clearInterval(state.cleanup_callback);
|
||||
state.cleanup_callback = null;
|
||||
$("#istyping").hide();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Default case");
|
||||
console.log(args);
|
||||
console.log(kwargs);
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const getState = () => state;
|
||||
/**
|
||||
* Sends a typing indicator to the server.
|
||||
*
|
||||
* @param {bool} state - The typing state, e.g., "typing" or "idle".
|
||||
*/
|
||||
const sendIsTyping = function () {
|
||||
Evennia.msg('is_typing_state', null, {"state": state.is_typing})
|
||||
}
|
||||
|
||||
//
|
||||
// Mandatory plugin init function
|
||||
const init = function () {
|
||||
let options = window.options;
|
||||
options["is_typing"] = true;
|
||||
Evennia = window.Evennia;
|
||||
const onLoggedIn = function () {
|
||||
setup()
|
||||
}
|
||||
|
||||
Evennia.emitter.on("is_typing", is_typing);
|
||||
/**
|
||||
* Sends a typing indicator to the server.
|
||||
*
|
||||
* @param {KeyboardEvent} event - The typing state, e.g., "typing" or "idle".
|
||||
*/
|
||||
const onKeydown = function (event) {
|
||||
const regex = new RegExp(`^\W*(${sayCommands.reduce((acc, cur)=> acc + "|" + cur, "").substring(1)})`)
|
||||
const inputfield = $(".inputfield:focus");
|
||||
|
||||
createDialog();
|
||||
console.log("Is Typing plugin initialized");
|
||||
};
|
||||
// A 'say' command is being used.
|
||||
if (Evennia.isConnected() &&
|
||||
inputfield.length === 1 &&
|
||||
event.key.length === 1 &&
|
||||
inputfield.val().match(regex)) {
|
||||
// Enter. Message sent. Reset.
|
||||
if (event.which === 13) {
|
||||
stoppedTyping()
|
||||
|
||||
return {
|
||||
init,
|
||||
onLoggedIn,
|
||||
onKeydown,
|
||||
onConnectionClose,
|
||||
getState,
|
||||
};
|
||||
})();
|
||||
// Speaking just started. Set is_talking and timeout.
|
||||
} else if (!state.is_typing) {
|
||||
startedTyping();
|
||||
|
||||
window.plugin_handler.add("is_typing", is_typing);
|
||||
// Expiration is nearing. Update timeout.
|
||||
} else if (Date.now() + timeout > state.timeout) {
|
||||
stillTyping();
|
||||
|
||||
}
|
||||
// Not talking anymore but state hasn't been updated yet.
|
||||
} else if (state.is_typing) {
|
||||
stoppedTyping();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset everything to defaults.
|
||||
*/
|
||||
const onConnectionClose = function () {
|
||||
state.is_typing = false;
|
||||
state.timeout = null;
|
||||
state.typing_players = []
|
||||
|
||||
if (state.callback) {
|
||||
clearTimeout(state.callback)
|
||||
}
|
||||
|
||||
if (state.cleanup_callback) {
|
||||
clearTimeout(state.cleanup_callback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any timed out players and hide the div if no one is talking
|
||||
*
|
||||
*/
|
||||
const cleanupTimedOutPlayers = function () {
|
||||
const now = Date.now();
|
||||
const timedOut = []
|
||||
|
||||
state.typing_players.forEach((player, index)=>{
|
||||
if (player.timeout < now) {
|
||||
timedOut.push(index)
|
||||
$(`#istyping-${player}`).remove()
|
||||
}
|
||||
})
|
||||
|
||||
timedOut.reverse().forEach(index=>state.typing_players.splice(index, 1))
|
||||
|
||||
if (state.typing_players.length === 0) {
|
||||
clearTimeout(state.cleanup_callback)
|
||||
$('#istyping').hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handles inbound comms from the server
|
||||
*
|
||||
* @param {{
|
||||
* type: string - What type of response is it?
|
||||
* payload - varies with type
|
||||
* }} kwargs
|
||||
*/
|
||||
const is_typing = function (args, kwargs) {
|
||||
if ('type' in kwargs) {
|
||||
switch (kwargs.type) {
|
||||
case 'setup':
|
||||
const {say_aliases, talking_timeout } = kwargs.payload
|
||||
timeout = talking_timeout
|
||||
setSayAliases(say_aliases)
|
||||
break;
|
||||
|
||||
case 'typing':
|
||||
const player = state.typing_players.filter(player=>player.name === kwargs.payload.name)
|
||||
|
||||
// New talker
|
||||
if (kwargs.payload.state &&
|
||||
player.length === 0) {
|
||||
state.typing_players.push({name: kwargs.payload.name, timeout: Date.now() + timeout})
|
||||
$('#typingplayers').append(playerElement(kwargs.payload.name))
|
||||
|
||||
// Existing talker is still going
|
||||
} else if (kwargs.payload.state &&
|
||||
player.length > 0) {
|
||||
player[0].timeout = Date.now() + timeout;
|
||||
|
||||
// They're done talking
|
||||
} else {
|
||||
state.typing_players = state.typing_players.filter(player=>player.name!== kwargs.payload.name)
|
||||
$(`#istyping-${kwargs.payload.name}`).remove()
|
||||
}
|
||||
|
||||
if (state.typing_players.length > 0 && !state.cleanup_callback) {
|
||||
state.cleanup_callback = setTimeout(cleanupTimedOutPlayers, 100);
|
||||
$('#istyping').show();
|
||||
|
||||
} else if (state.typing_players.length === 0 && state.cleanup_callback) {
|
||||
clearTimeout(state.cleanup_callback)
|
||||
state.cleanup_callback = null;
|
||||
$('#istyping').hide();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("is_typing: Unknown case")
|
||||
console.log(args)
|
||||
console.log(kwargs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getState = () => state
|
||||
|
||||
// Mandatory plugin init function
|
||||
const init = function () {
|
||||
let options = window.options;
|
||||
options["is_typing"] = true;
|
||||
Evennia = window.Evennia;
|
||||
|
||||
Evennia.emitter.on("is_typing", is_typing);
|
||||
|
||||
createDialog();
|
||||
|
||||
console.log('Is Typing plugin initialized');
|
||||
}
|
||||
|
||||
return {
|
||||
init,
|
||||
onLoggedIn,
|
||||
onKeydown,
|
||||
onConnectionClose,
|
||||
getState
|
||||
}
|
||||
})()
|
||||
|
||||
window.plugin_handler.add("is_typing", is_typing)
|
||||
Loading…
Add table
Add a link
Reference in a new issue