mirror of
https://github.com/evennia/evennia.git
synced 2026-03-23 00:06:30 +01:00
As suggested in mygame\commands\command.py, I'm overriding COMMAND_DEFAULT_CLASS in settings.py to try to make sweeping changes. Having certain commands inherit directly from the base Command class thwarts this. The only difference between Command and MuxCommand is that MuxCommand defines some custom parsing, but that's overridden by these commands anyway, so there should be no practical difference. The import change in game_template/commands/command.py was required to fix an exception on server reload caused by the channelhandler.py change (I guess something to do with import order?)
323 lines
11 KiB
Python
323 lines
11 KiB
Python
"""
|
|
The channel handler, accessed from this module as CHANNEL_HANDLER is a
|
|
singleton that handles the stored set of channels and how they are
|
|
represented against the cmdhandler.
|
|
|
|
If there is a channel named 'newbie', we want to be able to just write
|
|
|
|
newbie Hello!
|
|
|
|
For this to work, 'newbie', the name of the channel, must be
|
|
identified by the cmdhandler as a command name. The channelhandler
|
|
stores all channels as custom 'commands' that the cmdhandler can
|
|
import and look through.
|
|
|
|
> Warning - channel names take precedence over command names, so make
|
|
sure to not pick clashing channel names.
|
|
|
|
Unless deleting a channel you normally don't need to bother about the
|
|
channelhandler at all - the create_channel method handles the update.
|
|
|
|
To delete a channel cleanly, delete the channel object, then call
|
|
update() on the channelhandler. Or use Channel.objects.delete() which
|
|
does this for you.
|
|
|
|
"""
|
|
from django.conf import settings
|
|
from evennia.commands import cmdset
|
|
from evennia.utils.logger import tail_log_file
|
|
from evennia.utils.utils import class_from_module
|
|
from django.utils.translation import gettext as _
|
|
|
|
# we must late-import these since any overloads are likely to
|
|
# themselves be using these classes leading to a circular import.
|
|
|
|
_CHANNEL_HANDLER_CLASS = None
|
|
_CHANNEL_COMMAND_CLASS = None
|
|
_CHANNELDB = None
|
|
_COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
|
|
|
class ChannelCommand(_COMMAND_DEFAULT_CLASS):
|
|
"""
|
|
{channelkey} channel
|
|
|
|
{channeldesc}
|
|
|
|
Usage:
|
|
{lower_channelkey} <message>
|
|
{lower_channelkey}/history [start]
|
|
{lower_channelkey} off - mutes the channel
|
|
{lower_channelkey} on - unmutes the channel
|
|
|
|
Switch:
|
|
history: View 20 previous messages, either from the end or
|
|
from <start> number of messages from the end.
|
|
|
|
Example:
|
|
{lower_channelkey} Hello World!
|
|
{lower_channelkey}/history
|
|
{lower_channelkey}/history 30
|
|
|
|
"""
|
|
|
|
# ^note that channeldesc and lower_channelkey will be filled
|
|
# automatically by ChannelHandler
|
|
|
|
# this flag is what identifies this cmd as a channel cmd
|
|
# and branches off to the system send-to-channel command
|
|
# (which is customizable by admin)
|
|
is_channel = True
|
|
key = "general"
|
|
help_category = "Channel Names"
|
|
obj = None
|
|
arg_regex = r"\s.*?|/history.*?"
|
|
|
|
def parse(self):
|
|
"""
|
|
Simple parser
|
|
"""
|
|
# cmdhandler sends channame:msg here.
|
|
channelname, msg = self.args.split(":", 1)
|
|
self.history_start = None
|
|
if msg.startswith("/history"):
|
|
arg = msg[8:]
|
|
try:
|
|
self.history_start = int(arg) if arg else 0
|
|
except ValueError:
|
|
# if no valid number was given, ignore it
|
|
pass
|
|
self.args = (channelname.strip(), msg.strip())
|
|
|
|
def func(self):
|
|
"""
|
|
Create a new message and send it to channel, using
|
|
the already formatted input.
|
|
"""
|
|
global _CHANNELDB
|
|
if not _CHANNELDB:
|
|
from evennia.comms.models import ChannelDB as _CHANNELDB
|
|
|
|
channelkey, msg = self.args
|
|
caller = self.caller
|
|
if not msg:
|
|
self.msg(_("Say what?"))
|
|
return
|
|
channel = _CHANNELDB.objects.get_channel(channelkey)
|
|
|
|
if not channel:
|
|
self.msg(_("Channel '%s' not found.") % channelkey)
|
|
return
|
|
if not channel.has_connection(caller):
|
|
string = _("You are not connected to channel '%s'.")
|
|
self.msg(string % channelkey)
|
|
return
|
|
if not channel.access(caller, "send"):
|
|
string = _("You are not permitted to send to channel '%s'.")
|
|
self.msg(string % channelkey)
|
|
return
|
|
if msg == "on":
|
|
caller = caller if not hasattr(caller, "account") else caller.account
|
|
unmuted = channel.unmute(caller)
|
|
if unmuted:
|
|
self.msg(_("You start listening to %s.") % channel)
|
|
return
|
|
self.msg(_("You were already listening to %s.") % channel)
|
|
return
|
|
if msg == "off":
|
|
caller = caller if not hasattr(caller, "account") else caller.account
|
|
muted = channel.mute(caller)
|
|
if muted:
|
|
self.msg(_("You stop listening to %s.") % channel)
|
|
return
|
|
self.msg(_("You were already not listening to %s.") % channel)
|
|
return
|
|
if self.history_start is not None:
|
|
# Try to view history
|
|
log_file = channel.attributes.get("log_file", default="channel_%s.log" % channel.key)
|
|
|
|
def send_msg(lines):
|
|
return self.msg(
|
|
"".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines)
|
|
)
|
|
|
|
tail_log_file(log_file, self.history_start, 20, callback=send_msg)
|
|
else:
|
|
caller = caller if not hasattr(caller, "account") else caller.account
|
|
if caller in channel.mutelist:
|
|
self.msg(_("You currently have %s muted.") % channel)
|
|
return
|
|
channel.msg(msg, senders=self.caller, online=True)
|
|
|
|
def get_extra_info(self, caller, **kwargs):
|
|
"""
|
|
Let users know that this command is for communicating on a channel.
|
|
|
|
Args:
|
|
caller (TypedObject): A Character or Account who has entered an ambiguous command.
|
|
|
|
Returns:
|
|
A string with identifying information to disambiguate the object, conventionally with a preceding space.
|
|
"""
|
|
return _(" (channel)")
|
|
|
|
|
|
class ChannelHandler(object):
|
|
"""
|
|
The ChannelHandler manages all active in-game channels and
|
|
dynamically creates channel commands for users so that they can
|
|
just give the channel's key or alias to write to it. Whenever a
|
|
new channel is created in the database, the update() method on
|
|
this handler must be called to sync it with the database (this is
|
|
done automatically if creating the channel with
|
|
evennia.create_channel())
|
|
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""
|
|
Initializes the channel handler's internal state.
|
|
|
|
"""
|
|
self._cached_channel_cmds = {}
|
|
self._cached_cmdsets = {}
|
|
self._cached_channels = {}
|
|
|
|
def __str__(self):
|
|
"""
|
|
Returns the string representation of the handler
|
|
|
|
"""
|
|
return ", ".join(str(cmd) for cmd in self._cached_channel_cmds)
|
|
|
|
def clear(self):
|
|
"""
|
|
Reset the cache storage.
|
|
|
|
"""
|
|
self._cached_channel_cmds = {}
|
|
self._cached_cmdsets = {}
|
|
self._cached_channels = {}
|
|
|
|
def add(self, channel):
|
|
"""
|
|
Add an individual channel to the handler. This is called
|
|
whenever a new channel is created.
|
|
|
|
Args:
|
|
channel (Channel): The channel to add.
|
|
|
|
Notes:
|
|
To remove a channel, simply delete the channel object and
|
|
run self.update on the handler. This should usually be
|
|
handled automatically by one of the deletion methos of
|
|
the Channel itself.
|
|
|
|
"""
|
|
global _CHANNEL_COMMAND_CLASS
|
|
if not _CHANNEL_COMMAND_CLASS:
|
|
_CHANNEL_COMMAND_CLASS = class_from_module(settings.CHANNEL_COMMAND_CLASS)
|
|
|
|
# map the channel to a searchable command
|
|
cmd = _CHANNEL_COMMAND_CLASS(
|
|
key=channel.key.strip().lower(),
|
|
aliases=channel.aliases.all(),
|
|
locks="cmd:all();%s" % channel.locks,
|
|
help_category="Channel names",
|
|
obj=channel,
|
|
is_channel=True,
|
|
)
|
|
# format the help entry
|
|
key = channel.key
|
|
cmd.__doc__ = cmd.__doc__.format(
|
|
channelkey=key,
|
|
lower_channelkey=key.strip().lower(),
|
|
channeldesc=channel.attributes.get("desc", default="").strip(),
|
|
)
|
|
self._cached_channel_cmds[channel] = cmd
|
|
self._cached_channels[key] = channel
|
|
self._cached_cmdsets = {}
|
|
|
|
add_channel = add # legacy alias
|
|
|
|
def remove(self, channel):
|
|
"""
|
|
Remove channel from channelhandler. This will also delete it.
|
|
|
|
Args:
|
|
channel (Channel): Channel to remove/delete.
|
|
|
|
"""
|
|
if channel.pk:
|
|
channel.delete()
|
|
self.update()
|
|
|
|
def update(self):
|
|
"""
|
|
Updates the handler completely, including removing old removed
|
|
Channel objects. This must be called after deleting a Channel.
|
|
|
|
"""
|
|
global _CHANNELDB
|
|
if not _CHANNELDB:
|
|
from evennia.comms.models import ChannelDB as _CHANNELDB
|
|
self._cached_channel_cmds = {}
|
|
self._cached_cmdsets = {}
|
|
self._cached_channels = {}
|
|
for channel in _CHANNELDB.objects.get_all_channels():
|
|
self.add(channel)
|
|
|
|
def get(self, channelname=None):
|
|
"""
|
|
Get a channel from the handler, or all channels
|
|
|
|
Args:
|
|
channelame (str, optional): Channel key, case insensitive.
|
|
Returns
|
|
channels (list): The matching channels in a list, or all
|
|
channels in the handler.
|
|
|
|
"""
|
|
if channelname:
|
|
channel = self._cached_channels.get(channelname.lower(), None)
|
|
return [channel] if channel else []
|
|
return list(self._cached_channels.values())
|
|
|
|
def get_cmdset(self, source_object):
|
|
"""
|
|
Retrieve cmdset for channels this source_object has
|
|
access to send to.
|
|
|
|
Args:
|
|
source_object (Object): An object subscribing to one
|
|
or more channels.
|
|
|
|
Returns:
|
|
cmdsets (list): The Channel-Cmdsets `source_object` has
|
|
access to.
|
|
|
|
"""
|
|
if source_object in self._cached_cmdsets:
|
|
return self._cached_cmdsets[source_object]
|
|
else:
|
|
# create a new cmdset holding all viable channels
|
|
chan_cmdset = None
|
|
chan_cmds = [
|
|
channelcmd
|
|
for channel, channelcmd in self._cached_channel_cmds.items()
|
|
if channel.subscriptions.has(source_object)
|
|
and channelcmd.access(source_object, "send")
|
|
]
|
|
if chan_cmds:
|
|
chan_cmdset = cmdset.CmdSet()
|
|
chan_cmdset.key = "ChannelCmdSet"
|
|
chan_cmdset.priority = 101
|
|
chan_cmdset.duplicates = True
|
|
for cmd in chan_cmds:
|
|
chan_cmdset.add(cmd)
|
|
self._cached_cmdsets[source_object] = chan_cmdset
|
|
return chan_cmdset
|
|
|
|
|
|
# set up the singleton
|
|
CHANNEL_HANDLER = class_from_module(settings.CHANNEL_HANDLER_CLASS)()
|
|
CHANNELHANDLER = CHANNEL_HANDLER # legacy
|