mirror of
https://github.com/evennia/evennia.git
synced 2026-03-22 07:46:30 +01:00
Merge conflicts against master, including cmdhandler support for direct cmdobject input together with prefix-ignore mechanism from devel.
This commit is contained in:
commit
a648433db8
69 changed files with 2617 additions and 1771 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
|
@ -1,4 +1,23 @@
|
|||
# Evennia Changelog
|
||||
## Feb 2017:
|
||||
New devel branch created, to lead up to Evennia 0.7.
|
||||
|
||||
## Dec 2016:
|
||||
Lots of bugfixes and considerable uptick in contributors. Unittest coverage
|
||||
and PEP8 adoption and refactoring.
|
||||
|
||||
## May 2016:
|
||||
Evennia 0.6 with completely reworked Out-of-band system, making
|
||||
the message path completely flexible and built around input/outputfuncs.
|
||||
A completely new webclient, split into the evennia.js library and a
|
||||
gui library, making it easier to customize.
|
||||
|
||||
## Feb 2016:
|
||||
Added the new EvMenu and EvMore utilities, updated EvEdit and cleaned up
|
||||
a lot of the batchcommand functionality. Started work on new Devel branch.
|
||||
|
||||
## Sept 2015:
|
||||
Evennia 0.5. Merged devel branch, full library format implemented.
|
||||
|
||||
## Feb 2015:
|
||||
Development currently in devel/ branch. Moved typeclasses to use
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ likely file a bug report with the Evennia project.
|
|||
""")
|
||||
|
||||
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
|
||||
"reached for '{raw_string}' ({cmdclass})."
|
||||
"reached for '{raw_cmdname}' ({cmdclass})."
|
||||
|
||||
|
||||
def _msg_err(receiver, stringtuple):
|
||||
|
|
@ -235,7 +235,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
|||
for lobj in local_objlist:
|
||||
try:
|
||||
# call hook in case we need to do dynamic changing to cmdset
|
||||
_GA(lobj, "at_cmdset_get")()
|
||||
_GA(lobj, "at_cmdset_get")(caller=caller)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
# the call-type lock is checked here, it makes sure a player
|
||||
|
|
@ -391,7 +391,8 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
|
|||
|
||||
|
||||
@inlineCallbacks
|
||||
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, **kwargs):
|
||||
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None,
|
||||
cmdobj=None, cmdobj_key=None, **kwargs):
|
||||
"""
|
||||
This is the main mechanism that handles any string sent to the engine.
|
||||
|
||||
|
|
@ -413,6 +414,15 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
|||
precendence for same-name and same-prio commands.
|
||||
session (Session, optional): Relevant if callertype is "player" - the session will help
|
||||
retrieve the correct cmdsets from puppeted objects.
|
||||
cmdobj (Command, optional): If given a command instance, this will be executed using
|
||||
`called_by` as the caller, `raw_string` representing its arguments and (optionally)
|
||||
`cmdobj_key` as its input command name. No cmdset lookup will be performed but
|
||||
all other options apply as normal. This allows for running a specific Command
|
||||
within the command system mechanism.
|
||||
cmdobj_key (string, optional): Used together with `cmdobj` keyword to specify
|
||||
which cmdname should be assigned when calling the specified Command instance. This
|
||||
is made available as `self.cmdstring` when the Command runs.
|
||||
If not given, the command will be assumed to be called as `cmdobj.key`.
|
||||
|
||||
Kwargs:
|
||||
kwargs (any): other keyword arguments will be assigned as named variables on the
|
||||
|
|
@ -428,7 +438,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
|||
"""
|
||||
|
||||
@inlineCallbacks
|
||||
def _run_command(cmd, cmdname, raw_cmdname, args):
|
||||
def _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, player):
|
||||
"""
|
||||
Helper function: This initializes and runs the Command
|
||||
instance once the parser has identified it as either a normal
|
||||
|
|
@ -437,10 +447,13 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
|||
Args:
|
||||
cmd (Command): Command object
|
||||
cmdname (str): Name of command
|
||||
args (str): extra text entered after the identified command
|
||||
raw_cmdname (str): Name of Command, unaffected by eventual
|
||||
prefix-stripping (if no prefix-stripping, this is the same
|
||||
as cmdname).
|
||||
args (str): extra text entered after the identified command
|
||||
cmdset (CmdSet): Command sert the command belongs to (if any)..
|
||||
session (Session): Session of caller (if any).
|
||||
player (Player): Player of caller (if any).
|
||||
|
||||
Returns:
|
||||
deferred (Deferred): this will fire with the return of the
|
||||
|
|
@ -482,7 +495,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
|||
_COMMAND_NESTING[called_by] += 1
|
||||
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
|
||||
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT,
|
||||
raw_string=unformatted_raw_string,
|
||||
raw_cmdname=raw_cmdname,
|
||||
cmdclass=cmd.__class__)
|
||||
raise RuntimeError(err)
|
||||
|
||||
|
|
@ -543,78 +556,89 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
|||
|
||||
try: # catch bugs in cmdhandler itself
|
||||
try: # catch special-type commands
|
||||
if cmdobj:
|
||||
# the command object is already given
|
||||
cmd = cmdobj() if callable(cmdobj) else cmdobj
|
||||
cmdname = cmdobj_key if cmdobj_key else cmd.key
|
||||
args = raw_string
|
||||
unformatted_raw_string = "%s%s" % (cmdname, args)
|
||||
cmdset = None
|
||||
session = session
|
||||
player = player
|
||||
|
||||
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
|
||||
callertype, raw_string)
|
||||
if not cmdset:
|
||||
# this is bad and shouldn't happen.
|
||||
raise NoCmdSets
|
||||
# store the completely unmodified raw string - including
|
||||
# whitespace and eventual prefixes-to-be-stripped.
|
||||
unformatted_raw_string = raw_string
|
||||
raw_string = raw_string.strip()
|
||||
if not raw_string:
|
||||
# Empty input. Test for system command instead.
|
||||
syscmd = yield cmdset.get(CMD_NOINPUT)
|
||||
sysarg = ""
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
# Parse the input string and match to available cmdset.
|
||||
# This also checks for permissions, so all commands in match
|
||||
# are commands the caller is allowed to call.
|
||||
matches = yield _COMMAND_PARSER(raw_string, cmdset, caller)
|
||||
else:
|
||||
# no explicit cmdobject given, figure it out
|
||||
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
|
||||
callertype, raw_string)
|
||||
if not cmdset:
|
||||
# this is bad and shouldn't happen.
|
||||
raise NoCmdSets
|
||||
# store the completely unmodified raw string - including
|
||||
# whitespace and eventual prefixes-to-be-stripped.
|
||||
unformatted_raw_string = raw_string
|
||||
raw_string = raw_string.strip()
|
||||
if not raw_string:
|
||||
# Empty input. Test for system command instead.
|
||||
syscmd = yield cmdset.get(CMD_NOINPUT)
|
||||
sysarg = ""
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
# Parse the input string and match to available cmdset.
|
||||
# This also checks for permissions, so all commands in match
|
||||
# are commands the caller is allowed to call.
|
||||
matches = yield _COMMAND_PARSER(raw_string, cmdset, caller)
|
||||
|
||||
# Deal with matches
|
||||
# Deal with matches
|
||||
|
||||
if len(matches) > 1:
|
||||
# We have a multiple-match
|
||||
syscmd = yield cmdset.get(CMD_MULTIMATCH)
|
||||
sysarg = _("There were multiple matches.")
|
||||
if syscmd:
|
||||
# use custom CMD_MULTIMATCH
|
||||
syscmd.matches = matches
|
||||
else:
|
||||
# fall back to default error handling
|
||||
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0])
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
|
||||
cmdname, args, cmd = "", "", None
|
||||
if len(matches) == 1:
|
||||
# We have a unique command match. But it may still be invalid.
|
||||
match = matches[0]
|
||||
cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5]
|
||||
|
||||
if not matches:
|
||||
# No commands match our entered command
|
||||
syscmd = yield cmdset.get(CMD_NOMATCH)
|
||||
if syscmd:
|
||||
# use custom CMD_NOMATCH command
|
||||
sysarg = raw_string
|
||||
else:
|
||||
# fallback to default error text
|
||||
sysarg = _("Command '%s' is not available.") % raw_string
|
||||
suggestions = string_suggestions(raw_string,
|
||||
cmdset.get_all_cmd_keys_and_aliases(caller),
|
||||
cutoff=0.7, maxnum=3)
|
||||
if suggestions:
|
||||
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
|
||||
if len(matches) > 1:
|
||||
# We have a multiple-match
|
||||
syscmd = yield cmdset.get(CMD_MULTIMATCH)
|
||||
sysarg = _("There were multiple matches.")
|
||||
if syscmd:
|
||||
# use custom CMD_MULTIMATCH
|
||||
syscmd.matches = matches
|
||||
else:
|
||||
sysarg += _(" Type \"help\" for help.")
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
# fall back to default error handling
|
||||
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0])
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
|
||||
# Check if this is a Channel-cmd match.
|
||||
if hasattr(cmd, 'is_channel') and cmd.is_channel:
|
||||
# even if a user-defined syscmd is not defined, the
|
||||
# found cmd is already a system command in its own right.
|
||||
syscmd = yield cmdset.get(CMD_CHANNEL)
|
||||
if syscmd:
|
||||
# replace system command with custom version
|
||||
cmd = syscmd
|
||||
cmd.session = session
|
||||
sysarg = "%s:%s" % (cmdname, args)
|
||||
raise ExecSystemCommand(cmd, sysarg)
|
||||
cmdname, args, cmd = "", "", None
|
||||
if len(matches) == 1:
|
||||
# We have a unique command match. But it may still be invalid.
|
||||
match = matches[0]
|
||||
cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5]
|
||||
|
||||
if not matches:
|
||||
# No commands match our entered command
|
||||
syscmd = yield cmdset.get(CMD_NOMATCH)
|
||||
if syscmd:
|
||||
# use custom CMD_NOMATCH command
|
||||
sysarg = raw_string
|
||||
else:
|
||||
# fallback to default error text
|
||||
sysarg = _("Command '%s' is not available.") % raw_string
|
||||
suggestions = string_suggestions(raw_string,
|
||||
cmdset.get_all_cmd_keys_and_aliases(caller),
|
||||
cutoff=0.7, maxnum=3)
|
||||
if suggestions:
|
||||
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
|
||||
else:
|
||||
sysarg += _(" Type \"help\" for help.")
|
||||
raise ExecSystemCommand(syscmd, sysarg)
|
||||
|
||||
# Check if this is a Channel-cmd match.
|
||||
if hasattr(cmd, 'is_channel') and cmd.is_channel:
|
||||
# even if a user-defined syscmd is not defined, the
|
||||
# found cmd is already a system command in its own right.
|
||||
syscmd = yield cmdset.get(CMD_CHANNEL)
|
||||
if syscmd:
|
||||
# replace system command with custom version
|
||||
cmd = syscmd
|
||||
cmd.session = session
|
||||
sysarg = "%s:%s" % (cmdname, args)
|
||||
raise ExecSystemCommand(cmd, sysarg)
|
||||
|
||||
# A normal command.
|
||||
ret = yield _run_command(cmd, cmdname, raw_cmdname, args)
|
||||
ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, player)
|
||||
returnValue(ret)
|
||||
|
||||
except ErrorReported as exc:
|
||||
|
|
@ -629,7 +653,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
|
|||
sysarg = exc.sysarg
|
||||
|
||||
if syscmd:
|
||||
ret = yield _run_command(syscmd, syscmd.key, syscmd, sysarg)
|
||||
ret = yield _run_command(syscmd, syscmd.key, sysarg,
|
||||
unformatted_raw_string, cmdset, session, player)
|
||||
returnValue(ret)
|
||||
elif sysarg:
|
||||
# return system arg
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ def _init_command(cls, **kwargs):
|
|||
if cls.aliases and not is_iter(cls.aliases):
|
||||
try:
|
||||
cls.aliases = [str(alias).strip().lower()
|
||||
for alias in cls.aliases.split(',')]
|
||||
for alias in cls.aliases.split(',')]
|
||||
except Exception:
|
||||
cls.aliases = []
|
||||
cls.aliases = list(set(alias for alias in cls.aliases
|
||||
|
|
@ -57,7 +57,7 @@ def _init_command(cls, **kwargs):
|
|||
if not hasattr(cls, 'locks'):
|
||||
# default if one forgets to define completely
|
||||
cls.locks = "cmd:all()"
|
||||
if not "cmd:" in cls.locks:
|
||||
if "cmd:" not in cls.locks:
|
||||
cls.locks = "cmd:all();" + cls.locks
|
||||
for lockstring in cls.locks.split(';'):
|
||||
if lockstring and not ':' in lockstring:
|
||||
|
|
@ -197,7 +197,6 @@ class Command(with_metaclass(CommandMeta, object)):
|
|||
try:
|
||||
# first assume input is a command (the most common case)
|
||||
return self._matchset.intersection(cmd._matchset)
|
||||
#return cmd.key in self._matchset
|
||||
except AttributeError:
|
||||
# probably got a string
|
||||
return cmd in self._matchset
|
||||
|
|
@ -211,9 +210,8 @@ class Command(with_metaclass(CommandMeta, object)):
|
|||
"""
|
||||
try:
|
||||
return self._matchset.isdisjoint(cmd._matchset)
|
||||
#return not cmd.key in self._matcheset
|
||||
except AttributeError:
|
||||
return not cmd in self._matchset
|
||||
return cmd not in self._matchset
|
||||
|
||||
def __contains__(self, query):
|
||||
"""
|
||||
|
|
@ -308,7 +306,7 @@ class Command(with_metaclass(CommandMeta, object)):
|
|||
def msg(self, text=None, to_obj=None, from_obj=None,
|
||||
session=None, **kwargs):
|
||||
"""
|
||||
This is a shortcut instad of calling msg() directly on an
|
||||
This is a shortcut instead of calling msg() directly on an
|
||||
object - it will detect if caller is an Object or a Player and
|
||||
also appends self.session automatically if self.msg_all_sessions is False.
|
||||
|
||||
|
|
@ -398,17 +396,18 @@ class Command(with_metaclass(CommandMeta, object)):
|
|||
"""
|
||||
# a simple test command to show the available properties
|
||||
string = "-" * 50
|
||||
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
|
||||
string += "\n|w%s|n - Command variables from evennia:\n" % self.key
|
||||
string += "-" * 50
|
||||
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
|
||||
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
|
||||
string += "cmd locks (self.locks): {w%s{n\n" % self.locks
|
||||
string += "help category (self.help_category): {w%s{n\n" % self.help_category.capitalize()
|
||||
string += "object calling (self.caller): {w%s{n\n" % self.caller
|
||||
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
|
||||
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
|
||||
string += "\nname of cmd (self.key): |w%s|n\n" % self.key
|
||||
string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases
|
||||
string += "cmd locks (self.locks): |w%s|n\n" % self.locks
|
||||
string += "help category (self.help_category): |w%s|n\n" % self.help_category.capitalize()
|
||||
string += "object calling (self.caller): |w%s|n\n" % self.caller
|
||||
string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj
|
||||
string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring
|
||||
# show cmdset.key instead of cmdset to shorten output
|
||||
string += fill("current cmdset (self.cmdset): {w%s{n\n" % (self.cmdset.key if self.cmdset.key else self.cmdset.__class__))
|
||||
string += fill("current cmdset (self.cmdset): |w%s|n\n" %
|
||||
(self.cmdset.key if self.cmdset.key else self.cmdset.__class__))
|
||||
|
||||
self.caller.msg(string)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
#
|
||||
# This is Evennia's default connection screen. It is imported
|
||||
# and run from server/conf/connection_screens.py.
|
||||
#
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.utils import utils
|
||||
|
||||
DEFAULT_SCREEN = \
|
||||
"""{b=============================================================={n
|
||||
Welcome to {g%s{n, version %s!
|
||||
|
||||
If you have an existing account, connect to it by typing:
|
||||
{wconnect <username> <password>{n
|
||||
If you need to create an account, type (without the <>'s):
|
||||
{wcreate <username> <password>{n
|
||||
|
||||
If you have spaces in your username, enclose it in double quotes.
|
||||
Enter {whelp{n for more info. {wlook{n will re-show this screen.
|
||||
{b=============================================================={n""" \
|
||||
% (settings.SERVERNAME, utils.get_evennia_version())
|
||||
|
|
@ -9,7 +9,7 @@ import re
|
|||
from django.conf import settings
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.server.models import ServerConfig
|
||||
from evennia.utils import prettytable, search, class_from_module
|
||||
from evennia.utils import evtable, search, class_from_module
|
||||
|
||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implementing the function"
|
||||
"""Implementing the function"""
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
|||
# Carry out the booting of the sessions in the boot list.
|
||||
|
||||
feedback = None
|
||||
if not 'quiet' in self.switches:
|
||||
if 'quiet' not in self.switches:
|
||||
feedback = "You have been disconnected by %s.\n" % caller.name
|
||||
if reason:
|
||||
feedback += "\nReason given: %s" % reason
|
||||
|
|
@ -108,13 +108,12 @@ def list_bans(banlist):
|
|||
if not banlist:
|
||||
return "No active bans were found."
|
||||
|
||||
table = prettytable.PrettyTable(["{wid", "{wname/ip", "{wdate", "{wreason"])
|
||||
table = evtable.EvTable("|wid", "|wname/ip", "|wdate", "|wreason")
|
||||
for inum, ban in enumerate(banlist):
|
||||
table.add_row([str(inum + 1),
|
||||
ban[0] and ban[0] or ban[1],
|
||||
ban[3], ban[4]])
|
||||
string = "{wActive bans:{n\n%s" % table
|
||||
return string
|
||||
table.add_row(str(inum + 1),
|
||||
ban[0] and ban[0] or ban[1],
|
||||
ban[3], ban[4])
|
||||
return "|wActive bans:|n\n%s" % table
|
||||
|
||||
|
||||
class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -174,7 +173,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if not self.args or (self.switches
|
||||
and not any(switch in ('ip', 'name')
|
||||
for switch in self.switches)):
|
||||
for switch in self.switches)):
|
||||
self.caller.msg(list_bans(banlist))
|
||||
return
|
||||
|
||||
|
|
@ -202,7 +201,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
|||
# save updated banlist
|
||||
banlist.append(bantup)
|
||||
ServerConfig.objects.conf('server_bans', banlist)
|
||||
self.caller.msg("%s-Ban {w%s{n was added." % (typ, ban))
|
||||
self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban))
|
||||
|
||||
|
||||
class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -223,7 +222,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement unbanning"
|
||||
"""Implement unbanning"""
|
||||
|
||||
banlist = ServerConfig.objects.conf('server_bans')
|
||||
|
||||
|
|
@ -240,14 +239,14 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
|||
if not banlist:
|
||||
self.caller.msg("There are no bans to clear.")
|
||||
elif not (0 < num < len(banlist) + 1):
|
||||
self.caller.msg("Ban id {w%s{x was not found." % self.args)
|
||||
self.caller.msg("Ban id |w%s|x was not found." % self.args)
|
||||
else:
|
||||
# all is ok, clear ban
|
||||
ban = banlist[num - 1]
|
||||
del banlist[num - 1]
|
||||
ServerConfig.objects.conf('server_bans', banlist)
|
||||
self.caller.msg("Cleared ban %s: %s" %
|
||||
(num, " ".join([s for s in ban[:2]])))
|
||||
(num, " ".join([s for s in ban[:2]])))
|
||||
|
||||
|
||||
class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -270,7 +269,7 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implements the command."
|
||||
"""Implements the command."""
|
||||
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
|
@ -346,7 +345,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
"""Implement the command"""
|
||||
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
|
@ -382,7 +381,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
|||
obj = caller.search(objname, global_search=True)
|
||||
if not obj:
|
||||
return
|
||||
if rooms_only and not obj.location is None:
|
||||
if rooms_only and obj.location is not None:
|
||||
caller.msg("%s is not a room. Ignored." % objname)
|
||||
continue
|
||||
if players_only and not obj.has_player:
|
||||
|
|
@ -414,7 +413,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement the function."
|
||||
"""Implement the function."""
|
||||
|
||||
caller = self.caller
|
||||
|
||||
|
|
@ -455,7 +454,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implement function"
|
||||
"""Implement function"""
|
||||
|
||||
caller = self.caller
|
||||
switches = self.switches
|
||||
|
|
@ -481,36 +480,37 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg("You are not allowed to examine this object.")
|
||||
return
|
||||
|
||||
string = "Permissions on {w%s{n: " % obj.key
|
||||
string = "Permissions on |w%s|n: " % obj.key
|
||||
if not obj.permissions.all():
|
||||
string += "<None>"
|
||||
else:
|
||||
string += ", ".join(obj.permissions.all())
|
||||
if (hasattr(obj, 'player') and
|
||||
hasattr(obj.player, 'is_superuser') and
|
||||
obj.player.is_superuser):
|
||||
obj.player.is_superuser):
|
||||
string += "\n(... but this object is currently controlled by a SUPERUSER! "
|
||||
string += "All access checks are passed automatically.)"
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
# we supplied an argument on the form obj = perm
|
||||
|
||||
if not obj.access(caller, 'control'):
|
||||
caller.msg("You are not allowed to edit this object's permissions.")
|
||||
locktype = "edit" if playermode else "control"
|
||||
if not obj.access(caller, locktype):
|
||||
caller.msg("You are not allowed to edit this %s's permissions."
|
||||
% ("player" if playermode else "object"))
|
||||
return
|
||||
|
||||
cstring = ""
|
||||
tstring = ""
|
||||
caller_result = []
|
||||
target_result = []
|
||||
if 'del' in switches:
|
||||
# delete the given permission(s) from object.
|
||||
for perm in self.rhslist:
|
||||
obj.permissions.remove(perm)
|
||||
if obj.permissions.get(perm):
|
||||
cstring += "\nPermissions %s could not be removed from %s." % (perm, obj.name)
|
||||
caller_result.append("\nPermissions %s could not be removed from %s." % (perm, obj.name))
|
||||
else:
|
||||
cstring += "\nPermission %s removed from %s (if they existed)." % (perm, obj.name)
|
||||
tstring += "\n%s revokes the permission(s) %s from you." % (caller.name, perm)
|
||||
caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name))
|
||||
target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm))
|
||||
else:
|
||||
# add a new permission
|
||||
permissions = obj.permissions.all()
|
||||
|
|
@ -525,15 +525,16 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
|
||||
if perm in permissions:
|
||||
cstring += "\nPermission '%s' is already defined on %s." % (rhs, obj.name)
|
||||
caller_result.append("\nPermission '%s' is already defined on %s." % (rhs, obj.name))
|
||||
else:
|
||||
obj.permissions.add(perm)
|
||||
plystring = "the Player" if playermode else "the Object/Character"
|
||||
cstring += "\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring)
|
||||
tstring += "\n%s gives you (%s, %s) the permission '%s'." % (caller.name, obj.name, plystring, rhs)
|
||||
caller.msg(cstring.strip())
|
||||
if tstring:
|
||||
obj.msg(tstring.strip())
|
||||
caller_result.append("\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring))
|
||||
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
|
||||
% (caller.name, obj.name, plystring, rhs))
|
||||
caller.msg("".join(caller_result).strip())
|
||||
if target_result:
|
||||
obj.msg("".join(target_result).strip())
|
||||
|
||||
|
||||
class CmdWall(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -550,7 +551,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Admin"
|
||||
|
||||
def func(self):
|
||||
"Implements command"
|
||||
"""Implements command"""
|
||||
if not self.args:
|
||||
self.caller.msg("Usage: @wall <message>")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -29,15 +29,13 @@ from evennia.utils import logger, utils
|
|||
_RE_COMMENT = re.compile(r"^#.*?$", re.MULTILINE + re.DOTALL)
|
||||
_RE_CODE_START = re.compile(r"^# batchcode code:", re.MULTILINE)
|
||||
_COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
#from evennia.commands.default.muxcommand import _COMMAND_DEFAULT_CLASS
|
||||
|
||||
# limit symbols for API inclusion
|
||||
__all__ = ("CmdBatchCommands", "CmdBatchCode")
|
||||
|
||||
_HEADER_WIDTH = 70
|
||||
_UTF8_ERROR = \
|
||||
"""
|
||||
{rDecode error in '%s'.{n
|
||||
_UTF8_ERROR = """
|
||||
|rDecode error in '%s'.|n
|
||||
|
||||
This file contains non-ascii character(s). This is common if you
|
||||
wrote some input in a language that has more letters and special
|
||||
|
|
@ -83,9 +81,9 @@ print "leaving run ..."
|
|||
"""
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# Helper functions
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def format_header(caller, entry):
|
||||
"""
|
||||
|
|
@ -100,7 +98,7 @@ def format_header(caller, entry):
|
|||
header = utils.crop(entry, width=width)
|
||||
ptr = caller.ndb.batch_stackptr + 1
|
||||
stacklen = len(caller.ndb.batch_stack)
|
||||
header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header)
|
||||
header = "|w%02i/%02i|G: %s|n" % (ptr, stacklen, header)
|
||||
# add extra space to the side for padding.
|
||||
header = "%s%s" % (header, " " * (width - len(header)))
|
||||
header = header.replace('\n', '\\n')
|
||||
|
|
@ -114,7 +112,7 @@ def format_code(entry):
|
|||
"""
|
||||
code = ""
|
||||
for line in entry.split('\n'):
|
||||
code += "\n{G>>>{n %s" % line
|
||||
code += "\n|G>>>|n %s" % line
|
||||
return code.strip()
|
||||
|
||||
|
||||
|
|
@ -164,9 +162,9 @@ def step_pointer(caller, step=1):
|
|||
stack = caller.ndb.batch_stack
|
||||
nstack = len(stack)
|
||||
if ptr + step <= 0:
|
||||
caller.msg("{RBeginning of batch file.")
|
||||
caller.msg("|RBeginning of batch file.")
|
||||
if ptr + step >= nstack:
|
||||
caller.msg("{REnd of batch file.")
|
||||
caller.msg("|REnd of batch file.")
|
||||
caller.ndb.batch_stackptr = max(0, min(nstack - 1, ptr + step))
|
||||
|
||||
|
||||
|
|
@ -186,10 +184,10 @@ def show_curr(caller, showall=False):
|
|||
|
||||
string = format_header(caller, entry)
|
||||
codeall = entry.strip()
|
||||
string += "{G(hh for help)"
|
||||
string += "|G(hh for help)"
|
||||
if showall:
|
||||
for line in codeall.split('\n'):
|
||||
string += "\n{G|{n %s" % line
|
||||
string += "\n|G||n %s" % line
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
|
|
@ -217,9 +215,9 @@ def purge_processor(caller):
|
|||
|
||||
caller.scripts.validate() # this will purge interactive mode
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# main access commands
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -243,7 +241,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"Starts the processor."
|
||||
"""Starts the processor."""
|
||||
|
||||
caller = self.caller
|
||||
|
||||
|
|
@ -253,7 +251,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
python_path = self.args
|
||||
|
||||
#parse indata file
|
||||
# parse indata file
|
||||
|
||||
try:
|
||||
commands = BATCHCMD.parse_file(python_path)
|
||||
|
|
@ -289,7 +287,8 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
|||
caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path)
|
||||
show_curr(caller)
|
||||
else:
|
||||
caller.msg("Running Batch-command processor - Automatic mode for %s (this might take some time) ..." % python_path)
|
||||
caller.msg("Running Batch-command processor - Automatic mode for %s (this might take some time) ..."
|
||||
% python_path)
|
||||
|
||||
procpool = False
|
||||
if "PythonProcPool" in utils.server_services():
|
||||
|
|
@ -301,11 +300,11 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
|||
if procpool:
|
||||
# run in parallel process
|
||||
def callback(r):
|
||||
caller.msg(" {GBatchfile '%s' applied." % python_path)
|
||||
caller.msg(" |GBatchfile '%s' applied." % python_path)
|
||||
purge_processor(caller)
|
||||
|
||||
def errback(e):
|
||||
caller.msg(" {RError from processor: '%s'" % e)
|
||||
caller.msg(" |RError from processor: '%s'" % e)
|
||||
purge_processor(caller)
|
||||
|
||||
utils.run_async(_PROCPOOL_BATCHCMD_SOURCE,
|
||||
|
|
@ -315,7 +314,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
|||
at_err=errback)
|
||||
else:
|
||||
# run in-process (might block)
|
||||
for inum in range(len(commands)):
|
||||
for _ in range(len(commands)):
|
||||
# loop through the batch file
|
||||
if not batch_cmd_exec(caller):
|
||||
return
|
||||
|
|
@ -323,7 +322,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
|
|||
# clean out the safety cmdset and clean out all other
|
||||
# temporary attrs.
|
||||
string = " Batchfile '%s' applied." % python_path
|
||||
caller.msg("{G%s" % string)
|
||||
caller.msg("|G%s" % string)
|
||||
purge_processor(caller)
|
||||
|
||||
|
||||
|
|
@ -352,7 +351,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
|
|||
help_category = "Building"
|
||||
|
||||
def func(self):
|
||||
"Starts the processor."
|
||||
"""Starts the processor."""
|
||||
|
||||
caller = self.caller
|
||||
|
||||
|
|
@ -363,7 +362,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
|
|||
python_path = self.args
|
||||
debug = 'debug' in self.switches
|
||||
|
||||
#parse indata file
|
||||
# parse indata file
|
||||
try:
|
||||
codes = BATCHCODE.parse_file(python_path)
|
||||
except UnicodeDecodeError as err:
|
||||
|
|
@ -410,11 +409,11 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
|
|||
if procpool:
|
||||
# run in parallel process
|
||||
def callback(r):
|
||||
caller.msg(" {GBatchfile '%s' applied." % python_path)
|
||||
caller.msg(" |GBatchfile '%s' applied." % python_path)
|
||||
purge_processor(caller)
|
||||
|
||||
def errback(e):
|
||||
caller.msg(" {RError from processor: '%s'" % e)
|
||||
caller.msg(" |RError from processor: '%s'" % e)
|
||||
purge_processor(caller)
|
||||
utils.run_async(_PROCPOOL_BATCHCODE_SOURCE,
|
||||
codes=codes,
|
||||
|
|
@ -423,7 +422,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
|
|||
at_err=errback)
|
||||
else:
|
||||
# un in-process (will block)
|
||||
for inum in range(len(codes)):
|
||||
for _ in range(len(codes)):
|
||||
# loop through the batch file
|
||||
if not batch_code_exec(caller):
|
||||
return
|
||||
|
|
@ -431,14 +430,14 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
|
|||
# clean out the safety cmdset and clean out all other
|
||||
# temporary attrs.
|
||||
string = " Batchfile '%s' applied." % python_path
|
||||
caller.msg("{G%s" % string)
|
||||
caller.msg("|G%s" % string)
|
||||
purge_processor(caller)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# State-commands for the interactive batch processor modes
|
||||
# (these are the same for both processors)
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
class CmdStateAbort(_COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
|
|
@ -453,7 +452,7 @@ class CmdStateAbort(_COMMAND_DEFAULT_CLASS):
|
|||
locks = "cmd:perm(batchcommands)"
|
||||
|
||||
def func(self):
|
||||
"Exit back to default."
|
||||
"""Exit back to default."""
|
||||
purge_processor(self.caller)
|
||||
self.caller.msg("Exited processor and reset out active cmdset back to the default one.")
|
||||
|
||||
|
|
@ -472,6 +471,7 @@ class CmdStateLL(_COMMAND_DEFAULT_CLASS):
|
|||
def func(self):
|
||||
show_curr(self.caller, showall=True)
|
||||
|
||||
|
||||
class CmdStatePP(_COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
pp
|
||||
|
|
@ -644,7 +644,7 @@ class CmdStateSS(_COMMAND_DEFAULT_CLASS):
|
|||
else:
|
||||
step = 1
|
||||
|
||||
for istep in range(step):
|
||||
for _ in range(step):
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
batch_code_exec(caller)
|
||||
else:
|
||||
|
|
@ -673,7 +673,7 @@ class CmdStateSL(_COMMAND_DEFAULT_CLASS):
|
|||
else:
|
||||
step = 1
|
||||
|
||||
for istep in range(step):
|
||||
for _ in range(step):
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
batch_code_exec(caller)
|
||||
else:
|
||||
|
|
@ -699,7 +699,7 @@ class CmdStateCC(_COMMAND_DEFAULT_CLASS):
|
|||
ptr = caller.ndb.batch_stackptr
|
||||
step = nstack - ptr
|
||||
|
||||
for istep in range(step):
|
||||
for _ in range(step):
|
||||
if caller.ndb.batch_batchmode == "batch_code":
|
||||
batch_code_exec(caller)
|
||||
else:
|
||||
|
|
@ -775,7 +775,7 @@ class CmdStateQQ(_COMMAND_DEFAULT_CLASS):
|
|||
|
||||
|
||||
class CmdStateHH(_COMMAND_DEFAULT_CLASS):
|
||||
"Help command"
|
||||
"""Help command"""
|
||||
|
||||
key = "hh"
|
||||
help_category = "BatchProcess"
|
||||
|
|
@ -810,12 +810,12 @@ class CmdStateHH(_COMMAND_DEFAULT_CLASS):
|
|||
self.caller.msg(string)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Defining the cmdsets for the interactive batchprocessor
|
||||
# mode (same for both processors)
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
class BatchSafeCmdSet(CmdSet):
|
||||
"""
|
||||
|
|
@ -827,7 +827,7 @@ class BatchSafeCmdSet(CmdSet):
|
|||
priority = 150 # override other cmdsets.
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"Init the cmdset"
|
||||
"""Init the cmdset"""
|
||||
self.add(CmdStateAbort())
|
||||
|
||||
|
||||
|
|
@ -839,7 +839,7 @@ class BatchInteractiveCmdSet(CmdSet):
|
|||
priority = 104
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"init the cmdset"
|
||||
"""init the cmdset"""
|
||||
self.add(CmdStateAbort())
|
||||
self.add(CmdStateLL())
|
||||
self.add(CmdStatePP())
|
||||
|
|
|
|||
|
|
@ -591,9 +591,11 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
|
|||
if not obj:
|
||||
return
|
||||
desc = self.args
|
||||
|
||||
obj.db.desc = desc
|
||||
caller.msg("The description was set on %s." % obj.get_display_name(caller))
|
||||
if obj.access(caller, "edit"):
|
||||
obj.db.desc = desc
|
||||
caller.msg("The description was set on %s." % obj.get_display_name(caller))
|
||||
else:
|
||||
caller.msg("You don't have permission to edit the description of %s." % obj.key)
|
||||
|
||||
|
||||
class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -1138,7 +1140,7 @@ class CmdName(ObjManipCommand):
|
|||
caller.msg("No name defined!")
|
||||
return
|
||||
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
|
||||
caller.mgs("You don't have right to edit this player %s." % obj)
|
||||
caller.msg("You don't have right to edit this player %s." % obj)
|
||||
return
|
||||
obj.username = newname
|
||||
obj.save()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ class CharacterCmdSet(CmdSet):
|
|||
self.add(general.CmdDrop())
|
||||
self.add(general.CmdGive())
|
||||
self.add(general.CmdSay())
|
||||
self.add(general.CmdWhisper())
|
||||
self.add(general.CmdAccess())
|
||||
|
||||
# The help system
|
||||
|
|
|
|||
|
|
@ -60,16 +60,17 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
|
|||
"""
|
||||
Handle the looking.
|
||||
"""
|
||||
caller = self.caller
|
||||
if not self.args:
|
||||
target = self.caller.location
|
||||
target = caller.location
|
||||
if not target:
|
||||
self.caller.msg("You have no location to look at!")
|
||||
caller.msg("You have no location to look at!")
|
||||
return
|
||||
else:
|
||||
target = self.caller.search(self.args)
|
||||
target = caller.search(self.args, use_dbref=caller.check_permstring("Builders"))
|
||||
if not target:
|
||||
return
|
||||
self.msg(self.caller.at_look(target))
|
||||
self.msg(caller.at_look(target))
|
||||
|
||||
|
||||
class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -175,10 +176,9 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
|||
errstring += "Not a valid nick index."
|
||||
else:
|
||||
errstring += "Nick not found."
|
||||
|
||||
if "delete" in switches or "del" in switches:
|
||||
# clear the nick
|
||||
if caller.nicks.has(old_nickstring, category=nicktype):
|
||||
if old_nickstring and caller.nicks.has(old_nickstring, category=nicktype):
|
||||
caller.nicks.remove(old_nickstring, category=nicktype)
|
||||
string += "\nNick removed: '|w%s|n' -> |w%s|n." % (old_nickstring, old_replstring)
|
||||
else:
|
||||
|
|
@ -354,6 +354,8 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg("You give %s to %s." % (to_give.key, target.key))
|
||||
to_give.move_to(target, quiet=True)
|
||||
target.msg("%s gives you %s." % (caller.key, to_give.key))
|
||||
# Call the object script's at_give() method.
|
||||
to_give.at_give(caller, target)
|
||||
|
||||
|
||||
class CmdSetDesc(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -415,7 +417,50 @@ class CmdSay(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# Build the string to emit to neighbors.
|
||||
emit_string = '%s says, "%s|n"' % (caller.name, speech)
|
||||
caller.location.msg_contents(emit_string, exclude=caller, from_obj=caller)
|
||||
caller.location.msg_contents(text=(emit_string, {"type": "say"}),
|
||||
exclude=caller, from_obj=caller)
|
||||
|
||||
|
||||
class CmdWhisper(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
Speak privately as your character to another
|
||||
|
||||
Usage:
|
||||
whisper <player> = <message>
|
||||
|
||||
Talk privately to those in your current location, without
|
||||
others being informed.
|
||||
"""
|
||||
|
||||
key = "whisper"
|
||||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"""Run the whisper command"""
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if not self.lhs or not self.rhs:
|
||||
caller.msg("Usage: whisper <player> = <message>")
|
||||
return
|
||||
|
||||
receiver = caller.search(self.lhs)
|
||||
|
||||
if not receiver:
|
||||
return
|
||||
|
||||
if caller == receiver:
|
||||
caller.msg("You can't whisper to yourself.")
|
||||
return
|
||||
|
||||
speech = self.rhs
|
||||
|
||||
# Feedback for the object doing the talking.
|
||||
caller.msg('You whisper to %s, "%s|n"' % (receiver.key, speech))
|
||||
|
||||
# Build the string to emit to receiver.
|
||||
emit_string = '%s whispers, "%s|n"' % (caller.name, speech)
|
||||
receiver.msg(text=(emit_string, {"type": "whisper"}), from_obj=caller)
|
||||
|
||||
|
||||
class CmdPose(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -458,7 +503,8 @@ class CmdPose(COMMAND_DEFAULT_CLASS):
|
|||
self.caller.msg(msg)
|
||||
else:
|
||||
msg = "%s%s" % (self.caller.name, self.args)
|
||||
self.caller.location.msg_contents(msg, from_obj=self.caller)
|
||||
self.caller.location.msg_contents(text=(msg, {"type": "pose"}),
|
||||
from_obj=self.caller)
|
||||
|
||||
|
||||
class CmdAccess(COMMAND_DEFAULT_CLASS):
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class CmdHelp(Command):
|
|||
|
||||
if self.session.protocol_key in ("websocket", "ajax/comet"):
|
||||
try:
|
||||
options = self.caller.player.db._saved_webclient_options
|
||||
options = self.player.db._saved_webclient_options
|
||||
if options and options["helppopup"]:
|
||||
usemore = False
|
||||
except KeyError:
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from evennia.commands.command import Command
|
|||
# limit symbol import for API
|
||||
__all__ = ("MuxCommand", "MuxPlayerCommand")
|
||||
|
||||
|
||||
class MuxCommand(Command):
|
||||
"""
|
||||
This sets up the basis for a MUX command. The idea
|
||||
|
|
@ -98,7 +99,7 @@ class MuxCommand(Command):
|
|||
|
||||
# split out switches
|
||||
switches = []
|
||||
if args and len(args) > 1 and args[0] == "/":
|
||||
if args and len(args) > 1 and raw[0] == "/":
|
||||
# we have a switch, or a set of switches. These end with a space.
|
||||
switches = args[1:].split(None, 1)
|
||||
if len(switches) > 1:
|
||||
|
|
@ -150,33 +151,32 @@ class MuxCommand(Command):
|
|||
"""
|
||||
# a simple test command to show the available properties
|
||||
string = "-" * 50
|
||||
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
|
||||
string += "\n|w%s|n - Command variables from evennia:\n" % self.key
|
||||
string += "-" * 50
|
||||
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
|
||||
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
|
||||
string += "cmd locks (self.locks): {w%s{n\n" % self.locks
|
||||
string += "help category (self.help_category): {w%s{n\n" % self.help_category
|
||||
string += "object calling (self.caller): {w%s{n\n" % self.caller
|
||||
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
|
||||
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
|
||||
string += "\nname of cmd (self.key): |w%s|n\n" % self.key
|
||||
string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases
|
||||
string += "cmd locks (self.locks): |w%s|n\n" % self.locks
|
||||
string += "help category (self.help_category): |w%s|n\n" % self.help_category
|
||||
string += "object calling (self.caller): |w%s|n\n" % self.caller
|
||||
string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj
|
||||
string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring
|
||||
# show cmdset.key instead of cmdset to shorten output
|
||||
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
|
||||
|
||||
|
||||
string += utils.fill("current cmdset (self.cmdset): |w%s|n\n" % self.cmdset)
|
||||
string += "\n" + "-" * 50
|
||||
string += "\nVariables from MuxCommand baseclass\n"
|
||||
string += "\nVariables from MuxCommand baseclass\n"
|
||||
string += "-" * 50
|
||||
string += "\nraw argument (self.raw): {w%s{n \n" % self.raw
|
||||
string += "cmd args (self.args): {w%s{n\n" % self.args
|
||||
string += "cmd switches (self.switches): {w%s{n\n" % self.switches
|
||||
string += "space-separated arg list (self.arglist): {w%s{n\n" % self.arglist
|
||||
string += "lhs, left-hand side of '=' (self.lhs): {w%s{n\n" % self.lhs
|
||||
string += "lhs, comma separated (self.lhslist): {w%s{n\n" % self.lhslist
|
||||
string += "rhs, right-hand side of '=' (self.rhs): {w%s{n\n" % self.rhs
|
||||
string += "rhs, comma separated (self.rhslist): {w%s{n\n" % self.rhslist
|
||||
string += "\nraw argument (self.raw): |w%s|n \n" % self.raw
|
||||
string += "cmd args (self.args): |w%s|n\n" % self.args
|
||||
string += "cmd switches (self.switches): |w%s|n\n" % self.switches
|
||||
string += "space-separated arg list (self.arglist): |w%s|n\n" % self.arglist
|
||||
string += "lhs, left-hand side of '=' (self.lhs): |w%s|n\n" % self.lhs
|
||||
string += "lhs, comma separated (self.lhslist): |w%s|n\n" % self.lhslist
|
||||
string += "rhs, right-hand side of '=' (self.rhs): |w%s|n\n" % self.rhs
|
||||
string += "rhs, comma separated (self.rhslist): |w%s|n\n" % self.rhslist
|
||||
string += "-" * 50
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
class MuxPlayerCommand(MuxCommand):
|
||||
"""
|
||||
This is an on-Player version of the MuxCommand. Since these commands sit
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ The property self.character can be used to access the character when
|
|||
these commands are triggered with a connected character (such as the
|
||||
case of the @ooc command), it is None if we are OOC.
|
||||
|
||||
Note that under MULTISESSION_MODE > 2, Player- commands should use
|
||||
Note that under MULTISESSION_MODE > 2, Player commands should use
|
||||
self.msg() and similar methods to reroute returns to the correct
|
||||
method. Otherwise all text will be returned to all connected sessions.
|
||||
|
||||
|
|
@ -23,7 +23,7 @@ from builtins import range
|
|||
import time
|
||||
from django.conf import settings
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.utils import utils, create, search, prettytable, evtable
|
||||
from evennia.utils import utils, create, search, evtable
|
||||
|
||||
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
|
|||
"""
|
||||
|
||||
def parse(self):
|
||||
"Custom parsing"
|
||||
"""Custom parsing"""
|
||||
|
||||
super(MuxPlayerLookCommand, self).parse()
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
|
|||
# store playable property
|
||||
if self.args:
|
||||
self.playable = dict((utils.to_str(char.key.lower()), char)
|
||||
for char in playable).get(self.args.lower(), None)
|
||||
for char in playable).get(self.args.lower(), None)
|
||||
else:
|
||||
self.playable = playable
|
||||
|
||||
|
|
@ -83,10 +83,10 @@ class CmdOOCLook(MuxPlayerLookCommand):
|
|||
Look in the ooc state.
|
||||
"""
|
||||
|
||||
#This is an OOC version of the look command. Since a
|
||||
#Player doesn't have an in-game existence, there is no
|
||||
#concept of location or "self". If we are controlling
|
||||
#a character, pass control over to normal look.
|
||||
# This is an OOC version of the look command. Since a
|
||||
# Player doesn't have an in-game existence, there is no
|
||||
# concept of location or "self". If we are controlling
|
||||
# a character, pass control over to normal look.
|
||||
|
||||
key = "look"
|
||||
aliases = ["l", "ls"]
|
||||
|
|
@ -97,11 +97,11 @@ class CmdOOCLook(MuxPlayerLookCommand):
|
|||
player_caller = True
|
||||
|
||||
def func(self):
|
||||
"implement the ooc look command"
|
||||
"""implement the ooc look command"""
|
||||
|
||||
if _MULTISESSION_MODE < 2:
|
||||
# only one character allowed
|
||||
self.msg("You are out-of-character (OOC).\nUse {w@ic{n to get back into the game.")
|
||||
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
|
||||
return
|
||||
|
||||
# call on-player look helper method
|
||||
|
|
@ -128,7 +128,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
|||
player_caller = True
|
||||
|
||||
def func(self):
|
||||
"create the new character"
|
||||
"""create the new character"""
|
||||
player = self.player
|
||||
if not self.args:
|
||||
self.msg("Usage: @charcreate <charname> [= description]")
|
||||
|
|
@ -139,8 +139,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
|||
charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
|
||||
|
||||
if not player.is_superuser and \
|
||||
(player.db._playable_characters and
|
||||
len(player.db._playable_characters) >= charmax):
|
||||
(player.db._playable_characters and
|
||||
len(player.db._playable_characters) >= charmax):
|
||||
self.msg("You may only create a maximum of %i characters." % charmax)
|
||||
return
|
||||
from evennia.objects.models import ObjectDB
|
||||
|
|
@ -150,15 +150,13 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
|||
# check if this Character already exists. Note that we are only
|
||||
# searching the base character typeclass here, not any child
|
||||
# classes.
|
||||
self.msg("{rA character named '{w%s{r' already exists.{n" % key)
|
||||
self.msg("|rA character named '|w%s|r' already exists.|n" % key)
|
||||
return
|
||||
|
||||
# create the character
|
||||
start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
|
||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
||||
|
||||
|
||||
new_character = create.create_object(typeclass, key=key,
|
||||
location=start_location,
|
||||
home=default_home,
|
||||
|
|
@ -171,7 +169,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
|||
new_character.db.desc = desc
|
||||
elif not new_character.db.desc:
|
||||
new_character.db.desc = "This is a Player."
|
||||
self.msg("Created new character %s. Use {w@ic %s{n to enter the game as this character." % (new_character.key, new_character.key))
|
||||
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
|
||||
% (new_character.key, new_character.key))
|
||||
|
||||
|
||||
class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -188,7 +187,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "General"
|
||||
|
||||
def func(self):
|
||||
"delete the character"
|
||||
"""delete the character"""
|
||||
player = self.player
|
||||
|
||||
if not self.args:
|
||||
|
|
@ -196,23 +195,23 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
|
||||
# use the playable_characters list to search
|
||||
match = [char for char in utils.make_iter(player.db._playable_characters) if char.key.lower() == self.args.lower()]
|
||||
match = [char for char in utils.make_iter(player.db._playable_characters)
|
||||
if char.key.lower() == self.args.lower()]
|
||||
if not match:
|
||||
self.msg("You have no such character to delete.")
|
||||
return
|
||||
elif len(match) > 1:
|
||||
self.msg("Aborting - there are two characters with the same name. Ask an admin to delete the right one.")
|
||||
return
|
||||
else: # one match
|
||||
else: # one match
|
||||
from evennia.utils.evmenu import get_input
|
||||
|
||||
def _callback(caller, prompt, result):
|
||||
def _callback(caller, callback_prompt, result):
|
||||
if result.lower() == "yes":
|
||||
# only take action
|
||||
delobj = caller.ndb._char_to_delete
|
||||
key = delobj.key
|
||||
caller.db._playable_characters = [char for char
|
||||
in caller.db._playable_characters if char != delobj]
|
||||
caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj]
|
||||
delobj.delete()
|
||||
self.msg("Character '%s' was permanently deleted." % key)
|
||||
else:
|
||||
|
|
@ -272,7 +271,8 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
|
|||
self.msg("That is not a valid character choice.")
|
||||
return
|
||||
if len(new_character) > 1:
|
||||
self.msg("Multiple targets with the same name:\n %s" % ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character))
|
||||
self.msg("Multiple targets with the same name:\n %s"
|
||||
% ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character))
|
||||
return
|
||||
else:
|
||||
new_character = new_character[0]
|
||||
|
|
@ -280,7 +280,7 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
|
|||
player.puppet_object(session, new_character)
|
||||
player.db._last_puppet = new_character
|
||||
except RuntimeError as exc:
|
||||
self.msg("{rYou cannot become {C%s{n: %s" % (new_character.name, exc))
|
||||
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
|
||||
|
||||
|
||||
# note that this is inheriting from MuxPlayerLookCommand,
|
||||
|
|
@ -306,7 +306,7 @@ class CmdOOC(MuxPlayerLookCommand):
|
|||
player_caller = True
|
||||
|
||||
def func(self):
|
||||
"Implement function"
|
||||
"""Implement function"""
|
||||
|
||||
player = self.player
|
||||
session = self.session
|
||||
|
|
@ -322,17 +322,18 @@ class CmdOOC(MuxPlayerLookCommand):
|
|||
# disconnect
|
||||
try:
|
||||
player.unpuppet_object(session)
|
||||
self.msg("\n{GYou go OOC.{n\n")
|
||||
self.msg("\n|GYou go OOC.|n\n")
|
||||
|
||||
if _MULTISESSION_MODE < 2:
|
||||
# only one character allowed
|
||||
self.msg("You are out-of-character (OOC).\nUse {w@ic{n to get back into the game.")
|
||||
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
|
||||
return
|
||||
|
||||
self.msg(player.at_look(target=self.playable, session=session))
|
||||
|
||||
except RuntimeError as exc:
|
||||
self.msg("{rCould not unpuppet from {c%s{n: %s" % (old_char, exc))
|
||||
self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc))
|
||||
|
||||
|
||||
class CmdSessions(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
|
|
@ -352,23 +353,21 @@ class CmdSessions(COMMAND_DEFAULT_CLASS):
|
|||
player_caller = True
|
||||
|
||||
def func(self):
|
||||
"Implement function"
|
||||
"""Implement function"""
|
||||
player = self.player
|
||||
sessions = player.sessions.all()
|
||||
|
||||
table = prettytable.PrettyTable(["{wsessid",
|
||||
"{wprotocol",
|
||||
"{whost",
|
||||
"{wpuppet/character",
|
||||
"{wlocation"])
|
||||
table = evtable.EvTable("|wsessid",
|
||||
"|wprotocol",
|
||||
"|whost",
|
||||
"|wpuppet/character",
|
||||
"|wlocation")
|
||||
for sess in sorted(sessions, key=lambda x: x.sessid):
|
||||
char = player.get_puppet(sess)
|
||||
table.add_row([str(sess.sessid), str(sess.protocol_key),
|
||||
type(sess.address) == tuple and sess.address[0] or sess.address,
|
||||
char and str(char) or "None",
|
||||
char and str(char.location) or "N/A"])
|
||||
string = "{wYour current session(s):{n\n%s" % table
|
||||
self.msg(string)
|
||||
table.add_row(str(sess.sessid), str(sess.protocol_key),
|
||||
type(sess.address) == tuple and sess.address[0] or sess.address,
|
||||
char and str(char) or "None",
|
||||
char and str(char.location) or "N/A")
|
||||
self.msg("|wYour current session(s):|n\n%s" % table)
|
||||
|
||||
|
||||
class CmdWho(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -408,45 +407,45 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
|
|||
nplayers = (SESSIONS.player_count())
|
||||
if show_session_data:
|
||||
# privileged info
|
||||
table = prettytable.PrettyTable(["{wPlayer Name",
|
||||
"{wOn for",
|
||||
"{wIdle",
|
||||
"{wPuppeting",
|
||||
"{wRoom",
|
||||
"{wCmds",
|
||||
"{wProtocol",
|
||||
"{wHost"])
|
||||
for session in session_list:
|
||||
if not session.logged_in: continue
|
||||
delta_cmd = time.time() - session.cmd_last_visible
|
||||
delta_conn = time.time() - session.conn_time
|
||||
player = session.get_player()
|
||||
puppet = session.get_puppet()
|
||||
location = puppet.location.key if puppet and puppet.location else "None"
|
||||
table.add_row([utils.crop(player.name, width=25),
|
||||
utils.time_format(delta_conn, 0),
|
||||
utils.time_format(delta_cmd, 1),
|
||||
utils.crop(puppet.key if puppet else "None", width=25),
|
||||
utils.crop(location, width=25),
|
||||
session.cmd_total,
|
||||
session.protocol_key,
|
||||
isinstance(session.address, tuple) and session.address[0] or session.address])
|
||||
else:
|
||||
# unprivileged
|
||||
table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"])
|
||||
table = evtable.EvTable("|wPlayer Name",
|
||||
"|wOn for",
|
||||
"|wIdle",
|
||||
"|wPuppeting",
|
||||
"|wRoom",
|
||||
"|wCmds",
|
||||
"|wProtocol",
|
||||
"|wHost")
|
||||
for session in session_list:
|
||||
if not session.logged_in:
|
||||
continue
|
||||
delta_cmd = time.time() - session.cmd_last_visible
|
||||
delta_conn = time.time() - session.conn_time
|
||||
player = session.get_player()
|
||||
table.add_row([utils.crop(player.key, width=25),
|
||||
utils.time_format(delta_conn, 0),
|
||||
utils.time_format(delta_cmd, 1)])
|
||||
|
||||
isone = nplayers == 1
|
||||
string = "{wPlayers:{n\n%s\n%s unique account%s logged in." % (table, "One" if isone else nplayers, "" if isone else "s")
|
||||
self.msg(string)
|
||||
puppet = session.get_puppet()
|
||||
location = puppet.location.key if puppet and puppet.location else "None"
|
||||
table.add_row(utils.crop(player.name, width=25),
|
||||
utils.time_format(delta_conn, 0),
|
||||
utils.time_format(delta_cmd, 1),
|
||||
utils.crop(puppet.key if puppet else "None", width=25),
|
||||
utils.crop(location, width=25),
|
||||
session.cmd_total,
|
||||
session.protocol_key,
|
||||
isinstance(session.address, tuple) and session.address[0] or session.address)
|
||||
else:
|
||||
# unprivileged
|
||||
table = evtable.EvTable("|wPlayer name", "|wOn for", "|wIdle")
|
||||
for session in session_list:
|
||||
if not session.logged_in:
|
||||
continue
|
||||
delta_cmd = time.time() - session.cmd_last_visible
|
||||
delta_conn = time.time() - session.conn_time
|
||||
player = session.get_player()
|
||||
table.add_row(utils.crop(player.key, width=25),
|
||||
utils.time_format(delta_conn, 0),
|
||||
utils.time_format(delta_cmd, 1))
|
||||
is_one = nplayers == 1
|
||||
self.msg("|wPlayers:|n\n%s\n%s unique account%s logged in."
|
||||
% (table, "One" if is_one else nplayers, "" if is_one else "s"))
|
||||
|
||||
|
||||
class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -489,13 +488,13 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
|||
if "save" in self.switches:
|
||||
# save all options
|
||||
self.caller.db._saved_protocol_flags = flags
|
||||
self.msg("{gSaved all options. Use @option/clear to remove.{n")
|
||||
self.msg("|gSaved all options. Use @option/clear to remove.|n")
|
||||
if "clear" in self.switches:
|
||||
# clear all saves
|
||||
self.caller.db._saved_protocol_flags = {}
|
||||
self.msg("{gCleared all saved options.")
|
||||
self.msg("|gCleared all saved options.")
|
||||
|
||||
options = dict(flags) # make a copy of the flag dict
|
||||
options = dict(flags) # make a copy of the flag dict
|
||||
saved_options = dict(self.caller.attributes.get("_saved_protocol_flags", default={}))
|
||||
|
||||
if "SCREENWIDTH" in options:
|
||||
|
|
@ -503,7 +502,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
|||
options["SCREENWIDTH"] = options["SCREENWIDTH"][0]
|
||||
else:
|
||||
options["SCREENWIDTH"] = " \n".join("%s : %s" % (screenid, size)
|
||||
for screenid, size in options["SCREENWIDTH"].iteritems())
|
||||
for screenid, size in options["SCREENWIDTH"].iteritems())
|
||||
if "SCREENHEIGHT" in options:
|
||||
if len(options["SCREENHEIGHT"]) == 1:
|
||||
options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0]
|
||||
|
|
@ -521,8 +520,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
|||
changed = "|y*|n" if key in saved_options and flags[key] != saved_options[key] else ""
|
||||
row.append("%s%s" % (saved, changed))
|
||||
table.add_row(*row)
|
||||
|
||||
self.msg("{wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table))
|
||||
self.msg("|wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table))
|
||||
|
||||
return
|
||||
|
||||
|
|
@ -532,30 +530,30 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# Try to assign new values
|
||||
|
||||
def validate_encoding(val):
|
||||
def validate_encoding(new_encoding):
|
||||
# helper: change encoding
|
||||
try:
|
||||
utils.to_str(utils.to_unicode("test-string"), encoding=val)
|
||||
utils.to_str(utils.to_unicode("test-string"), encoding=new_encoding)
|
||||
except LookupError:
|
||||
raise RuntimeError("The encoding '|w%s|n' is invalid. " % val)
|
||||
raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding)
|
||||
return val
|
||||
|
||||
def validate_size(val):
|
||||
return {0: int(val)}
|
||||
def validate_size(new_size):
|
||||
return {0: int(new_size)}
|
||||
|
||||
def validate_bool(val):
|
||||
return True if val.lower() in ("true", "on", "1") else False
|
||||
def validate_bool(new_bool):
|
||||
return True if new_bool.lower() in ("true", "on", "1") else False
|
||||
|
||||
def update(name, val, validator):
|
||||
def update(new_name, new_val, validator):
|
||||
# helper: update property and report errors
|
||||
try:
|
||||
old_val = flags[name]
|
||||
new_val = validator(val)
|
||||
flags[name] = new_val
|
||||
self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (name, old_val, new_val))
|
||||
return {name: new_val}
|
||||
old_val = flags.get(new_name, False)
|
||||
new_val = validator(new_val)
|
||||
flags[new_name] = new_val
|
||||
self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (new_name, old_val, new_val))
|
||||
return {new_name: new_val}
|
||||
except Exception, err:
|
||||
self.msg("|rCould not set option |w%s|r:|n %s" % (name, err))
|
||||
self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err))
|
||||
return False
|
||||
|
||||
validators = {"ANSI": validate_bool,
|
||||
|
|
@ -590,16 +588,15 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
|||
saved_options.update(optiondict)
|
||||
self.player.attributes.add("_saved_protocol_flags", saved_options)
|
||||
for key in optiondict:
|
||||
self.msg("{gSaved option %s.{n" % key)
|
||||
self.msg("|gSaved option %s.|n" % key)
|
||||
if "clear" in self.switches:
|
||||
# clear this save
|
||||
for key in optiondict:
|
||||
self.player.attributes.get("_saved_protocol_flags", {}).pop(key, None)
|
||||
self.msg("{gCleared saved %s." % key)
|
||||
|
||||
|
||||
self.msg("|gCleared saved %s." % key)
|
||||
self.session.update_flags(**optiondict)
|
||||
|
||||
|
||||
class CmdPassword(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
change your password
|
||||
|
|
@ -616,14 +613,14 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
|
|||
player_caller = True
|
||||
|
||||
def func(self):
|
||||
"hook function."
|
||||
"""hook function."""
|
||||
|
||||
player = self.player
|
||||
if not self.rhs:
|
||||
self.msg("Usage: @password <oldpass> = <newpass>")
|
||||
return
|
||||
oldpass = self.lhslist[0] # this is already stripped by parse()
|
||||
newpass = self.rhslist[0] # ''
|
||||
oldpass = self.lhslist[0] # Both of these are
|
||||
newpass = self.rhslist[0] # already stripped by parse()
|
||||
if not player.check_password(oldpass):
|
||||
self.msg("The specified old password isn't correct.")
|
||||
elif len(newpass) < 3:
|
||||
|
|
@ -654,11 +651,11 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
|
|||
player_caller = True
|
||||
|
||||
def func(self):
|
||||
"hook function"
|
||||
"""hook function"""
|
||||
player = self.player
|
||||
|
||||
if 'all' in self.switches:
|
||||
player.msg("{RQuitting{n all sessions. Hope to see you soon again.", session=self.session)
|
||||
player.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session)
|
||||
for session in player.sessions.all():
|
||||
player.disconnect_session_from_player(session)
|
||||
else:
|
||||
|
|
@ -673,7 +670,6 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
|
|||
player.disconnect_session_from_player(self.session)
|
||||
|
||||
|
||||
|
||||
class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
testing which colors your client support
|
||||
|
|
@ -695,23 +691,23 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
|||
player_caller = True
|
||||
|
||||
def table_format(self, table):
|
||||
"""
|
||||
Helper method to format the ansi/xterm256 tables.
|
||||
Takes a table of columns [[val,val,...],[val,val,...],...]
|
||||
"""
|
||||
if not table:
|
||||
return [[]]
|
||||
"""
|
||||
Helper method to format the ansi/xterm256 tables.
|
||||
Takes a table of columns [[val,val,...],[val,val,...],...]
|
||||
"""
|
||||
if not table:
|
||||
return [[]]
|
||||
|
||||
extra_space = 1
|
||||
max_widths = [max([len(str(val)) for val in col]) for col in table]
|
||||
ftable = []
|
||||
for irow in range(len(table[0])):
|
||||
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
|
||||
for icol, col in enumerate(table)])
|
||||
return ftable
|
||||
extra_space = 1
|
||||
max_widths = [max([len(str(val)) for val in col]) for col in table]
|
||||
ftable = []
|
||||
for irow in range(len(table[0])):
|
||||
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " *
|
||||
extra_space for icol, col in enumerate(table)])
|
||||
return ftable
|
||||
|
||||
def func(self):
|
||||
"Show color tables"
|
||||
"""Show color tables"""
|
||||
|
||||
if self.args.startswith("a"):
|
||||
# show ansi 16-color table
|
||||
|
|
@ -721,9 +717,11 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
|||
# show all ansi color-related codes
|
||||
col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[48:56]]
|
||||
col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[56:64]]
|
||||
col3 = ["%s%s|n" % (code.replace("\\",""), code.replace("|", "||").replace("\\", "")) for code, _ in ap.ext_ansi_map[-8:]]
|
||||
col4 = ["%s%s|n" % (code.replace("\\",""), code.replace("|", "||").replace("\\", "")) for code, _ in ap.ansi_bright_bgs[-8:]]
|
||||
col2.extend(["" for i in range(len(col1)-len(col2))])
|
||||
col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
|
||||
for code, _ in ap.ext_ansi_map[-8:]]
|
||||
col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
|
||||
for code, _ in ap.ansi_bright_bgs[-8:]]
|
||||
col2.extend(["" for _ in range(len(col1)-len(col2))])
|
||||
table = utils.format_table([col1, col2, col4, col3])
|
||||
string = "ANSI colors:"
|
||||
for row in table:
|
||||
|
|
@ -742,9 +740,8 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
|||
# foreground table
|
||||
table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib)))
|
||||
# background table
|
||||
table[6+ir].append("|%i%i%i|[%i%i%i%s|n" % (5 - ir, 5 - ig, 5 - ib,
|
||||
ir, ig, ib,
|
||||
"||[%i%i%i" % (ir, ig, ib)))
|
||||
table[6+ir].append("|%i%i%i|[%i%i%i%s|n"
|
||||
% (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib)))
|
||||
table = self.table_format(table)
|
||||
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):"
|
||||
string += "\n" + "\n".join("".join(row) for row in table)
|
||||
|
|
@ -753,15 +750,15 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
|||
for igray in range(6):
|
||||
letter = chr(97 + (ibatch*6 + igray))
|
||||
inverse = chr(122 - (ibatch*6 + igray))
|
||||
table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % (letter)))
|
||||
table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % (letter)))
|
||||
table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % letter))
|
||||
table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter))
|
||||
for igray in range(6):
|
||||
# the last row (y, z) has empty columns
|
||||
if igray < 2:
|
||||
letter = chr(121 + igray)
|
||||
inverse = chr(98 - igray)
|
||||
fg = "|=%s%s |n" % (letter, "||=%s" % (letter))
|
||||
bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % (letter))
|
||||
fg = "|=%s%s |n" % (letter, "||=%s" % letter)
|
||||
bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter)
|
||||
else:
|
||||
fg, bg = " ", " "
|
||||
table[0 + igray].append(fg)
|
||||
|
|
@ -800,7 +797,7 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
|
|||
player_caller = True
|
||||
|
||||
def _recache_locks(self, player):
|
||||
"Helper method to reset the lockhandler on an already puppeted object"
|
||||
"""Helper method to reset the lockhandler on an already puppeted object"""
|
||||
if self.session:
|
||||
char = self.session.puppet
|
||||
if char:
|
||||
|
|
@ -811,25 +808,26 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
|
|||
player.locks.reset()
|
||||
|
||||
def func(self):
|
||||
"Perform the command"
|
||||
"""Perform the command"""
|
||||
player = self.player
|
||||
permstr = player.is_superuser and " (superuser)" or " (%s)" % (", ".join(player.permissions.all()))
|
||||
permstr = player.is_superuser and " (superuser)" or "(%s)" % (", ".join(player.permissions.all()))
|
||||
if self.cmdstring == '@unquell':
|
||||
if not player.attributes.get('_quell'):
|
||||
self.msg("Already using normal Player permissions%s." % permstr)
|
||||
self.msg("Already using normal Player permissions %s." % permstr)
|
||||
else:
|
||||
player.attributes.remove('_quell')
|
||||
self.msg("Player permissions%s restored." % permstr)
|
||||
self.msg("Player permissions %s restored." % permstr)
|
||||
else:
|
||||
if player.attributes.get('_quell'):
|
||||
self.msg("Already quelling Player%s permissions." % permstr)
|
||||
self.msg("Already quelling Player %s permissions." % permstr)
|
||||
return
|
||||
player.attributes.add('_quell', True)
|
||||
puppet = self.session.puppet
|
||||
if puppet:
|
||||
cpermstr = " (%s)" % ", ".join(puppet.permissions.all())
|
||||
cpermstr = "Quelling to current puppet's permissions%s." % cpermstr
|
||||
cpermstr += "\n(Note: If this is higher than Player permissions%s, the lowest of the two will be used.)" % permstr
|
||||
cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
|
||||
cpermstr = "Quelling to current puppet's permissions %s." % cpermstr
|
||||
cpermstr += "\n(Note: If this is higher than Player permissions %s," \
|
||||
" the lowest of the two will be used.)" % permstr
|
||||
cpermstr += "\nUse @unquell to return to normal permission usage."
|
||||
self.msg(cpermstr)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Define function"
|
||||
"""Define function"""
|
||||
# Only allow shutdown if caller has session
|
||||
if not self.caller.sessions.get():
|
||||
return
|
||||
|
|
@ -126,6 +126,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
|
|||
def _py_load(caller):
|
||||
return ""
|
||||
|
||||
|
||||
def _py_code(caller, buf):
|
||||
"""
|
||||
Execute the buffer.
|
||||
|
|
@ -139,10 +140,12 @@ def _py_code(caller, buf):
|
|||
show_input=False)
|
||||
return True
|
||||
|
||||
|
||||
def _py_quit(caller):
|
||||
del caller.db._py_measure_time
|
||||
caller.msg("Exited the code editor.")
|
||||
|
||||
|
||||
def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
|
||||
show_input=True):
|
||||
"""
|
||||
|
|
@ -174,9 +177,9 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
|
|||
if show_input:
|
||||
try:
|
||||
caller.msg(">>> %s" % pycode, session=session,
|
||||
options={"raw":True})
|
||||
options={"raw": True})
|
||||
except TypeError:
|
||||
caller.msg(">>> %s" % pycode, options={"raw":True})
|
||||
caller.msg(">>> %s" % pycode, options={"raw": True})
|
||||
|
||||
try:
|
||||
try:
|
||||
|
|
@ -204,9 +207,10 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
|
|||
ret = "\n".join("%s" % line for line in errlist if line)
|
||||
|
||||
try:
|
||||
caller.msg(ret, session=session, options={"raw":True})
|
||||
caller.msg(ret, session=session, options={"raw": True})
|
||||
except TypeError:
|
||||
caller.msg(ret, options={"raw":True})
|
||||
caller.msg(ret, options={"raw": True})
|
||||
|
||||
|
||||
class CmdPy(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
|
|
@ -234,8 +238,8 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
|
|||
You can explore The evennia API from inside the game by calling
|
||||
evennia.help(), evennia.managers.help() etc.
|
||||
|
||||
{rNote: In the wrong hands this command is a severe security risk.
|
||||
It should only be accessible by trusted server admins/superusers.{n
|
||||
|rNote: In the wrong hands this command is a severe security risk.
|
||||
It should only be accessible by trusted server admins/superusers.|n
|
||||
|
||||
"""
|
||||
key = "@py"
|
||||
|
|
@ -244,7 +248,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"hook function"
|
||||
"""hook function"""
|
||||
|
||||
caller = self.caller
|
||||
pycode = self.args
|
||||
|
|
@ -266,13 +270,14 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
|
|||
# helper function. Kept outside so it can be imported and run
|
||||
# by other commands.
|
||||
|
||||
|
||||
def format_script_list(scripts):
|
||||
"Takes a list of scripts and formats the output."
|
||||
"""Takes a list of scripts and formats the output."""
|
||||
if not scripts:
|
||||
return "<No scripts>"
|
||||
|
||||
table = EvTable("{wdbref{n", "{wobj{n", "{wkey{n", "{wintval{n", "{wnext{n",
|
||||
"{wrept{n", "{wdb", "{wtypeclass{n", "{wdesc{n",
|
||||
table = EvTable("|wdbref|n", "|wobj|n", "|wkey|n", "|wintval|n", "|wnext|n",
|
||||
"|wrept|n", "|wdb", "|wtypeclass|n", "|wdesc|n",
|
||||
align='r', border="tablecols")
|
||||
for script in scripts:
|
||||
nextrep = script.time_until_next_repeat()
|
||||
|
|
@ -326,12 +331,11 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"implement method"
|
||||
"""implement method"""
|
||||
|
||||
caller = self.caller
|
||||
args = self.args
|
||||
|
||||
string = ""
|
||||
if args:
|
||||
if "start" in self.switches:
|
||||
# global script-start mode
|
||||
|
|
@ -374,9 +378,9 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
else:
|
||||
string = "Stopping script '%s'." % scripts[0].key
|
||||
scripts[0].stop()
|
||||
#import pdb
|
||||
#pdb.set_trace()
|
||||
ScriptDB.objects.validate() #just to be sure all is synced
|
||||
# import pdb # DEBUG
|
||||
# pdb.set_trace() # DEBUG
|
||||
ScriptDB.objects.validate() # just to be sure all is synced
|
||||
else:
|
||||
# multiple matches.
|
||||
string = "Multiple script matches. Please refine your search:\n"
|
||||
|
|
@ -409,26 +413,21 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Implement the command"
|
||||
"""Implement the command"""
|
||||
|
||||
caller = self.caller
|
||||
|
||||
if self.args and self.args.isdigit():
|
||||
nlim = int(self.args)
|
||||
else:
|
||||
nlim = 10
|
||||
|
||||
nlim = int(self.args) if self.args and self.args.isdigit() else 10
|
||||
nobjs = ObjectDB.objects.count()
|
||||
base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count()
|
||||
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=base_char_typeclass).count()
|
||||
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(
|
||||
db_typeclass_path=base_char_typeclass).count()
|
||||
nexits = ObjectDB.objects.filter(db_location__isnull=False, db_destination__isnull=False).count()
|
||||
nother = nobjs - nchars - nrooms - nexits
|
||||
|
||||
nobjs = nobjs or 1 # fix zero-div error with empty database
|
||||
nobjs = nobjs or 1 # fix zero-div error with empty database
|
||||
|
||||
# total object sum table
|
||||
totaltable = EvTable("{wtype{n", "{wcomment{n", "{wcount{n", "{w%%{n", border="table", align="l")
|
||||
totaltable = EvTable("|wtype|n", "|wcomment|n", "|wcount|n", "|w%%|n", border="table", align="l")
|
||||
totaltable.align = 'l'
|
||||
totaltable.add_row("Characters", "(BASE_CHARACTER_TYPECLASS)", nchars, "%.2f" % ((float(nchars) / nobjs) * 100))
|
||||
totaltable.add_row("Rooms", "(location=None)", nrooms, "%.2f" % ((float(nrooms) / nobjs) * 100))
|
||||
|
|
@ -436,7 +435,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
|||
totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100))
|
||||
|
||||
# typeclass table
|
||||
typetable = EvTable("{wtypeclass{n", "{wcount{n", "{w%%{n", border="table", align="l")
|
||||
typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="table", align="l")
|
||||
typetable.align = 'l'
|
||||
dbtotals = ObjectDB.objects.object_totals()
|
||||
for path, count in dbtotals.items():
|
||||
|
|
@ -444,15 +443,15 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# last N table
|
||||
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim):]
|
||||
latesttable = EvTable("{wcreated{n", "{wdbref{n", "{wname{n", "{wtypeclass{n", align="l", border="table")
|
||||
latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table")
|
||||
latesttable.align = 'l'
|
||||
for obj in objs:
|
||||
latesttable.add_row(utils.datetime_format(obj.date_created),
|
||||
obj.dbref, obj.key, obj.path)
|
||||
|
||||
string = "\n{wObject subtype totals (out of %i Objects):{n\n%s" % (nobjs, totaltable)
|
||||
string += "\n{wObject typeclass distribution:{n\n%s" % typetable
|
||||
string += "\n{wLast %s Objects created:{n\n%s" % (min(nobjs, nlim), latesttable)
|
||||
string = "\n|wObject subtype totals (out of %i Objects):|n\n%s" % (nobjs, totaltable)
|
||||
string += "\n|wObject typeclass distribution:|n\n%s" % typetable
|
||||
string += "\n|wLast %s Objects created:|n\n%s" % (min(nobjs, nlim), latesttable)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
|
|
@ -473,7 +472,7 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"List the players"
|
||||
"""List the players"""
|
||||
|
||||
caller = self.caller
|
||||
if self.args and self.args.isdigit():
|
||||
|
|
@ -485,17 +484,17 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# typeclass table
|
||||
dbtotals = PlayerDB.objects.object_totals()
|
||||
typetable = EvTable("{wtypeclass{n", "{wcount{n", "{w%%{n", border="cells", align="l")
|
||||
typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l")
|
||||
for path, count in dbtotals.items():
|
||||
typetable.add_row(path, count, "%.2f" % ((float(count) / nplayers) * 100))
|
||||
# last N table
|
||||
plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):]
|
||||
latesttable = EvTable("{wcreated{n", "{wdbref{n", "{wname{n", "{wtypeclass{n", border="cells", align="l")
|
||||
latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l")
|
||||
for ply in plyrs:
|
||||
latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path)
|
||||
|
||||
string = "\n{wPlayer typeclass distribution:{n\n%s" % typetable
|
||||
string += "\n{wLast %s Players created:{n\n%s" % (min(nplayers, nlim), latesttable)
|
||||
string = "\n|wPlayer typeclass distribution:|n\n%s" % typetable
|
||||
string += "\n|wLast %s Players created:|n\n%s" % (min(nplayers, nlim), latesttable)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
|
|
@ -525,7 +524,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Implement command"
|
||||
"""Implement command"""
|
||||
|
||||
caller = self.caller
|
||||
switches = self.switches
|
||||
|
|
@ -540,9 +539,9 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
|||
if not switches or switches[0] == "list":
|
||||
# Just display the list of installed services and their
|
||||
# status, then exit.
|
||||
table = EvTable("{wService{n (use @services/start|stop|delete)", "{wstatus", align="l")
|
||||
table = EvTable("|wService|n (use @services/start|stop|delete)", "|wstatus", align="l")
|
||||
for service in service_collection.services:
|
||||
table.add_row(service.name, service.running and "{gRunning" or "{rNot Running")
|
||||
table.add_row(service.name, service.running and "|gRunning" or "|rNot Running")
|
||||
caller.msg(unicode(table))
|
||||
return
|
||||
|
||||
|
|
@ -584,7 +583,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
|
||||
if switches[0] == "start":
|
||||
#Starts a service.
|
||||
# Attempt to start a service.
|
||||
if service.running:
|
||||
caller.msg('That service is already running.')
|
||||
return
|
||||
|
|
@ -608,23 +607,23 @@ class CmdAbout(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Show the version"
|
||||
"""Display information about server or target"""
|
||||
|
||||
string = """
|
||||
{cEvennia{n %s{n
|
||||
|cEvennia|n %s|n
|
||||
MUD/MUX/MU* development system
|
||||
|
||||
{wLicence{n BSD 3-Clause Licence
|
||||
{wWeb{n http://www.evennia.com
|
||||
{wIrc{n #evennia on FreeNode
|
||||
{wForum{n http://www.evennia.com/discussions
|
||||
{wMaintainer{n (2010-) Griatch (griatch AT gmail DOT com)
|
||||
{wMaintainer{n (2006-10) Greg Taylor
|
||||
|wLicence|n https://opensource.org/licenses/BSD-3-Clause
|
||||
|wWeb|n http://www.evennia.com
|
||||
|wIrc|n #evennia on FreeNode
|
||||
|wForum|n http://www.evennia.com/discussions
|
||||
|wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com)
|
||||
|wMaintainer|n (2006-10) Greg Taylor
|
||||
|
||||
{wOS{n %s
|
||||
{wPython{n %s
|
||||
{wTwisted{n %s
|
||||
{wDjango{n %s
|
||||
|wOS|n %s
|
||||
|wPython|n %s
|
||||
|wTwisted|n %s
|
||||
|wDjango|n %s
|
||||
""" % (utils.get_evennia_version(),
|
||||
os.name,
|
||||
sys.version.split()[0],
|
||||
|
|
@ -649,8 +648,8 @@ class CmdTime(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Show server time data in a table."
|
||||
table1 = EvTable("|wServer time","", align="l", width=78)
|
||||
"""Show server time data in a table."""
|
||||
table1 = EvTable("|wServer time", "", align="l", width=78)
|
||||
table1.add_row("Current uptime", utils.time_format(gametime.uptime(), 3))
|
||||
table1.add_row("Total runtime", utils.time_format(gametime.runtime(), 2))
|
||||
table1.add_row("First start", datetime.datetime.fromtimestamp(gametime.server_epoch()))
|
||||
|
|
@ -682,20 +681,20 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
Some Important statistics in the table:
|
||||
|
||||
{wServer load{n is an average of processor usage. It's usually
|
||||
|wServer load|n is an average of processor usage. It's usually
|
||||
between 0 (no usage) and 1 (100% usage), but may also be
|
||||
temporarily higher if your computer has multiple CPU cores.
|
||||
|
||||
The {wResident/Virtual memory{n displays the total memory used by
|
||||
The |wResident/Virtual memory|n displays the total memory used by
|
||||
the server process.
|
||||
|
||||
Evennia {wcaches{n all retrieved database entities when they are
|
||||
Evennia |wcaches|n all retrieved database entities when they are
|
||||
loaded by use of the idmapper functionality. This allows Evennia
|
||||
to maintain the same instances of an entity and allowing
|
||||
non-persistent storage schemes. The total amount of cached objects
|
||||
are displayed plus a breakdown of database object types.
|
||||
|
||||
The {wflushmem{n switch allows to flush the object cache. Please
|
||||
The |wflushmem|n switch allows to flush the object cache. Please
|
||||
note that due to how Python's memory management works, releasing
|
||||
caches may not show you a lower Residual/Virtual memory footprint,
|
||||
the released memory will instead be re-used by the program.
|
||||
|
|
@ -707,7 +706,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
|
|||
help_category = "System"
|
||||
|
||||
def func(self):
|
||||
"Show list."
|
||||
"""Show list."""
|
||||
|
||||
global _IDMAPPER
|
||||
if not _IDMAPPER:
|
||||
|
|
@ -741,21 +740,21 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
|
|||
if has_psutil:
|
||||
loadavg = psutil.cpu_percent()
|
||||
_mem = psutil.virtual_memory()
|
||||
rmem = _mem.used / (1000.0 * 1000)
|
||||
rmem = _mem.used / (1000.0 * 1000)
|
||||
pmem = _mem.percent
|
||||
|
||||
if "mem" in self.switches:
|
||||
string = "Total computer memory usage: {w%g{n MB (%g%%)"
|
||||
string = "Total computer memory usage: |w%g|n MB (%g%%)"
|
||||
self.caller.msg(string % (rmem, pmem))
|
||||
return
|
||||
# Display table
|
||||
loadtable = EvTable("property", "statistic", align="l")
|
||||
loadtable.add_row("Total CPU load", "%g %%" % loadavg)
|
||||
loadtable.add_row("Total computer memory usage","%g MB (%g%%)" % (rmem, pmem))
|
||||
loadtable.add_row("Total computer memory usage", "%g MB (%g%%)" % (rmem, pmem))
|
||||
loadtable.add_row("Process ID", "%g" % pid),
|
||||
else:
|
||||
loadtable = "Not available on Windows without 'psutil' library " \
|
||||
"(install with {wpip install psutil{n)."
|
||||
"(install with |wpip install psutil|n)."
|
||||
|
||||
else:
|
||||
# Linux / BSD (OSX) - proper pid-based statistics
|
||||
|
|
@ -767,46 +766,49 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
|
|||
loadavg = os.getloadavg()[0]
|
||||
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1000.0 # resident memory
|
||||
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1000.0 # virtual memory
|
||||
pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # percent of resident memory to total
|
||||
pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # % of resident memory to total
|
||||
rusage = _RESOURCE.getrusage(_RESOURCE.RUSAGE_SELF)
|
||||
|
||||
if "mem" in self.switches:
|
||||
string = "Memory usage: RMEM: {w%g{n MB (%g%%), " \
|
||||
" VMEM (res+swap+cache): {w%g{n MB."
|
||||
string = "Memory usage: RMEM: |w%g|n MB (%g%%), VMEM (res+swap+cache): |w%g|n MB."
|
||||
self.caller.msg(string % (rmem, pmem, vmem))
|
||||
return
|
||||
|
||||
loadtable = EvTable("property", "statistic", align="l")
|
||||
loadtable.add_row("Server load (1 min)", "%g" % loadavg)
|
||||
loadtable.add_row("Process ID", "%g" % pid),
|
||||
loadtable.add_row("Memory usage","%g MB (%g%%)" % (rmem, pmem))
|
||||
loadtable.add_row("Memory usage", "%g MB (%g%%)" % (rmem, pmem))
|
||||
loadtable.add_row("Virtual address space", "")
|
||||
loadtable.add_row("{x(resident+swap+caching){n", "%g MB" % vmem)
|
||||
loadtable.add_row("CPU time used (total)", "%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime))
|
||||
loadtable.add_row("CPU time used (user)", "%s (%gs)" % (utils.time_format(rusage.ru_stime), rusage.ru_stime))
|
||||
loadtable.add_row("Page faults", "%g hard, %g soft, %g swapouts" % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap))
|
||||
loadtable.add_row("|x(resident+swap+caching)|n", "%g MB" % vmem)
|
||||
loadtable.add_row("CPU time used (total)", "%s (%gs)"
|
||||
% (utils.time_format(rusage.ru_utime), rusage.ru_utime))
|
||||
loadtable.add_row("CPU time used (user)", "%s (%gs)"
|
||||
% (utils.time_format(rusage.ru_stime), rusage.ru_stime))
|
||||
loadtable.add_row("Page faults", "%g hard, %g soft, %g swapouts"
|
||||
% (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap))
|
||||
loadtable.add_row("Disk I/O", "%g reads, %g writes" % (rusage.ru_inblock, rusage.ru_oublock))
|
||||
loadtable.add_row("Network I/O", "%g in, %g out" % (rusage.ru_msgrcv, rusage.ru_msgsnd))
|
||||
loadtable.add_row("Context switching", "%g vol, %g forced, %g signals" % (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals))
|
||||
|
||||
loadtable.add_row("Context switching", "%g vol, %g forced, %g signals"
|
||||
% (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals))
|
||||
|
||||
# os-generic
|
||||
|
||||
string = "{wServer CPU and Memory load:{n\n%s" % loadtable
|
||||
string = "|wServer CPU and Memory load:|n\n%s" % loadtable
|
||||
|
||||
# object cache count (note that sys.getsiseof is not called so this works for pypy too.
|
||||
total_num, cachedict = _IDMAPPER.cache_size()
|
||||
sorted_cache = sorted([(key, num) for key, num in cachedict.items() if num > 0],
|
||||
key=lambda tup: tup[1], reverse=True)
|
||||
key=lambda tup: tup[1], reverse=True)
|
||||
memtable = EvTable("entity name", "number", "idmapper %", align="l")
|
||||
for tup in sorted_cache:
|
||||
memtable.add_row(tup[0], "%i" % tup[1], "%.2f" % (float(tup[1]) / total_num * 100))
|
||||
|
||||
string += "\n{w Entity idmapper cache:{n %i items\n%s" % (total_num, memtable)
|
||||
string += "\n|w Entity idmapper cache:|n %i items\n%s" % (total_num, memtable)
|
||||
|
||||
# return to caller
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
class CmdTickers(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
View running tickers
|
||||
|
|
@ -832,13 +834,9 @@ class CmdTickers(COMMAND_DEFAULT_CLASS):
|
|||
table = EvTable("interval (s)", "object", "path/methodname", "idstring", "db")
|
||||
for sub in all_subs:
|
||||
table.add_row(sub[3],
|
||||
"%s%s" % (sub[0] or "[None]", sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or ""),
|
||||
"%s%s" % (sub[0] or "[None]",
|
||||
sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or ""),
|
||||
sub[1] if sub[1] else sub[2],
|
||||
sub[4] or "[Unset]",
|
||||
"*" if sub[5] else "-")
|
||||
self.caller.msg("|wActive tickers|n:\n" + unicode(table))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class CommandTest(EvenniaTest):
|
|||
Tests a command
|
||||
"""
|
||||
|
||||
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None):
|
||||
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None, obj=None):
|
||||
"""
|
||||
Test a command by assigning all the needed
|
||||
properties to cmdobj and running
|
||||
|
|
@ -48,6 +48,10 @@ class CommandTest(EvenniaTest):
|
|||
cmdobj.at_post_cmd()
|
||||
The msgreturn value is compared to eventual
|
||||
output sent to caller.msg in the game
|
||||
|
||||
Returns:
|
||||
msg (str): The received message that was sent to the caller.
|
||||
|
||||
"""
|
||||
caller = caller if caller else self.char1
|
||||
receiver = receiver if receiver else caller
|
||||
|
|
@ -60,9 +64,10 @@ class CommandTest(EvenniaTest):
|
|||
cmdobj.session = SESSIONS.session_from_sessid(1)
|
||||
cmdobj.player = self.player
|
||||
cmdobj.raw_string = cmdobj.key + " " + args
|
||||
cmdobj.obj = caller if caller else self.char1
|
||||
cmdobj.obj = obj or (caller if caller else self.char1)
|
||||
# test
|
||||
old_msg = receiver.msg
|
||||
returned_msg = ""
|
||||
try:
|
||||
receiver.msg = Mock()
|
||||
cmdobj.at_pre_cmd()
|
||||
|
|
@ -74,18 +79,23 @@ class CommandTest(EvenniaTest):
|
|||
for name, args, kwargs in receiver.msg.mock_calls]
|
||||
# Get the first element of a tuple if msg received a tuple instead of a string
|
||||
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
|
||||
returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg)
|
||||
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
||||
if msg is not None:
|
||||
returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg)
|
||||
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
||||
if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()):
|
||||
sep1 = "\n" + "="*30 + "Wanted message" + "="*34 + "\n"
|
||||
sep2 = "\n" + "="*30 + "Returned message" + "="*32 + "\n"
|
||||
sep3 = "\n" + "="*78
|
||||
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
|
||||
raise AssertionError(retval)
|
||||
else:
|
||||
returned_msg = "\n".join(stored_msg)
|
||||
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
|
||||
finally:
|
||||
receiver.msg = old_msg
|
||||
|
||||
return returned_msg
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Individual module Tests
|
||||
# ------------------------------------------------------------
|
||||
|
|
@ -119,6 +129,9 @@ class TestGeneral(CommandTest):
|
|||
def test_say(self):
|
||||
self.call(general.CmdSay(), "Testing", "You say, \"Testing\"")
|
||||
|
||||
def test_whisper(self):
|
||||
self.call(general.CmdWhisper(), "Obj = Testing", "You whisper to Obj, \"Testing\"")
|
||||
|
||||
def test_access(self):
|
||||
self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):")
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
|||
# would also block dummyrunner, so it's not added as default.
|
||||
|
||||
_LATEST_FAILED_LOGINS = defaultdict(list)
|
||||
|
||||
|
||||
def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS):
|
||||
"""
|
||||
This will check the session's address against the
|
||||
|
|
@ -95,8 +97,8 @@ def create_guest_player(session):
|
|||
bans = ServerConfig.objects.conf("server_bans")
|
||||
if bans and any(tup[2].match(session.address) for tup in bans if tup[2]):
|
||||
# this is a banned IP!
|
||||
string = "{rYou have been banned and cannot continue from here." \
|
||||
"\nIf you feel this ban is in error, please email an admin.{x"
|
||||
string = "|rYou have been banned and cannot continue from here." \
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
session.msg(string)
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
return True, None
|
||||
|
|
@ -118,14 +120,11 @@ def create_guest_player(session):
|
|||
permissions = settings.PERMISSION_GUEST_DEFAULT
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
ptypeclass = settings.BASE_GUEST_TYPECLASS
|
||||
new_player = _create_player(session, playername, password,
|
||||
permissions, ptypeclass)
|
||||
new_player = _create_player(session, playername, password, permissions, ptypeclass)
|
||||
if new_player:
|
||||
_create_character(session, new_player, typeclass,
|
||||
home, permissions)
|
||||
_create_character(session, new_player, typeclass, home, permissions)
|
||||
return True, new_player
|
||||
|
||||
|
||||
except Exception:
|
||||
# We are in the middle between logged in and -not, so we have
|
||||
# to handle tracebacks ourselves at this point. If we don't,
|
||||
|
|
@ -150,7 +149,7 @@ def create_normal_player(session, name, password):
|
|||
# check for too many login errors too quick.
|
||||
if _throttle(session, maxlim=5, timeout=5*60):
|
||||
# timeout is 5 minutes.
|
||||
session.msg("{RYou made too many connection attempts. Try again in a few minutes.{n")
|
||||
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
||||
return None
|
||||
|
||||
# Match account name and check password
|
||||
|
|
@ -167,15 +166,14 @@ def create_normal_player(session, name, password):
|
|||
player.at_failed_login(session)
|
||||
return None
|
||||
|
||||
|
||||
# Check IP and/or name bans
|
||||
bans = ServerConfig.objects.conf("server_bans")
|
||||
if bans and (any(tup[0]==player.name.lower() for tup in bans)
|
||||
if bans and (any(tup[0] == player.name.lower() for tup in bans)
|
||||
or
|
||||
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
||||
# this is a banned IP or name!
|
||||
string = "{rYou have been banned and cannot continue from here." \
|
||||
"\nIf you feel this ban is in error, please email an admin.{x"
|
||||
string = "|rYou have been banned and cannot continue from here." \
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
session.msg(string)
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
return None
|
||||
|
|
@ -213,7 +211,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
|||
# check for too many login errors too quick.
|
||||
if _throttle(session, maxlim=5, timeout=5*60, storage=_LATEST_FAILED_LOGINS):
|
||||
# timeout is 5 minutes.
|
||||
session.msg("{RYou made too many connection attempts. Try again in a few minutes.{n")
|
||||
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
|
||||
return
|
||||
|
||||
args = self.args
|
||||
|
|
@ -237,12 +235,6 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
|
|||
name, password = parts
|
||||
player = create_normal_player(session, name, password)
|
||||
if player:
|
||||
# actually do the login. This will call all other hooks:
|
||||
# session.at_login()
|
||||
# player.at_init() # always called when object is loaded from disk
|
||||
# player.at_first_login() # only once, for player-centric setup
|
||||
# player.at_pre_login()
|
||||
# player.at_post_login(session=session)
|
||||
session.sessionhandler.login(session, player)
|
||||
|
||||
|
||||
|
|
@ -264,7 +256,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
|||
arg_regex = r"\s.*?|$"
|
||||
|
||||
def func(self):
|
||||
"Do checks and create account"
|
||||
"""Do checks and create account"""
|
||||
|
||||
session = self.caller
|
||||
args = self.args.strip()
|
||||
|
|
@ -309,12 +301,12 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# Check IP and/or name bans
|
||||
bans = ServerConfig.objects.conf("server_bans")
|
||||
if bans and (any(tup[0]==playername.lower() for tup in bans)
|
||||
if bans and (any(tup[0] == playername.lower() for tup in bans)
|
||||
or
|
||||
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
||||
# this is a banned IP or name!
|
||||
string = "{rYou have been banned and cannot continue from here." \
|
||||
"\nIf you feel this ban is in error, please email an admin.{x"
|
||||
string = "|rYou have been banned and cannot continue from here." \
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
session.msg(string)
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
return
|
||||
|
|
@ -327,8 +319,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
|||
if new_player:
|
||||
if MULTISESSION_MODE < 2:
|
||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||
_create_character(session, new_player, typeclass,
|
||||
default_home, permissions)
|
||||
_create_character(session, new_player, typeclass, default_home, permissions)
|
||||
# tell the caller everything went well.
|
||||
string = "A new account '%s' was created. Welcome!"
|
||||
if " " in playername:
|
||||
|
|
@ -361,7 +352,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS):
|
|||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Simply close the connection."
|
||||
"""Simply close the connection."""
|
||||
session = self.caller
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
|
||||
|
|
@ -383,7 +374,7 @@ class CmdUnconnectedLook(COMMAND_DEFAULT_CLASS):
|
|||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Show the connect screen."
|
||||
"""Show the connect screen."""
|
||||
connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE)
|
||||
if not connection_screen:
|
||||
connection_screen = "No connection screen found. Please contact an admin."
|
||||
|
|
@ -405,25 +396,25 @@ class CmdUnconnectedHelp(COMMAND_DEFAULT_CLASS):
|
|||
locks = "cmd:all()"
|
||||
|
||||
def func(self):
|
||||
"Shows help"
|
||||
"""Shows help"""
|
||||
|
||||
string = \
|
||||
"""
|
||||
You are not yet logged into the game. Commands available at this point:
|
||||
|
||||
{wcreate{n - create a new account
|
||||
{wconnect{n - connect with an existing account
|
||||
{wlook{n - re-show the connection screen
|
||||
{whelp{n - show this help
|
||||
{wencoding{n - change the text encoding to match your client
|
||||
{wscreenreader{n - make the server more suitable for use with screen readers
|
||||
{wquit{n - abort the connection
|
||||
|wcreate|n - create a new account
|
||||
|wconnect|n - connect with an existing account
|
||||
|wlook|n - re-show the connection screen
|
||||
|whelp|n - show this help
|
||||
|wencoding|n - change the text encoding to match your client
|
||||
|wscreenreader|n - make the server more suitable for use with screen readers
|
||||
|wquit|n - abort the connection
|
||||
|
||||
First create an account e.g. with {wcreate Anna c67jHL8p{n
|
||||
(If you have spaces in your name, use double quotes: {wcreate "Anna the Barbarian" c67jHL8p{n
|
||||
Next you can connect to the game: {wconnect Anna c67jHL8p{n
|
||||
First create an account e.g. with |wcreate Anna c67jHL8p|n
|
||||
(If you have spaces in your name, use double quotes: |wcreate "Anna the Barbarian" c67jHL8p|n
|
||||
Next you can connect to the game: |wconnect Anna c67jHL8p|n
|
||||
|
||||
You can use the {wlook{n command if you want to see the connect screen again.
|
||||
You can use the |wlook|n command if you want to see the connect screen again.
|
||||
|
||||
"""
|
||||
self.caller.msg(string)
|
||||
|
|
@ -479,10 +470,10 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
|
|||
pencoding = self.session.protocol_flags.get("ENCODING", None)
|
||||
string = ""
|
||||
if pencoding:
|
||||
string += "Default encoding: {g%s{n (change with {w@encoding <encoding>{n)" % pencoding
|
||||
string += "Default encoding: |g%s|n (change with |w@encoding <encoding>|n)" % pencoding
|
||||
encodings = settings.ENCODINGS
|
||||
if encodings:
|
||||
string += "\nServer's alternative encodings (tested in this order):\n {g%s{n" % ", ".join(encodings)
|
||||
string += "\nServer's alternative encodings (tested in this order):\n |g%s|n" % ", ".join(encodings)
|
||||
if not string:
|
||||
string = "No encodings found."
|
||||
else:
|
||||
|
|
@ -492,7 +483,8 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
|
|||
try:
|
||||
utils.to_str(utils.to_unicode("test-string"), encoding=encoding)
|
||||
except LookupError:
|
||||
string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n" % (encoding, old_encoding)
|
||||
string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n"\
|
||||
% (encoding, old_encoding)
|
||||
else:
|
||||
self.session.protocol_flags["ENCODING"] = encoding
|
||||
string = "Your custom text encoding was changed from '|w%s|n' to '|w%s|n'." % (old_encoding, encoding)
|
||||
|
|
@ -501,6 +493,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
|
|||
self.session.sessionhandler.session_portal_sync(self.session)
|
||||
self.caller.msg(string.strip())
|
||||
|
||||
|
||||
class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
Activate screenreader mode.
|
||||
|
|
@ -515,21 +508,20 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
|
|||
aliases = "@screenreader"
|
||||
|
||||
def func(self):
|
||||
"Flips screenreader setting."
|
||||
"""Flips screenreader setting."""
|
||||
new_setting = not self.session.protocol_flags.get("SCREENREADER", False)
|
||||
self.session.protocol_flags["SCREENREADER"] = new_setting
|
||||
string = "Screenreader mode turned {w%s{n." % ("on" if new_setting else "off")
|
||||
self.session.protocol_flags["SCREENREADER"] = new_setting
|
||||
string = "Screenreader mode turned |w%s|n." % ("on" if new_setting else "off")
|
||||
self.caller.msg(string)
|
||||
self.session.sessionhandler.session_portal_sync(self.session)
|
||||
|
||||
|
||||
def _create_player(session, playername, password, permissions, typeclass=None):
|
||||
def _create_player(session, playername, password, permissions, typeclass=None, email=None):
|
||||
"""
|
||||
Helper function, creates a player of the specified typeclass.
|
||||
"""
|
||||
try:
|
||||
new_player = create.create_player(playername, None, password,
|
||||
permissions=permissions, typeclass=typeclass)
|
||||
new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass)
|
||||
|
||||
except Exception as e:
|
||||
session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e)
|
||||
|
|
@ -543,7 +535,7 @@ def _create_player(session, playername, password, permissions, typeclass=None):
|
|||
|
||||
# join the new player to the public channel
|
||||
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
|
||||
if not pchannel.connect(new_player):
|
||||
if not pchannel or not pchannel.connect(new_player):
|
||||
string = "New player '%s' could not connect to public channel!" % new_player.key
|
||||
logger.log_err(string)
|
||||
return new_player
|
||||
|
|
@ -555,8 +547,7 @@ def _create_character(session, new_player, typeclass, home, permissions):
|
|||
This is meant for Guest and MULTISESSION_MODE < 2 situations.
|
||||
"""
|
||||
try:
|
||||
new_character = create.create_object(typeclass, key=new_player.key,
|
||||
home=home, permissions=permissions)
|
||||
new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions)
|
||||
# set playable character list
|
||||
new_player.db._playable_characters.append(new_character)
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist]
|
||||
if subs:
|
||||
# display listening subscribers in bold
|
||||
string = ", ".join([player.key if player not in listening else "{w%s{n" % player.key for player in subs])
|
||||
string = ", ".join([player.key if player not in listening else "|w%s|n" % player.key for player in subs])
|
||||
else:
|
||||
string = "<None>"
|
||||
return string
|
||||
|
|
@ -126,7 +126,6 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
return True
|
||||
return False
|
||||
|
||||
|
||||
def connect(self, subscriber):
|
||||
"""
|
||||
Connect the user to this channel. This checks access.
|
||||
|
|
@ -254,7 +253,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
try:
|
||||
# note our addition of the from_channel keyword here. This could be checked
|
||||
# by a custom player.msg() to treat channel-receives differently.
|
||||
entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel":self.id})
|
||||
entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id})
|
||||
except AttributeError as e:
|
||||
logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity))
|
||||
|
||||
|
|
@ -331,7 +330,6 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
"""
|
||||
self.msg(message, senders=senders, header=header, keep_log=False)
|
||||
|
||||
|
||||
# hooks
|
||||
|
||||
def channel_prefix(self, msg=None, emit=False):
|
||||
|
|
@ -376,7 +374,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
message accordingly.
|
||||
|
||||
Args:
|
||||
msgob (Msg or TempMsg): The message to analyze for a pose.
|
||||
msgobj (Msg or TempMsg): The message to analyze for a pose.
|
||||
sender_string (str): The name of the sender/poser.
|
||||
|
||||
Returns:
|
||||
|
|
@ -426,7 +424,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
Hook method. Formats a message body for display.
|
||||
|
||||
Args:
|
||||
msgob (Msg or TempMsg): The message object to send.
|
||||
msgobj (Msg or TempMsg): The message object to send.
|
||||
emit (bool, optional): The message is agnostic of senders.
|
||||
|
||||
Returns:
|
||||
|
|
@ -475,7 +473,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
value, leaving the channel will be aborted.
|
||||
|
||||
Args:
|
||||
joiner (object): The joining object.
|
||||
leaver (object): The leaving object.
|
||||
|
||||
Returns:
|
||||
should_leave (bool): If `False`, channel parting is aborted.
|
||||
|
|
@ -488,7 +486,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
Hook method. Runs right after an object or player leaves a channel.
|
||||
|
||||
Args:
|
||||
joiner (object): The joining object.
|
||||
leaver (object): The leaving object.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -580,7 +580,7 @@ class CmdDecline(CmdTradeBase):
|
|||
self.msg_other(caller, self.str_other
|
||||
% "%s changes their mind, |Rdeclining|n the current offer." % caller.key)
|
||||
else:
|
||||
# no accept_ance to change
|
||||
# no acceptance to change
|
||||
caller.msg(self.str_caller % "You |Rdecline|n the current offer.")
|
||||
self.msg_other(caller, self.str_other % "%s declines the current offer." % caller.key)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,32 +2,25 @@
|
|||
|
||||
Contribution - Griatch 2011
|
||||
|
||||
[Note - with the advent of MULTISESSION_MODE=2, this is not really as
|
||||
> Note - with the advent of MULTISESSION_MODE=2, this is not really as
|
||||
necessary anymore - the ooclook and @charcreate commands in that mode
|
||||
replaces this module with better functionality.]
|
||||
replaces this module with better functionality. This remains here for
|
||||
inspiration.
|
||||
|
||||
This is a simple character creation commandset. A suggestion is to
|
||||
test this together with menu_login, which doesn't create a Character
|
||||
on its own. This shows some more info and gives the Player the option
|
||||
to create a character without any more customizations than their name
|
||||
(further options are unique for each game anyway).
|
||||
This is a simple character creation commandset for the Player level.
|
||||
It shows some more info and gives the Player the option to create a
|
||||
character without any more customizations than their name (further
|
||||
options are unique for each game anyway).
|
||||
|
||||
Since this extends the OOC cmdset, logging in from the menu will
|
||||
automatically drop the Player into this cmdset unless they logged off
|
||||
while puppeting a Character already before.
|
||||
In MULTISESSION_MODEs 0 and 1, you will automatically log into an
|
||||
existing Character. When using `@ooc` you will then end up in this
|
||||
cmdset.
|
||||
|
||||
Installation:
|
||||
|
||||
Read the instructions in contrib/examples/cmdset.py in order to create
|
||||
a new default cmdset module for Evennia to use (copy the template up
|
||||
one level, and change the settings file's relevant variables to point
|
||||
to the cmdsets inside). If you already have such a module you should
|
||||
of course use that.
|
||||
|
||||
Next import this module in your custom cmdset module and add the
|
||||
following line to the end of OOCCmdSet's at_cmdset_creation():
|
||||
|
||||
self.add(chargen.OOCCmdSetCharGen)
|
||||
Import this module to `mygame/commands/default_cmdsets.py` and
|
||||
add `chargen.OOCCMdSetCharGen` to the `PlayerCmdSet` class
|
||||
(it says where to add it). Reload.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -179,7 +172,7 @@ class CmdOOCCharacterCreate(Command):
|
|||
else:
|
||||
avail_chars = [new_character.id]
|
||||
self.caller.db._character_dbrefs = avail_chars
|
||||
self.caller.msg("|gThe Character |c%s|g was successfully created!" % charname)
|
||||
self.caller.msg("|gThe character |c%s|g was successfully created!" % charname)
|
||||
|
||||
|
||||
class OOCCmdSetCharGen(default_cmds.PlayerCmdSet):
|
||||
|
|
|
|||
|
|
@ -12,14 +12,23 @@ Usage:
|
|||
|
||||
Use as the normal gametime module, that is by importing and using the
|
||||
helper functions in this module in your own code. The calendar can be
|
||||
specified in your settings file by adding and setting custom values
|
||||
for one or more of the variables `TIME_SECS_PER_MIN`,
|
||||
`TIME_MINS_PER_HOUR`, `TIME_DAYS_PER_WEEK`, `TIME_WEEKS_PER_MONTH` and
|
||||
`TIME_MONTHS_PER_YEAR`. These are all given in seconds and whereas
|
||||
they are called "week", "month" etc these names could represent
|
||||
whatever fits your game. You can also set `TIME_UNITS` to a dict
|
||||
mapping the name of a unit to its length in seconds (like `{"min":
|
||||
60, ...}. If not given, sane defaults will be used.
|
||||
customized by adding the `TIME_UNITS` dictionary to your settings
|
||||
file. This maps unit names to their length, expressed in the smallest
|
||||
unit. Here's the default as an example:
|
||||
|
||||
TIME_UNITS = {
|
||||
"sec": 1,
|
||||
"min": 60,
|
||||
"hr": 60 * 60,
|
||||
"hour": 60 * 60,
|
||||
"day": 60 * 60 * 24,
|
||||
"week": 60 * 60 * 24 * 7,
|
||||
"month": 60 * 60 * 24 * 7 * 4,
|
||||
"yr": 60 * 60 * 24 * 7 * 4 * 12,
|
||||
"year": 60 * 60 * 24 * 7 * 4 * 12, }
|
||||
|
||||
When using a custom calendar, these time unit names are used as kwargs to
|
||||
the converter functions in this module.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -28,33 +37,23 @@ mapping the name of a unit to its length in seconds (like `{"min":
|
|||
from django.conf import settings
|
||||
from evennia import DefaultScript
|
||||
from evennia.utils.create import create_script
|
||||
from evennia.utils.gametime import gametime
|
||||
from evennia.utils import gametime
|
||||
# The game time speedup / slowdown relative real time
|
||||
TIMEFACTOR = settings.TIME_FACTOR
|
||||
|
||||
# Game-time units, in game time seconds. These are supplied as a
|
||||
# convenient measure for determining the current in-game time, e.g.
|
||||
# when defining in-game events. The words month, week and year can be
|
||||
# used to mean whatever units of time are used in your game.
|
||||
SEC = 1
|
||||
MIN = getattr(settings, "TIME_SECS_PER_MIN", 60)
|
||||
HOUR = getattr(settings, "TIME_MINS_PER_HOUR", 60) * MIN
|
||||
DAY = getattr(settings, "TIME_HOURS_PER_DAY", 24) * HOUR
|
||||
WEEK = getattr(settings, "TIME_DAYS_PER_WEEK", 7) * DAY
|
||||
MONTH = getattr(settings, "TIME_WEEKS_PER_MONTH", 4) * WEEK
|
||||
YEAR = getattr(settings, "TIME_MONTHS_PER_YEAR", 12) * MONTH
|
||||
# these are the unit names understood by the scheduler.
|
||||
# These are the unit names understood by the scheduler.
|
||||
# Each unit must be consistent and expressed in seconds.
|
||||
UNITS = getattr(settings, "TIME_UNITS", {
|
||||
"sec": SEC,
|
||||
"min": MIN,
|
||||
"hr": HOUR,
|
||||
"hour": HOUR,
|
||||
"day": DAY,
|
||||
"week": WEEK,
|
||||
"month": MONTH,
|
||||
"year": YEAR,
|
||||
"yr": YEAR,
|
||||
})
|
||||
# default custom calendar
|
||||
"sec": 1,
|
||||
"min": 60,
|
||||
"hr": 60 * 60,
|
||||
"hour": 60 * 60,
|
||||
"day": 60 * 60 * 24,
|
||||
"week": 60 * 60 * 24 * 7,
|
||||
"month": 60 * 60 * 24 * 7 * 4,
|
||||
"yr": 60 * 60 * 24 * 7 * 4 * 12,
|
||||
"year": 60 * 60 * 24 * 7 * 4 * 12, })
|
||||
|
||||
|
||||
def time_to_tuple(seconds, *divisors):
|
||||
|
|
@ -92,7 +91,8 @@ def gametime_to_realtime(format=False, **kwargs):
|
|||
|
||||
Kwargs:
|
||||
format (bool): Formatting the output.
|
||||
times (int): The various components of the time (must match UNITS).
|
||||
days, month etc (int): These are the names of time units that must
|
||||
match the `settings.TIME_UNITS` dict keys.
|
||||
|
||||
Returns:
|
||||
time (float or tuple): The realtime difference or the same
|
||||
|
|
@ -163,7 +163,7 @@ def custom_gametime(absolute=False):
|
|||
week, day, hour, minute, second).
|
||||
|
||||
"""
|
||||
current = gametime(absolute=absolute)
|
||||
current = gametime.gametime(absolute=absolute)
|
||||
units = sorted(set(UNITS.values()), reverse=True)
|
||||
del units[-1]
|
||||
return time_to_tuple(current, *units)
|
||||
|
|
@ -186,7 +186,7 @@ def real_seconds_until(**kwargs):
|
|||
The number of real seconds before the given game time is up.
|
||||
|
||||
"""
|
||||
current = gametime(absolute=True)
|
||||
current = gametime.gametime(absolute=True)
|
||||
units = sorted(set(UNITS.values()), reverse=True)
|
||||
# Remove seconds from the tuple
|
||||
del units[-1]
|
||||
|
|
@ -232,25 +232,26 @@ def schedule(callback, repeat=False, **kwargs):
|
|||
"""
|
||||
Call the callback when the game time is up.
|
||||
|
||||
This function will setup a script that will be called when the
|
||||
time corresponds to the game time. If the game is stopped for
|
||||
more than a few seconds, the callback may be called with a slight
|
||||
delay. If `repeat` is set to True, the callback will be called
|
||||
again next time the game time matches the given time. The time
|
||||
is given in units as keyword arguments. For instance:
|
||||
>>> schedule(func, min=5, sec=0) # Will call next hour at :05.
|
||||
>>> schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30.
|
||||
|
||||
Args:
|
||||
callback (function): the callback function that will be called [1].
|
||||
repeat (bool, optional): should the callback be called regularly?
|
||||
times (str: int): the time to call the callback.
|
||||
|
||||
[1] The callback must be a top-level function, since the script will
|
||||
be persistent.
|
||||
callback (function): The callback function that will be called. This
|
||||
must be a top-level function since the script will be persistent.
|
||||
repeat (bool, optional): Should the callback be called regularly?
|
||||
day, month, etc (str: int): The time units to call the callback; should
|
||||
match the keys of TIME_UNITS.
|
||||
|
||||
Returns:
|
||||
The created script (Script).
|
||||
script (Script): The created script.
|
||||
|
||||
Examples:
|
||||
schedule(func, min=5, sec=0) # Will call next hour at :05.
|
||||
schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30.
|
||||
Notes:
|
||||
This function will setup a script that will be called when the
|
||||
time corresponds to the game time. If the game is stopped for
|
||||
more than a few seconds, the callback may be called with a
|
||||
slight delay. If `repeat` is set to True, the callback will be
|
||||
called again next time the game time matches the given time.
|
||||
The time is given in units as keyword arguments.
|
||||
|
||||
"""
|
||||
seconds = real_seconds_until(**kwargs)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ from evennia.commands.cmdset import CmdSet
|
|||
from evennia.utils import create, logger, utils, ansi
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate
|
||||
|
||||
# limit symbol import for API
|
||||
__all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate",
|
||||
|
|
@ -161,99 +162,77 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
"""Do checks and create account"""
|
||||
|
||||
session = self.caller
|
||||
|
||||
try:
|
||||
playername, email, password = self.playerinfo
|
||||
except ValueError:
|
||||
string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>"
|
||||
session.msg(string)
|
||||
return
|
||||
if not re.findall('^[\w. @+-]+$', playername) or not (0 < len(playername) <= 30):
|
||||
session.msg("\n\r Playername can be max 30 characters, or less. Letters, spaces,"
|
||||
" digits and @/./+/-/_ only.") # this echoes the restrictions made by django's auth module.
|
||||
return
|
||||
if not email or not password:
|
||||
session.msg("\n\r You have to supply an e-mail address followed by a password.")
|
||||
return
|
||||
|
||||
if not utils.validate_email_address(email):
|
||||
# check so the email at least looks ok.
|
||||
session.msg("'%s' is not a valid e-mail address." % email)
|
||||
return
|
||||
|
||||
# Run sanity and security checks
|
||||
|
||||
if PlayerDB.objects.filter(username=playername):
|
||||
# player already exists
|
||||
# sanity checks
|
||||
if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30):
|
||||
# this echoes the restrictions made by django's auth
|
||||
# module (except not allowing spaces, for convenience of
|
||||
# logging in).
|
||||
string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
|
||||
session.msg(string)
|
||||
return
|
||||
# strip excessive spaces in playername
|
||||
playername = re.sub(r"\s+", " ", playername).strip()
|
||||
if PlayerDB.objects.filter(username__iexact=playername):
|
||||
# player already exists (we also ignore capitalization here)
|
||||
session.msg("Sorry, there is already a player with the name '%s'." % playername)
|
||||
return
|
||||
if PlayerDB.objects.get_player_from_email(email):
|
||||
# email already set on a player
|
||||
session.msg("Sorry, there is already a player with that email address.")
|
||||
return
|
||||
if len(password) < 3:
|
||||
# too short password
|
||||
string = "Your password must be at least 3 characters or longer."
|
||||
string += "\n\rFor best security, make it at least 8 characters long, "
|
||||
string += "avoid making it a real word and mix numbers into it."
|
||||
# Reserve playernames found in GUEST_LIST
|
||||
if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST):
|
||||
string = "\n\r That name is reserved. Please choose another Playername."
|
||||
session.msg(string)
|
||||
return
|
||||
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
|
||||
string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @/./+/-/_/' only." \
|
||||
"\nFor best security, make it longer than 8 characters. You can also use a phrase of" \
|
||||
"\nmany words if you enclose the password in double quotes."
|
||||
session.msg(string)
|
||||
return
|
||||
|
||||
# Check IP and/or name bans
|
||||
bans = ServerConfig.objects.conf("server_bans")
|
||||
if bans and (any(tup[0] == playername.lower() for tup in bans)
|
||||
or
|
||||
any(tup[2].match(session.address) for tup in bans if tup[2])):
|
||||
# this is a banned IP or name!
|
||||
string = "|rYou have been banned and cannot continue from here." \
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
session.msg(string)
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
return
|
||||
|
||||
# everything's ok. Create the new player account.
|
||||
try:
|
||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
permissions = settings.PERMISSION_PLAYER_DEFAULT
|
||||
|
||||
try:
|
||||
new_player = create.create_player(playername, email, password, permissions=permissions)
|
||||
|
||||
except Exception as e:
|
||||
session.msg("There was an error creating the default Player/Character:\n%s\n"
|
||||
" If this problem persists, contact an admin." % e)
|
||||
logger.log_trace()
|
||||
return
|
||||
|
||||
# This needs to be set so the engine knows this player is
|
||||
# logging in for the first time. (so it knows to call the right
|
||||
# hooks during login later)
|
||||
new_player.db.FIRST_LOGIN = True
|
||||
|
||||
# join the new player to the public channel
|
||||
pchanneldef = settings.CHANNEL_PUBLIC
|
||||
if pchanneldef:
|
||||
pchannel = ChannelDB.objects.get_channel(pchanneldef[0])
|
||||
if not pchannel.connect(new_player):
|
||||
string = "New player '%s' could not connect to public channel!" % new_player.key
|
||||
logger.log_err(string)
|
||||
|
||||
if MULTISESSION_MODE < 2:
|
||||
# if we only allow one character, create one with the same name as Player
|
||||
# (in mode 2, the character must be created manually once logging in)
|
||||
new_character = create.create_object(typeclass, key=playername,
|
||||
location=default_home, home=default_home,
|
||||
permissions=permissions)
|
||||
# set playable character list
|
||||
new_player.db._playable_characters.append(new_character)
|
||||
|
||||
# allow only the character itself and the player to puppet this character (and Admin).
|
||||
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
|
||||
(new_character.id, new_player.id))
|
||||
|
||||
# If no description is set, set a default description
|
||||
if not new_character.db.desc:
|
||||
new_character.db.desc = "This is a Player."
|
||||
# We need to set this to have @ic auto-connect to this character
|
||||
new_player.db._last_puppet = new_character
|
||||
|
||||
# tell the caller everything went well.
|
||||
string = "A new account '%s' was created. Welcome!"
|
||||
if " " in playername:
|
||||
string += "\n\nYou can now log in with the command 'connect %s <your password>'."
|
||||
else:
|
||||
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
||||
session.msg(string % (playername, email))
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email)
|
||||
if new_player:
|
||||
if MULTISESSION_MODE < 2:
|
||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||
default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions)
|
||||
# tell the caller everything went well.
|
||||
string = "A new account '%s' was created. Welcome!"
|
||||
if " " in playername:
|
||||
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
|
||||
else:
|
||||
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
||||
session.msg(string % (playername, email))
|
||||
|
||||
except Exception:
|
||||
# We are in the middle between logged in and -not, so we have
|
||||
|
|
@ -261,6 +240,7 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
# we won't see any errors at all.
|
||||
session.msg("An error occurred. Please e-mail an admin if the problem persists.")
|
||||
logger.log_trace()
|
||||
raise
|
||||
|
||||
|
||||
class CmdUnconnectedQuit(MuxCommand):
|
||||
|
|
@ -276,8 +256,7 @@ class CmdUnconnectedQuit(MuxCommand):
|
|||
def func(self):
|
||||
"""Simply close the connection."""
|
||||
session = self.caller
|
||||
session.msg("Good bye! Disconnecting ...")
|
||||
session.session_disconnect()
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
|
||||
|
||||
class CmdUnconnectedLook(MuxCommand):
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ Installation/testing:
|
|||
"""
|
||||
from __future__ import division
|
||||
|
||||
import datetime
|
||||
import re
|
||||
from django.conf import settings
|
||||
from evennia import DefaultRoom
|
||||
|
|
@ -129,12 +130,13 @@ class ExtendedRoom(DefaultRoom):
|
|||
"""
|
||||
Calculate the current time and season ids.
|
||||
"""
|
||||
# get the current time as parts of year and parts of day
|
||||
# returns a tuple (years,months,weeks,days,hours,minutes,sec)
|
||||
time = gametime.gametime(format=True)
|
||||
month, hour = time[1], time[4]
|
||||
season = float(month) / MONTHS_PER_YEAR
|
||||
timeslot = float(hour) / HOURS_PER_DAY
|
||||
# get the current time as parts of year and parts of day.
|
||||
# we assume a standard calendar here and use 24h format.
|
||||
timestamp = gametime.gametime(absolute=True)
|
||||
# note that fromtimestamp includes the effects of server time zone!
|
||||
datestamp = datetime.datetime.fromtimestamp(timestamp)
|
||||
season = float(datestamp.month) / MONTHS_PER_YEAR
|
||||
timeslot = float(datestamp.hour) / HOURS_PER_DAY
|
||||
|
||||
# figure out which slots these represent
|
||||
if SEASONAL_BOUNDARIES[0] <= season < SEASONAL_BOUNDARIES[1]:
|
||||
|
|
|
|||
|
|
@ -12,18 +12,18 @@ When in use, all messages being sent to the character will make use of
|
|||
the character's gender, for example the echo
|
||||
|
||||
```
|
||||
char.msg("%s falls on {p face with a thud." % char.key)
|
||||
char.msg("%s falls on |p face with a thud." % char.key)
|
||||
```
|
||||
|
||||
will result in "Tom falls on his|her|its face with a thud" depending
|
||||
on the gender of the object being messaged. Default gender is
|
||||
"neutral".
|
||||
will result in "Tom falls on his|her|its|their face with a thud"
|
||||
depending on the gender of the object being messaged. Default gender
|
||||
is "ambiguous" (they).
|
||||
|
||||
To use, have DefaultCharacter inherit from this, or change
|
||||
setting.DEFAULT_CHARACTER to point to this class.
|
||||
|
||||
The `@gender` command needs to be added to the default cmdset
|
||||
before it becomes available.
|
||||
The `@gender` command needs to be added to the default cmdset before
|
||||
it becomes available.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ _GENDER_PRONOUN_MAP = {"male": {"s": "he",
|
|||
"p": "their",
|
||||
"a": "theirs"}
|
||||
}
|
||||
_RE_GENDER_PRONOUN = re.compile(r'({s|{S|{o|{O|{p|{P|{a|{A)')
|
||||
_RE_GENDER_PRONOUN = re.compile(r'(?<!\|)\|(?!\|)[sSoOpPaA]')
|
||||
|
||||
# in-game command for setting the gender
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ class SetGender(Command):
|
|||
Sets gender on yourself
|
||||
|
||||
Usage:
|
||||
@gender male|female|neutral|ambiguous
|
||||
@gender male||female||neutral||ambiguous
|
||||
|
||||
"""
|
||||
key = "@gender"
|
||||
|
|
@ -73,10 +73,10 @@ class SetGender(Command):
|
|||
caller = self.caller
|
||||
arg = self.args.strip().lower()
|
||||
if not arg in ("male", "female", "neutral", "ambiguous"):
|
||||
caller.msg("Usage: @gender male|female|neutral|ambiguous")
|
||||
caller.msg("Usage: @gender male||female||neutral||ambiguous")
|
||||
return
|
||||
caller.db.gender = arg
|
||||
caller.msg("Your gender was set to %s." % arg)
|
||||
caller.msg("Your gender was set to %s." % arg)
|
||||
|
||||
|
||||
# Gender-aware character class
|
||||
|
|
@ -103,10 +103,10 @@ class GenderCharacter(DefaultCharacter):
|
|||
regex_match (MatchObject): the regular expression match.
|
||||
|
||||
Notes:
|
||||
- `{s`, `{S`: Subjective form: he, she, it, He, She, It, They
|
||||
- `{o`, `{O`: Objective form: him, her, it, Him, Her, It, Them
|
||||
- `{p`, `{P`: Possessive form: his, her, its, His, Her, Its, Their
|
||||
- `{a`, `{A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
|
||||
- `|s`, `|S`: Subjective form: he, she, it, He, She, It, They
|
||||
- `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them
|
||||
- `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their
|
||||
- `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
|
||||
|
||||
"""
|
||||
typ = regex_match.group()[1] # "s", "O" etc
|
||||
|
|
@ -134,5 +134,8 @@ class GenderCharacter(DefaultCharacter):
|
|||
|
||||
"""
|
||||
# pre-process the text before continuing
|
||||
text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text)
|
||||
try:
|
||||
text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text)
|
||||
except TypeError:
|
||||
pass
|
||||
super(GenderCharacter, self).msg(text, from_obj=from_obj, session=session, **kwargs)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@ Evennia Contribution - grungies1138 2016
|
|||
A simple Brandymail style @mail system that uses the Msg class from Evennia Core.
|
||||
|
||||
Installation:
|
||||
import MailCommand from this module into the default Player or Character command set
|
||||
import CmdMail from this module (from evennia.contrib.mail import CmdMail),
|
||||
and add into the default Player or Character command set (self.add(CmdMail)).
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
from evennia import ObjectDB, PlayerDB
|
||||
from evennia import default_cmds
|
||||
from evennia.utils import create, evtable
|
||||
from evennia.utils import create, evtable, make_iter
|
||||
from evennia.comms.models import Msg
|
||||
|
||||
|
||||
|
|
@ -48,7 +52,7 @@ class CmdMail(default_cmds.MuxCommand):
|
|||
@mail 2
|
||||
@mail Griatch=New mail/Hey man, I am sending you a message!
|
||||
@mail/delete 6
|
||||
@mail/forward feend78 Griatch=You guys should read this.
|
||||
@mail/forward feend78 Griatch=4/You guys should read this.
|
||||
@mail/reply 9=Thanks for the info!
|
||||
"""
|
||||
key = "@mail"
|
||||
|
|
@ -56,9 +60,67 @@ class CmdMail(default_cmds.MuxCommand):
|
|||
lock = "cmd:all()"
|
||||
help_category = "General"
|
||||
|
||||
def search_targets(self, namelist):
|
||||
"""
|
||||
Search a list of targets of the same type as caller.
|
||||
|
||||
Args:
|
||||
caller (Object or Player): The type of object to search.
|
||||
namelist (list): List of strings for objects to search for.
|
||||
|
||||
Returns:
|
||||
targetlist (list): List of matches, if any.
|
||||
|
||||
"""
|
||||
nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist))
|
||||
if hasattr(self.caller, "player") and self.caller.player:
|
||||
matches = list(ObjectDB.objects.filter(db_key__iregex=nameregex))
|
||||
else:
|
||||
matches = list(PlayerDB.objects.filter(username__iregex=nameregex))
|
||||
return matches
|
||||
|
||||
def get_all_mail(self):
|
||||
"""
|
||||
Returns a list of all the messages where the caller is a recipient.
|
||||
|
||||
Returns:
|
||||
messages (list): list of Msg objects.
|
||||
"""
|
||||
# mail_messages = Msg.objects.get_by_tag(category="mail")
|
||||
# messages = []
|
||||
try:
|
||||
player = self.caller.player
|
||||
except AttributeError:
|
||||
player = self.caller
|
||||
messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player)
|
||||
return messages
|
||||
|
||||
def send_mail(self, recipients, subject, message, caller):
|
||||
"""
|
||||
Function for sending new mail. Also useful for sending notifications from objects or systems.
|
||||
|
||||
Args:
|
||||
recipients (list): list of Player or character objects to receive the newly created mails.
|
||||
subject (str): The header or subject of the message to be delivered.
|
||||
message (str): The body of the message being sent.
|
||||
caller (obj): The object (or Player or Character) that is sending the message.
|
||||
"""
|
||||
for recipient in recipients:
|
||||
recipient.msg("You have received a new @mail from %s" % caller)
|
||||
new_message = create.create_message(self.caller, message, receivers=recipient, header=subject)
|
||||
new_message.tags.add("U", category="mail")
|
||||
|
||||
if recipients:
|
||||
caller.msg("You sent your message.")
|
||||
return
|
||||
else:
|
||||
caller.msg("No valid players found. Cannot send message.")
|
||||
return
|
||||
|
||||
def func(self):
|
||||
subject = ""
|
||||
body = ""
|
||||
|
||||
if self.switches or self.args:
|
||||
if "delete" in self.switches:
|
||||
try:
|
||||
|
|
@ -66,12 +128,15 @@ class CmdMail(default_cmds.MuxCommand):
|
|||
self.caller.msg("No Message ID given. Unable to delete.")
|
||||
return
|
||||
else:
|
||||
if self.get_all_mail()[int(self.lhs) - 1]:
|
||||
self.get_all_mail()[int(self.lhs) - 1].delete()
|
||||
all_mail = self.get_all_mail()
|
||||
mind = int(self.lhs) - 1
|
||||
if all_mail[mind]:
|
||||
all_mail[mind].delete()
|
||||
self.caller.msg("Message %s deleted" % self.lhs)
|
||||
else:
|
||||
self.caller.msg("That message does not exist.")
|
||||
return
|
||||
raise IndexError
|
||||
except IndexError:
|
||||
self.caller.msg("That message does not exist.")
|
||||
except ValueError:
|
||||
self.caller.msg("Usage: @mail/delete <message ID>")
|
||||
elif "forward" in self.switches:
|
||||
|
|
@ -83,29 +148,33 @@ class CmdMail(default_cmds.MuxCommand):
|
|||
self.caller.msg("You must define a message to forward.")
|
||||
return
|
||||
else:
|
||||
all_mail = self.get_all_mail()
|
||||
if "/" in self.rhs:
|
||||
message_number, message = self.rhs.split("/")
|
||||
if self.get_all_mail()[int(message_number) - 1]:
|
||||
old_message = self.get_all_mail()[int(message_number) - 1]
|
||||
mind = int(message_number) - 1
|
||||
|
||||
self.send_mail(self.lhslist, "FWD: " + old_message.header,
|
||||
if all_mail[mind]:
|
||||
old_message = all_mail[mind]
|
||||
|
||||
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header,
|
||||
message + "\n---- Original Message ----\n" + old_message.message,
|
||||
self.caller)
|
||||
self.caller.msg("Message forwarded.")
|
||||
else:
|
||||
self.caller.msg("Message does not exist.")
|
||||
return
|
||||
raise IndexError
|
||||
else:
|
||||
if self.get_all_mail()[int(self.rhs) - 1]:
|
||||
old_message = self.get_all_mail()[int(self.rhs) - 1]
|
||||
self.send_mail(self.lhslist, "FWD: " + old_message.header,
|
||||
mind = int(self.rhs) - 1
|
||||
if all_mail[mind]:
|
||||
old_message = all_mail[mind]
|
||||
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header,
|
||||
"\n---- Original Message ----\n" + old_message.message, self.caller)
|
||||
self.caller.msg("Message forwarded.")
|
||||
old_message.tags.remove("u", category="mail")
|
||||
old_message.tags.add("f", category="mail")
|
||||
else:
|
||||
self.caller.msg("Message does not exist.")
|
||||
return
|
||||
raise IndexError
|
||||
except IndexError:
|
||||
self.caller.msg("Message does not exixt.")
|
||||
except ValueError:
|
||||
self.caller.msg("Usage: @mail/forward <player list>=<#>[/<Message>]")
|
||||
elif "reply" in self.switches:
|
||||
|
|
@ -117,29 +186,33 @@ class CmdMail(default_cmds.MuxCommand):
|
|||
self.caller.msg("You must supply a reply message")
|
||||
return
|
||||
else:
|
||||
if self.get_all_mail()[int(self.lhs) - 1]:
|
||||
old_message = self.get_all_mail()[int(self.lhs) - 1]
|
||||
all_mail = self.get_all_mail()
|
||||
mind = int(self.lhs) - 1
|
||||
if all_mail[mind]:
|
||||
old_message = all_mail[mind]
|
||||
self.send_mail(old_message.senders, "RE: " + old_message.header,
|
||||
self.rhs + "\n---- Original Message ----\n" + old_message.message, self.caller)
|
||||
old_message.tags.remove("u", category="mail")
|
||||
old_message.tags.add("r", category="mail")
|
||||
return
|
||||
else:
|
||||
self.caller.msg("Message does not exist.")
|
||||
return
|
||||
raise IndexError
|
||||
except IndexError:
|
||||
self.caller.msg("Message does not exist.")
|
||||
except ValueError:
|
||||
self.caller.msg("Usage: @mail/reply <#>=<message>")
|
||||
else:
|
||||
# normal send
|
||||
if self.rhs:
|
||||
if "/" in self.rhs:
|
||||
subject, body = self.rhs.split("/", 1)
|
||||
else:
|
||||
body = self.rhs
|
||||
self.send_mail(self.lhslist, subject, body, self.caller)
|
||||
self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller)
|
||||
else:
|
||||
try:
|
||||
message = self.get_all_mail()[int(self.lhs) - 1]
|
||||
except ValueError:
|
||||
except (ValueError, IndexError):
|
||||
self.caller.msg("'%s' is not a valid mail id." % self.lhs)
|
||||
return
|
||||
|
||||
|
|
@ -176,47 +249,8 @@ class CmdMail(default_cmds.MuxCommand):
|
|||
table.reformat_column(4, width=7)
|
||||
|
||||
self.caller.msg(_HEAD_CHAR * _WIDTH)
|
||||
self.caller.msg(table)
|
||||
self.caller.msg(unicode(table))
|
||||
self.caller.msg(_HEAD_CHAR * _WIDTH)
|
||||
else:
|
||||
self.caller.msg("Sorry, you don't have any messages. What a pathetic loser!")
|
||||
self.caller.msg("There are no messages in your inbox.")
|
||||
|
||||
def get_all_mail(self):
|
||||
"""
|
||||
Returns a list of all the messages where the caller is a recipient.
|
||||
|
||||
Returns:
|
||||
messages (list): list of Msg objects.
|
||||
"""
|
||||
# mail_messages = Msg.objects.get_by_tag(category="mail")
|
||||
# messages = []
|
||||
messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=self.caller.player)
|
||||
return messages
|
||||
|
||||
def send_mail(self, recipients, subject, message, caller):
|
||||
"""
|
||||
Function for sending new mail. Also useful for sending notifications from objects or systems.
|
||||
|
||||
Args:
|
||||
recipients (list): list of Player or character objects to receive the newly created mails.
|
||||
subject (str): The header or subject of the message to be delivered.
|
||||
message (str): The body of the message being sent.
|
||||
caller (obj): The object (or Player or Character) that is sending the message.
|
||||
"""
|
||||
recobjs = []
|
||||
for char in recipients:
|
||||
|
||||
if self.caller.player.search(char) is not None:
|
||||
recobjs.append(self.caller.player.search(char))
|
||||
if recobjs:
|
||||
for recipient in recobjs:
|
||||
recipient.msg("You have received a new @mail from %s" % caller)
|
||||
|
||||
new_message = create.create_message(self.caller, message, receivers=recipient, header=subject)
|
||||
new_message.tags.add("U", category="mail")
|
||||
|
||||
caller.msg("You sent your message.")
|
||||
return
|
||||
else:
|
||||
caller.msg("No valid players found. Cannot send message.")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ from typeclasses import rooms, exits
|
|||
from random import randint
|
||||
import random
|
||||
|
||||
|
||||
# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an
|
||||
# island surrounded by water (≈). By giving no instructions for the water
|
||||
# characters we effectively skip it and create no rooms for those squares.
|
||||
|
|
@ -287,6 +288,88 @@ def _map_to_list(game_map):
|
|||
else character for character in list_map]
|
||||
|
||||
|
||||
def build_map(caller, game_map, legend, iterations=1, build_exits=True):
|
||||
"""
|
||||
Receives the fetched map and legend vars provided by the player.
|
||||
|
||||
Args:
|
||||
caller (Object): The creator of the map.
|
||||
game_map (str): An ASCII map string.
|
||||
legend (dict): Mapping of map symbols to object types.
|
||||
iterations (int): The number of iteration passes.
|
||||
build_exits (bool): Create exits between new rooms.
|
||||
|
||||
Notes:
|
||||
The map
|
||||
is iterated over character by character, comparing it to the trigger
|
||||
characters in the legend var and executing the build instructions on
|
||||
finding a match. The map is iterated over according to the `iterations`
|
||||
value and exits are optionally generated between adjacent rooms according
|
||||
to the `build_exits` value.
|
||||
|
||||
"""
|
||||
|
||||
# Split map string to list of rows and create reference list.
|
||||
caller.msg("Creating Map...")
|
||||
caller.msg(game_map)
|
||||
game_map = _map_to_list(game_map)
|
||||
|
||||
# Create a reference dictionary which be passed to build functions and
|
||||
# will store obj returned by build functions so objs can be referenced.
|
||||
room_dict = {}
|
||||
|
||||
caller.msg("Creating Landmass...")
|
||||
for iteration in xrange(iterations):
|
||||
for y in xrange(len(game_map)):
|
||||
for x in xrange(len(game_map[y])):
|
||||
for key in legend:
|
||||
# obs - we must use == for unicode
|
||||
if utils.to_unicode(game_map[y][x]) == utils.to_unicode(key):
|
||||
room = legend[key](x, y, iteration=iteration,
|
||||
room_dict=room_dict,
|
||||
caller=caller)
|
||||
if iteration == 0:
|
||||
room_dict[(x, y)] = room
|
||||
|
||||
if build_exits:
|
||||
# Creating exits. Assumes single room object in dict entry
|
||||
caller.msg("Connecting Areas...")
|
||||
for loc_key, location in room_dict.iteritems():
|
||||
x = loc_key[0]
|
||||
y = loc_key[1]
|
||||
|
||||
# north
|
||||
if (x, y-1) in room_dict:
|
||||
if room_dict[(x, y-1)]:
|
||||
create_object(exits.Exit, key="north",
|
||||
aliases=["n"], location=location,
|
||||
destination=room_dict[(x, y-1)])
|
||||
|
||||
# east
|
||||
if (x+1, y) in room_dict:
|
||||
if room_dict[(x+1, y)]:
|
||||
create_object(exits.Exit, key="east",
|
||||
aliases=["e"], location=location,
|
||||
destination=room_dict[(x+1, y)])
|
||||
|
||||
# south
|
||||
if (x, y+1) in room_dict:
|
||||
if room_dict[(x, y+1)]:
|
||||
create_object(exits.Exit, key="south",
|
||||
aliases=["s"], location=location,
|
||||
destination=room_dict[(x, y+1)])
|
||||
|
||||
# west
|
||||
if (x-1, y) in room_dict:
|
||||
if room_dict[(x-1, y)]:
|
||||
create_object(exits.Exit, key="west",
|
||||
aliases=["w"], location=location,
|
||||
destination=room_dict[(x-1, y)])
|
||||
|
||||
caller.msg("Map Created.")
|
||||
|
||||
# access command
|
||||
|
||||
class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
Build a map from a 2D ASCII map.
|
||||
|
|
@ -396,72 +479,3 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
|
|||
# Pass map and legend to the build function.
|
||||
build_map(caller, game_map, legend, iterations, build_exits)
|
||||
|
||||
|
||||
def build_map(caller, game_map, legend, iterations=1, build_exits=True):
|
||||
"""
|
||||
Receives the fetched map and legend vars provided by the player. The map
|
||||
is iterated over character by character, comparing it to the trigger
|
||||
characters in the legend var and executing the build instructions on
|
||||
finding a match. The map is iterated over according to the `iterations`
|
||||
value and exits are optionally generated between adjacent rooms according
|
||||
to the `build_exits` value.
|
||||
|
||||
"""
|
||||
|
||||
# Split map string to list of rows and create reference list.
|
||||
caller.msg("Creating Map...")
|
||||
caller.msg(game_map)
|
||||
game_map = _map_to_list(game_map)
|
||||
|
||||
# Create a reference dictionary which be passed to build functions and
|
||||
# will store obj returned by build functions so objs can be referenced.
|
||||
room_dict = {}
|
||||
|
||||
caller.msg("Creating Landmass...")
|
||||
for iteration in xrange(iterations):
|
||||
for y in xrange(len(game_map)):
|
||||
for x in xrange(len(game_map[y])):
|
||||
for key in legend:
|
||||
if game_map[y][x] in key:
|
||||
room = legend[key](x, y, iteration=iteration,
|
||||
room_dict=room_dict,
|
||||
caller=caller)
|
||||
if iteration == 0:
|
||||
room_dict[(x, y)] = room
|
||||
|
||||
if build_exits:
|
||||
# Creating exits. Assumes single room object in dict entry
|
||||
caller.msg("Connecting Areas...")
|
||||
for loc_key, location in room_dict.iteritems():
|
||||
x = loc_key[0]
|
||||
y = loc_key[1]
|
||||
|
||||
# north
|
||||
if (x, y-1) in room_dict:
|
||||
if room_dict[(x, y-1)]:
|
||||
create_object(exits.Exit, key="north",
|
||||
aliases=["n"], location=location,
|
||||
destination=room_dict[(x, y-1)])
|
||||
|
||||
# east
|
||||
if (x+1, y) in room_dict:
|
||||
if room_dict[(x+1, y)]:
|
||||
create_object(exits.Exit, key="east",
|
||||
aliases=["e"], location=location,
|
||||
destination=room_dict[(x+1, y)])
|
||||
|
||||
# south
|
||||
if (x, y+1) in room_dict:
|
||||
if room_dict[(x, y+1)]:
|
||||
create_object(exits.Exit, key="south",
|
||||
aliases=["s"], location=location,
|
||||
destination=room_dict[(x, y+1)])
|
||||
|
||||
# west
|
||||
if (x-1, y) in room_dict:
|
||||
if room_dict[(x-1, y)]:
|
||||
create_object(exits.Exit, key="west",
|
||||
aliases=["w"], location=location,
|
||||
destination=room_dict[(x-1, y)])
|
||||
|
||||
caller.msg("Map Created.")
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ Contribution - Griatch 2016
|
|||
|
||||
A simple two-way exit that represents a door that can be opened and
|
||||
closed. Can easily be expanded from to make it lockable, destroyable
|
||||
etc.
|
||||
etc. Note that the simpledoor is based on Evennia locks, so it will
|
||||
not work for a superuser (which bypasses all locks) - the superuser
|
||||
will always appear to be able to close/open the door over and over
|
||||
without the locks stopping you. To use the door, use `@quell` or a
|
||||
non-superuser account.
|
||||
|
||||
Installation:
|
||||
|
||||
|
|
|
|||
|
|
@ -135,9 +135,10 @@ class CmdStop(Command):
|
|||
stored deferred from the exit traversal above.
|
||||
"""
|
||||
currently_moving = self.caller.ndb.currently_moving
|
||||
if currently_moving:
|
||||
if currently_moving and not currently_moving.called:
|
||||
currently_moving.cancel()
|
||||
self.caller.msg("You stop moving.")
|
||||
self.caller.location.msg_contents("%s stops." % self.get_display_name())
|
||||
for observer in self.caller.location.contents_get(self.caller):
|
||||
observer.msg("%s stops." % self.caller.get_display_name(observer))
|
||||
else:
|
||||
self.caller.msg("You are not moving.")
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Testing suite for contrib folder
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
import datetime
|
||||
from evennia.commands.default.tests import CommandTest
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from mock import Mock
|
||||
from mock import Mock, patch
|
||||
|
||||
# Testing of rplanguage module
|
||||
|
||||
|
|
@ -168,44 +169,52 @@ class TestRPSystem(EvenniaTest):
|
|||
|
||||
# Testing of ExtendedRoom contrib
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.contrib import extended_room
|
||||
from evennia import gametime
|
||||
from evennia.objects.objects import DefaultRoom
|
||||
|
||||
# mock gametime to return 7th month, 10 in morning
|
||||
gametime.gametime = Mock(return_value=(None, 7, None, None, 10))
|
||||
# mock settings so we're not affected by a given server's hours of day/months in year
|
||||
settings.TIME_MONTH_PER_YEAR = 12
|
||||
settings.TIME_HOUR_PER_DAY = 24
|
||||
class ForceUTCDatetime(datetime.datetime):
|
||||
|
||||
"""Force UTC datetime."""
|
||||
|
||||
@classmethod
|
||||
def fromtimestamp(cls, timestamp):
|
||||
"""Force fromtimestamp to run with naive datetimes."""
|
||||
return datetime.datetime.utcfromtimestamp(timestamp)
|
||||
|
||||
@patch('evennia.contrib.extended_room.datetime.datetime', ForceUTCDatetime)
|
||||
class TestExtendedRoom(CommandTest):
|
||||
room_typeclass = extended_room.ExtendedRoom
|
||||
DETAIL_DESC = "A test detail."
|
||||
SUMMER_DESC = "A summer description."
|
||||
SPRING_DESC = "A spring description."
|
||||
OLD_DESC = "Old description."
|
||||
settings.TIME_ZONE = "UTC"
|
||||
|
||||
def setUp(self):
|
||||
super(TestExtendedRoom, self).setUp()
|
||||
self.room1.ndb.last_timeslot = "night"
|
||||
self.room1.ndb.last_timeslot = "afternoon"
|
||||
self.room1.ndb.last_season = "winter"
|
||||
self.room1.db.details = {'testdetail': self.DETAIL_DESC}
|
||||
self.room1.db.summer_desc = self.SUMMER_DESC
|
||||
self.room1.db.spring_desc = self.SPRING_DESC
|
||||
self.room1.db.desc = self.OLD_DESC
|
||||
# mock gametime to return April 9, 2064, at 21:06 (spring evening)
|
||||
gametime.gametime = Mock(return_value=2975000766)
|
||||
|
||||
def test_return_appearance(self):
|
||||
# get the appearance of a non-extended room for contrast purposes
|
||||
old_desc = DefaultRoom.return_appearance(self.room1, self.char1)
|
||||
# the new appearance should be the old one, but with the desc switched
|
||||
self.assertEqual(old_desc.replace(self.OLD_DESC, self.SUMMER_DESC), self.room1.return_appearance(self.char1))
|
||||
self.assertEqual("summer", self.room1.ndb.last_season)
|
||||
self.assertEqual("morning", self.room1.ndb.last_timeslot)
|
||||
self.assertEqual(old_desc.replace(self.OLD_DESC, self.SPRING_DESC),
|
||||
self.room1.return_appearance(self.char1))
|
||||
self.assertEqual("spring", self.room1.ndb.last_season)
|
||||
self.assertEqual("evening", self.room1.ndb.last_timeslot)
|
||||
|
||||
def test_return_detail(self):
|
||||
self.assertEqual(self.DETAIL_DESC, self.room1.return_detail("testdetail"))
|
||||
|
||||
def test_cmdextendedlook(self):
|
||||
self.call(extended_room.CmdExtendedLook(), "here", "Room(#1)\n%s" % self.SUMMER_DESC)
|
||||
self.call(extended_room.CmdExtendedLook(), "here", "Room(#1)\n%s" % self.SPRING_DESC)
|
||||
self.call(extended_room.CmdExtendedLook(), "testdetail", self.DETAIL_DESC)
|
||||
self.call(extended_room.CmdExtendedLook(), "nonexistent", "Could not find 'nonexistent'.")
|
||||
|
||||
|
|
@ -219,7 +228,7 @@ class TestExtendedRoom(CommandTest):
|
|||
self.call(extended_room.CmdExtendedDesc(), "", "Descriptions on Room:")
|
||||
|
||||
def test_cmdgametime(self):
|
||||
self.call(extended_room.CmdGameTime(), "", "It's a summer day, in the morning.")
|
||||
self.call(extended_room.CmdGameTime(), "", "It's a spring day, in the evening.")
|
||||
|
||||
|
||||
# Test the contrib barter system
|
||||
|
|
@ -306,11 +315,11 @@ class TestBarter(CommandTest):
|
|||
self.call(barter.CmdTradeHelp(), "", "Trading commands\n", caller=self.char1)
|
||||
self.call(barter.CmdFinish(), ": Ending.", "You say, \"Ending.\"\n [You aborted trade. No deal was made.]")
|
||||
|
||||
# Test wilderness
|
||||
|
||||
from evennia.contrib import wilderness
|
||||
from evennia import DefaultCharacter
|
||||
|
||||
|
||||
class TestWilderness(EvenniaTest):
|
||||
|
||||
def setUp(self):
|
||||
|
|
@ -426,3 +435,323 @@ class TestWilderness(EvenniaTest):
|
|||
for direction, correct_loc in directions.iteritems(): # Not compatible with Python 3
|
||||
new_loc = wilderness.get_new_coordinates(loc, direction)
|
||||
self.assertEquals(new_loc, correct_loc, direction)
|
||||
|
||||
# Testing chargen contrib
|
||||
from evennia.contrib import chargen
|
||||
|
||||
class TestChargen(CommandTest):
|
||||
|
||||
def test_ooclook(self):
|
||||
self.call(chargen.CmdOOCLook(), "foo", "You have no characters to look at", caller=self.player)
|
||||
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.", caller=self.player)
|
||||
|
||||
def test_charcreate(self):
|
||||
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "The character testchar was successfully created!", caller=self.player)
|
||||
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "Character testchar already exists.", caller=self.player)
|
||||
self.assertTrue(self.player.db._character_dbrefs)
|
||||
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.",caller=self.player)
|
||||
self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.player)
|
||||
|
||||
# Testing custom_gametime
|
||||
from evennia.contrib import custom_gametime
|
||||
|
||||
def _testcallback():
|
||||
pass
|
||||
|
||||
class TestCustomGameTime(EvenniaTest):
|
||||
def setUp(self):
|
||||
super(TestCustomGameTime, self).setUp()
|
||||
gametime.gametime = Mock(return_value=2975000898.46) # does not seem to work
|
||||
def tearDown(self):
|
||||
if hasattr(self, "timescript"):
|
||||
self.timescript.stop()
|
||||
def test_time_to_tuple(self):
|
||||
self.assertEqual(custom_gametime.time_to_tuple(10000, 34,2,4,6,1), (294, 2, 0, 0, 0, 0))
|
||||
self.assertEqual(custom_gametime.time_to_tuple(10000, 3,3,4), (3333, 0, 0, 1))
|
||||
self.assertEqual(custom_gametime.time_to_tuple(100000, 239,24,3), (418, 4, 0, 2))
|
||||
def test_gametime_to_realtime(self):
|
||||
self.assertEqual(custom_gametime.gametime_to_realtime(days=2, mins=4), 86520.0)
|
||||
self.assertEqual(custom_gametime.gametime_to_realtime(format=True, days=2), (0,0,0,1,0,0,0))
|
||||
def test_realtime_to_gametime(self):
|
||||
self.assertEqual(custom_gametime.realtime_to_gametime(days=2, mins=34), 349680.0)
|
||||
self.assertEqual(custom_gametime.realtime_to_gametime(days=2, mins=34, format=True), (0, 0, 0, 4, 1, 8, 0))
|
||||
self.assertEqual(custom_gametime.realtime_to_gametime(format=True, days=2, mins=4), (0, 0, 0, 4, 0, 8, 0))
|
||||
def test_custom_gametime(self):
|
||||
self.assertEqual(custom_gametime.custom_gametime(), (102, 5, 2, 6, 21, 8, 18))
|
||||
self.assertEqual(custom_gametime.custom_gametime(absolute=True), (102, 5, 2, 6, 21, 8, 18))
|
||||
def test_real_seconds_until(self):
|
||||
self.assertEqual(custom_gametime.real_seconds_until(year=2300, month=11, day=6), 31911667199.77)
|
||||
def test_schedule(self):
|
||||
self.timescript = custom_gametime.schedule(_testcallback, repeat=True, min=5, sec=0)
|
||||
self.assertEqual(self.timescript.interval, 1700.7699999809265)
|
||||
|
||||
# Test dice module
|
||||
|
||||
|
||||
@patch('random.randint', return_value=5)
|
||||
class TestDice(CommandTest):
|
||||
def test_roll_dice(self, mocked_randint):
|
||||
# we must import dice here for the mocked randint to apply correctly.
|
||||
from evennia.contrib import dice
|
||||
self.assertEqual(dice.roll_dice(6, 6, modifier=('+', 4)), mocked_randint()*6 + 4)
|
||||
self.assertEqual(dice.roll_dice(6, 6, conditional=('<', 35)), True)
|
||||
self.assertEqual(dice.roll_dice(6, 6, conditional=('>', 33)), False)
|
||||
def test_cmddice(self, mocked_randint):
|
||||
from evennia.contrib import dice
|
||||
self.call(dice.CmdDice(), "3d6 + 4", "You roll 3d6 + 4.| Roll(s): 5, 5 and 5. Total result is 19.")
|
||||
self.call(dice.CmdDice(), "100000d1000", "The maximum roll allowed is 10000d10000.")
|
||||
self.call(dice.CmdDice(), "/secret 3d6 + 4", "You roll 3d6 + 4 (secret, not echoed).")
|
||||
|
||||
# Test email-login
|
||||
|
||||
from evennia.contrib import email_login
|
||||
|
||||
class TestEmailLogin(CommandTest):
|
||||
def test_connect(self):
|
||||
self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test", "The email 'mytest@test.com' does not match any accounts.")
|
||||
self.call(email_login.CmdUnconnectedCreate(), '"mytest" mytest@test.com test11111', "A new account 'mytest' was created. Welcome!")
|
||||
self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.player.sessions.get()[0])
|
||||
def test_quit(self):
|
||||
self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.player.sessions.get()[0])
|
||||
def test_unconnectedlook(self):
|
||||
self.call(email_login.CmdUnconnectedLook(), "", "==========")
|
||||
def test_unconnectedhelp(self):
|
||||
self.call(email_login.CmdUnconnectedHelp(), "", "You are not yet logged into the game.")
|
||||
|
||||
# test gendersub contrib
|
||||
|
||||
from evennia.contrib import gendersub
|
||||
|
||||
class TestGenderSub(CommandTest):
|
||||
def test_setgender(self):
|
||||
self.call(gendersub.SetGender(), "male", "Your gender was set to male.")
|
||||
self.call(gendersub.SetGender(), "ambiguous", "Your gender was set to ambiguous.")
|
||||
self.call(gendersub.SetGender(), "Foo", "Usage: @gender")
|
||||
def test_gendercharacter(self):
|
||||
char = create_object(gendersub.GenderCharacter, key="Gendered", location=self.room1)
|
||||
txt = "Test |p gender"
|
||||
self.assertEqual(gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender")
|
||||
|
||||
# test mail contrib
|
||||
|
||||
from evennia.contrib import mail
|
||||
|
||||
class TestMail(CommandTest):
|
||||
def test_mail(self):
|
||||
self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.player)
|
||||
self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.player)
|
||||
self.call(mail.CmdMail(), "Char=Message 1", "You have received a new @mail from Char|You sent your message.", caller=self.char1)
|
||||
self.call(mail.CmdMail(), "Char=Message 2", "You sent your message.", caller=self.char2)
|
||||
self.call(mail.CmdMail(), "TestPlayer2=Message 2",
|
||||
"You have received a new @mail from TestPlayer2(player 2)|You sent your message.", caller=self.player2)
|
||||
self.call(mail.CmdMail(), "TestPlayer=Message 1", "You sent your message.", caller=self.player2)
|
||||
self.call(mail.CmdMail(), "TestPlayer=Message 2", "You sent your message.", caller=self.player2)
|
||||
self.call(mail.CmdMail(), "", "| ID: From: Subject:", caller=self.player)
|
||||
self.call(mail.CmdMail(), "2", "From: TestPlayer2", caller=self.player)
|
||||
self.call(mail.CmdMail(), "/forward TestPlayer2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.player)
|
||||
self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.player)
|
||||
self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.player)
|
||||
|
||||
# test map builder contrib
|
||||
|
||||
from evennia.contrib import mapbuilder
|
||||
|
||||
class TestMapBuilder(CommandTest):
|
||||
def test_cmdmapbuilder(self):
|
||||
self.call(mapbuilder.CmdMapBuilder(),
|
||||
"evennia.contrib.mapbuilder.EXAMPLE1_MAP evennia.contrib.mapbuilder.EXAMPLE1_LEGEND",
|
||||
"""Creating Map...|≈≈≈≈≈
|
||||
≈♣n♣≈
|
||||
≈∩▲∩≈
|
||||
≈♠n♠≈
|
||||
≈≈≈≈≈
|
||||
|Creating Landmass...|""")
|
||||
self.call(mapbuilder.CmdMapBuilder(),
|
||||
"evennia.contrib.mapbuilder.EXAMPLE2_MAP evennia.contrib.mapbuilder.EXAMPLE2_LEGEND",
|
||||
"""Creating Map...|≈ ≈ ≈ ≈ ≈
|
||||
|
||||
≈ ♣♣♣ ≈
|
||||
≈ ♣ ♣ ♣ ≈
|
||||
≈ ♣♣♣ ≈
|
||||
|
||||
≈ ≈ ≈ ≈ ≈
|
||||
|Creating Landmass...|""")
|
||||
|
||||
|
||||
# test menu_login
|
||||
|
||||
from evennia.contrib import menu_login
|
||||
|
||||
class TestMenuLogin(CommandTest):
|
||||
def test_cmdunloggedlook(self):
|
||||
self.call(menu_login.CmdUnloggedinLook(), "", "======")
|
||||
|
||||
|
||||
# test multidescer contrib
|
||||
|
||||
from evennia.contrib import multidescer
|
||||
|
||||
class TestMultidescer(CommandTest):
|
||||
def test_cmdmultidesc(self):
|
||||
self.call(multidescer.CmdMultiDesc(),"/list", "Stored descs:\ncaller:")
|
||||
self.call(multidescer.CmdMultiDesc(),"test = Desc 1", "Stored description 'test': \"Desc 1\"")
|
||||
self.call(multidescer.CmdMultiDesc(),"test2 = Desc 2", "Stored description 'test2': \"Desc 2\"")
|
||||
self.call(multidescer.CmdMultiDesc(),"/swap test-test2", "Swapped descs 'test' and 'test2'.")
|
||||
self.call(multidescer.CmdMultiDesc(),"test3 = Desc 3init", "Stored description 'test3': \"Desc 3init\"")
|
||||
self.call(multidescer.CmdMultiDesc(),"/list", "Stored descs:\ntest3: Desc 3init\ntest: Desc 1\ntest2: Desc 2\ncaller:")
|
||||
self.call(multidescer.CmdMultiDesc(),"test3 = Desc 3", "Stored description 'test3': \"Desc 3\"")
|
||||
self.call(multidescer.CmdMultiDesc(),"/set test1 + test2 + + test3", "test1 Desc 2 Desc 3\n\n"
|
||||
"The above was set as the current description.")
|
||||
self.assertEqual(self.char1.db.desc, "test1 Desc 2 Desc 3")
|
||||
|
||||
# test simpledoor contrib
|
||||
|
||||
from evennia.contrib import simpledoor
|
||||
|
||||
class TestSimpleDoor(CommandTest):
|
||||
def test_cmdopen(self):
|
||||
self.call(simpledoor.CmdOpen(), "newdoor;door:contrib.simpledoor.SimpleDoor,backdoor;door = Room2",
|
||||
"Created new Exit 'newdoor' from Room to Room2 (aliases: door).|Note: A doortype exit was "
|
||||
"created ignored eventual custom returnexit type.|Created new Exit 'newdoor' from Room2 to Room (aliases: door).")
|
||||
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You close newdoor.", cmdstring="close")
|
||||
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "newdoor is already closed.", cmdstring="close")
|
||||
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You open newdoor.", cmdstring="open")
|
||||
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "newdoor is already open.", cmdstring="open")
|
||||
|
||||
# test slow_exit contrib
|
||||
|
||||
from evennia.contrib import slow_exit
|
||||
slow_exit.MOVE_DELAY = {"stroll":0, "walk": 0, "run": 0, "sprint": 0}
|
||||
|
||||
class TestSlowExit(CommandTest):
|
||||
def test_exit(self):
|
||||
exi = create_object(slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2)
|
||||
exi.at_traverse(self.char1, self.room2)
|
||||
self.call(slow_exit.CmdSetSpeed(), "walk", "You are now walking.")
|
||||
self.call(slow_exit.CmdStop(), "", "You stop moving.")
|
||||
|
||||
# test talking npc contrib
|
||||
|
||||
from evennia.contrib import talking_npc
|
||||
|
||||
class TestTalkingNPC(CommandTest):
|
||||
def test_talkingnpc(self):
|
||||
npc = create_object(talking_npc.TalkingNPC, key="npctalker", location=self.room1)
|
||||
self.call(talking_npc.CmdTalk(), "","(You walk up and talk to Char.)|")
|
||||
npc.delete()
|
||||
|
||||
|
||||
# tests for the tutorial world
|
||||
|
||||
# test tutorial_world/mob
|
||||
|
||||
from evennia.contrib.tutorial_world import mob
|
||||
|
||||
class TestTutorialWorldMob(EvenniaTest):
|
||||
def test_mob(self):
|
||||
mobobj = create_object(mob.Mob, key="mob")
|
||||
self.assertEqual(mobobj.db.is_dead, True)
|
||||
mobobj.set_alive()
|
||||
self.assertEqual(mobobj.db.is_dead, False)
|
||||
mobobj.set_dead()
|
||||
self.assertEqual(mobobj.db.is_dead, True)
|
||||
mobobj._set_ticker(0, "foo", stop=True)
|
||||
#TODO should be expanded with further tests of the modes and damage etc.
|
||||
|
||||
# test tutorial_world/objects
|
||||
|
||||
from evennia.contrib.tutorial_world import objects as tutobjects
|
||||
|
||||
class TestTutorialWorldObjects(CommandTest):
|
||||
def test_tutorialobj(self):
|
||||
obj1 = create_object(tutobjects.TutorialObject, key="tutobj")
|
||||
obj1.reset()
|
||||
self.assertEqual(obj1.location, obj1.home)
|
||||
def test_readable(self):
|
||||
readable = create_object(tutobjects.Readable, key="book", location=self.room1)
|
||||
readable.db.readable_text = "Text to read"
|
||||
self.call(tutobjects.CmdRead(), "book","You read book:\n Text to read", obj=readable)
|
||||
def test_climbable(self):
|
||||
climbable = create_object(tutobjects.Climbable, key="tree", location=self.room1)
|
||||
self.call(tutobjects.CmdClimb(), "tree", "You climb tree. Having looked around, you climb down again.", obj=climbable)
|
||||
self.assertEqual(self.char1.tags.get("tutorial_climbed_tree", category="tutorial_world"), "tutorial_climbed_tree")
|
||||
def test_obelisk(self):
|
||||
obelisk = create_object(tutobjects.Obelisk, key="obelisk", location=self.room1)
|
||||
self.assertEqual(obelisk.return_appearance(self.char1).startswith("|cobelisk("), True)
|
||||
def test_lightsource(self):
|
||||
light = create_object(tutobjects.LightSource, key="torch", location=self.room1)
|
||||
self.call(tutobjects.CmdLight(), "", "You light torch.", obj=light)
|
||||
light._burnout()
|
||||
if hasattr(light, "deferred"):
|
||||
light.deferred.cancel()
|
||||
self.assertFalse(light.pk)
|
||||
def test_crumblingwall(self):
|
||||
wall = create_object(tutobjects.CrumblingWall, key="wall", location=self.room1)
|
||||
self.assertFalse(wall.db.button_exposed)
|
||||
self.assertFalse(wall.db.exit_open)
|
||||
wall.db.root_pos = {"yellow":0, "green":0,"red":0,"blue":0}
|
||||
self.call(tutobjects.CmdShiftRoot(), "blue root right",
|
||||
"You shove the root adorned with small blue flowers to the right.", obj=wall)
|
||||
self.call(tutobjects.CmdShiftRoot(), "red root left",
|
||||
"You shift the reddish root to the left.", obj=wall)
|
||||
self.call(tutobjects.CmdShiftRoot(), "yellow root down",
|
||||
"You shove the root adorned with small yellow flowers downwards.", obj=wall)
|
||||
self.call(tutobjects.CmdShiftRoot(), "green root up",
|
||||
"You shift the weedy green root upwards.|Holding aside the root you think you notice something behind it ...", obj=wall)
|
||||
self.call(tutobjects.CmdPressButton(), "",
|
||||
"You move your fingers over the suspicious depression, then gives it a decisive push. First", obj=wall)
|
||||
self.assertTrue(wall.db.button_exposed)
|
||||
self.assertTrue(wall.db.exit_open)
|
||||
wall.reset()
|
||||
if hasattr(wall, "deferred"):
|
||||
wall.deferred.cancel()
|
||||
wall.delete()
|
||||
def test_weapon(self):
|
||||
weapon = create_object(tutobjects.Weapon, key="sword", location=self.char1)
|
||||
self.call(tutobjects.CmdAttack(), "Char", "You stab with sword.", obj=weapon, cmdstring="stab")
|
||||
self.call(tutobjects.CmdAttack(), "Char", "You slash with sword.", obj=weapon, cmdstring="slash")
|
||||
def test_weaponrack(self):
|
||||
rack = create_object(tutobjects.WeaponRack, key="rack", location=self.room1)
|
||||
rack.db.available_weapons = ["sword"]
|
||||
self.call(tutobjects.CmdGetWeapon(), "", "You find Rusty sword.", obj=rack)
|
||||
|
||||
# test tutorial_world/
|
||||
from evennia.contrib.tutorial_world import rooms as tutrooms
|
||||
|
||||
class TestTutorialWorldRooms(CommandTest):
|
||||
def test_cmdtutorial(self):
|
||||
room = create_object(tutrooms.TutorialRoom, key="tutroom")
|
||||
self.char1.location = room
|
||||
self.call(tutrooms.CmdTutorial(), "", "Sorry, there is no tutorial help available here.")
|
||||
self.call(tutrooms.CmdTutorialSetDetail(), "detail;foo;foo2 = A detail", "Detail set: 'detail;foo;foo2': 'A detail'", obj=room)
|
||||
self.call(tutrooms.CmdTutorialLook(), "", "tutroom(", obj=room)
|
||||
self.call(tutrooms.CmdTutorialLook(), "detail", "A detail", obj=room)
|
||||
self.call(tutrooms.CmdTutorialLook(), "foo", "A detail", obj=room)
|
||||
room.delete()
|
||||
def test_weatherroom(self):
|
||||
room = create_object(tutrooms.WeatherRoom, key="weatherroom")
|
||||
room.update_weather()
|
||||
tutrooms.TICKER_HANDLER.remove(interval=room.db.interval, callback=room.update_weather, idstring="tutorial")
|
||||
room.delete()
|
||||
def test_introroom(self):
|
||||
room = create_object(tutrooms.IntroRoom, key="introroom")
|
||||
room.at_object_receive(self.char1, self.room1)
|
||||
def test_bridgeroom(self):
|
||||
room = create_object(tutrooms.BridgeRoom, key="bridgeroom")
|
||||
room.update_weather()
|
||||
self.char1.move_to(room)
|
||||
self.call(tutrooms.CmdBridgeHelp(), "", "You are trying hard not to fall off the bridge ...", obj=room)
|
||||
self.call(tutrooms.CmdLookBridge(), "", "bridgeroom\nYou are standing very close to the the bridge's western foundation.", obj=room)
|
||||
room.at_object_leave(self.char1, self.room1)
|
||||
tutrooms.TICKER_HANDLER.remove(interval=room.db.interval, callback=room.update_weather, idstring="tutorial")
|
||||
room.delete()
|
||||
def test_darkroom(self):
|
||||
room = create_object(tutrooms.DarkRoom, key="darkroom")
|
||||
self.char1.move_to(room)
|
||||
self.call(tutrooms.CmdDarkHelp(), "", "Can't help you until")
|
||||
def test_teleportroom(self):
|
||||
create_object(tutrooms.TeleportRoom, key="teleportroom")
|
||||
def test_outroroom(self):
|
||||
create_object(tutrooms.OutroRoom, key="outroroom")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Example script for testing. This adds a simple timer that has your
|
|||
character make observations and notices at irregular intervals.
|
||||
|
||||
To test, use
|
||||
@script me = examples.bodyfunctions.BodyFunctions
|
||||
@script me = tutorial_examples.bodyfunctions.BodyFunctions
|
||||
|
||||
The script will only send messages to the object it is stored on, so
|
||||
make sure to put it on yourself or you won't see any messages!
|
||||
|
|
|
|||
|
|
@ -367,8 +367,9 @@ class LightSource(TutorialObject):
|
|||
pass
|
||||
finally:
|
||||
# start the burn timer. When it runs out, self._burnout
|
||||
# will be called.
|
||||
utils.delay(60 * 3, self._burnout)
|
||||
# will be called. We store the deferred so it can be
|
||||
# killed in unittesting.
|
||||
self.deferred = utils.delay(60 * 3, self._burnout)
|
||||
return True
|
||||
|
||||
|
||||
|
|
@ -636,9 +637,10 @@ class CrumblingWall(TutorialObject, DefaultExit):
|
|||
self.caller.msg("The exit leads nowhere, there's just more stone behind it ...")
|
||||
else:
|
||||
self.destination = eloc[0]
|
||||
self.exit_open = True
|
||||
# start a 45 second timer before closing again
|
||||
utils.delay(45, self.reset)
|
||||
self.db.exit_open = True
|
||||
# start a 45 second timer before closing again. We store the deferred so it can be
|
||||
# killed in unittesting.
|
||||
self.deferred = utils.delay(45, self.reset)
|
||||
|
||||
def _translate_position(self, root, ipos):
|
||||
"""Translates the position into words"""
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ class TutorialRoom(DefaultRoom):
|
|||
# These are rainy weather strings
|
||||
WEATHER_STRINGS = (
|
||||
"The rain coming down from the iron-grey sky intensifies.",
|
||||
"A gush of wind throws the rain right in your face. Despite your cloak you shiver.",
|
||||
"A gust of wind throws the rain right in your face. Despite your cloak you shiver.",
|
||||
"The rainfall eases a bit and the sky momentarily brightens.",
|
||||
"For a moment it looks like the rain is slowing, then it begins anew with renewed force.",
|
||||
"The rain pummels you with large, heavy drops. You hear the rumble of thunder in the distance.",
|
||||
|
|
@ -313,8 +313,8 @@ class WeatherRoom(TutorialRoom):
|
|||
# subscribe ourselves to a ticker to repeatedly call the hook
|
||||
# "update_weather" on this object. The interval is randomized
|
||||
# so as to not have all weather rooms update at the same time.
|
||||
interval = random.randint(50, 70)
|
||||
TICKER_HANDLER.add(interval=interval, callback=self.update_weather, idstring="tutorial")
|
||||
self.db.interval = random.randint(50, 70)
|
||||
TICKER_HANDLER.add(interval=self.db.interval, callback=self.update_weather, idstring="tutorial")
|
||||
# this is parsed by the 'tutorial' command on TutorialRooms.
|
||||
self.db.tutorial_info = \
|
||||
"This room has a Script running that has it echo a weather-related message at irregular intervals."
|
||||
|
|
@ -590,7 +590,7 @@ class BridgeCmdSet(CmdSet):
|
|||
|
||||
BRIDGE_WEATHER = (
|
||||
"The rain intensifies, making the planks of the bridge even more slippery.",
|
||||
"A gush of wind throws the rain right in your face.",
|
||||
"A gust of wind throws the rain right in your face.",
|
||||
"The rainfall eases a bit and the sky momentarily brightens.",
|
||||
"The bridge shakes under the thunder of a closeby thunder strike.",
|
||||
"The rain pummels you with large, heavy drops. You hear the distinct howl of a large hound in the distance.",
|
||||
|
|
|
|||
|
|
@ -216,20 +216,20 @@ msgstr ""
|
|||
#: server/initial_setup.py:29
|
||||
msgid ""
|
||||
"\n"
|
||||
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if "
|
||||
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
|
||||
"you need\n"
|
||||
"help, want to contribute, report issues or just join the community.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with {w@batchcommand "
|
||||
"tutorial_world.build{n.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with |w@batchcommand "
|
||||
"tutorial_world.build|n.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Bienvenue dans ton nouveau jeu basé sur {wEvennia{n ! Visitez http://www."
|
||||
"Bienvenue dans ton nouveau jeu basé sur |wEvennia|n ! Visitez http://www."
|
||||
"evennia.com si vous avez besoin\n"
|
||||
"d'aide, si vous voulez contribuer, rapporter des problèmes ou faire partie "
|
||||
"de la communauté.\n"
|
||||
"En tant que Joueur #1 vous pouvez créer une zone de démo/tutoriel avec "
|
||||
"{w@batchcommand tutorial_world.build{n.\n"
|
||||
"|w@batchcommand tutorial_world.build|n.\n"
|
||||
" "
|
||||
|
||||
#: server/initial_setup.py:102
|
||||
|
|
|
|||
|
|
@ -221,16 +221,16 @@ msgstr "Aggiorna l'handler del canale."
|
|||
#: .\server\initial_setup.py:29
|
||||
msgid ""
|
||||
"\n"
|
||||
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if you need\n"
|
||||
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need\n"
|
||||
"help, want to contribute, report issues or just join the community.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with {w@batchcommand tutorial_world.build{n.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Benvenuto al tuo nuovo gioco creato con {wEvennia{n! Visita http://www.evennia.com se ti\n"
|
||||
"Benvenuto al tuo nuovo gioco creato con |wEvennia|n! Visita http://www.evennia.com se ti\n"
|
||||
"serve aiuto, se vuoi collaborare, segnalare errori o se desideri unirti alla comunità online.\n"
|
||||
"In qualità di Giocatore #1 puoi creare un'area dimostrativa/tutorial digitando il comando:\n"
|
||||
"{w@batchcommand tutorial_world.build{n.\n"
|
||||
"|w@batchcommand tutorial_world.build|n.\n"
|
||||
" "
|
||||
|
||||
#: .\server\initial_setup.py:99
|
||||
|
|
|
|||
|
|
@ -208,19 +208,19 @@ msgstr ""
|
|||
#: server/initial_setup.py:30
|
||||
msgid ""
|
||||
"\n"
|
||||
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if "
|
||||
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
|
||||
"you need\n"
|
||||
"help, want to contribute, report issues or just join the community.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with {w@batchcommand "
|
||||
"tutorial_world.build{n.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with |w@batchcommand "
|
||||
"tutorial_world.build|n.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Bem-vindo a seu novo jogo criado com {wEvennia{n! Visite http://www.evennia.com\n"
|
||||
"Bem-vindo a seu novo jogo criado com |wEvennia|n! Visite http://www.evennia.com\n"
|
||||
"se você precisar de ajuda, desejar contribuir, reportar bugs ou apenas\n"
|
||||
"juntar-se à comunidade.\n"
|
||||
"Como Player #1 você pode criar uma área demonstrativa/tutorial digitando\n"
|
||||
"o comando {w@batchcommand tutorial_world.build{n.\n"
|
||||
"o comando |w@batchcommand tutorial_world.build|n.\n"
|
||||
" "
|
||||
|
||||
#: server/initial_setup.py:103
|
||||
|
|
|
|||
|
|
@ -214,11 +214,11 @@ msgstr "Detta är en generisk lagringskontainer."
|
|||
#: server/initial_setup.py:29
|
||||
msgid ""
|
||||
"\n"
|
||||
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if "
|
||||
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
|
||||
"you need\n"
|
||||
"help, want to contribute, report issues or just join the community.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with {w@batchcommand "
|
||||
"tutorial_world.build{n.\n"
|
||||
"As Player #1 you can create a demo/tutorial area with |w@batchcommand "
|
||||
"tutorial_world.build|n.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -40,16 +40,21 @@ class ObjectCreateForm(forms.ModelForm):
|
|||
fields = '__all__'
|
||||
db_key = forms.CharField(label="Name/Key",
|
||||
widget=forms.TextInput(attrs={'size': '78'}),
|
||||
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
|
||||
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. "
|
||||
"If creating a Character, check so the name is unique among characters!",)
|
||||
db_typeclass_path = forms.CharField(label="Typeclass",
|
||||
initial=settings.BASE_OBJECT_TYPECLASS,
|
||||
widget=forms.TextInput(attrs={'size': '78'}),
|
||||
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
|
||||
help_text="This defines what 'type' of entity this is. This variable holds a "
|
||||
"Python path to a module with a valid Evennia Typeclass. If you are "
|
||||
"creating a Character you should use the typeclass defined by "
|
||||
"settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
|
||||
db_cmdset_storage = forms.CharField(label="CmdSet",
|
||||
initial="",
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={'size': '78'}),
|
||||
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
|
||||
help_text="Most non-character objects don't need a cmdset"
|
||||
" and can leave this field blank.")
|
||||
raw_id_fields = ('db_destination', 'db_location', 'db_home')
|
||||
|
||||
|
||||
|
|
@ -63,8 +68,10 @@ class ObjectEditForm(ObjectCreateForm):
|
|||
fields = '__all__'
|
||||
db_lock_storage = forms.CharField(label="Locks",
|
||||
required=False,
|
||||
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
|
||||
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
|
||||
widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}),
|
||||
help_text="In-game lock definition string. If not given, defaults will be used. "
|
||||
"This string should be on the form "
|
||||
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
|
||||
|
||||
|
||||
class ObjectDBAdmin(admin.ModelAdmin):
|
||||
|
|
@ -90,15 +97,15 @@ class ObjectDBAdmin(admin.ModelAdmin):
|
|||
form = ObjectEditForm
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ),
|
||||
('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
|
||||
'fields': (('db_key', 'db_typeclass_path'), ('db_lock_storage', ),
|
||||
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
|
||||
)}),
|
||||
)
|
||||
|
||||
add_form = ObjectCreateForm
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'fields': (('db_key','db_typeclass_path'),
|
||||
'fields': (('db_key', 'db_typeclass_path'),
|
||||
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
|
||||
)}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ _MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
|
|||
|
||||
# Try to use a custom way to parse id-tagged multimatches.
|
||||
|
||||
|
||||
class ObjectDBManager(TypedObjectManager):
|
||||
"""
|
||||
This ObjectManager implements methods for searching
|
||||
|
|
@ -79,11 +80,13 @@ class ObjectDBManager(TypedObjectManager):
|
|||
if dbref:
|
||||
return dbref
|
||||
# not a dbref. Search by name.
|
||||
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
|
||||
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
|
||||
if obj]) or Q()
|
||||
if exact:
|
||||
return self.filter(cand_restriction & Q(db_player__username__iexact=ostring))
|
||||
else: # fuzzy matching
|
||||
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)).values_list("db_key", flat=True)
|
||||
else: # fuzzy matching
|
||||
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)
|
||||
).values_list("db_key", flat=True)
|
||||
if candidates:
|
||||
index_matches = string_partial_matching(ply_cands, ostring, ret_index=True)
|
||||
return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches]
|
||||
|
|
@ -103,7 +106,8 @@ class ObjectDBManager(TypedObjectManager):
|
|||
Returns:
|
||||
matches (list): The matching objects.
|
||||
"""
|
||||
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
|
||||
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
|
||||
if obj]) or Q()
|
||||
return self.filter(cand_restriction & Q(db_key__iexact=oname, db_typeclass_path__exact=otypeclass_path))
|
||||
|
||||
# attr/property related
|
||||
|
|
@ -121,7 +125,9 @@ class ObjectDBManager(TypedObjectManager):
|
|||
matches (list): All objects having the given attribute_name defined at all.
|
||||
|
||||
"""
|
||||
cand_restriction = candidates != None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
|
||||
cand_restriction = candidates is not None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj
|
||||
in make_iter(candidates)
|
||||
if obj]) or Q()
|
||||
return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
|
||||
|
||||
@returns_typeclass_list
|
||||
|
|
@ -144,20 +150,23 @@ class ObjectDBManager(TypedObjectManager):
|
|||
cannot be indexed, searching by Attribute key is to be preferred whenever possible.
|
||||
|
||||
"""
|
||||
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
|
||||
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
|
||||
if obj]) or Q()
|
||||
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
|
||||
|
||||
## This doesn't work if attribute_value is an object. Workaround below
|
||||
# This doesn't work if attribute_value is an object. Workaround below
|
||||
|
||||
if isinstance(attribute_value, (basestring, int, float, bool)):
|
||||
return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value))
|
||||
return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name,
|
||||
db_attributes__db_value=attribute_value))
|
||||
else:
|
||||
# We have to loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
|
||||
# We must loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
|
||||
global _ATTR
|
||||
if not _ATTR:
|
||||
from evennia.typeclasses.models import Attribute as _ATTR
|
||||
cands = list(self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name)))
|
||||
results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands, db_value=attribute_value)]
|
||||
results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands,
|
||||
db_value=attribute_value)]
|
||||
return chain(*results)
|
||||
|
||||
@returns_typeclass_list
|
||||
|
|
@ -174,8 +183,9 @@ class ObjectDBManager(TypedObjectManager):
|
|||
|
||||
"""
|
||||
property_name = "db_%s" % property_name.lstrip('db_')
|
||||
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
|
||||
querykwargs = {property_name:None}
|
||||
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
|
||||
if obj]) or Q()
|
||||
querykwargs = {property_name: None}
|
||||
try:
|
||||
return list(self.filter(cand_restriction).exclude(Q(**querykwargs)))
|
||||
except exceptions.FieldError:
|
||||
|
|
@ -198,8 +208,9 @@ class ObjectDBManager(TypedObjectManager):
|
|||
if isinstance(property_name, basestring):
|
||||
if not property_name.startswith('db_'):
|
||||
property_name = "db_%s" % property_name
|
||||
querykwargs = {property_name:property_value}
|
||||
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
|
||||
querykwargs = {property_name: property_value}
|
||||
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
|
||||
if obj]) or Q()
|
||||
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
|
||||
try:
|
||||
return list(self.filter(cand_restriction & type_restriction & Q(**querykwargs)))
|
||||
|
|
@ -207,7 +218,8 @@ class ObjectDBManager(TypedObjectManager):
|
|||
return []
|
||||
except ValueError:
|
||||
from evennia.utils import logger
|
||||
logger.log_err("The property '%s' does not support search criteria of the type %s." % (property_name, type(property_value)))
|
||||
logger.log_err("The property '%s' does not support search criteria of the type %s." %
|
||||
(property_name, type(property_value)))
|
||||
return []
|
||||
|
||||
@returns_typeclass_list
|
||||
|
|
@ -228,7 +240,7 @@ class ObjectDBManager(TypedObjectManager):
|
|||
|
||||
@returns_typeclass_list
|
||||
def get_objs_with_key_or_alias(self, ostring, exact=True,
|
||||
candidates=None, typeclasses=None):
|
||||
candidates=None, typeclasses=None):
|
||||
"""
|
||||
Args:
|
||||
ostring (str): A search criterion.
|
||||
|
|
@ -253,7 +265,7 @@ class ObjectDBManager(TypedObjectManager):
|
|||
|
||||
# build query objects
|
||||
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
|
||||
cand_restriction = candidates != None and Q(pk__in=candidates_id) or Q()
|
||||
cand_restriction = candidates is not None and Q(pk__in=candidates_id) or Q()
|
||||
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
|
||||
if exact:
|
||||
# exact match - do direct search
|
||||
|
|
@ -264,7 +276,8 @@ class ObjectDBManager(TypedObjectManager):
|
|||
search_candidates = self.filter(cand_restriction & type_restriction)
|
||||
else:
|
||||
# fuzzy without supplied candidates - we select our own candidates
|
||||
search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct()
|
||||
search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) |
|
||||
Q(db_tags__db_key__istartswith=ostring))).distinct()
|
||||
# fuzzy matching
|
||||
key_strings = search_candidates.values_list("db_key", flat=True).order_by("id")
|
||||
|
||||
|
|
@ -275,10 +288,10 @@ class ObjectDBManager(TypedObjectManager):
|
|||
else:
|
||||
# match by alias rather than by key
|
||||
search_candidates = search_candidates.filter(db_tags__db_tagtype__iexact="alias",
|
||||
db_tags__db_key__icontains=ostring)
|
||||
db_tags__db_key__icontains=ostring)
|
||||
alias_strings = []
|
||||
alias_candidates = []
|
||||
#TODO create the alias_strings and alias_candidates lists more effiently?
|
||||
# TODO create the alias_strings and alias_candidates lists more efficiently?
|
||||
for candidate in search_candidates:
|
||||
for alias in candidate.aliases.all():
|
||||
alias_strings.append(alias)
|
||||
|
|
@ -343,13 +356,16 @@ class ObjectDBManager(TypedObjectManager):
|
|||
"""
|
||||
if attribute_name:
|
||||
# attribute/property search (always exact).
|
||||
matches = self.get_objs_with_db_property_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
|
||||
matches = self.get_objs_with_db_property_value(attribute_name, searchdata,
|
||||
candidates=candidates, typeclasses=typeclass)
|
||||
if matches:
|
||||
return matches
|
||||
return self.get_objs_with_attr_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
|
||||
return self.get_objs_with_attr_value(attribute_name, searchdata,
|
||||
candidates=candidates, typeclasses=typeclass)
|
||||
else:
|
||||
# normal key/alias search
|
||||
return self.get_objs_with_key_or_alias(searchdata, exact=exact, candidates=candidates, typeclasses=typeclass)
|
||||
return self.get_objs_with_key_or_alias(searchdata, exact=exact,
|
||||
candidates=candidates, typeclasses=typeclass)
|
||||
|
||||
if not searchdata and searchdata != 0:
|
||||
return []
|
||||
|
|
@ -372,7 +388,7 @@ class ObjectDBManager(TypedObjectManager):
|
|||
candidates = [cand for cand in make_iter(candidates) if cand]
|
||||
if typeclass:
|
||||
candidates = [cand for cand in candidates
|
||||
if _GA(cand, "db_typeclass_path") in typeclass]
|
||||
if _GA(cand, "db_typeclass_path") in typeclass]
|
||||
|
||||
dbref = not attribute_name and exact and use_dbref and self.dbref(searchdata)
|
||||
if dbref:
|
||||
|
|
@ -418,7 +434,6 @@ class ObjectDBManager(TypedObjectManager):
|
|||
|
||||
#
|
||||
# ObjectManager Copy method
|
||||
#
|
||||
|
||||
def copy_object(self, original_object, new_key=None,
|
||||
new_location=None, new_home=None,
|
||||
|
|
|
|||
|
|
@ -51,8 +51,7 @@ class ContentsHandler(object):
|
|||
Re-initialize the content cache
|
||||
|
||||
"""
|
||||
self._pkcache.update(dict((obj.pk, None) for obj in
|
||||
ObjectDB.objects.filter(db_location=self.obj) if obj.pk))
|
||||
self._pkcache.update(dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk))
|
||||
|
||||
def get(self, exclude=None):
|
||||
"""
|
||||
|
|
@ -79,7 +78,7 @@ class ContentsHandler(object):
|
|||
return [self._idcache[pk] for pk in pks]
|
||||
except KeyError:
|
||||
# this means an actual failure of caching. Return real database match.
|
||||
logger.log_err("contents cache failed for %s." % (self.obj.key))
|
||||
logger.log_err("contents cache failed for %s." % self.obj.key)
|
||||
return list(ObjectDB.objects.filter(db_location=self.obj))
|
||||
|
||||
def add(self, obj):
|
||||
|
|
@ -110,11 +109,12 @@ class ContentsHandler(object):
|
|||
self._pkcache = {}
|
||||
self.init()
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# ObjectDB
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class ObjectDB(TypedObject):
|
||||
"""
|
||||
|
|
@ -173,17 +173,18 @@ class ObjectDB(TypedObject):
|
|||
help_text='a Player connected to this object, if any.')
|
||||
# the session id associated with this player, if any
|
||||
db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id",
|
||||
help_text="csv list of session ids of connected Player, if any.")
|
||||
help_text="csv list of session ids of connected Player, if any.")
|
||||
# The location in the game world. Since this one is likely
|
||||
# to change often, we set this with the 'location' property
|
||||
# to transparently handle Typeclassing.
|
||||
db_location = models.ForeignKey('self', related_name="locations_set", db_index=True, on_delete=models.SET_NULL,
|
||||
blank=True, null=True, verbose_name='game location')
|
||||
blank=True, null=True, verbose_name='game location')
|
||||
# a safety location, this usually don't change much.
|
||||
db_home = models.ForeignKey('self', related_name="homes_set", on_delete=models.SET_NULL,
|
||||
blank=True, null=True, verbose_name='home location')
|
||||
blank=True, null=True, verbose_name='home location')
|
||||
# destination of this object - primarily used by exits.
|
||||
db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True, on_delete=models.SET_NULL,
|
||||
db_destination = models.ForeignKey('self', related_name="destinations_set",
|
||||
db_index=True, on_delete=models.SET_NULL,
|
||||
blank=True, null=True, verbose_name='destination',
|
||||
help_text='a destination, used only by exit objects.')
|
||||
# database storage of persistant cmdsets.
|
||||
|
|
@ -204,28 +205,28 @@ class ObjectDB(TypedObject):
|
|||
|
||||
# cmdset_storage property handling
|
||||
def __cmdset_storage_get(self):
|
||||
"getter"
|
||||
"""getter"""
|
||||
storage = self.db_cmdset_storage
|
||||
return [path.strip() for path in storage.split(',')] if storage else []
|
||||
|
||||
def __cmdset_storage_set(self, value):
|
||||
"setter"
|
||||
self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
|
||||
"""setter"""
|
||||
self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
|
||||
self.save(update_fields=["db_cmdset_storage"])
|
||||
|
||||
def __cmdset_storage_del(self):
|
||||
"deleter"
|
||||
"""deleter"""
|
||||
self.db_cmdset_storage = None
|
||||
self.save(update_fields=["db_cmdset_storage"])
|
||||
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
|
||||
|
||||
# location getsetter
|
||||
def __location_get(self):
|
||||
"Get location"
|
||||
"""Get location"""
|
||||
return self.db_location
|
||||
|
||||
def __location_set(self, location):
|
||||
"Set location, checking for loops and allowing dbref"
|
||||
"""Set location, checking for loops and allowing dbref"""
|
||||
if isinstance(location, (basestring, int)):
|
||||
# allow setting of #dbref
|
||||
dbid = dbref(location, reqhash=False)
|
||||
|
|
@ -237,9 +238,9 @@ class ObjectDB(TypedObject):
|
|||
pass
|
||||
try:
|
||||
def is_loc_loop(loc, depth=0):
|
||||
"Recursively traverse target location, trying to catch a loop."
|
||||
"""Recursively traverse target location, trying to catch a loop."""
|
||||
if depth > 10:
|
||||
return
|
||||
return None
|
||||
elif loc == self:
|
||||
raise RuntimeError
|
||||
elif loc is None:
|
||||
|
|
@ -248,7 +249,7 @@ class ObjectDB(TypedObject):
|
|||
try:
|
||||
is_loc_loop(location)
|
||||
except RuntimeWarning:
|
||||
# we caught a infitite location loop!
|
||||
# we caught an infinite location loop!
|
||||
# (location1 is in location2 which is in location1 ...)
|
||||
pass
|
||||
|
||||
|
|
@ -281,7 +282,7 @@ class ObjectDB(TypedObject):
|
|||
return
|
||||
|
||||
def __location_del(self):
|
||||
"Cleanly delete the location reference"
|
||||
"""Cleanly delete the location reference"""
|
||||
self.db_location = None
|
||||
self.save(update_fields=["db_location"])
|
||||
location = property(__location_get, __location_set, __location_del)
|
||||
|
|
@ -311,7 +312,6 @@ class ObjectDB(TypedObject):
|
|||
[o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()]
|
||||
|
||||
class Meta(object):
|
||||
"Define Django meta options"
|
||||
"""Define Django meta options"""
|
||||
verbose_name = "Object"
|
||||
verbose_name_plural = "Objects"
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from evennia.commands.cmdsethandler import CmdSetHandler
|
|||
from evennia.commands import cmdhandler
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import (variable_from_module, lazy_property,
|
||||
make_iter, to_unicode, calledby)
|
||||
make_iter, to_unicode, calledby, is_iter)
|
||||
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ _SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
|
|||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
class ObjectSessionHandler(object):
|
||||
"""
|
||||
Handles the get/setting of the sessid
|
||||
|
|
@ -133,7 +134,7 @@ class ObjectSessionHandler(object):
|
|||
Remove session from handler.
|
||||
|
||||
Args:
|
||||
sessid (Session or int): Session or session id to remove.
|
||||
session (Session or int): Session or session id to remove.
|
||||
|
||||
"""
|
||||
try:
|
||||
|
|
@ -144,7 +145,7 @@ class ObjectSessionHandler(object):
|
|||
sessid_cache = self._sessid_cache
|
||||
if sessid in sessid_cache:
|
||||
sessid_cache.remove(sessid)
|
||||
self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
|
||||
self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
|
||||
self.obj.save(update_fields=["db_sessid"])
|
||||
|
||||
def clear(self):
|
||||
|
|
@ -167,10 +168,9 @@ class ObjectSessionHandler(object):
|
|||
return len(self._sessid_cache)
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Base class to inherit from.
|
||||
#
|
||||
|
||||
|
||||
class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
||||
"""
|
||||
|
|
@ -221,7 +221,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
|
||||
"""
|
||||
return self.db_player and self.db_player.is_superuser \
|
||||
and not self.db_player.attributes.get("_quell")
|
||||
and not self.db_player.attributes.get("_quell")
|
||||
|
||||
def contents_get(self, exclude=None):
|
||||
"""
|
||||
|
|
@ -241,7 +241,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
|
||||
"""
|
||||
con = self.contents_cache.get(exclude=exclude)
|
||||
#print "contents_get:", self, con, id(self), calledby()
|
||||
# print "contents_get:", self, con, id(self), calledby() # DEBUG
|
||||
return con
|
||||
contents = property(contents_get)
|
||||
|
||||
|
|
@ -370,8 +370,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
# do nick-replacement on search
|
||||
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
|
||||
|
||||
if(global_search or (is_string and searchdata.startswith("#") and
|
||||
len(searchdata) > 1 and searchdata[1:].isdigit())):
|
||||
if (global_search or (is_string and searchdata.startswith("#") and
|
||||
len(searchdata) > 1 and searchdata[1:].isdigit())):
|
||||
# only allow exact matching if searching the entire database
|
||||
# or unique #dbrefs
|
||||
exact = True
|
||||
|
|
@ -403,8 +403,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
use_dbref=use_dbref)
|
||||
if quiet:
|
||||
return results
|
||||
return _AT_SEARCH_RESULT(results, self, query=searchdata,
|
||||
nofound_string=nofound_string, multimatch_string=multimatch_string)
|
||||
return _AT_SEARCH_RESULT(results, self, query=searchdata,
|
||||
nofound_string=nofound_string, multimatch_string=multimatch_string)
|
||||
|
||||
def search_player(self, searchdata, quiet=False):
|
||||
"""
|
||||
|
|
@ -474,11 +474,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
# nick replacement - we require full-word matching.
|
||||
# do text encoding conversion
|
||||
raw_string = to_unicode(raw_string)
|
||||
raw_string = self.nicks.nickreplace(raw_string,
|
||||
categories=("inputline", "channel"), include_player=True)
|
||||
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True)
|
||||
return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs)
|
||||
|
||||
|
||||
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
|
||||
"""
|
||||
Emits something to a session attached to the object.
|
||||
|
|
@ -549,21 +547,28 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
for obj in contents:
|
||||
func(obj, **kwargs)
|
||||
|
||||
def msg_contents(self, message, exclude=None, from_obj=None, mapping=None, **kwargs):
|
||||
def msg_contents(self, text=None, exclude=None, from_obj=None, mapping=None, **kwargs):
|
||||
"""
|
||||
Emits a message to all objects inside this object.
|
||||
|
||||
Args:
|
||||
message (str): Message to send.
|
||||
text (str or tuple): Message to send. If a tuple, this should be
|
||||
on the valid OOB outmessage form `(message, {kwargs})`,
|
||||
where kwargs are optional data passed to the `text`
|
||||
outputfunc.
|
||||
exclude (list, optional): A list of objects not to send to.
|
||||
from_obj (Object, optional): An object designated as the
|
||||
"sender" of the message. See `DefaultObject.msg()` for
|
||||
more info.
|
||||
mapping (dict, optional): A mapping of formatting keys
|
||||
`{"key":<object>, "key2":<object2>,...}. The keys
|
||||
must match `{key}` markers in `message` and will be
|
||||
must match `{key}` markers in the `text` if this is a string or
|
||||
in the internal `message` if `text` is a tuple. These
|
||||
formatting statements will be
|
||||
replaced by the return of `<object>.get_display_name(looker)`
|
||||
for every looker that is messaged.
|
||||
for every looker in contents that receives the
|
||||
message. This allows for every object to potentially
|
||||
get its own customized string.
|
||||
Kwargs:
|
||||
Keyword arguments will be passed on to `obj.msg()` for all
|
||||
messaged objects.
|
||||
|
|
@ -577,14 +582,23 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
not have `get_display_name()`, its string value will be used.
|
||||
|
||||
Example:
|
||||
Say char is a Character object and npc is an NPC object:
|
||||
Say Char is a Character object and Npc is an NPC object:
|
||||
|
||||
action = 'kicks'
|
||||
char.location.msg_contents(
|
||||
"{attacker} {action} {defender}",
|
||||
mapping=dict(attacker=char, defender=npc, action=action),
|
||||
exclude=(char, npc))
|
||||
"{attacker} kicks {defender}",
|
||||
mapping=dict(attacker=char, defender=npc), exclude=(char, npc))
|
||||
|
||||
This will result in everyone in the room seeing 'Char kicks NPC'
|
||||
where everyone may potentially see different results for Char and Npc
|
||||
depending on the results of `char.get_display_name(looker)` and
|
||||
`npc.get_display_name(looker)` for each particular onlooker
|
||||
|
||||
"""
|
||||
# we also accept an outcommand on the form (message, {kwargs})
|
||||
is_outcmd = text and is_iter(text)
|
||||
inmessage = text[0] if is_outcmd else text
|
||||
outkwargs = text[1] if is_outcmd and len(text) > 1 else {}
|
||||
|
||||
contents = self.contents
|
||||
if exclude:
|
||||
exclude = make_iter(exclude)
|
||||
|
|
@ -592,12 +606,12 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
for obj in contents:
|
||||
if mapping:
|
||||
substitutions = {t: sub.get_display_name(obj)
|
||||
if hasattr(sub, 'get_display_name')
|
||||
else str(sub)
|
||||
for t, sub in mapping.items()}
|
||||
obj.msg(message.format(**substitutions), from_obj=from_obj, **kwargs)
|
||||
if hasattr(sub, 'get_display_name')
|
||||
else str(sub) for t, sub in mapping.items()}
|
||||
outmessage = inmessage.format(**substitutions)
|
||||
else:
|
||||
obj.msg(message, from_obj=from_obj, **kwargs)
|
||||
outmessage = inmessage
|
||||
obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
|
||||
|
||||
def move_to(self, destination, quiet=False,
|
||||
emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True):
|
||||
|
|
@ -642,7 +656,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
|
||||
"""
|
||||
def logerr(string="", err=None):
|
||||
"Simple log helper method"
|
||||
"""Simple log helper method"""
|
||||
logger.log_trace()
|
||||
self.msg("%s%s" % (string, "" if err is None else " (%s)" % err))
|
||||
return
|
||||
|
|
@ -658,7 +672,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
self.location = None
|
||||
return True
|
||||
emit_to_obj.msg(_("The destination doesn't exist."))
|
||||
return
|
||||
return False
|
||||
if destination.destination and use_destination:
|
||||
# traverse exits
|
||||
destination = destination.destination
|
||||
|
|
@ -667,7 +681,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
if move_hooks:
|
||||
try:
|
||||
if not self.at_before_move(destination):
|
||||
return
|
||||
return False
|
||||
except Exception as err:
|
||||
logerr(errtxt % "at_before_move()", err)
|
||||
return False
|
||||
|
|
@ -684,7 +698,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
return False
|
||||
|
||||
if not quiet:
|
||||
#tell the old room we are leaving
|
||||
# tell the old room we are leaving
|
||||
try:
|
||||
self.announce_move_from(destination)
|
||||
except Exception as err:
|
||||
|
|
@ -704,7 +718,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
self.announce_move_to(source_location)
|
||||
except Exception as err:
|
||||
logerr(errtxt % "announce_move_to()", err)
|
||||
return False
|
||||
return False
|
||||
|
||||
if move_hooks:
|
||||
# Perform eventual extra commands on the receiving location
|
||||
|
|
@ -779,7 +793,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
obj.msg(_(string))
|
||||
obj.move_to(home)
|
||||
|
||||
|
||||
def copy(self, new_key=None):
|
||||
"""
|
||||
Makes an identical copy of this object, identical except for a
|
||||
|
|
@ -803,15 +816,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
"""
|
||||
key = self.key
|
||||
num = 1
|
||||
for obj in (obj for obj in self.location.contents
|
||||
if obj.key.startswith(key) and
|
||||
obj.key.lstrip(key).isdigit()):
|
||||
for _ in (obj for obj in self.location.contents
|
||||
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
|
||||
num += 1
|
||||
return "%s%03i" % (key, num)
|
||||
new_key = new_key or find_clone_key()
|
||||
return ObjectDB.objects.copy_object(self, new_key=new_key)
|
||||
|
||||
delete_iter = 0
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes this object. Before deletion, this method makes sure
|
||||
|
|
@ -862,7 +875,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
self.attributes.clear()
|
||||
self.nicks.clear()
|
||||
self.aliases.clear()
|
||||
self.location = None # this updates contents_cache for our location
|
||||
self.location = None # this updates contents_cache for our location
|
||||
|
||||
# Perform the deletion of the object
|
||||
super(DefaultObject, self).delete()
|
||||
|
|
@ -955,8 +968,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
|
||||
self.basetype_posthook_setup()
|
||||
|
||||
|
||||
## hooks called by the game engine
|
||||
# hooks called by the game engine #
|
||||
|
||||
def basetype_setup(self):
|
||||
"""
|
||||
|
|
@ -1032,8 +1044,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
have no cmdsets.
|
||||
|
||||
Kwargs:
|
||||
Usually not set but could be used e.g. to force rebuilding
|
||||
of a dynamically created cmdset or similar.
|
||||
caller (Session, Object or Player): The caller requesting
|
||||
this cmdset.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
|
@ -1118,9 +1130,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
|
||||
Args:
|
||||
result (bool): The outcome of the access call.
|
||||
accessing_obj (Object or Player): The entity trying to
|
||||
gain access. access_type (str): The type of access that
|
||||
was requested.
|
||||
accessing_obj (Object or Player): The entity trying to gain access.
|
||||
access_type (str): The type of access that was requested.
|
||||
|
||||
Kwargs:
|
||||
Not used by default, added for possible expandability in a
|
||||
|
|
@ -1147,10 +1158,10 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
before it is even started.
|
||||
|
||||
"""
|
||||
#return has_perm(self, destination, "can_move")
|
||||
# return has_perm(self, destination, "can_move")
|
||||
return True
|
||||
|
||||
def announce_move_from(self, destination):
|
||||
def announce_move_from(self, destination, msg=None, mapping=None):
|
||||
"""
|
||||
Called if the move is to be announced. This is
|
||||
called while we are still standing in the old
|
||||
|
|
@ -1158,25 +1169,56 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
|
||||
Args:
|
||||
destination (Object): The place we are going to.
|
||||
msg (str, optional): a replacement message.
|
||||
mapping (dict, optional): additional mapping objects.
|
||||
|
||||
You can override this method and call its parent with a
|
||||
message to simply change the default message. In the string,
|
||||
you can use the following as mappings (between braces):
|
||||
object: the object which is moving.
|
||||
exit: the exit from which the object is moving (if found).
|
||||
origin: the location of the object before the move.
|
||||
destination: the location of the object after moving.
|
||||
|
||||
"""
|
||||
if not self.location:
|
||||
return
|
||||
string = "%s is leaving %s, heading for %s."
|
||||
location = self.location
|
||||
for obj in self.location.contents:
|
||||
if obj != self:
|
||||
obj.msg(string % (self.get_display_name(obj),
|
||||
location.get_display_name(obj) if location else "nowhere",
|
||||
destination.get_display_name(obj)))
|
||||
if msg:
|
||||
string = msg
|
||||
else:
|
||||
string = "{object} is leaving {origin}, heading for {destination}."
|
||||
|
||||
def announce_move_to(self, source_location):
|
||||
location = self.location
|
||||
exits = [o for o in location.contents if o.location is location and o.destination is destination]
|
||||
if not mapping:
|
||||
mapping = {}
|
||||
|
||||
mapping.update({
|
||||
"object": self,
|
||||
"exit": exits[0] if exits else "somwhere",
|
||||
"origin": location or "nowhere",
|
||||
"destination": destination or "nowhere",
|
||||
})
|
||||
|
||||
location.msg_contents(string, exclude=(self, ), mapping=mapping)
|
||||
|
||||
def announce_move_to(self, source_location, msg=None, mapping=None):
|
||||
"""
|
||||
Called after the move if the move was not quiet. At this point
|
||||
we are standing in the new location.
|
||||
|
||||
Args:
|
||||
source_location (Object): The place we came from
|
||||
msg (str, optional): the replacement message if location.
|
||||
mapping (dict, optional): additional mapping objects.
|
||||
|
||||
You can override this method and call its parent with a
|
||||
message to simply change the default message. In the string,
|
||||
you can use the following as mappings (between braces):
|
||||
object: the object which is moving.
|
||||
exit: the exit from which the object is moving (if found).
|
||||
origin: the location of the object before the move.
|
||||
destination: the location of the object after moving.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -1187,13 +1229,31 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
self.location.msg(string)
|
||||
return
|
||||
|
||||
string = "%s arrives to %s%s."
|
||||
location = self.location
|
||||
for obj in self.location.contents:
|
||||
if obj != self:
|
||||
obj.msg(string % (self.get_display_name(obj),
|
||||
location.get_display_name(obj) if location else "nowhere",
|
||||
" from %s" % source_location.get_display_name(obj) if source_location else ""))
|
||||
if source_location:
|
||||
if msg:
|
||||
string = msg
|
||||
else:
|
||||
string = "{object} arrives to {destination} from {origin}."
|
||||
else:
|
||||
string = "{object} arrives to {destination}."
|
||||
|
||||
origin = source_location
|
||||
destination = self.location
|
||||
exits = []
|
||||
if origin:
|
||||
exits = [o for o in destination.contents if o.location is destination and o.destination is origin]
|
||||
|
||||
if not mapping:
|
||||
mapping = {}
|
||||
|
||||
mapping.update({
|
||||
"object": self,
|
||||
"exit": exits[0] if exits else "somewhere",
|
||||
"origin": origin or "nowhere",
|
||||
"destination": destination or "nowhere",
|
||||
})
|
||||
|
||||
destination.msg_contents(string, exclude=(self, ), mapping=mapping)
|
||||
|
||||
def at_after_move(self, source_location):
|
||||
"""
|
||||
|
|
@ -1288,7 +1348,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
check for this. .
|
||||
|
||||
Consider this a pre-processing method before msg is passed on
|
||||
to the user sesssion. If this method returns False, the msg
|
||||
to the user session. If this method returns False, the msg
|
||||
will not be passed on.
|
||||
|
||||
Args:
|
||||
|
|
@ -1341,7 +1401,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
return ""
|
||||
# get and identify all objects
|
||||
visible = (con for con in self.contents if con != looker and
|
||||
con.access(looker, "view"))
|
||||
con.access(looker, "view"))
|
||||
exits, users, things = [], [], []
|
||||
for con in visible:
|
||||
key = con.get_display_name(looker)
|
||||
|
|
@ -1416,6 +1476,22 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
"""
|
||||
pass
|
||||
|
||||
def at_give(self, giver, getter):
|
||||
"""
|
||||
Called by the default `give` command when this object has been
|
||||
given.
|
||||
|
||||
Args:
|
||||
giver (Object): The object giving this object.
|
||||
getter (Object): The object getting this object.
|
||||
|
||||
Notes:
|
||||
This hook cannot stop the give from happening. Use
|
||||
permissions for that.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_drop(self, dropper):
|
||||
"""
|
||||
Called by the default `drop` command when this object has been
|
||||
|
|
@ -1425,7 +1501,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
|
|||
dropper (Object): The object which just dropped this object.
|
||||
|
||||
Notes:
|
||||
This hook cannot stop the pickup from happening. Use
|
||||
This hook cannot stop the drop from happening. Use
|
||||
permissions from that.
|
||||
|
||||
"""
|
||||
|
|
@ -1473,7 +1549,7 @@ class DefaultCharacter(DefaultObject):
|
|||
"""
|
||||
super(DefaultCharacter, self).basetype_setup()
|
||||
self.locks.add(";".join(["get:false()", # noone can pick up the character
|
||||
"call:false()"])) # no commands can be called on character from outside
|
||||
"call:false()"])) # no commands can be called on character from outside
|
||||
# add the default cmdset
|
||||
self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
|
||||
|
||||
|
|
@ -1565,7 +1641,7 @@ class DefaultCharacter(DefaultObject):
|
|||
|
||||
#
|
||||
# Base Room object
|
||||
#
|
||||
|
||||
|
||||
class DefaultRoom(DefaultObject):
|
||||
"""
|
||||
|
|
@ -1581,7 +1657,7 @@ class DefaultRoom(DefaultObject):
|
|||
|
||||
super(DefaultRoom, self).basetype_setup()
|
||||
self.locks.add(";".join(["get:false()",
|
||||
"puppet:false()"])) # would be weird to puppet a room ...
|
||||
"puppet:false()"])) # would be weird to puppet a room ...
|
||||
self.location = None
|
||||
|
||||
|
||||
|
|
@ -1631,7 +1707,7 @@ class ExitCommand(command.Command):
|
|||
|
||||
#
|
||||
# Base Exit object
|
||||
#
|
||||
|
||||
|
||||
class DefaultExit(DefaultObject):
|
||||
"""
|
||||
|
|
@ -1683,8 +1759,8 @@ class DefaultExit(DefaultObject):
|
|||
exit_cmdset.add(cmd)
|
||||
return exit_cmdset
|
||||
|
||||
|
||||
# Command hooks
|
||||
|
||||
def basetype_setup(self):
|
||||
"""
|
||||
Setup exit-security
|
||||
|
|
@ -1696,8 +1772,8 @@ class DefaultExit(DefaultObject):
|
|||
super(DefaultExit, self).basetype_setup()
|
||||
|
||||
# setting default locks (overload these in at_object_creation()
|
||||
self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
|
||||
"traverse:all()", # who can pass through exit by default
|
||||
self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
|
||||
"traverse:all()", # who can pass through exit by default
|
||||
"get:false()"])) # noone can pick up the exit
|
||||
|
||||
# an exit should have a destination (this is replaced at creation time)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ from evennia.utils import utils
|
|||
|
||||
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
|
||||
_IRC_ENABLED = settings.IRC_ENABLED
|
||||
_RSS_ENABLED = settings.RSS_ENABLED
|
||||
|
||||
_SESSIONS = None
|
||||
|
||||
|
||||
|
|
@ -78,8 +81,10 @@ class BotStarter(DefaultScript):
|
|||
"""
|
||||
self.db.started = False
|
||||
|
||||
#
|
||||
# Bot base class
|
||||
|
||||
|
||||
class Bot(DefaultPlayer):
|
||||
"""
|
||||
A Bot will start itself when the server starts (it will generally
|
||||
|
|
@ -157,6 +162,12 @@ class IRCBot(Bot):
|
|||
irc_ssl (bool): Indicates whether to use SSL connection.
|
||||
|
||||
"""
|
||||
if not _IRC_ENABLED:
|
||||
# the bot was created, then IRC was turned off. We delete
|
||||
# ourselves (this will also kill the start script)
|
||||
self.delete()
|
||||
return
|
||||
|
||||
global _SESSIONS
|
||||
if not _SESSIONS:
|
||||
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
||||
|
|
@ -187,9 +198,9 @@ class IRCBot(Bot):
|
|||
|
||||
# instruct the server and portal to create a new session with
|
||||
# the stored configuration
|
||||
configdict = {"uid":self.dbid,
|
||||
configdict = {"uid": self.dbid,
|
||||
"botname": self.db.irc_botname,
|
||||
"channel": self.db.irc_channel ,
|
||||
"channel": self.db.irc_channel,
|
||||
"network": self.db.irc_network,
|
||||
"port": self.db.irc_port,
|
||||
"ssl": self.db.irc_ssl}
|
||||
|
|
@ -316,31 +327,32 @@ class IRCBot(Bot):
|
|||
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % player.name, width=25),
|
||||
utils.time_format(delta_conn, 0),
|
||||
utils.time_format(delta_cmd, 1)))
|
||||
text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w:w.lower()))
|
||||
text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w: w.lower()))
|
||||
elif txt.lower().startswith("about"):
|
||||
# some bot info
|
||||
text = "This is an Evennia IRC bot connecting from '%s'." % settings.SERVERNAME
|
||||
else:
|
||||
text = "I understand 'who' and 'about'."
|
||||
super(IRCBot, self).msg(privmsg=((text,), {"user":user}))
|
||||
super(IRCBot, self).msg(privmsg=((text,), {"user": user}))
|
||||
else:
|
||||
# something to send to the main channel
|
||||
if kwargs["type"] == "action":
|
||||
# An action (irc pose)
|
||||
text = "%s@%s %s" % (kwargs["user"], kwargs["channel"], txt)
|
||||
|
||||
else:
|
||||
# msg - A normal channel message
|
||||
text = "%s@%s: %s" % (kwargs["user"], kwargs["channel"], txt)
|
||||
|
||||
if not self.ndb.ev_channel and self.db.ev_channel:
|
||||
# cache channel lookup
|
||||
self.ndb.ev_channel = self.db.ev_channel
|
||||
if self.ndb.ev_channel:
|
||||
self.ndb.ev_channel.msg(text, senders=self.id)
|
||||
if not self.ndb.ev_channel and self.db.ev_channel:
|
||||
# cache channel lookup
|
||||
self.ndb.ev_channel = self.db.ev_channel
|
||||
if self.ndb.ev_channel:
|
||||
self.ndb.ev_channel.msg(text, senders=self.id)
|
||||
|
||||
#
|
||||
# RSS
|
||||
|
||||
|
||||
class RSSBot(Bot):
|
||||
"""
|
||||
An RSS relayer. The RSS protocol itself runs a ticker to update
|
||||
|
|
@ -354,12 +366,17 @@ class RSSBot(Bot):
|
|||
Args:
|
||||
ev_channel (str): Key of the Evennia channel to connect to.
|
||||
rss_url (str): Full URL to the RSS feed to subscribe to.
|
||||
rss_update_rate (int): How often for the feedreader to update.
|
||||
rss_rate (int): How often for the feedreader to update.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If `ev_channel` does not exist.
|
||||
|
||||
"""
|
||||
if not _RSS_ENABLED:
|
||||
# The bot was created, then RSS was turned off. Delete ourselves.
|
||||
self.delete()
|
||||
return
|
||||
|
||||
global _SESSIONS
|
||||
if not _SESSIONS:
|
||||
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
|
||||
|
|
@ -390,7 +407,7 @@ class RSSBot(Bot):
|
|||
Args:
|
||||
session (Session, optional): Session responsible for this
|
||||
command.
|
||||
text (str, optional): Command string.
|
||||
txt (str, optional): Command string.
|
||||
kwargs (dict, optional): Additional Information passed from bot.
|
||||
Not used by the RSSbot by default.
|
||||
|
||||
|
|
|
|||
|
|
@ -104,17 +104,6 @@ class PlayerDB(TypedObject, AbstractUser):
|
|||
class Meta(object):
|
||||
verbose_name = 'Player'
|
||||
|
||||
# alias to the objs property
|
||||
def __characters_get(self):
|
||||
return self.objs
|
||||
|
||||
def __characters_set(self, value):
|
||||
self.objs = value
|
||||
|
||||
def __characters_del(self):
|
||||
raise Exception("Cannot delete name")
|
||||
characters = property(__characters_get, __characters_set, __characters_del)
|
||||
|
||||
# cmdset_storage property
|
||||
# This seems very sensitive to caching, so leaving it be for now /Griatch
|
||||
#@property
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
|
|||
_CMDSET_PLAYER = settings.CMDSET_PLAYER
|
||||
_CONNECT_CHANNEL = None
|
||||
|
||||
|
||||
class PlayerSessionHandler(object):
|
||||
"""
|
||||
Manages the session(s) attached to a player.
|
||||
|
|
@ -99,7 +100,6 @@ class PlayerSessionHandler(object):
|
|||
return len(self.get())
|
||||
|
||||
|
||||
|
||||
class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
||||
"""
|
||||
This is the base Typeclass for all Players. Players represent
|
||||
|
|
@ -188,7 +188,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
def sessions(self):
|
||||
return PlayerSessionHandler(self)
|
||||
|
||||
|
||||
# session-related methods
|
||||
|
||||
def disconnect_session_from_player(self, session):
|
||||
|
|
@ -337,8 +336,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
by this Player.
|
||||
|
||||
"""
|
||||
return list(set(session.puppet for session in self.sessions.all()
|
||||
if session.puppet))
|
||||
return list(set(session.puppet for session in self.sessions.all() if session.puppet))
|
||||
|
||||
def __get_single_puppet(self):
|
||||
"""
|
||||
|
|
@ -383,7 +381,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
self.nicks.clear()
|
||||
self.aliases.clear()
|
||||
super(DefaultPlayer, self).delete(*args, **kwargs)
|
||||
## methods inherited from database model
|
||||
# methods inherited from database model
|
||||
|
||||
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -447,8 +445,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
|
||||
"""
|
||||
raw_string = to_unicode(raw_string)
|
||||
raw_string = self.nicks.nickreplace(raw_string,
|
||||
categories=("inputline", "channel"), include_player=False)
|
||||
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=False)
|
||||
if not session and _MULTISESSION_MODE in (0, 1):
|
||||
# for these modes we use the first/only session
|
||||
sessions = self.sessions.get()
|
||||
|
|
@ -557,7 +554,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
return time.time() - float(min(conn))
|
||||
return None
|
||||
|
||||
## player hooks
|
||||
# player hooks
|
||||
|
||||
def basetype_setup(self):
|
||||
"""
|
||||
|
|
@ -600,7 +597,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Note that the hooks below also exist in the character object's
|
||||
# typeclass. You can often ignore these and rely on the character
|
||||
# ones instead, unless you are implementing a multi-character game
|
||||
|
|
@ -847,34 +843,36 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
is_su = self.is_superuser
|
||||
|
||||
# text shown when looking in the ooc area
|
||||
string = "Account |g%s|n (you are Out-of-Character)" % (self.key)
|
||||
result = ["Account |g%s|n (you are Out-of-Character)" % self.key]
|
||||
|
||||
nsess = len(sessions)
|
||||
string += nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess
|
||||
result.append(nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess)
|
||||
for isess, sess in enumerate(sessions):
|
||||
csessid = sess.sessid
|
||||
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address))
|
||||
string += "\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) or " %s" % (isess + 1), addr)
|
||||
string += "\n\n |whelp|n - more commands"
|
||||
string += "\n |wooc <Text>|n - talk on public channel"
|
||||
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple)
|
||||
and str(sess.address[0]) or str(sess.address))
|
||||
result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1)
|
||||
or " %s" % (isess + 1), addr))
|
||||
result.append("\n\n |whelp|n - more commands")
|
||||
result.append("\n |wooc <Text>|n - talk on public channel")
|
||||
|
||||
charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
|
||||
|
||||
if is_su or len(characters) < charmax:
|
||||
if not characters:
|
||||
string += "\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one."
|
||||
result.append("\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one.")
|
||||
else:
|
||||
string += "\n |w@charcreate <name> [=description]|n - create new character"
|
||||
string += "\n |w@chardelete <name>|n - delete a character (cannot be undone!)"
|
||||
result.append("\n |w@charcreate <name> [=description]|n - create new character")
|
||||
result.append("\n |w@chardelete <name>|n - delete a character (cannot be undone!)")
|
||||
|
||||
if characters:
|
||||
string_s_ending = len(characters) > 1 and "s" or ""
|
||||
string += "\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)"
|
||||
result.append("\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)")
|
||||
if is_su:
|
||||
string += "\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters))
|
||||
result.append("\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters)))
|
||||
else:
|
||||
string += "\n\nAvailable character%s%s:" % (string_s_ending,
|
||||
charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "")
|
||||
result.append("\n\nAvailable character%s%s:"
|
||||
% (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or ""))
|
||||
|
||||
for char in characters:
|
||||
csessions = char.sessions.all()
|
||||
|
|
@ -883,14 +881,16 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
|
|||
# character is already puppeted
|
||||
sid = sess in sessions and sessions.index(sess) + 1
|
||||
if sess and sid:
|
||||
string += "\n - |G%s|n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid)
|
||||
result.append("\n - |G%s|n [%s] (played by you in session %i)"
|
||||
% (char.key, ", ".join(char.permissions.all()), sid))
|
||||
else:
|
||||
string += "\n - |R%s|n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all()))
|
||||
result.append("\n - |R%s|n [%s] (played by someone else)"
|
||||
% (char.key, ", ".join(char.permissions.all())))
|
||||
else:
|
||||
# character is "free to puppet"
|
||||
string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all()))
|
||||
string = ("-" * 68) + "\n" + string + "\n" + ("-" * 68)
|
||||
return string
|
||||
result.append("\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())))
|
||||
look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68)
|
||||
return look_string
|
||||
|
||||
|
||||
class DefaultGuest(DefaultPlayer):
|
||||
|
|
@ -910,7 +910,6 @@ class DefaultGuest(DefaultPlayer):
|
|||
self._send_to_connect_channel("|G%s connected|n" % self.key)
|
||||
self.puppet_object(session, self.db._last_puppet)
|
||||
|
||||
|
||||
def at_server_shutdown(self):
|
||||
"""
|
||||
We repeat the functionality of `at_disconnect()` here just to
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ The separation works like this:
|
|||
|
||||
Portal - (AMP client) handles protocols. It contains a list of connected
|
||||
sessions in a dictionary for identifying the respective player
|
||||
connected. If it looses the AMP connection it will automatically
|
||||
connected. If it loses the AMP connection it will automatically
|
||||
try to reconnect.
|
||||
|
||||
Server - (AMP server) Handles all mud operations. The server holds its own list
|
||||
|
|
@ -32,33 +32,33 @@ from twisted.internet import protocol
|
|||
from twisted.internet.defer import Deferred
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import to_str, variable_from_module
|
||||
import zlib # Used in Compressed class
|
||||
|
||||
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
|
||||
|
||||
# communication bits
|
||||
# (chr(9) and chr(10) are \t and \n, so skipping them)
|
||||
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
PCONN = chr(1) # portal session connect
|
||||
PDISCONN = chr(2) # portal session disconnect
|
||||
PSYNC = chr(3) # portal session sync
|
||||
SLOGIN = chr(4) # server session login
|
||||
SDISCONN = chr(5) # server session disconnect
|
||||
SDISCONNALL = chr(6) # server session disconnect all
|
||||
SSHUTD = chr(7) # server shutdown
|
||||
SSYNC = chr(8) # server session sync
|
||||
SCONN = chr(11) # server creating new connection (for irc bots and etc)
|
||||
PCONNSYNC = chr(12) # portal post-syncing a session
|
||||
PDISCONNALL = chr(13) # portal session disconnect all
|
||||
PCONNSYNC = chr(12) # portal post-syncing a session
|
||||
PDISCONNALL = chr(13) # portal session disconnect all
|
||||
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed)
|
||||
|
||||
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
|
||||
BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds
|
||||
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
|
||||
BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds
|
||||
|
||||
# buffers
|
||||
_SENDBATCH = defaultdict(list)
|
||||
_MSGBUFFER = defaultdict(list)
|
||||
|
||||
import zlib
|
||||
|
||||
def get_restart_mode(restart_file):
|
||||
"""
|
||||
|
|
@ -323,9 +323,9 @@ dumps = lambda data: to_str(pickle.dumps(to_str(data), pickle.HIGHEST_PROTOCOL))
|
|||
loads = lambda data: pickle.loads(to_str(data))
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# Core AMP protocol for communication Server <-> Portal
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
class AMPProtocol(amp.AMP):
|
||||
"""
|
||||
|
|
@ -385,7 +385,6 @@ class AMPProtocol(amp.AMP):
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Error handling
|
||||
|
||||
def errback(self, e, info):
|
||||
|
|
@ -447,7 +446,7 @@ class AMPProtocol(amp.AMP):
|
|||
Access method called by the Portal and executed on the Portal.
|
||||
|
||||
Args:
|
||||
sessid (int): Unique Session id.
|
||||
session (session): Session
|
||||
kwargs (any, optional): Optional data.
|
||||
|
||||
Returns:
|
||||
|
|
@ -473,7 +472,6 @@ class AMPProtocol(amp.AMP):
|
|||
self.factory.portal.sessions.data_out(session, **kwargs)
|
||||
return {}
|
||||
|
||||
|
||||
def send_MsgServer2Portal(self, session, **kwargs):
|
||||
"""
|
||||
Access method - executed on the Server for sending data
|
||||
|
|
@ -506,7 +504,7 @@ class AMPProtocol(amp.AMP):
|
|||
# create a new session and sync it
|
||||
server_sessionhandler.portal_connect(kwargs.get("sessiondata"))
|
||||
|
||||
elif operation == PCONNSYNC: #portal_session_sync
|
||||
elif operation == PCONNSYNC: # portal_session_sync
|
||||
server_sessionhandler.portal_session_sync(kwargs.get("sessiondata"))
|
||||
|
||||
elif operation == PDISCONN: # portal_session_disconnect
|
||||
|
|
@ -515,7 +513,7 @@ class AMPProtocol(amp.AMP):
|
|||
if session:
|
||||
server_sessionhandler.portal_disconnect(session)
|
||||
|
||||
elif operation == PDISCONNALL: # portal_disconnect_all
|
||||
elif operation == PDISCONNALL: # portal_disconnect_all
|
||||
# portal orders all sessions to close
|
||||
server_sessionhandler.portal_disconnect_all()
|
||||
|
||||
|
|
@ -545,7 +543,7 @@ class AMPProtocol(amp.AMP):
|
|||
"""
|
||||
return self.send_data(AdminPortal2Server, session.sessid, operation=operation, **kwargs)
|
||||
|
||||
# Portal administraton from the Server side
|
||||
# Portal administration from the Server side
|
||||
|
||||
@AdminServer2Portal.responder
|
||||
def portal_receive_adminserver2portal(self, packed_data):
|
||||
|
|
@ -562,7 +560,6 @@ class AMPProtocol(amp.AMP):
|
|||
operation = kwargs.pop("operation")
|
||||
portal_sessionhandler = self.factory.portal.sessions
|
||||
|
||||
|
||||
if operation == SLOGIN: # server_session_login
|
||||
# a session has authenticated; sync it.
|
||||
session = portal_sessionhandler.get(sessid)
|
||||
|
|
@ -591,7 +588,7 @@ class AMPProtocol(amp.AMP):
|
|||
# set a flag in case we are about to shut down soon
|
||||
self.factory.server_restart_mode = True
|
||||
|
||||
elif operation == SCONN: # server_force_connection (for irc/etc)
|
||||
elif operation == SCONN: # server_force_connection (for irc/etc)
|
||||
portal_sessionhandler.server_connect(**kwargs)
|
||||
|
||||
else:
|
||||
|
|
@ -665,4 +662,5 @@ class AMPProtocol(amp.AMP):
|
|||
module=modulepath,
|
||||
function=functionname,
|
||||
args=dumps(args),
|
||||
kwargs=dumps(kwargs)).addCallback(lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall")
|
||||
kwargs=dumps(kwargs)).addCallback(
|
||||
lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall")
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ from evennia.server.models import ServerConfig
|
|||
from evennia.utils import create, logger
|
||||
|
||||
|
||||
ERROR_NO_SUPERUSER = \
|
||||
"""
|
||||
ERROR_NO_SUPERUSER = """
|
||||
No superuser exists yet. The superuser is the 'owner' account on
|
||||
the Evennia server. Create a new superuser using the command
|
||||
|
||||
|
|
@ -26,16 +25,14 @@ ERROR_NO_SUPERUSER = \
|
|||
"""
|
||||
|
||||
|
||||
LIMBO_DESC = \
|
||||
_("""
|
||||
Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if you need
|
||||
LIMBO_DESC = _("""
|
||||
Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need
|
||||
help, want to contribute, report issues or just join the community.
|
||||
As Player #1 you can create a demo/tutorial area with {w@batchcommand tutorial_world.build{n.
|
||||
As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.
|
||||
""")
|
||||
|
||||
|
||||
WARNING_POSTGRESQL_FIX = \
|
||||
"""
|
||||
WARNING_POSTGRESQL_FIX = """
|
||||
PostgreSQL-psycopg2 compatibility fix:
|
||||
The in-game channels {chan1}, {chan2} and {chan3} were created,
|
||||
but the superuser was not yet connected to them. Please use in
|
||||
|
|
|
|||
|
|
@ -40,18 +40,23 @@ IRC_MAGENTA = "13"
|
|||
IRC_DGREY = "14"
|
||||
IRC_GRAY = "15"
|
||||
|
||||
# test:
|
||||
# obsolete test:
|
||||
# {rred {ggreen {yyellow {bblue {mmagenta {ccyan {wwhite {xdgrey
|
||||
# {Rdred {Gdgreen {Ydyellow {Bdblue {Mdmagenta {Cdcyan {Wlgrey {Xblack
|
||||
# {[rredbg {[ggreenbg {[yyellowbg {[bbluebg {[mmagentabg {[ccyanbg {[wlgreybg {[xblackbg
|
||||
|
||||
# test:
|
||||
# |rred |ggreen |yyellow |bblue |mmagenta |ccyan |wwhite |xdgrey
|
||||
# |Rdred |Gdgreen |Ydyellow |Bdblue |Mdmagenta |Cdcyan |Wlgrey |Xblack
|
||||
# |[rredbg |[ggreenbg |[yyellowbg |[bbluebg |[mmagentabg |[ccyanbg |[wlgreybg |[xblackbg
|
||||
|
||||
IRC_COLOR_MAP = dict([
|
||||
# obs - {-type colors are deprecated but still used in many places.
|
||||
(r'{n', IRC_RESET), # reset
|
||||
(r'{n', IRC_RESET), # reset
|
||||
(r'{/', ""), # line break
|
||||
(r'{-', " "), # tab
|
||||
(r'{_', " "), # space
|
||||
(r'{*', ""), # invert
|
||||
(r'{-', " "), # tab
|
||||
(r'{_', " "), # space
|
||||
(r'{*', ""), # invert
|
||||
(r'{^', ""), # blinking text
|
||||
|
||||
(r'{r', IRC_COLOR + IRC_RED),
|
||||
|
|
@ -69,7 +74,7 @@ IRC_COLOR_MAP = dict([
|
|||
(r'{B', IRC_COLOR + IRC_DBLUE),
|
||||
(r'{M', IRC_COLOR + IRC_DMAGENTA),
|
||||
(r'{C', IRC_COLOR + IRC_DCYAN),
|
||||
(r'{W', IRC_COLOR + IRC_GRAY), # light grey
|
||||
(r'{W', IRC_COLOR + IRC_GRAY), # light grey
|
||||
(r'{X', IRC_COLOR + IRC_BLACK), # pure black
|
||||
|
||||
(r'{[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
|
||||
|
|
@ -79,14 +84,14 @@ IRC_COLOR_MAP = dict([
|
|||
(r'{[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
|
||||
(r'{[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
|
||||
(r'{[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background
|
||||
(r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background
|
||||
(r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background
|
||||
|
||||
# |-type formatting is the thing to use.
|
||||
(r'|n', IRC_RESET), # reset
|
||||
(r'|n', IRC_RESET), # reset
|
||||
(r'|/', ""), # line break
|
||||
(r'|-', " "), # tab
|
||||
(r'|_', " "), # space
|
||||
(r'|*', ""), # invert
|
||||
(r'|-', " "), # tab
|
||||
(r'|_', " "), # space
|
||||
(r'|*', ""), # invert
|
||||
(r'|^', ""), # blinking text
|
||||
|
||||
(r'|r', IRC_COLOR + IRC_RED),
|
||||
|
|
@ -104,7 +109,7 @@ IRC_COLOR_MAP = dict([
|
|||
(r'|B', IRC_COLOR + IRC_DBLUE),
|
||||
(r'|M', IRC_COLOR + IRC_DMAGENTA),
|
||||
(r'|C', IRC_COLOR + IRC_DCYAN),
|
||||
(r'|W', IRC_COLOR + IRC_GRAY), # light grey
|
||||
(r'|W', IRC_COLOR + IRC_GRAY), # light grey
|
||||
(r'|X', IRC_COLOR + IRC_BLACK), # pure black
|
||||
|
||||
(r'|[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
|
||||
|
|
@ -114,12 +119,13 @@ IRC_COLOR_MAP = dict([
|
|||
(r'|[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
|
||||
(r'|[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
|
||||
(r'|[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background
|
||||
(r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background
|
||||
(r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background
|
||||
])
|
||||
RE_IRC_COLOR = re.compile(r"|".join([re.escape(key) for key in viewkeys(IRC_COLOR_MAP)]), re.DOTALL)
|
||||
RE_MXP = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL)
|
||||
RE_MXP = re.compile(r'\|lc(.*?)\|lt(.*?)\|le', re.DOTALL)
|
||||
RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
|
||||
|
||||
|
||||
def sub_irc(ircmatch):
|
||||
"""
|
||||
Substitute irc color info. Used by re.sub.
|
||||
|
|
@ -133,6 +139,7 @@ def sub_irc(ircmatch):
|
|||
"""
|
||||
return IRC_COLOR_MAP.get(ircmatch.group(), "")
|
||||
|
||||
|
||||
def parse_irc_colors(string):
|
||||
"""
|
||||
Parse {-type syntax and replace with IRC color markers
|
||||
|
|
@ -156,9 +163,10 @@ def parse_irc_colors(string):
|
|||
|
||||
# IRC bot
|
||||
|
||||
|
||||
class IRCBot(irc.IRCClient, Session):
|
||||
"""
|
||||
An IRC bot that tracks actitivity in a channel as well
|
||||
An IRC bot that tracks activity in a channel as well
|
||||
as sends text to it when prompted
|
||||
|
||||
"""
|
||||
|
|
@ -190,7 +198,7 @@ class IRCBot(irc.IRCClient, Session):
|
|||
logger.log_info("IRC bot '%s' connected to %s at %s:%s." % (self.nickname, self.channel,
|
||||
self.network, self.port))
|
||||
|
||||
def disconnect(self, reason=None):
|
||||
def disconnect(self, reason=""):
|
||||
"""
|
||||
Called by sessionhandler to disconnect this protocol.
|
||||
|
||||
|
|
@ -198,7 +206,7 @@ class IRCBot(irc.IRCClient, Session):
|
|||
reason (str): Motivation for the disconnect.
|
||||
|
||||
"""
|
||||
self.sessionhandler.disconnect(self)
|
||||
self.sessionhandler.disconnect(self, reason=reason)
|
||||
self.stopping = True
|
||||
self.transport.loseConnection()
|
||||
|
||||
|
|
@ -246,14 +254,14 @@ class IRCBot(irc.IRCClient, Session):
|
|||
self.sendLine("NAMES %s" % self.channel)
|
||||
|
||||
def irc_RPL_NAMREPLY(self, prefix, params):
|
||||
"Handles IRC NAME request returns (nicklist)"
|
||||
""""Handles IRC NAME request returns (nicklist)"""
|
||||
channel = params[2].lower()
|
||||
if channel != self.channel.lower():
|
||||
return
|
||||
self.nicklist += params[3].split(' ')
|
||||
|
||||
def irc_RPL_ENDOFNAMES(self, prefix, params):
|
||||
"Called when the nicklist has finished being returned."
|
||||
"""Called when the nicklist has finished being returned."""
|
||||
channel = params[1].lower()
|
||||
if channel != self.channel.lower():
|
||||
return
|
||||
|
|
@ -271,7 +279,6 @@ class IRCBot(irc.IRCClient, Session):
|
|||
"""
|
||||
self.data_in(text="", type="ping", user="server", channel=self.channel, timing=time)
|
||||
|
||||
|
||||
def data_in(self, text=None, **kwargs):
|
||||
"""
|
||||
Data IRC -> Server.
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ if os.name == 'nt':
|
|||
# For Windows we need to handle pid files manually.
|
||||
PORTAL_PIDFILE = os.path.join(settings.GAME_DIR, "server", 'portal.pid')
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# Evennia Portal settings
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
VERSION = get_evennia_version()
|
||||
|
||||
|
|
@ -76,6 +76,8 @@ AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
|
|||
# Maintenance function - this is called repeatedly by the portal.
|
||||
|
||||
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
|
||||
|
||||
def _portal_maintenance():
|
||||
"""
|
||||
The maintenance function handles repeated checks and updates that
|
||||
|
|
@ -94,12 +96,12 @@ def _portal_maintenance():
|
|||
if _IDLE_TIMEOUT > 0:
|
||||
# only start the maintenance task if we care about idling.
|
||||
_maintenance_task = LoopingCall(_portal_maintenance)
|
||||
_maintenance_task.start(60) # called every minute
|
||||
_maintenance_task.start(60) # called every minute
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# Portal Service object
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
class Portal(object):
|
||||
|
||||
"""
|
||||
|
|
@ -180,11 +182,11 @@ class Portal(object):
|
|||
self.shutdown_complete = True
|
||||
reactor.callLater(0, reactor.stop)
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Start the Portal proxy server and add all active services
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
# twistd requires us to define the variable 'application' so it knows
|
||||
# what to execute from.
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ _CONNECTION_QUEUE = deque()
|
|||
|
||||
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# Portal-SessionHandler class
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class PortalSessionHandler(SessionHandler):
|
||||
"""
|
||||
This object holds the sessions connected to the portal at any time.
|
||||
|
|
@ -95,7 +97,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
if len(_CONNECTION_QUEUE) > 1:
|
||||
session.data_out(text=[["%s DoS protection is active. You are queued to connect in %g seconds ..." % (
|
||||
settings.SERVERNAME,
|
||||
len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)],{}])
|
||||
len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)], {}])
|
||||
now = time.time()
|
||||
if (now - self.connection_last < _MIN_TIME_BETWEEN_CONNECTS) or not self.portal.amp_protocol:
|
||||
if not session or not self.connection_task:
|
||||
|
|
@ -176,8 +178,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
del self[session.sessid]
|
||||
|
||||
# Tell the Server to disconnect its version of the Session as well.
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(session,
|
||||
operation=PDISCONN)
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(session, operation=PDISCONN)
|
||||
|
||||
def disconnect_all(self):
|
||||
"""
|
||||
|
|
@ -194,7 +195,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
# inform Server; wait until finished sending before we continue
|
||||
# removing all the sessions.
|
||||
self.portal.amp_protocol.send_AdminPortal2Server(DUMMYSESSION,
|
||||
operation=PDISCONNALL).addCallback(_callback, self)
|
||||
operation=PDISCONNALL).addCallback(_callback, self)
|
||||
|
||||
def server_connect(self, protocol_path="", config=dict()):
|
||||
"""
|
||||
|
|
@ -233,8 +234,8 @@ class PortalSessionHandler(SessionHandler):
|
|||
Called by server to force a disconnect by sessid.
|
||||
|
||||
Args:
|
||||
sessid (int): Session id to disconnect.
|
||||
reason (str, optional): Motivation for disconect.
|
||||
session (portalsession): Session to disconnect.
|
||||
reason (str, optional): Motivation for disconnect.
|
||||
|
||||
"""
|
||||
if session:
|
||||
|
|
@ -335,7 +336,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
|
||||
"""
|
||||
for session in self.values():
|
||||
self.data_out(session, text=[[message],{}])
|
||||
self.data_out(session, text=[[message], {}])
|
||||
|
||||
def data_in(self, session, **kwargs):
|
||||
"""
|
||||
|
|
@ -352,8 +353,8 @@ class PortalSessionHandler(SessionHandler):
|
|||
Data is serialized before passed on.
|
||||
|
||||
"""
|
||||
#from evennia.server.profiling.timetrace import timetrace
|
||||
#text = timetrace(text, "portalsessionhandler.data_in")
|
||||
# from evennia.server.profiling.timetrace import timetrace # DEBUG
|
||||
# text = timetrace(text, "portalsessionhandler.data_in") # DEBUG
|
||||
try:
|
||||
text = kwargs['text']
|
||||
if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT:
|
||||
|
|
@ -365,16 +366,16 @@ class PortalSessionHandler(SessionHandler):
|
|||
pass
|
||||
if session:
|
||||
now = time.time()
|
||||
if self.command_counter > _MAX_COMMAND_RATE:
|
||||
if self.command_counter > _MAX_COMMAND_RATE > 0:
|
||||
# data throttle (anti DoS measure)
|
||||
dT = now - self.command_counter_reset
|
||||
delta_time = now - self.command_counter_reset
|
||||
self.command_counter = 0
|
||||
self.command_counter_reset = now
|
||||
self.command_overflow = dT < 1.0
|
||||
self.command_overflow = delta_time < 1.0
|
||||
if self.command_overflow:
|
||||
reactor.callLater(1.0, self.data_in, None)
|
||||
if self.command_overflow:
|
||||
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW],{}])
|
||||
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW], {}])
|
||||
return
|
||||
# scrub data
|
||||
kwargs = self.clean_senddata(session, kwargs)
|
||||
|
|
@ -385,7 +386,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
self.portal.amp_protocol.send_MsgPortal2Server(session,
|
||||
**kwargs)
|
||||
else:
|
||||
# called by the callLater callback
|
||||
# called by the callLater callback
|
||||
if self.command_overflow:
|
||||
self.command_overflow = False
|
||||
reactor.callLater(1.0, self.data_in, None)
|
||||
|
|
@ -405,8 +406,8 @@ class PortalSessionHandler(SessionHandler):
|
|||
method exixts, it sends the data to a method send_default.
|
||||
|
||||
"""
|
||||
#from evennia.server.profiling.timetrace import timetrace
|
||||
#text = timetrace(text, "portalsessionhandler.data_out")
|
||||
# from evennia.server.profiling.timetrace import timetrace # DEBUG
|
||||
# text = timetrace(text, "portalsessionhandler.data_out") # DEBUG
|
||||
|
||||
# distribute outgoing data to the correct session methods.
|
||||
if session:
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ packages).
|
|||
try:
|
||||
from twisted.conch.ssh.keys import Key
|
||||
except ImportError:
|
||||
raise ImportError (_SSH_IMPORT_ERROR)
|
||||
raise ImportError(_SSH_IMPORT_ERROR)
|
||||
|
||||
from twisted.conch.ssh.userauth import SSHUserAuthServer
|
||||
from twisted.conch.ssh import common
|
||||
|
|
@ -49,7 +49,7 @@ from evennia.players.models import PlayerDB
|
|||
from evennia.utils import ansi
|
||||
from evennia.utils.utils import to_str
|
||||
|
||||
_RE_N = re.compile(r"\{n$")
|
||||
_RE_N = re.compile(r"\|n$")
|
||||
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
|
||||
_GAME_DIR = settings.GAME_DIR
|
||||
|
||||
|
|
@ -206,7 +206,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
|
||||
"""
|
||||
for line in string.split('\n'):
|
||||
#this is the telnet-specific method for sending
|
||||
# the telnet-specific method for sending
|
||||
self.terminal.write(line)
|
||||
self.terminal.nextLine()
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ class SshProtocol(Manhole, session.Session):
|
|||
Note that it must be actively turned back on again!
|
||||
|
||||
"""
|
||||
#print "telnet.send_text", args,kwargs
|
||||
# print "telnet.send_text", args,kwargs # DEBUG
|
||||
text = args[0] if args else ""
|
||||
if text is None:
|
||||
return
|
||||
|
|
@ -268,8 +268,8 @@ class SshProtocol(Manhole, session.Session):
|
|||
useansi = options.get("ansi", flags.get('ANSI', True))
|
||||
raw = options.get("raw", flags.get("RAW", False))
|
||||
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
|
||||
#echo = options.get("echo", None)
|
||||
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
|
||||
# echo = options.get("echo", None) # DEBUG
|
||||
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
|
||||
|
||||
if screenreader:
|
||||
# screenreader mode cleans up output
|
||||
|
|
@ -283,7 +283,8 @@ class SshProtocol(Manhole, session.Session):
|
|||
else:
|
||||
# we need to make sure to kill the color at the end in order
|
||||
# to match the webclient output.
|
||||
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=False)
|
||||
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
|
||||
strip_ansi=nocolor, xterm256=xterm256, mxp=False)
|
||||
self.sendLine(linetosend)
|
||||
|
||||
def send_prompt(self, *args, **kwargs):
|
||||
|
|
@ -453,11 +454,10 @@ def makeFactory(configdict):
|
|||
factory.publicKeys = {'ssh-rsa': publicKey}
|
||||
factory.privateKeys = {'ssh-rsa': privateKey}
|
||||
except Exception as err:
|
||||
print ( "getKeyPair error: {err}\n WARNING: Evennia could not " \
|
||||
"auto-generate SSH keypair. Using conch default keys instead.\n" \
|
||||
"If this error persists, create {pub} and " \
|
||||
"{priv} yourself using third-party tools.".format(
|
||||
err=err, pub=pubkeyfile, priv=privkeyfile))
|
||||
print("getKeyPair error: {err}\n WARNING: Evennia could not "
|
||||
"auto-generate SSH keypair. Using conch default keys instead.\n"
|
||||
"If this error persists, create {pub} and "
|
||||
"{priv} yourself using third-party tools.".format(err=err, pub=pubkeyfile, priv=privkeyfile))
|
||||
|
||||
factory.services = factory.services.copy()
|
||||
factory.services['ssh-userauth'] = ExtraInfoAuthServer
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class SSLProtocol(TelnetProtocol):
|
|||
super(SSLProtocol, self).__init__(*args, **kwargs)
|
||||
self.protocol_name = "ssl"
|
||||
|
||||
|
||||
def verify_SSL_key_and_cert(keyfile, certfile):
|
||||
"""
|
||||
This function looks for RSA key and certificate in the current
|
||||
|
|
@ -82,7 +83,7 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
# try to create the certificate
|
||||
CERT_EXPIRE = 365 * 20 # twenty years validity
|
||||
# default:
|
||||
#openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
|
||||
# openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
|
||||
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
|
||||
try:
|
||||
subprocess.call(exestring)
|
||||
|
|
|
|||
|
|
@ -19,12 +19,13 @@ from evennia.server.portal.mxp import Mxp, mxp_parse
|
|||
from evennia.utils import ansi
|
||||
from evennia.utils.utils import to_str
|
||||
|
||||
_RE_N = re.compile(r"\{n$")
|
||||
_RE_N = re.compile(r"\|n$")
|
||||
_RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE)
|
||||
_RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE)
|
||||
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
|
||||
_IDLE_COMMAND = settings.IDLE_COMMAND + "\n"
|
||||
|
||||
|
||||
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
||||
"""
|
||||
Each player connecting over telnet (ie using most traditional mud
|
||||
|
|
@ -46,7 +47,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
client_address = client_address[0] if client_address else None
|
||||
# this number is counted down for every handshake that completes.
|
||||
# when it reaches 0 the portal/server syncs their data
|
||||
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
|
||||
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
|
||||
self.init_session(self.protocol_name, client_address, self.factory.sessionhandler)
|
||||
|
||||
# negotiate client size
|
||||
|
|
@ -79,7 +80,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.toggle_nop_keepalive()
|
||||
|
||||
def _send_nop_keepalive(self):
|
||||
"Send NOP keepalive unless flag is set"
|
||||
"""Send NOP keepalive unless flag is set"""
|
||||
if self.protocol_flags.get("NOPKEEPALIVE"):
|
||||
self._write(IAC + NOP)
|
||||
|
||||
|
|
@ -140,7 +141,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
enable (bool): If this option should be enabled.
|
||||
|
||||
"""
|
||||
return (option == MCCP or option==ECHO)
|
||||
return option == MCCP or option == ECHO
|
||||
|
||||
def disableLocal(self, option):
|
||||
"""
|
||||
|
|
@ -178,7 +179,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
directly.
|
||||
|
||||
Args:
|
||||
string (str): Incoming data.
|
||||
data (str): Incoming data.
|
||||
|
||||
"""
|
||||
if not data:
|
||||
|
|
@ -188,7 +189,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
# legacy clients. There should never be a reason to send a
|
||||
# lone NULL character so this seems to be a safe thing to
|
||||
# support for backwards compatibility. It also stops the
|
||||
# NULL from continously popping up as an unknown command.
|
||||
# NULL from continuously popping up as an unknown command.
|
||||
data = [_IDLE_COMMAND]
|
||||
else:
|
||||
data = _RE_LINEBREAK.split(data)
|
||||
|
|
@ -205,7 +206,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
self.data_in(text=dat + "\n")
|
||||
|
||||
def _write(self, data):
|
||||
"hook overloading the one used in plain telnet"
|
||||
"""hook overloading the one used in plain telnet"""
|
||||
data = data.replace('\n', '\r\n').replace('\r\r\n', '\r\n')
|
||||
super(TelnetProtocol, self)._write(mccp_compress(self, data))
|
||||
|
||||
|
|
@ -217,24 +218,23 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
line (str): Line to send.
|
||||
|
||||
"""
|
||||
#escape IAC in line mode, and correctly add \r\n
|
||||
# escape IAC in line mode, and correctly add \r\n
|
||||
line += self.delimiter
|
||||
line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n')
|
||||
return self.transport.write(mccp_compress(self, line))
|
||||
|
||||
|
||||
# Session hooks
|
||||
|
||||
def disconnect(self, reason=None):
|
||||
def disconnect(self, reason=""):
|
||||
"""
|
||||
generic hook for the engine to call in order to
|
||||
disconnect this protocol.
|
||||
|
||||
Args:
|
||||
reason (str): Reason for disconnecting.
|
||||
reason (str, optional): Reason for disconnecting.
|
||||
|
||||
"""
|
||||
self.data_out(text=((reason or "",), {}))
|
||||
self.data_out(text=((reason,), {}))
|
||||
self.connectionLost(reason)
|
||||
|
||||
def data_in(self, **kwargs):
|
||||
|
|
@ -245,8 +245,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
kwargs (any): Options from the protocol.
|
||||
|
||||
"""
|
||||
#from evennia.server.profiling.timetrace import timetrace
|
||||
#text = timetrace(text, "telnet.data_in")
|
||||
# from evennia.server.profiling.timetrace import timetrace # DEBUG
|
||||
# text = timetrace(text, "telnet.data_in") # DEBUG
|
||||
|
||||
self.sessionhandler.data_in(self, **kwargs)
|
||||
|
||||
|
|
@ -297,7 +297,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
|
||||
echo = options.get("echo", None)
|
||||
mxp = options.get("mxp", flags.get("MXP", False))
|
||||
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
|
||||
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
|
||||
|
||||
if screenreader:
|
||||
# screenreader mode cleans up output
|
||||
|
|
@ -306,9 +306,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
|
||||
if options.get("send_prompt"):
|
||||
# send a prompt instead.
|
||||
prompt = text
|
||||
if not raw:
|
||||
# processing
|
||||
prompt = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256)
|
||||
prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + ("|n" if prompt[-1] != "|" else "||n"),
|
||||
strip_ansi=nocolor, xterm256=xterm256)
|
||||
if mxp:
|
||||
prompt = mxp_parse(prompt)
|
||||
prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n')
|
||||
|
|
@ -335,7 +337,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
else:
|
||||
# we need to make sure to kill the color at the end in order
|
||||
# to match the webclient output.
|
||||
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
|
||||
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
|
||||
strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
|
||||
if mxp:
|
||||
linetosend = mxp_parse(linetosend)
|
||||
self.sendLine(linetosend)
|
||||
|
|
@ -348,7 +351,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
|
|||
kwargs["options"].update({"send_prompt": True})
|
||||
self.send_text(*args, **kwargs)
|
||||
|
||||
|
||||
def send_default(self, cmdname, *args, **kwargs):
|
||||
"""
|
||||
Send other oob data
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ from evennia.utils.utils import to_str
|
|||
|
||||
# MSDP-relevant telnet cmd/opt-codes
|
||||
MSDP = chr(69)
|
||||
MSDP_VAR = chr(1) #^A
|
||||
MSDP_VAL = chr(2) #^B
|
||||
MSDP_TABLE_OPEN = chr(3) #^C
|
||||
MSDP_TABLE_CLOSE = chr(4) #^D
|
||||
MSDP_ARRAY_OPEN = chr(5) #^E
|
||||
MSDP_ARRAY_CLOSE = chr(6) #^F
|
||||
MSDP_VAR = chr(1) # ^A
|
||||
MSDP_VAL = chr(2) # ^B
|
||||
MSDP_TABLE_OPEN = chr(3) # ^C
|
||||
MSDP_TABLE_CLOSE = chr(4) # ^D
|
||||
MSDP_ARRAY_OPEN = chr(5) # ^E
|
||||
MSDP_ARRAY_CLOSE = chr(6) # ^F
|
||||
|
||||
# GMCP
|
||||
GMCP = chr(201)
|
||||
|
|
@ -51,13 +51,15 @@ force_str = lambda inp: to_str(inp, force_string=True)
|
|||
|
||||
# pre-compiled regexes
|
||||
# returns 2-tuple
|
||||
msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_TABLE_OPEN,
|
||||
MSDP_TABLE_CLOSE))
|
||||
msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
|
||||
% (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_TABLE_OPEN,
|
||||
MSDP_TABLE_CLOSE))
|
||||
# returns 2-tuple
|
||||
msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_ARRAY_OPEN,
|
||||
MSDP_ARRAY_CLOSE))
|
||||
msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
|
||||
% (MSDP_VAR, MSDP_VAL,
|
||||
MSDP_ARRAY_OPEN,
|
||||
MSDP_ARRAY_CLOSE))
|
||||
msdp_regex_var = re.compile(r"%s" % MSDP_VAR)
|
||||
msdp_regex_val = re.compile(r"%s" % MSDP_VAL)
|
||||
|
||||
|
|
@ -67,7 +69,8 @@ EVENNIA_TO_GMCP = {"client_options": "Core.Supports.Get",
|
|||
"repeat": "Char.Repeat.Update",
|
||||
"monitor": "Char.Monitor.Update"}
|
||||
|
||||
# Msdp object handler
|
||||
|
||||
# MSDP/GMCP communication handler
|
||||
|
||||
class TelnetOOB(object):
|
||||
"""
|
||||
|
|
@ -100,7 +103,7 @@ class TelnetOOB(object):
|
|||
Client reports No msdp supported or wanted.
|
||||
|
||||
Args:
|
||||
options (Option): Not used.
|
||||
option (Option): Not used.
|
||||
|
||||
"""
|
||||
# no msdp, check GMCP
|
||||
|
|
@ -173,7 +176,7 @@ class TelnetOOB(object):
|
|||
if not (args or kwargs):
|
||||
return msdp_cmdname
|
||||
|
||||
#print "encode_msdp in:", cmdname, args, kwargs
|
||||
# print("encode_msdp in:", cmdname, args, kwargs) # DEBUG
|
||||
|
||||
msdp_args = ''
|
||||
if args:
|
||||
|
|
@ -182,30 +185,30 @@ class TelnetOOB(object):
|
|||
msdp_args += args[0]
|
||||
else:
|
||||
msdp_args += "{msdp_array_open}" \
|
||||
"{msdp_args}" \
|
||||
"{msdp_array_close}".format(
|
||||
msdp_array_open=MSDP_ARRAY_OPEN,
|
||||
msdp_array_close=MSDP_ARRAY_CLOSE,
|
||||
msdp_args= "".join("%s%s" % (
|
||||
MSDP_VAL, json.dumps(val))
|
||||
for val in args))
|
||||
|
||||
"{msdp_args}" \
|
||||
"{msdp_array_close}".format(
|
||||
msdp_array_open=MSDP_ARRAY_OPEN,
|
||||
msdp_array_close=MSDP_ARRAY_CLOSE,
|
||||
msdp_args="".join("%s%s"
|
||||
% (MSDP_VAL, json.dumps(val))
|
||||
for val in args))
|
||||
|
||||
msdp_kwargs = ""
|
||||
if kwargs:
|
||||
msdp_kwargs = msdp_cmdname
|
||||
msdp_kwargs += "{msdp_table_open}" \
|
||||
"{msdp_kwargs}" \
|
||||
"{msdp_table_close}".format(
|
||||
msdp_table_open=MSDP_TABLE_OPEN,
|
||||
msdp_table_close=MSDP_TABLE_CLOSE,
|
||||
msdp_kwargs = "".join("%s%s%s%s" % (
|
||||
MSDP_VAR, key, MSDP_VAL, json.dumps(val))
|
||||
for key, val in kwargs.iteritems()))
|
||||
"{msdp_kwargs}" \
|
||||
"{msdp_table_close}".format(
|
||||
msdp_table_open=MSDP_TABLE_OPEN,
|
||||
msdp_table_close=MSDP_TABLE_CLOSE,
|
||||
msdp_kwargs="".join("%s%s%s%s"
|
||||
% (MSDP_VAR, key, MSDP_VAL,
|
||||
json.dumps(val))
|
||||
for key, val in kwargs.iteritems()))
|
||||
|
||||
msdp_string = msdp_args + msdp_kwargs
|
||||
|
||||
#print "msdp_string:", msdp_string
|
||||
# print("msdp_string:", msdp_string) # DEBUG
|
||||
return msdp_string
|
||||
|
||||
def encode_gmcp(self, cmdname, *args, **kwargs):
|
||||
|
|
@ -238,10 +241,10 @@ class TelnetOOB(object):
|
|||
gmcp_string = "%s %s" % (cmdname, json.dumps([args, kwargs]))
|
||||
else:
|
||||
gmcp_string = "%s %s" % (cmdname, json.dumps(args))
|
||||
else: # only kwargs
|
||||
else: # only kwargs
|
||||
gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs))
|
||||
|
||||
#print "gmcp string", gmcp_string
|
||||
# print("gmcp string", gmcp_string) # DEBUG
|
||||
return gmcp_string
|
||||
|
||||
def decode_msdp(self, data):
|
||||
|
|
@ -271,7 +274,7 @@ class TelnetOOB(object):
|
|||
if hasattr(data, "__iter__"):
|
||||
data = "".join(data)
|
||||
|
||||
#print "decode_msdp in:", data
|
||||
# print("decode_msdp in:", data) # DEBUG
|
||||
|
||||
tables = {}
|
||||
arrays = {}
|
||||
|
|
@ -279,7 +282,7 @@ class TelnetOOB(object):
|
|||
|
||||
# decode tables
|
||||
for key, table in msdp_regex_table.findall(data):
|
||||
tables[key] = {} if not key in tables else tables[key]
|
||||
tables[key] = {} if key not in tables else tables[key]
|
||||
for varval in msdp_regex_var.split(table)[1:]:
|
||||
var, val = msdp_regex_val.split(varval, 1)
|
||||
if var:
|
||||
|
|
@ -288,7 +291,7 @@ class TelnetOOB(object):
|
|||
# decode arrays from all that was not a table
|
||||
data_no_tables = msdp_regex_table.sub("", data)
|
||||
for key, array in msdp_regex_array.findall(data_no_tables):
|
||||
arrays[key] = [] if not key in arrays else arrays[key]
|
||||
arrays[key] = [] if key not in arrays else arrays[key]
|
||||
parts = msdp_regex_val.split(array)
|
||||
if len(parts) == 2:
|
||||
arrays[key].append(parts[1])
|
||||
|
|
@ -326,10 +329,9 @@ class TelnetOOB(object):
|
|||
for key, var in variables.iteritems():
|
||||
cmds[key] = [[var], {}]
|
||||
|
||||
#print "msdp data in:", cmds
|
||||
# print("msdp data in:", cmds) # DEBUG
|
||||
self.protocol.data_in(**cmds)
|
||||
|
||||
|
||||
def decode_gmcp(self, data):
|
||||
"""
|
||||
Decodes incoming GMCP data on the form 'varname <structure>'.
|
||||
|
|
@ -353,7 +355,7 @@ class TelnetOOB(object):
|
|||
if hasattr(data, "__iter__"):
|
||||
data = "".join(data)
|
||||
|
||||
#print "decode_gmcp in:", data
|
||||
# print("decode_gmcp in:", data) # DEBUG
|
||||
if data:
|
||||
try:
|
||||
cmdname, structure = data.split(None, 1)
|
||||
|
|
@ -368,7 +370,7 @@ class TelnetOOB(object):
|
|||
args, kwargs = [], {}
|
||||
if hasattr(structure, "__iter__"):
|
||||
if isinstance(structure, dict):
|
||||
kwargs = {key: value for key, value in structure.iteritems() if key }
|
||||
kwargs = {key: value for key, value in structure.iteritems() if key}
|
||||
else:
|
||||
args = list(structure)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ MTTS = [(128, 'PROXY'),
|
|||
(2, 'VT100'),
|
||||
(1, 'ANSI')]
|
||||
|
||||
|
||||
class Ttype(object):
|
||||
"""
|
||||
Handles ttype negotiations. Called and initiated by the
|
||||
|
|
@ -104,22 +105,21 @@ class Ttype(object):
|
|||
# use name to identify support for xterm256. Many of these
|
||||
# only support after a certain version, but all support
|
||||
# it since at least 4 years. We assume recent client here for now.
|
||||
xterm256 = False
|
||||
cupper = clientname.upper()
|
||||
if cupper.startswith("MUDLET"):
|
||||
# supports xterm256 stably since 1.1 (2010?)
|
||||
xterm256 = cupper.split("MUDLET",1)[1].strip() >= "1.1"
|
||||
xterm256 = cupper.split("MUDLET", 1)[1].strip() >= "1.1"
|
||||
else:
|
||||
xterm256 = (cupper.startswith("XTERM") or
|
||||
cupper.endswith("-256COLOR") or
|
||||
cupper in ("ATLANTIS", # > 0.9.9.0 (aug 2009)
|
||||
"CMUD", # > 3.04 (mar 2009)
|
||||
"KILDCLIENT", # > 2.2.0 (sep 2005)
|
||||
"MUDLET", # > beta 15 (sep 2009)
|
||||
"MUSHCLIENT", # > 4.02 (apr 2007)
|
||||
"PUTTY", # > 0.58 (apr 2005)
|
||||
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
|
||||
"POTATO")) # > 2.00 (maybe earlier)
|
||||
"CMUD", # > 3.04 (mar 2009)
|
||||
"KILDCLIENT", # > 2.2.0 (sep 2005)
|
||||
"MUDLET", # > beta 15 (sep 2009)
|
||||
"MUSHCLIENT", # > 4.02 (apr 2007)
|
||||
"PUTTY", # > 0.58 (apr 2005)
|
||||
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
|
||||
"POTATO")) # > 2.00 (maybe earlier)
|
||||
|
||||
# all clients supporting TTYPE at all seem to support ANSI
|
||||
self.protocol.protocol_flags['ANSI'] = True
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ from django.utils.translation import ugettext as _
|
|||
|
||||
# Handlers for Session.db/ndb operation
|
||||
|
||||
|
||||
class NDbHolder(object):
|
||||
"Holder for allowing property access of attributes"
|
||||
"""Holder for allowing property access of attributes"""
|
||||
def __init__(self, obj, name, manager_name='attributes'):
|
||||
_SA(self, name, _GA(obj, manager_name))
|
||||
_SA(self, 'name', name)
|
||||
|
|
@ -145,9 +146,9 @@ class NAttributeHandler(object):
|
|||
return [key for key in self._store if not key.startswith("_")]
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# Server Session
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
class ServerSession(Session):
|
||||
"""
|
||||
|
|
@ -160,7 +161,7 @@ class ServerSession(Session):
|
|||
|
||||
"""
|
||||
def __init__(self):
|
||||
"Initiate to avoid AttributeErrors down the line"
|
||||
"""Initiate to avoid AttributeErrors down the line"""
|
||||
self.puppet = None
|
||||
self.player = None
|
||||
self.cmdset_storage_string = ""
|
||||
|
|
@ -203,7 +204,7 @@ class ServerSession(Session):
|
|||
obj.player = self.player
|
||||
self.puid = obj.id
|
||||
self.puppet = obj
|
||||
#obj.scripts.validate()
|
||||
# obj.scripts.validate()
|
||||
obj.locks.cache_lock_bypass(obj)
|
||||
|
||||
def at_login(self, player):
|
||||
|
|
@ -264,7 +265,6 @@ class ServerSession(Session):
|
|||
MONITOR_HANDLER.remove(player, "_saved_webclient_options",
|
||||
self.sessid)
|
||||
|
||||
|
||||
def get_player(self):
|
||||
"""
|
||||
Get the player associated with this session
|
||||
|
|
@ -364,7 +364,6 @@ class ServerSession(Session):
|
|||
self.protocol_flags.update(kwargs)
|
||||
self.sessionhandler.session_portal_sync(self)
|
||||
|
||||
|
||||
def data_out(self, **kwargs):
|
||||
"""
|
||||
Sending data from Evennia->Client
|
||||
|
|
@ -437,7 +436,7 @@ class ServerSession(Session):
|
|||
self.sessionhandler.data_in(self, **kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
"Handle session comparisons"
|
||||
"""Handle session comparisons"""
|
||||
try:
|
||||
return self.address == other.address
|
||||
except AttributeError:
|
||||
|
|
@ -462,11 +461,9 @@ class ServerSession(Session):
|
|||
return "%s%s@%s" % (self.uname, symbol, address)
|
||||
|
||||
def __unicode__(self):
|
||||
"Unicode representation"
|
||||
"""Unicode representation"""
|
||||
return u"%s" % str(self)
|
||||
|
||||
|
||||
|
||||
# Dummy API hooks for use during non-loggedin operation
|
||||
|
||||
def at_cmdset_get(self, **kwargs):
|
||||
|
|
@ -488,7 +485,7 @@ class ServerSession(Session):
|
|||
def attributes(self):
|
||||
return self.nattributes
|
||||
|
||||
#@property
|
||||
# @property
|
||||
def ndb_get(self):
|
||||
"""
|
||||
A non-persistent store (ndb: NonDataBase). Everything stored
|
||||
|
|
@ -503,7 +500,7 @@ class ServerSession(Session):
|
|||
self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes")
|
||||
return self._ndb_holder
|
||||
|
||||
#@ndb.setter
|
||||
# @ndb.setter
|
||||
def ndb_set(self, value):
|
||||
"""
|
||||
Stop accidentally replacing the db object
|
||||
|
|
@ -516,9 +513,9 @@ class ServerSession(Session):
|
|||
string += "Use ndb.attr=value instead."
|
||||
raise Exception(string)
|
||||
|
||||
#@ndb.deleter
|
||||
# @ndb.deleter
|
||||
def ndb_del(self):
|
||||
"Stop accidental deletion."
|
||||
"""Stop accidental deletion."""
|
||||
raise Exception("Cannot delete the ndb object!")
|
||||
ndb = property(ndb_get, ndb_set, ndb_del)
|
||||
db = property(ndb_get, ndb_set, ndb_del)
|
||||
|
|
@ -526,5 +523,5 @@ class ServerSession(Session):
|
|||
# Mock access method for the session (there is no lock info
|
||||
# at this stage, so we just present a uniform API)
|
||||
def access(self, *args, **kwargs):
|
||||
"Dummy method to mimic the logged-in API."
|
||||
"""Dummy method to mimic the logged-in API."""
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ from twisted.web.wsgi import WSGIResource
|
|||
from django.conf import settings
|
||||
from django.core.handlers.wsgi import WSGIHandler
|
||||
|
||||
from evennia.utils import logger
|
||||
|
||||
_UPSTREAM_IPS = settings.UPSTREAM_IPS
|
||||
_DEBUG = settings.DEBUG
|
||||
|
||||
|
|
@ -70,6 +72,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
|
|||
resource (EvenniaReverseProxyResource): A proxy resource.
|
||||
|
||||
"""
|
||||
request.notifyFinish().addErrback(lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f))
|
||||
return EvenniaReverseProxyResource(
|
||||
self.host, self.port, self.path + '/' + urlquote(path, safe=""),
|
||||
self.reactor)
|
||||
|
|
@ -98,6 +101,8 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
|
|||
request.getAllHeaders(), request.content.read(), request)
|
||||
clientFactory.noisy = False
|
||||
self.reactor.connectTCP(self.host, self.port, clientFactory)
|
||||
# don't trigger traceback if connection is lost before request finish.
|
||||
request.notifyFinish().addErrback(lambda f: f.cancel())
|
||||
return NOT_DONE_YET
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,11 +24,12 @@ from evennia.utils.utils import lazy_property, to_str, make_iter
|
|||
|
||||
_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Attributes
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class Attribute(SharedMemoryModel):
|
||||
"""
|
||||
|
|
@ -90,7 +91,7 @@ class Attribute(SharedMemoryModel):
|
|||
'date_created', editable=False, auto_now_add=True)
|
||||
|
||||
# Database manager
|
||||
#objects = managers.AttributeManager()
|
||||
# objects = managers.AttributeManager()
|
||||
|
||||
@lazy_property
|
||||
def locks(self):
|
||||
|
|
@ -110,12 +111,15 @@ class Attribute(SharedMemoryModel):
|
|||
|
||||
def __lock_storage_get(self):
|
||||
return self.db_lock_storage
|
||||
|
||||
def __lock_storage_set(self, value):
|
||||
self.db_lock_storage = value
|
||||
self.save(update_fields=["db_lock_storage"])
|
||||
|
||||
def __lock_storage_del(self):
|
||||
self.db_lock_storage = ""
|
||||
self.save(update_fields=["db_lock_storage"])
|
||||
|
||||
lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del)
|
||||
|
||||
# Wrapper properties to easily set database fields. These are
|
||||
|
|
@ -127,7 +131,7 @@ class Attribute(SharedMemoryModel):
|
|||
# is the object in question).
|
||||
|
||||
# value property (wraps db_value)
|
||||
#@property
|
||||
# @property
|
||||
def __value_get(self):
|
||||
"""
|
||||
Getter. Allows for `value = self.value`.
|
||||
|
|
@ -137,19 +141,19 @@ class Attribute(SharedMemoryModel):
|
|||
"""
|
||||
return from_pickle(self.db_value, db_obj=self)
|
||||
|
||||
#@value.setter
|
||||
# @value.setter
|
||||
def __value_set(self, new_value):
|
||||
"""
|
||||
Setter. Allows for self.value = value. We cannot cache here,
|
||||
see self.__value_get.
|
||||
"""
|
||||
self.db_value = to_pickle(new_value)
|
||||
#print "value_set, self.db_value:", repr(self.db_value)
|
||||
# print("value_set, self.db_value:", repr(self.db_value)) # DEBUG
|
||||
self.save(update_fields=["db_value"])
|
||||
|
||||
#@value.deleter
|
||||
# @value.deleter
|
||||
def __value_del(self):
|
||||
"Deleter. Allows for del attr.value. This removes the entire attribute."
|
||||
"""Deleter. Allows for del attr.value. This removes the entire attribute."""
|
||||
self.delete()
|
||||
value = property(__value_get, __value_set, __value_del)
|
||||
|
||||
|
|
@ -163,7 +167,7 @@ class Attribute(SharedMemoryModel):
|
|||
return smart_str("%s(%s)" % (self.db_key, self.id))
|
||||
|
||||
def __unicode__(self):
|
||||
return u"%s(%s)" % (self.db_key,self.id)
|
||||
return u"%s(%s)" % (self.db_key, self.id)
|
||||
|
||||
def access(self, accessing_obj, access_type='read', default=False, **kwargs):
|
||||
"""
|
||||
|
|
@ -202,7 +206,7 @@ class AttributeHandler(object):
|
|||
_attrtype = None
|
||||
|
||||
def __init__(self, obj):
|
||||
"Initialize handler."
|
||||
"""Initialize handler."""
|
||||
self.obj = obj
|
||||
self._objid = obj.id
|
||||
self._model = to_str(obj.__dbclass__.__name__.lower())
|
||||
|
|
@ -213,16 +217,16 @@ class AttributeHandler(object):
|
|||
self._cache_complete = False
|
||||
|
||||
def _fullcache(self):
|
||||
"Cache all attributes of this object"
|
||||
query = {"%s__id" % self._model : self._objid,
|
||||
"attribute__db_model" : self._model,
|
||||
"attribute__db_attrtype" : self._attrtype}
|
||||
"""Cache all attributes of this object"""
|
||||
query = {"%s__id" % self._model: self._objid,
|
||||
"attribute__db_model": self._model,
|
||||
"attribute__db_attrtype": self._attrtype}
|
||||
attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
|
||||
self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(),
|
||||
attr.db_category.lower() if attr.db_category else None),
|
||||
attr) for attr in attrs)
|
||||
self._cache_complete = True
|
||||
|
||||
|
||||
def _getcache(self, key=None, category=None):
|
||||
"""
|
||||
Retrieve from cache or database (always caches)
|
||||
|
|
@ -264,15 +268,15 @@ class AttributeHandler(object):
|
|||
del self._cache[cachekey]
|
||||
if cachefound:
|
||||
if attr:
|
||||
return [attr] # return cached entity
|
||||
return [attr] # return cached entity
|
||||
else:
|
||||
return [] # no such attribute: return an empty list
|
||||
else:
|
||||
query = {"%s__id" % self._model : self._objid,
|
||||
"attribute__db_model" : self._model,
|
||||
"attribute__db_attrtype" : self._attrtype,
|
||||
"attribute__db_key__iexact" : key.lower(),
|
||||
"attribute__db_category__iexact" : category.lower() if category else None}
|
||||
query = {"%s__id" % self._model: self._objid,
|
||||
"attribute__db_model": self._model,
|
||||
"attribute__db_attrtype": self._attrtype,
|
||||
"attribute__db_key__iexact": key.lower(),
|
||||
"attribute__db_category__iexact": category.lower() if category else None}
|
||||
conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)
|
||||
if conn:
|
||||
attr = conn[0].attribute
|
||||
|
|
@ -290,15 +294,15 @@ class AttributeHandler(object):
|
|||
# for this category before
|
||||
catkey = "-%s" % category
|
||||
if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache:
|
||||
return [attr for key, attr in self._cache.items() if key.endswith(catkey)]
|
||||
return [attr for key, attr in self._cache.items() if key.endswith(catkey) and attr]
|
||||
else:
|
||||
# we have to query to make this category up-date in the cache
|
||||
query = {"%s__id" % self._model : self._objid,
|
||||
"attribute__db_model" : self._model,
|
||||
"attribute__db_attrtype" : self._attrtype,
|
||||
"attribute__db_category__iexact" : category.lower() if category else None}
|
||||
attrs = [conn.attribute for conn in getattr(self.obj,
|
||||
self._m2m_fieldname).through.objects.filter(**query)]
|
||||
query = {"%s__id" % self._model: self._objid,
|
||||
"attribute__db_model": self._model,
|
||||
"attribute__db_attrtype": self._attrtype,
|
||||
"attribute__db_category__iexact": category.lower() if category else None}
|
||||
attrs = [conn.attribute for conn
|
||||
in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
|
||||
for attr in attrs:
|
||||
if attr.pk:
|
||||
cachekey = "%s-%s" % (attr.db_key, category)
|
||||
|
|
@ -306,7 +310,6 @@ class AttributeHandler(object):
|
|||
# mark category cache as up-to-date
|
||||
self._catcache[catkey] = True
|
||||
return attrs
|
||||
return []
|
||||
|
||||
def _setcache(self, key, category, attr_obj):
|
||||
"""
|
||||
|
|
@ -318,7 +321,7 @@ class AttributeHandler(object):
|
|||
attr_obj (Attribute): The newly saved attribute
|
||||
|
||||
"""
|
||||
if not key: # don't allow an empty key in cache
|
||||
if not key: # don't allow an empty key in cache
|
||||
return
|
||||
cachekey = "%s-%s" % (key, category)
|
||||
catkey = "-%s" % category
|
||||
|
|
@ -342,7 +345,7 @@ class AttributeHandler(object):
|
|||
self._cache.pop(cachekey, None)
|
||||
else:
|
||||
self._cache = {key: attrobj for key, attrobj in
|
||||
self._cache.items() if not key.endswith(catkey)}
|
||||
self._cache.items() if not key.endswith(catkey)}
|
||||
# mark that the category cache is no longer up-to-date
|
||||
self._catcache.pop(catkey, None)
|
||||
self._cache_complete = False
|
||||
|
|
@ -402,6 +405,7 @@ class AttributeHandler(object):
|
|||
accessing_obj (object, optional): If set, an `attrread`
|
||||
permission lock will be checked before returning each
|
||||
looked-after Attribute.
|
||||
default_access (bool, optional):
|
||||
|
||||
Returns:
|
||||
result (any, Attribute or list): This will be the value of the found
|
||||
|
|
@ -416,7 +420,7 @@ class AttributeHandler(object):
|
|||
"""
|
||||
|
||||
class RetDefault(object):
|
||||
"Holds default values"
|
||||
"""Holds default values"""
|
||||
def __init__(self):
|
||||
self.key = None
|
||||
self.value = default
|
||||
|
|
@ -444,8 +448,7 @@ class AttributeHandler(object):
|
|||
ret = ret if return_obj else [attr.value for attr in ret if attr]
|
||||
if not ret:
|
||||
return ret if len(key) > 1 else default
|
||||
return ret[0] if len(ret)==1 else ret
|
||||
|
||||
return ret[0] if len(ret) == 1 else ret
|
||||
|
||||
def add(self, key, value, category=None, lockstring="",
|
||||
strattr=False, accessing_obj=None, default_access=True):
|
||||
|
|
@ -470,8 +473,7 @@ class AttributeHandler(object):
|
|||
`attrcreate` is defined on the Attribute in question.
|
||||
|
||||
"""
|
||||
if accessing_obj and not self.obj.access(accessing_obj,
|
||||
self._attrcreate, default=default_access):
|
||||
if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access):
|
||||
# check create access
|
||||
return
|
||||
|
||||
|
|
@ -494,21 +496,20 @@ class AttributeHandler(object):
|
|||
attr_obj.value = value
|
||||
else:
|
||||
# create a new Attribute (no OOB handlers can be notified)
|
||||
kwargs = {"db_key" : keystr,
|
||||
"db_category" : category,
|
||||
"db_model" : self._model,
|
||||
"db_attrtype" : self._attrtype,
|
||||
"db_value" : None if strattr else to_pickle(value),
|
||||
"db_strvalue" : value if strattr else None}
|
||||
kwargs = {"db_key": keystr,
|
||||
"db_category": category,
|
||||
"db_model": self._model,
|
||||
"db_attrtype": self._attrtype,
|
||||
"db_value": None if strattr else to_pickle(value),
|
||||
"db_strvalue": value if strattr else None}
|
||||
new_attr = Attribute(**kwargs)
|
||||
new_attr.save()
|
||||
getattr(self.obj, self._m2m_fieldname).add(new_attr)
|
||||
# update cache
|
||||
self._setcache(keystr, category, new_attr)
|
||||
|
||||
|
||||
def batch_add(self, key, value, category=None, lockstring="",
|
||||
strattr=False, accessing_obj=None, default_access=True):
|
||||
strattr=False, accessing_obj=None, default_access=True):
|
||||
"""
|
||||
Batch-version of `add()`. This is more efficient than
|
||||
repeat-calling add when having many Attributes to add.
|
||||
|
|
@ -535,8 +536,7 @@ class AttributeHandler(object):
|
|||
RuntimeError: If `key` and `value` lists are not of the
|
||||
same lengths.
|
||||
"""
|
||||
if accessing_obj and not self.obj.access(accessing_obj,
|
||||
self._attrcreate, default=default_access):
|
||||
if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access):
|
||||
# check create access
|
||||
return
|
||||
|
||||
|
|
@ -564,12 +564,12 @@ class AttributeHandler(object):
|
|||
attr_obj.value = new_value
|
||||
else:
|
||||
# create a new Attribute (no OOB handlers can be notified)
|
||||
kwargs = {"db_key" : keystr,
|
||||
"db_category" : category,
|
||||
kwargs = {"db_key": keystr,
|
||||
"db_category": category,
|
||||
"db_model": self._model,
|
||||
"db_attrtype" : self._attrtype,
|
||||
"db_value" : None if strattr else to_pickle(new_value),
|
||||
"db_strvalue" : value if strattr else None}
|
||||
"db_attrtype": self._attrtype,
|
||||
"db_value": None if strattr else to_pickle(new_value),
|
||||
"db_strvalue": value if strattr else None}
|
||||
new_attr = Attribute(**kwargs)
|
||||
new_attr.save()
|
||||
new_attrobjs.append(new_attr)
|
||||
|
|
@ -578,7 +578,6 @@ class AttributeHandler(object):
|
|||
# Add new objects to m2m field all at once
|
||||
getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs)
|
||||
|
||||
|
||||
def remove(self, key, raise_exception=False, category=None,
|
||||
accessing_obj=None, default_access=True):
|
||||
"""
|
||||
|
|
@ -664,7 +663,7 @@ class AttributeHandler(object):
|
|||
key=lambda o: o.id)
|
||||
if accessing_obj:
|
||||
return [attr for attr in attrs
|
||||
if attr.access(accessing_obj, self._attredit, default=default_access)]
|
||||
if attr.access(accessing_obj, self._attredit, default=default_access)]
|
||||
else:
|
||||
return attrs
|
||||
|
||||
|
|
@ -729,7 +728,6 @@ def initialize_nick_templates(in_template, out_template):
|
|||
|
||||
"""
|
||||
|
||||
|
||||
# create the regex for in_template
|
||||
regex_string = fnmatch.translate(in_template)
|
||||
# we must account for a possible line break coming over the wire
|
||||
|
|
@ -876,12 +874,12 @@ class NickHandler(AttributeHandler):
|
|||
nicks = {}
|
||||
for category in make_iter(categories):
|
||||
nicks.update({nick.key: nick
|
||||
for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key})
|
||||
for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key})
|
||||
if include_player and self.obj.has_player:
|
||||
for category in make_iter(categories):
|
||||
nicks.update({nick.key: nick
|
||||
for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True))
|
||||
if nick and nick.key})
|
||||
for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True))
|
||||
if nick and nick.key})
|
||||
for key, nick in nicks.iteritems():
|
||||
nick_regex, template, _, _ = nick.value
|
||||
regex = self._regex_cache.get(nick_regex)
|
||||
|
|
|
|||
|
|
@ -5,12 +5,15 @@ Use the codes defined in ANSIPARSER in your text
|
|||
to apply colour to text according to the ANSI standard.
|
||||
|
||||
Examples:
|
||||
This is %crRed text%cn and this is normal again.
|
||||
This is {rRed text{n and this is normal again.
|
||||
This is |rRed text|n and this is normal again.
|
||||
This is {rRed text{n and this is normal again. # soon to be depreciated
|
||||
This is %crRed text%cn and this is normal again. # depreciated
|
||||
|
||||
|
||||
Mostly you should not need to call parse_ansi() explicitly;
|
||||
it is run by Evennia just before returning data to/from the
|
||||
user.
|
||||
user. Depreciated example forms are available by extending
|
||||
the ansi mapping.
|
||||
|
||||
"""
|
||||
from builtins import object, range
|
||||
|
|
@ -133,7 +136,7 @@ class ANSIParser(object):
|
|||
rgbtag = rgbmatch.group()[1:]
|
||||
|
||||
background = rgbtag[0] == '['
|
||||
grayscale = rgbtag[0 + int(background)] == '='
|
||||
grayscale = rgbtag[0 + int(background)] == '='
|
||||
if not grayscale:
|
||||
# 6x6x6 color-cube (xterm indexes 16-231)
|
||||
if background:
|
||||
|
|
@ -143,9 +146,9 @@ class ANSIParser(object):
|
|||
else:
|
||||
# grayscale values (xterm indexes 0, 232-255, 15) for full spectrum
|
||||
letter = rgbtag[int(background) + 1]
|
||||
if (letter == 'a'):
|
||||
if letter == 'a':
|
||||
colval = 16 # pure black @ index 16 (first color cube entry)
|
||||
elif (letter == 'z'):
|
||||
elif letter == 'z':
|
||||
colval = 231 # pure white @ index 231 (last color cube entry)
|
||||
else:
|
||||
# letter in range [b..y] (exactly 24 values!)
|
||||
|
|
@ -161,8 +164,8 @@ class ANSIParser(object):
|
|||
colval = 16 + (red * 36) + (green * 6) + blue
|
||||
|
||||
return "\033[%s8;5;%sm" % (3 + int(background), colval)
|
||||
# replaced since some cliens (like Potato) does not accept codes with leading zeroes, see issue #1024.
|
||||
#return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10)
|
||||
# replaced since some clients (like Potato) does not accept codes with leading zeroes, see issue #1024.
|
||||
# return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10)
|
||||
|
||||
else:
|
||||
# xterm256 not supported, convert the rgb value to ansi instead
|
||||
|
|
@ -289,7 +292,7 @@ class ANSIParser(object):
|
|||
in_string = utils.to_str(string)
|
||||
|
||||
# do string replacement
|
||||
parsed_string = ""
|
||||
parsed_string = ""
|
||||
parts = self.ansi_escapes.split(in_string) + [" "]
|
||||
for part, sep in zip(parts[::2], parts[1::2]):
|
||||
pstring = self.xterm256_sub.sub(do_xterm256, part)
|
||||
|
|
@ -307,7 +310,7 @@ class ANSIParser(object):
|
|||
# cache and crop old cache
|
||||
_PARSE_CACHE[cachekey] = parsed_string
|
||||
if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE:
|
||||
_PARSE_CACHE.popitem(last=False)
|
||||
_PARSE_CACHE.popitem(last=False)
|
||||
|
||||
return parsed_string
|
||||
|
||||
|
|
@ -321,8 +324,8 @@ class ANSIParser(object):
|
|||
(r'{/', ANSI_RETURN), # line break
|
||||
(r'{-', ANSI_TAB), # tab
|
||||
(r'{_', ANSI_SPACE), # space
|
||||
(r'{*', ANSI_INVERSE), # invert
|
||||
(r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
|
||||
(r'{*', ANSI_INVERSE), # invert
|
||||
(r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
|
||||
(r'{u', ANSI_UNDERLINE), # underline
|
||||
|
||||
(r'{r', hilite + ANSI_RED),
|
||||
|
|
@ -366,14 +369,14 @@ class ANSIParser(object):
|
|||
(r'{[W', ANSI_BACK_WHITE), # light grey background
|
||||
(r'{[X', ANSI_BACK_BLACK), # pure black background
|
||||
|
||||
## alternative |-format
|
||||
# alternative |-format
|
||||
|
||||
(r'|n', ANSI_NORMAL), # reset
|
||||
(r'|/', ANSI_RETURN), # line break
|
||||
(r'|-', ANSI_TAB), # tab
|
||||
(r'|_', ANSI_SPACE), # space
|
||||
(r'|*', ANSI_INVERSE), # invert
|
||||
(r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
|
||||
(r'|*', ANSI_INVERSE), # invert
|
||||
(r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
|
||||
(r'|u', ANSI_UNDERLINE), # underline
|
||||
|
||||
(r'|r', hilite + ANSI_RED),
|
||||
|
|
@ -433,8 +436,7 @@ class ANSIParser(object):
|
|||
(r'{[w', r'{[555'), # white background
|
||||
(r'{[x', r'{[222'), # dark grey background
|
||||
|
||||
## |-style variations
|
||||
|
||||
# |-style variations
|
||||
(r'|[r', r'|[500'),
|
||||
(r'|[g', r'|[050'),
|
||||
(r'|[y', r'|[550'),
|
||||
|
|
@ -448,13 +450,13 @@ class ANSIParser(object):
|
|||
# the sub_xterm256 method
|
||||
|
||||
xterm256_map = [
|
||||
(r'\{[0-5]{3}', ""), # {123 - foreground colour
|
||||
(r'\{[0-5]{3}', ""), # {123 - foreground colour
|
||||
(r'\{\[[0-5]{3}', ""), # {[123 - background colour
|
||||
## |-style
|
||||
(r'\|[0-5]{3}', ""), # |123 - foreground colour
|
||||
(r'\|\[[0-5]{3}', ""), # |[123 - background colour
|
||||
# |-style
|
||||
(r'\|[0-5]{3}', ""), # |123 - foreground colour
|
||||
(r'\|\[[0-5]{3}', ""), # |[123 - background colour
|
||||
|
||||
## grayscale entries including ansi extremes: {=a .. {=z
|
||||
# grayscale entries including ansi extremes: {=a .. {=z
|
||||
(r'\{=[a-z]', ""),
|
||||
(r'\{\[=[a-z]', ""),
|
||||
(r'\|=[a-z]', ""),
|
||||
|
|
@ -512,6 +514,7 @@ def strip_ansi(string, parser=ANSI_PARSER):
|
|||
markup.
|
||||
|
||||
Args:
|
||||
string (str): The string to strip.
|
||||
parser (ansi.AnsiParser, optional): The parser to use.
|
||||
|
||||
Returns:
|
||||
|
|
@ -520,6 +523,7 @@ def strip_ansi(string, parser=ANSI_PARSER):
|
|||
"""
|
||||
return parser.parse_ansi(string, strip_ansi=True)
|
||||
|
||||
|
||||
def strip_raw_ansi(string, parser=ANSI_PARSER):
|
||||
"""
|
||||
Remove raw ansi codes from string. This assumes pure
|
||||
|
|
@ -544,7 +548,7 @@ def raw(string):
|
|||
string (str): The raw, escaped string.
|
||||
|
||||
"""
|
||||
return string.replace('{', '{{')
|
||||
return string.replace('{', '{{').replace('|', '||')
|
||||
|
||||
|
||||
def group(lst, n):
|
||||
|
|
@ -1056,7 +1060,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
|||
|
||||
res.append(self[start:len(self)])
|
||||
if drop_spaces:
|
||||
return [part for part in res if part != ""]
|
||||
return [part for part in res if part != ""]
|
||||
return res
|
||||
|
||||
def rsplit(self, by=None, maxsplit=-1):
|
||||
|
|
@ -1129,7 +1133,6 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
|||
rstripped = rstripped[::-1]
|
||||
return ANSIString(lstripped + raw[ir1:ir2+1] + rstripped)
|
||||
|
||||
|
||||
def lstrip(self, chars=None):
|
||||
"""
|
||||
Strip from the left, taking ANSI markers into account.
|
||||
|
|
|
|||
|
|
@ -183,12 +183,13 @@ _ENCODINGS = settings.ENCODINGS
|
|||
_RE_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE)
|
||||
_RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
|
||||
_RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE)
|
||||
_RE_CODE_OR_HEADER = re.compile(r"(\A|^\#CODE|^\#HEADER).*?$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)", re.MULTILINE + re.DOTALL)
|
||||
_RE_CODE_OR_HEADER = re.compile(r"(\A|^\#CODE|^\#HEADER).*?$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)",
|
||||
re.MULTILINE + re.DOTALL)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
# Helper function
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def read_batchfile(pythonpath, file_ending='.py'):
|
||||
"""
|
||||
|
|
@ -212,8 +213,7 @@ def read_batchfile(pythonpath, file_ending='.py'):
|
|||
"""
|
||||
|
||||
# find all possible absolute paths
|
||||
abspaths = utils.pypath_to_realpath(pythonpath,
|
||||
file_ending, settings.BASE_BATCHPROCESS_PATHS)
|
||||
abspaths = utils.pypath_to_realpath(pythonpath, file_ending, settings.BASE_BATCHPROCESS_PATHS)
|
||||
if not abspaths:
|
||||
raise IOError
|
||||
text = None
|
||||
|
|
@ -237,11 +237,11 @@ def read_batchfile(pythonpath, file_ending='.py'):
|
|||
return text
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Batch-command processor
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
class BatchCommandProcessor(object):
|
||||
"""
|
||||
|
|
@ -271,35 +271,35 @@ class BatchCommandProcessor(object):
|
|||
text = "".join(read_batchfile(pythonpath, file_ending='.ev'))
|
||||
|
||||
def replace_insert(match):
|
||||
"Map replace entries"
|
||||
"""Map replace entries"""
|
||||
return "\n#".join(self.parse_file(match.group(1)))
|
||||
|
||||
# insert commands from inserted files
|
||||
text = _RE_INSERT.sub(replace_insert, text)
|
||||
#text = re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
|
||||
# re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
|
||||
# get all commands
|
||||
commands = _RE_CMD_SPLIT.split(text)
|
||||
#commands = re.split(r"^\#.*?$", text, flags=re.MULTILINE)
|
||||
#remove eventual newline at the end of commands
|
||||
# re.split(r"^\#.*?$", text, flags=re.MULTILINE)
|
||||
# remove eventual newline at the end of commands
|
||||
commands = [c.strip('\r\n') for c in commands]
|
||||
commands = [c for c in commands if c]
|
||||
|
||||
return commands
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Batch-code processor
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def tb_filename(tb):
|
||||
"Helper to get filename from traceback"
|
||||
"""Helper to get filename from traceback"""
|
||||
return tb.tb_frame.f_code.co_filename
|
||||
|
||||
|
||||
def tb_iter(tb):
|
||||
"Traceback iterator."
|
||||
"""Traceback iterator."""
|
||||
while tb is not None:
|
||||
yield tb
|
||||
tb = tb.tb_next
|
||||
|
|
@ -341,7 +341,7 @@ class BatchCodeProcessor(object):
|
|||
text = "".join(read_batchfile(pythonpath, file_ending='.py'))
|
||||
|
||||
def replace_insert(match):
|
||||
"Run parse_file on the import before sub:ing it into this file"
|
||||
"""Run parse_file on the import before sub:ing it into this file"""
|
||||
path = match.group(1)
|
||||
return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path))
|
||||
|
||||
|
|
@ -356,7 +356,7 @@ class BatchCodeProcessor(object):
|
|||
code = text[istart:iend]
|
||||
if mtype == "#HEADER":
|
||||
headers.append(code)
|
||||
else: # either #CODE or matching from start of file
|
||||
else: # either #CODE or matching from start of file
|
||||
codes.append(code)
|
||||
|
||||
# join all headers together to one
|
||||
|
|
@ -365,7 +365,6 @@ class BatchCodeProcessor(object):
|
|||
codes = ["%s# batchcode code:\n%s" % (header, code) for code in codes]
|
||||
return codes
|
||||
|
||||
|
||||
def code_exec(self, code, extra_environ=None, debug=False):
|
||||
"""
|
||||
Execute a single code block, including imports and appending
|
||||
|
|
@ -406,7 +405,7 @@ class BatchCodeProcessor(object):
|
|||
err = ""
|
||||
for iline, line in enumerate(code.split("\n")):
|
||||
if iline == lineno:
|
||||
err += "\n{w%02i{n: %s" % (iline + 1, line)
|
||||
err += "\n|w%02i|n: %s" % (iline + 1, line)
|
||||
elif lineno - 5 < iline < lineno + 5:
|
||||
err += "\n%02i: %s" % (iline + 1, line)
|
||||
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ _GA = object.__getattribute__
|
|||
|
||||
#
|
||||
# Game Object creation
|
||||
#
|
||||
|
||||
def create_object(typeclass=None, key=None, location=None,
|
||||
home=None, permissions=None, locks=None,
|
||||
aliases=None, tags=None, destination=None, report_to=None, nohome=False):
|
||||
|
||||
def create_object(typeclass=None, key=None, location=None, home=None,
|
||||
permissions=None, locks=None, aliases=None, tags=None,
|
||||
destination=None, report_to=None, nohome=False):
|
||||
"""
|
||||
|
||||
Create a new in-game object.
|
||||
|
|
@ -110,26 +110,24 @@ def create_object(typeclass=None, key=None, location=None,
|
|||
|
||||
# create new instance
|
||||
new_object = typeclass(db_key=key, db_location=location,
|
||||
db_destination=destination, db_home=home,
|
||||
db_typeclass_path=typeclass.path)
|
||||
db_destination=destination, db_home=home,
|
||||
db_typeclass_path=typeclass.path)
|
||||
# store the call signature for the signal
|
||||
new_object._createdict = {"key":key, "location":location, "destination":destination,
|
||||
"home":home, "typeclass":typeclass.path, "permissions":permissions,
|
||||
"locks":locks, "aliases":aliases, "tags": tags,
|
||||
"report_to":report_to, "nohome":nohome}
|
||||
new_object._createdict = dict(key=key, location=location, destination=destination, home=home,
|
||||
typeclass=typeclass.path, permissions=permissions, locks=locks,
|
||||
aliases=aliases, tags=tags, report_to=report_to, nohome=nohome)
|
||||
# this will trigger the save signal which in turn calls the
|
||||
# at_first_save hook on the typeclass, where the _createdict can be
|
||||
# used.
|
||||
new_object.save()
|
||||
return new_object
|
||||
|
||||
#alias for create_object
|
||||
# alias for create_object
|
||||
object = create_object
|
||||
|
||||
|
||||
#
|
||||
# Script creation
|
||||
#
|
||||
|
||||
def create_script(typeclass=None, key=None, obj=None, player=None, locks=None,
|
||||
interval=None, start_delay=None, repeats=None,
|
||||
|
|
@ -194,19 +192,16 @@ def create_script(typeclass=None, key=None, obj=None, player=None, locks=None,
|
|||
new_script = typeclass(**kwarg)
|
||||
|
||||
# store the call signature for the signal
|
||||
new_script._createdict = {"key":key, "obj":obj, "player":player,
|
||||
"locks":locks, "interval":interval,
|
||||
"start_delay":start_delay, "repeats":repeats,
|
||||
"persistent":persistent, "autostart":autostart,
|
||||
"report_to":report_to}
|
||||
|
||||
new_script._createdict = dict(key=key, obj=obj, player=player, locks=locks, interval=interval,
|
||||
start_delay=start_delay, repeats=repeats, persistent=persistent,
|
||||
autostart=autostart, report_to=report_to)
|
||||
# this will trigger the save signal which in turn calls the
|
||||
# at_first_save hook on the typeclass, where the _createdict
|
||||
# can be used.
|
||||
new_script.save()
|
||||
return new_script
|
||||
|
||||
#alias
|
||||
# alias
|
||||
script = create_script
|
||||
|
||||
|
||||
|
|
@ -227,6 +222,7 @@ def create_help_entry(key, entrytext, category="General", locks=None, aliases=No
|
|||
entrytext (str): The body of te help entry
|
||||
category (str, optional): The help category of the entry.
|
||||
locks (str, optional): A lockstring to restrict access.
|
||||
aliases (list of str): List of alternative (likely shorter) keynames.
|
||||
|
||||
Returns:
|
||||
help (HelpEntry): A newly created help entry.
|
||||
|
|
@ -260,10 +256,8 @@ help_entry = create_help_entry
|
|||
|
||||
#
|
||||
# Comm system methods
|
||||
#
|
||||
|
||||
def create_message(senderobj, message, channels=None,
|
||||
receivers=None, locks=None, header=None):
|
||||
def create_message(senderobj, message, channels=None, receivers=None, locks=None, header=None):
|
||||
"""
|
||||
Create a new communication Msg. Msgs represent a unit of
|
||||
database-persistent communication between entites.
|
||||
|
|
@ -325,7 +319,7 @@ def create_channel(key, aliases=None, desc=None,
|
|||
key (str): This must be unique.
|
||||
|
||||
Kwargs:
|
||||
aliases (list): List of alternative (likely shorter) keynames.
|
||||
aliases (list of str): List of alternative (likely shorter) keynames.
|
||||
desc (str): A description of the channel, for use in listings.
|
||||
locks (str): Lockstring.
|
||||
keep_log (bool): Log channel throughput.
|
||||
|
|
@ -346,8 +340,7 @@ def create_channel(key, aliases=None, desc=None,
|
|||
new_channel = typeclass(db_key=key)
|
||||
|
||||
# store call signature for the signal
|
||||
new_channel._createdict = {"key":key, "aliases":aliases,
|
||||
"desc":desc, "locks":locks, "keep_log":keep_log}
|
||||
new_channel._createdict = dict(key=key, aliases=aliases, desc=desc, locks=locks, keep_log=keep_log)
|
||||
|
||||
# this will trigger the save signal which in turn calls the
|
||||
# at_first_save hook on the typeclass, where the _createdict can be
|
||||
|
|
@ -358,11 +351,11 @@ def create_channel(key, aliases=None, desc=None,
|
|||
channel = create_channel
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Player creation methods
|
||||
#
|
||||
|
||||
|
||||
def create_player(key, email, password,
|
||||
typeclass=None,
|
||||
is_superuser=False,
|
||||
|
|
@ -376,7 +369,7 @@ def create_player(key, email, password,
|
|||
key (str): The player's name. This should be unique.
|
||||
email (str): Email on valid addr@addr.domain form. This is
|
||||
technically required but if set to `None`, an email of
|
||||
`dummy@dummy.com` will be used as a placeholder.
|
||||
`dummy@example.com` will be used as a placeholder.
|
||||
password (str): Password in cleartext.
|
||||
|
||||
Kwargs:
|
||||
|
|
@ -411,7 +404,7 @@ def create_player(key, email, password,
|
|||
# correctly when each object is recovered).
|
||||
|
||||
if not email:
|
||||
email = "dummy@dummy.com"
|
||||
email = "dummy@example.com"
|
||||
if _PlayerDB.objects.filter(username__iexact=key):
|
||||
raise ValueError("A Player with the name '%s' already exists." % key)
|
||||
|
||||
|
|
@ -426,8 +419,7 @@ def create_player(key, email, password,
|
|||
is_staff=is_superuser, is_superuser=is_superuser,
|
||||
last_login=now, date_joined=now)
|
||||
new_player.set_password(password)
|
||||
new_player._createdict = {"locks":locks, "permissions":permissions,
|
||||
"report_to":report_to}
|
||||
new_player._createdict = dict(locks=locks, permissions=permissions, report_to=report_to)
|
||||
# saving will trigger the signal that calls the
|
||||
# at_first_save hook on the typeclass, where the _createdict
|
||||
# can be used.
|
||||
|
|
|
|||
|
|
@ -33,10 +33,11 @@ from evennia.utils.utils import to_str, uses_database
|
|||
from evennia.utils import logger
|
||||
|
||||
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle",
|
||||
"dbserialize", "dbunserialize")
|
||||
"dbserialize", "dbunserialize")
|
||||
|
||||
PICKLE_PROTOCOL = 2
|
||||
|
||||
|
||||
def _get_mysql_db_version():
|
||||
"""
|
||||
This is a helper method for specifically getting the version
|
||||
|
|
@ -93,7 +94,7 @@ def _TO_DATESTRING(obj):
|
|||
|
||||
|
||||
def _init_globals():
|
||||
"Lazy importing to avoid circular import issues"
|
||||
"""Lazy importing to avoid circular import issues"""
|
||||
global _FROM_MODEL_MAP, _TO_MODEL_MAP, _SESSION_HANDLER
|
||||
if not _FROM_MODEL_MAP:
|
||||
_FROM_MODEL_MAP = defaultdict(str)
|
||||
|
|
@ -110,7 +111,7 @@ def _init_globals():
|
|||
|
||||
|
||||
def _save(method):
|
||||
"method decorator that saves data to Attribute"
|
||||
"""method decorator that saves data to Attribute"""
|
||||
def save_wrapper(self, *args, **kwargs):
|
||||
self.__doc__ = method.__doc__
|
||||
ret = method(self, *args, **kwargs)
|
||||
|
|
@ -127,17 +128,17 @@ class _SaverMutable(object):
|
|||
will not save the updated value to the database.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
"store all properties for tracking the tree"
|
||||
"""store all properties for tracking the tree"""
|
||||
self._parent = kwargs.pop("_parent", None)
|
||||
self._db_obj = kwargs.pop("_db_obj", None)
|
||||
self._data = None
|
||||
|
||||
def __nonzero__(self):
|
||||
"Make sure to evaluate as False if empty"
|
||||
"""Make sure to evaluate as False if empty"""
|
||||
return bool(self._data)
|
||||
|
||||
def _save_tree(self):
|
||||
"recursively traverse back up the tree, save when we reach the root"
|
||||
"""recursively traverse back up the tree, save when we reach the root"""
|
||||
if self._parent:
|
||||
self._parent._save_tree()
|
||||
elif self._db_obj:
|
||||
|
|
@ -146,9 +147,9 @@ class _SaverMutable(object):
|
|||
logger.log_err("_SaverMutable %s has no root Attribute to save to." % self)
|
||||
|
||||
def _convert_mutables(self, data):
|
||||
"converts mutables to Saver* variants and assigns ._parent property"
|
||||
"""converts mutables to Saver* variants and assigns ._parent property"""
|
||||
def process_tree(item, parent):
|
||||
"recursively populate the tree, storing parents"
|
||||
"""recursively populate the tree, storing parents"""
|
||||
dtype = type(item)
|
||||
if dtype in (basestring, int, float, bool, tuple):
|
||||
return item
|
||||
|
|
@ -218,7 +219,6 @@ class _SaverList(_SaverMutable, MutableSequence):
|
|||
return self._data.index(value, *args)
|
||||
|
||||
|
||||
|
||||
class _SaverDict(_SaverMutable, MutableMapping):
|
||||
"""
|
||||
A dict that stores changes to an Attribute when updated
|
||||
|
|
@ -290,8 +290,10 @@ class _SaverDeque(_SaverMutable):
|
|||
# maxlen property
|
||||
def _getmaxlen(self):
|
||||
return self._data.maxlen
|
||||
|
||||
def _setmaxlen(self, value):
|
||||
self._data.maxlen = value
|
||||
|
||||
def _delmaxlen(self):
|
||||
del self._data.maxlen
|
||||
maxlen = property(_getmaxlen, _setmaxlen, _delmaxlen)
|
||||
|
|
@ -314,7 +316,7 @@ class _SaverDeque(_SaverMutable):
|
|||
|
||||
#
|
||||
# serialization helpers
|
||||
#
|
||||
|
||||
|
||||
def pack_dbobj(item):
|
||||
"""
|
||||
|
|
@ -335,7 +337,7 @@ def pack_dbobj(item):
|
|||
# build the internal representation as a tuple
|
||||
# ("__packed_dbobj__", key, creation_time, id)
|
||||
return natural_key and ('__packed_dbobj__', natural_key,
|
||||
_TO_DATESTRING(obj), _GA(obj, "id")) or item
|
||||
_TO_DATESTRING(obj), _GA(obj, "id")) or item
|
||||
|
||||
|
||||
def unpack_dbobj(item):
|
||||
|
|
@ -391,10 +393,11 @@ def pack_session(item):
|
|||
# to be accepted as actually being a session (sessids gets
|
||||
# reused all the time).
|
||||
return item.conn_time and item.sessid and ('__packed_session__',
|
||||
_GA(item, "sessid"),
|
||||
_GA(item, "conn_time"))
|
||||
_GA(item, "sessid"),
|
||||
_GA(item, "conn_time"))
|
||||
return None
|
||||
|
||||
|
||||
def unpack_session(item):
|
||||
"""
|
||||
Check and convert internal representations back to Sessions.
|
||||
|
|
@ -419,7 +422,7 @@ def unpack_session(item):
|
|||
|
||||
#
|
||||
# Access methods
|
||||
#
|
||||
|
||||
|
||||
def to_pickle(data):
|
||||
"""
|
||||
|
|
@ -437,7 +440,7 @@ def to_pickle(data):
|
|||
|
||||
"""
|
||||
def process_item(item):
|
||||
"Recursive processor and identification of data"
|
||||
"""Recursive processor and identification of data"""
|
||||
dtype = type(item)
|
||||
if dtype in (basestring, int, float, bool):
|
||||
return item
|
||||
|
|
@ -466,7 +469,7 @@ def to_pickle(data):
|
|||
return process_item(data)
|
||||
|
||||
|
||||
#@transaction.autocommit
|
||||
# @transaction.autocommit
|
||||
def from_pickle(data, db_obj=None):
|
||||
"""
|
||||
This should be fed a just de-pickled data object. It will be converted back
|
||||
|
|
@ -489,7 +492,7 @@ def from_pickle(data, db_obj=None):
|
|||
|
||||
"""
|
||||
def process_item(item):
|
||||
"Recursive processor and identification of data"
|
||||
"""Recursive processor and identification of data"""
|
||||
dtype = type(item)
|
||||
if dtype in (basestring, int, float, bool):
|
||||
return item
|
||||
|
|
@ -518,7 +521,7 @@ def from_pickle(data, db_obj=None):
|
|||
return item
|
||||
|
||||
def process_tree(item, parent):
|
||||
"Recursive processor, building a parent-tree from iterable data"
|
||||
"""Recursive processor, building a parent-tree from iterable data"""
|
||||
dtype = type(item)
|
||||
if dtype in (basestring, int, float, bool):
|
||||
return item
|
||||
|
|
@ -534,7 +537,7 @@ def from_pickle(data, db_obj=None):
|
|||
elif dtype == dict:
|
||||
dat = _SaverDict(_parent=parent)
|
||||
dat._data.update((process_item(key), process_tree(val, dat))
|
||||
for key, val in item.items())
|
||||
for key, val in item.items())
|
||||
return dat
|
||||
elif dtype == set:
|
||||
dat = _SaverSet(_parent=parent)
|
||||
|
|
@ -543,7 +546,7 @@ def from_pickle(data, db_obj=None):
|
|||
elif dtype == OrderedDict:
|
||||
dat = _SaverOrderedDict(_parent=parent)
|
||||
dat._data.update((process_item(key), process_tree(val, dat))
|
||||
for key, val in item.items())
|
||||
for key, val in item.items())
|
||||
return dat
|
||||
elif dtype == deque:
|
||||
dat = _SaverDeque(_parent=parent)
|
||||
|
|
@ -571,7 +574,7 @@ def from_pickle(data, db_obj=None):
|
|||
elif dtype == dict:
|
||||
dat = _SaverDict(_db_obj=db_obj)
|
||||
dat._data.update((process_item(key), process_tree(val, dat))
|
||||
for key, val in data.items())
|
||||
for key, val in data.items())
|
||||
return dat
|
||||
elif dtype == set:
|
||||
dat = _SaverSet(_db_obj=db_obj)
|
||||
|
|
@ -580,7 +583,7 @@ def from_pickle(data, db_obj=None):
|
|||
elif dtype == OrderedDict:
|
||||
dat = _SaverOrderedDict(_db_obj=db_obj)
|
||||
dat._data.update((process_item(key), process_tree(val, dat))
|
||||
for key, val in data.items())
|
||||
for key, val in data.items())
|
||||
return dat
|
||||
elif dtype == deque:
|
||||
dat = _SaverDeque(_db_obj=db_obj)
|
||||
|
|
@ -590,20 +593,20 @@ def from_pickle(data, db_obj=None):
|
|||
|
||||
|
||||
def do_pickle(data):
|
||||
"Perform pickle to string"
|
||||
"""Perform pickle to string"""
|
||||
return to_str(dumps(data, protocol=PICKLE_PROTOCOL))
|
||||
|
||||
|
||||
def do_unpickle(data):
|
||||
"Retrieve pickle from pickled string"
|
||||
"""Retrieve pickle from pickled string"""
|
||||
return loads(to_str(data))
|
||||
|
||||
|
||||
def dbserialize(data):
|
||||
"Serialize to pickled form in one step"
|
||||
"""Serialize to pickled form in one step"""
|
||||
return do_pickle(to_pickle(data))
|
||||
|
||||
|
||||
def dbunserialize(data, db_obj=None):
|
||||
"Un-serialize in one step. See from_pickle for help db_obj."
|
||||
"""Un-serialize in one step. See from_pickle for help db_obj."""
|
||||
return from_pickle(do_unpickle(data), db_obj=db_obj)
|
||||
|
|
|
|||
|
|
@ -60,14 +60,13 @@ _RE_GROUP = re.compile(r"\".*?\"|\'.*?\'|\S*")
|
|||
# use NAWS in the future?
|
||||
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# texts
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
_HELP_TEXT = \
|
||||
"""
|
||||
_HELP_TEXT = """
|
||||
<txt> - any non-command is appended to the end of the buffer.
|
||||
: <l> - view buffer or only line(s) <l>
|
||||
:: <l> - raw-view buffer or only line(s) <l>
|
||||
|
|
@ -105,31 +104,27 @@ _HELP_TEXT = \
|
|||
:echo - turn echoing of the input on/off (helpful for some clients)
|
||||
"""
|
||||
|
||||
_HELP_LEGEND = \
|
||||
"""
|
||||
_HELP_LEGEND = """
|
||||
Legend:
|
||||
<l> - line number, like '5' or range, like '3:7'.
|
||||
<w> - a single word, or multiple words with quotes around them.
|
||||
<txt> - longer string, usually not needing quotes.
|
||||
"""
|
||||
|
||||
_HELP_CODE = \
|
||||
"""
|
||||
_HELP_CODE = """
|
||||
:! - Execute code buffer without saving
|
||||
:< - Decrease the level of automatic indentation for the next lines
|
||||
:> - Increase the level of automatic indentation for the next lines
|
||||
:= - Switch automatic indentation on/off
|
||||
""".lstrip("\n")
|
||||
|
||||
_ERROR_LOADFUNC = \
|
||||
"""
|
||||
_ERROR_LOADFUNC = """
|
||||
{error}
|
||||
|
||||
|rBuffer load function error. Could not load initial data.|n
|
||||
"""
|
||||
|
||||
_ERROR_SAVEFUNC = \
|
||||
"""
|
||||
_ERROR_SAVEFUNC = """
|
||||
{error}
|
||||
|
||||
|rSave function returned an error. Buffer not saved.|n
|
||||
|
|
@ -140,15 +135,13 @@ _ERROR_NO_SAVEFUNC = "|rNo save function defined. Buffer cannot be saved.|n"
|
|||
_MSG_SAVE_NO_CHANGE = "No changes need saving"
|
||||
_DEFAULT_NO_QUITFUNC = "Exited editor."
|
||||
|
||||
_ERROR_QUITFUNC = \
|
||||
"""
|
||||
_ERROR_QUITFUNC = """
|
||||
{error}
|
||||
|
||||
|rQuit function gave an error. Skipping.|n
|
||||
"""
|
||||
|
||||
_ERROR_PERSISTENT_SAVING = \
|
||||
"""
|
||||
_ERROR_PERSISTENT_SAVING = """
|
||||
{error}
|
||||
|
||||
|rThe editor state could not be saved for persistent mode. Switching
|
||||
|
|
@ -157,9 +150,9 @@ an eventual server reload - so save often!)|n
|
|||
"""
|
||||
|
||||
_TRACE_PERSISTENT_SAVING = \
|
||||
"EvEditor persistent-mode error. Commonly, this is because one or " \
|
||||
"more of the EvEditor callbacks could not be pickled, for example " \
|
||||
"because it's a class method or is defined inside another function."
|
||||
"EvEditor persistent-mode error. Commonly, this is because one or " \
|
||||
"more of the EvEditor callbacks could not be pickled, for example " \
|
||||
"because it's a class method or is defined inside another function."
|
||||
|
||||
|
||||
_MSG_NO_UNDO = "Nothing to undo."
|
||||
|
|
@ -167,11 +160,12 @@ _MSG_NO_REDO = "Nothing to redo."
|
|||
_MSG_UNDO = "Undid one step."
|
||||
_MSG_REDO = "Redid one step."
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Handle yes/no quit question
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class CmdSaveYesNo(Command):
|
||||
"""
|
||||
|
|
@ -185,7 +179,7 @@ class CmdSaveYesNo(Command):
|
|||
help_cateogory = "LineEditor"
|
||||
|
||||
def func(self):
|
||||
"Implement the yes/no choice."
|
||||
"""Implement the yes/no choice."""
|
||||
# this is only called from inside the lineeditor
|
||||
# so caller.ndb._lineditor must be set.
|
||||
|
||||
|
|
@ -200,21 +194,21 @@ class CmdSaveYesNo(Command):
|
|||
|
||||
|
||||
class SaveYesNoCmdSet(CmdSet):
|
||||
"Stores the yesno question"
|
||||
"""Stores the yesno question"""
|
||||
key = "quitsave_yesno"
|
||||
priority = 1
|
||||
mergetype = "Replace"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"at cmdset creation"
|
||||
"""at cmdset creation"""
|
||||
self.add(CmdSaveYesNo())
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Editor commands
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
class CmdEditorBase(Command):
|
||||
"""
|
||||
|
|
@ -239,7 +233,6 @@ class CmdEditorBase(Command):
|
|||
txt - extra text (string), could be encased in quotes.
|
||||
"""
|
||||
|
||||
linebuffer = []
|
||||
editor = self.caller.ndb._eveditor
|
||||
if not editor:
|
||||
# this will completely replace the editor
|
||||
|
|
@ -297,11 +290,7 @@ class CmdEditorBase(Command):
|
|||
arglist = arglist[1:]
|
||||
|
||||
# nicer output formatting of the line range.
|
||||
lstr = ""
|
||||
if not linerange or lstart + 1 == lend:
|
||||
lstr = "line %i" % (lstart + 1)
|
||||
else:
|
||||
lstr = "lines %i-%i" % (lstart + 1, lend)
|
||||
lstr = "line %i" % (lstart + 1) if not linerange or lstart + 1 == lend else "lines %i-%i" % (lstart + 1, lend)
|
||||
|
||||
# arg1 and arg2 is whatever arguments. Line numbers or -ranges are
|
||||
# never included here.
|
||||
|
|
@ -369,25 +358,14 @@ class CmdLineInput(CmdEditorBase):
|
|||
"""
|
||||
caller = self.caller
|
||||
editor = caller.ndb._eveditor
|
||||
|
||||
buf = editor.get_buffer()
|
||||
|
||||
# add a line of text to buffer
|
||||
line = self.raw_string.strip("\r\n")
|
||||
if not editor._codefunc:
|
||||
if not buf:
|
||||
buf = line
|
||||
else:
|
||||
buf = buf + "\n%s" % line
|
||||
else:
|
||||
if editor._codefunc and editor._indent >= 0:
|
||||
# if automatic indentation is active, add spaces
|
||||
if editor._indent >= 0:
|
||||
line = editor.deduce_indent(line, buf)
|
||||
|
||||
if not buf:
|
||||
buf = line
|
||||
else:
|
||||
buf = buf + "\n%s" % line
|
||||
line = editor.deduce_indent(line, buf)
|
||||
buf = line if not buf else buf + "\n%s" % line
|
||||
self.editor.update_buffer(buf)
|
||||
if self.editor._echo_mode:
|
||||
# need to do it here or we will be off one line
|
||||
|
|
@ -398,11 +376,10 @@ class CmdLineInput(CmdEditorBase):
|
|||
if indent < 0:
|
||||
indent = "off"
|
||||
|
||||
self.caller.msg("{b%02i|{n ({g%s{n) %s" % (
|
||||
self.caller.msg("|b%02i|||n (|g%s|n) %s" % (
|
||||
cline, indent, line))
|
||||
else:
|
||||
self.caller.msg("{b%02i|{n %s" % (cline, self.args))
|
||||
|
||||
self.caller.msg("|b%02i|||n %s" % (cline, self.args))
|
||||
|
||||
|
||||
class CmdEditorGroup(CmdEditorBase):
|
||||
|
|
@ -410,7 +387,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
Commands for the editor
|
||||
"""
|
||||
key = ":editor_command_group"
|
||||
aliases = [":","::", ":::", ":h", ":w", ":wq", ":q", ":q!", ":u", ":uu", ":UU",
|
||||
aliases = [":", "::", ":::", ":h", ":w", ":wq", ":q", ":q!", ":u", ":uu", ":UU",
|
||||
":dd", ":dw", ":DD", ":y", ":x", ":p", ":i", ":j",
|
||||
":r", ":I", ":A", ":s", ":S", ":f", ":fi", ":fd", ":echo",
|
||||
":!", ":<", ":>", ":="]
|
||||
|
|
@ -443,9 +420,9 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
buf = linebuffer[lstart:lend]
|
||||
editor.display_buffer(buf=buf,
|
||||
offset=lstart,
|
||||
linenums=False, options={"raw":True})
|
||||
linenums=False, options={"raw": True})
|
||||
else:
|
||||
editor.display_buffer(linenums=False, options={"raw":True})
|
||||
editor.display_buffer(linenums=False, options={"raw": True})
|
||||
elif cmd == ":::":
|
||||
# Insert single colon alone on a line
|
||||
editor.update_buffer(editor.buffer + "\n:")
|
||||
|
|
@ -485,7 +462,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
# :dd <l> - delete line <l>
|
||||
buf = linebuffer[:lstart] + linebuffer[lend:]
|
||||
editor.update_buffer(buf)
|
||||
caller.msg("Deleted %s." % (self.lstr))
|
||||
caller.msg("Deleted %s." % self.lstr)
|
||||
elif cmd == ":dw":
|
||||
# :dw <w> - delete word in entire buffer
|
||||
# :dw <l> <w> delete word only on line(s) <l>
|
||||
|
|
@ -556,7 +533,8 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.raw_string and not editor._codefunc:
|
||||
caller.msg("You need to enter text to insert.")
|
||||
else:
|
||||
buf = linebuffer[:lstart] + ["%s%s" % (self.args, line) for line in linebuffer[lstart:lend]] + linebuffer[lend:]
|
||||
buf = linebuffer[:lstart] + ["%s%s" % (self.args, line)
|
||||
for line in linebuffer[lstart:lend]] + linebuffer[lend:]
|
||||
editor.update_buffer(buf)
|
||||
caller.msg("Inserted text at beginning of %s." % self.lstr)
|
||||
elif cmd == ":A":
|
||||
|
|
@ -564,7 +542,8 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.args:
|
||||
caller.msg("You need to enter text to append.")
|
||||
else:
|
||||
buf = linebuffer[:lstart] + ["%s%s" % (line, self.args) for line in linebuffer[lstart:lend]] + linebuffer[lend:]
|
||||
buf = linebuffer[:lstart] + ["%s%s" % (line, self.args)
|
||||
for line in linebuffer[lstart:lend]] + linebuffer[lend:]
|
||||
editor.update_buffer(buf)
|
||||
caller.msg("Appended text to end of %s." % self.lstr)
|
||||
elif cmd == ":s":
|
||||
|
|
@ -576,7 +555,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.linerange:
|
||||
lstart = 0
|
||||
lend = self.cline + 1
|
||||
caller.msg("Search-replaced %s -> %s for lines %i-%i." % (self.arg1, self.arg2, lstart + 1 , lend))
|
||||
caller.msg("Search-replaced %s -> %s for lines %i-%i." % (self.arg1, self.arg2, lstart + 1, lend))
|
||||
else:
|
||||
caller.msg("Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr))
|
||||
sarea = "\n".join(linebuffer[lstart:lend])
|
||||
|
|
@ -585,7 +564,8 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
regarg = self.arg1.strip("\'").strip('\"')
|
||||
if " " in regarg:
|
||||
regarg = regarg.replace(" ", " +")
|
||||
sarea = re.sub(regex % (regarg, regarg, regarg, regarg, regarg), self.arg2.strip("\'").strip('\"'), sarea, re.MULTILINE)
|
||||
sarea = re.sub(regex % (regarg, regarg, regarg, regarg, regarg), self.arg2.strip("\'").strip('\"'),
|
||||
sarea, re.MULTILINE)
|
||||
buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:]
|
||||
editor.update_buffer(buf)
|
||||
elif cmd == ":f":
|
||||
|
|
@ -594,7 +574,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.linerange:
|
||||
lstart = 0
|
||||
lend = self.cline + 1
|
||||
caller.msg("Flood filled lines %i-%i." % (lstart + 1 , lend))
|
||||
caller.msg("Flood filled lines %i-%i." % (lstart + 1, lend))
|
||||
else:
|
||||
caller.msg("Flood filled %s." % self.lstr)
|
||||
fbuf = "\n".join(linebuffer[lstart:lend])
|
||||
|
|
@ -605,7 +585,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
# :f <l> <w> justify buffer of <l> with <w> as align (one of
|
||||
# f(ull), c(enter), r(ight) or l(left). Default is full.
|
||||
align_map = {"full": "f", "f": "f", "center": "c", "c": "c",
|
||||
"right": "r", "r": "r", "left": "l", "l": "l"}
|
||||
"right": "r", "r": "r", "left": "l", "l": "l"}
|
||||
align_name = {"f": "Full", "c": "Center", "l": "Left", "r": "Right"}
|
||||
width = _DEFAULT_WIDTH
|
||||
if self.arg1 and self.arg1.lower() not in align_map:
|
||||
|
|
@ -628,7 +608,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.linerange:
|
||||
lstart = 0
|
||||
lend = self.cline + 1
|
||||
caller.msg("Indented lines %i-%i." % (lstart + 1 , lend))
|
||||
caller.msg("Indented lines %i-%i." % (lstart + 1, lend))
|
||||
else:
|
||||
caller.msg("Indented %s." % self.lstr)
|
||||
fbuf = [indent + line for line in linebuffer[lstart:lend]]
|
||||
|
|
@ -639,7 +619,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.linerange:
|
||||
lstart = 0
|
||||
lend = self.cline + 1
|
||||
caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1 , lend))
|
||||
caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1, lend))
|
||||
else:
|
||||
caller.msg("Removed left margin (dedented) %s." % self.lstr)
|
||||
fbuf = "\n".join(linebuffer[lstart:lend])
|
||||
|
|
@ -693,18 +673,20 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
|
||||
|
||||
class EvEditorCmdSet(CmdSet):
|
||||
"CmdSet for the editor commands"
|
||||
"""CmdSet for the editor commands"""
|
||||
key = "editorcmdset"
|
||||
mergetype = "Replace"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdLineInput())
|
||||
self.add(CmdEditorGroup())
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Main Editor object
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class EvEditor(object):
|
||||
"""
|
||||
|
|
@ -790,12 +772,10 @@ class EvEditor(object):
|
|||
if persistent:
|
||||
# save in tuple {kwargs, other options}
|
||||
try:
|
||||
caller.attributes.add("_eveditor_saved",(
|
||||
{"loadfunc":loadfunc, "savefunc": savefunc,
|
||||
"quitfunc": quitfunc, "codefunc": codefunc,
|
||||
"key": key, "persistent": persistent},
|
||||
{"_pristine_buffer": self._pristine_buffer,
|
||||
"_sep": self._sep}))
|
||||
caller.attributes.add("_eveditor_saved", (
|
||||
dict(loadfunc=loadfunc, savefunc=savefunc, quitfunc=quitfunc,
|
||||
codefunc=codefunc, key=key, persistent=persistent),
|
||||
dict(_pristine_buffer=self._pristine_buffer, _sep=self._sep)))
|
||||
caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer))
|
||||
caller.attributes.add("_eveditor_unsaved", False)
|
||||
caller.attributes.add("_eveditor_indent", 0)
|
||||
|
|
@ -923,7 +903,7 @@ class EvEditor(object):
|
|||
self._undo_buffer = self._undo_buffer[:self._undo_pos + 1] + [self._buffer]
|
||||
self._undo_pos = len(self._undo_buffer) - 1
|
||||
|
||||
def display_buffer(self, buf=None, offset=0, linenums=True, options={"raw":False}):
|
||||
def display_buffer(self, buf=None, offset=0, linenums=True, options={"raw": False}):
|
||||
"""
|
||||
This displays the line editor buffer, or selected parts of it.
|
||||
|
||||
|
|
@ -933,7 +913,7 @@ class EvEditor(object):
|
|||
`offset` should define the actual starting line number, to
|
||||
get the linenum display right.
|
||||
linenums (bool, optional): Show line numbers in buffer.
|
||||
raw (bool, optional): Tell protocol to not parse
|
||||
options: raw (bool, optional): Tell protocol to not parse
|
||||
formatting information.
|
||||
|
||||
"""
|
||||
|
|
@ -949,10 +929,10 @@ class EvEditor(object):
|
|||
|
||||
sep = self._sep
|
||||
header = "|n" + sep * 10 + "Line Editor [%s]" % self._key + sep * (_DEFAULT_WIDTH-20-len(self._key))
|
||||
footer = "|n" + sep * 10 + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) \
|
||||
+ sep * 12 + "(:h for help)" + sep * 28
|
||||
footer = "|n" + sep * 10 +\
|
||||
"[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) + sep * 12 + "(:h for help)" + sep * 28
|
||||
if linenums:
|
||||
main = "\n".join("{b%02i|{n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines))
|
||||
main = "\n".join("|b%02i|||n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines))
|
||||
else:
|
||||
main = "\n".join(lines)
|
||||
string = "%s\n%s\n%s" % (header, main, footer)
|
||||
|
|
@ -1018,6 +998,7 @@ class EvEditor(object):
|
|||
self._indent += 1
|
||||
if self._persistent:
|
||||
self._caller.attributes.add("_eveditor_indent", self._indent)
|
||||
|
||||
def swap_autoindent(self):
|
||||
"""Swap automatic indentation on or off."""
|
||||
if self._codefunc:
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ Example usage:
|
|||
|
||||
Where `caller` is the Object to use the menu on - it will get a new
|
||||
cmdset while using the Menu. The menu_module_path is the python path
|
||||
to a python module containing function defintions. By adjusting the
|
||||
to a python module containing function definitions. By adjusting the
|
||||
keyword options of the Menu() initialization call you can start the
|
||||
menu at different places in the menu definition file, adjust if the
|
||||
menu command should overload the normal commands or not, etc.
|
||||
|
||||
The `perstent` keyword will make the menu survive a server reboot.
|
||||
The `persistent` keyword will make the menu survive a server reboot.
|
||||
It is `False` by default. Note that if using persistent mode, every
|
||||
node and callback in the menu must be possible to be *pickled*, this
|
||||
excludes e.g. callables that are class methods or functions defined
|
||||
|
|
@ -31,7 +31,7 @@ dynamically or as part of another function. In non-persistent mode
|
|||
no such restrictions exist.
|
||||
|
||||
The menu is defined in a module (this can be the same module as the
|
||||
command definition too) with function defintions:
|
||||
command definition too) with function definitions:
|
||||
|
||||
```python
|
||||
|
||||
|
|
@ -181,8 +181,7 @@ _HELP_NO_OPTIONS = _("Commands: help, quit")
|
|||
_HELP_NO_OPTIONS_NO_QUIT = _("Commands: help")
|
||||
_HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.")
|
||||
|
||||
_ERROR_PERSISTENT_SAVING = \
|
||||
"""
|
||||
_ERROR_PERSISTENT_SAVING = """
|
||||
{error}
|
||||
|
||||
|rThe menu state could not be saved for persistent mode. Switching
|
||||
|
|
@ -190,10 +189,9 @@ to non-persistent mode (which means the menu session won't survive
|
|||
an eventual server reload).|n
|
||||
"""
|
||||
|
||||
_TRACE_PERSISTENT_SAVING = \
|
||||
"EvMenu persistent-mode error. Commonly, this is because one or " \
|
||||
"more of the EvEditor callbacks could not be pickled, for example " \
|
||||
"because it's a class method or is defined inside another function."
|
||||
_TRACE_PERSISTENT_SAVING = "EvMenu persistent-mode error. Commonly, this is because one or " \
|
||||
"more of the EvEditor callbacks could not be pickled, for example " \
|
||||
"because it's a class method or is defined inside another function."
|
||||
|
||||
|
||||
class EvMenuError(RuntimeError):
|
||||
|
|
@ -203,11 +201,12 @@ class EvMenuError(RuntimeError):
|
|||
"""
|
||||
pass
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Menu command and command set
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class CmdEvMenuNode(Command):
|
||||
"""
|
||||
|
|
@ -227,7 +226,11 @@ class CmdEvMenuNode(Command):
|
|||
# this will re-start a completely new evmenu call.
|
||||
saved_options = caller.attributes.get("_menutree_saved")
|
||||
if saved_options:
|
||||
startnode, startnode_input = caller.attributes.get("_menutree_saved_startnode")
|
||||
startnode_tuple = caller.attributes.get("_menutree_saved_startnode")
|
||||
try:
|
||||
startnode, startnode_input = startnode_tuple
|
||||
except ValueError: # old form of startnode store
|
||||
startnode, startnode_input = startnode_tuple, ""
|
||||
if startnode:
|
||||
saved_options[2]["startnode"] = startnode
|
||||
saved_options[2]["startnode_input"] = startnode_input
|
||||
|
|
@ -254,7 +257,7 @@ class CmdEvMenuNode(Command):
|
|||
menu = caller.ndb._menutree
|
||||
if not menu:
|
||||
# can't restore from a session
|
||||
err = "Menu object not found as %s.ndb._menutree!" % (orig_caller)
|
||||
err = "Menu object not found as %s.ndb._menutree!" % orig_caller
|
||||
orig_caller.msg(err) # don't give the session as a kwarg here, direct to original
|
||||
raise EvMenuError(err)
|
||||
# we must do this after the caller with the menui has been correctly identified since it
|
||||
|
|
@ -287,7 +290,8 @@ class EvMenuCmdSet(CmdSet):
|
|||
#
|
||||
# Menu main class
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class EvMenu(object):
|
||||
"""
|
||||
|
|
@ -436,7 +440,17 @@ class EvMenu(object):
|
|||
"persistent": persistent}
|
||||
calldict.update(kwargs)
|
||||
try:
|
||||
caller.attributes.add("_menutree_saved", ( self.__class__, (menudata, ), calldict ))
|
||||
caller.attributes.add("_menutree_saved",
|
||||
((menudata, ),
|
||||
{"startnode": startnode,
|
||||
"cmdset_mergetype": cmdset_mergetype,
|
||||
"cmdset_priority": cmdset_priority,
|
||||
"auto_quit": auto_quit, "auto_look": auto_look, "auto_help": auto_help,
|
||||
"cmd_on_exit": cmd_on_exit,
|
||||
"nodetext_formatter": nodetext_formatter,
|
||||
"options_formatter": options_formatter,
|
||||
"node_formatter": node_formatter, "input_parser": input_parser,
|
||||
"persistent": persistent, }))
|
||||
caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input))
|
||||
except Exception as err:
|
||||
caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session)
|
||||
|
|
@ -505,7 +519,6 @@ class EvMenu(object):
|
|||
# format the entire node
|
||||
return self.node_formatter(nodetext, optionstext)
|
||||
|
||||
|
||||
def _execute_node(self, nodename, raw_string):
|
||||
"""
|
||||
Execute a node.
|
||||
|
|
@ -542,15 +555,12 @@ class EvMenu(object):
|
|||
raise
|
||||
return nodetext, options
|
||||
|
||||
|
||||
def display_nodetext(self):
|
||||
self.caller.msg(self.nodetext, session=self._session)
|
||||
|
||||
|
||||
def display_helptext(self):
|
||||
self.caller.msg(self.helptext, session=self._session)
|
||||
|
||||
|
||||
def callback_goto(self, callback, goto, raw_string):
|
||||
"""
|
||||
Call callback and goto in sequence.
|
||||
|
|
@ -860,25 +870,29 @@ class CmdGetInput(Command):
|
|||
aliases = _CMD_NOINPUT
|
||||
|
||||
def func(self):
|
||||
"This is called when user enters anything."
|
||||
"""This is called when user enters anything."""
|
||||
caller = self.caller
|
||||
callback = caller.ndb._getinput._callback
|
||||
if not callback:
|
||||
# this can be happen if called from a player-command when IC
|
||||
caller = self.player
|
||||
callback = caller.ndb._getinput._callback
|
||||
if not callback:
|
||||
raise RuntimeError("No input callback found.")
|
||||
try:
|
||||
getinput = caller.ndb._getinput
|
||||
if not getinput and hasattr(caller, "player"):
|
||||
getinput = caller.player.ndb._getinput
|
||||
caller = caller.player
|
||||
callback = getinput._callback
|
||||
|
||||
caller.ndb._getinput._session = self.session
|
||||
prompt = caller.ndb._getinput._prompt
|
||||
result = self.raw_string.strip() # we strip the ending line break caused by sending
|
||||
caller.ndb._getinput._session = self.session
|
||||
prompt = caller.ndb._getinput._prompt
|
||||
result = self.raw_string.strip() # we strip the ending line break caused by sending
|
||||
|
||||
ok = not callback(caller, prompt, result)
|
||||
if ok:
|
||||
# only clear the state if the callback does not return
|
||||
# anything
|
||||
del caller.ndb._getinput
|
||||
ok = not callback(caller, prompt, result)
|
||||
if ok:
|
||||
# only clear the state if the callback does not return
|
||||
# anything
|
||||
del caller.ndb._getinput
|
||||
caller.cmdset.remove(InputCmdSet)
|
||||
except Exception:
|
||||
# make sure to clean up cmdset if something goes wrong
|
||||
caller.msg("|rError in get_input. Choice not confirmed (report to admin)|n")
|
||||
logger.log_trace("Error in get_input")
|
||||
caller.cmdset.remove(InputCmdSet)
|
||||
|
||||
|
||||
|
|
@ -894,12 +908,12 @@ class InputCmdSet(CmdSet):
|
|||
no_channels = False
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"called once at creation"
|
||||
"""called once at creation"""
|
||||
self.add(CmdGetInput())
|
||||
|
||||
|
||||
class _Prompt(object):
|
||||
"Dummy holder"
|
||||
"""Dummy holder"""
|
||||
pass
|
||||
|
||||
|
||||
|
|
@ -958,11 +972,11 @@ def get_input(caller, prompt, callback, session=None):
|
|||
caller.msg(prompt, session=session)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# test menu strucure and testing command
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
# -------------------------------------------------------------
|
||||
|
||||
def test_start_node(caller):
|
||||
menu = caller.ndb._menutree
|
||||
|
|
@ -978,17 +992,17 @@ def test_start_node(caller):
|
|||
The menu was initialized with two variables: %s and %s.
|
||||
""" % (menu.testval, menu.testval2)
|
||||
|
||||
options = ({"key": ("{yS{net", "s"),
|
||||
options = ({"key": ("|yS|net", "s"),
|
||||
"desc": "Set an attribute on yourself.",
|
||||
"exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"),
|
||||
"goto": "test_set_node"},
|
||||
{"key": ("{yL{nook", "l"),
|
||||
{"key": ("|yL|nook", "l"),
|
||||
"desc": "Look and see a custom message.",
|
||||
"goto": "test_look_node"},
|
||||
{"key": ("{yV{niew", "v"),
|
||||
{"key": ("|yV|niew", "v"),
|
||||
"desc": "View your own name",
|
||||
"goto": "test_view_node"},
|
||||
{"key": ("{yQ{nuit", "quit", "q", "Q"),
|
||||
{"key": ("|yQ|nuit", "quit", "q", "Q"),
|
||||
"desc": "Quit this menu example.",
|
||||
"goto": "test_end_node"},
|
||||
{"key": "_default",
|
||||
|
|
@ -998,16 +1012,17 @@ def test_start_node(caller):
|
|||
|
||||
def test_look_node(caller):
|
||||
text = ""
|
||||
options = {"key": ("{yL{nook", "l"),
|
||||
options = {"key": ("|yL|nook", "l"),
|
||||
"desc": "Go back to the previous menu.",
|
||||
"goto": "test_start_node"}
|
||||
return text, options
|
||||
|
||||
|
||||
def test_set_node(caller):
|
||||
text = ("""
|
||||
The attribute 'menuattrtest' was set to
|
||||
|
||||
{w%s{n
|
||||
|w%s|n
|
||||
|
||||
(check it with examine after quitting the menu).
|
||||
|
||||
|
|
@ -1015,9 +1030,8 @@ def test_set_node(caller):
|
|||
string "_default", meaning it will catch any input, in this case
|
||||
to return to the main menu. So you can e.g. press <return> to go
|
||||
back now.
|
||||
""" % caller.db.menuattrtest,
|
||||
# optional help text for this node
|
||||
"""
|
||||
""" % caller.db.menuattrtest, # optional help text for this node
|
||||
"""
|
||||
This is the help entry for this node. It is created by returning
|
||||
the node text as a tuple - the second string in that tuple will be
|
||||
used as the help text.
|
||||
|
|
@ -1031,7 +1045,7 @@ def test_set_node(caller):
|
|||
|
||||
def test_view_node(caller):
|
||||
text = """
|
||||
Your name is {g%s{n!
|
||||
Your name is |g%s|n!
|
||||
|
||||
click |lclook|lthere|le to trigger a look command under MXP.
|
||||
This node's option has no explicit key (nor the "_default" key
|
||||
|
|
@ -1044,11 +1058,11 @@ def test_view_node(caller):
|
|||
return text, options
|
||||
|
||||
|
||||
def test_displayinput_node(caller, raw_string):
|
||||
def test_displayinput_node(caller, raw_string):
|
||||
text = """
|
||||
You entered the text:
|
||||
|
||||
"{w%s{n"
|
||||
"|w%s|n"
|
||||
|
||||
... which could now be handled or stored here in some way if this
|
||||
was not just an example.
|
||||
|
|
@ -1059,7 +1073,7 @@ def test_displayinput_node(caller, raw_string):
|
|||
to the start node.
|
||||
""" % raw_string
|
||||
options = {"key": "_default",
|
||||
"goto": "test_start_node"}
|
||||
"goto": "test_start_node"}
|
||||
return text, options
|
||||
|
||||
|
||||
|
|
@ -1089,5 +1103,5 @@ class CmdTestMenu(Command):
|
|||
self.caller.msg("Usage: testmenu menumodule")
|
||||
return
|
||||
# start menu
|
||||
EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True, cmdset_mergetype="Replace",
|
||||
testval="val", testval2="val2")
|
||||
EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True,
|
||||
cmdset_mergetype="Replace", testval="val", testval2="val2")
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ class EvMore(object):
|
|||
lines.append(line)
|
||||
|
||||
# always limit number of chars to 10 000 per page
|
||||
height = min(10000 // width, height)
|
||||
height = min(10000 // max(1, width), height)
|
||||
|
||||
self._pages = ["\n".join(lines[i:i+height]) for i in range(0, len(lines), height)]
|
||||
self._npages = len(self._pages)
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ from evennia.utils.ansi import ANSIString
|
|||
|
||||
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||
|
||||
|
||||
def _to_ansi(obj):
|
||||
"""
|
||||
convert to ANSIString.
|
||||
|
|
@ -142,6 +143,8 @@ def _to_ansi(obj):
|
|||
|
||||
_unicode = unicode
|
||||
_whitespace = '\t\n\x0b\x0c\r '
|
||||
|
||||
|
||||
class ANSITextWrapper(TextWrapper):
|
||||
"""
|
||||
This is a wrapper work class for handling strings with ANSI tags
|
||||
|
|
@ -158,8 +161,8 @@ class ANSITextWrapper(TextWrapper):
|
|||
becomes " foo bar baz".
|
||||
"""
|
||||
return text
|
||||
##TODO: Ignore expand_tabs/replace_whitespace until ANSISTring handles them.
|
||||
## - don't remove this code. /Griatch
|
||||
# TODO: Ignore expand_tabs/replace_whitespace until ANSIString handles them.
|
||||
# - don't remove this code. /Griatch
|
||||
# if self.expand_tabs:
|
||||
# text = text.expandtabs()
|
||||
# if self.replace_whitespace:
|
||||
|
|
@ -169,7 +172,6 @@ class ANSITextWrapper(TextWrapper):
|
|||
# text = text.translate(self.unicode_whitespace_trans)
|
||||
# return text
|
||||
|
||||
|
||||
def _split(self, text):
|
||||
"""_split(text : string) -> [string]
|
||||
|
||||
|
|
@ -289,6 +291,7 @@ def wrap(text, width=_DEFAULT_WIDTH, **kwargs):
|
|||
w = ANSITextWrapper(width=width, **kwargs)
|
||||
return w.wrap(text)
|
||||
|
||||
|
||||
def fill(text, width=_DEFAULT_WIDTH, **kwargs):
|
||||
"""Fill a single paragraph of text, returning a new string.
|
||||
|
||||
|
|
@ -311,6 +314,7 @@ def fill(text, width=_DEFAULT_WIDTH, **kwargs):
|
|||
|
||||
# EvCell class (see further down for the EvTable itself)
|
||||
|
||||
|
||||
class EvCell(object):
|
||||
"""
|
||||
Holds a single data cell for the table. A cell has a certain width
|
||||
|
|
@ -384,7 +388,7 @@ class EvCell(object):
|
|||
padwidth = int(padwidth) if padwidth is not None else None
|
||||
self.pad_left = int(kwargs.get("pad_left", padwidth if padwidth is not None else 1))
|
||||
self.pad_right = int(kwargs.get("pad_right", padwidth if padwidth is not None else 1))
|
||||
self.pad_top = int( kwargs.get("pad_top", padwidth if padwidth is not None else 0))
|
||||
self.pad_top = int(kwargs.get("pad_top", padwidth if padwidth is not None else 0))
|
||||
self.pad_bottom = int(kwargs.get("pad_bottom", padwidth if padwidth is not None else 0))
|
||||
|
||||
self.enforce_size = kwargs.get("enforce_size", False)
|
||||
|
|
@ -429,7 +433,7 @@ class EvCell(object):
|
|||
self.align = kwargs.get("align", "l")
|
||||
self.valign = kwargs.get("valign", "c")
|
||||
|
||||
#self.data = self._split_lines(unicode(data))
|
||||
# self.data = self._split_lines(unicode(data))
|
||||
self.data = self._split_lines(_to_ansi(data))
|
||||
self.raw_width = max(m_len(line) for line in self.data)
|
||||
self.raw_height = len(self.data)
|
||||
|
|
@ -442,20 +446,20 @@ class EvCell(object):
|
|||
if "width" in kwargs:
|
||||
width = kwargs.pop("width")
|
||||
self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right
|
||||
if self.width <= 0 and self.raw_width > 0:
|
||||
if self.width <= 0 < self.raw_width:
|
||||
raise Exception("Cell width too small - no space for data.")
|
||||
else:
|
||||
self.width = self.raw_width
|
||||
if "height" in kwargs:
|
||||
height = kwargs.pop("height")
|
||||
self.height = height - self.pad_top - self.pad_bottom - self.border_top - self.border_bottom
|
||||
if self.height <= 0 and self.raw_height > 0:
|
||||
if self.height <= 0 < self.raw_height:
|
||||
raise Exception("Cell height too small - no space for data.")
|
||||
else:
|
||||
self.height = self.raw_height
|
||||
|
||||
# prepare data
|
||||
#self.formatted = self._reformat()
|
||||
# self.formatted = self._reformat()
|
||||
|
||||
def _crop(self, text, width):
|
||||
"""
|
||||
|
|
@ -512,8 +516,8 @@ class EvCell(object):
|
|||
if 0 < width < m_len(line):
|
||||
# replace_whitespace=False, expand_tabs=False is a
|
||||
# fix for ANSIString not supporting expand_tabs/translate
|
||||
adjusted_data.extend([ANSIString(part + ANSIString("{n"))
|
||||
for part in wrap(line, width=width, drop_whitespace=False)])
|
||||
adjusted_data.extend([ANSIString(part + ANSIString("|n"))
|
||||
for part in wrap(line, width=width, drop_whitespace=False)])
|
||||
else:
|
||||
adjusted_data.append(line)
|
||||
if self.enforce_size:
|
||||
|
|
@ -526,7 +530,7 @@ class EvCell(object):
|
|||
adjusted_data[-1] = adjusted_data[-1][:-2] + ".."
|
||||
elif excess < 0:
|
||||
# too few lines. Fill to height.
|
||||
adjusted_data.extend(["" for i in range(excess)])
|
||||
adjusted_data.extend(["" for _ in range(excess)])
|
||||
|
||||
return adjusted_data
|
||||
|
||||
|
|
@ -577,11 +581,14 @@ class EvCell(object):
|
|||
hfill_char = self.hfill_char
|
||||
width = self.width
|
||||
if align == "l":
|
||||
lines= [(line.lstrip(" ") + " " if line.startswith(" ") and not line.startswith(" ") else line) + hfill_char * (width - m_len(line)) for line in data]
|
||||
lines = [(line.lstrip(" ") + " " if line.startswith(" ") and not line.startswith(" ")
|
||||
else line) + hfill_char * (width - m_len(line)) for line in data]
|
||||
return lines
|
||||
elif align == "r":
|
||||
return [hfill_char * (width - m_len(line)) + (" " + line.rstrip(" ") if line.endswith(" ") and not line.endswith(" ") else line) for line in data]
|
||||
else: # center, 'c'
|
||||
return [hfill_char * (width - m_len(line)) + (" " + line.rstrip(" ")
|
||||
if line.endswith(" ") and not line.endswith(" ")
|
||||
else line) for line in data]
|
||||
else: # center, 'c'
|
||||
return [self._center(line, self.width, self.hfill_char) for line in data]
|
||||
|
||||
def _valign(self, data):
|
||||
|
|
@ -605,11 +612,11 @@ class EvCell(object):
|
|||
return data
|
||||
# only care if we need to add new lines
|
||||
if valign == 't':
|
||||
return data + [padline for i in range(excess)]
|
||||
return data + [padline for _ in range(excess)]
|
||||
elif valign == 'b':
|
||||
return [padline for i in range(excess)] + data
|
||||
else: # center
|
||||
narrowside = [padline for i in range(excess // 2)]
|
||||
return [padline for _ in range(excess)] + data
|
||||
else: # center
|
||||
narrowside = [padline for _ in range(excess // 2)]
|
||||
widerside = narrowside + [padline]
|
||||
if excess % 2:
|
||||
# uneven padding
|
||||
|
|
@ -635,8 +642,8 @@ class EvCell(object):
|
|||
left = self.hpad_char * self.pad_left
|
||||
right = self.hpad_char * self.pad_right
|
||||
vfill = (self.width + self.pad_left + self.pad_right) * self.vpad_char
|
||||
top = [vfill for i in range(self.pad_top)]
|
||||
bottom = [vfill for i in range(self.pad_bottom)]
|
||||
top = [vfill for _ in range(self.pad_top)]
|
||||
bottom = [vfill for _ in range(self.pad_bottom)]
|
||||
return top + [left + line + right for line in data] + bottom
|
||||
|
||||
def _border(self, data):
|
||||
|
|
@ -654,18 +661,17 @@ class EvCell(object):
|
|||
left = self.border_left_char * self.border_left + ANSIString('|n')
|
||||
right = ANSIString('|n') + self.border_right_char * self.border_right
|
||||
|
||||
cwidth = self.width + self.pad_left + self.pad_right + \
|
||||
max(0,self.border_left-1) + max(0, self.border_right-1)
|
||||
cwidth = self.width + self.pad_left + self.pad_right + max(0, self.border_left-1) + max(0, self.border_right-1)
|
||||
|
||||
vfill = self.corner_top_left_char if left else ""
|
||||
vfill += cwidth * self.border_top_char
|
||||
vfill += self.corner_top_right_char if right else ""
|
||||
top = [vfill for i in range(self.border_top)]
|
||||
top = [vfill for _ in range(self.border_top)]
|
||||
|
||||
vfill = self.corner_bottom_left_char if left else ""
|
||||
vfill += cwidth * self.border_bottom_char
|
||||
vfill += self.corner_bottom_right_char if right else ""
|
||||
bottom = [vfill for i in range(self.border_bottom)]
|
||||
bottom = [vfill for _ in range(self.border_bottom)]
|
||||
|
||||
return top + [left + line + right for line in data] + bottom
|
||||
|
||||
|
|
@ -699,7 +705,7 @@ class EvCell(object):
|
|||
natural_height (int): Height of cell.
|
||||
|
||||
"""
|
||||
return len(self.formatted) #if self.formatted else 0
|
||||
return len(self.formatted) # if self.formatted else 0
|
||||
|
||||
def get_width(self):
|
||||
"""
|
||||
|
|
@ -709,7 +715,7 @@ class EvCell(object):
|
|||
natural_width (int): Width of cell.
|
||||
|
||||
"""
|
||||
return m_len(self.formatted[0]) #if self.formatted else 0
|
||||
return m_len(self.formatted[0]) # if self.formatted else 0
|
||||
|
||||
def replace_data(self, data, **kwargs):
|
||||
"""
|
||||
|
|
@ -723,7 +729,7 @@ class EvCell(object):
|
|||
`EvCell.__init__`.
|
||||
|
||||
"""
|
||||
#self.data = self._split_lines(unicode(data))
|
||||
# self.data = self._split_lines(unicode(data))
|
||||
self.data = self._split_lines(_to_ansi(data))
|
||||
self.raw_width = max(m_len(line) for line in self.data)
|
||||
self.raw_height = len(self.data)
|
||||
|
|
@ -746,7 +752,7 @@ class EvCell(object):
|
|||
padwidth = int(padwidth) if padwidth is not None else None
|
||||
self.pad_left = int(kwargs.pop("pad_left", padwidth if padwidth is not None else self.pad_left))
|
||||
self.pad_right = int(kwargs.pop("pad_right", padwidth if padwidth is not None else self.pad_right))
|
||||
self.pad_top = int( kwargs.pop("pad_top", padwidth if padwidth is not None else self.pad_top))
|
||||
self.pad_top = int(kwargs.pop("pad_top", padwidth if padwidth is not None else self.pad_top))
|
||||
self.pad_bottom = int(kwargs.pop("pad_bottom", padwidth if padwidth is not None else self.pad_bottom))
|
||||
|
||||
self.enforce_size = kwargs.get("enforce_size", False)
|
||||
|
|
@ -764,22 +770,34 @@ class EvCell(object):
|
|||
self.vfill_char = vfill_char[0] if vfill_char else self.vfill_char
|
||||
|
||||
borderwidth = kwargs.get("border_width", None)
|
||||
self.border_left = kwargs.pop("border_left", borderwidth if borderwidth is not None else self.border_left)
|
||||
self.border_right = kwargs.pop("border_right", borderwidth if borderwidth is not None else self.border_right)
|
||||
self.border_top = kwargs.pop("border_top", borderwidth if borderwidth is not None else self.border_top)
|
||||
self.border_bottom = kwargs.pop("border_bottom", borderwidth if borderwidth is not None else self.border_bottom)
|
||||
self.border_left = kwargs.pop(
|
||||
"border_left", borderwidth if borderwidth is not None else self.border_left)
|
||||
self.border_right = kwargs.pop(
|
||||
"border_right", borderwidth if borderwidth is not None else self.border_right)
|
||||
self.border_top = kwargs.pop(
|
||||
"border_top", borderwidth if borderwidth is not None else self.border_top)
|
||||
self.border_bottom = kwargs.pop(
|
||||
"border_bottom", borderwidth if borderwidth is not None else self.border_bottom)
|
||||
|
||||
borderchar = kwargs.get("border_char", None)
|
||||
self.border_left_char = kwargs.pop("border_left_char", borderchar if borderchar else self.border_left_char)
|
||||
self.border_right_char = kwargs.pop("border_right_char", borderchar if borderchar else self.border_right_char)
|
||||
self.border_top_char = kwargs.pop("border_topchar", borderchar if borderchar else self.border_top_char)
|
||||
self.border_bottom_char = kwargs.pop("border_bottom_char", borderchar if borderchar else self.border_bottom_char)
|
||||
self.border_left_char = kwargs.pop(
|
||||
"border_left_char", borderchar if borderchar else self.border_left_char)
|
||||
self.border_right_char = kwargs.pop(
|
||||
"border_right_char", borderchar if borderchar else self.border_right_char)
|
||||
self.border_top_char = kwargs.pop(
|
||||
"border_topchar", borderchar if borderchar else self.border_top_char)
|
||||
self.border_bottom_char = kwargs.pop(
|
||||
"border_bottom_char", borderchar if borderchar else self.border_bottom_char)
|
||||
|
||||
corner_char = kwargs.get("corner_char", None)
|
||||
self.corner_top_left_char = kwargs.pop("corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char)
|
||||
self.corner_top_right_char = kwargs.pop("corner_top_right", corner_char if corner_char is not None else self.corner_top_right_char)
|
||||
self.corner_bottom_left_char = kwargs.pop("corner_bottom_left", corner_char if corner_char is not None else self.corner_bottom_left_char)
|
||||
self.corner_bottom_right_char = kwargs.pop("corner_bottom_right", corner_char if corner_char is not None else self.corner_bottom_right_char)
|
||||
self.corner_top_left_char = kwargs.pop(
|
||||
"corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char)
|
||||
self.corner_top_right_char = kwargs.pop(
|
||||
"corner_top_right", corner_char if corner_char is not None else self.corner_top_right_char)
|
||||
self.corner_bottom_left_char = kwargs.pop(
|
||||
"corner_bottom_left", corner_char if corner_char is not None else self.corner_bottom_left_char)
|
||||
self.corner_bottom_right_char = kwargs.pop(
|
||||
"corner_bottom_right", corner_char if corner_char is not None else self.corner_bottom_right_char)
|
||||
|
||||
# this is used by the table to adjust size of cells with borders in the middle
|
||||
# of the table
|
||||
|
|
@ -793,13 +811,16 @@ class EvCell(object):
|
|||
# Handle sizes
|
||||
if "width" in kwargs:
|
||||
width = kwargs.pop("width")
|
||||
self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right + self.trim_horizontal
|
||||
if self.width <= 0 and self.raw_width > 0:
|
||||
self.width = width - self.pad_left - self.pad_right\
|
||||
- self.border_left - self.border_right + self.trim_horizontal
|
||||
# if self.width <= 0 and self.raw_width > 0:
|
||||
if self.width <= 0 < self.raw_width:
|
||||
raise Exception("Cell width too small, no room for data.")
|
||||
if "height" in kwargs:
|
||||
height = kwargs.pop("height")
|
||||
self.height = height - self.pad_top - self.pad_bottom - self.border_top - self.border_bottom + self.trim_vertical
|
||||
if self.height <= 0 and self.raw_height > 0:
|
||||
self.height = height - self.pad_top - self.pad_bottom\
|
||||
- self.border_top - self.border_bottom + self.trim_vertical
|
||||
if self.height <= 0 < self.raw_height:
|
||||
raise Exception("Cell height too small, no room for data.")
|
||||
|
||||
# reformat (to new sizes, padding, header and borders)
|
||||
|
|
@ -868,8 +889,8 @@ class EvColumn(object):
|
|||
col = self.column
|
||||
kwargs.update(self.options)
|
||||
# use fixed width or adjust to the largest cell
|
||||
if not "width" in kwargs:
|
||||
[cell.reformat() for cell in col] # this is necessary to get initial widths of all cells
|
||||
if "width" not in kwargs:
|
||||
[cell.reformat() for cell in col] # this is necessary to get initial widths of all cells
|
||||
kwargs["width"] = max(cell.get_width() for cell in col) if col else 0
|
||||
[cell.reformat(**kwargs) for cell in col]
|
||||
|
||||
|
|
@ -900,11 +921,11 @@ class EvColumn(object):
|
|||
ypos = min(len(self.column)-1, max(0, int(ypos)))
|
||||
new_cells = [EvCell(data, **self.options) for data in args]
|
||||
self.column = self.column[:ypos] + new_cells + self.column[ypos:]
|
||||
#self._balance(**kwargs)
|
||||
# self._balance(**kwargs)
|
||||
|
||||
def reformat(self, **kwargs):
|
||||
"""
|
||||
Change the options for the collumn.
|
||||
Change the options for the column.
|
||||
|
||||
Kwargs:
|
||||
Keywords as per `EvCell.__init__`.
|
||||
|
|
@ -930,19 +951,24 @@ class EvColumn(object):
|
|||
|
||||
def __repr__(self):
|
||||
return "<EvColumn\n %s>" % ("\n ".join([repr(cell) for cell in self.column]))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.column)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.column)
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.column[index]
|
||||
|
||||
def __setitem__(self, index, value):
|
||||
self.column[index] = value
|
||||
|
||||
def __delitem__(self, index):
|
||||
del self.column[index]
|
||||
|
||||
|
||||
## Main Evtable class
|
||||
# Main Evtable class
|
||||
|
||||
class EvTable(object):
|
||||
"""
|
||||
|
|
@ -998,7 +1024,7 @@ class EvTable(object):
|
|||
height (int, optional): Fixed height of table. Defaults to being unset. Width is
|
||||
still given precedence. If given, table cells will crop text rather
|
||||
than expand vertically.
|
||||
evenwidth (bool, optional): Used with the `width` keyword. Adjusts collumns to have as even width as
|
||||
evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as even width as
|
||||
possible. This often looks best also for mixed-length tables. Default is `False`.
|
||||
maxwidth (int, optional): This will set a maximum width
|
||||
of the table while allowing it to be smaller. Only if it grows wider than this
|
||||
|
|
@ -1025,10 +1051,10 @@ class EvTable(object):
|
|||
excess = len(header) - len(table)
|
||||
if excess > 0:
|
||||
# header bigger than table
|
||||
table.extend([] for i in range(excess))
|
||||
table.extend([] for _ in range(excess))
|
||||
elif excess < 0:
|
||||
# too short header
|
||||
header.extend(_to_ansi(["" for i in range(abs(excess))]))
|
||||
header.extend(_to_ansi(["" for _ in range(abs(excess))]))
|
||||
for ix, heading in enumerate(header):
|
||||
table[ix].insert(0, heading)
|
||||
else:
|
||||
|
|
@ -1043,7 +1069,7 @@ class EvTable(object):
|
|||
border = kwargs.pop("border", "tablecols")
|
||||
if border is None:
|
||||
border = "none"
|
||||
if not border in ("none", "table", "tablecols",
|
||||
if border not in ("none", "table", "tablecols",
|
||||
"header", "incols", "cols", "rows", "cells"):
|
||||
raise Exception("Unsupported border type: '%s'" % border)
|
||||
self.border = border
|
||||
|
|
@ -1052,10 +1078,14 @@ class EvTable(object):
|
|||
self.border_width = kwargs.get("border_width", 1)
|
||||
self.corner_char = kwargs.get("corner_char", "+")
|
||||
pcorners = kwargs.pop("pretty_corners", False)
|
||||
self.corner_top_left_char = _to_ansi(kwargs.pop("corner_top_left_char", '.' if pcorners else self.corner_char))
|
||||
self.corner_top_right_char = _to_ansi(kwargs.pop("corner_top_right_char", '.' if pcorners else self.corner_char))
|
||||
self.corner_bottom_left_char = _to_ansi(kwargs.pop("corner_bottom_left_char", ' ' if pcorners else self.corner_char))
|
||||
self.corner_bottom_right_char = _to_ansi(kwargs.pop("corner_bottom_right_char", ' ' if pcorners else self.corner_char))
|
||||
self.corner_top_left_char = _to_ansi(kwargs.pop(
|
||||
"corner_top_left_char", '.' if pcorners else self.corner_char))
|
||||
self.corner_top_right_char = _to_ansi(kwargs.pop(
|
||||
"corner_top_right_char", '.' if pcorners else self.corner_char))
|
||||
self.corner_bottom_left_char = _to_ansi(kwargs.pop(
|
||||
"corner_bottom_left_char", ' ' if pcorners else self.corner_char))
|
||||
self.corner_bottom_right_char = _to_ansi(kwargs.pop(
|
||||
"corner_bottom_right_char", ' ' if pcorners else self.corner_char))
|
||||
|
||||
self.width = kwargs.pop("width", None)
|
||||
self.height = kwargs.pop("height", None)
|
||||
|
|
@ -1079,7 +1109,7 @@ class EvTable(object):
|
|||
self.worktable = None
|
||||
|
||||
# balance the table
|
||||
#self._balance()
|
||||
# self._balance()
|
||||
|
||||
def _cellborders(self, ix, iy, nx, ny, **kwargs):
|
||||
"""
|
||||
|
|
@ -1114,7 +1144,7 @@ class EvTable(object):
|
|||
headchar = self.header_line_char
|
||||
|
||||
def corners(ret):
|
||||
"Handle corners of table"
|
||||
"""Handle corners of table"""
|
||||
if ix == 0 and iy == 0:
|
||||
ret["corner_top_left_char"] = self.corner_top_left_char
|
||||
if ix == nx and iy == 0:
|
||||
|
|
@ -1126,47 +1156,47 @@ class EvTable(object):
|
|||
return ret
|
||||
|
||||
def left_edge(ret):
|
||||
"add vertical border along left table edge"
|
||||
"""add vertical border along left table edge"""
|
||||
if ix == 0:
|
||||
ret["border_left"] = bwidth
|
||||
#ret["trim_horizontal"] = bwidth
|
||||
# ret["trim_horizontal"] = bwidth
|
||||
return ret
|
||||
|
||||
def top_edge(ret):
|
||||
"add border along top table edge"
|
||||
"""add border along top table edge"""
|
||||
if iy == 0:
|
||||
ret["border_top"] = bwidth
|
||||
#ret["trim_vertical"] = bwidth
|
||||
# ret["trim_vertical"] = bwidth
|
||||
return ret
|
||||
|
||||
def right_edge(ret):
|
||||
"add vertical border along right table edge"
|
||||
if ix == nx:# and 0 < iy < ny:
|
||||
"""add vertical border along right table edge"""
|
||||
if ix == nx: # and 0 < iy < ny:
|
||||
ret["border_right"] = bwidth
|
||||
#ret["trim_horizontal"] = 0
|
||||
# ret["trim_horizontal"] = 0
|
||||
return ret
|
||||
|
||||
def bottom_edge(ret):
|
||||
"add border along bottom table edge"
|
||||
"""add border along bottom table edge"""
|
||||
if iy == ny:
|
||||
ret["border_bottom"] = bwidth
|
||||
#ret["trim_vertical"] = bwidth
|
||||
# ret["trim_vertical"] = bwidth
|
||||
return ret
|
||||
|
||||
def cols(ret):
|
||||
"Adding vertical borders inside the table"
|
||||
"""Adding vertical borders inside the table"""
|
||||
if 0 <= ix < nx:
|
||||
ret["border_right"] = bwidth
|
||||
return ret
|
||||
|
||||
def rows(ret):
|
||||
"Adding horizontal borders inside the table"
|
||||
"""Adding horizontal borders inside the table"""
|
||||
if 0 <= iy < ny:
|
||||
ret["border_bottom"] = bwidth
|
||||
return ret
|
||||
|
||||
def head(ret):
|
||||
"Add header underline"
|
||||
"""Add header underline"""
|
||||
if iy == 0:
|
||||
# put different bottom line for header
|
||||
ret["border_bottom"] = bwidth
|
||||
|
|
@ -1176,15 +1206,15 @@ class EvTable(object):
|
|||
# use the helper functions to define various
|
||||
# table "styles"
|
||||
|
||||
if border in ("table", "tablecols","cells"):
|
||||
if border in ("table", "tablecols", "cells"):
|
||||
ret = bottom_edge(right_edge(top_edge(left_edge(corners(ret)))))
|
||||
if border in ("cols", "tablecols", "cells"):
|
||||
ret = cols(right_edge(left_edge(ret)))
|
||||
if border in ("incols"):
|
||||
if border in "incols":
|
||||
ret = cols(ret)
|
||||
if border in ("rows", "cells"):
|
||||
ret = rows(bottom_edge(top_edge(ret)))
|
||||
if header and not border in ("none", None):
|
||||
if header and border not in ("none", None):
|
||||
ret = head(ret)
|
||||
|
||||
return ret
|
||||
|
|
@ -1197,7 +1227,7 @@ class EvTable(object):
|
|||
options = self.options
|
||||
for ix, col in enumerate(self.worktable):
|
||||
for iy, cell in enumerate(col):
|
||||
col.reformat_cell(iy, **self._cellborders(ix,iy,nx,ny,**options))
|
||||
col.reformat_cell(iy, **self._cellborders(ix, iy, nx, ny, **options))
|
||||
|
||||
def _balance(self):
|
||||
"""
|
||||
|
|
@ -1211,6 +1241,8 @@ class EvTable(object):
|
|||
# actual table. This allows us to add columns/rows
|
||||
# and re-balance over and over without issue.
|
||||
self.worktable = deepcopy(self.table)
|
||||
# self._borders()
|
||||
# return
|
||||
options = copy(self.options)
|
||||
|
||||
# balance number of rows to make a rectangular table
|
||||
|
|
@ -1222,7 +1254,7 @@ class EvTable(object):
|
|||
self.worktable[icol].reformat(**options)
|
||||
if nrow < nrowmax:
|
||||
# add more rows to too-short columns
|
||||
empty_rows = ["" for i in range(nrowmax-nrow)]
|
||||
empty_rows = ["" for _ in range(nrowmax-nrow)]
|
||||
self.worktable[icol].add_rows(*empty_rows)
|
||||
self.ncols = ncols
|
||||
self.nrows = nrowmax
|
||||
|
|
@ -1251,16 +1283,16 @@ class EvTable(object):
|
|||
|
||||
excess = width - cwmin
|
||||
if self.evenwidth:
|
||||
# make each collumn of equal width
|
||||
for i in range(excess):
|
||||
# flood-fill the minimum table starting with the smallest collumns
|
||||
# make each column of equal width
|
||||
for _ in range(excess):
|
||||
# flood-fill the minimum table starting with the smallest columns
|
||||
ci = cwidths_min.index(min(cwidths_min))
|
||||
cwidths_min[ci] += 1
|
||||
cwidths = cwidths_min
|
||||
else:
|
||||
# make each collumn expand more proportional to their data size
|
||||
for i in range(excess):
|
||||
# fill wider collumns first
|
||||
# make each column expand more proportional to their data size
|
||||
for _ in range(excess):
|
||||
# fill wider columns first
|
||||
ci = cwidths.index(max(cwidths))
|
||||
cwidths_min[ci] += 1
|
||||
cwidths[ci] -= 3
|
||||
|
|
@ -1280,8 +1312,9 @@ class EvTable(object):
|
|||
# if we are fixing the table height, it means cells must crop text instead of resizing.
|
||||
if nrowmax:
|
||||
|
||||
# get minimum possible cell heights for each collumn
|
||||
cheights_min = [max(cell.get_min_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
|
||||
# get minimum possible cell heights for each column
|
||||
cheights_min = [max(cell.get_min_height()
|
||||
for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
|
||||
chmin = sum(cheights_min)
|
||||
|
||||
if chmin > self.height:
|
||||
|
|
@ -1294,9 +1327,9 @@ class EvTable(object):
|
|||
|
||||
excess = self.height - chmin
|
||||
even = self.height % 2 == 0
|
||||
for i in range(excess):
|
||||
for position in range(excess):
|
||||
# expand the cells with the most rows first
|
||||
if 0 <= i < nrowmax and nrowmax > 1:
|
||||
if 0 <= position < nrowmax and nrowmax > 1:
|
||||
# avoid adding to header first round (looks bad on very small tables)
|
||||
ci = cheights[1:].index(max(cheights[1:])) + 1
|
||||
else:
|
||||
|
|
@ -1318,7 +1351,7 @@ class EvTable(object):
|
|||
col.reformat_cell(iy, height=cheights[iy], **options)
|
||||
except Exception as e:
|
||||
msg = "ix=%s, iy=%s, height=%s: %s" % (ix, iy, cheights[iy], e.message)
|
||||
raise Exception ("Error in vertical allign:\n %s" % msg)
|
||||
raise Exception("Error in vertical align:\n %s" % msg)
|
||||
|
||||
# calculate actual table width/height in characters
|
||||
self.cwidth = sum(cwidths)
|
||||
|
|
@ -1387,12 +1420,12 @@ class EvTable(object):
|
|||
if excess > 0:
|
||||
# we need to add new rows to table
|
||||
for col in self.table:
|
||||
empty_rows = ["" for i in range(excess)]
|
||||
empty_rows = ["" for _ in range(excess)]
|
||||
col.add_rows(*empty_rows, **options)
|
||||
self.nrows += excess
|
||||
elif excess < 0:
|
||||
# we need to add new rows to new column
|
||||
empty_rows = ["" for i in range(abs(excess))]
|
||||
empty_rows = ["" for _ in range(abs(excess))]
|
||||
column.add_rows(*empty_rows, **options)
|
||||
self.nrows -= excess
|
||||
|
||||
|
|
@ -1411,7 +1444,7 @@ class EvTable(object):
|
|||
xpos = min(wtable-1, max(0, int(xpos)))
|
||||
self.table.insert(xpos, column)
|
||||
self.ncols += 1
|
||||
#self._balance()
|
||||
# self._balance()
|
||||
|
||||
def add_row(self, *args, **kwargs):
|
||||
"""
|
||||
|
|
@ -1444,12 +1477,12 @@ class EvTable(object):
|
|||
|
||||
if excess > 0:
|
||||
# we need to add new empty columns to table
|
||||
empty_rows = ["" for i in range(htable)]
|
||||
self.table.extend([EvColumn(*empty_rows, **options) for i in range(excess)])
|
||||
empty_rows = ["" for _ in range(htable)]
|
||||
self.table.extend([EvColumn(*empty_rows, **options) for _ in range(excess)])
|
||||
self.ncols += excess
|
||||
elif excess < 0:
|
||||
# we need to add more cells to row
|
||||
row.extend(["" for i in range(abs(excess))])
|
||||
row.extend(["" for _ in range(abs(excess))])
|
||||
self.ncols -= excess
|
||||
|
||||
if ypos is None or ypos > htable - 1:
|
||||
|
|
@ -1462,7 +1495,7 @@ class EvTable(object):
|
|||
for icol, col in enumerate(self.table):
|
||||
col.add_rows(row[icol], ypos=ypos, **options)
|
||||
self.nrows += 1
|
||||
#self._balance()
|
||||
# self._balance()
|
||||
|
||||
def reformat(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -1523,16 +1556,17 @@ class EvTable(object):
|
|||
return [line for line in self._generate_lines()]
|
||||
|
||||
def __str__(self):
|
||||
"print table (this also balances it)"
|
||||
return str(unicode(ANSIString("\n").join([line for line in self._generate_lines()])))
|
||||
"""print table (this also balances it)"""
|
||||
return str(unicode(ANSIString("\n").join([line for line in self._generate_lines()])))
|
||||
|
||||
def __unicode__(self):
|
||||
return unicode(ANSIString("\n").join([line for line in self._generate_lines()]))
|
||||
return unicode(ANSIString("\n").join([line for line in self._generate_lines()]))
|
||||
|
||||
|
||||
def _test():
|
||||
"Test"
|
||||
table = EvTable("{yHeading1{n", "{gHeading2{n", table=[[1,2,3],[4,5,6],[7,8,9]], border="cells", align="l")
|
||||
table.add_column("{rThis is long data{n", "{bThis is even longer data{n")
|
||||
"""Test"""
|
||||
table = EvTable("|yHeading1|n", "|gHeading2|n", table=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], border="cells", align="l")
|
||||
table.add_column("|rThis is long data|n", "|bThis is even longer data|n")
|
||||
table.add_row("This is a single row")
|
||||
print(unicode(table))
|
||||
table.reformat(width=50)
|
||||
|
|
@ -1541,5 +1575,9 @@ def _test():
|
|||
print(unicode(table))
|
||||
return table
|
||||
|
||||
|
||||
def _test2():
|
||||
table = EvTable("|yHeading1|n", "|B|[GHeading2|n", "Heading3")
|
||||
for i in range(100):
|
||||
table.add_row("This is col 0, row %i" % i, "|gThis is col 1, row |w%i|n|g.|n" % i, "This is col 2, row %i" % i)
|
||||
return table
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ _GAME_EPOCH = None
|
|||
# Helper Script dealing in gametime (created by `schedule` function
|
||||
# below).
|
||||
|
||||
|
||||
class TimeScript(DefaultScript):
|
||||
"""Gametime-sensitive script."""
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ class TimeScript(DefaultScript):
|
|||
|
||||
# Access functions
|
||||
|
||||
|
||||
def runtime():
|
||||
"""
|
||||
Get the total runtime of the server since first start (minus
|
||||
|
|
@ -134,8 +136,9 @@ def gametime(absolute=False):
|
|||
gtime = epoch + (runtime() - GAME_TIME_OFFSET) * TIMEFACTOR
|
||||
return gtime
|
||||
|
||||
def real_seconds_until(sec=None, min=None, hour=None, day=None,
|
||||
month=None, year=None):
|
||||
|
||||
def real_seconds_until(sec=None, min=None, hour=None,
|
||||
day=None, month=None, year=None):
|
||||
"""
|
||||
Return the real seconds until game time.
|
||||
|
||||
|
|
@ -187,8 +190,9 @@ def real_seconds_until(sec=None, min=None, hour=None, day=None,
|
|||
seconds = (projected - current).total_seconds()
|
||||
return seconds / TIMEFACTOR
|
||||
|
||||
def schedule(callback, repeat=False, sec=None, min=None, hour=None,
|
||||
day=None, month=None, year=None):
|
||||
|
||||
def schedule(callback, repeat=False, sec=None, min=None,
|
||||
hour=None, day=None, month=None, year=None):
|
||||
"""
|
||||
Call a callback at a given in-game time.
|
||||
|
||||
|
|
@ -212,12 +216,12 @@ def schedule(callback, repeat=False, sec=None, min=None, hour=None,
|
|||
schedule(func, min=5, sec=0) # Will call 5 minutes past the next (in-game) hour.
|
||||
schedule(func, hour=2, min=30, sec=0) # Will call the next (in-game) day at 02:30.
|
||||
"""
|
||||
seconds = real_seconds_until(sec=sec, min=min, hour=hour, day=day,
|
||||
month=month, year=year)
|
||||
seconds = real_seconds_until(sec=sec, min=min, hour=hour,
|
||||
day=day, month=month, year=year)
|
||||
script = create_script("evennia.utils.gametime.TimeScript",
|
||||
key="TimeScript", desc="A gametime-sensitive script",
|
||||
interval=seconds, start_delay=True,
|
||||
repeats=-1 if repeat else 1)
|
||||
key="TimeScript", desc="A gametime-sensitive script",
|
||||
interval=seconds, start_delay=True,
|
||||
repeats=-1 if repeat else 1)
|
||||
script.db.callback = callback
|
||||
script.db.gametime = {
|
||||
"sec": sec,
|
||||
|
|
@ -229,6 +233,7 @@ def schedule(callback, repeat=False, sec=None, min=None, hour=None,
|
|||
}
|
||||
return script
|
||||
|
||||
|
||||
def reset_gametime():
|
||||
"""
|
||||
Resets the game time to make it start from the current time. Note that
|
||||
|
|
@ -238,5 +243,3 @@ def reset_gametime():
|
|||
global GAME_TIME_OFFSET
|
||||
GAME_TIME_OFFSET = runtime()
|
||||
ServerConfig.objects.conf("gametime_offset", GAME_TIME_OFFSET)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ from twisted.internet.threads import deferToThread
|
|||
_LOGDIR = None
|
||||
_TIMEZONE = None
|
||||
|
||||
|
||||
def timeformat(when=None):
|
||||
"""
|
||||
This helper function will format the current time in the same
|
||||
|
|
@ -88,7 +89,7 @@ def log_err(errmsg):
|
|||
Prints/logs an error message to the server log.
|
||||
|
||||
Args:
|
||||
errormsg (str): The message to be logged.
|
||||
errmsg (str): The message to be logged.
|
||||
|
||||
"""
|
||||
try:
|
||||
|
|
@ -97,7 +98,7 @@ def log_err(errmsg):
|
|||
errmsg = str(e)
|
||||
for line in errmsg.splitlines():
|
||||
log.msg('[EE] %s' % line)
|
||||
#log.err('ERROR: %s' % (errormsg,))
|
||||
# log.err('ERROR: %s' % (errmsg,))
|
||||
log_errmsg = log_err
|
||||
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ def log_warn(warnmsg):
|
|||
warnmsg = str(e)
|
||||
for line in warnmsg.splitlines():
|
||||
log.msg('[WW] %s' % line)
|
||||
#log.msg('WARNING: %s' % (warnmsg,))
|
||||
# log.msg('WARNING: %s' % (warnmsg,))
|
||||
log_warnmsg = log_warn
|
||||
|
||||
|
||||
|
|
@ -152,7 +153,8 @@ log_depmsg = log_dep
|
|||
|
||||
# Arbitrary file logger
|
||||
|
||||
_LOG_FILE_HANDLES = {} # holds open log handles
|
||||
_LOG_FILE_HANDLES = {} # holds open log handles
|
||||
|
||||
|
||||
def _open_log_file(filename):
|
||||
"""
|
||||
|
|
@ -171,7 +173,7 @@ def _open_log_file(filename):
|
|||
return _LOG_FILE_HANDLES[filename]
|
||||
else:
|
||||
try:
|
||||
filehandle = open(filename, "a+") # append mode + reading
|
||||
filehandle = open(filename, "a+") # append mode + reading
|
||||
_LOG_FILE_HANDLES[filename] = filehandle
|
||||
return filehandle
|
||||
except IOError:
|
||||
|
|
@ -184,13 +186,14 @@ def log_file(msg, filename="game.log"):
|
|||
Arbitrary file logger using threads.
|
||||
|
||||
Args:
|
||||
msg (str): String to append to logfile.
|
||||
filename (str, optional): Defaults to 'game.log'. All logs
|
||||
will appear in the logs directory and log entries will start
|
||||
on new lines following datetime info.
|
||||
|
||||
"""
|
||||
def callback(filehandle, msg):
|
||||
"Writing to file and flushing result"
|
||||
"""Writing to file and flushing result"""
|
||||
msg = "\n%s [-] %s" % (timeformat(), msg.strip())
|
||||
filehandle.write(msg)
|
||||
# since we don't close the handle, we need to flush
|
||||
|
|
@ -199,7 +202,7 @@ def log_file(msg, filename="game.log"):
|
|||
filehandle.flush()
|
||||
|
||||
def errback(failure):
|
||||
"Catching errors to normal log"
|
||||
"""Catching errors to normal log"""
|
||||
log_trace()
|
||||
|
||||
# save to server/logs/ directory
|
||||
|
|
@ -230,7 +233,7 @@ def tail_log_file(filename, offset, nlines, callback=None):
|
|||
|
||||
"""
|
||||
def seek_file(filehandle, offset, nlines, callback):
|
||||
"step backwards in chunks and stop only when we have enough lines"
|
||||
"""step backwards in chunks and stop only when we have enough lines"""
|
||||
lines_found = []
|
||||
buffer_size = 4098
|
||||
block_count = -1
|
||||
|
|
@ -254,7 +257,7 @@ def tail_log_file(filename, offset, nlines, callback=None):
|
|||
return lines_found
|
||||
|
||||
def errback(failure):
|
||||
"Catching errors to normal log"
|
||||
"""Catching errors to normal log"""
|
||||
log_trace()
|
||||
|
||||
filehandle = _open_log_file(filename)
|
||||
|
|
|
|||
|
|
@ -65,10 +65,7 @@ else:
|
|||
from cgi import escape
|
||||
|
||||
# hrule styles
|
||||
FRAME = 0
|
||||
ALL = 1
|
||||
NONE = 2
|
||||
HEADER = 3
|
||||
FRAME, ALL, NONE, HEADER = range(4)
|
||||
|
||||
# Table styles
|
||||
DEFAULT = 10
|
||||
|
|
@ -78,12 +75,13 @@ RANDOM = 20
|
|||
|
||||
_re = re.compile("\033\[[0-9;]*m")
|
||||
|
||||
|
||||
def _ansi(method):
|
||||
"decorator for converting ansi in input"
|
||||
"""decorator for converting ansi in input"""
|
||||
def wrapper(self, *args, **kwargs):
|
||||
def convert(inp):
|
||||
if isinstance(inp, basestring):
|
||||
return parse_ansi("{n%s{n" % inp)
|
||||
return parse_ansi("|n%s|n" % inp)
|
||||
elif hasattr(inp, '__iter__'):
|
||||
li = []
|
||||
for element in inp:
|
||||
|
|
@ -96,19 +94,20 @@ def _ansi(method):
|
|||
return li
|
||||
return inp
|
||||
args = [convert(arg) for arg in args]
|
||||
#kwargs = dict((key, convert(val)) for key, val in kwargs.items())
|
||||
# kwargs = dict((key, convert(val)) for key, val in kwargs.items())
|
||||
return method(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def _get_size(text):
|
||||
lines = text.split("\n")
|
||||
height = len(lines)
|
||||
width = max([_str_block_width(line) for line in lines])
|
||||
return (width, height)
|
||||
return width, height
|
||||
|
||||
|
||||
class PrettyTable(object):
|
||||
|
||||
|
||||
@_ansi
|
||||
def __init__(self, field_names=None, **kwargs):
|
||||
|
||||
|
|
@ -153,9 +152,12 @@ class PrettyTable(object):
|
|||
self._widths = []
|
||||
|
||||
# Options
|
||||
self._options = "start end fields header border sortby reversesort sort_key attributes format hrules vrules".split()
|
||||
self._options.extend("int_format float_format padding_width left_padding_width right_padding_width".split())
|
||||
self._options.extend("vertical_char horizontal_char junction_char header_style valign xhtml print_empty".split())
|
||||
self._options = "start end fields header border sortby reversesort" \
|
||||
" sort_key attributes format hrules vrules".split()
|
||||
self._options.extend("int_format float_format padding_width "
|
||||
"left_padding_width right_padding_width".split())
|
||||
self._options.extend("vertical_char horizontal_char junction_char"
|
||||
" header_style valign xhtml print_empty".split())
|
||||
for option in self._options:
|
||||
if option in kwargs:
|
||||
self._validate_option(option, kwargs[option])
|
||||
|
|
@ -263,10 +265,10 @@ class PrettyTable(object):
|
|||
|
||||
if py3k:
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
return self.__unicode__()
|
||||
else:
|
||||
def __str__(self):
|
||||
return self.__unicode__().encode(self.encoding)
|
||||
return self.__unicode__().encode(self.encoding)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.get_string()
|
||||
|
|
@ -283,31 +285,32 @@ class PrettyTable(object):
|
|||
# Secondly, in the _get_options method, where keyword arguments are mixed with persistent settings
|
||||
|
||||
def _validate_option(self, option, val):
|
||||
if option in ("field_names"):
|
||||
if option in "field_names":
|
||||
self._validate_field_names(val)
|
||||
elif option in ("start", "end", "max_width", "padding_width", "left_padding_width", "right_padding_width", "format"):
|
||||
elif option in ("start", "end", "max_width", "padding_width",
|
||||
"left_padding_width", "right_padding_width", "format"):
|
||||
self._validate_nonnegative_int(option, val)
|
||||
elif option in ("sortby"):
|
||||
elif option in "sortby":
|
||||
self._validate_field_name(option, val)
|
||||
elif option in ("sort_key"):
|
||||
elif option in "sort_key":
|
||||
self._validate_function(option, val)
|
||||
elif option in ("hrules"):
|
||||
elif option in "hrules":
|
||||
self._validate_hrules(option, val)
|
||||
elif option in ("vrules"):
|
||||
elif option in "vrules":
|
||||
self._validate_vrules(option, val)
|
||||
elif option in ("fields"):
|
||||
elif option in "fields":
|
||||
self._validate_all_field_names(option, val)
|
||||
elif option in ("header", "border", "reversesort", "xhtml", "print_empty"):
|
||||
self._validate_true_or_false(option, val)
|
||||
elif option in ("header_style"):
|
||||
elif option in "header_style":
|
||||
self._validate_header_style(val)
|
||||
elif option in ("int_format"):
|
||||
elif option in "int_format":
|
||||
self._validate_int_format(option, val)
|
||||
elif option in ("float_format"):
|
||||
elif option in "float_format":
|
||||
self._validate_float_format(option, val)
|
||||
elif option in ("vertical_char", "horizontal_char", "junction_char"):
|
||||
self._validate_single_char(option, val)
|
||||
elif option in ("attributes"):
|
||||
elif option in "attributes":
|
||||
self._validate_attributes(option, val)
|
||||
else:
|
||||
raise Exception("Unrecognised option: %s!" % option)
|
||||
|
|
@ -316,14 +319,16 @@ class PrettyTable(object):
|
|||
# Check for appropriate length
|
||||
if self._field_names:
|
||||
try:
|
||||
assert len(val) == len(self._field_names)
|
||||
assert len(val) == len(self._field_names)
|
||||
except AssertionError:
|
||||
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._field_names)))
|
||||
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)"
|
||||
% (len(val), len(self._field_names)))
|
||||
if self._rows:
|
||||
try:
|
||||
assert len(val) == len(self._rows[0])
|
||||
assert len(val) == len(self._rows[0])
|
||||
except AssertionError:
|
||||
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._rows[0])))
|
||||
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)"
|
||||
% (len(val), len(self._rows[0])))
|
||||
# Check for uniqueness
|
||||
try:
|
||||
assert len(val) == len(set(val))
|
||||
|
|
@ -338,13 +343,13 @@ class PrettyTable(object):
|
|||
|
||||
def _validate_align(self, val):
|
||||
try:
|
||||
assert val in ["l","c","r"]
|
||||
assert val in ["l", "c", "r"]
|
||||
except AssertionError:
|
||||
raise Exception("Alignment %s is invalid, use l, c or r!" % val)
|
||||
|
||||
def _validate_valign(self, val):
|
||||
try:
|
||||
assert val in ["t","m","b",None]
|
||||
assert val in ["t", "m", "b", None]
|
||||
except AssertionError:
|
||||
raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
|
||||
|
||||
|
|
@ -436,6 +441,7 @@ class PrettyTable(object):
|
|||
|
||||
fields - list or tuple of field names"""
|
||||
return self._field_names
|
||||
|
||||
def _set_field_names(self, val):
|
||||
val = [self._unicode(x) for x in val]
|
||||
self._validate_option("field_names", val)
|
||||
|
|
@ -461,30 +467,37 @@ class PrettyTable(object):
|
|||
else:
|
||||
for field in self._field_names:
|
||||
self._valign[field] = "t"
|
||||
|
||||
field_names = property(_get_field_names, _set_field_names)
|
||||
|
||||
def _get_align(self):
|
||||
return self._align
|
||||
|
||||
def _set_align(self, val):
|
||||
self._validate_align(val)
|
||||
for field in self._field_names:
|
||||
self._align[field] = val
|
||||
|
||||
align = property(_get_align, _set_align)
|
||||
|
||||
def _get_valign(self):
|
||||
return self._valign
|
||||
|
||||
def _set_valign(self, val):
|
||||
self._validate_valign(val)
|
||||
for field in self._field_names:
|
||||
self._valign[field] = val
|
||||
|
||||
valign = property(_get_valign, _set_valign)
|
||||
|
||||
def _get_max_width(self):
|
||||
return self._max_width
|
||||
|
||||
def _set_max_width(self, val):
|
||||
self._validate_option("max_width", val)
|
||||
for field in self._field_names:
|
||||
self._max_width[field] = val
|
||||
|
||||
max_width = property(_get_max_width, _set_max_width)
|
||||
|
||||
def _get_fields(self):
|
||||
|
|
@ -494,9 +507,11 @@ class PrettyTable(object):
|
|||
|
||||
fields - list or tuple of field names to include in displays"""
|
||||
return self._fields
|
||||
|
||||
def _set_fields(self, val):
|
||||
self._validate_option("fields", val)
|
||||
self._fields = val
|
||||
|
||||
fields = property(_get_fields, _set_fields)
|
||||
|
||||
def _get_start(self):
|
||||
|
|
@ -510,6 +525,7 @@ class PrettyTable(object):
|
|||
def _set_start(self, val):
|
||||
self._validate_option("start", val)
|
||||
self._start = val
|
||||
|
||||
start = property(_get_start, _set_start)
|
||||
|
||||
def _get_end(self):
|
||||
|
|
@ -519,9 +535,11 @@ class PrettyTable(object):
|
|||
|
||||
end - index of last data row to include in output PLUS ONE (list slice style)"""
|
||||
return self._end
|
||||
|
||||
def _set_end(self, val):
|
||||
self._validate_option("end", val)
|
||||
self._end = val
|
||||
|
||||
end = property(_get_end, _set_end)
|
||||
|
||||
def _get_sortby(self):
|
||||
|
|
@ -531,9 +549,11 @@ class PrettyTable(object):
|
|||
|
||||
sortby - field name to sort by"""
|
||||
return self._sortby
|
||||
|
||||
def _set_sortby(self, val):
|
||||
self._validate_option("sortby", val)
|
||||
self._sortby = val
|
||||
|
||||
sortby = property(_get_sortby, _set_sortby)
|
||||
|
||||
def _get_reversesort(self):
|
||||
|
|
@ -543,9 +563,11 @@ class PrettyTable(object):
|
|||
|
||||
reveresort - set to True to sort by descending order, or False to sort by ascending order"""
|
||||
return self._reversesort
|
||||
|
||||
def _set_reversesort(self, val):
|
||||
self._validate_option("reversesort", val)
|
||||
self._reversesort = val
|
||||
|
||||
reversesort = property(_get_reversesort, _set_reversesort)
|
||||
|
||||
def _get_sort_key(self):
|
||||
|
|
@ -555,9 +577,11 @@ class PrettyTable(object):
|
|||
|
||||
sort_key - a function which takes one argument and returns something to be sorted"""
|
||||
return self._sort_key
|
||||
|
||||
def _set_sort_key(self, val):
|
||||
self._validate_option("sort_key", val)
|
||||
self._sort_key = val
|
||||
|
||||
sort_key = property(_get_sort_key, _set_sort_key)
|
||||
|
||||
def _get_header(self):
|
||||
|
|
@ -567,9 +591,11 @@ class PrettyTable(object):
|
|||
|
||||
header - print a header showing field names (True or False)"""
|
||||
return self._header
|
||||
|
||||
def _set_header(self, val):
|
||||
self._validate_option("header", val)
|
||||
self._header = val
|
||||
|
||||
header = property(_get_header, _set_header)
|
||||
|
||||
def _get_header_style(self):
|
||||
|
|
@ -579,9 +605,11 @@ class PrettyTable(object):
|
|||
|
||||
header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)"""
|
||||
return self._header_style
|
||||
|
||||
def _set_header_style(self, val):
|
||||
self._validate_header_style(val)
|
||||
self._header_style = val
|
||||
|
||||
header_style = property(_get_header_style, _set_header_style)
|
||||
|
||||
def _get_border(self):
|
||||
|
|
@ -591,9 +619,11 @@ class PrettyTable(object):
|
|||
|
||||
border - print a border around the table (True or False)"""
|
||||
return self._border
|
||||
|
||||
def _set_border(self, val):
|
||||
self._validate_option("border", val)
|
||||
self._border = val
|
||||
|
||||
border = property(_get_border, _set_border)
|
||||
|
||||
def _get_hrules(self):
|
||||
|
|
@ -603,9 +633,11 @@ class PrettyTable(object):
|
|||
|
||||
hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE"""
|
||||
return self._hrules
|
||||
|
||||
def _set_hrules(self, val):
|
||||
self._validate_option("hrules", val)
|
||||
self._hrules = val
|
||||
|
||||
hrules = property(_get_hrules, _set_hrules)
|
||||
|
||||
def _get_vrules(self):
|
||||
|
|
@ -615,9 +647,11 @@ class PrettyTable(object):
|
|||
|
||||
vrules - vertical rules style. Allowed values: FRAME, ALL, NONE"""
|
||||
return self._vrules
|
||||
|
||||
def _set_vrules(self, val):
|
||||
self._validate_option("vrules", val)
|
||||
self._vrules = val
|
||||
|
||||
vrules = property(_get_vrules, _set_vrules)
|
||||
|
||||
def _get_int_format(self):
|
||||
|
|
@ -626,10 +660,12 @@ class PrettyTable(object):
|
|||
|
||||
int_format - integer format string"""
|
||||
return self._int_format
|
||||
|
||||
def _set_int_format(self, val):
|
||||
# self._validate_option("int_format", val)
|
||||
# self._validate_option("int_format", val)
|
||||
for field in self._field_names:
|
||||
self._int_format[field] = val
|
||||
|
||||
int_format = property(_get_int_format, _set_int_format)
|
||||
|
||||
def _get_float_format(self):
|
||||
|
|
@ -638,10 +674,12 @@ class PrettyTable(object):
|
|||
|
||||
float_format - floating point format string"""
|
||||
return self._float_format
|
||||
|
||||
def _set_float_format(self, val):
|
||||
# self._validate_option("float_format", val)
|
||||
# self._validate_option("float_format", val)
|
||||
for field in self._field_names:
|
||||
self._float_format[field] = val
|
||||
|
||||
float_format = property(_get_float_format, _set_float_format)
|
||||
|
||||
def _get_padding_width(self):
|
||||
|
|
@ -651,9 +689,11 @@ class PrettyTable(object):
|
|||
|
||||
padding_width - number of spaces, must be a positive integer"""
|
||||
return self._padding_width
|
||||
|
||||
def _set_padding_width(self, val):
|
||||
self._validate_option("padding_width", val)
|
||||
self._padding_width = val
|
||||
|
||||
padding_width = property(_get_padding_width, _set_padding_width)
|
||||
|
||||
def _get_left_padding_width(self):
|
||||
|
|
@ -663,9 +703,11 @@ class PrettyTable(object):
|
|||
|
||||
left_padding - number of spaces, must be a positive integer"""
|
||||
return self._left_padding_width
|
||||
|
||||
def _set_left_padding_width(self, val):
|
||||
self._validate_option("left_padding_width", val)
|
||||
self._left_padding_width = val
|
||||
|
||||
left_padding_width = property(_get_left_padding_width, _set_left_padding_width)
|
||||
|
||||
def _get_right_padding_width(self):
|
||||
|
|
@ -675,9 +717,11 @@ class PrettyTable(object):
|
|||
|
||||
right_padding - number of spaces, must be a positive integer"""
|
||||
return self._right_padding_width
|
||||
|
||||
def _set_right_padding_width(self, val):
|
||||
self._validate_option("right_padding_width", val)
|
||||
self._right_padding_width = val
|
||||
|
||||
right_padding_width = property(_get_right_padding_width, _set_right_padding_width)
|
||||
|
||||
def _get_vertical_char(self):
|
||||
|
|
@ -687,10 +731,12 @@ class PrettyTable(object):
|
|||
|
||||
vertical_char - single character string used to draw vertical lines"""
|
||||
return self._vertical_char
|
||||
|
||||
def _set_vertical_char(self, val):
|
||||
val = self._unicode(val)
|
||||
self._validate_option("vertical_char", val)
|
||||
self._vertical_char = val
|
||||
|
||||
vertical_char = property(_get_vertical_char, _set_vertical_char)
|
||||
|
||||
def _get_horizontal_char(self):
|
||||
|
|
@ -700,10 +746,12 @@ class PrettyTable(object):
|
|||
|
||||
horizontal_char - single character string used to draw horizontal lines"""
|
||||
return self._horizontal_char
|
||||
|
||||
def _set_horizontal_char(self, val):
|
||||
val = self._unicode(val)
|
||||
self._validate_option("horizontal_char", val)
|
||||
self._horizontal_char = val
|
||||
|
||||
horizontal_char = property(_get_horizontal_char, _set_horizontal_char)
|
||||
|
||||
def _get_junction_char(self):
|
||||
|
|
@ -713,10 +761,12 @@ class PrettyTable(object):
|
|||
|
||||
junction_char - single character string used to draw line junctions"""
|
||||
return self._junction_char
|
||||
|
||||
def _set_junction_char(self, val):
|
||||
val = self._unicode(val)
|
||||
self._validate_option("vertical_char", val)
|
||||
self._junction_char = val
|
||||
|
||||
junction_char = property(_get_junction_char, _set_junction_char)
|
||||
|
||||
def _get_format(self):
|
||||
|
|
@ -726,9 +776,11 @@ class PrettyTable(object):
|
|||
|
||||
format - True or False"""
|
||||
return self._format
|
||||
|
||||
def _set_format(self, val):
|
||||
self._validate_option("format", val)
|
||||
self._format = val
|
||||
|
||||
format = property(_get_format, _set_format)
|
||||
|
||||
def _get_print_empty(self):
|
||||
|
|
@ -738,9 +790,11 @@ class PrettyTable(object):
|
|||
|
||||
print_empty - True or False"""
|
||||
return self._print_empty
|
||||
|
||||
def _set_print_empty(self, val):
|
||||
self._validate_option("print_empty", val)
|
||||
self._print_empty = val
|
||||
|
||||
print_empty = property(_get_print_empty, _set_print_empty)
|
||||
|
||||
def _get_attributes(self):
|
||||
|
|
@ -750,9 +804,11 @@ class PrettyTable(object):
|
|||
|
||||
attributes - dictionary of attributes"""
|
||||
return self._attributes
|
||||
|
||||
def _set_attributes(self, val):
|
||||
self._validate_option("attributes", val)
|
||||
self._attributes = val
|
||||
|
||||
attributes = property(_get_attributes, _set_attributes)
|
||||
|
||||
##############################
|
||||
|
|
@ -825,8 +881,8 @@ class PrettyTable(object):
|
|||
self.border = random.choice((True, False))
|
||||
self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
|
||||
self._vrules = random.choice((ALL, FRAME, NONE))
|
||||
self.left_padding_width = random.randint(0,5)
|
||||
self.right_padding_width = random.randint(0,5)
|
||||
self.left_padding_width = random.randint(0, 5)
|
||||
self.right_padding_width = random.randint(0, 5)
|
||||
self.vertical_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
|
||||
self.horizontal_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
|
||||
self.junction_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
|
||||
|
|
@ -846,9 +902,10 @@ class PrettyTable(object):
|
|||
has fields"""
|
||||
|
||||
if self._field_names and len(row) != len(self._field_names):
|
||||
raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" %(len(row),len(self._field_names)))
|
||||
raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)"
|
||||
% (len(row), len(self._field_names)))
|
||||
if not self._field_names:
|
||||
self.field_names = [("Field %d" % (n+1)) for n in range(0,len(row))]
|
||||
self.field_names = [("Field %d" % (n+1)) for n in range(0, len(row))]
|
||||
self._rows.append(list(row))
|
||||
|
||||
def del_row(self, row_index):
|
||||
|
|
@ -860,7 +917,7 @@ class PrettyTable(object):
|
|||
row_index - The index of the row you want to delete. Indexing starts at 0."""
|
||||
|
||||
if row_index > len(self._rows)-1:
|
||||
raise Exception("Cant delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
|
||||
raise Exception("Can't delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
|
||||
del self._rows[row_index]
|
||||
|
||||
@_ansi
|
||||
|
|
@ -1113,7 +1170,7 @@ class PrettyTable(object):
|
|||
|
||||
def _stringify_row(self, row, options):
|
||||
|
||||
for index, field, value, width, in zip(range(0,len(row)), self._field_names, row, self._widths):
|
||||
for index, field, value, width, in zip(range(0, len(row)), self._field_names, row, self._widths):
|
||||
# Enforce max widths
|
||||
lines = value.split("\n")
|
||||
new_lines = []
|
||||
|
|
@ -1145,14 +1202,14 @@ class PrettyTable(object):
|
|||
|
||||
valign = self._valign[field]
|
||||
lines = value.split("\n")
|
||||
dHeight = row_height - len(lines)
|
||||
if dHeight:
|
||||
dheight = row_height - len(lines)
|
||||
if dheight:
|
||||
if valign == "m":
|
||||
lines = [""] * (dHeight // 2) + lines + [""] * (dHeight - (dHeight // 2))
|
||||
lines = [""] * (dheight // 2) + lines + [""] * (dheight - (dheight // 2))
|
||||
elif valign == "b":
|
||||
lines = [""] * dHeight + lines
|
||||
lines = [""] * dheight + lines
|
||||
else:
|
||||
lines = lines + [""] * dHeight
|
||||
lines += [""] * dheight
|
||||
|
||||
y = 0
|
||||
for l in lines:
|
||||
|
|
@ -1174,7 +1231,7 @@ class PrettyTable(object):
|
|||
bits[y].pop()
|
||||
bits[y].append(options["vertical_char"])
|
||||
|
||||
if options["border"] and options["hrules"]== ALL:
|
||||
if options["border"] and options["hrules"] == ALL:
|
||||
bits[row_height-1].append("\n")
|
||||
bits[row_height-1].append(self._hrule)
|
||||
|
||||
|
|
@ -1227,8 +1284,7 @@ class PrettyTable(object):
|
|||
else:
|
||||
linebreak = "<br>"
|
||||
|
||||
open_tag = []
|
||||
open_tag.append("<table")
|
||||
open_tag = ["<table"]
|
||||
if options["attributes"]:
|
||||
for attr_name in options["attributes"]:
|
||||
open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
|
||||
|
|
@ -1268,8 +1324,7 @@ class PrettyTable(object):
|
|||
else:
|
||||
linebreak = "<br>"
|
||||
|
||||
open_tag = []
|
||||
open_tag.append("<table")
|
||||
open_tag = ["<table"]
|
||||
if options["border"]:
|
||||
if options["hrules"] == ALL and options["vrules"] == ALL:
|
||||
open_tag.append(" frame=\"box\" rules=\"all\"")
|
||||
|
|
@ -1297,7 +1352,9 @@ class PrettyTable(object):
|
|||
for field in self._field_names:
|
||||
if options["fields"] and field not in options["fields"]:
|
||||
continue
|
||||
lines.append(" <th style=\"padding-left: %dem; padding-right: %dem; text-align: center\">%s</th>" % (lpad, rpad, escape(field).replace("\n", linebreak)))
|
||||
lines.append(" <th style=\"padding-left: %dem; padding-right: "
|
||||
"%dem; text-align: center\">%s</th>"
|
||||
% (lpad, rpad, escape(field).replace("\n", linebreak)))
|
||||
lines.append(" </tr>")
|
||||
|
||||
# Data
|
||||
|
|
@ -1306,14 +1363,16 @@ class PrettyTable(object):
|
|||
aligns = []
|
||||
valigns = []
|
||||
for field in self._field_names:
|
||||
aligns.append({ "l" : "left", "r" : "right", "c" : "center" }[self._align[field]])
|
||||
valigns.append({"t" : "top", "m" : "middle", "b" : "bottom"}[self._valign[field]])
|
||||
aligns.append(dict(l="left", r="right", c="center")[self._align[field]])
|
||||
valigns.append(dict(t="top", m="middle", b="bottom")[self._valign[field]])
|
||||
for row in formatted_rows:
|
||||
lines.append(" <tr>")
|
||||
for field, datum, align, valign in zip(self._field_names, row, aligns, valigns):
|
||||
if options["fields"] and field not in options["fields"]:
|
||||
continue
|
||||
lines.append(" <td style=\"padding-left: %dem; padding-right: %dem; text-align: %s; vertical-align: %s\">%s</td>" % (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak)))
|
||||
lines.append(" <td style=\"padding-left: %dem; padding-right: %dem; "
|
||||
"text-align: %s; vertical-align: %s\">%s</td>"
|
||||
% (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak)))
|
||||
lines.append(" </tr>")
|
||||
lines.append("</table>")
|
||||
|
||||
|
|
@ -1323,10 +1382,11 @@ class PrettyTable(object):
|
|||
# UNICODE WIDTH FUNCTIONS #
|
||||
##############################
|
||||
|
||||
|
||||
def _char_block_width(char):
|
||||
# Basic Latin, which is probably the most common case
|
||||
#if char in xrange(0x0021, 0x007e):
|
||||
#if char >= 0x0021 and char <= 0x007e:
|
||||
# if char in xrange(0x0021, 0x007e):
|
||||
# if char >= 0x0021 and char <= 0x007e:
|
||||
if 0x0021 <= char <= 0x007e:
|
||||
return 1
|
||||
# Chinese, Japanese, Korean (common)
|
||||
|
|
@ -1356,6 +1416,7 @@ def _char_block_width(char):
|
|||
# Take a guess
|
||||
return 1
|
||||
|
||||
|
||||
def _str_block_width(val):
|
||||
|
||||
return sum(itermap(_char_block_width, itermap(ord, _re.sub("", val))))
|
||||
|
|
@ -1364,7 +1425,8 @@ def _str_block_width(val):
|
|||
# TABLE FACTORIES #
|
||||
##############################
|
||||
|
||||
def from_csv(fp, field_names = None, **kwargs):
|
||||
|
||||
def from_csv(fp, field_names=None, **kwargs):
|
||||
|
||||
dialect = csv.Sniffer().sniff(fp.read(1024))
|
||||
fp.seek(0)
|
||||
|
|
@ -1381,6 +1443,7 @@ def from_csv(fp, field_names = None, **kwargs):
|
|||
|
||||
return table
|
||||
|
||||
|
||||
def from_db_cursor(cursor, **kwargs):
|
||||
|
||||
if cursor.description:
|
||||
|
|
@ -1390,6 +1453,7 @@ def from_db_cursor(cursor, **kwargs):
|
|||
table.add_row(row)
|
||||
return table
|
||||
|
||||
|
||||
class TableHandler(HTMLParser):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
|
@ -1403,12 +1467,12 @@ class TableHandler(HTMLParser):
|
|||
self.last_content = ""
|
||||
self.is_last_row_header = False
|
||||
|
||||
def handle_starttag(self,tag, attrs):
|
||||
def handle_starttag(self, tag, attrs):
|
||||
self.active = tag
|
||||
if tag == "th":
|
||||
self.is_last_row_header = True
|
||||
|
||||
def handle_endtag(self,tag):
|
||||
def handle_endtag(self, tag):
|
||||
if tag in ["th", "td"]:
|
||||
stripped_content = self.last_content.strip()
|
||||
self.last_row.append(stripped_content)
|
||||
|
|
@ -1425,7 +1489,6 @@ class TableHandler(HTMLParser):
|
|||
self.last_content = " "
|
||||
self.active = None
|
||||
|
||||
|
||||
def handle_data(self, data):
|
||||
self.last_content += data
|
||||
|
||||
|
|
@ -1437,10 +1500,10 @@ class TableHandler(HTMLParser):
|
|||
for row in self.rows:
|
||||
if len(row[0]) < self.max_row_width:
|
||||
appends = self.max_row_width - len(row[0])
|
||||
for i in range(1,appends):
|
||||
for _ in range(1, appends):
|
||||
row[0].append("-")
|
||||
|
||||
if row[1] == True:
|
||||
if row[1] is True:
|
||||
self.make_fields_unique(row[0])
|
||||
table.field_names = row[0]
|
||||
else:
|
||||
|
|
@ -1456,6 +1519,7 @@ class TableHandler(HTMLParser):
|
|||
if fields[i] == fields[j]:
|
||||
fields[j] += "'"
|
||||
|
||||
|
||||
def from_html(html_code, **kwargs):
|
||||
"""
|
||||
Generates a list of PrettyTables from a string of HTML code. Each <table> in
|
||||
|
|
@ -1466,6 +1530,7 @@ def from_html(html_code, **kwargs):
|
|||
parser.feed(html_code)
|
||||
return parser.tables
|
||||
|
||||
|
||||
def from_html_one(html_code, **kwargs):
|
||||
"""
|
||||
Generates a PrettyTables from a string of HTML code which contains only a
|
||||
|
|
@ -1483,6 +1548,7 @@ def from_html_one(html_code, **kwargs):
|
|||
# MAIN (TEST FUNCTION) #
|
||||
##############################
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
|
||||
|
|
@ -1490,7 +1556,7 @@ def main():
|
|||
x.reversesort = True
|
||||
x.int_format["Area"] = "04d"
|
||||
x.float_format = "6.1f"
|
||||
x.align["City name"] = "l" # Left align city names
|
||||
x.align["City name"] = "l" # Left align city names
|
||||
x.add_row(["Adelaide", 1295, 1158259, 600.5])
|
||||
x.add_row(["Brisbane", 5905, 1857594, 1146.4])
|
||||
x.add_row(["Darwin", 112, 120900, 1714.7])
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_c
|
|||
Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
|
||||
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
# Search manager-wrappers
|
||||
#------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
#
|
||||
# Search objects as a character
|
||||
|
|
@ -199,10 +199,16 @@ help_entries = search_help
|
|||
|
||||
def search_object_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
return ObjectDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
|
||||
|
||||
|
||||
def search_player_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
return PlayerDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
|
||||
|
||||
|
||||
def search_script_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
return ScriptDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
|
||||
|
||||
|
||||
def search_channel_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
return Channel.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
|
||||
|
||||
|
|
@ -218,6 +224,8 @@ search_attribute_object = ObjectDB.objects.get_attribute
|
|||
|
||||
# Note that this returns the object attached to the tag, not the tag
|
||||
# object itself (this is usually what you want)
|
||||
|
||||
|
||||
def search_object_by_tag(key=None, category=None):
|
||||
"""
|
||||
Find object based on tag or category.
|
||||
|
|
@ -235,7 +243,9 @@ def search_object_by_tag(key=None, category=None):
|
|||
|
||||
"""
|
||||
return ObjectDB.objects.get_by_tag(key=key, category=category)
|
||||
search_tag = search_object_by_tag # this is the most common case
|
||||
search_tag = search_object_by_tag # this is the most common case
|
||||
|
||||
|
||||
def search_player_tag(key=None, category=None):
|
||||
"""
|
||||
Find player based on tag or category.
|
||||
|
|
@ -253,6 +263,8 @@ def search_player_tag(key=None, category=None):
|
|||
|
||||
"""
|
||||
return PlayerDB.objects.get_by_tag(key=key, category=category)
|
||||
|
||||
|
||||
def search_script_tag(key=None, category=None):
|
||||
"""
|
||||
Find script based on tag or category.
|
||||
|
|
@ -270,6 +282,8 @@ def search_script_tag(key=None, category=None):
|
|||
|
||||
"""
|
||||
return ScriptDB.objects.get_by_tag(key=key, category=category)
|
||||
|
||||
|
||||
def search_channel_tag(key=None, category=None):
|
||||
"""
|
||||
Find channel based on tag or category.
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@ many traits with a normal *goblin*.
|
|||
from __future__ import print_function
|
||||
|
||||
import copy
|
||||
#TODO
|
||||
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
#os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
# TODO
|
||||
# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
# os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
|
||||
|
||||
from django.conf import settings
|
||||
from random import randint
|
||||
|
|
@ -131,9 +131,10 @@ def _get_prototype(dic, prot, protparents):
|
|||
new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents)
|
||||
prot.update(new_prot)
|
||||
prot.update(dic)
|
||||
prot.pop("prototype", None) # we don't need this anymore
|
||||
prot.pop("prototype", None) # we don't need this anymore
|
||||
return prot
|
||||
|
||||
|
||||
def _batch_create_object(*objparams):
|
||||
"""
|
||||
This is a cut-down version of the create_object() function,
|
||||
|
|
@ -141,7 +142,7 @@ def _batch_create_object(*objparams):
|
|||
so make sure the spawned Typeclass works before using this!
|
||||
|
||||
Args:
|
||||
objsparams (any): Aach argument should be a tuple of arguments
|
||||
objsparams (any): Each argument should be a tuple of arguments
|
||||
for the respective creation/add handlers in the following
|
||||
order: (create, permissions, locks, aliases, nattributes,
|
||||
attributes)
|
||||
|
|
@ -153,8 +154,9 @@ def _batch_create_object(*objparams):
|
|||
# bulk create all objects in one go
|
||||
|
||||
# unfortunately this doesn't work since bulk_create doesn't creates pks;
|
||||
# the result are double objects at the next stage
|
||||
#dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
|
||||
# the result would be duplicate objects at the next stage, so we comment
|
||||
# it out for now:
|
||||
# dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
|
||||
|
||||
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
|
||||
objs = []
|
||||
|
|
@ -167,7 +169,7 @@ def _batch_create_object(*objparams):
|
|||
"aliases": objparam[3],
|
||||
"nattributes": objparam[4],
|
||||
"attributes": objparam[5],
|
||||
"tags":objparam[6]}
|
||||
"tags": objparam[6]}
|
||||
# this triggers all hooks
|
||||
obj.save()
|
||||
# run eventual extra code
|
||||
|
|
@ -201,9 +203,9 @@ def spawn(*prototypes, **kwargs):
|
|||
if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"):
|
||||
protmodules = make_iter(settings.PROTOTYPE_MODULES)
|
||||
for prototype_module in protmodules:
|
||||
protparents.update(dict((key, val)
|
||||
for key, val in all_from_module(prototype_module).items() if isinstance(val, dict)))
|
||||
#overload module's protparents with specifically given protparents
|
||||
protparents.update(dict((key, val) for key, val in
|
||||
all_from_module(prototype_module).items() if isinstance(val, dict)))
|
||||
# overload module's protparents with specifically given protparents
|
||||
protparents.update(kwargs.get("prototype_parents", {}))
|
||||
for key, prototype in protparents.items():
|
||||
_validate_prototype(key, prototype, protparents, [])
|
||||
|
|
@ -223,10 +225,10 @@ def spawn(*prototypes, **kwargs):
|
|||
# extract the keyword args we need to create the object itself. If we get a callable,
|
||||
# call that to get the value (don't catch errors)
|
||||
create_kwargs = {}
|
||||
keyval = prot.pop("key", "Spawned Object %06i" % randint(1,100000))
|
||||
keyval = prot.pop("key", "Spawned Object %06i" % randint(1, 100000))
|
||||
create_kwargs["db_key"] = keyval() if callable(keyval) else keyval
|
||||
|
||||
locval = prot.pop("location", None)
|
||||
locval = prot.pop("location", None)
|
||||
create_kwargs["db_location"] = locval() if callable(locval) else _handle_dbref(locval)
|
||||
|
||||
homval = prot.pop("home", settings.DEFAULT_HOME)
|
||||
|
|
@ -244,7 +246,7 @@ def spawn(*prototypes, **kwargs):
|
|||
lockval = prot.pop("locks", "")
|
||||
lock_string = lockval() if callable(lockval) else lockval
|
||||
aliasval = prot.pop("aliases", "")
|
||||
alias_string = aliasval() if callable(aliasval) else aliasval
|
||||
alias_string = aliasval() if callable(aliasval) else aliasval
|
||||
tagval = prot.pop("tags", "")
|
||||
tags = tagval() if callable(tagval) else tagval
|
||||
exval = prot.pop("exec", "")
|
||||
|
|
@ -252,16 +254,16 @@ def spawn(*prototypes, **kwargs):
|
|||
|
||||
# extract ndb assignments
|
||||
nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value)
|
||||
for key, value in prot.items() if key.startswith("ndb_"))
|
||||
for key, value in prot.items() if key.startswith("ndb_"))
|
||||
|
||||
# the rest are attributes
|
||||
attributes = dict((key, value() if callable(value) else value)
|
||||
for key, value in prot.items()
|
||||
if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_")))
|
||||
for key, value in prot.items()
|
||||
if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_")))
|
||||
|
||||
# pack for call into _batch_create_object
|
||||
objsparams.append( (create_kwargs, permission_string, lock_string,
|
||||
alias_string, nattributes, attributes, tags, execs) )
|
||||
objsparams.append((create_kwargs, permission_string, lock_string,
|
||||
alias_string, nattributes, attributes, tags, execs))
|
||||
|
||||
return _batch_create_object(*objsparams)
|
||||
|
||||
|
|
@ -271,33 +273,35 @@ if __name__ == "__main__":
|
|||
|
||||
protparents = {
|
||||
"NOBODY": {},
|
||||
#"INFINITE" : {
|
||||
# "prototype":"INFINITE"
|
||||
#},
|
||||
"GOBLIN" : {
|
||||
# "INFINITE" : {
|
||||
# "prototype":"INFINITE"
|
||||
# },
|
||||
"GOBLIN": {
|
||||
"key": "goblin grunt",
|
||||
"health": lambda: randint(20,30),
|
||||
"health": lambda: randint(20, 30),
|
||||
"resists": ["cold", "poison"],
|
||||
"attacks": ["fists"],
|
||||
"weaknesses": ["fire", "light"]
|
||||
},
|
||||
"GOBLIN_WIZARD" : {
|
||||
"GOBLIN_WIZARD": {
|
||||
"prototype": "GOBLIN",
|
||||
"key": "goblin wizard",
|
||||
"spells": ["fire ball", "lighting bolt"]
|
||||
},
|
||||
"GOBLIN_ARCHER" : {
|
||||
"GOBLIN_ARCHER": {
|
||||
"prototype": "GOBLIN",
|
||||
"key": "goblin archer",
|
||||
"attacks": ["short bow"]
|
||||
},
|
||||
"ARCHWIZARD" : {
|
||||
"ARCHWIZARD": {
|
||||
"attacks": ["archwizard staff"],
|
||||
},
|
||||
"GOBLIN_ARCHWIZARD" : {
|
||||
"GOBLIN_ARCHWIZARD": {
|
||||
"key": "goblin archwizard",
|
||||
"prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD")
|
||||
"prototype": ("GOBLIN_WIZARD", "ARCHWIZARD")
|
||||
}
|
||||
}
|
||||
# test
|
||||
print([o.key for o in spawn(protparents["GOBLIN"], protparents["GOBLIN_ARCHWIZARD"], prototype_parents=protparents)])
|
||||
print([o.key for o in spawn(protparents["GOBLIN"],
|
||||
protparents["GOBLIN_ARCHWIZARD"],
|
||||
prototype_parents=protparents)])
|
||||
|
|
|
|||
|
|
@ -31,13 +31,11 @@ class ANSIStringTestCase(TestCase):
|
|||
"""
|
||||
Make sure the ANSIString is always constructed correctly.
|
||||
"""
|
||||
clean = u'This isA{r testTest'
|
||||
encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA{r test\x1b[0mTest\x1b[0m'
|
||||
target = ANSIString(r'{gThis is{rA{{r test{nTest{n')
|
||||
char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31,
|
||||
32, 37, 38, 39, 40]
|
||||
code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 24, 33, 34, 35, 36, 41, 42, 43, 44]
|
||||
clean = u'This isA|r testTest'
|
||||
encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA|r test\x1b[0mTest\x1b[0m'
|
||||
target = ANSIString(r'|gThis is|rA||r test|nTest|n')
|
||||
char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 37, 38, 39, 40]
|
||||
code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, 23, 24, 33, 34, 35, 36, 41, 42, 43, 44]
|
||||
self.checker(target, encoded, clean)
|
||||
self.table_check(target, char_table, code_table)
|
||||
self.checker(ANSIString(target), encoded, clean)
|
||||
|
|
@ -54,7 +52,7 @@ class ANSIStringTestCase(TestCase):
|
|||
Verifies that slicing an ANSIString results in expected color code
|
||||
distribution.
|
||||
"""
|
||||
target = ANSIString(r'{gTest{rTest{n')
|
||||
target = ANSIString(r'|gTest|rTest|n')
|
||||
result = target[:3]
|
||||
self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes')
|
||||
result = target[:4]
|
||||
|
|
@ -80,7 +78,7 @@ class ANSIStringTestCase(TestCase):
|
|||
Verifies that re.split and .split behave similarly and that color
|
||||
codes end up where they should.
|
||||
"""
|
||||
target = ANSIString("{gThis is {nA split string{g")
|
||||
target = ANSIString("|gThis is |nA split string|g")
|
||||
first = (u'\x1b[1m\x1b[32mThis is \x1b[0m', u'This is ')
|
||||
second = (u'\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m',
|
||||
u' split string')
|
||||
|
|
@ -96,9 +94,9 @@ class ANSIStringTestCase(TestCase):
|
|||
Verify that joining a set of ANSIStrings works.
|
||||
"""
|
||||
# This isn't the desired behavior, but the expected one. Python
|
||||
# concatinates the in-memory representation with the built-in string's
|
||||
# concatenates the in-memory representation with the built-in string's
|
||||
# join.
|
||||
l = [ANSIString("{gTest{r") for s in range(0, 3)]
|
||||
l = [ANSIString("|gTest|r") for _ in range(0, 3)]
|
||||
# Force the generator to be evaluated.
|
||||
result = "".join(l)
|
||||
self.assertEqual(unicode(result), u'TestTestTest')
|
||||
|
|
@ -112,14 +110,14 @@ class ANSIStringTestCase(TestCase):
|
|||
Make sure that length reporting on ANSIStrings does not include
|
||||
ANSI codes.
|
||||
"""
|
||||
self.assertEqual(len(ANSIString('{gTest{n')), 4)
|
||||
self.assertEqual(len(ANSIString('|gTest|n')), 4)
|
||||
|
||||
def test_capitalize(self):
|
||||
"""
|
||||
Make sure that capitalization works. This is the simplest of the
|
||||
_transform functions.
|
||||
"""
|
||||
target = ANSIString('{gtest{n')
|
||||
target = ANSIString('|gtest|n')
|
||||
result = u'\x1b[1m\x1b[32mTest\x1b[0m'
|
||||
self.checker(target.capitalize(), result, u'Test')
|
||||
|
||||
|
|
@ -127,8 +125,8 @@ class ANSIStringTestCase(TestCase):
|
|||
"""
|
||||
Make sure MXP tags are not treated like ANSI codes, but normal text.
|
||||
"""
|
||||
mxp1 = "{lclook{ltat{le"
|
||||
mxp2 = "Start to {lclook here{ltclick somewhere here{le first"
|
||||
mxp1 = "|lclook|ltat|le"
|
||||
mxp2 = "Start to |lclook here|ltclick somewhere here|le first"
|
||||
self.assertEqual(15, len(ANSIString(mxp1)))
|
||||
self.assertEqual(53, len(ANSIString(mxp2)))
|
||||
# These would indicate an issue with the tables.
|
||||
|
|
@ -139,17 +137,15 @@ class ANSIStringTestCase(TestCase):
|
|||
|
||||
def test_add(self):
|
||||
"""
|
||||
Verify concatination works correctly.
|
||||
Verify concatenation works correctly.
|
||||
"""
|
||||
a = ANSIString("{gTest")
|
||||
b = ANSIString("{cString{n")
|
||||
a = ANSIString("|gTest")
|
||||
b = ANSIString("|cString|n")
|
||||
c = a + b
|
||||
result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m'
|
||||
self.checker(c, result, u'TestString')
|
||||
char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27]
|
||||
code_table = [
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31
|
||||
]
|
||||
code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31]
|
||||
self.table_check(c, char_table, code_table)
|
||||
|
||||
def test_strip(self):
|
||||
|
|
@ -166,7 +162,7 @@ class ANSIStringTestCase(TestCase):
|
|||
|
||||
class TestIsIter(TestCase):
|
||||
def test_is_iter(self):
|
||||
self.assertEqual(True, utils.is_iter([1,2,3,4]))
|
||||
self.assertEqual(True, utils.is_iter([1, 2, 3, 4]))
|
||||
self.assertEqual(False, utils.is_iter("This is not an iterable"))
|
||||
|
||||
|
||||
|
|
@ -213,10 +209,10 @@ class TestListToString(TestCase):
|
|||
[1,2,3] -> '"1", "2" and "3"'
|
||||
"""
|
||||
def test_list_to_string(self):
|
||||
self.assertEqual('1, 2, 3', utils.list_to_string([1,2,3], endsep=""))
|
||||
self.assertEqual('"1", "2", "3"', utils.list_to_string([1,2,3], endsep="", addquote=True))
|
||||
self.assertEqual('1, 2 and 3', utils.list_to_string([1,2,3]))
|
||||
self.assertEqual('"1", "2" and "3"', utils.list_to_string([1,2,3], endsep="and", addquote=True))
|
||||
self.assertEqual('1, 2, 3', utils.list_to_string([1, 2, 3], endsep=""))
|
||||
self.assertEqual('"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep="", addquote=True))
|
||||
self.assertEqual('1, 2 and 3', utils.list_to_string([1, 2, 3]))
|
||||
self.assertEqual('"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True))
|
||||
|
||||
|
||||
class TestMLen(TestCase):
|
||||
|
|
@ -231,10 +227,10 @@ class TestMLen(TestCase):
|
|||
self.assertEqual(utils.m_len('|lclook|ltat|le'), 2)
|
||||
|
||||
def test_mxp_ansi_string(self):
|
||||
self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le{n')), 2)
|
||||
self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le|n')), 2)
|
||||
|
||||
def test_non_mxp_ansi_string(self):
|
||||
self.assertEqual(utils.m_len(ANSIString('{gHello{n')), 5)
|
||||
self.assertEqual(utils.m_len(ANSIString('{gHello{n')), 5) # TODO - cause this to fail by default.
|
||||
self.assertEqual(utils.m_len(ANSIString('|gHello|n')), 5)
|
||||
|
||||
def test_list(self):
|
||||
|
|
@ -246,6 +242,7 @@ class TestMLen(TestCase):
|
|||
|
||||
from .text2html import TextToHTMLparser
|
||||
|
||||
|
||||
class TestTextToHTMLparser(TestCase):
|
||||
def setUp(self):
|
||||
self.parser = TextToHTMLparser()
|
||||
|
|
@ -255,71 +252,78 @@ class TestTextToHTMLparser(TestCase):
|
|||
|
||||
def test_url_scheme_ftp(self):
|
||||
self.assertEqual(self.parser.convert_urls('ftp.example.com'),
|
||||
'<a href="ftp.example.com" target="_blank">ftp.example.com</a>')
|
||||
'<a href="ftp.example.com" target="_blank">ftp.example.com</a>')
|
||||
|
||||
def test_url_scheme_www(self):
|
||||
self.assertEqual(self.parser.convert_urls('www.example.com'),
|
||||
'<a href="www.example.com" target="_blank">www.example.com</a>')
|
||||
'<a href="www.example.com" target="_blank">www.example.com</a>')
|
||||
|
||||
def test_url_scheme_ftpproto(self):
|
||||
self.assertEqual(self.parser.convert_urls('ftp://ftp.example.com'),
|
||||
'<a href="ftp://ftp.example.com" target="_blank">ftp://ftp.example.com</a>')
|
||||
'<a href="ftp://ftp.example.com" target="_blank">ftp://ftp.example.com</a>')
|
||||
|
||||
def test_url_scheme_http(self):
|
||||
self.assertEqual(self.parser.convert_urls('http://example.com'),
|
||||
'<a href="http://example.com" target="_blank">http://example.com</a>')
|
||||
'<a href="http://example.com" target="_blank">http://example.com</a>')
|
||||
|
||||
def test_url_scheme_https(self):
|
||||
self.assertEqual(self.parser.convert_urls('https://example.com'),
|
||||
'<a href="https://example.com" target="_blank">https://example.com</a>')
|
||||
'<a href="https://example.com" target="_blank">https://example.com</a>')
|
||||
|
||||
def test_url_chars_slash(self):
|
||||
self.assertEqual(self.parser.convert_urls('www.example.com/homedir'),
|
||||
'<a href="www.example.com/homedir" target="_blank">www.example.com/homedir</a>')
|
||||
'<a href="www.example.com/homedir" target="_blank">www.example.com/homedir</a>')
|
||||
|
||||
def test_url_chars_colon(self):
|
||||
self.assertEqual(self.parser.convert_urls('https://example.com:8000/login/'),
|
||||
'<a href="https://example.com:8000/login/" target="_blank">https://example.com:8000/login/</a>')
|
||||
'<a href="https://example.com:8000/login/" target="_blank">'
|
||||
'https://example.com:8000/login/</a>')
|
||||
|
||||
def test_url_chars_querystring(self):
|
||||
self.assertEqual(self.parser.convert_urls('https://example.com/submitform?field1=val1+val3&field2=val2'),
|
||||
'<a href="https://example.com/submitform?field1=val1+val3&field2=val2" target="_blank">https://example.com/submitform?field1=val1+val3&field2=val2</a>')
|
||||
'<a href="https://example.com/submitform?field1=val1+val3&field2=val2" target="_blank">'
|
||||
'https://example.com/submitform?field1=val1+val3&field2=val2</a>')
|
||||
|
||||
def test_url_chars_anchor(self):
|
||||
self.assertEqual(self.parser.convert_urls('http://www.example.com/menu#section_1'),
|
||||
'<a href="http://www.example.com/menu#section_1" target="_blank">http://www.example.com/menu#section_1</a>')
|
||||
'<a href="http://www.example.com/menu#section_1" target="_blank">'
|
||||
'http://www.example.com/menu#section_1</a>')
|
||||
|
||||
def test_url_chars_exclam(self):
|
||||
self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve'),
|
||||
'<a href="https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve" target="_blank">https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve</a>')
|
||||
self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/'
|
||||
'?fromgroups#!categories/evennia/ainneve'),
|
||||
'<a href="https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve"'
|
||||
' target="_blank">https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve</a>')
|
||||
|
||||
def test_url_edge_leadingw(self):
|
||||
self.assertEqual(self.parser.convert_urls('wwww.example.com'),
|
||||
'w<a href="www.example.com" target="_blank">www.example.com</a>')
|
||||
'w<a href="www.example.com" target="_blank">www.example.com</a>')
|
||||
|
||||
def test_url_edge_following_period_eol(self):
|
||||
self.assertEqual(self.parser.convert_urls('www.example.com.'),
|
||||
'<a href="www.example.com" target="_blank">www.example.com</a>.')
|
||||
'<a href="www.example.com" target="_blank">www.example.com</a>.')
|
||||
|
||||
def test_url_edge_following_period(self):
|
||||
self.assertEqual(self.parser.convert_urls('see www.example.com. '),
|
||||
'see <a href="www.example.com" target="_blank">www.example.com</a>. ')
|
||||
'see <a href="www.example.com" target="_blank">www.example.com</a>. ')
|
||||
|
||||
def test_url_edge_brackets(self):
|
||||
self.assertEqual(self.parser.convert_urls('[http://example.com/]'),
|
||||
'[<a href="http://example.com/" target="_blank">http://example.com/</a>]')
|
||||
'[<a href="http://example.com/" target="_blank">http://example.com/</a>]')
|
||||
|
||||
def test_url_edge_multiline(self):
|
||||
self.assertEqual(self.parser.convert_urls(' * http://example.com/info\n * bullet'),
|
||||
' * <a href="http://example.com/info" target="_blank">http://example.com/info</a>\n * bullet')
|
||||
' * <a href="http://example.com/info" target="_blank">'
|
||||
'http://example.com/info</a>\n * bullet')
|
||||
|
||||
def test_url_edge_following_htmlentity(self):
|
||||
self.assertEqual(self.parser.convert_urls('http://example.com/info<span>'),
|
||||
'<a href="http://example.com/info" target="_blank">http://example.com/info</a><span>')
|
||||
'<a href="http://example.com/info" target="_blank">http://example.com/info</a><span>')
|
||||
|
||||
def test_url_edge_surrounded_spans(self):
|
||||
self.assertEqual(self.parser.convert_urls('</span>http://example.com/<span class="red">'),
|
||||
'</span><a href="http://example.com/" target="_blank">http://example.com/</a><span class="red">')
|
||||
'</span><a href="http://example.com/" target="_blank">'
|
||||
'http://example.com/</a><span class="red">')
|
||||
|
||||
from evennia.utils import evmenu
|
||||
from mock import Mock
|
||||
|
|
@ -338,8 +342,9 @@ class TestEvMenu(TestCase):
|
|||
|
||||
from evennia.utils import inlinefuncs
|
||||
|
||||
|
||||
class TestInlineFuncs(TestCase):
|
||||
"Test the nested inlinefunc module"
|
||||
"""Test the nested inlinefunc module"""
|
||||
def test_nofunc(self):
|
||||
self.assertEqual(inlinefuncs.parse_inlinefunc(
|
||||
"as$382ewrw w we w werw,|44943}"),
|
||||
|
|
@ -372,12 +377,146 @@ class TestInlineFuncs(TestCase):
|
|||
|
||||
from evennia.utils import evform
|
||||
|
||||
|
||||
class TestEvForm(TestCase):
|
||||
def test_form(self):
|
||||
self.maxDiff = None
|
||||
self.assertEqual(evform._test(),
|
||||
u'.------------------------------------------------.\n| |\n| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m Player: \x1b[0m\x1b[1m\x1b[33mGriatch \x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m |\n| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n| |\n >----------------------------------------------<\n| |\n| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0mfellow\x1b[0m \x1b[0m INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n| |\n >----------.-----------------------------------<\n| | |\n| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m | \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m | \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m | \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m | \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| | |\n -----------`-------------------------------------\n')
|
||||
u'.------------------------------------------------.\n'
|
||||
u'| |\n'
|
||||
u'| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b'
|
||||
u'[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m '
|
||||
u'Player: \x1b[0m\x1b[1m\x1b[33mGriatch '
|
||||
u'\x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m '
|
||||
u'|\n'
|
||||
u'| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n'
|
||||
u'| |\n'
|
||||
u' >----------------------------------------------<\n'
|
||||
u'| |\n'
|
||||
u'| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m'
|
||||
u' STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m'
|
||||
u' DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
|
||||
u'| \x1b[0mfellow\x1b[0m \x1b[0m'
|
||||
u' INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m'
|
||||
u' STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
|
||||
u'| \x1b[0m \x1b[0m'
|
||||
u' LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m'
|
||||
u' MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n'
|
||||
u'| |\n'
|
||||
u' >----------.-----------------------------------<\n'
|
||||
u'| | |\n'
|
||||
u'| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m '
|
||||
u'| \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m'
|
||||
u'|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
|
||||
u'| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n'
|
||||
u'| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m '
|
||||
u'| \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m'
|
||||
u'|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
|
||||
u'| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m '
|
||||
u'| \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m'
|
||||
u'|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
|
||||
u'| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m '
|
||||
u'| \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m'
|
||||
u'|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
|
||||
u'| | |\n'
|
||||
u' -----------`-------------------------------------\n')
|
||||
|
||||
def test_ansi_escape(self):
|
||||
# note that in a msg() call, the result would be the correct |-----,
|
||||
# in a print, ansi only gets called once, so ||----- is the result
|
||||
self.assertEqual(unicode(evform.EvForm(form={"FORM":"\n||-----"})), "||-----")
|
||||
|
||||
class TestTimeformat(TestCase):
|
||||
"""
|
||||
Default function header from utils.py:
|
||||
time_format(seconds, style=0)
|
||||
|
||||
"""
|
||||
|
||||
def test_style_0(self):
|
||||
"""Test the style 0 of time_format."""
|
||||
self.assertEqual(utils.time_format(0, 0), "00:00")
|
||||
self.assertEqual(utils.time_format(28, 0), "00:00")
|
||||
self.assertEqual(utils.time_format(92, 0), "00:01")
|
||||
self.assertEqual(utils.time_format(300, 0), "00:05")
|
||||
self.assertEqual(utils.time_format(660, 0), "00:11")
|
||||
self.assertEqual(utils.time_format(3600, 0), "01:00")
|
||||
self.assertEqual(utils.time_format(3725, 0), "01:02")
|
||||
self.assertEqual(utils.time_format(86350, 0), "23:59")
|
||||
self.assertEqual(utils.time_format(86800, 0), "1d 00:06")
|
||||
self.assertEqual(utils.time_format(130800, 0), "1d 12:20")
|
||||
self.assertEqual(utils.time_format(530800, 0), "6d 03:26")
|
||||
|
||||
def test_style_1(self):
|
||||
"""Test the style 1 of time_format."""
|
||||
self.assertEqual(utils.time_format(0, 1), "0s")
|
||||
self.assertEqual(utils.time_format(28, 1), "28s")
|
||||
self.assertEqual(utils.time_format(92, 1), "1m")
|
||||
self.assertEqual(utils.time_format(300, 1), "5m")
|
||||
self.assertEqual(utils.time_format(660, 1), "11m")
|
||||
self.assertEqual(utils.time_format(3600, 1), "1h")
|
||||
self.assertEqual(utils.time_format(3725, 1), "1h")
|
||||
self.assertEqual(utils.time_format(86350, 1), "23h")
|
||||
self.assertEqual(utils.time_format(86800, 1), "1d")
|
||||
self.assertEqual(utils.time_format(130800, 1), "1d")
|
||||
self.assertEqual(utils.time_format(530800, 1), "6d")
|
||||
|
||||
def test_style_2(self):
|
||||
"""Test the style 2 of time_format."""
|
||||
self.assertEqual(utils.time_format(0, 2), "0 minutes")
|
||||
self.assertEqual(utils.time_format(28, 2), "0 minutes")
|
||||
self.assertEqual(utils.time_format(92, 2), "1 minute")
|
||||
self.assertEqual(utils.time_format(300, 2), "5 minutes")
|
||||
self.assertEqual(utils.time_format(660, 2), "11 minutes")
|
||||
self.assertEqual(utils.time_format(3600, 2), "1 hour, 0 minutes")
|
||||
self.assertEqual(utils.time_format(3725, 2), "1 hour, 2 minutes")
|
||||
self.assertEqual(utils.time_format(86350, 2), "23 hours, 59 minutes")
|
||||
self.assertEqual(utils.time_format(86800, 2),
|
||||
"1 day, 0 hours, 6 minutes")
|
||||
self.assertEqual(utils.time_format(130800, 2),
|
||||
"1 day, 12 hours, 20 minutes")
|
||||
self.assertEqual(utils.time_format(530800, 2),
|
||||
"6 days, 3 hours, 26 minutes")
|
||||
|
||||
def test_style_3(self):
|
||||
"""Test the style 3 of time_format."""
|
||||
self.assertEqual(utils.time_format(0, 3), "")
|
||||
self.assertEqual(utils.time_format(28, 3), "28 seconds")
|
||||
self.assertEqual(utils.time_format(92, 3), "1 minute 32 seconds")
|
||||
self.assertEqual(utils.time_format(300, 3), "5 minutes 0 seconds")
|
||||
self.assertEqual(utils.time_format(660, 3), "11 minutes 0 seconds")
|
||||
self.assertEqual(utils.time_format(3600, 3),
|
||||
"1 hour, 0 minutes")
|
||||
self.assertEqual(utils.time_format(3725, 3),
|
||||
"1 hour, 2 minutes 5 seconds")
|
||||
self.assertEqual(utils.time_format(86350, 3),
|
||||
"23 hours, 59 minutes 10 seconds")
|
||||
self.assertEqual(utils.time_format(86800, 3),
|
||||
"1 day, 0 hours, 6 minutes 40 seconds")
|
||||
self.assertEqual(utils.time_format(130800, 3),
|
||||
"1 day, 12 hours, 20 minutes 0 seconds")
|
||||
self.assertEqual(utils.time_format(530800, 3),
|
||||
"6 days, 3 hours, 26 minutes 40 seconds")
|
||||
|
||||
def test_style_4(self):
|
||||
"""Test the style 4 of time_format."""
|
||||
self.assertEqual(utils.time_format(0, 4), "0 seconds")
|
||||
self.assertEqual(utils.time_format(28, 4), "28 seconds")
|
||||
self.assertEqual(utils.time_format(92, 4), "a minute")
|
||||
self.assertEqual(utils.time_format(300, 4), "5 minutes")
|
||||
self.assertEqual(utils.time_format(660, 4), "11 minutes")
|
||||
self.assertEqual(utils.time_format(3600, 4), "an hour")
|
||||
self.assertEqual(utils.time_format(3725, 4), "an hour")
|
||||
self.assertEqual(utils.time_format(86350, 4), "23 hours")
|
||||
self.assertEqual(utils.time_format(86800, 4), "a day")
|
||||
self.assertEqual(utils.time_format(130800, 4), "a day")
|
||||
self.assertEqual(utils.time_format(530800, 4), "6 days")
|
||||
self.assertEqual(utils.time_format(3030800, 4), "a month")
|
||||
self.assertEqual(utils.time_format(7030800, 4), "2 months")
|
||||
self.assertEqual(utils.time_format(40030800, 4), "a year")
|
||||
self.assertEqual(utils.time_format(90030800, 4), "2 years")
|
||||
|
||||
def test_unknown_format(self):
|
||||
"""Test that unknown formats raise exceptions."""
|
||||
self.assertRaises(ValueError, utils.time_format, 0, 5)
|
||||
self.assertRaises(ValueError, utils.time_format, 0, "u")
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ _DA = object.__delattr__
|
|||
|
||||
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||
|
||||
|
||||
def is_iter(iterable):
|
||||
"""
|
||||
Checks if an object behaves iterably.
|
||||
|
|
@ -62,6 +63,7 @@ def is_iter(iterable):
|
|||
"""
|
||||
return hasattr(iterable, '__iter__')
|
||||
|
||||
|
||||
def make_iter(obj):
|
||||
"""
|
||||
Makes sure that the object is always iterable.
|
||||
|
|
@ -201,7 +203,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
|
|||
distribute odd spaces randomly to one of the gaps.
|
||||
"""
|
||||
line_rest = width - (wlen + ngaps)
|
||||
gap = " " # minimum gap between words
|
||||
gap = " " # minimum gap between words
|
||||
if line_rest > 0:
|
||||
if align == 'l':
|
||||
line[-1] += " " * line_rest
|
||||
|
|
@ -211,7 +213,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
|
|||
pad = " " * (line_rest // 2)
|
||||
line[0] = pad + line[0]
|
||||
line[-1] = line[-1] + pad + " " * (line_rest % 2)
|
||||
else: # align 'f'
|
||||
else: # align 'f'
|
||||
gap += " " * (line_rest // max(1, ngaps))
|
||||
rest_gap = line_rest % max(1, ngaps)
|
||||
for i in range(rest_gap):
|
||||
|
|
@ -250,8 +252,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
|
|||
wlen += word[1]
|
||||
ngaps += 1
|
||||
|
||||
|
||||
if line: # catch any line left behind
|
||||
if line: # catch any line left behind
|
||||
lines.append(_process_line(line))
|
||||
indentstring = " " * indent
|
||||
return "\n".join([indentstring + line for line in lines])
|
||||
|
|
@ -344,6 +345,7 @@ def time_format(seconds, style=0):
|
|||
1. "1d"
|
||||
2. "1 day, 8 hours, 30 minutes"
|
||||
3. "1 day, 8 hours, 30 minutes, 10 seconds"
|
||||
4. highest unit (like "3 years" or "8 months" or "1 second")
|
||||
Returns:
|
||||
timeformatted (str): A pretty time string.
|
||||
"""
|
||||
|
|
@ -360,7 +362,7 @@ def time_format(seconds, style=0):
|
|||
minutes = seconds // 60
|
||||
seconds -= minutes * 60
|
||||
|
||||
retval = ""
|
||||
retval = ""
|
||||
if style == 0:
|
||||
"""
|
||||
Standard colon-style output.
|
||||
|
|
@ -432,6 +434,38 @@ def time_format(seconds, style=0):
|
|||
else:
|
||||
seconds_str = '%i seconds ' % seconds
|
||||
retval = '%s%s%s%s' % (days_str, hours_str, minutes_str, seconds_str)
|
||||
elif style == 4:
|
||||
"""
|
||||
Only return the highest unit.
|
||||
"""
|
||||
if days >= 730: # Several years
|
||||
return "{} years".format(days // 365)
|
||||
elif days >= 365: # One year
|
||||
return "a year"
|
||||
elif days >= 62: # Several months
|
||||
return "{} months".format(days // 31)
|
||||
elif days >= 31: # One month
|
||||
return "a month"
|
||||
elif days >= 2: # Several days
|
||||
return "{} days".format(days)
|
||||
elif days > 0:
|
||||
return "a day"
|
||||
elif hours >= 2: # Several hours
|
||||
return "{} hours".format(hours)
|
||||
elif hours > 0: # One hour
|
||||
return "an hour"
|
||||
elif minutes >= 2: # Several minutes
|
||||
return "{} minutes".format(minutes)
|
||||
elif minutes > 0: # One minute
|
||||
return "a minute"
|
||||
elif seconds >= 2: # Several seconds
|
||||
return "{} seconds".format(seconds)
|
||||
elif seconds == 1:
|
||||
return "a second"
|
||||
else:
|
||||
return "0 seconds"
|
||||
else:
|
||||
raise ValueError("Unknown style for time format: %s" % style)
|
||||
|
||||
return retval.strip()
|
||||
|
||||
|
|
@ -522,13 +556,13 @@ def pypath_to_realpath(python_path, file_ending='.py', pypath_prefixes=None):
|
|||
"""
|
||||
path = python_path.strip().split('.')
|
||||
plong = osjoin(*path) + file_ending
|
||||
pshort = osjoin(*path[1:]) + file_ending if len(path) > 1 else plong # in case we had evennia. or mygame.
|
||||
pshort = osjoin(*path[1:]) + file_ending if len(path) > 1 else plong # in case we had evennia. or mygame.
|
||||
prefixlong = [osjoin(*ppath.strip().split('.'))
|
||||
for ppath in make_iter(pypath_prefixes)] \
|
||||
if pypath_prefixes else []
|
||||
for ppath in make_iter(pypath_prefixes)] \
|
||||
if pypath_prefixes else []
|
||||
prefixshort = [osjoin(*ppath.strip().split('.')[1:])
|
||||
for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1] \
|
||||
if pypath_prefixes else []
|
||||
for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1]\
|
||||
if pypath_prefixes else []
|
||||
paths = [plong] + \
|
||||
[osjoin(_EVENNIA_DIR, prefix, plong) for prefix in prefixlong] + \
|
||||
[osjoin(_GAME_DIR, prefix, plong) for prefix in prefixlong] + \
|
||||
|
|
@ -616,6 +650,7 @@ dbid_to_obj = dbref_to_obj
|
|||
_UNICODE_MAP = {"EM DASH": "-", "FIGURE DASH": "-", "EN DASH": "-", "HORIZONTAL BAR": "-",
|
||||
"HORIZONTAL ELLIPSIS": "...", "RIGHT SINGLE QUOTATION MARK": "'"}
|
||||
|
||||
|
||||
def latinify(unicode_string, default='?', pure_ascii=False):
|
||||
"""
|
||||
Convert a unicode string to "safe" ascii/latin-1 characters.
|
||||
|
|
@ -643,7 +678,7 @@ def latinify(unicode_string, default='?', pure_ascii=False):
|
|||
# point name; e.g., since `name(u'á') == 'LATIN SMALL
|
||||
# LETTER A WITH ACUTE'` translate `á` to `a`. However, in
|
||||
# some cases the unicode name is still "LATIN LETTER"
|
||||
# although no direct equivalent in the Latin alphabeth
|
||||
# although no direct equivalent in the Latin alphabet
|
||||
# exists (e.g., Þ, "LATIN CAPITAL LETTER THORN") -- we can
|
||||
# avoid these cases by checking that the letter name is
|
||||
# composed of one letter only.
|
||||
|
|
@ -910,6 +945,8 @@ def delay(timedelay, callback, *args, **kwargs):
|
|||
|
||||
_TYPECLASSMODELS = None
|
||||
_OBJECTMODELS = None
|
||||
|
||||
|
||||
def clean_object_caches(obj):
|
||||
"""
|
||||
Clean all object caches on the given object.
|
||||
|
|
@ -1146,8 +1183,6 @@ def all_from_module(module):
|
|||
# module if available (try to avoid not imports)
|
||||
members = getmembers(mod, predicate=lambda obj: getmodule(obj) in (mod, None))
|
||||
return dict((key, val) for key, val in members if not key.startswith("_"))
|
||||
#return dict((key, val) for key, val in mod.__dict__.items()
|
||||
# if not (key.startswith("_") or ismodule(val)))
|
||||
|
||||
|
||||
def callables_from_module(module):
|
||||
|
|
@ -1209,7 +1244,7 @@ def variable_from_module(module, variable=None, default=None):
|
|||
else:
|
||||
# get all
|
||||
result = [val for key, val in mod.__dict__.items()
|
||||
if not (key.startswith("_") or ismodule(val))]
|
||||
if not (key.startswith("_") or ismodule(val))]
|
||||
|
||||
if len(result) == 1:
|
||||
return result[0]
|
||||
|
|
@ -1348,6 +1383,7 @@ def class_from_module(path, defaultpaths=None):
|
|||
# alias
|
||||
object_from_module = class_from_module
|
||||
|
||||
|
||||
def init_new_player(player):
|
||||
"""
|
||||
Deprecated.
|
||||
|
|
@ -1442,7 +1478,7 @@ def string_partial_matching(alternatives, inp, ret_index=True):
|
|||
# (this will invalidate input in the wrong word order)
|
||||
submatch = [last_index + alt_num for alt_num, alt_word
|
||||
in enumerate(alt_words[last_index:])
|
||||
if alt_word.startswith(inp_word)]
|
||||
if alt_word.startswith(inp_word)]
|
||||
if submatch:
|
||||
last_index = min(submatch) + 1
|
||||
score += 1
|
||||
|
|
@ -1488,7 +1524,7 @@ def format_table(table, extra_space=1):
|
|||
for ir, row in enumarate(ftable):
|
||||
if ir == 0:
|
||||
# make first row white
|
||||
string += "\n{w" + ""join(row) + "{n"
|
||||
string += "\n|w" + ""join(row) + "|n"
|
||||
else:
|
||||
string += "\n" + "".join(row)
|
||||
print string
|
||||
|
|
@ -1539,6 +1575,8 @@ def get_evennia_pids():
|
|||
|
||||
from gc import get_referents
|
||||
from sys import getsizeof
|
||||
|
||||
|
||||
def deepsize(obj, max_depth=4):
|
||||
"""
|
||||
Get not only size of the given object, but also the size of
|
||||
|
|
@ -1562,21 +1600,22 @@ def deepsize(obj, max_depth=4):
|
|||
|
||||
"""
|
||||
def _recurse(o, dct, depth):
|
||||
if max_depth >= 0 and depth > max_depth:
|
||||
if 0 <= max_depth < depth:
|
||||
return
|
||||
for ref in get_referents(o):
|
||||
idr = id(ref)
|
||||
if not idr in dct:
|
||||
if idr not in dct:
|
||||
dct[idr] = (ref, getsizeof(ref, default=0))
|
||||
_recurse(ref, dct, depth+1)
|
||||
sizedict = {}
|
||||
_recurse(obj, sizedict, 0)
|
||||
#count = len(sizedict) + 1
|
||||
size = getsizeof(obj) + sum([p[1] for p in sizedict.values()])
|
||||
return size
|
||||
|
||||
# lazy load handler
|
||||
_missing = object()
|
||||
|
||||
|
||||
class lazy_property(object):
|
||||
"""
|
||||
Delays loading of property until first access. Credit goes to the
|
||||
|
|
@ -1597,14 +1636,14 @@ class lazy_property(object):
|
|||
|
||||
"""
|
||||
def __init__(self, func, name=None, doc=None):
|
||||
"Store all properties for now"
|
||||
"""Store all properties for now"""
|
||||
self.__name__ = name or func.__name__
|
||||
self.__module__ = func.__module__
|
||||
self.__doc__ = doc or func.__doc__
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
"Triggers initialization"
|
||||
"""Triggers initialization"""
|
||||
if obj is None:
|
||||
return self
|
||||
value = obj.__dict__.get(self.__name__, _missing)
|
||||
|
|
@ -1614,7 +1653,9 @@ class lazy_property(object):
|
|||
return value
|
||||
|
||||
_STRIP_ANSI = None
|
||||
_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([unichr(c) for c in range(0,32)])))# + range(127,160)])))
|
||||
_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([unichr(c) for c in range(0, 32)]))) # + range(127,160)])))
|
||||
|
||||
|
||||
def strip_control_sequences(string):
|
||||
"""
|
||||
Remove non-print text sequences.
|
||||
|
|
@ -1675,12 +1716,12 @@ def m_len(target):
|
|||
return len(ANSI_PARSER.strip_mxp(target))
|
||||
return len(target)
|
||||
|
||||
#------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
# Search handler function
|
||||
#------------------------------------------------------------------
|
||||
# -------------------------------------------------------------------
|
||||
#
|
||||
# Replace this hook function by changing settings.SEARCH_AT_RESULT.
|
||||
#
|
||||
|
||||
|
||||
def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
||||
"""
|
||||
|
|
@ -1696,7 +1737,7 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
|||
should the result pass through.
|
||||
caller (Object): The object performing the search and/or which should
|
||||
receive error messages.
|
||||
query (str, optional): The search query used to produce `matches`.
|
||||
query (str, optional): The search query used to produce `matches`.
|
||||
quiet (bool, optional): If `True`, no messages will be echoed to caller
|
||||
on errors.
|
||||
|
||||
|
|
@ -1774,6 +1815,7 @@ class LimitedSizeOrderedDict(OrderedDict):
|
|||
super(LimitedSizeOrderedDict, self).update(*args, **kwargs)
|
||||
self._check_size()
|
||||
|
||||
|
||||
def get_game_dir_path():
|
||||
"""
|
||||
This is called by settings_default in order to determine the path
|
||||
|
|
@ -1784,7 +1826,7 @@ def get_game_dir_path():
|
|||
|
||||
"""
|
||||
# current working directory, assumed to be somewhere inside gamedir.
|
||||
for i in range(10):
|
||||
for _ in range(10):
|
||||
gpath = os.getcwd()
|
||||
if "server" in os.listdir(gpath):
|
||||
if os.path.isfile(os.path.join("server", "conf", "settings.py")):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue