Format code with black. Add makefile to run fmt/tests

This commit is contained in:
Griatch 2019-09-28 18:18:11 +02:00
parent d00bce9288
commit c2c7fa311a
299 changed files with 19037 additions and 11611 deletions

View file

@ -29,6 +29,7 @@ without arguments starts a full interactive Python console.
- Remove `pillow` requirement (install especially if using imagefield) - Remove `pillow` requirement (install especially if using imagefield)
- Add Simplified Korean translation (user aceamro) - Add Simplified Korean translation (user aceamro)
- Show warning on `start -l` if settings contains values unsafe for production. - Show warning on `start -l` if settings contains values unsafe for production.
- Make code auto-formatted with Black.
## Evennia 0.9 (2018-2019) ## Evennia 0.9 (2018-2019)

View file

@ -1,12 +1,39 @@
default: install # This is used with `make <option>` and is used for running various
# administration operations on the code.
BLACK_FORMAT_CONFIGS = --target-version py37 --line-length 100 BLACK_FORMAT_CONFIGS = --target-version py37 --line-length 100
TEST_GAME_DIR = .test_game_dir
TESTS?=evennia
default:
@echo " Usage: "
@echo " make install - install evennia (recommended to activate virtualenv first)"
@echo " make fmt/format - run the black autoformatter on the source code"
@echo " make lint - run black in --check mode"
@echo " make test - run evennia test suite with all default values."
@echo " make tests=evennia.path test - run only specific test or tests."
@echo " make testp - run test suite using multiple cores."
install: install:
python setup.py develop pip install -e .
fmt: format:
black $(BLACK_FORMAT_CONFIGS) evennia black $(BLACK_FORMAT_CONFIGS) evennia
fmt: format
lint: lint:
black --check $(BLACK_FORMAT_CONFIGS) evennia black --check $(BLACK_FORMAT_CONFIGS) evennia
test:
evennia --init $(TEST_GAME_DIR);\
cd $(TEST_GAME_DIR);\
evennia migrate;\
evennia test --keepdb $(TESTS);\
testp:
evennia --init $(TEST_GAME_DIR);\
cd $(TEST_GAME_DIR);\
evennia migrate;\
evennia test --keepdb --parallel 4 $(tests);\

View file

@ -136,14 +136,16 @@ def _create_version():
version = "Unknown" version = "Unknown"
root = os.path.dirname(os.path.abspath(__file__)) root = os.path.dirname(os.path.abspath(__file__))
try: try:
with open(os.path.join(root, "VERSION.txt"), 'r') as f: with open(os.path.join(root, "VERSION.txt"), "r") as f:
version = f.read().strip() version = f.read().strip()
except IOError as err: except IOError as err:
print(err) print(err)
try: try:
rev = check_output( rev = (
"git rev-parse --short HEAD", check_output("git rev-parse --short HEAD", shell=True, cwd=root, stderr=STDOUT)
shell=True, cwd=root, stderr=STDOUT).strip().decode() .strip()
.decode()
)
version = "%s (rev %s)" % (version, rev) version = "%s (rev %s)" % (version, rev)
except (IOError, CalledProcessError, OSError): except (IOError, CalledProcessError, OSError):
# ignore if we cannot get to git # ignore if we cannot get to git
@ -259,9 +261,10 @@ def _init():
def _help(self): def _help(self):
"Returns list of contents" "Returns list of contents"
names = [name for name in self.__class__.__dict__ if not name.startswith('_')] names = [name for name in self.__class__.__dict__ if not name.startswith("_")]
names += [name for name in self.__dict__ if not name.startswith('_')] names += [name for name in self.__dict__ if not name.startswith("_")]
print(self.__doc__ + "-" * 60 + "\n" + ", ".join(names)) print(self.__doc__ + "-" * 60 + "\n" + ", ".join(names))
help = property(_help) help = property(_help)
class DBmanagers(_EvContainer): class DBmanagers(_EvContainer):
@ -281,6 +284,7 @@ def _init():
attributes - Attributes.objects attributes - Attributes.objects
""" """
from .help.models import HelpEntry from .help.models import HelpEntry
from .accounts.models import AccountDB from .accounts.models import AccountDB
from .scripts.models import ScriptDB from .scripts.models import ScriptDB
@ -302,7 +306,7 @@ def _init():
tags = Tag.objects tags = Tag.objects
# remove these so they are not visible as properties # remove these so they are not visible as properties
del HelpEntry, AccountDB, ScriptDB, Msg, ChannelDB del HelpEntry, AccountDB, ScriptDB, Msg, ChannelDB
#del ExternalChannelConnection # del ExternalChannelConnection
del ObjectDB, ServerConfig, Tag, Attribute del ObjectDB, ServerConfig, Tag, Attribute
managers = DBmanagers() managers = DBmanagers()
@ -325,15 +329,26 @@ def _init():
def __init__(self): def __init__(self):
"populate the object with commands" "populate the object with commands"
def add_cmds(module): def add_cmds(module):
"helper method for populating this object with cmds" "helper method for populating this object with cmds"
from evennia.utils import utils from evennia.utils import utils
cmdlist = utils.variable_from_module(module, module.__all__) cmdlist = utils.variable_from_module(module, module.__all__)
self.__dict__.update(dict([(c.__name__, c) for c in cmdlist])) self.__dict__.update(dict([(c.__name__, c) for c in cmdlist]))
from .commands.default import (admin, batchprocess, from .commands.default import (
building, comms, general, admin,
account, help, system, unloggedin) batchprocess,
building,
comms,
general,
account,
help,
system,
unloggedin,
)
add_cmds(admin) add_cmds(admin)
add_cmds(building) add_cmds(building)
add_cmds(batchprocess) add_cmds(batchprocess)
@ -367,19 +382,23 @@ def _init():
access the properties on the imported syscmdkeys object. access the properties on the imported syscmdkeys object.
""" """
from .commands import cmdhandler from .commands import cmdhandler
CMD_NOINPUT = cmdhandler.CMD_NOINPUT CMD_NOINPUT = cmdhandler.CMD_NOINPUT
CMD_NOMATCH = cmdhandler.CMD_NOMATCH CMD_NOMATCH = cmdhandler.CMD_NOMATCH
CMD_MULTIMATCH = cmdhandler.CMD_MULTIMATCH CMD_MULTIMATCH = cmdhandler.CMD_MULTIMATCH
CMD_CHANNEL = cmdhandler.CMD_CHANNEL CMD_CHANNEL = cmdhandler.CMD_CHANNEL
CMD_LOGINSTART = cmdhandler.CMD_LOGINSTART CMD_LOGINSTART = cmdhandler.CMD_LOGINSTART
del cmdhandler del cmdhandler
syscmdkeys = SystemCmds() syscmdkeys = SystemCmds()
del SystemCmds del SystemCmds
del _EvContainer del _EvContainer
# typeclases # typeclases
from .utils.utils import class_from_module from .utils.utils import class_from_module
BASE_ACCOUNT_TYPECLASS = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) BASE_ACCOUNT_TYPECLASS = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
BASE_OBJECT_TYPECLASS = class_from_module(settings.BASE_OBJECT_TYPECLASS) BASE_OBJECT_TYPECLASS = class_from_module(settings.BASE_OBJECT_TYPECLASS)
BASE_CHARACTER_TYPECLASS = class_from_module(settings.BASE_CHARACTER_TYPECLASS) BASE_CHARACTER_TYPECLASS = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
@ -422,20 +441,22 @@ def set_trace(term_size=(140, 40), debugger="auto"):
""" """
import sys import sys
dbg = None dbg = None
if debugger in ('auto', 'pudb'): if debugger in ("auto", "pudb"):
try: try:
from pudb import debugger from pudb import debugger
dbg = debugger.Debugger(stdout=sys.__stdout__,
term_size=term_size) dbg = debugger.Debugger(stdout=sys.__stdout__, term_size=term_size)
except ImportError: except ImportError:
if debugger == 'pudb': if debugger == "pudb":
raise raise
pass pass
if not dbg: if not dbg:
import pdb import pdb
dbg = pdb.Pdb(stdout=sys.__stdout__) dbg = pdb.Pdb(stdout=sys.__stdout__)
try: try:

View file

@ -26,11 +26,12 @@ from evennia.commands import cmdhandler
from evennia.server.models import ServerConfig from evennia.server.models import ServerConfig
from evennia.server.throttle import Throttle from evennia.server.throttle import Throttle
from evennia.utils import class_from_module, create, logger from evennia.utils import class_from_module, create, logger
from evennia.utils.utils import (lazy_property, to_str, from evennia.utils.utils import lazy_property, to_str, make_iter, is_iter, variable_from_module
make_iter, is_iter, from evennia.server.signals import (
variable_from_module) SIGNAL_ACCOUNT_POST_CREATE,
from evennia.server.signals import (SIGNAL_ACCOUNT_POST_CREATE, SIGNAL_OBJECT_POST_PUPPET, SIGNAL_OBJECT_POST_PUPPET,
SIGNAL_OBJECT_POST_UNPUPPET) SIGNAL_OBJECT_POST_UNPUPPET,
)
from evennia.typeclasses.attributes import NickHandler from evennia.typeclasses.attributes import NickHandler
from evennia.scripts.scripthandler import ScriptHandler from evennia.scripts.scripthandler import ScriptHandler
from evennia.commands.cmdsethandler import CmdSetHandler from evennia.commands.cmdsethandler import CmdSetHandler
@ -43,7 +44,7 @@ __all__ = ("DefaultAccount",)
_SESSIONS = None _SESSIONS = None
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
_MULTISESSION_MODE = settings.MULTISESSION_MODE _MULTISESSION_MODE = settings.MULTISESSION_MODE
_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
_CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT _CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT
@ -202,12 +203,14 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
@lazy_property @lazy_property
def options(self): def options(self):
return OptionHandler(self, return OptionHandler(
options_dict=settings.OPTIONS_ACCOUNT_DEFAULT, self,
savefunc=self.attributes.add, options_dict=settings.OPTIONS_ACCOUNT_DEFAULT,
loadfunc=self.attributes.get, savefunc=self.attributes.add,
save_kwargs={"category": 'option'}, loadfunc=self.attributes.get,
load_kwargs={"category": 'option'}) save_kwargs={"category": "option"},
load_kwargs={"category": "option"},
)
# Do not make this a lazy property; the web UI will not refresh it! # Do not make this a lazy property; the web UI will not refresh it!
@property @property
@ -266,7 +269,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# already puppeting this object # already puppeting this object
self.msg("You are already puppeting this object.") self.msg("You are already puppeting this object.")
return return
if not obj.access(self, 'puppet'): if not obj.access(self, "puppet"):
# no access # no access
self.msg(f"You don't have permission to puppet '{obj.key}'.") self.msg(f"You don't have permission to puppet '{obj.key}'.")
return return
@ -389,6 +392,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if _MULTISESSION_MODE in (0, 1): if _MULTISESSION_MODE in (0, 1):
return puppets and puppets[0] or None return puppets and puppets[0] or None
return puppets return puppets
character = property(__get_single_puppet) character = property(__get_single_puppet)
puppet = property(__get_single_puppet) puppet = property(__get_single_puppet)
@ -407,21 +411,23 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
""" """
ip = kwargs.get('ip', '').strip() ip = kwargs.get("ip", "").strip()
username = kwargs.get('username', '').lower().strip() username = kwargs.get("username", "").lower().strip()
# Check IP and/or name bans # Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans") bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == username for tup in bans if username) or if bans and (
any(tup[2].match(ip) for tup in bans if ip and tup[2])): any(tup[0] == username for tup in bans if username)
or any(tup[2].match(ip) for tup in bans if ip and tup[2])
):
return True return True
return False return False
@classmethod @classmethod
def get_username_validators( def get_username_validators(
cls, validator_config=getattr( cls, validator_config=getattr(settings, "AUTH_USERNAME_VALIDATORS", [])
settings, 'AUTH_USERNAME_VALIDATORS', [])): ):
""" """
Retrieves and instantiates validators for usernames. Retrieves and instantiates validators for usernames.
@ -436,16 +442,18 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
objs = [] objs = []
for validator in validator_config: for validator in validator_config:
try: try:
klass = import_string(validator['NAME']) klass = import_string(validator["NAME"])
except ImportError: except ImportError:
msg = (f"The module in NAME could not be imported: {validator['NAME']}. " msg = (
"Check your AUTH_USERNAME_VALIDATORS setting.") f"The module in NAME could not be imported: {validator['NAME']}. "
"Check your AUTH_USERNAME_VALIDATORS setting."
)
raise ImproperlyConfigured(msg) raise ImproperlyConfigured(msg)
objs.append(klass(**validator.get('OPTIONS', {}))) objs.append(klass(**validator.get("OPTIONS", {})))
return objs return objs
@classmethod @classmethod
def authenticate(cls, username, password, ip='', **kwargs): def authenticate(cls, username, password, ip="", **kwargs):
""" """
Checks the given username/password against the database to see if the Checks the given username/password against the database to see if the
credentials are valid. credentials are valid.
@ -480,7 +488,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# See if authentication is currently being throttled # See if authentication is currently being throttled
if ip and LOGIN_THROTTLE.check(ip): if ip and LOGIN_THROTTLE.check(ip):
errors.append('Too many login failures; please try again in a few minutes.') errors.append("Too many login failures; please try again in a few minutes.")
# With throttle active, do not log continued hits-- it is a # With throttle active, do not log continued hits-- it is a
# waste of storage and can be abused to make your logs harder to # waste of storage and can be abused to make your logs harder to
@ -491,27 +499,29 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
banned = cls.is_banned(username=username, ip=ip) banned = cls.is_banned(username=username, ip=ip)
if banned: if banned:
# this is a banned IP or name! # this is a banned IP or name!
errors.append("|rYou have been banned and cannot continue from here." errors.append(
"\nIf you feel this ban is in error, please email an admin.|x") "|rYou have been banned and cannot continue from here."
logger.log_sec(f'Authentication Denied (Banned): {username} (IP: {ip}).') "\nIf you feel this ban is in error, please email an admin.|x"
LOGIN_THROTTLE.update(ip, 'Too many sightings of banned artifact.') )
logger.log_sec(f"Authentication Denied (Banned): {username} (IP: {ip}).")
LOGIN_THROTTLE.update(ip, "Too many sightings of banned artifact.")
return None, errors return None, errors
# Authenticate and get Account object # Authenticate and get Account object
account = authenticate(username=username, password=password) account = authenticate(username=username, password=password)
if not account: if not account:
# User-facing message # User-facing message
errors.append('Username and/or password is incorrect.') errors.append("Username and/or password is incorrect.")
# Log auth failures while throttle is inactive # Log auth failures while throttle is inactive
logger.log_sec(f'Authentication Failure: {username} (IP: {ip}).') logger.log_sec(f"Authentication Failure: {username} (IP: {ip}).")
# Update throttle # Update throttle
if ip: if ip:
LOGIN_THROTTLE.update(ip, 'Too many authentication failures.') LOGIN_THROTTLE.update(ip, "Too many authentication failures.")
# Try to call post-failure hook # Try to call post-failure hook
session = kwargs.get('session', None) session = kwargs.get("session", None)
if session: if session:
account = AccountDB.objects.get_account_from_name(username) account = AccountDB.objects.get_account_from_name(username)
if account: if account:
@ -520,7 +530,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
return None, errors return None, errors
# Account successfully authenticated # Account successfully authenticated
logger.log_sec(f'Authentication Success: {account} (IP: {ip}).') logger.log_sec(f"Authentication Success: {account} (IP: {ip}).")
return account, errors return account, errors
@classmethod @classmethod
@ -659,17 +669,19 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
account = None account = None
errors = [] errors = []
username = kwargs.get('username') username = kwargs.get("username")
password = kwargs.get('password') password = kwargs.get("password")
email = kwargs.get('email', '').strip() email = kwargs.get("email", "").strip()
guest = kwargs.get('guest', False) guest = kwargs.get("guest", False)
permissions = kwargs.get('permissions', settings.PERMISSION_ACCOUNT_DEFAULT) permissions = kwargs.get("permissions", settings.PERMISSION_ACCOUNT_DEFAULT)
typeclass = kwargs.get('typeclass', cls) typeclass = kwargs.get("typeclass", cls)
ip = kwargs.get('ip', '') ip = kwargs.get("ip", "")
if ip and CREATION_THROTTLE.check(ip): if ip and CREATION_THROTTLE.check(ip):
errors.append("You are creating too many accounts. Please log into an existing account.") errors.append(
"You are creating too many accounts. Please log into an existing account."
)
return None, errors return None, errors
# Normalize username # Normalize username
@ -696,19 +708,25 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
banned = cls.is_banned(username=username, ip=ip) banned = cls.is_banned(username=username, ip=ip)
if banned: if banned:
# this is a banned IP or name! # this is a banned IP or name!
string = "|rYou have been banned and cannot continue from here." \ string = (
"\nIf you feel this ban is in error, please email an admin.|x" "|rYou have been banned and cannot continue from here."
"\nIf you feel this ban is in error, please email an admin.|x"
)
errors.append(string) errors.append(string)
return None, errors return None, errors
# everything's ok. Create the new account. # everything's ok. Create the new account.
try: try:
try: try:
account = create.create_account(username, email, password, permissions=permissions, typeclass=typeclass) account = create.create_account(
logger.log_sec(f'Account Created: {account} (IP: {ip}).') username, email, password, permissions=permissions, typeclass=typeclass
)
logger.log_sec(f"Account Created: {account} (IP: {ip}).")
except Exception as e: except Exception as e:
errors.append("There was an error creating the Account. If this problem persists, contact an admin.") errors.append(
"There was an error creating the Account. If this problem persists, contact an admin."
)
logger.log_trace() logger.log_trace()
return None, errors return None, errors
@ -730,14 +748,20 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if account and settings.MULTISESSION_MODE < 2: if account and settings.MULTISESSION_MODE < 2:
# Load the appropriate Character class # Load the appropriate Character class
character_typeclass = kwargs.get('character_typeclass', settings.BASE_CHARACTER_TYPECLASS) character_typeclass = kwargs.get(
character_home = kwargs.get('home') "character_typeclass", settings.BASE_CHARACTER_TYPECLASS
)
character_home = kwargs.get("home")
Character = class_from_module(character_typeclass) Character = class_from_module(character_typeclass)
# Create the character # Create the character
character, errs = Character.create( character, errs = Character.create(
account.key, account, ip=ip, typeclass=character_typeclass, account.key,
permissions=permissions, home=character_home account,
ip=ip,
typeclass=character_typeclass,
permissions=permissions,
home=character_home,
) )
errors.extend(errs) errors.extend(errs)
@ -758,7 +782,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# Update the throttle to indicate a new account was created from this IP # Update the throttle to indicate a new account was created from this IP
if ip and not guest: if ip and not guest:
CREATION_THROTTLE.update(ip, 'Too many accounts being created.') CREATION_THROTTLE.update(ip, "Too many accounts being created.")
SIGNAL_ACCOUNT_POST_CREATE.send(sender=account, ip=ip) SIGNAL_ACCOUNT_POST_CREATE.send(sender=account, ip=ip)
return account, errors return account, errors
@ -786,6 +810,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
self.nicks.clear() self.nicks.clear()
self.aliases.clear() self.aliases.clear()
super().delete(*args, **kwargs) super().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): def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
@ -826,7 +851,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
kwargs["options"] = options kwargs["options"] = options
if text is not None: if text is not None:
kwargs['text'] = to_str(text) kwargs["text"] = to_str(text)
# session relay # session relay
sessions = make_iter(session) if session else self.sessions.all() sessions = make_iter(session) if session else self.sessions.all()
@ -853,17 +878,29 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
commands at run-time. commands at run-time.
""" """
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_account=False) raw_string = self.nicks.nickreplace(
raw_string, categories=("inputline", "channel"), include_account=False
)
if not session and _MULTISESSION_MODE in (0, 1): if not session and _MULTISESSION_MODE in (0, 1):
# for these modes we use the first/only session # for these modes we use the first/only session
sessions = self.sessions.get() sessions = self.sessions.get()
session = sessions[0] if sessions else None session = sessions[0] if sessions else None
return cmdhandler.cmdhandler(self, raw_string, return cmdhandler.cmdhandler(
callertype="account", session=session, **kwargs) self, raw_string, callertype="account", session=session, **kwargs
)
def search(self, searchdata, return_puppet=False, search_object=False, def search(
typeclass=None, nofound_string=None, multimatch_string=None, use_nicks=True, **kwargs): self,
searchdata,
return_puppet=False,
search_object=False,
typeclass=None,
nofound_string=None,
multimatch_string=None,
use_nicks=True,
**kwargs,
):
""" """
This is similar to `DefaultObject.search` but defaults to searching This is similar to `DefaultObject.search` but defaults to searching
for Accounts only. for Accounts only.
@ -900,17 +937,25 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# handle me, self and *me, *self # handle me, self and *me, *self
if isinstance(searchdata, str): if isinstance(searchdata, str):
# handle wrapping of common terms # handle wrapping of common terms
if searchdata.lower() in ("me", "*me", "self", "*self",): if searchdata.lower() in ("me", "*me", "self", "*self"):
return self return self
if search_object: if search_object:
matches = ObjectDB.objects.object_search(searchdata, typeclass=typeclass, use_nicks=use_nicks) matches = ObjectDB.objects.object_search(
searchdata, typeclass=typeclass, use_nicks=use_nicks
)
else: else:
searchdata = self.nicks.nickreplace(searchdata, categories=("account", ), include_account=False) searchdata = self.nicks.nickreplace(
searchdata, categories=("account",), include_account=False
)
matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass) matches = AccountDB.objects.account_search(searchdata, typeclass=typeclass)
matches = _AT_SEARCH_RESULT(matches, self, query=searchdata, matches = _AT_SEARCH_RESULT(
nofound_string=nofound_string, matches,
multimatch_string=multimatch_string) self,
query=searchdata,
nofound_string=nofound_string,
multimatch_string=multimatch_string,
)
if matches and return_puppet: if matches and return_puppet:
try: try:
return matches.puppet return matches.puppet
@ -918,7 +963,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
return None return None
return matches return matches
def access(self, accessing_obj, access_type='read', default=False, no_superuser_bypass=False, **kwargs): def access(
self, accessing_obj, access_type="read", default=False, no_superuser_bypass=False, **kwargs
):
""" """
Determines if another object has permission to access this Determines if another object has permission to access this
object in whatever way. object in whatever way.
@ -938,8 +985,12 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
result (bool): Result of access check. result (bool): Result of access check.
""" """
result = super().access(accessing_obj, access_type=access_type, result = super().access(
default=default, no_superuser_bypass=no_superuser_bypass) accessing_obj,
access_type=access_type,
default=default,
no_superuser_bypass=no_superuser_bypass,
)
self.at_access(result, accessing_obj, access_type, **kwargs) self.at_access(result, accessing_obj, access_type, **kwargs)
return result return result
@ -974,9 +1025,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
""" """
# A basic security setup # A basic security setup
lockstring = "examine:perm(Admin);edit:perm(Admin);" \ lockstring = (
"delete:perm(Admin);boot:perm(Admin);msg:all();" \ "examine:perm(Admin);edit:perm(Admin);"
"noidletimeout:perm(Builder) or perm(noidletimeout)" "delete:perm(Admin);boot:perm(Admin);msg:all();"
"noidletimeout:perm(Builder) or perm(noidletimeout)"
)
self.locks.add(lockstring) self.locks.add(lockstring)
# The ooc account cmdset # The ooc account cmdset
@ -991,8 +1044,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
""" """
# set an (empty) attribute holding the characters this account has # set an (empty) attribute holding the characters this account has
lockstring = "attrread:perm(Admins);attredit:perm(Admins);" \ lockstring = "attrread:perm(Admins);attredit:perm(Admins);" "attrcreate:perm(Admins);"
"attrcreate:perm(Admins);"
self.attributes.add("_playable_characters", [], lockstring=lockstring) self.attributes.add("_playable_characters", [], lockstring=lockstring)
self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring) self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring)
@ -1147,13 +1199,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
global _MUDINFO_CHANNEL global _MUDINFO_CHANNEL
if not _MUDINFO_CHANNEL: if not _MUDINFO_CHANNEL:
try: try:
_MUDINFO_CHANNEL = ChannelDB.objects.filter( _MUDINFO_CHANNEL = ChannelDB.objects.filter(db_key=settings.CHANNEL_MUDINFO["key"])[
db_key=settings.CHANNEL_MUDINFO["key"])[0] 0
]
except Exception: except Exception:
logger.log_trace() logger.log_trace()
now = timezone.now() now = timezone.now()
now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month, now = "%02i-%02i-%02i(%02i:%02i)" % (now.year, now.month, now.day, now.hour, now.minute)
now.day, now.hour, now.minute)
if _MUDINFO_CHANNEL: if _MUDINFO_CHANNEL:
_MUDINFO_CHANNEL.tempmsg(f"[{_MUDINFO_CHANNEL.key}, {now}]: {message}") _MUDINFO_CHANNEL.tempmsg(f"[{_MUDINFO_CHANNEL.key}, {now}]: {message}")
else: else:
@ -1206,8 +1258,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# we make sure to clean up the _playable_characters list in case # we make sure to clean up the _playable_characters list in case
# any was deleted in the interim. # any was deleted in the interim.
self.db._playable_characters = [char for char in self.db._playable_characters if char] self.db._playable_characters = [char for char in self.db._playable_characters if char]
self.msg(self.at_look(target=self.db._playable_characters, self.msg(
session=session), session=session) self.at_look(target=self.db._playable_characters, session=session), session=session
)
def at_failed_login(self, session, **kwargs): def at_failed_login(self, session, **kwargs):
""" """
@ -1355,19 +1408,27 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
result = [f"Account |g{self.key}|n (you are Out-of-Character)"] result = [f"Account |g{self.key}|n (you are Out-of-Character)"]
nsess = len(sessions) nsess = len(sessions)
result.append(nsess == 1 and result.append(
"\n\n|wConnected session:|n" or nsess == 1
f"\n\n|wConnected sessions ({nsess}):|n") and "\n\n|wConnected session:|n"
or f"\n\n|wConnected sessions ({nsess}):|n"
)
for isess, sess in enumerate(sessions): for isess, sess in enumerate(sessions):
csessid = sess.sessid csessid = sess.sessid
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and addr = "%s (%s)" % (
str(sess.address[0]) or sess.protocol_key,
str(sess.address)) isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address),
result.append("\n %s %s" % ( )
session and result.append(
session.sessid == csessid and "\n %s %s"
"|w* %s|n" % (isess + 1) or % (
" %s" % (isess + 1), addr)) session
and 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\n |whelp|n - more commands")
result.append("\n |wooc <Text>|n - talk on public channel") result.append("\n |wooc <Text>|n - talk on public channel")
@ -1375,19 +1436,30 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
if is_su or len(characters) < charmax: if is_su or len(characters) < charmax:
if not characters: if not characters:
result.append("\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: else:
result.append("\n |w@charcreate <name> [=description]|n - create new character") result.append("\n |w@charcreate <name> [=description]|n - create new character")
result.append("\n |w@chardelete <name>|n - delete a character (cannot be undone!)") result.append(
"\n |w@chardelete <name>|n - delete a character (cannot be undone!)"
)
if characters: if characters:
string_s_ending = len(characters) > 1 and "s" or "" string_s_ending = len(characters) > 1 and "s" or ""
result.append("\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: if is_su:
result.append(f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):") result.append(
f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):"
)
else: else:
result.append("\n\nAvailable character%s%s:" result.append(
% (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "")) "\n\nAvailable character%s%s:"
% (
string_s_ending,
charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "",
)
)
for char in characters: for char in characters:
csessions = char.sessions.all() csessions = char.sessions.all()
@ -1396,9 +1468,13 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
# character is already puppeted # character is already puppeted
sid = sess in sessions and sessions.index(sess) + 1 sid = sess in sessions and sessions.index(sess) + 1
if sess and sid: if sess and sid:
result.append(f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] (played by you in session {sid})") result.append(
f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] (played by you in session {sid})"
)
else: else:
result.append(f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] (played by someone else)") result.append(
f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] (played by someone else)"
)
else: else:
# character is "free to puppet" # character is "free to puppet"
result.append(f"\n - {char.key} [{', '.join(char.permissions.all())}]") result.append(f"\n - {char.key} [{', '.join(char.permissions.all())}]")
@ -1437,11 +1513,11 @@ class DefaultGuest(DefaultAccount):
errors = [] errors = []
account = None account = None
username = None username = None
ip = kwargs.get('ip', '').strip() ip = kwargs.get("ip", "").strip()
# check if guests are enabled. # check if guests are enabled.
if not settings.GUEST_ENABLED: if not settings.GUEST_ENABLED:
errors.append('Guest accounts are not enabled on this server.') errors.append("Guest accounts are not enabled on this server.")
return None, errors return None, errors
try: try:
@ -1453,7 +1529,7 @@ class DefaultGuest(DefaultAccount):
if not username: if not username:
errors.append("All guest accounts are in use. Please try again later.") errors.append("All guest accounts are in use. Please try again later.")
if ip: if ip:
LOGIN_THROTTLE.update(ip, 'Too many requests for Guest access.') LOGIN_THROTTLE.update(ip, "Too many requests for Guest access.")
return None, errors return None, errors
else: else:
# build a new account with the found guest username # build a new account with the found guest username

View file

@ -18,34 +18,34 @@ class AccountDBChangeForm(UserChangeForm):
Modify the accountdb class. Modify the accountdb class.
""" """
class Meta(object): class Meta(object):
model = AccountDB model = AccountDB
fields = '__all__' fields = "__all__"
username = forms.RegexField( username = forms.RegexField(
label="Username", label="Username",
max_length=30, max_length=30,
regex=r'^[\w. @+-]+$', regex=r"^[\w. @+-]+$",
widget=forms.TextInput( widget=forms.TextInput(attrs={"size": "30"}),
attrs={'size': '30'}),
error_messages={ error_messages={
'invalid': "This value may contain only letters, spaces, numbers " "invalid": "This value may contain only letters, spaces, numbers "
"and @/./+/-/_ characters."}, "and @/./+/-/_ characters."
help_text="30 characters or fewer. Letters, spaces, digits and " },
"@/./+/-/_ only.") help_text="30 characters or fewer. Letters, spaces, digits and " "@/./+/-/_ only.",
)
def clean_username(self): def clean_username(self):
""" """
Clean the username and check its existence. Clean the username and check its existence.
""" """
username = self.cleaned_data['username'] username = self.cleaned_data["username"]
if username.upper() == self.instance.username.upper(): if username.upper() == self.instance.username.upper():
return username return username
elif AccountDB.objects.filter(username__iexact=username): elif AccountDB.objects.filter(username__iexact=username):
raise forms.ValidationError('An account with that name ' raise forms.ValidationError("An account with that name " "already exists.")
'already exists.') return self.cleaned_data["username"]
return self.cleaned_data['username']
class AccountDBCreationForm(UserCreationForm): class AccountDBCreationForm(UserCreationForm):
@ -55,28 +55,27 @@ class AccountDBCreationForm(UserCreationForm):
class Meta(object): class Meta(object):
model = AccountDB model = AccountDB
fields = '__all__' fields = "__all__"
username = forms.RegexField( username = forms.RegexField(
label="Username", label="Username",
max_length=30, max_length=30,
regex=r'^[\w. @+-]+$', regex=r"^[\w. @+-]+$",
widget=forms.TextInput( widget=forms.TextInput(attrs={"size": "30"}),
attrs={'size': '30'}),
error_messages={ error_messages={
'invalid': "This value may contain only letters, spaces, numbers " "invalid": "This value may contain only letters, spaces, numbers "
"and @/./+/-/_ characters."}, "and @/./+/-/_ characters."
help_text="30 characters or fewer. Letters, spaces, digits and " },
"@/./+/-/_ only.") help_text="30 characters or fewer. Letters, spaces, digits and " "@/./+/-/_ only.",
)
def clean_username(self): def clean_username(self):
""" """
Cleanup username. Cleanup username.
""" """
username = self.cleaned_data['username'] username = self.cleaned_data["username"]
if AccountDB.objects.filter(username__iexact=username): if AccountDB.objects.filter(username__iexact=username):
raise forms.ValidationError('An account with that name already ' raise forms.ValidationError("An account with that name already " "exists.")
'exists.')
return username return username
@ -85,61 +84,66 @@ class AccountForm(forms.ModelForm):
Defines how to display Accounts Defines how to display Accounts
""" """
class Meta(object): class Meta(object):
model = AccountDB model = AccountDB
fields = '__all__' fields = "__all__"
db_key = forms.RegexField( db_key = forms.RegexField(
label="Username", label="Username",
initial="AccountDummy", initial="AccountDummy",
max_length=30, max_length=30,
regex=r'^[\w. @+-]+$', regex=r"^[\w. @+-]+$",
required=False, required=False,
widget=forms.TextInput(attrs={'size': '30'}), widget=forms.TextInput(attrs={"size": "30"}),
error_messages={ error_messages={
'invalid': "This value may contain only letters, spaces, numbers" "invalid": "This value may contain only letters, spaces, numbers"
" and @/./+/-/_ characters."}, " and @/./+/-/_ characters."
},
help_text="This should be the same as the connected Account's key " help_text="This should be the same as the connected Account's key "
"name. 30 characters or fewer. Letters, spaces, digits and " "name. 30 characters or fewer. Letters, spaces, digits and "
"@/./+/-/_ only.") "@/./+/-/_ only.",
)
db_typeclass_path = forms.CharField( db_typeclass_path = forms.CharField(
label="Typeclass", label="Typeclass",
initial=settings.BASE_ACCOUNT_TYPECLASS, initial=settings.BASE_ACCOUNT_TYPECLASS,
widget=forms.TextInput( widget=forms.TextInput(attrs={"size": "78"}),
attrs={'size': '78'}),
help_text="Required. Defines what 'type' of entity this is. This " help_text="Required. Defines what 'type' of entity this is. This "
"variable holds a Python path to a module with a valid " "variable holds a Python path to a module with a valid "
"Evennia Typeclass. Defaults to " "Evennia Typeclass. Defaults to "
"settings.BASE_ACCOUNT_TYPECLASS.") "settings.BASE_ACCOUNT_TYPECLASS.",
)
db_permissions = forms.CharField( db_permissions = forms.CharField(
label="Permissions", label="Permissions",
initial=settings.PERMISSION_ACCOUNT_DEFAULT, initial=settings.PERMISSION_ACCOUNT_DEFAULT,
required=False, required=False,
widget=forms.TextInput( widget=forms.TextInput(attrs={"size": "78"}),
attrs={'size': '78'}),
help_text="In-game permissions. A comma-separated list of text " help_text="In-game permissions. A comma-separated list of text "
"strings checked by certain locks. They are often used for " "strings checked by certain locks. They are often used for "
"hierarchies, such as letting an Account have permission " "hierarchies, such as letting an Account have permission "
"'Admin', 'Builder' etc. An Account permission can be " "'Admin', 'Builder' etc. An Account permission can be "
"overloaded by the permissions of a controlled Character. " "overloaded by the permissions of a controlled Character. "
"Normal accounts use 'Accounts' by default.") "Normal accounts use 'Accounts' by default.",
)
db_lock_storage = forms.CharField( db_lock_storage = forms.CharField(
label="Locks", label="Locks",
widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}), widget=forms.Textarea(attrs={"cols": "100", "rows": "2"}),
required=False, required=False,
help_text="In-game lock definition string. If not given, defaults " help_text="In-game lock definition string. If not given, defaults "
"will be used. This string should be on the form " "will be used. This string should be on the form "
"<i>type:lockfunction(args);type2:lockfunction2(args);...") "<i>type:lockfunction(args);type2:lockfunction2(args);...",
)
db_cmdset_storage = forms.CharField( db_cmdset_storage = forms.CharField(
label="cmdset", label="cmdset",
initial=settings.CMDSET_ACCOUNT, initial=settings.CMDSET_ACCOUNT,
widget=forms.TextInput(attrs={'size': '78'}), widget=forms.TextInput(attrs={"size": "78"}),
required=False, required=False,
help_text="python path to account cmdset class (set in " help_text="python path to account cmdset class (set in "
"settings.CMDSET_ACCOUNT by default)") "settings.CMDSET_ACCOUNT by default)",
)
class AccountInline(admin.StackedInline): class AccountInline(admin.StackedInline):
@ -147,19 +151,29 @@ class AccountInline(admin.StackedInline):
Inline creation of Account Inline creation of Account
""" """
model = AccountDB model = AccountDB
template = "admin/accounts/stacked.html" template = "admin/accounts/stacked.html"
form = AccountForm form = AccountForm
fieldsets = ( fieldsets = (
("In-game Permissions and Locks", (
{'fields': ('db_lock_storage',), "In-game Permissions and Locks",
#{'fields': ('db_permissions', 'db_lock_storage'), {
'description': "<i>These are permissions/locks for in-game use. " "fields": ("db_lock_storage",),
"They are unrelated to website access rights.</i>"}), # {'fields': ('db_permissions', 'db_lock_storage'),
("In-game Account data", "description": "<i>These are permissions/locks for in-game use. "
{'fields': ('db_typeclass_path', 'db_cmdset_storage'), "They are unrelated to website access rights.</i>",
'description': "<i>These fields define in-game-specific properties " },
"for the Account object in-game.</i>"})) ),
(
"In-game Account data",
{
"fields": ("db_typeclass_path", "db_cmdset_storage"),
"description": "<i>These fields define in-game-specific properties "
"for the Account object in-game.</i>",
},
),
)
extra = 1 extra = 1
max_num = 1 max_num = 1
@ -170,6 +184,7 @@ class AccountTagInline(TagInline):
Inline Account Tags. Inline Account Tags.
""" """
model = AccountDB.db_tags.through model = AccountDB.db_tags.through
related_field = "accountdb" related_field = "accountdb"
@ -179,6 +194,7 @@ class AccountAttributeInline(AttributeInline):
Inline Account Attributes. Inline Account Attributes.
""" """
model = AccountDB.db_attributes.through model = AccountDB.db_attributes.through
related_field = "accountdb" related_field = "accountdb"
@ -189,30 +205,43 @@ class AccountDBAdmin(BaseUserAdmin):
""" """
list_display = ('username', 'email', 'is_staff', 'is_superuser') list_display = ("username", "email", "is_staff", "is_superuser")
form = AccountDBChangeForm form = AccountDBChangeForm
add_form = AccountDBCreationForm add_form = AccountDBCreationForm
inlines = [AccountTagInline, AccountAttributeInline] inlines = [AccountTagInline, AccountAttributeInline]
fieldsets = ( fieldsets = (
(None, {'fields': ('username', 'password', 'email')}), (None, {"fields": ("username", "password", "email")}),
('Website profile', { (
'fields': ('first_name', 'last_name'), "Website profile",
'description': "<i>These are not used " {
"in the default system.</i>"}), "fields": ("first_name", "last_name"),
('Website dates', { "description": "<i>These are not used " "in the default system.</i>",
'fields': ('last_login', 'date_joined'), },
'description': '<i>Relevant only to the website.</i>'}), ),
('Website Permissions', { (
'fields': ('is_active', 'is_staff', 'is_superuser', "Website dates",
'user_permissions', 'groups'), {
'description': "<i>These are permissions/permission groups for " "fields": ("last_login", "date_joined"),
"accessing the admin site. They are unrelated to " "description": "<i>Relevant only to the website.</i>",
"in-game access rights.</i>"}), },
('Game Options', { ),
'fields': ('db_typeclass_path', 'db_cmdset_storage', (
'db_lock_storage'), "Website Permissions",
'description': '<i>These are attributes that are more relevant ' {
'to gameplay.</i>'})) "fields": ("is_active", "is_staff", "is_superuser", "user_permissions", "groups"),
"description": "<i>These are permissions/permission groups for "
"accessing the admin site. They are unrelated to "
"in-game access rights.</i>",
},
),
(
"Game Options",
{
"fields": ("db_typeclass_path", "db_cmdset_storage", "db_lock_storage"),
"description": "<i>These are attributes that are more relevant " "to gameplay.</i>",
},
),
)
# ('Game Options', {'fields': ( # ('Game Options', {'fields': (
# 'db_typeclass_path', 'db_cmdset_storage', # 'db_typeclass_path', 'db_cmdset_storage',
# 'db_permissions', 'db_lock_storage'), # 'db_permissions', 'db_lock_storage'),
@ -220,10 +249,15 @@ class AccountDBAdmin(BaseUserAdmin):
# 'more relevant to gameplay.</i>'})) # 'more relevant to gameplay.</i>'}))
add_fieldsets = ( add_fieldsets = (
(None, (
{'fields': ('username', 'password1', 'password2', 'email'), None,
'description': "<i>These account details are shared by the admin " {
"system and the game.</i>"},),) "fields": ("username", "password1", "password2", "email"),
"description": "<i>These account details are shared by the admin "
"system and the game.</i>",
},
),
)
def save_model(self, request, obj, form, change): def save_model(self, request, obj, form, change):
""" """
@ -246,6 +280,7 @@ class AccountDBAdmin(BaseUserAdmin):
def response_add(self, request, obj, post_url_continue=None): def response_add(self, request, obj, post_url_continue=None):
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id])) return HttpResponseRedirect(reverse("admin:accounts_accountdb_change", args=[obj.id]))

View file

@ -23,6 +23,7 @@ _SESSIONS = None
# Bot helper utilities # Bot helper utilities
class BotStarter(DefaultScript): class BotStarter(DefaultScript):
""" """
This non-repeating script has the This non-repeating script has the
@ -80,6 +81,7 @@ class BotStarter(DefaultScript):
""" """
self.db.started = False self.db.started = False
# #
# Bot base class # Bot base class
@ -100,8 +102,10 @@ class Bot(DefaultAccount):
# the text encoding to use. # the text encoding to use.
self.db.encoding = "utf-8" self.db.encoding = "utf-8"
# A basic security setup (also avoid idle disconnects) # A basic security setup (also avoid idle disconnects)
lockstring = "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);" \ lockstring = (
"boot:perm(Admin);msg:false();noidletimeout:true()" "examine:perm(Admin);edit:perm(Admin);delete:perm(Admin);"
"boot:perm(Admin);msg:false();noidletimeout:true()"
)
self.locks.add(lockstring) self.locks.add(lockstring)
# set the basics of being a bot # set the basics of being a bot
script_key = str(self.key) script_key = str(self.key)
@ -143,16 +147,25 @@ class Bot(DefaultAccount):
# IRC # IRC
class IRCBot(Bot): class IRCBot(Bot):
""" """
Bot for handling IRC connections. Bot for handling IRC connections.
""" """
# override this on a child class to use custom factory # override this on a child class to use custom factory
factory_path = "evennia.server.portal.irc.IRCBotFactory" factory_path = "evennia.server.portal.irc.IRCBotFactory"
def start(self, ev_channel=None, irc_botname=None, irc_channel=None, def start(
irc_network=None, irc_port=None, irc_ssl=None): self,
ev_channel=None,
irc_botname=None,
irc_channel=None,
irc_network=None,
irc_port=None,
irc_ssl=None,
):
""" """
Start by telling the portal to start a new session. Start by telling the portal to start a new session.
@ -202,12 +215,14 @@ class IRCBot(Bot):
# instruct the server and portal to create a new session with # instruct the server and portal to create a new session with
# the stored configuration # the stored configuration
configdict = {"uid": self.dbid, configdict = {
"botname": self.db.irc_botname, "uid": self.dbid,
"channel": self.db.irc_channel, "botname": self.db.irc_botname,
"network": self.db.irc_network, "channel": self.db.irc_channel,
"port": self.db.irc_port, "network": self.db.irc_network,
"ssl": self.db.irc_ssl} "port": self.db.irc_port,
"ssl": self.db.irc_ssl,
}
_SESSIONS.start_bot_session(self.factory_path, configdict) _SESSIONS.start_bot_session(self.factory_path, configdict)
def at_msg_send(self, **kwargs): def at_msg_send(self, **kwargs):
@ -275,8 +290,11 @@ class IRCBot(Bot):
# cache channel lookup # cache channel lookup
self.ndb.ev_channel = self.db.ev_channel self.ndb.ev_channel = self.db.ev_channel
if ("from_channel" in options and text and if (
self.ndb.ev_channel.dbid == options["from_channel"]): "from_channel" in options
and text
and self.ndb.ev_channel.dbid == options["from_channel"]
):
if not from_obj or from_obj != [self]: if not from_obj or from_obj != [self]:
super().msg(channel=text) super().msg(channel=text)
@ -338,9 +356,14 @@ class IRCBot(Bot):
delta_cmd = t0 - sess.cmd_last_visible delta_cmd = t0 - sess.cmd_last_visible
delta_conn = t0 - session.conn_time delta_conn = t0 - session.conn_time
account = sess.get_account() account = sess.get_account()
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % account.name, width=25), whos.append(
utils.time_format(delta_conn, 0), "%s (%s/%s)"
utils.time_format(delta_cmd, 1))) % (
utils.crop("|w%s|n" % account.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1),
)
)
text = f"Who list (online/idle): {', '.join(sorted(whos, key=lambda w: w.lower()))}" text = f"Who list (online/idle): {', '.join(sorted(whos, key=lambda w: w.lower()))}"
elif txt.lower().startswith("about"): elif txt.lower().startswith("about"):
# some bot info # some bot info
@ -364,6 +387,7 @@ class IRCBot(Bot):
if self.ndb.ev_channel: if self.ndb.ev_channel:
self.ndb.ev_channel.msg(text, senders=self) self.ndb.ev_channel.msg(text, senders=self)
# #
# RSS # RSS
@ -410,9 +434,7 @@ class RSSBot(Bot):
self.db.rss_rate = rss_rate self.db.rss_rate = rss_rate
# instruct the server and portal to create a new session with # instruct the server and portal to create a new session with
# the stored configuration # the stored configuration
configdict = {"uid": self.dbid, configdict = {"uid": self.dbid, "url": self.db.rss_url, "rate": self.db.rss_rate}
"url": self.db.rss_url,
"rate": self.db.rss_rate}
_SESSIONS.start_bot_session("evennia.server.portal.rss.RSSBotFactory", configdict) _SESSIONS.start_bot_session("evennia.server.portal.rss.RSSBotFactory", configdict)
def execute_cmd(self, txt=None, session=None, **kwargs): def execute_cmd(self, txt=None, session=None, **kwargs):
@ -437,12 +459,14 @@ class RSSBot(Bot):
# Grapevine bot # Grapevine bot
class GrapevineBot(Bot): class GrapevineBot(Bot):
""" """
g Grapevine (https://grapevine.haus) relayer. The channel to connect to is the first g Grapevine (https://grapevine.haus) relayer. The channel to connect to is the first
name in the settings.GRAPEVINE_CHANNELS list. name in the settings.GRAPEVINE_CHANNELS list.
""" """
factory_path = "evennia.server.portal.grapevine.RestartingWebsocketServerFactory" factory_path = "evennia.server.portal.grapevine.RestartingWebsocketServerFactory"
def start(self, ev_channel=None, grapevine_channel=None): def start(self, ev_channel=None, grapevine_channel=None):
@ -472,8 +496,7 @@ class GrapevineBot(Bot):
self.db.grapevine_channel = grapevine_channel self.db.grapevine_channel = grapevine_channel
# these will be made available as properties on the protocol factory # these will be made available as properties on the protocol factory
configdict = {"uid": self.dbid, configdict = {"uid": self.dbid, "grapevine_channel": self.db.grapevine_channel}
"grapevine_channel": self.db.grapevine_channel}
_SESSIONS.start_bot_session(self.factory_path, configdict) _SESSIONS.start_bot_session(self.factory_path, configdict)
@ -501,8 +524,11 @@ class GrapevineBot(Bot):
# cache channel lookup # cache channel lookup
self.ndb.ev_channel = self.db.ev_channel self.ndb.ev_channel = self.db.ev_channel
if ("from_channel" in options and text and if (
self.ndb.ev_channel.dbid == options["from_channel"]): "from_channel" in options
and text
and self.ndb.ev_channel.dbid == options["from_channel"]
):
if not from_obj or from_obj != [self]: if not from_obj or from_obj != [self]:
# send outputfunc channel(msg, chan, sender) # send outputfunc channel(msg, chan, sender)
@ -511,11 +537,25 @@ class GrapevineBot(Bot):
# prefix since we pass that explicitly anyway # prefix since we pass that explicitly anyway
prefix, text = text.split(":", 1) prefix, text = text.split(":", 1)
super().msg(channel=(text.strip(), self.db.grapevine_channel, super().msg(
", ".join(obj.key for obj in from_obj), {})) channel=(
text.strip(),
self.db.grapevine_channel,
", ".join(obj.key for obj in from_obj),
{},
)
)
def execute_cmd(self, txt=None, session=None, event=None, grapevine_channel=None, def execute_cmd(
sender=None, game=None, **kwargs): self,
txt=None,
session=None,
event=None,
grapevine_channel=None,
sender=None,
game=None,
**kwargs,
):
""" """
Take incoming data from protocol and send it to connected channel. This is Take incoming data from protocol and send it to connected channel. This is
triggered by the bot_data_in Inputfunc. triggered by the bot_data_in Inputfunc.

View file

@ -5,7 +5,8 @@ The managers for the custom Account object and permissions.
import datetime import datetime
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.models import UserManager from django.contrib.auth.models import UserManager
from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager) from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
__all__ = ("AccountManager",) __all__ = ("AccountManager",)
@ -13,6 +14,7 @@ __all__ = ("AccountManager",)
# Account Manager # Account Manager
# #
class AccountDBManager(TypedObjectManager, UserManager): class AccountDBManager(TypedObjectManager, UserManager):
""" """
This AccountManager implements methods for searching This AccountManager implements methods for searching
@ -90,8 +92,7 @@ class AccountDBManager(TypedObjectManager, UserManager):
end_date = timezone.now() end_date = timezone.now()
tdelta = datetime.timedelta(days) tdelta = datetime.timedelta(days)
start_date = end_date - tdelta start_date = end_date - tdelta
return self.filter(last_login__range=( return self.filter(last_login__range=(start_date, end_date)).order_by("-last_login")
start_date, end_date)).order_by('-last_login')
def get_account_from_email(self, uemail): def get_account_from_email(self, uemail):
""" """
@ -176,7 +177,8 @@ class AccountDBManager(TypedObjectManager, UserManager):
# try alias match # try alias match
matches = self.filter( matches = self.filter(
db_tags__db_tagtype__iexact="alias", db_tags__db_tagtype__iexact="alias",
**{"db_tags__db_key__iexact" if exact else "db_tags__db_key__icontains": ostring}) **{"db_tags__db_key__iexact" if exact else "db_tags__db_key__icontains": ostring},
)
return matches return matches
# back-compatibility alias # back-compatibility alias

View file

@ -8,42 +8,168 @@ import django.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("auth", "0001_initial"), ("typeclasses", "0001_initial")]
('auth', '0001_initial'),
('typeclasses', '0001_initial'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='AccountDB', name="AccountDB",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('password', models.CharField(max_length=128, verbose_name='password')), "id",
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')), models.AutoField(
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), verbose_name="ID", serialize=False, auto_created=True, primary_key=True
('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])), ),
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)), ),
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)), ("password", models.CharField(max_length=128, verbose_name="password")),
('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)), (
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), "last_login",
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), models.DateTimeField(
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), default=django.utils.timezone.now, verbose_name="last login"
('db_key', models.CharField(max_length=255, verbose_name='key', db_index=True)), ),
('db_typeclass_path', models.CharField(help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name='typeclass')), ),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), (
('db_lock_storage', models.TextField(help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name='locks', blank=True)), "is_superuser",
('db_is_connected', models.BooleanField(default=False, help_text='If account is connected to game or not', verbose_name='is_connected')), models.BooleanField(
('db_cmdset_storage', models.CharField(help_text='optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name='cmdset')), default=False,
('db_is_bot', models.BooleanField(default=False, help_text='Used to identify irc/rss bots', verbose_name='is_bot')), help_text="Designates that this user has all permissions without explicitly assigning them.",
('db_attributes', models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)), verbose_name="superuser status",
('db_tags', models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True)), ),
('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')), ),
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')), (
"username",
models.CharField(
help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.",
unique=True,
max_length=30,
verbose_name="username",
validators=[
django.core.validators.RegexValidator(
"^[\\w.@+-]+$", "Enter a valid username.", "invalid"
)
],
),
),
(
"first_name",
models.CharField(max_length=30, verbose_name="first name", blank=True),
),
(
"last_name",
models.CharField(max_length=30, verbose_name="last name", blank=True),
),
(
"email",
models.EmailField(max_length=75, verbose_name="email address", blank=True),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("db_key", models.CharField(max_length=255, verbose_name="key", db_index=True)),
(
"db_typeclass_path",
models.CharField(
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.",
max_length=255,
null=True,
verbose_name="typeclass",
),
),
(
"db_date_created",
models.DateTimeField(auto_now_add=True, verbose_name="creation date"),
),
(
"db_lock_storage",
models.TextField(
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.",
verbose_name="locks",
blank=True,
),
),
(
"db_is_connected",
models.BooleanField(
default=False,
help_text="If account is connected to game or not",
verbose_name="is_connected",
),
),
(
"db_cmdset_storage",
models.CharField(
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.",
max_length=255,
null=True,
verbose_name="cmdset",
),
),
(
"db_is_bot",
models.BooleanField(
default=False,
help_text="Used to identify irc/rss bots",
verbose_name="is_bot",
),
),
(
"db_attributes",
models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
null=True,
),
),
(
"db_tags",
models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
null=True,
),
),
(
"groups",
models.ManyToManyField(
related_query_name="user",
related_name="user_set",
to="auth.Group",
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of his/her group.",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
related_query_name="user",
related_name="user_set",
to="auth.Permission",
blank=True,
help_text="Specific permissions for this user.",
verbose_name="user permissions",
),
),
], ],
options={ options={"verbose_name": "Account", "verbose_name_plural": "Accounts"},
'verbose_name': 'Account',
'verbose_name_plural': 'Accounts',
},
bases=(models.Model,), bases=(models.Model,),
), )
] ]

View file

@ -13,10 +13,6 @@ def convert_defaults(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounts", "0001_initial")]
('accounts', '0001_initial'),
]
operations = [ operations = [migrations.RunPython(convert_defaults)]
migrations.RunPython(convert_defaults),
]

View file

@ -6,31 +6,17 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounts", "0002_move_defaults")]
('accounts', '0002_move_defaults'),
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='DefaultAccount', name="DefaultAccount", fields=[], options={"proxy": True}, bases=("accounts.accountdb",)
fields=[
],
options={
'proxy': True,
},
bases=('accounts.accountdb',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='DefaultGuest', name="DefaultGuest",
fields=[ fields=[],
], options={"proxy": True},
options={ bases=("accounts.defaultaccount",),
'proxy': True,
},
bases=('accounts.defaultaccount',),
),
migrations.AlterModelOptions(
name='accountdb',
options={'verbose_name': 'Account'},
), ),
migrations.AlterModelOptions(name="accountdb", options={"verbose_name": "Account"}),
] ]

View file

@ -8,41 +8,52 @@ import django.core.validators
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounts", "0003_auto_20150209_2234")]
('accounts', '0003_auto_20150209_2234'),
]
operations = [ operations = [
migrations.DeleteModel( migrations.DeleteModel(name="DefaultGuest"),
name='DefaultGuest', migrations.DeleteModel(name="DefaultAccount"),
),
migrations.DeleteModel(
name='DefaultAccount',
),
migrations.AlterModelManagers( migrations.AlterModelManagers(
name='accountdb', name="accountdb", managers=[("objects", evennia.accounts.manager.AccountDBManager())]
managers=[
('objects', evennia.accounts.manager.AccountDBManager()),
],
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='email', name="email",
field=models.EmailField(max_length=254, verbose_name='email address', blank=True), field=models.EmailField(max_length=254, verbose_name="email address", blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='groups', name="groups",
field=models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups'), field=models.ManyToManyField(
related_query_name="user",
related_name="user_set",
to="auth.Group",
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
verbose_name="groups",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='last_login', name="last_login",
field=models.DateTimeField(null=True, verbose_name='last login', blank=True), field=models.DateTimeField(null=True, verbose_name="last login", blank=True),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='username', name="username",
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username'), field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
max_length=30,
validators=[
django.core.validators.RegexValidator(
"^[\\w.@+-]+$",
"Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.",
"invalid",
)
],
help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.",
unique=True,
verbose_name="username",
),
), ),
] ]

View file

@ -8,14 +8,24 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounts", "0004_auto_20150403_2339")]
('accounts', '0004_auto_20150403_2339'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='username', name="username",
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username'), field=models.CharField(
), error_messages={"unique": "A user with that username already exists."},
help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=30,
unique=True,
validators=[
django.core.validators.RegexValidator(
"^[\\w.@+-]+$",
"Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.",
)
],
verbose_name="username",
),
)
] ]

View file

@ -8,24 +8,35 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounts", "0005_auto_20160905_0902")]
('accounts', '0005_auto_20160905_0902'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_attributes', name="db_attributes",
field=models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), field=models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_tags', name="db_tags",
field=models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), field=models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='username', name="username",
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.ASCIIUsernameValidator()], verbose_name='username'), field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.ASCIIUsernameValidator()],
verbose_name="username",
),
), ),
] ]

View file

@ -8,31 +8,33 @@ from django.db import migrations
def forwards(apps, schema_editor): def forwards(apps, schema_editor):
try: try:
PlayerDB = apps.get_model('players', 'PlayerDB') PlayerDB = apps.get_model("players", "PlayerDB")
except LookupError: except LookupError:
# playerdb not available. Skip. # playerdb not available. Skip.
return return
AccountDB = apps.get_model('accounts', 'AccountDB') AccountDB = apps.get_model("accounts", "AccountDB")
for player in PlayerDB.objects.all(): for player in PlayerDB.objects.all():
account = AccountDB(id=player.id, account = AccountDB(
password=player.password, id=player.id,
is_superuser=player.is_superuser, password=player.password,
last_login=player.last_login, is_superuser=player.is_superuser,
username=player.username, last_login=player.last_login,
first_name=player.first_name, username=player.username,
last_name=player.last_name, first_name=player.first_name,
email=player.email, last_name=player.last_name,
is_staff=player.is_staff, email=player.email,
is_active=player.is_active, is_staff=player.is_staff,
date_joined=player.date_joined, is_active=player.is_active,
db_key=player.db_key, date_joined=player.date_joined,
db_typeclass_path=player.db_typeclass_path, db_key=player.db_key,
db_date_created=player.db_date_created, db_typeclass_path=player.db_typeclass_path,
db_lock_storage=player.db_lock_storage, db_date_created=player.db_date_created,
db_is_connected=player.db_is_connected, db_lock_storage=player.db_lock_storage,
db_cmdset_storage=player.db_cmdset_storage, db_is_connected=player.db_is_connected,
db_is_bot=player.db_is_bot) db_cmdset_storage=player.db_cmdset_storage,
db_is_bot=player.db_is_bot,
)
account.save() account.save()
for group in player.groups.all(): for group in player.groups.all():
account.groups.add(group) account.groups.add(group)
@ -46,13 +48,9 @@ def forwards(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounts", "0006_auto_20170606_1731")]
('accounts', '0006_auto_20170606_1731'),
]
operations = [ operations = [migrations.RunPython(forwards, migrations.RunPython.noop)]
migrations.RunPython(forwards, migrations.RunPython.noop)
]
if global_apps.is_installed('players'): if global_apps.is_installed("players"):
dependencies.append(('players', '0006_auto_20170606_1731')) dependencies.append(("players", "0006_auto_20170606_1731"))

View file

@ -8,65 +8,93 @@ import evennia.accounts.manager
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("accounts", "0007_copy_player_to_account")]
('accounts', '0007_copy_player_to_account'),
]
operations = [ operations = [
migrations.AlterModelManagers( migrations.AlterModelManagers(
name='accountdb', name="accountdb", managers=[("objects", evennia.accounts.manager.AccountDBManager())]
managers=[
('objects', evennia.accounts.manager.AccountDBManager()),
],
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_attributes', name="db_attributes",
field=models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), field=models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_cmdset_storage', name="db_cmdset_storage",
field=models.CharField(help_text='optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name='cmdset'), field=models.CharField(
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.",
max_length=255,
null=True,
verbose_name="cmdset",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_date_created', name="db_date_created",
field=models.DateTimeField(auto_now_add=True, verbose_name='creation date'), field=models.DateTimeField(auto_now_add=True, verbose_name="creation date"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_is_bot', name="db_is_bot",
field=models.BooleanField(default=False, help_text='Used to identify irc/rss bots', verbose_name='is_bot'), field=models.BooleanField(
default=False, help_text="Used to identify irc/rss bots", verbose_name="is_bot"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_is_connected', name="db_is_connected",
field=models.BooleanField(default=False, help_text='If player is connected to game or not', verbose_name='is_connected'), field=models.BooleanField(
default=False,
help_text="If player is connected to game or not",
verbose_name="is_connected",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_key', name="db_key",
field=models.CharField(db_index=True, max_length=255, verbose_name='key'), field=models.CharField(db_index=True, max_length=255, verbose_name="key"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_lock_storage', name="db_lock_storage",
field=models.TextField(blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name='locks'), field=models.TextField(
blank=True,
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.",
verbose_name="locks",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_tags', name="db_tags",
field=models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), field=models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='db_typeclass_path', name="db_typeclass_path",
field=models.CharField(help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name='typeclass'), field=models.CharField(
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.",
max_length=255,
null=True,
verbose_name="typeclass",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='accountdb', model_name="accountdb",
name='username', name="username",
field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'), field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
verbose_name="username",
),
), ),
] ]

View file

@ -27,8 +27,8 @@ from evennia.server.signals import SIGNAL_ACCOUNT_POST_RENAME
__all__ = ("AccountDB",) __all__ = ("AccountDB",)
#_ME = _("me") # _ME = _("me")
#_SELF = _("self") # _SELF = _("self")
_MULTISESSION_MODE = settings.MULTISESSION_MODE _MULTISESSION_MODE = settings.MULTISESSION_MODE
@ -39,11 +39,12 @@ _DA = object.__delattr__
_TYPECLASS = None _TYPECLASS = None
#------------------------------------------------------------ # ------------------------------------------------------------
# #
# AccountDB # AccountDB
# #
#------------------------------------------------------------ # ------------------------------------------------------------
class AccountDB(TypedObject, AbstractUser): class AccountDB(TypedObject, AbstractUser):
""" """
@ -83,14 +84,22 @@ class AccountDB(TypedObject, AbstractUser):
# store a connected flag here too, not just in sessionhandler. # store a connected flag here too, not just in sessionhandler.
# This makes it easier to track from various out-of-process locations # This makes it easier to track from various out-of-process locations
db_is_connected = models.BooleanField(default=False, db_is_connected = models.BooleanField(
verbose_name="is_connected", default=False,
help_text="If player is connected to game or not") verbose_name="is_connected",
help_text="If player is connected to game or not",
)
# database storage of persistant cmdsets. # database storage of persistant cmdsets.
db_cmdset_storage = models.CharField('cmdset', max_length=255, null=True, db_cmdset_storage = models.CharField(
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.") "cmdset",
max_length=255,
null=True,
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.",
)
# marks if this is a "virtual" bot account object # marks if this is a "virtual" bot account object
db_is_bot = models.BooleanField(default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots") db_is_bot = models.BooleanField(
default=False, verbose_name="is_bot", help_text="Used to identify irc/rss bots"
)
# Database manager # Database manager
objects = AccountDBManager() objects = AccountDBManager()
@ -101,20 +110,20 @@ class AccountDB(TypedObject, AbstractUser):
__applabel__ = "accounts" __applabel__ = "accounts"
class Meta(object): class Meta(object):
verbose_name = 'Account' verbose_name = "Account"
# cmdset_storage property # cmdset_storage property
# This seems very sensitive to caching, so leaving it be for now /Griatch # This seems very sensitive to caching, so leaving it be for now /Griatch
#@property # @property
def __cmdset_storage_get(self): def __cmdset_storage_get(self):
""" """
Getter. Allows for value = self.name. Returns a list of cmdset_storage. Getter. Allows for value = self.name. Returns a list of cmdset_storage.
""" """
storage = self.db_cmdset_storage storage = self.db_cmdset_storage
# we need to check so storage is not None # we need to check so storage is not None
return [path.strip() for path in storage.split(',')] if storage else [] return [path.strip() for path in storage.split(",")] if storage else []
#@cmdset_storage.setter # @cmdset_storage.setter
def __cmdset_storage_set(self, value): def __cmdset_storage_set(self, value):
""" """
Setter. Allows for self.name = value. Stores as a comma-separated Setter. Allows for self.name = value. Stores as a comma-separated
@ -123,11 +132,12 @@ class AccountDB(TypedObject, AbstractUser):
_SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value))) _SA(self, "db_cmdset_storage", ",".join(str(val).strip() for val in make_iter(value)))
_GA(self, "save")() _GA(self, "save")()
#@cmdset_storage.deleter # @cmdset_storage.deleter
def __cmdset_storage_del(self): def __cmdset_storage_del(self):
"Deleter. Allows for del self.name" "Deleter. Allows for del self.name"
_SA(self, "db_cmdset_storage", None) _SA(self, "db_cmdset_storage", None)
_GA(self, "save")() _GA(self, "save")()
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del) cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
# #
@ -140,7 +150,7 @@ class AccountDB(TypedObject, AbstractUser):
def __repr__(self): def __repr__(self):
return f"{self.name}(account#{self.dbid})" return f"{self.name}(account#{self.dbid})"
#@property # @property
def __username_get(self): def __username_get(self):
return self.username return self.username
@ -157,7 +167,7 @@ class AccountDB(TypedObject, AbstractUser):
name = property(__username_get, __username_set, __username_del) name = property(__username_get, __username_set, __username_del)
key = property(__username_get, __username_set, __username_del) key = property(__username_get, __username_set, __username_del)
#@property # @property
def __uid_get(self): def __uid_get(self):
"Getter. Retrieves the user id" "Getter. Retrieves the user id"
return self.id return self.id
@ -167,4 +177,5 @@ class AccountDB(TypedObject, AbstractUser):
def __uid_del(self): def __uid_del(self):
raise Exception("User id cannot be deleted!") raise Exception("User id cannot be deleted!")
uid = property(__uid_get, __uid_set, __uid_del) uid = property(__uid_get, __uid_set, __uid_del)

View file

@ -20,12 +20,15 @@ class TestAccountSessionHandler(TestCase):
def setUp(self): def setUp(self):
self.account = create.create_account( self.account = create.create_account(
f"TestAccount{randint(0, 999999)}", email="test@test.com", f"TestAccount{randint(0, 999999)}",
password="testpassword", typeclass=DefaultAccount) email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.handler = AccountSessionHandler(self.account) self.handler = AccountSessionHandler(self.account)
def tearDown(self): def tearDown(self):
if hasattr(self, 'account'): if hasattr(self, "account"):
self.account.delete() self.account.delete()
def test_get(self): def test_get(self):
@ -67,22 +70,24 @@ class TestAccountSessionHandler(TestCase):
class TestDefaultGuest(EvenniaTest): class TestDefaultGuest(EvenniaTest):
"Check DefaultGuest class" "Check DefaultGuest class"
ip = '212.216.134.22' ip = "212.216.134.22"
@override_settings(GUEST_ENABLED=False) @override_settings(GUEST_ENABLED=False)
def test_create_not_enabled(self): def test_create_not_enabled(self):
# Guest account should not be permitted # Guest account should not be permitted
account, errors = DefaultGuest.authenticate(ip=self.ip) account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertFalse(account, 'Guest account was created despite being disabled.') self.assertFalse(account, "Guest account was created despite being disabled.")
def test_authenticate(self): def test_authenticate(self):
# Create a guest account # Create a guest account
account, errors = DefaultGuest.authenticate(ip=self.ip) account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertTrue(account, 'Guest account should have been created.') self.assertTrue(account, "Guest account should have been created.")
# Create a second guest account # Create a second guest account
account, errors = DefaultGuest.authenticate(ip=self.ip) account, errors = DefaultGuest.authenticate(ip=self.ip)
self.assertFalse(account, 'Two guest accounts were created with a single entry on the guest list!') self.assertFalse(
account, "Two guest accounts were created with a single entry on the guest list!"
)
@patch("evennia.accounts.accounts.ChannelDB.objects.get_channel") @patch("evennia.accounts.accounts.ChannelDB.objects.get_channel")
def test_create(self, get_channel): def test_create(self, get_channel):
@ -112,42 +117,52 @@ class TestDefaultGuest(EvenniaTest):
class TestDefaultAccountAuth(EvenniaTest): class TestDefaultAccountAuth(EvenniaTest):
def setUp(self): def setUp(self):
super(TestDefaultAccountAuth, self).setUp() super(TestDefaultAccountAuth, self).setUp()
self.password = "testpassword" self.password = "testpassword"
self.account.delete() self.account.delete()
self.account = create.create_account(f"TestAccount{randint(100000, 999999)}", email="test@test.com", password=self.password, typeclass=DefaultAccount) self.account = create.create_account(
f"TestAccount{randint(100000, 999999)}",
email="test@test.com",
password=self.password,
typeclass=DefaultAccount,
)
def test_authentication(self): def test_authentication(self):
"Confirm Account authentication method is authenticating/denying users." "Confirm Account authentication method is authenticating/denying users."
# Valid credentials # Valid credentials
obj, errors = DefaultAccount.authenticate(self.account.name, self.password) obj, errors = DefaultAccount.authenticate(self.account.name, self.password)
self.assertTrue(obj, 'Account did not authenticate given valid credentials.') self.assertTrue(obj, "Account did not authenticate given valid credentials.")
# Invalid credentials # Invalid credentials
obj, errors = DefaultAccount.authenticate(self.account.name, 'xyzzy') obj, errors = DefaultAccount.authenticate(self.account.name, "xyzzy")
self.assertFalse(obj, 'Account authenticated using invalid credentials.') self.assertFalse(obj, "Account authenticated using invalid credentials.")
def test_create(self): def test_create(self):
"Confirm Account creation is working as expected." "Confirm Account creation is working as expected."
# Create a normal account # Create a normal account
account, errors = DefaultAccount.create(username='ziggy', password='stardust11') account, errors = DefaultAccount.create(username="ziggy", password="stardust11")
self.assertTrue(account, 'New account should have been created.') self.assertTrue(account, "New account should have been created.")
# Try creating a duplicate account # Try creating a duplicate account
account2, errors = DefaultAccount.create(username='Ziggy', password='starman11') account2, errors = DefaultAccount.create(username="Ziggy", password="starman11")
self.assertFalse(account2, 'Duplicate account name should not have been allowed.') self.assertFalse(account2, "Duplicate account name should not have been allowed.")
account.delete() account.delete()
def test_throttle(self): def test_throttle(self):
"Confirm throttle activates on too many failures." "Confirm throttle activates on too many failures."
for x in range(20): for x in range(20):
obj, errors = DefaultAccount.authenticate(self.account.name, 'xyzzy', ip='12.24.36.48') obj, errors = DefaultAccount.authenticate(self.account.name, "xyzzy", ip="12.24.36.48")
self.assertFalse(obj, 'Authentication was provided a bogus password; this should NOT have returned an account!') self.assertFalse(
obj,
"Authentication was provided a bogus password; this should NOT have returned an account!",
)
self.assertTrue('too many login failures' in errors[-1].lower(), 'Failed logins should have been throttled.') self.assertTrue(
"too many login failures" in errors[-1].lower(),
"Failed logins should have been throttled.",
)
def test_username_validation(self): def test_username_validation(self):
"Check username validators deny relevant usernames" "Check username validators deny relevant usernames"
@ -156,7 +171,7 @@ class TestDefaultAccountAuth(EvenniaTest):
if not uses_database("mysql"): if not uses_database("mysql"):
# TODO As of Mar 2019, mysql does not pass this test due to collation problems # TODO As of Mar 2019, mysql does not pass this test due to collation problems
# that has not been possible to resolve # that has not been possible to resolve
result, error = DefaultAccount.validate_username('¯\_(ツ)_/¯') result, error = DefaultAccount.validate_username("¯\_(ツ)_/¯")
self.assertFalse(result, "Validator allowed kanji in username.") self.assertFalse(result, "Validator allowed kanji in username.")
# Should not allow duplicate username # Should not allow duplicate username
@ -164,35 +179,44 @@ class TestDefaultAccountAuth(EvenniaTest):
self.assertFalse(result, "Duplicate username should not have passed validation.") self.assertFalse(result, "Duplicate username should not have passed validation.")
# Should not allow username too short # Should not allow username too short
result, error = DefaultAccount.validate_username('xx') result, error = DefaultAccount.validate_username("xx")
self.assertFalse(result, "2-character username passed validation.") self.assertFalse(result, "2-character username passed validation.")
def test_password_validation(self): def test_password_validation(self):
"Check password validators deny bad passwords" "Check password validators deny bad passwords"
account = create.create_account(f"TestAccount{randint(100000, 999999)}", account = create.create_account(
email="test@test.com", password="testpassword", typeclass=DefaultAccount) f"TestAccount{randint(100000, 999999)}",
for bad in ('', '123', 'password', 'TestAccount', '#', 'xyzzy'): email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
for bad in ("", "123", "password", "TestAccount", "#", "xyzzy"):
self.assertFalse(account.validate_password(bad, account=self.account)[0]) self.assertFalse(account.validate_password(bad, account=self.account)[0])
"Check validators allow sufficiently complex passwords" "Check validators allow sufficiently complex passwords"
for better in ('Mxyzptlk', "j0hn, i'M 0n1y d4nc1nG"): for better in ("Mxyzptlk", "j0hn, i'M 0n1y d4nc1nG"):
self.assertTrue(account.validate_password(better, account=self.account)[0]) self.assertTrue(account.validate_password(better, account=self.account)[0])
account.delete() account.delete()
def test_password_change(self): def test_password_change(self):
"Check password setting and validation is working as expected" "Check password setting and validation is working as expected"
account = create.create_account(f"TestAccount{randint(100000, 999999)}", account = create.create_account(
email="test@test.com", password="testpassword", typeclass=DefaultAccount) f"TestAccount{randint(100000, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
# Try setting some bad passwords # Try setting some bad passwords
for bad in ('', '#', 'TestAccount', 'password'): for bad in ("", "#", "TestAccount", "password"):
valid, error = account.validate_password(bad, account) valid, error = account.validate_password(bad, account)
self.assertFalse(valid) self.assertFalse(valid)
# Try setting a better password (test for False; returns None on success) # Try setting a better password (test for False; returns None on success)
self.assertFalse(account.set_password('Mxyzptlk')) self.assertFalse(account.set_password("Mxyzptlk"))
account.delete() account.delete()
@ -228,8 +252,11 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler import evennia.server.sessionhandler
account = create.create_account( account = create.create_account(
f"TestAccount{randint(0, 999999)}", email="test@test.com", f"TestAccount{randint(0, 999999)}",
password="testpassword", typeclass=DefaultAccount) email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.s1.uid = account.uid self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -239,7 +266,9 @@ class TestDefaultAccount(TestCase):
obj = Mock() obj = Mock()
self.s1.puppet = obj self.s1.puppet = obj
account.puppet_object(self.s1, obj) account.puppet_object(self.s1, obj)
self.s1.data_out.assert_called_with(options=None, text="You are already puppeting this object.") self.s1.data_out.assert_called_with(
options=None, text="You are already puppeting this object."
)
self.assertIsNone(obj.at_post_puppet.call_args) self.assertIsNone(obj.at_post_puppet.call_args)
def test_puppet_object_no_permission(self): def test_puppet_object_no_permission(self):
@ -247,7 +276,12 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler import evennia.server.sessionhandler
account = create.create_account(f"TestAccount{randint(0, 999999)}", email="test@test.com", password="testpassword", typeclass=DefaultAccount) account = create.create_account(
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.s1.uid = account.uid self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -257,7 +291,9 @@ class TestDefaultAccount(TestCase):
account.puppet_object(self.s1, obj) account.puppet_object(self.s1, obj)
self.assertTrue(self.s1.data_out.call_args[1]['text'].startswith("You don't have permission to puppet")) self.assertTrue(
self.s1.data_out.call_args[1]["text"].startswith("You don't have permission to puppet")
)
self.assertIsNone(obj.at_post_puppet.call_args) self.assertIsNone(obj.at_post_puppet.call_args)
@override_settings(MULTISESSION_MODE=0) @override_settings(MULTISESSION_MODE=0)
@ -266,7 +302,12 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler import evennia.server.sessionhandler
account = create.create_account(f"TestAccount{randint(0, 999999)}", email="test@test.com", password="testpassword", typeclass=DefaultAccount) account = create.create_account(
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.s1.uid = account.uid self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -281,7 +322,9 @@ class TestDefaultAccount(TestCase):
account.puppet_object(self.s1, obj) account.puppet_object(self.s1, obj)
# works because django.conf.settings.MULTISESSION_MODE is not in (1, 3) # works because django.conf.settings.MULTISESSION_MODE is not in (1, 3)
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("from another of your sessions.|n")) self.assertTrue(
self.s1.data_out.call_args[1]["text"].endswith("from another of your sessions.|n")
)
self.assertTrue(obj.at_post_puppet.call_args[1] == {}) self.assertTrue(obj.at_post_puppet.call_args[1] == {})
def test_puppet_object_already_puppeted(self): def test_puppet_object_already_puppeted(self):
@ -289,7 +332,12 @@ class TestDefaultAccount(TestCase):
import evennia.server.sessionhandler import evennia.server.sessionhandler
account = create.create_account(f"TestAccount{randint(0, 999999)}", email="test@test.com", password="testpassword", typeclass=DefaultAccount) account = create.create_account(
f"TestAccount{randint(0, 999999)}",
email="test@test.com",
password="testpassword",
typeclass=DefaultAccount,
)
self.account = account self.account = account
self.s1.uid = account.uid self.s1.uid = account.uid
evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1 evennia.server.sessionhandler.SESSIONS[self.s1.uid] = self.s1
@ -304,28 +352,33 @@ class TestDefaultAccount(TestCase):
obj.at_post_puppet = Mock() obj.at_post_puppet = Mock()
account.puppet_object(self.s1, obj) account.puppet_object(self.s1, obj)
self.assertTrue(self.s1.data_out.call_args[1]['text'].endswith("is already puppeted by another Account.")) self.assertTrue(
self.s1.data_out.call_args[1]["text"].endswith(
"is already puppeted by another Account."
)
)
self.assertIsNone(obj.at_post_puppet.call_args) self.assertIsNone(obj.at_post_puppet.call_args)
class TestAccountPuppetDeletion(EvenniaTest): class TestAccountPuppetDeletion(EvenniaTest):
@override_settings(MULTISESSION_MODE=2) @override_settings(MULTISESSION_MODE=2)
def test_puppet_deletion(self): def test_puppet_deletion(self):
# Check for existing chars # Check for existing chars
self.assertFalse(self.account.db._playable_characters, self.assertFalse(
'Account should not have any chars by default.') self.account.db._playable_characters, "Account should not have any chars by default."
)
# Add char1 to account's playable characters # Add char1 to account's playable characters
self.account.db._playable_characters.append(self.char1) self.account.db._playable_characters.append(self.char1)
self.assertTrue(self.account.db._playable_characters, self.assertTrue(self.account.db._playable_characters, "Char was not added to account.")
'Char was not added to account.')
# See what happens when we delete char1. # See what happens when we delete char1.
self.char1.delete() self.char1.delete()
# Playable char list should be empty. # Playable char list should be empty.
self.assertFalse(self.account.db._playable_characters, self.assertFalse(
f'Playable character list is not empty! {self.account.db._playable_characters}') self.account.db._playable_characters,
f"Playable character list is not empty! {self.account.db._playable_characters}",
)
class TestDefaultAccountEv(EvenniaTest): class TestDefaultAccountEv(EvenniaTest):
@ -333,6 +386,7 @@ class TestDefaultAccountEv(EvenniaTest):
Testing using the EvenniaTest parent Testing using the EvenniaTest parent
""" """
def test_characters_property(self): def test_characters_property(self):
"test existence of None in _playable_characters Attr" "test existence of None in _playable_characters Attr"
self.account.db._playable_characters = [self.char1, None] self.account.db._playable_characters = [self.char1, None]
@ -353,7 +407,9 @@ class TestDefaultAccountEv(EvenniaTest):
self.assertEqual(idle, 10) self.assertEqual(idle, 10)
# test no sessions # test no sessions
with patch("evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]) as mock_sessh: with patch(
"evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]
) as mock_sessh:
idle = self.account.idle_time idle = self.account.idle_time
self.assertEqual(idle, None) self.assertEqual(idle, None)
@ -364,18 +420,21 @@ class TestDefaultAccountEv(EvenniaTest):
self.assertEqual(conn, 10) self.assertEqual(conn, 10)
# test no sessions # test no sessions
with patch("evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]) as mock_sessh: with patch(
"evennia.accounts.accounts._SESSIONS.sessions_from_account", return_value=[]
) as mock_sessh:
idle = self.account.connection_time idle = self.account.connection_time
self.assertEqual(idle, None) self.assertEqual(idle, None)
def test_create_account(self): def test_create_account(self):
acct = create.account( acct = create.account(
"TestAccount3", "test@test.com", "testpassword123", "TestAccount3",
"test@test.com",
"testpassword123",
locks="test:all()", locks="test:all()",
tags=[("tag1", "category1"), ("tag2", "category2", "data1"), ("tag3", None)], tags=[("tag1", "category1"), ("tag2", "category2", "data1"), ("tag3", None)],
attributes=[("key1", "value1", "category1", attributes=[("key1", "value1", "category1", "edit:false()", True), ("key2", "value2")],
"edit:false()", True), )
("key2", "value2")])
acct.save() acct.save()
self.assertTrue(acct.pk) self.assertTrue(acct.pk)
@ -389,7 +448,7 @@ class TestDefaultAccountEv(EvenniaTest):
ret = self.account.at_look(target=self.obj1, session=self.session) ret = self.account.at_look(target=self.obj1, session=self.session)
self.assertTrue("Obj" in ret) self.assertTrue("Obj" in ret)
ret = self.account.at_look(target="Invalid", session=self.session) ret = self.account.at_look(target="Invalid", session=self.session)
self.assertEqual(ret, 'Invalid has no in-game appearance.') self.assertEqual(ret, "Invalid has no in-game appearance.")
def test_msg(self): def test_msg(self):
self.account.msg self.account.msg

View file

@ -63,7 +63,7 @@ _COMMAND_RECURSION_LIMIT = 10
# This decides which command parser is to be used. # This decides which command parser is to be used.
# You have to restart the server for changes to take effect. # You have to restart the server for changes to take effect.
_COMMAND_PARSER = utils.variable_from_module(*settings.COMMAND_PARSER.rsplit('.', 1)) _COMMAND_PARSER = utils.variable_from_module(*settings.COMMAND_PARSER.rsplit(".", 1))
# System command names - import these variables rather than trying to # System command names - import these variables rather than trying to
# remember the actual string constants. If not defined, Evennia # remember the actual string constants. If not defined, Evennia
@ -82,7 +82,7 @@ CMD_CHANNEL = "__send_to_channel_command"
CMD_LOGINSTART = "__unloggedin_look_command" CMD_LOGINSTART = "__unloggedin_look_command"
# Function for handling multiple command matches. # Function for handling multiple command matches.
_SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
# Output strings. The first is the IN_GAME_ERRORS return, the second # Output strings. The first is the IN_GAME_ERRORS return, the second
# is the normal "production message to echo to the account. # is the normal "production message to echo to the account.
@ -93,7 +93,8 @@ An untrapped error occurred.
""", """,
""" """
An untrapped error occurred. Please file a bug report detailing the steps to reproduce. An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
""") """,
)
_ERROR_CMDSETS = ( _ERROR_CMDSETS = (
""" """
@ -103,7 +104,8 @@ error in one of the cmdsets to merge.
""" """
A cmdset merger-error occurred. Please file a bug report detailing the A cmdset merger-error occurred. Please file a bug report detailing the
steps to reproduce. steps to reproduce.
""") """,
)
_ERROR_NOCMDSETS = ( _ERROR_NOCMDSETS = (
""" """
@ -114,7 +116,8 @@ multiple causes.
No command sets found! This is a sign of a critical bug. If No command sets found! This is a sign of a critical bug. If
disconnecting/reconnecting doesn't" solve the problem, try to contact disconnecting/reconnecting doesn't" solve the problem, try to contact
the server admin through" some other means for assistance. the server admin through" some other means for assistance.
""") """,
)
_ERROR_CMDHANDLER = ( _ERROR_CMDHANDLER = (
""" """
@ -125,10 +128,12 @@ traceback and steps to reproduce.
""" """
A command handler bug occurred. Please notify staff - they should A command handler bug occurred. Please notify staff - they should
likely file a bug report with the Evennia project. likely file a bug report with the Evennia project.
""") """,
)
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \ _ERROR_RECURSION_LIMIT = (
"reached for '{raw_cmdname}' ({cmdclass})." "Command recursion limit ({recursion_limit}) " "reached for '{raw_cmdname}' ({cmdclass})."
)
# delayed imports # delayed imports
@ -137,6 +142,7 @@ _GET_INPUT = None
# helper functions # helper functions
def _msg_err(receiver, stringtuple): def _msg_err(receiver, stringtuple):
""" """
Helper function for returning an error to the caller. Helper function for returning an error to the caller.
@ -153,13 +159,19 @@ def _msg_err(receiver, stringtuple):
tracestring = format_exc() tracestring = format_exc()
logger.log_trace() logger.log_trace()
if _IN_GAME_ERRORS: if _IN_GAME_ERRORS:
receiver.msg(string.format(traceback=tracestring, receiver.msg(
errmsg=stringtuple[0].strip(), string.format(
timestamp=timestamp).strip()) traceback=tracestring, errmsg=stringtuple[0].strip(), timestamp=timestamp
).strip()
)
else: else:
receiver.msg(string.format(traceback=tracestring.splitlines()[-1], receiver.msg(
errmsg=stringtuple[1].strip(), string.format(
timestamp=timestamp).strip()) traceback=tracestring.splitlines()[-1],
errmsg=stringtuple[1].strip(),
timestamp=timestamp,
).strip()
)
def _progressive_cmd_run(cmd, generator, response=None): def _progressive_cmd_run(cmd, generator, response=None):
@ -227,6 +239,7 @@ def _process_input(caller, prompt, result, cmd, generator):
# custom Exceptions # custom Exceptions
class NoCmdSets(Exception): class NoCmdSets(Exception):
"No cmdsets found. Critical error." "No cmdsets found. Critical error."
pass pass
@ -248,6 +261,7 @@ class ErrorReported(Exception):
self.args = (raw_string,) self.args = (raw_string,)
self.raw_string = raw_string self.raw_string = raw_string
# Helper function # Helper function
@ -280,6 +294,7 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
""" """
try: try:
@inlineCallbacks @inlineCallbacks
def _get_channel_cmdset(account_or_obj): def _get_channel_cmdset(account_or_obj):
""" """
@ -308,8 +323,9 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
if location: if location:
# Gather all cmdsets stored on objects in the room and # Gather all cmdsets stored on objects in the room and
# also in the caller's inventory and the location itself # also in the caller's inventory and the location itself
local_objlist = yield (location.contents_get(exclude=obj) + local_objlist = yield (
obj.contents_get() + [location]) location.contents_get(exclude=obj) + obj.contents_get() + [location]
)
local_objlist = [o for o in local_objlist if not o._is_deleted] local_objlist = [o for o in local_objlist if not o._is_deleted]
for lobj in local_objlist: for lobj in local_objlist:
try: try:
@ -320,11 +336,18 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
# the call-type lock is checked here, it makes sure an account # the call-type lock is checked here, it makes sure an account
# is not seeing e.g. the commands on a fellow account (which is why # is not seeing e.g. the commands on a fellow account (which is why
# the no_superuser_bypass must be True) # the no_superuser_bypass must be True)
local_obj_cmdsets = \ local_obj_cmdsets = yield list(
yield list(chain.from_iterable( chain.from_iterable(
lobj.cmdset.cmdset_stack for lobj in local_objlist lobj.cmdset.cmdset_stack
if (lobj.cmdset.current and for lobj in local_objlist
lobj.access(caller, access_type='call', no_superuser_bypass=True)))) if (
lobj.cmdset.current
and lobj.access(
caller, access_type="call", no_superuser_bypass=True
)
)
)
)
for cset in local_obj_cmdsets: for cset in local_obj_cmdsets:
# This is necessary for object sets, or we won't be able to # This is necessary for object sets, or we won't be able to
# separate the command sets from each other in a busy room. We # separate the command sets from each other in a busy room. We
@ -370,7 +393,9 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
local_obj_cmdsets = yield _get_local_obj_cmdsets(obj) local_obj_cmdsets = yield _get_local_obj_cmdsets(obj)
if current.no_exits: if current.no_exits:
# filter out all exits # filter out all exits
local_obj_cmdsets = [cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"] local_obj_cmdsets = [
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
]
cmdsets += local_obj_cmdsets cmdsets += local_obj_cmdsets
if not current.no_channels: if not current.no_channels:
# also objs may have channels # also objs may have channels
@ -392,7 +417,9 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
local_obj_cmdsets = yield _get_local_obj_cmdsets(obj) local_obj_cmdsets = yield _get_local_obj_cmdsets(obj)
if current.no_exits: if current.no_exits:
# filter out all exits # filter out all exits
local_obj_cmdsets = [cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"] local_obj_cmdsets = [
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
]
cmdsets += local_obj_cmdsets cmdsets += local_obj_cmdsets
if not current.no_channels: if not current.no_channels:
# also objs may have channels # also objs may have channels
@ -408,7 +435,9 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
local_obj_cmdsets = yield _get_local_obj_cmdsets(obj) local_obj_cmdsets = yield _get_local_obj_cmdsets(obj)
if current.no_exits: if current.no_exits:
# filter out all exits # filter out all exits
local_obj_cmdsets = [cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"] local_obj_cmdsets = [
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
]
cmdsets += yield local_obj_cmdsets cmdsets += yield local_obj_cmdsets
if not current.no_channels: if not current.no_channels:
# also objs may have channels # also objs may have channels
@ -417,11 +446,11 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype) raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype)
# weed out all non-found sets # weed out all non-found sets
cmdsets = yield [cmdset for cmdset in cmdsets cmdsets = yield [cmdset for cmdset in cmdsets if cmdset and cmdset.key != "_EMPTY_CMDSET"]
if cmdset and cmdset.key != "_EMPTY_CMDSET"]
# report cmdset errors to user (these should already have been logged) # report cmdset errors to user (these should already have been logged)
yield [report_to.msg(cmdset.errmessage) for cmdset in cmdsets yield [
if cmdset.key == "_CMDSET_ERROR"] report_to.msg(cmdset.errmessage) for cmdset in cmdsets if cmdset.key == "_CMDSET_ERROR"
]
if cmdsets: if cmdsets:
# faster to do tuple on list than to build tuple directly # faster to do tuple on list than to build tuple directly
@ -463,14 +492,23 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
except Exception: except Exception:
_msg_err(caller, _ERROR_CMDSETS) _msg_err(caller, _ERROR_CMDSETS)
raise raise
#raise ErrorReported # raise ErrorReported
# Main command-handler function # Main command-handler function
@inlineCallbacks @inlineCallbacks
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, def cmdhandler(
cmdobj=None, cmdobj_key=None, **kwargs): 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. This is the main mechanism that handles any string sent to the engine.
@ -557,7 +595,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
# since this may be different for every command when # since this may be different for every command when
# merging multuple cmdsets # merging multuple cmdsets
if hasattr(cmd, 'obj') and hasattr(cmd.obj, 'scripts'): if hasattr(cmd, "obj") and hasattr(cmd.obj, "scripts"):
# cmd.obj is automatically made available by the cmdhandler. # cmd.obj is automatically made available by the cmdhandler.
# we make sure to validate its scripts. # we make sure to validate its scripts.
yield cmd.obj.scripts.validate() yield cmd.obj.scripts.validate()
@ -572,9 +610,11 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
_COMMAND_NESTING[called_by] += 1 _COMMAND_NESTING[called_by] += 1
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT: if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT, err = _ERROR_RECURSION_LIMIT.format(
raw_cmdname=raw_cmdname, recursion_limit=_COMMAND_RECURSION_LIMIT,
cmdclass=cmd.__class__) raw_cmdname=raw_cmdname,
cmdclass=cmd.__class__,
)
raise RuntimeError(err) raise RuntimeError(err)
# pre-command hook # pre-command hook
@ -653,8 +693,9 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
else: else:
# no explicit cmdobject given, figure it out # no explicit cmdobject given, figure it out
cmdset = yield get_and_merge_cmdsets(caller, session, account, obj, cmdset = yield get_and_merge_cmdsets(
callertype, raw_string) caller, session, account, obj, callertype, raw_string
)
if not cmdset: if not cmdset:
# this is bad and shouldn't happen. # this is bad and shouldn't happen.
raise NoCmdSets raise NoCmdSets
@ -683,7 +724,9 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
syscmd.matches = matches syscmd.matches = matches
else: else:
# fall back to default error handling # fall back to default error handling
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0]) sysarg = yield _SEARCH_AT_RESULT(
[match[2] for match in matches], caller, query=matches[0][0]
)
raise ExecSystemCommand(syscmd, sysarg) raise ExecSystemCommand(syscmd, sysarg)
cmdname, args, cmd, raw_cmdname = "", "", None, "" cmdname, args, cmd, raw_cmdname = "", "", None, ""
@ -701,17 +744,22 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
else: else:
# fallback to default error text # fallback to default error text
sysarg = _("Command '%s' is not available.") % raw_string sysarg = _("Command '%s' is not available.") % raw_string
suggestions = string_suggestions(raw_string, suggestions = string_suggestions(
cmdset.get_all_cmd_keys_and_aliases(caller), raw_string,
cutoff=0.7, maxnum=3) cmdset.get_all_cmd_keys_and_aliases(caller),
cutoff=0.7,
maxnum=3,
)
if suggestions: if suggestions:
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True) sysarg += _(" Maybe you meant %s?") % utils.list_to_string(
suggestions, _("or"), addquote=True
)
else: else:
sysarg += _(" Type \"help\" for help.") sysarg += _(' Type "help" for help.')
raise ExecSystemCommand(syscmd, sysarg) raise ExecSystemCommand(syscmd, sysarg)
# Check if this is a Channel-cmd match. # Check if this is a Channel-cmd match.
if hasattr(cmd, 'is_channel') and cmd.is_channel: if hasattr(cmd, "is_channel") and cmd.is_channel:
# even if a user-defined syscmd is not defined, the # even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right. # found cmd is already a system command in its own right.
syscmd = yield cmdset.get(CMD_CHANNEL) syscmd = yield cmdset.get(CMD_CHANNEL)
@ -738,8 +786,9 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
sysarg = exc.sysarg sysarg = exc.sysarg
if syscmd: if syscmd:
ret = yield _run_command(syscmd, syscmd.key, sysarg, ret = yield _run_command(
unformatted_raw_string, cmdset, session, account) syscmd, syscmd.key, sysarg, unformatted_raw_string, cmdset, session, account
)
returnValue(ret) returnValue(ret)
elif sysarg: elif sysarg:
# return system arg # return system arg

View file

@ -65,22 +65,33 @@ def build_matches(raw_string, cmdset, include_prefixes=False):
# use the cmdname as-is # use the cmdname as-is
l_raw_string = raw_string.lower() l_raw_string = raw_string.lower()
for cmd in cmdset: for cmd in cmdset:
matches.extend([create_match(cmdname, raw_string, cmd, cmdname) matches.extend(
for cmdname in [cmd.key] + cmd.aliases [
if cmdname and l_raw_string.startswith(cmdname.lower()) and create_match(cmdname, raw_string, cmd, cmdname)
(not cmd.arg_regex or for cmdname in [cmd.key] + cmd.aliases
cmd.arg_regex.match(l_raw_string[len(cmdname):]))]) if cmdname
and l_raw_string.startswith(cmdname.lower())
and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :]))
]
)
else: else:
# strip prefixes set in settings # strip prefixes set in settings
raw_string = (raw_string.lstrip(_CMD_IGNORE_PREFIXES) raw_string = (
if len(raw_string) > 1 else raw_string) raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string
)
l_raw_string = raw_string.lower() l_raw_string = raw_string.lower()
for cmd in cmdset: for cmd in cmdset:
for raw_cmdname in [cmd.key] + cmd.aliases: for raw_cmdname in [cmd.key] + cmd.aliases:
cmdname = (raw_cmdname.lstrip(_CMD_IGNORE_PREFIXES) cmdname = (
if len(raw_cmdname) > 1 else raw_cmdname) raw_cmdname.lstrip(_CMD_IGNORE_PREFIXES)
if cmdname and l_raw_string.startswith(cmdname.lower()) and \ if len(raw_cmdname) > 1
(not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):])): else raw_cmdname
)
if (
cmdname
and l_raw_string.startswith(cmdname.lower())
and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :]))
):
matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname)) matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname))
except Exception: except Exception:
log_trace("cmdhandler error. raw_input:%s" % raw_string) log_trace("cmdhandler error. raw_input:%s" % raw_string)
@ -168,18 +179,19 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex)) return cmdparser(new_raw_string, cmdset, caller, match_index=int(mindex))
if _CMD_IGNORE_PREFIXES: if _CMD_IGNORE_PREFIXES:
# still no match. Try to strip prefixes # still no match. Try to strip prefixes
raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string raw_string = (
raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string
)
matches = build_matches(raw_string, cmdset, include_prefixes=False) matches = build_matches(raw_string, cmdset, include_prefixes=False)
# only select command matches we are actually allowed to call. # only select command matches we are actually allowed to call.
matches = [match for match in matches if match[2].access(caller, 'cmd')] matches = [match for match in matches if match[2].access(caller, "cmd")]
# try to bring the number of matches down to 1 # try to bring the number of matches down to 1
if len(matches) > 1: if len(matches) > 1:
# See if it helps to analyze the match with preserved case but only if # See if it helps to analyze the match with preserved case but only if
# it leaves at least one match. # it leaves at least one match.
trimmed = [match for match in matches trimmed = [match for match in matches if raw_string.startswith(match[0])]
if raw_string.startswith(match[0])]
if trimmed: if trimmed:
matches = trimmed matches = trimmed
@ -188,14 +200,14 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
matches = sorted(matches, key=lambda m: m[3]) matches = sorted(matches, key=lambda m: m[3])
# only pick the matches with highest count quality # only pick the matches with highest count quality
quality = [mat[3] for mat in matches] quality = [mat[3] for mat in matches]
matches = matches[-quality.count(quality[-1]):] matches = matches[-quality.count(quality[-1]) :]
if len(matches) > 1: if len(matches) > 1:
# still multiple matches. Fall back to ratio-based quality. # still multiple matches. Fall back to ratio-based quality.
matches = sorted(matches, key=lambda m: m[4]) matches = sorted(matches, key=lambda m: m[4])
# only pick the highest rated ratio match # only pick the highest rated ratio match
quality = [mat[4] for mat in matches] quality = [mat[4] for mat in matches]
matches = matches[-quality.count(quality[-1]):] matches = matches[-quality.count(quality[-1]) :]
if len(matches) > 1 and match_index is not None and 0 < match_index <= len(matches): if len(matches) > 1 and match_index is not None and 0 < match_index <= len(matches):
# We couldn't separate match by quality, but we have an # We couldn't separate match by quality, but we have an

View file

@ -29,6 +29,7 @@ Set theory.
from weakref import WeakKeyDictionary from weakref import WeakKeyDictionary
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from evennia.utils.utils import inherits_from, is_iter from evennia.utils.utils import inherits_from, is_iter
__all__ = ("CmdSet",) __all__ = ("CmdSet",)
@ -38,6 +39,7 @@ class _CmdSetMeta(type):
the cmdset class. the cmdset class.
""" """
def __init__(cls, *args, **kwargs): def __init__(cls, *args, **kwargs):
""" """
Fixes some things in the cmdclass Fixes some things in the cmdclass
@ -45,7 +47,7 @@ class _CmdSetMeta(type):
""" """
# by default we key the cmdset the same as the # by default we key the cmdset the same as the
# name of its class. # name of its class.
if not hasattr(cls, 'key') or not cls.key: if not hasattr(cls, "key") or not cls.key:
cls.key = cls.__name__ cls.key = cls.__name__
cls.path = "%s.%s" % (cls.__module__, cls.__name__) cls.path = "%s.%s" % (cls.__module__, cls.__name__)
@ -156,9 +158,18 @@ class CmdSet(object, metaclass=_CmdSetMeta):
key_mergetypes = {} key_mergetypes = {}
errmessage = "" errmessage = ""
# pre-store properties to duplicate straight off # pre-store properties to duplicate straight off
to_duplicate = ("key", "cmdsetobj", "no_exits", "no_objs", to_duplicate = (
"no_channels", "permanent", "mergetype", "key",
"priority", "duplicates", "errmessage") "cmdsetobj",
"no_exits",
"no_objs",
"no_channels",
"permanent",
"mergetype",
"priority",
"duplicates",
"errmessage",
)
def __init__(self, cmdsetobj=None, key=None): def __init__(self, cmdsetobj=None, key=None):
""" """
@ -211,8 +222,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
if cmdset_a.duplicates and cmdset_a.priority == cmdset_b.priority: if cmdset_a.duplicates and cmdset_a.priority == cmdset_b.priority:
cmdset_c.commands.extend(cmdset_b.commands) cmdset_c.commands.extend(cmdset_b.commands)
else: else:
cmdset_c.commands.extend([cmd for cmd in cmdset_b cmdset_c.commands.extend([cmd for cmd in cmdset_b if cmd not in cmdset_a])
if cmd not in cmdset_a])
return cmdset_c return cmdset_c
def _intersect(self, cmdset_a, cmdset_b): def _intersect(self, cmdset_a, cmdset_b):
@ -324,7 +334,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
commands (str): Representation of commands in Cmdset. commands (str): Representation of commands in Cmdset.
""" """
return ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o:o.key)]) return ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
def __iter__(self): def __iter__(self):
""" """
@ -375,8 +385,9 @@ class CmdSet(object, metaclass=_CmdSetMeta):
# A higher or equal priority to B # A higher or equal priority to B
# preserve system __commands # preserve system __commands
sys_commands = sys_commands_a + [cmd for cmd in sys_commands_b sys_commands = sys_commands_a + [
if cmd not in sys_commands_a] cmd for cmd in sys_commands_b if cmd not in sys_commands_a
]
mergetype = cmdset_a.key_mergetypes.get(self.key, cmdset_a.mergetype) mergetype = cmdset_a.key_mergetypes.get(self.key, cmdset_a.mergetype)
if mergetype == "Intersect": if mergetype == "Intersect":
@ -391,7 +402,9 @@ class CmdSet(object, metaclass=_CmdSetMeta):
# pass through options whenever they are set, unless the merging or higher-prio # pass through options whenever they are set, unless the merging or higher-prio
# set changes the setting (i.e. has a non-None value). We don't pass through # set changes the setting (i.e. has a non-None value). We don't pass through
# the duplicates setting; that is per-merge # the duplicates setting; that is per-merge
cmdset_c.no_channels = self.no_channels if cmdset_a.no_channels is None else cmdset_a.no_channels cmdset_c.no_channels = (
self.no_channels if cmdset_a.no_channels is None else cmdset_a.no_channels
)
cmdset_c.no_exits = self.no_exits if cmdset_a.no_exits is None else cmdset_a.no_exits cmdset_c.no_exits = self.no_exits if cmdset_a.no_exits is None else cmdset_a.no_exits
cmdset_c.no_objs = self.no_objs if cmdset_a.no_objs is None else cmdset_a.no_objs cmdset_c.no_objs = self.no_objs if cmdset_a.no_objs is None else cmdset_a.no_objs
@ -399,8 +412,9 @@ class CmdSet(object, metaclass=_CmdSetMeta):
# B higher priority than A # B higher priority than A
# preserver system __commands # preserver system __commands
sys_commands = sys_commands_b + [cmd for cmd in sys_commands_a sys_commands = sys_commands_b + [
if cmd not in sys_commands_b] cmd for cmd in sys_commands_a if cmd not in sys_commands_b
]
mergetype = self.key_mergetypes.get(cmdset_a.key, self.mergetype) mergetype = self.key_mergetypes.get(cmdset_a.key, self.mergetype)
if mergetype == "Intersect": if mergetype == "Intersect":
@ -415,7 +429,9 @@ class CmdSet(object, metaclass=_CmdSetMeta):
# pass through options whenever they are set, unless the higher-prio # pass through options whenever they are set, unless the higher-prio
# set changes the setting (i.e. has a non-None value). We don't pass through # set changes the setting (i.e. has a non-None value). We don't pass through
# the duplicates setting; that is per-merge # the duplicates setting; that is per-merge
cmdset_c.no_channels = cmdset_a.no_channels if self.no_channels is None else self.no_channels cmdset_c.no_channels = (
cmdset_a.no_channels if self.no_channels is None else self.no_channels
)
cmdset_c.no_exits = cmdset_a.no_exits if self.no_exits is None else self.no_exits cmdset_c.no_exits = cmdset_a.no_exits if self.no_exits is None else self.no_exits
cmdset_c.no_objs = cmdset_a.no_objs if self.no_objs is None else self.no_objs cmdset_c.no_objs = cmdset_a.no_objs if self.no_objs is None else self.no_objs
@ -465,8 +481,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
string += "infinite loop. When adding a cmdset to another, " string += "infinite loop. When adding a cmdset to another, "
string += "make sure they are not themself cyclically added to " string += "make sure they are not themself cyclically added to "
string += "the new cmdset somewhere in the chain." string += "the new cmdset somewhere in the chain."
raise RuntimeError(_(string) % {"cmd": cmd, raise RuntimeError(_(string) % {"cmd": cmd, "class": self.__class__})
"class": self.__class__})
cmds = cmd.commands cmds = cmd.commands
elif is_iter(cmd): elif is_iter(cmd):
cmds = [self._instantiate(c) for c in cmd] cmds = [self._instantiate(c) for c in cmd]
@ -476,7 +491,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
system_commands = self.system_commands system_commands = self.system_commands
for cmd in cmds: for cmd in cmds:
# add all commands # add all commands
if not hasattr(cmd, 'obj'): if not hasattr(cmd, "obj"):
cmd.obj = self.cmdsetobj cmd.obj = self.cmdsetobj
try: try:
ic = commands.index(cmd) ic = commands.index(cmd)
@ -578,8 +593,9 @@ class CmdSet(object, metaclass=_CmdSetMeta):
for cmd in self.commands: for cmd in self.commands:
if cmd.key in unique: if cmd.key in unique:
ocmd = unique[cmd.key] ocmd = unique[cmd.key]
if (hasattr(cmd, 'obj') and cmd.obj == caller) and not \ if (hasattr(cmd, "obj") and cmd.obj == caller) and not (
(hasattr(ocmd, 'obj') and ocmd.obj == caller): hasattr(ocmd, "obj") and ocmd.obj == caller
):
unique[cmd.key] = cmd unique[cmd.key] = cmd
else: else:
unique[cmd.key] = cmd unique[cmd.key] = cmd
@ -601,8 +617,7 @@ class CmdSet(object, metaclass=_CmdSetMeta):
""" """
names = [] names = []
if caller: if caller:
[names.extend(cmd._keyaliases) for cmd in self.commands [names.extend(cmd._keyaliases) for cmd in self.commands if cmd.access(caller)]
if cmd.access(caller)]
else: else:
[names.extend(cmd._keyaliases) for cmd in self.commands] [names.extend(cmd._keyaliases) for cmd in self.commands]
return names return names

View file

@ -73,6 +73,7 @@ from evennia.commands.cmdset import CmdSet
from evennia.server.models import ServerConfig from evennia.server.models import ServerConfig
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
__all__ = ("import_cmdset", "CmdSetHandler") __all__ = ("import_cmdset", "CmdSetHandler")
_CACHED_CMDSETS = {} _CACHED_CMDSETS = {}
@ -86,37 +87,41 @@ _CMDSET_FALLBACKS = settings.CMDSET_FALLBACKS
_ERROR_CMDSET_IMPORT = _( _ERROR_CMDSET_IMPORT = _(
"""{traceback} """{traceback}
Error loading cmdset '{path}' Error loading cmdset '{path}'
(Traceback was logged {timestamp})""") (Traceback was logged {timestamp})"""
)
_ERROR_CMDSET_KEYERROR = _( _ERROR_CMDSET_KEYERROR = _(
"""Error loading cmdset: No cmdset class '{classname}' in '{path}'. """Error loading cmdset: No cmdset class '{classname}' in '{path}'.
(Traceback was logged {timestamp})""") (Traceback was logged {timestamp})"""
)
_ERROR_CMDSET_SYNTAXERROR = _( _ERROR_CMDSET_SYNTAXERROR = _(
"""{traceback} """{traceback}
SyntaxError encountered when loading cmdset '{path}'. SyntaxError encountered when loading cmdset '{path}'.
(Traceback was logged {timestamp})""") (Traceback was logged {timestamp})"""
)
_ERROR_CMDSET_EXCEPTION = _( _ERROR_CMDSET_EXCEPTION = _(
"""{traceback} """{traceback}
Compile/Run error when loading cmdset '{path}'.", Compile/Run error when loading cmdset '{path}'.",
(Traceback was logged {timestamp})""") (Traceback was logged {timestamp})"""
)
_ERROR_CMDSET_FALLBACK = _( _ERROR_CMDSET_FALLBACK = _(
""" """
Error encountered for cmdset at path '{path}'. Error encountered for cmdset at path '{path}'.
Replacing with fallback '{fallback_path}'. Replacing with fallback '{fallback_path}'.
""") """
_ERROR_CMDSET_NO_FALLBACK = _(
"""Fallback path '{fallback_path}' failed to generate a cmdset."""
) )
_ERROR_CMDSET_NO_FALLBACK = _("""Fallback path '{fallback_path}' failed to generate a cmdset.""")
class _ErrorCmdSet(CmdSet): class _ErrorCmdSet(CmdSet):
""" """
This is a special cmdset used to report errors. This is a special cmdset used to report errors.
""" """
key = "_CMDSET_ERROR" key = "_CMDSET_ERROR"
errmessage = "Error when loading cmdset." errmessage = "Error when loading cmdset."
@ -125,6 +130,7 @@ class _EmptyCmdSet(CmdSet):
""" """
This cmdset represents an empty cmdset This cmdset represents an empty cmdset
""" """
key = "_EMPTY_CMDSET" key = "_EMPTY_CMDSET"
priority = -101 priority = -101
mergetype = "Union" mergetype = "Union"
@ -153,8 +159,9 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
for the benefit of the handler. for the benefit of the handler.
""" """
python_paths = [path] + ["%s.%s" % (prefix, path) python_paths = [path] + [
for prefix in _CMDSET_PATHS if not path.startswith(prefix)] "%s.%s" % (prefix, path) for prefix in _CMDSET_PATHS if not path.startswith(prefix)
]
errstring = "" errstring = ""
for python_path in python_paths: for python_path in python_paths:
@ -199,30 +206,44 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
logger.log_trace() logger.log_trace()
errstring += _ERROR_CMDSET_IMPORT errstring += _ERROR_CMDSET_IMPORT
if _IN_GAME_ERRORS: if _IN_GAME_ERRORS:
errstring = errstring.format(path=python_path, traceback=format_exc(), timestamp=logger.timeformat()) errstring = errstring.format(
path=python_path, traceback=format_exc(), timestamp=logger.timeformat()
)
else: else:
errstring = errstring.format(path=python_path, traceback=err, timestamp=logger.timeformat()) errstring = errstring.format(
path=python_path, traceback=err, timestamp=logger.timeformat()
)
break break
except KeyError: except KeyError:
logger.log_trace() logger.log_trace()
errstring += _ERROR_CMDSET_KEYERROR errstring += _ERROR_CMDSET_KEYERROR
errstring = errstring.format(classname=classname, path=python_path, timestamp=logger.timeformat()) errstring = errstring.format(
classname=classname, path=python_path, timestamp=logger.timeformat()
)
break break
except SyntaxError as err: except SyntaxError as err:
logger.log_trace() logger.log_trace()
errstring += _ERROR_CMDSET_SYNTAXERROR errstring += _ERROR_CMDSET_SYNTAXERROR
if _IN_GAME_ERRORS: if _IN_GAME_ERRORS:
errstring = errstring.format(path=python_path, traceback=format_exc(), timestamp=logger.timeformat()) errstring = errstring.format(
path=python_path, traceback=format_exc(), timestamp=logger.timeformat()
)
else: else:
errstring = errstring.format(path=python_path, traceback=err, timestamp=logger.timeformat()) errstring = errstring.format(
path=python_path, traceback=err, timestamp=logger.timeformat()
)
break break
except Exception as err: except Exception as err:
logger.log_trace() logger.log_trace()
errstring += _ERROR_CMDSET_EXCEPTION errstring += _ERROR_CMDSET_EXCEPTION
if _IN_GAME_ERRORS: if _IN_GAME_ERRORS:
errstring = errstring.format(path=python_path, traceback=format_exc(), timestamp=logger.timeformat()) errstring = errstring.format(
path=python_path, traceback=format_exc(), timestamp=logger.timeformat()
)
else: else:
errstring = errstring.format(path=python_path, traceback=err, timestamp=logger.timeformat()) errstring = errstring.format(
path=python_path, traceback=err, timestamp=logger.timeformat()
)
break break
if errstring: if errstring:
@ -237,6 +258,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
return err_cmdset return err_cmdset
return None # undefined error return None # undefined error
# classes # classes
@ -296,9 +318,14 @@ class CmdSetHandler(object):
permstring = "perm" permstring = "perm"
if mergetype != cmdset.mergetype: if mergetype != cmdset.mergetype:
mergetype = "%s^" % (mergetype) mergetype = "%s^" % (mergetype)
string += "\n %i: <%s (%s, prio %i, %s)>: %s" % \ string += "\n %i: <%s (%s, prio %i, %s)>: %s" % (
(snum, cmdset.key, mergetype, snum,
cmdset.priority, permstring, cmdset) cmdset.key,
mergetype,
cmdset.priority,
permstring,
cmdset,
)
mergelist.append(str(snum)) mergelist.append(str(snum))
string += "\n" string += "\n"
@ -310,19 +337,24 @@ class CmdSetHandler(object):
mergetype = mergetype.format(mergetype=mergetype, cmdset=merged_on) mergetype = mergetype.format(mergetype=mergetype, cmdset=merged_on)
if mergelist: if mergelist:
tmpstring = _(" <Merged {mergelist} {mergetype}, prio {prio}>: {current}") tmpstring = _(" <Merged {mergelist} {mergetype}, prio {prio}>: {current}")
string += tmpstring.format(mergelist="+".join(mergelist), string += tmpstring.format(
mergetype=mergetype, prio=self.current.priority, mergelist="+".join(mergelist),
current=self.current) mergetype=mergetype,
prio=self.current.priority,
current=self.current,
)
else: else:
permstring = "non-perm" permstring = "non-perm"
if self.current.permanent: if self.current.permanent:
permstring = "perm" permstring = "perm"
tmpstring = _(" <{key} ({mergetype}, prio {prio}, {permstring})>:\n {keylist}") tmpstring = _(" <{key} ({mergetype}, prio {prio}, {permstring})>:\n {keylist}")
string += tmpstring.format(key=self.current.key, mergetype=mergetype, string += tmpstring.format(
prio=self.current.priority, key=self.current.key,
permstring=permstring, mergetype=mergetype,
keylist=", ".join(cmd.key for prio=self.current.priority,
cmd in sorted(self.current, key=lambda o: o.key))) permstring=permstring,
keylist=", ".join(cmd.key for cmd in sorted(self.current, key=lambda o: o.key)),
)
return string.strip() return string.strip()
def _import_cmdset(self, cmdset_path, emit_to_obj=None): def _import_cmdset(self, cmdset_path, emit_to_obj=None):
@ -363,23 +395,27 @@ class CmdSetHandler(object):
elif path: elif path:
cmdset = self._import_cmdset(path) cmdset = self._import_cmdset(path)
if cmdset: if cmdset:
if cmdset.key == '_CMDSET_ERROR': if cmdset.key == "_CMDSET_ERROR":
# If a cmdset fails to load, check if we have a fallback path to use # If a cmdset fails to load, check if we have a fallback path to use
fallback_path = _CMDSET_FALLBACKS.get(path, None) fallback_path = _CMDSET_FALLBACKS.get(path, None)
if fallback_path: if fallback_path:
err = _ERROR_CMDSET_FALLBACK.format(path=path, fallback_path=fallback_path) err = _ERROR_CMDSET_FALLBACK.format(
path=path, fallback_path=fallback_path
)
logger.log_err(err) logger.log_err(err)
if _IN_GAME_ERRORS: if _IN_GAME_ERRORS:
self.obj.msg(err) self.obj.msg(err)
cmdset = self._import_cmdset(fallback_path) cmdset = self._import_cmdset(fallback_path)
# If no cmdset is returned from the fallback, we can't go further # If no cmdset is returned from the fallback, we can't go further
if not cmdset: if not cmdset:
err = _ERROR_CMDSET_NO_FALLBACK.format(fallback_path=fallback_path) err = _ERROR_CMDSET_NO_FALLBACK.format(
fallback_path=fallback_path
)
logger.log_err(err) logger.log_err(err)
if _IN_GAME_ERRORS: if _IN_GAME_ERRORS:
self.obj.msg(err) self.obj.msg(err)
continue continue
cmdset.permanent = cmdset.key != '_CMDSET_ERROR' cmdset.permanent = cmdset.key != "_CMDSET_ERROR"
self.cmdset_stack.append(cmdset) self.cmdset_stack.append(cmdset)
# merge the stack into a new merged cmdset # merge the stack into a new merged cmdset
@ -429,9 +465,9 @@ class CmdSetHandler(object):
elif isinstance(cmdset, str): elif isinstance(cmdset, str):
# this is (maybe) a python path. Try to import from cache. # this is (maybe) a python path. Try to import from cache.
cmdset = self._import_cmdset(cmdset) cmdset = self._import_cmdset(cmdset)
if cmdset and cmdset.key != '_CMDSET_ERROR': if cmdset and cmdset.key != "_CMDSET_ERROR":
cmdset.permanent = permanent cmdset.permanent = permanent
if permanent and cmdset.key != '_CMDSET_ERROR': if permanent and cmdset.key != "_CMDSET_ERROR":
# store the path permanently # store the path permanently
storage = self.obj.cmdset_storage or [""] storage = self.obj.cmdset_storage or [""]
if default_cmdset: if default_cmdset:
@ -498,13 +534,15 @@ class CmdSetHandler(object):
self.obj.cmdset_storage = storage self.obj.cmdset_storage = storage
else: else:
# try it as a callable # try it as a callable
if callable(cmdset) and hasattr(cmdset, 'path'): if callable(cmdset) and hasattr(cmdset, "path"):
delcmdsets = [cset for cset in self.cmdset_stack[1:] delcmdsets = [cset for cset in self.cmdset_stack[1:] if cset.path == cmdset.path]
if cset.path == cmdset.path]
else: else:
# try it as a path or key # try it as a path or key
delcmdsets = [cset for cset in self.cmdset_stack[1:] delcmdsets = [
if cset.path == cmdset or cset.key == cmdset] cset
for cset in self.cmdset_stack[1:]
if cset.path == cmdset or cset.key == cmdset
]
storage = [] storage = []
if any(cset.permanent for cset in delcmdsets): if any(cset.permanent for cset in delcmdsets):
@ -530,6 +568,7 @@ class CmdSetHandler(object):
pass pass
# re-sync the cmdsethandler. # re-sync the cmdsethandler.
self.update() self.update()
# legacy alias # legacy alias
delete = remove delete = remove
@ -539,6 +578,7 @@ class CmdSetHandler(object):
""" """
self.remove(default_cmdset=True) self.remove(default_cmdset=True)
# legacy alias # legacy alias
delete_default = remove_default delete_default = remove_default
@ -582,22 +622,26 @@ class CmdSetHandler(object):
has_cmdset (bool): Whether or not the cmdset is in the handler. has_cmdset (bool): Whether or not the cmdset is in the handler.
""" """
if callable(cmdset) and hasattr(cmdset, 'path'): if callable(cmdset) and hasattr(cmdset, "path"):
# try it as a callable # try it as a callable
if must_be_default: if must_be_default:
return self.cmdset_stack and (self.cmdset_stack[0].path == cmdset.path) return self.cmdset_stack and (self.cmdset_stack[0].path == cmdset.path)
else: else:
return any([cset for cset in self.cmdset_stack return any([cset for cset in self.cmdset_stack if cset.path == cmdset.path])
if cset.path == cmdset.path])
else: else:
# try it as a path or key # try it as a path or key
if must_be_default: if must_be_default:
return self.cmdset_stack and ( return self.cmdset_stack and (
self.cmdset_stack[0].key == cmdset or self.cmdset_stack[0].key == cmdset or self.cmdset_stack[0].path == cmdset
self.cmdset_stack[0].path == cmdset) )
else: else:
return any([cset for cset in self.cmdset_stack return any(
if cset.path == cmdset or cset.key == cmdset]) [
cset
for cset in self.cmdset_stack
if cset.path == cmdset or cset.key == cmdset
]
)
# backwards-compatability alias # backwards-compatability alias
has_cmdset = has has_cmdset = has

View file

@ -37,12 +37,10 @@ def _init_command(cls, **kwargs):
cls.key = cls.key.lower() cls.key = cls.key.lower()
if cls.aliases and not is_iter(cls.aliases): if cls.aliases and not is_iter(cls.aliases):
try: try:
cls.aliases = [str(alias).strip().lower() cls.aliases = [str(alias).strip().lower() for alias in cls.aliases.split(",")]
for alias in cls.aliases.split(',')]
except Exception: except Exception:
cls.aliases = [] cls.aliases = []
cls.aliases = list(set(alias for alias in cls.aliases cls.aliases = list(set(alias for alias in cls.aliases if alias and alias != cls.key))
if alias and alias != cls.key))
# optimization - a set is much faster to match against than a list # optimization - a set is much faster to match against than a list
cls._matchset = set([cls.key] + cls.aliases) cls._matchset = set([cls.key] + cls.aliases)
@ -55,24 +53,24 @@ def _init_command(cls, **kwargs):
# pre-process locks as defined in class definition # pre-process locks as defined in class definition
temp = [] temp = []
if hasattr(cls, 'permissions'): if hasattr(cls, "permissions"):
cls.locks = cls.permissions cls.locks = cls.permissions
if not hasattr(cls, 'locks'): if not hasattr(cls, "locks"):
# default if one forgets to define completely # default if one forgets to define completely
cls.locks = "cmd:all()" cls.locks = "cmd:all()"
if "cmd:" not in cls.locks: if "cmd:" not in cls.locks:
cls.locks = "cmd:all();" + cls.locks cls.locks = "cmd:all();" + cls.locks
for lockstring in cls.locks.split(';'): for lockstring in cls.locks.split(";"):
if lockstring and ':' not in lockstring: if lockstring and ":" not in lockstring:
lockstring = "cmd:%s" % lockstring lockstring = "cmd:%s" % lockstring
temp.append(lockstring) temp.append(lockstring)
cls.lock_storage = ";".join(temp) cls.lock_storage = ";".join(temp)
if hasattr(cls, 'arg_regex') and isinstance(cls.arg_regex, str): if hasattr(cls, "arg_regex") and isinstance(cls.arg_regex, str):
cls.arg_regex = re.compile(r"%s" % cls.arg_regex, re.I + re.UNICODE) cls.arg_regex = re.compile(r"%s" % cls.arg_regex, re.I + re.UNICODE)
if not hasattr(cls, "auto_help"): if not hasattr(cls, "auto_help"):
cls.auto_help = True cls.auto_help = True
if not hasattr(cls, 'is_exit'): if not hasattr(cls, "is_exit"):
cls.is_exit = False cls.is_exit = False
if not hasattr(cls, "help_category"): if not hasattr(cls, "help_category"):
cls.help_category = "general" cls.help_category = "general"
@ -83,10 +81,12 @@ class CommandMeta(type):
""" """
The metaclass cleans up all properties on the class The metaclass cleans up all properties on the class
""" """
def __init__(cls, *args, **kwargs): def __init__(cls, *args, **kwargs):
_init_command(cls, **kwargs) _init_command(cls, **kwargs)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# The Command class is the basic unit of an Evennia command; when # The Command class is the basic unit of an Evennia command; when
# defining new commands, the admin subclass this class and # defining new commands, the admin subclass this class and
# define their own parser method to handle the input. The # define their own parser method to handle the input. The
@ -215,7 +215,7 @@ class Command(object, metaclass=CommandMeta):
str, too. str, too.
""" """
return hash('\n'.join(self._matchset)) return hash("\n".join(self._matchset))
def __ne__(self, cmd): def __ne__(self, cmd):
""" """
@ -283,7 +283,7 @@ class Command(object, metaclass=CommandMeta):
""" """
if isinstance(new_aliases, str): if isinstance(new_aliases, str):
new_aliases = new_aliases.split(';') new_aliases = new_aliases.split(";")
aliases = (str(alias).strip().lower() for alias in make_iter(new_aliases)) aliases = (str(alias).strip().lower() for alias in make_iter(new_aliases))
self.aliases = list(set(alias for alias in aliases if alias != self.key)) self.aliases = list(set(alias for alias in aliases if alias != self.key))
self._optimize() self._optimize()
@ -319,8 +319,7 @@ class Command(object, metaclass=CommandMeta):
""" """
return self.lockhandler.check(srcobj, access_type, default=default) return self.lockhandler.check(srcobj, access_type, default=default)
def msg(self, text=None, to_obj=None, from_obj=None, def msg(self, text=None, to_obj=None, from_obj=None, session=None, **kwargs):
session=None, **kwargs):
""" """
This is a shortcut instead 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 an Account and object - it will detect if caller is an Object or an Account and
@ -410,7 +409,9 @@ class Command(object, metaclass=CommandMeta):
set in self.parse()) set in self.parse())
""" """
variables = '\n'.join(" |w{}|n ({}): {}".format(key, type(val), val) for key, val in self.__dict__.items()) variables = "\n".join(
" |w{}|n ({}): {}".format(key, type(val), val) for key, val in self.__dict__.items()
)
string = f""" string = f"""
Command {self} has no defined `func()` - showing on-command variables: Command {self} has no defined `func()` - showing on-command variables:
{variables} {variables}
@ -430,8 +431,10 @@ Command {self} has no defined `func()` - showing on-command variables:
string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj 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 += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring
# show cmdset.key instead of cmdset to shorten output # show cmdset.key instead of cmdset to shorten output
string += fill("current cmdset (self.cmdset): |w%s|n\n" % string += fill(
(self.cmdset.key if self.cmdset.key else self.cmdset.__class__)) "current cmdset (self.cmdset): |w%s|n\n"
% (self.cmdset.key if self.cmdset.key else self.cmdset.__class__)
)
self.caller.msg(string) self.caller.msg(string)
@ -453,7 +456,7 @@ Command {self} has no defined `func()` - showing on-command variables:
object, conventionally with a preceding space. object, conventionally with a preceding space.
""" """
if hasattr(self, 'obj') and self.obj and self.obj != caller: if hasattr(self, "obj") and self.obj and self.obj != caller:
return " (%s)" % self.obj.get_display_name(caller).strip() return " (%s)" % self.obj.get_display_name(caller).strip()
return "" return ""
@ -486,7 +489,7 @@ Command {self} has no defined `func()` - showing on-command variables:
""" """
if self.session: if self.session:
return self.session.protocol_flags['SCREENWIDTH'][0] return self.session.protocol_flags["SCREENWIDTH"][0]
def styled_table(self, *args, **kwargs): def styled_table(self, *args, **kwargs):
""" """
@ -503,35 +506,48 @@ Command {self} has no defined `func()` - showing on-command variables:
or incomplete and ready for use with `.add_row` or `.add_collumn`. or incomplete and ready for use with `.add_row` or `.add_collumn`.
""" """
border_color = self.account.options.get('border_color') border_color = self.account.options.get("border_color")
column_color = self.account.options.get('column_names_color') column_color = self.account.options.get("column_names_color")
colornames = ['|%s%s|n' % (column_color, col) for col in args] colornames = ["|%s%s|n" % (column_color, col) for col in args]
h_line_char = kwargs.pop('header_line_char', '~') h_line_char = kwargs.pop("header_line_char", "~")
header_line_char = ANSIString(f'|{border_color}{h_line_char}|n') header_line_char = ANSIString(f"|{border_color}{h_line_char}|n")
c_char = kwargs.pop('corner_char', '+') c_char = kwargs.pop("corner_char", "+")
corner_char = ANSIString(f'|{border_color}{c_char}|n') corner_char = ANSIString(f"|{border_color}{c_char}|n")
b_left_char = kwargs.pop('border_left_char', '||') b_left_char = kwargs.pop("border_left_char", "||")
border_left_char = ANSIString(f'|{border_color}{b_left_char}|n') border_left_char = ANSIString(f"|{border_color}{b_left_char}|n")
b_right_char = kwargs.pop('border_right_char', '||') b_right_char = kwargs.pop("border_right_char", "||")
border_right_char = ANSIString(f'|{border_color}{b_right_char}|n') border_right_char = ANSIString(f"|{border_color}{b_right_char}|n")
b_bottom_char = kwargs.pop('border_bottom_char', '-') b_bottom_char = kwargs.pop("border_bottom_char", "-")
border_bottom_char = ANSIString(f'|{border_color}{b_bottom_char}|n') border_bottom_char = ANSIString(f"|{border_color}{b_bottom_char}|n")
b_top_char = kwargs.pop('border_top_char', '-') b_top_char = kwargs.pop("border_top_char", "-")
border_top_char = ANSIString(f'|{border_color}{b_top_char}|n') border_top_char = ANSIString(f"|{border_color}{b_top_char}|n")
table = EvTable(*colornames, header_line_char=header_line_char, corner_char=corner_char, table = EvTable(
border_left_char=border_left_char, border_right_char=border_right_char, *colornames,
border_top_char=border_top_char, **kwargs) header_line_char=header_line_char,
corner_char=corner_char,
border_left_char=border_left_char,
border_right_char=border_right_char,
border_top_char=border_top_char,
**kwargs,
)
return table return table
def _render_decoration(self, header_text=None, fill_character=None, edge_character=None, def _render_decoration(
mode='header', color_header=True, width=None): self,
header_text=None,
fill_character=None,
edge_character=None,
mode="header",
color_header=True,
width=None,
):
""" """
Helper for formatting a string into a pretty display, for a header, separator or footer. Helper for formatting a string into a pretty display, for a header, separator or footer.
@ -550,9 +566,9 @@ Command {self} has no defined `func()` - showing on-command variables:
""" """
colors = dict() colors = dict()
colors['border'] = self.account.options.get('border_color') colors["border"] = self.account.options.get("border_color")
colors['headertext'] = self.account.options.get('%s_text_color' % mode) colors["headertext"] = self.account.options.get("%s_text_color" % mode)
colors['headerstar'] = self.account.options.get('%s_star_color' % mode) colors["headerstar"] = self.account.options.get("%s_star_color" % mode)
width = width or self.client_width() width = width or self.client_width()
if edge_character: if edge_character:
@ -561,17 +577,19 @@ Command {self} has no defined `func()` - showing on-command variables:
if header_text: if header_text:
if color_header: if color_header:
header_text = ANSIString(header_text).clean() header_text = ANSIString(header_text).clean()
header_text = ANSIString('|n|%s%s|n' % (colors['headertext'], header_text)) header_text = ANSIString("|n|%s%s|n" % (colors["headertext"], header_text))
if mode == 'header': if mode == "header":
begin_center = ANSIString("|n|%s<|%s* |n" % (colors['border'], colors['headerstar'])) begin_center = ANSIString(
end_center = ANSIString("|n |%s*|%s>|n" % (colors['headerstar'], colors['border'])) "|n|%s<|%s* |n" % (colors["border"], colors["headerstar"])
)
end_center = ANSIString("|n |%s*|%s>|n" % (colors["headerstar"], colors["border"]))
center_string = ANSIString(begin_center + header_text + end_center) center_string = ANSIString(begin_center + header_text + end_center)
else: else:
center_string = ANSIString('|n |%s%s |n' % (colors['headertext'], header_text)) center_string = ANSIString("|n |%s%s |n" % (colors["headertext"], header_text))
else: else:
center_string = '' center_string = ""
fill_character = self.account.options.get('%s_fill' % mode) fill_character = self.account.options.get("%s_fill" % mode)
remain_fill = width - len(center_string) remain_fill = width - len(center_string)
if remain_fill % 2 == 0: if remain_fill % 2 == 0:
@ -581,13 +599,15 @@ Command {self} has no defined `func()` - showing on-command variables:
right_width = math.floor(remain_fill / 2) right_width = math.floor(remain_fill / 2)
left_width = math.ceil(remain_fill / 2) left_width = math.ceil(remain_fill / 2)
right_fill = ANSIString('|n|%s%s|n' % (colors['border'], fill_character * int(right_width))) right_fill = ANSIString("|n|%s%s|n" % (colors["border"], fill_character * int(right_width)))
left_fill = ANSIString('|n|%s%s|n' % (colors['border'], fill_character * int(left_width))) left_fill = ANSIString("|n|%s%s|n" % (colors["border"], fill_character * int(left_width)))
if edge_character: if edge_character:
edge_fill = ANSIString('|n|%s%s|n' % (colors['border'], edge_character)) edge_fill = ANSIString("|n|%s%s|n" % (colors["border"], edge_character))
main_string = ANSIString(center_string) main_string = ANSIString(center_string)
final_send = ANSIString(edge_fill) + left_fill + main_string + right_fill + ANSIString(edge_fill) final_send = (
ANSIString(edge_fill) + left_fill + main_string + right_fill + ANSIString(edge_fill)
)
else: else:
final_send = left_fill + ANSIString(center_string) + right_fill final_send = left_fill + ANSIString(center_string) + right_fill
return final_send return final_send
@ -597,8 +617,8 @@ Command {self} has no defined `func()` - showing on-command variables:
Create a pretty header. Create a pretty header.
""" """
if 'mode' not in kwargs: if "mode" not in kwargs:
kwargs['mode'] = 'header' kwargs["mode"] = "header"
return self._render_decoration(*args, **kwargs) return self._render_decoration(*args, **kwargs)
def styled_separator(self, *args, **kwargs): def styled_separator(self, *args, **kwargs):
@ -606,8 +626,8 @@ Command {self} has no defined `func()` - showing on-command variables:
Create a separator. Create a separator.
""" """
if 'mode' not in kwargs: if "mode" not in kwargs:
kwargs['mode'] = 'separator' kwargs["mode"] = "separator"
return self._render_decoration(*args, **kwargs) return self._render_decoration(*args, **kwargs)
def styled_footer(self, *args, **kwargs): def styled_footer(self, *args, **kwargs):
@ -615,8 +635,8 @@ Command {self} has no defined `func()` - showing on-command variables:
Create a pretty footer. Create a pretty footer.
""" """
if 'mode' not in kwargs: if "mode" not in kwargs:
kwargs['mode'] = 'footer' kwargs["mode"] = "footer"
return self._render_decoration(*args, **kwargs) return self._render_decoration(*args, **kwargs)

View file

@ -30,9 +30,21 @@ _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
_MULTISESSION_MODE = settings.MULTISESSION_MODE _MULTISESSION_MODE = settings.MULTISESSION_MODE
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdOOCLook", "CmdIC", "CmdOOC", "CmdPassword", "CmdQuit", __all__ = (
"CmdCharCreate", "CmdOption", "CmdSessions", "CmdWho", "CmdOOCLook",
"CmdColorTest", "CmdQuell", "CmdCharDelete", "CmdStyle") "CmdIC",
"CmdOOC",
"CmdPassword",
"CmdQuit",
"CmdCharCreate",
"CmdOption",
"CmdSessions",
"CmdWho",
"CmdColorTest",
"CmdQuell",
"CmdCharDelete",
"CmdStyle",
)
class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS): class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS):
@ -60,8 +72,9 @@ class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS):
self.account.db._playable_characters = playable self.account.db._playable_characters = playable
# store playable property # store playable property
if self.args: if self.args:
self.playable = dict((utils.to_str(char.key.lower()), char) self.playable = dict((utils.to_str(char.key.lower()), char) for char in playable).get(
for char in playable).get(self.args.lower(), None) self.args.lower(), None
)
else: else:
self.playable = playable self.playable = playable
@ -119,6 +132,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
always be able to access your character using lower-case letters always be able to access your character using lower-case letters
if you want. if you want.
""" """
key = "charcreate" key = "charcreate"
locks = "cmd:pperm(Player)" locks = "cmd:pperm(Player)"
help_category = "General" help_category = "General"
@ -137,12 +151,13 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
charmax = _MAX_NR_CHARACTERS charmax = _MAX_NR_CHARACTERS
if not account.is_superuser and \ if not account.is_superuser and (
(account.db._playable_characters and account.db._playable_characters and len(account.db._playable_characters) >= charmax
len(account.db._playable_characters) >= charmax): ):
self.msg("You may only create a maximum of %i characters." % charmax) self.msg("You may only create a maximum of %i characters." % charmax)
return return
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB
typeclass = settings.BASE_CHARACTER_TYPECLASS typeclass = settings.BASE_CHARACTER_TYPECLASS
if ObjectDB.objects.filter(db_typeclass_path=typeclass, db_key__iexact=key): if ObjectDB.objects.filter(db_typeclass_path=typeclass, db_key__iexact=key):
@ -156,21 +171,27 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
start_location = ObjectDB.objects.get_id(settings.START_LOCATION) start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
permissions = settings.PERMISSION_ACCOUNT_DEFAULT permissions = settings.PERMISSION_ACCOUNT_DEFAULT
new_character = create.create_object(typeclass, key=key, new_character = create.create_object(
location=start_location, typeclass, key=key, location=start_location, home=default_home, permissions=permissions
home=default_home, )
permissions=permissions)
# only allow creator (and developers) to puppet this char # only allow creator (and developers) to puppet this char
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer);delete:id(%i) or perm(Admin)" % new_character.locks.add(
(new_character.id, account.id, account.id)) "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer);delete:id(%i) or perm(Admin)"
% (new_character.id, account.id, account.id)
)
account.db._playable_characters.append(new_character) account.db._playable_characters.append(new_character)
if desc: if desc:
new_character.db.desc = desc new_character.db.desc = desc
elif not new_character.db.desc: elif not new_character.db.desc:
new_character.db.desc = "This is a character." new_character.db.desc = "This is a character."
self.msg("Created new character %s. Use |wic %s|n to enter the game as this character." self.msg(
% (new_character.key, new_character.key)) "Created new character %s. Use |wic %s|n to enter the game as this character."
logger.log_sec('Character Created: %s (Caller: %s, IP: %s).' % (new_character, account, self.session.address)) % (new_character.key, new_character.key)
)
logger.log_sec(
"Character Created: %s (Caller: %s, IP: %s)."
% (new_character, account, self.session.address)
)
class CmdCharDelete(COMMAND_DEFAULT_CLASS): class CmdCharDelete(COMMAND_DEFAULT_CLASS):
@ -182,6 +203,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
Permanently deletes one of your characters. Permanently deletes one of your characters.
""" """
key = "chardelete" key = "chardelete"
locks = "cmd:pperm(Player)" locks = "cmd:pperm(Player)"
help_category = "General" help_category = "General"
@ -195,13 +217,18 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
return return
# use the playable_characters list to search # use the playable_characters list to search
match = [char for char in utils.make_iter(account.db._playable_characters) match = [
if char.key.lower() == self.args.lower()] char
for char in utils.make_iter(account.db._playable_characters)
if char.key.lower() == self.args.lower()
]
if not match: if not match:
self.msg("You have no such character to delete.") self.msg("You have no such character to delete.")
return return
elif len(match) > 1: elif len(match) > 1:
self.msg("Aborting - there are two characters with the same name. Ask an admin to delete the right one.") self.msg(
"Aborting - there are two characters with the same name. Ask an admin to delete the right one."
)
return return
else: # one match else: # one match
from evennia.utils.evmenu import get_input from evennia.utils.evmenu import get_input
@ -211,10 +238,15 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
# only take action # only take action
delobj = caller.ndb._char_to_delete delobj = caller.ndb._char_to_delete
key = delobj.key key = delobj.key
caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj] caller.db._playable_characters = [
pc for pc in caller.db._playable_characters if pc != delobj
]
delobj.delete() delobj.delete()
self.msg("Character '%s' was permanently deleted." % key) self.msg("Character '%s' was permanently deleted." % key)
logger.log_sec('Character Deleted: %s (Caller: %s, IP: %s).' % (key, account, self.session.address)) logger.log_sec(
"Character Deleted: %s (Caller: %s, IP: %s)."
% (key, account, self.session.address)
)
else: else:
self.msg("Deletion was aborted.") self.msg("Deletion was aborted.")
del caller.ndb._char_to_delete del caller.ndb._char_to_delete
@ -223,11 +255,13 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
account.ndb._char_to_delete = match account.ndb._char_to_delete = match
# Return if caller has no permission to delete this # Return if caller has no permission to delete this
if not match.access(account, 'delete'): if not match.access(account, "delete"):
self.msg("You do not have permission to delete this character.") self.msg("You do not have permission to delete this character.")
return return
prompt = "|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?" prompt = (
"|rThis will permanently destroy '%s'. This cannot be undone.|n Continue yes/[no]?"
)
get_input(account, prompt % match.key, _callback) get_input(account, prompt % match.key, _callback)
@ -273,23 +307,33 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
return return
if not new_character: if not new_character:
# search for a matching character # search for a matching character
new_character = [char for char in search.object_search(self.args) if char.access(account, "puppet")] new_character = [
char for char in search.object_search(self.args) if char.access(account, "puppet")
]
if not new_character: if not new_character:
self.msg("That is not a valid character choice.") self.msg("That is not a valid character choice.")
return return
if len(new_character) > 1: if len(new_character) > 1:
self.msg("Multiple targets with the same name:\n %s" self.msg(
% ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character)) "Multiple targets with the same name:\n %s"
% ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character)
)
return return
else: else:
new_character = new_character[0] new_character = new_character[0]
try: try:
account.puppet_object(session, new_character) account.puppet_object(session, new_character)
account.db._last_puppet = new_character account.db._last_puppet = new_character
logger.log_sec('Puppet Success: (Caller: %s, Target: %s, IP: %s).' % (account, new_character, self.session.address)) logger.log_sec(
"Puppet Success: (Caller: %s, Target: %s, IP: %s)."
% (account, new_character, self.session.address)
)
except RuntimeError as exc: 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))
logger.log_sec('Puppet Failed: %s (Caller: %s, Target: %s, IP: %s).' % (exc, account, new_character, self.session.address)) logger.log_sec(
"Puppet Failed: %s (Caller: %s, Target: %s, IP: %s)."
% (exc, account, new_character, self.session.address)
)
# note that this is inheriting from MuxAccountLookCommand, # note that this is inheriting from MuxAccountLookCommand,
@ -354,6 +398,7 @@ class CmdSessions(COMMAND_DEFAULT_CLASS):
Lists the sessions currently connected to your account. Lists the sessions currently connected to your account.
""" """
key = "sessions" key = "sessions"
locks = "cmd:all()" locks = "cmd:all()"
help_category = "General" help_category = "General"
@ -365,17 +410,18 @@ class CmdSessions(COMMAND_DEFAULT_CLASS):
"""Implement function""" """Implement function"""
account = self.account account = self.account
sessions = account.sessions.all() sessions = account.sessions.all()
table = self.styled_table("|wsessid", table = self.styled_table(
"|wprotocol", "|wsessid", "|wprotocol", "|whost", "|wpuppet/character", "|wlocation"
"|whost", )
"|wpuppet/character",
"|wlocation")
for sess in sorted(sessions, key=lambda x: x.sessid): for sess in sorted(sessions, key=lambda x: x.sessid):
char = account.get_puppet(sess) char = account.get_puppet(sess)
table.add_row(str(sess.sessid), str(sess.protocol_key), table.add_row(
isinstance(sess.address, tuple) and sess.address[0] or sess.address, str(sess.sessid),
char and str(char) or "None", str(sess.protocol_key),
char and str(char.location) or "N/A") isinstance(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) self.msg("|wYour current session(s):|n\n%s" % table)
@ -411,19 +457,23 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
if self.cmdstring == "doing": if self.cmdstring == "doing":
show_session_data = False show_session_data = False
else: else:
show_session_data = account.check_permstring("Developer") or account.check_permstring("Admins") show_session_data = account.check_permstring("Developer") or account.check_permstring(
"Admins"
)
naccounts = SESSIONS.account_count() naccounts = SESSIONS.account_count()
if show_session_data: if show_session_data:
# privileged info # privileged info
table = self.styled_table("|wAccount Name", table = self.styled_table(
"|wOn for", "|wAccount Name",
"|wIdle", "|wOn for",
"|wPuppeting", "|wIdle",
"|wRoom", "|wPuppeting",
"|wCmds", "|wRoom",
"|wProtocol", "|wCmds",
"|wHost") "|wProtocol",
"|wHost",
)
for session in session_list: for session in session_list:
if not session.logged_in: if not session.logged_in:
continue continue
@ -432,14 +482,16 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
account = session.get_account() account = session.get_account()
puppet = session.get_puppet() puppet = session.get_puppet()
location = puppet.location.key if puppet and puppet.location else "None" location = puppet.location.key if puppet and puppet.location else "None"
table.add_row(utils.crop(account.get_display_name(account), width=25), table.add_row(
utils.time_format(delta_conn, 0), utils.crop(account.get_display_name(account), width=25),
utils.time_format(delta_cmd, 1), utils.time_format(delta_conn, 0),
utils.crop(puppet.get_display_name(account) if puppet else "None", width=25), utils.time_format(delta_cmd, 1),
utils.crop(location, width=25), utils.crop(puppet.get_display_name(account) if puppet else "None", width=25),
session.cmd_total, utils.crop(location, width=25),
session.protocol_key, session.cmd_total,
isinstance(session.address, tuple) and session.address[0] or session.address) session.protocol_key,
isinstance(session.address, tuple) and session.address[0] or session.address,
)
else: else:
# unprivileged # unprivileged
table = self.styled_table("|wAccount name", "|wOn for", "|wIdle") table = self.styled_table("|wAccount name", "|wOn for", "|wIdle")
@ -449,12 +501,16 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
delta_cmd = time.time() - session.cmd_last_visible delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time delta_conn = time.time() - session.conn_time
account = session.get_account() account = session.get_account()
table.add_row(utils.crop(account.get_display_name(account), width=25), table.add_row(
utils.time_format(delta_conn, 0), utils.crop(account.get_display_name(account), width=25),
utils.time_format(delta_cmd, 1)) utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1),
)
is_one = naccounts == 1 is_one = naccounts == 1
self.msg("|wAccounts:|n\n%s\n%s unique account%s logged in." self.msg(
% (table, "One" if is_one else naccounts, "" if is_one else "s")) "|wAccounts:|n\n%s\n%s unique account%s logged in."
% (table, "One" if is_one else naccounts, "" if is_one else "s")
)
class CmdOption(COMMAND_DEFAULT_CLASS): class CmdOption(COMMAND_DEFAULT_CLASS):
@ -474,6 +530,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
""" """
key = "option" key = "option"
aliases = "options" aliases = "options"
switch_options = ("save", "clear") switch_options = ("save", "clear")
@ -511,14 +568,18 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
if len(options["SCREENWIDTH"]) == 1: if len(options["SCREENWIDTH"]) == 1:
options["SCREENWIDTH"] = options["SCREENWIDTH"][0] options["SCREENWIDTH"] = options["SCREENWIDTH"][0]
else: else:
options["SCREENWIDTH"] = " \n".join("%s : %s" % (screenid, size) options["SCREENWIDTH"] = " \n".join(
for screenid, size in options["SCREENWIDTH"].items()) "%s : %s" % (screenid, size)
for screenid, size in options["SCREENWIDTH"].items()
)
if "SCREENHEIGHT" in options: if "SCREENHEIGHT" in options:
if len(options["SCREENHEIGHT"]) == 1: if len(options["SCREENHEIGHT"]) == 1:
options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0] options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0]
else: else:
options["SCREENHEIGHT"] = " \n".join("%s : %s" % (screenid, size) options["SCREENHEIGHT"] = " \n".join(
for screenid, size in options["SCREENHEIGHT"].items()) "%s : %s" % (screenid, size)
for screenid, size in options["SCREENHEIGHT"].items()
)
options.pop("TTYPE", None) options.pop("TTYPE", None)
header = ("Name", "Value", "Saved") if saved_options else ("Name", "Value") header = ("Name", "Value", "Saved") if saved_options else ("Name", "Value")
@ -527,7 +588,9 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
row = [key, options[key]] row = [key, options[key]]
if saved_options: if saved_options:
saved = " |YYes|n" if key in saved_options else "" saved = " |YYes|n" if key in saved_options else ""
changed = "|y*|n" if key in saved_options and flags[key] != saved_options[key] else "" changed = (
"|y*|n" if key in saved_options and flags[key] != saved_options[key] else ""
)
row.append("%s%s" % (saved, changed)) row.append("%s%s" % (saved, changed))
table.add_row(*row) 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))
@ -563,30 +626,35 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
self.msg("Option |w%s|n was kept as '|w%s|n'." % (new_name, old_val)) self.msg("Option |w%s|n was kept as '|w%s|n'." % (new_name, old_val))
else: else:
flags[new_name] = 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)) 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} return {new_name: new_val}
except Exception as err: except Exception as err:
self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err)) self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err))
return False return False
validators = {"ANSI": validate_bool, validators = {
"CLIENTNAME": utils.to_str, "ANSI": validate_bool,
"ENCODING": validate_encoding, "CLIENTNAME": utils.to_str,
"MCCP": validate_bool, "ENCODING": validate_encoding,
"NOGOAHEAD": validate_bool, "MCCP": validate_bool,
"MXP": validate_bool, "NOGOAHEAD": validate_bool,
"NOCOLOR": validate_bool, "MXP": validate_bool,
"NOPKEEPALIVE": validate_bool, "NOCOLOR": validate_bool,
"OOB": validate_bool, "NOPKEEPALIVE": validate_bool,
"RAW": validate_bool, "OOB": validate_bool,
"SCREENHEIGHT": validate_size, "RAW": validate_bool,
"SCREENWIDTH": validate_size, "SCREENHEIGHT": validate_size,
"SCREENREADER": validate_bool, "SCREENWIDTH": validate_size,
"TERM": utils.to_str, "SCREENREADER": validate_bool,
"UTF-8": validate_bool, "TERM": utils.to_str,
"XTERM256": validate_bool, "UTF-8": validate_bool,
"INPUTDEBUG": validate_bool, "XTERM256": validate_bool,
"FORCEDENDLINE": validate_bool} "INPUTDEBUG": validate_bool,
"FORCEDENDLINE": validate_bool,
}
name = self.lhs.upper() name = self.lhs.upper()
val = self.rhs.strip() val = self.rhs.strip()
@ -621,6 +689,7 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
Changes your password. Make sure to pick a safe one. Changes your password. Make sure to pick a safe one.
""" """
key = "password" key = "password"
locks = "cmd:pperm(Player)" locks = "cmd:pperm(Player)"
@ -650,7 +719,10 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
account.set_password(newpass) account.set_password(newpass)
account.save() account.save()
self.msg("Password changed.") self.msg("Password changed.")
logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, account, self.session.address)) logger.log_sec(
"Password Changed: %s (Caller: %s, IP: %s)."
% (account, account, self.session.address)
)
class CmdQuit(COMMAND_DEFAULT_CLASS): class CmdQuit(COMMAND_DEFAULT_CLASS):
@ -666,6 +738,7 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
Gracefully disconnect your current session from the Gracefully disconnect your current session from the
game. Use the /all switch to disconnect from all sessions. game. Use the /all switch to disconnect from all sessions.
""" """
key = "quit" key = "quit"
switch_options = ("all",) switch_options = ("all",)
locks = "cmd:all()" locks = "cmd:all()"
@ -677,8 +750,10 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
"""hook function""" """hook function"""
account = self.account account = self.account
if 'all' in self.switches: if "all" in self.switches:
account.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session) account.msg(
"|RQuitting|n all sessions. Hope to see you soon again.", session=self.session
)
reason = "quit/all" reason = "quit/all"
for session in account.sessions.all(): for session in account.sessions.all():
account.disconnect_session_from_account(session, reason) account.disconnect_session_from_account(session, reason)
@ -688,7 +763,10 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
if nsess == 2: if nsess == 2:
account.msg("|RQuitting|n. One session is still connected.", session=self.session) account.msg("|RQuitting|n. One session is still connected.", session=self.session)
elif nsess > 2: elif nsess > 2:
account.msg("|RQuitting|n. %i sessions are still connected." % (nsess - 1), session=self.session) account.msg(
"|RQuitting|n. %i sessions are still connected." % (nsess - 1),
session=self.session,
)
else: else:
# we are quitting the last available session # we are quitting the last available session
account.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session) account.msg("|RQuitting|n. Hope to see you again, soon.", session=self.session)
@ -708,6 +786,7 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
standard. No checking is done to determine your client supports standard. No checking is done to determine your client supports
color - if not you will see rubbish appear. color - if not you will see rubbish appear.
""" """
key = "color" key = "color"
locks = "cmd:all()" locks = "cmd:all()"
help_category = "General" help_category = "General"
@ -719,7 +798,7 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
# relevant color tags to display. Replace if using another schema. # relevant color tags to display. Replace if using another schema.
# This command can only show one set of markup. # This command can only show one set of markup.
slice_bright_fg = slice(7, 15) # from ANSI_PARSER.ansi_map slice_bright_fg = slice(7, 15) # from ANSI_PARSER.ansi_map
slice_dark_fg = slice(15, 23) # from ANSI_PARSER.ansi_map slice_dark_fg = slice(15, 23) # from ANSI_PARSER.ansi_map
slice_dark_bg = slice(-8, None) # from ANSI_PARSER.ansi_map slice_dark_bg = slice(-8, None) # from ANSI_PARSER.ansi_map
slice_bright_bg = slice(None, None) # from ANSI_PARSER.ansi_xterm256_bright_bg_map slice_bright_bg = slice(None, None) # from ANSI_PARSER.ansi_xterm256_bright_bg_map
@ -735,8 +814,12 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
max_widths = [max([len(str(val)) for val in col]) for col in table] max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = [] ftable = []
for irow in range(len(table[0])): for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * ftable.append(
extra_space for icol, col in enumerate(table)]) [
str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
for icol, col in enumerate(table)
]
)
return ftable return ftable
def func(self): def func(self):
@ -745,26 +828,37 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
if self.args.startswith("a"): if self.args.startswith("a"):
# show ansi 16-color table # show ansi 16-color table
from evennia.utils import ansi from evennia.utils import ansi
ap = ansi.ANSI_PARSER ap = ansi.ANSI_PARSER
# ansi colors # ansi colors
# show all ansi color-related codes # show all ansi color-related codes
bright_fg = ["%s%s|n" % (code, code.replace("|", "||")) bright_fg = [
for code, _ in ap.ansi_map[self.slice_bright_fg]] "%s%s|n" % (code, code.replace("|", "||"))
dark_fg = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ansi_map[self.slice_bright_fg]
for code, _ in ap.ansi_map[self.slice_dark_fg]] ]
dark_bg = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) dark_fg = [
for code, _ in ap.ansi_map[self.slice_dark_bg]] "%s%s|n" % (code, code.replace("|", "||"))
bright_bg = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", "")) for code, _ in ap.ansi_map[self.slice_dark_fg]
for code, _ in ap.ansi_xterm256_bright_bg_map[self.slice_bright_bg]] ]
dark_bg = [
"%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ansi_map[self.slice_dark_bg]
]
bright_bg = [
"%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ansi_xterm256_bright_bg_map[self.slice_bright_bg]
]
dark_fg.extend(["" for _ in range(len(bright_fg) - len(dark_fg))]) dark_fg.extend(["" for _ in range(len(bright_fg) - len(dark_fg))])
table = utils.format_table([bright_fg, dark_fg, bright_bg, dark_bg]) table = utils.format_table([bright_fg, dark_fg, bright_bg, dark_bg])
string = "ANSI colors:" string = "ANSI colors:"
for row in table: for row in table:
string += "\n " + " ".join(row) string += "\n " + " ".join(row)
self.msg(string) self.msg(string)
self.msg("||X : black. ||/ : return, ||- : tab, ||_ : space, ||* : invert, ||u : underline\n" self.msg(
"To combine background and foreground, add background marker last, e.g. ||r||[B.\n" "||X : black. ||/ : return, ||- : tab, ||_ : space, ||* : invert, ||u : underline\n"
"Note: bright backgrounds like ||[r requires your client handling Xterm256 colors.") "To combine background and foreground, add background marker last, e.g. ||r||[B.\n"
"Note: bright backgrounds like ||[r requires your client handling Xterm256 colors."
)
elif self.args.startswith("x"): elif self.args.startswith("x"):
# show xterm256 table # show xterm256 table
@ -775,8 +869,10 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
# foreground table # foreground table
table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib))) table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib)))
# background table # background table
table[6 + ir].append("|%i%i%i|[%i%i%i%s|n" table[6 + ir].append(
% (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib))) "|%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) table = self.table_format(table)
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):" 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) string += "\n" + "\n".join("".join(row) for row in table)
@ -845,24 +941,30 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
def func(self): def func(self):
"""Perform the command""" """Perform the command"""
account = self.account account = self.account
permstr = account.is_superuser and " (superuser)" or "(%s)" % (", ".join(account.permissions.all())) permstr = (
if self.cmdstring in ('unquell', 'unquell'): account.is_superuser
if not account.attributes.get('_quell'): and " (superuser)"
or "(%s)" % (", ".join(account.permissions.all()))
)
if self.cmdstring in ("unquell", "unquell"):
if not account.attributes.get("_quell"):
self.msg("Already using normal Account permissions %s." % permstr) self.msg("Already using normal Account permissions %s." % permstr)
else: else:
account.attributes.remove('_quell') account.attributes.remove("_quell")
self.msg("Account permissions %s restored." % permstr) self.msg("Account permissions %s restored." % permstr)
else: else:
if account.attributes.get('_quell'): if account.attributes.get("_quell"):
self.msg("Already quelling Account %s permissions." % permstr) self.msg("Already quelling Account %s permissions." % permstr)
return return
account.attributes.add('_quell', True) account.attributes.add("_quell", True)
puppet = self.session.puppet puppet = self.session.puppet
if puppet: if puppet:
cpermstr = "(%s)" % ", ".join(puppet.permissions.all()) cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
cpermstr = "Quelling to current puppet's permissions %s." % cpermstr cpermstr = "Quelling to current puppet's permissions %s." % cpermstr
cpermstr += "\n(Note: If this is higher than Account permissions %s," \ cpermstr += (
" the lowest of the two will be used.)" % permstr "\n(Note: If this is higher than Account permissions %s,"
" the lowest of the two will be used.)" % permstr
)
cpermstr += "\nUse unquell to return to normal permission usage." cpermstr += "\nUse unquell to return to normal permission usage."
self.msg(cpermstr) self.msg(cpermstr)
else: else:
@ -884,7 +986,7 @@ class CmdStyle(COMMAND_DEFAULT_CLASS):
""" """
key = "style" key = "style"
switch_options = ['clear'] switch_options = ["clear"]
def func(self): def func(self):
if not self.args: if not self.args:
@ -893,11 +995,12 @@ class CmdStyle(COMMAND_DEFAULT_CLASS):
self.set() self.set()
def list_styles(self): def list_styles(self):
table = self.styled_table('Option', 'Description', 'Type', 'Value', width=78) table = self.styled_table("Option", "Description", "Type", "Value", width=78)
for op_key in self.account.options.options_dict.keys(): for op_key in self.account.options.options_dict.keys():
op_found = self.account.options.get(op_key, return_obj=True) op_found = self.account.options.get(op_key, return_obj=True)
table.add_row(op_key, op_found.description, table.add_row(
op_found.__class__.__name__, op_found.display()) op_key, op_found.description, op_found.__class__.__name__, op_found.display()
)
self.msg(str(table)) self.msg(str(table))
def set(self): def set(self):
@ -906,4 +1009,4 @@ class CmdStyle(COMMAND_DEFAULT_CLASS):
except ValueError as e: except ValueError as e:
self.msg(str(e)) self.msg(str(e))
return return
self.msg('Style %s set to %s' % (self.lhs, result)) self.msg("Style %s set to %s" % (self.lhs, result))

View file

@ -16,8 +16,16 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
# limit members for API inclusion # limit members for API inclusion
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", __all__ = (
"CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall", "CmdForce") "CmdBoot",
"CmdBan",
"CmdUnban",
"CmdEmit",
"CmdNewPassword",
"CmdPerm",
"CmdWall",
"CmdForce",
)
class CmdBoot(COMMAND_DEFAULT_CLASS): class CmdBoot(COMMAND_DEFAULT_CLASS):
@ -49,14 +57,14 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
caller.msg("Usage: boot[/switches] <account> [:reason]") caller.msg("Usage: boot[/switches] <account> [:reason]")
return return
if ':' in args: if ":" in args:
args, reason = [a.strip() for a in args.split(':', 1)] args, reason = [a.strip() for a in args.split(":", 1)]
else: else:
args, reason = args, "" args, reason = args, ""
boot_list = [] boot_list = []
if 'sid' in self.switches: if "sid" in self.switches:
# Boot a particular session id. # Boot a particular session id.
sessions = SESSIONS.get_sessions(True) sessions = SESSIONS.get_sessions(True)
for sess in sessions: for sess in sessions:
@ -71,8 +79,8 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
caller.msg("Account %s was not found." % args) caller.msg("Account %s was not found." % args)
return return
pobj = pobj[0] pobj = pobj[0]
if not pobj.access(caller, 'boot'): if not pobj.access(caller, "boot"):
string = "You don't have the permission to boot %s." % (pobj.key, ) string = "You don't have the permission to boot %s." % (pobj.key,)
caller.msg(string) caller.msg(string)
return return
# we have a bootable object with a connected user # we have a bootable object with a connected user
@ -87,7 +95,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
# Carry out the booting of the sessions in the boot list. # Carry out the booting of the sessions in the boot list.
feedback = None feedback = None
if 'quiet' not in self.switches: if "quiet" not in self.switches:
feedback = "You have been disconnected by %s.\n" % caller.name feedback = "You have been disconnected by %s.\n" % caller.name
if reason: if reason:
feedback += "\nReason given: %s" % reason feedback += "\nReason given: %s" % reason
@ -97,7 +105,10 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
session.account.disconnect_session_from_account(session) session.account.disconnect_session_from_account(session)
if pobj and boot_list: if pobj and boot_list:
logger.log_sec('Booted: %s (Reason: %s, Caller: %s, IP: %s).' % (pobj, reason, caller, self.session.address)) logger.log_sec(
"Booted: %s (Reason: %s, Caller: %s, IP: %s)."
% (pobj, reason, caller, self.session.address)
)
# regex matching IP addresses with wildcards, eg. 233.122.4.* # regex matching IP addresses with wildcards, eg. 233.122.4.*
@ -118,9 +129,7 @@ def list_bans(cmd, banlist):
table = cmd.styled_table("|wid", "|wname/ip", "|wdate", "|wreason") table = cmd.styled_table("|wid", "|wname/ip", "|wdate", "|wreason")
for inum, ban in enumerate(banlist): for inum, ban in enumerate(banlist):
table.add_row(str(inum + 1), table.add_row(str(inum + 1), ban[0] and ban[0] or ban[1], ban[3], ban[4])
ban[0] and ban[0] or ban[1],
ban[3], ban[4])
return "|wActive bans:|n\n%s" % table return "|wActive bans:|n\n%s" % table
@ -157,6 +166,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
or region. or region.
""" """
key = "ban" key = "ban"
aliases = ["bans"] aliases = ["bans"]
locks = "cmd:perm(ban) or perm(Developer)" locks = "cmd:perm(ban) or perm(Developer)"
@ -175,20 +185,20 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
'reason' is any optional info given to the command. Unset 'reason' is any optional info given to the command. Unset
values in each tuple is set to the empty string. values in each tuple is set to the empty string.
""" """
banlist = ServerConfig.objects.conf('server_bans') banlist = ServerConfig.objects.conf("server_bans")
if not banlist: if not banlist:
banlist = [] banlist = []
if not self.args or (self.switches and if not self.args or (
not any(switch in ('ip', 'name') self.switches and not any(switch in ("ip", "name") for switch in self.switches)
for switch in self.switches)): ):
self.caller.msg(list_bans(self, banlist)) self.caller.msg(list_bans(self, banlist))
return return
now = time.ctime() now = time.ctime()
reason = "" reason = ""
if ':' in self.args: if ":" in self.args:
ban, reason = self.args.rsplit(':', 1) ban, reason = self.args.rsplit(":", 1)
else: else:
ban = self.args ban = self.args
ban = ban.lower() ban = ban.lower()
@ -202,15 +212,18 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
typ = "IP" typ = "IP"
ban = ipban[0] ban = ipban[0]
# replace * with regex form and compile it # replace * with regex form and compile it
ipregex = ban.replace('.', '\.') ipregex = ban.replace(".", "\.")
ipregex = ipregex.replace('*', '[0-9]{1,3}') ipregex = ipregex.replace("*", "[0-9]{1,3}")
ipregex = re.compile(r"%s" % ipregex) ipregex = re.compile(r"%s" % ipregex)
bantup = ("", ban, ipregex, now, reason) bantup = ("", ban, ipregex, now, reason)
# save updated banlist # save updated banlist
banlist.append(bantup) banlist.append(bantup)
ServerConfig.objects.conf('server_bans', banlist) 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))
logger.log_sec('Banned %s: %s (Caller: %s, IP: %s).' % (typ, ban.strip(), self.caller, self.session.address)) logger.log_sec(
"Banned %s: %s (Caller: %s, IP: %s)."
% (typ, ban.strip(), self.caller, self.session.address)
)
class CmdUnban(COMMAND_DEFAULT_CLASS): class CmdUnban(COMMAND_DEFAULT_CLASS):
@ -226,6 +239,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
unban. unban.
""" """
key = "unban" key = "unban"
locks = "cmd:perm(unban) or perm(Developer)" locks = "cmd:perm(unban) or perm(Developer)"
help_category = "Admin" help_category = "Admin"
@ -233,7 +247,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
def func(self): def func(self):
"""Implement unbanning""" """Implement unbanning"""
banlist = ServerConfig.objects.conf('server_bans') banlist = ServerConfig.objects.conf("server_bans")
if not self.args: if not self.args:
self.caller.msg(list_bans(self, banlist)) self.caller.msg(list_bans(self, banlist))
@ -253,11 +267,13 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
# all is ok, clear ban # all is ok, clear ban
ban = banlist[num - 1] ban = banlist[num - 1]
del banlist[num - 1] del banlist[num - 1]
ServerConfig.objects.conf('server_bans', banlist) ServerConfig.objects.conf("server_bans", banlist)
value = " ".join([s for s in ban[:2]]) value = " ".join([s for s in ban[:2]])
self.caller.msg("Cleared ban %s: %s" % self.caller.msg("Cleared ban %s: %s" % (num, value))
(num, value)) logger.log_sec(
logger.log_sec('Unbanned: %s (Caller: %s, IP: %s).' % (value.strip(), self.caller, self.session.address)) "Unbanned: %s (Caller: %s, IP: %s)."
% (value.strip(), self.caller, self.session.address)
)
class CmdEmit(COMMAND_DEFAULT_CLASS): class CmdEmit(COMMAND_DEFAULT_CLASS):
@ -280,6 +296,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
limited forms of emit, for sending to rooms and limited forms of emit, for sending to rooms and
to accounts respectively. to accounts respectively.
""" """
key = "emit" key = "emit"
aliases = ["pemit", "remit"] aliases = ["pemit", "remit"]
switch_options = ("room", "accounts", "contents") switch_options = ("room", "accounts", "contents")
@ -300,15 +317,15 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
caller.msg(string) caller.msg(string)
return return
rooms_only = 'rooms' in self.switches rooms_only = "rooms" in self.switches
accounts_only = 'accounts' in self.switches accounts_only = "accounts" in self.switches
send_to_contents = 'contents' in self.switches send_to_contents = "contents" in self.switches
# we check which command was used to force the switches # we check which command was used to force the switches
if self.cmdstring == 'remit': if self.cmdstring == "remit":
rooms_only = True rooms_only = True
send_to_contents = True send_to_contents = True
elif self.cmdstring == 'pemit': elif self.cmdstring == "pemit":
accounts_only = True accounts_only = True
if not self.rhs: if not self.rhs:
@ -329,7 +346,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
if accounts_only and not obj.has_account: if accounts_only and not obj.has_account:
caller.msg("%s has no active account. Ignored." % objname) caller.msg("%s has no active account. Ignored." % objname)
continue continue
if obj.access(caller, 'tell'): if obj.access(caller, "tell"):
obj.msg(message) obj.msg(message)
if send_to_contents and hasattr(obj, "msg_contents"): if send_to_contents and hasattr(obj, "msg_contents"):
obj.msg_contents(message) obj.msg_contents(message)
@ -382,9 +399,10 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
account.save() account.save()
self.msg("%s - new password set to '%s'." % (account.name, newpass)) self.msg("%s - new password set to '%s'." % (account.name, newpass))
if account.character != caller: if account.character != caller:
account.msg("%s has changed your password to '%s'." % (caller.name, account.msg("%s has changed your password to '%s'." % (caller.name, newpass))
newpass)) logger.log_sec(
logger.log_sec('Password Changed: %s (Caller: %s, IP: %s).' % (account, caller, self.session.address)) "Password Changed: %s (Caller: %s, IP: %s)." % (account, caller, self.session.address)
)
class CmdPerm(COMMAND_DEFAULT_CLASS): class CmdPerm(COMMAND_DEFAULT_CLASS):
@ -402,6 +420,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
This command sets/clears individual permission strings on an object This command sets/clears individual permission strings on an object
or account. If no permission is given, list all permissions on <object>. or account. If no permission is given, list all permissions on <object>.
""" """
key = "perm" key = "perm"
aliases = "setperm" aliases = "setperm"
switch_options = ("del", "account") switch_options = ("del", "account")
@ -420,7 +439,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
caller.msg(string) caller.msg(string)
return return
accountmode = 'account' in self.switches or lhs.startswith('*') accountmode = "account" in self.switches or lhs.startswith("*")
lhs = lhs.lstrip("*") lhs = lhs.lstrip("*")
if accountmode: if accountmode:
@ -431,7 +450,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
return return
if not rhs: if not rhs:
if not obj.access(caller, 'examine'): if not obj.access(caller, "examine"):
caller.msg("You are not allowed to examine this object.") caller.msg("You are not allowed to examine this object.")
return return
@ -440,9 +459,11 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
string += "<None>" string += "<None>"
else: else:
string += ", ".join(obj.permissions.all()) string += ", ".join(obj.permissions.all())
if (hasattr(obj, 'account') and if (
hasattr(obj.account, 'is_superuser') and hasattr(obj, "account")
obj.account.is_superuser): and hasattr(obj.account, "is_superuser")
and obj.account.is_superuser
):
string += "\n(... but this object is currently controlled by a SUPERUSER! " string += "\n(... but this object is currently controlled by a SUPERUSER! "
string += "All access checks are passed automatically.)" string += "All access checks are passed automatically.)"
caller.msg(string) caller.msg(string)
@ -451,22 +472,33 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
# we supplied an argument on the form obj = perm # we supplied an argument on the form obj = perm
locktype = "edit" if accountmode else "control" locktype = "edit" if accountmode else "control"
if not obj.access(caller, locktype): if not obj.access(caller, locktype):
caller.msg("You are not allowed to edit this %s's permissions." caller.msg(
% ("account" if accountmode else "object")) "You are not allowed to edit this %s's permissions."
% ("account" if accountmode else "object")
)
return return
caller_result = [] caller_result = []
target_result = [] target_result = []
if 'del' in switches: if "del" in switches:
# delete the given permission(s) from object. # delete the given permission(s) from object.
for perm in self.rhslist: for perm in self.rhslist:
obj.permissions.remove(perm) obj.permissions.remove(perm)
if obj.permissions.get(perm): if obj.permissions.get(perm):
caller_result.append("\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: else:
caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name)) caller_result.append(
target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm)) "\nPermission %s removed from %s (if they existed)." % (perm, obj.name)
logger.log_sec('Permissions Deleted: %s, %s (Caller: %s, IP: %s).' % (perm, obj, caller, self.session.address)) )
target_result.append(
"\n%s revokes the permission(s) %s from you." % (caller.name, perm)
)
logger.log_sec(
"Permissions Deleted: %s, %s (Caller: %s, IP: %s)."
% (perm, obj, caller, self.session.address)
)
else: else:
# add a new permission # add a new permission
permissions = obj.permissions.all() permissions = obj.permissions.all()
@ -475,20 +507,32 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
# don't allow to set a permission higher in the hierarchy than # don't allow to set a permission higher in the hierarchy than
# the one the caller has (to prevent self-escalation) # the one the caller has (to prevent self-escalation)
if (perm.lower() in PERMISSION_HIERARCHY and not if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(
obj.locks.check_lockstring(caller, "dummy:perm(%s)" % perm)): caller, "dummy:perm(%s)" % perm
caller.msg("You cannot assign a permission higher than the one you have yourself.") ):
caller.msg(
"You cannot assign a permission higher than the one you have yourself."
)
return return
if perm in permissions: if perm in permissions:
caller_result.append("\nPermission '%s' is already defined on %s." % (perm, obj.name)) caller_result.append(
"\nPermission '%s' is already defined on %s." % (perm, obj.name)
)
else: else:
obj.permissions.add(perm) obj.permissions.add(perm)
plystring = "the Account" if accountmode else "the Object/Character" plystring = "the Account" if accountmode else "the Object/Character"
caller_result.append("\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring)) caller_result.append(
target_result.append("\n%s gives you (%s, %s) the permission '%s'." "\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring)
% (caller.name, obj.name, plystring, perm)) )
logger.log_sec('Permissions Added: %s, %s (Caller: %s, IP: %s).' % (obj, perm, caller, self.session.address)) target_result.append(
"\n%s gives you (%s, %s) the permission '%s'."
% (caller.name, obj.name, plystring, perm)
)
logger.log_sec(
"Permissions Added: %s, %s (Caller: %s, IP: %s)."
% (obj, perm, caller, self.session.address)
)
caller.msg("".join(caller_result).strip()) caller.msg("".join(caller_result).strip())
if target_result: if target_result:
@ -505,6 +549,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
Announces a message to all connected sessions Announces a message to all connected sessions
including all currently unlogged in. including all currently unlogged in.
""" """
key = "wall" key = "wall"
locks = "cmd:perm(wall) or perm(Admin)" locks = "cmd:perm(wall) or perm(Admin)"
help_category = "Admin" help_category = "Admin"
@ -514,7 +559,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
if not self.args: if not self.args:
self.caller.msg("Usage: wall <message>") self.caller.msg("Usage: wall <message>")
return return
message = "%s shouts \"%s\"" % (self.caller.name, self.args) message = '%s shouts "%s"' % (self.caller.name, self.args)
self.msg("Announcing to all connected sessions ...") self.msg("Announcing to all connected sessions ...")
SESSIONS.announce_all(message) SESSIONS.announce_all(message)
@ -529,6 +574,7 @@ class CmdForce(COMMAND_DEFAULT_CLASS):
Example: Example:
force bob=get stick force bob=get stick
""" """
key = "force" key = "force"
locks = "cmd:perm(spawn) or perm(Builder)" locks = "cmd:perm(spawn) or perm(Builder)"
help_category = "Building" help_category = "Building"

View file

@ -84,6 +84,7 @@ print "leaving run ..."
# Helper functions # Helper functions
# ------------------------------------------------------------- # -------------------------------------------------------------
def format_header(caller, entry): def format_header(caller, entry):
""" """
Formats a header Formats a header
@ -100,7 +101,7 @@ def format_header(caller, entry):
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. # add extra space to the side for padding.
header = "%s%s" % (header, " " * (width - len(header))) header = "%s%s" % (header, " " * (width - len(header)))
header = header.replace('\n', '\\n') header = header.replace("\n", "\\n")
return header return header
@ -110,7 +111,7 @@ def format_code(entry):
Formats the viewing of code and errors Formats the viewing of code and errors
""" """
code = "" code = ""
for line in entry.split('\n'): for line in entry.split("\n"):
code += "\n|G>>>|n %s" % line code += "\n|G>>>|n %s" % line
return code.strip() return code.strip()
@ -141,8 +142,7 @@ def batch_code_exec(caller):
code = stack[ptr] code = stack[ptr]
caller.msg(format_header(caller, code)) caller.msg(format_header(caller, code))
err = BATCHCODE.code_exec(code, err = BATCHCODE.code_exec(code, extra_environ={"caller": caller}, debug=debug)
extra_environ={"caller": caller}, debug=debug)
if err: if err:
caller.msg(format_code(err)) caller.msg(format_code(err))
return False return False
@ -185,7 +185,7 @@ def show_curr(caller, showall=False):
codeall = entry.strip() codeall = entry.strip()
string += "|G(hh for help)" string += "|G(hh for help)"
if showall: if showall:
for line in codeall.split('\n'): for line in codeall.split("\n"):
string += "\n|G||n %s" % line string += "\n|G||n %s" % line
caller.msg(string) caller.msg(string)
@ -214,6 +214,7 @@ def purge_processor(caller):
caller.scripts.validate() # this will purge interactive mode caller.scripts.validate() # this will purge interactive mode
# ------------------------------------------------------------- # -------------------------------------------------------------
# main access commands # main access commands
# ------------------------------------------------------------- # -------------------------------------------------------------
@ -234,6 +235,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
Runs batches of commands from a batch-cmd text file (*.ev). Runs batches of commands from a batch-cmd text file (*.ev).
""" """
key = "batchcommands" key = "batchcommands"
aliases = ["batchcommand", "batchcmd"] aliases = ["batchcommand", "batchcmd"]
switch_options = ("interactive",) switch_options = ("interactive",)
@ -263,8 +265,10 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
err = "{}\n".format(str(err)) err = "{}\n".format(str(err))
else: else:
err = "" err = ""
string = "%s'%s' could not load. You have to supply python paths " \ string = (
"from one of the defined batch-file directories\n (%s)." "%s'%s' could not load. You have to supply python paths "
"from one of the defined batch-file directories\n (%s)."
)
caller.msg(string % (err, python_path, ", ".join(settings.BASE_BATCHPROCESS_PATHS))) caller.msg(string % (err, python_path, ", ".join(settings.BASE_BATCHPROCESS_PATHS)))
return return
if not commands: if not commands:
@ -282,7 +286,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
caller.ndb.batch_cmdset_backup = list(caller.cmdset.cmdset_stack) caller.ndb.batch_cmdset_backup = list(caller.cmdset.cmdset_stack)
caller.cmdset.add(BatchSafeCmdSet) caller.cmdset.add(BatchSafeCmdSet)
if 'inter' in switches or 'interactive' in switches: if "inter" in switches or "interactive" in switches:
# Allow more control over how batch file is executed # Allow more control over how batch file is executed
# Set interactive state directly # Set interactive state directly
@ -291,9 +295,10 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path) caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path)
show_curr(caller) show_curr(caller)
else: else:
caller.msg("Running Batch-command processor - Automatic mode " caller.msg(
"for %s (this might take some time) ..." "Running Batch-command processor - Automatic mode "
% python_path) "for %s (this might take some time) ..." % python_path
)
procpool = False procpool = False
if "PythonProcPool" in utils.server_services(): if "PythonProcPool" in utils.server_services():
@ -312,11 +317,13 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
caller.msg(" |RError from processor: '%s'" % e) caller.msg(" |RError from processor: '%s'" % e)
purge_processor(caller) purge_processor(caller)
utils.run_async(_PROCPOOL_BATCHCMD_SOURCE, utils.run_async(
commands=commands, _PROCPOOL_BATCHCMD_SOURCE,
caller=caller, commands=commands,
at_return=callback, caller=caller,
at_err=errback) at_return=callback,
at_err=errback,
)
else: else:
# run in-process (might block) # run in-process (might block)
for _ in range(len(commands)): for _ in range(len(commands)):
@ -350,6 +357,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
Runs batches of commands from a batch-code text file (*.py). Runs batches of commands from a batch-code text file (*.py).
""" """
key = "batchcode" key = "batchcode"
aliases = ["batchcodes"] aliases = ["batchcodes"]
switch_options = ("interactive", "debug") switch_options = ("interactive", "debug")
@ -366,7 +374,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
caller.msg("Usage: batchcode[/interactive/debug] <path.to.file>") caller.msg("Usage: batchcode[/interactive/debug] <path.to.file>")
return return
python_path = self.args python_path = self.args
debug = 'debug' in self.switches debug = "debug" in self.switches
# parse indata file # parse indata file
try: try:
@ -379,8 +387,10 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
err = "{}\n".format(str(err)) err = "{}\n".format(str(err))
else: else:
err = "" err = ""
string = "%s'%s' could not load. You have to supply python paths " \ string = (
"from one of the defined batch-file directories\n (%s)." "%s'%s' could not load. You have to supply python paths "
"from one of the defined batch-file directories\n (%s)."
)
caller.msg(string % (err, python_path, ", ".join(settings.BASE_BATCHPROCESS_PATHS))) caller.msg(string % (err, python_path, ", ".join(settings.BASE_BATCHPROCESS_PATHS)))
return return
if not codes: if not codes:
@ -399,7 +409,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
caller.ndb.batch_cmdset_backup = list(caller.cmdset.cmdset_stack) caller.ndb.batch_cmdset_backup = list(caller.cmdset.cmdset_stack)
caller.cmdset.add(BatchSafeCmdSet) caller.cmdset.add(BatchSafeCmdSet)
if 'inter' in switches or 'interactive'in switches: if "inter" in switches or "interactive" in switches:
# Allow more control over how batch file is executed # Allow more control over how batch file is executed
# Set interactive state directly # Set interactive state directly
@ -425,11 +435,14 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
def errback(e): def errback(e):
caller.msg(" |RError from processor: '%s'" % e) caller.msg(" |RError from processor: '%s'" % e)
purge_processor(caller) purge_processor(caller)
utils.run_async(_PROCPOOL_BATCHCODE_SOURCE,
codes=codes, utils.run_async(
caller=caller, _PROCPOOL_BATCHCODE_SOURCE,
at_return=callback, codes=codes,
at_err=errback) caller=caller,
at_return=callback,
at_err=errback,
)
else: else:
# un in-process (will block) # un in-process (will block)
for _ in range(len(codes)): for _ in range(len(codes)):
@ -449,6 +462,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
# (these are the same for both processors) # (these are the same for both processors)
# ------------------------------------------------------------- # -------------------------------------------------------------
class CmdStateAbort(_COMMAND_DEFAULT_CLASS): class CmdStateAbort(_COMMAND_DEFAULT_CLASS):
""" """
abort abort
@ -457,6 +471,7 @@ class CmdStateAbort(_COMMAND_DEFAULT_CLASS):
the default cmdset, regardless of what current cmdset the processor might the default cmdset, regardless of what current cmdset the processor might
have put us in (e.g. when testing buggy scripts etc). have put us in (e.g. when testing buggy scripts etc).
""" """
key = "abort" key = "abort"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -474,6 +489,7 @@ class CmdStateLL(_COMMAND_DEFAULT_CLASS):
Look at the full source for the current Look at the full source for the current
command definition. command definition.
""" """
key = "ll" key = "ll"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -488,6 +504,7 @@ class CmdStatePP(_COMMAND_DEFAULT_CLASS):
Process the currently shown command definition. Process the currently shown command definition.
""" """
key = "pp" key = "pp"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -510,6 +527,7 @@ class CmdStateRR(_COMMAND_DEFAULT_CLASS):
Reload the batch file, keeping the current Reload the batch file, keeping the current
position in it. position in it.
""" """
key = "rr" key = "rr"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -532,6 +550,7 @@ class CmdStateRRR(_COMMAND_DEFAULT_CLASS):
Reload the batch file, starting over Reload the batch file, starting over
from the beginning. from the beginning.
""" """
key = "rrr" key = "rrr"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -553,6 +572,7 @@ class CmdStateNN(_COMMAND_DEFAULT_CLASS):
Go to next command. No commands are executed. Go to next command. No commands are executed.
""" """
key = "nn" key = "nn"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -575,6 +595,7 @@ class CmdStateNL(_COMMAND_DEFAULT_CLASS):
Go to next command, viewing its full source. Go to next command, viewing its full source.
No commands are executed. No commands are executed.
""" """
key = "nl" key = "nl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -597,6 +618,7 @@ class CmdStateBB(_COMMAND_DEFAULT_CLASS):
Backwards to previous command. No commands Backwards to previous command. No commands
are executed. are executed.
""" """
key = "bb" key = "bb"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -619,6 +641,7 @@ class CmdStateBL(_COMMAND_DEFAULT_CLASS):
Backwards to previous command, viewing its full Backwards to previous command, viewing its full
source. No commands are executed. source. No commands are executed.
""" """
key = "bl" key = "bl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -642,6 +665,7 @@ class CmdStateSS(_COMMAND_DEFAULT_CLASS):
one. If steps is given, one. If steps is given,
process this many commands. process this many commands.
""" """
key = "ss" key = "ss"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -671,6 +695,7 @@ class CmdStateSL(_COMMAND_DEFAULT_CLASS):
one, viewing its full source. If steps is given, one, viewing its full source. If steps is given,
process this many commands. process this many commands.
""" """
key = "sl" key = "sl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -699,6 +724,7 @@ class CmdStateCC(_COMMAND_DEFAULT_CLASS):
Continue to process all remaining Continue to process all remaining
commands. commands.
""" """
key = "cc" key = "cc"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -727,6 +753,7 @@ class CmdStateJJ(_COMMAND_DEFAULT_CLASS):
Jump to specific command number Jump to specific command number
""" """
key = "jj" key = "jj"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -751,6 +778,7 @@ class CmdStateJL(_COMMAND_DEFAULT_CLASS):
Jump to specific command number and view its full source. Jump to specific command number and view its full source.
""" """
key = "jl" key = "jl"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -775,6 +803,7 @@ class CmdStateQQ(_COMMAND_DEFAULT_CLASS):
Quit the batchprocessor. Quit the batchprocessor.
""" """
key = "qq" key = "qq"
help_category = "BatchProcess" help_category = "BatchProcess"
locks = "cmd:perm(batchcommands)" locks = "cmd:perm(batchcommands)"
@ -827,12 +856,14 @@ class CmdStateHH(_COMMAND_DEFAULT_CLASS):
# #
# ------------------------------------------------------------- # -------------------------------------------------------------
class BatchSafeCmdSet(CmdSet): class BatchSafeCmdSet(CmdSet):
""" """
The base cmdset for the batch processor. The base cmdset for the batch processor.
This sets a 'safe' abort command that will This sets a 'safe' abort command that will
always be available to get out of everything. always be available to get out of everything.
""" """
key = "Batch_default" key = "Batch_default"
priority = 150 # override other cmdsets. priority = 150 # override other cmdsets.
@ -845,6 +876,7 @@ class BatchInteractiveCmdSet(CmdSet):
""" """
The cmdset for the interactive batch processor mode. The cmdset for the interactive batch processor mode.
""" """
key = "Batch_interactive" key = "Batch_interactive"
priority = 104 priority = 104

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@ class CharacterCmdSet(CmdSet):
""" """
Implements the default command set. Implements the default command set.
""" """
key = "DefaultCharacter" key = "DefaultCharacter"
priority = 0 priority = 0

View file

@ -9,6 +9,7 @@ class SessionCmdSet(CmdSet):
""" """
Sets up the unlogged cmdset. Sets up the unlogged cmdset.
""" """
key = "DefaultSession" key = "DefaultSession"
priority = -20 priority = -20

View file

@ -11,6 +11,7 @@ class UnloggedinCmdSet(CmdSet):
""" """
Sets up the unlogged cmdset. Sets up the unlogged cmdset.
""" """
key = "DefaultUnloggedin" key = "DefaultUnloggedin"
priority = 0 priority = 0

View file

@ -23,10 +23,22 @@ CHANNEL_DEFAULT_TYPECLASS = class_from_module(settings.BASE_CHANNEL_TYPECLASS)
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdAddCom", "CmdDelCom", "CmdAllCom", __all__ = (
"CmdChannels", "CmdCdestroy", "CmdCBoot", "CmdCemit", "CmdAddCom",
"CmdCWho", "CmdChannelCreate", "CmdClock", "CmdCdesc", "CmdDelCom",
"CmdPage", "CmdIRC2Chan", "CmdRSS2Chan") "CmdAllCom",
"CmdChannels",
"CmdCdestroy",
"CmdCBoot",
"CmdCemit",
"CmdCWho",
"CmdChannelCreate",
"CmdClock",
"CmdCdesc",
"CmdPage",
"CmdIRC2Chan",
"CmdRSS2Chan",
)
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
@ -38,8 +50,11 @@ def find_channel(caller, channelname, silent=False, noaliases=False):
channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname) channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname)
if not channels: if not channels:
if not noaliases: if not noaliases:
channels = [chan for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() channels = [
if channelname in chan.aliases.all()] chan
for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
if channelname in chan.aliases.all()
]
if channels: if channels:
return channels[0] return channels[0]
if not silent: if not silent:
@ -99,7 +114,7 @@ class CmdAddCom(COMMAND_DEFAULT_CLASS):
return return
# check permissions # check permissions
if not channel.access(account, 'listen'): if not channel.access(account, "listen"):
self.msg("%s: You are not allowed to listen to this channel." % channel.key) self.msg("%s: You are not allowed to listen to this channel." % channel.key)
return return
@ -171,8 +186,11 @@ class CmdDelCom(COMMAND_DEFAULT_CLASS):
delnicks = "all" in self.switches delnicks = "all" in self.switches
# find all nicks linked to this channel and delete them # find all nicks linked to this channel and delete them
if delnicks: if delnicks:
for nick in [nick for nick in make_iter(caller.nicks.get(category="channel", return_obj=True)) for nick in [
if nick and nick.pk and nick.value[3].lower() == chkey]: nick
for nick in make_iter(caller.nicks.get(category="channel", return_obj=True))
if nick and nick.pk and nick.value[3].lower() == chkey
]:
nick.delete() nick.delete()
disconnect = channel.disconnect(account) disconnect = channel.disconnect(account)
if disconnect: if disconnect:
@ -227,8 +245,11 @@ class CmdAllCom(COMMAND_DEFAULT_CLASS):
if args == "on": if args == "on":
# get names of all channels available to listen to # get names of all channels available to listen to
# and activate them all # and activate them all
channels = [chan for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() channels = [
if chan.access(caller, 'listen')] chan
for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
if chan.access(caller, "listen")
]
for channel in channels: for channel in channels:
self.execute_cmd("addcom %s" % channel.key) self.execute_cmd("addcom %s" % channel.key)
elif args == "off": elif args == "off":
@ -238,15 +259,21 @@ class CmdAllCom(COMMAND_DEFAULT_CLASS):
self.execute_cmd("delcom %s" % channel.key) self.execute_cmd("delcom %s" % channel.key)
elif args == "destroy": elif args == "destroy":
# destroy all channels you control # destroy all channels you control
channels = [chan for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() channels = [
if chan.access(caller, 'control')] chan
for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
if chan.access(caller, "control")
]
for channel in channels: for channel in channels:
self.execute_cmd("cdestroy %s" % channel.key) self.execute_cmd("cdestroy %s" % channel.key)
elif args == "who": elif args == "who":
# run a who, listing the subscribers on visible channels. # run a who, listing the subscribers on visible channels.
string = "\n|CChannel subscriptions|n" string = "\n|CChannel subscriptions|n"
channels = [chan for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() channels = [
if chan.access(caller, 'listen')] chan
for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
if chan.access(caller, "listen")
]
if not channels: if not channels:
string += "No channels." string += "No channels."
for channel in channels: for channel in channels:
@ -270,6 +297,7 @@ class CmdChannels(COMMAND_DEFAULT_CLASS):
Use 'comlist' to only view your current channel subscriptions. Use 'comlist' to only view your current channel subscriptions.
Use addcom/delcom to join and leave channels Use addcom/delcom to join and leave channels
""" """
key = "channels" key = "channels"
aliases = ["clist", "comlist", "chanlist", "channellist", "all channels"] aliases = ["clist", "comlist", "chanlist", "channellist", "all channels"]
help_category = "Comms" help_category = "Comms"
@ -284,8 +312,11 @@ class CmdChannels(COMMAND_DEFAULT_CLASS):
caller = self.caller caller = self.caller
# all channels we have available to listen to # all channels we have available to listen to
channels = [chan for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels() channels = [
if chan.access(caller, 'listen')] chan
for chan in CHANNEL_DEFAULT_TYPECLASS.objects.get_all_channels()
if chan.access(caller, "listen")
]
if not channels: if not channels:
self.msg("No channels available.") self.msg("No channels available.")
return return
@ -294,22 +325,46 @@ class CmdChannels(COMMAND_DEFAULT_CLASS):
if self.cmdstring == "comlist": if self.cmdstring == "comlist":
# just display the subscribed channels with no extra info # just display the subscribed channels with no extra info
comtable = self.styled_table("|wchannel|n", "|wmy aliases|n", comtable = self.styled_table(
"|wdescription|n", align="l", maxwidth=_DEFAULT_WIDTH) "|wchannel|n",
"|wmy aliases|n",
"|wdescription|n",
align="l",
maxwidth=_DEFAULT_WIDTH,
)
for chan in subs: for chan in subs:
clower = chan.key.lower() clower = chan.key.lower()
nicks = caller.nicks.get(category="channel", return_obj=True) nicks = caller.nicks.get(category="channel", return_obj=True)
comtable.add_row(*["%s%s" % (chan.key, chan.aliases.all() and comtable.add_row(
"(%s)" % ",".join(chan.aliases.all()) or ""), *[
"%s" % ",".join(nick.db_key for nick in make_iter(nicks) "%s%s"
if nick and nick.value[3].lower() == clower), % (
chan.db.desc]) chan.key,
self.msg("\n|wChannel subscriptions|n (use |wchannels|n to list all," chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or "",
" |waddcom|n/|wdelcom|n to sub/unsub):|n\n%s" % comtable) ),
"%s"
% ",".join(
nick.db_key
for nick in make_iter(nicks)
if nick and nick.value[3].lower() == clower
),
chan.db.desc,
]
)
self.msg(
"\n|wChannel subscriptions|n (use |wchannels|n to list all,"
" |waddcom|n/|wdelcom|n to sub/unsub):|n\n%s" % comtable
)
else: else:
# full listing (of channels caller is able to listen to) # full listing (of channels caller is able to listen to)
comtable = self.styled_table("|wsub|n", "|wchannel|n", "|wmy aliases|n", comtable = self.styled_table(
"|wlocks|n", "|wdescription|n", maxwidth=_DEFAULT_WIDTH) "|wsub|n",
"|wchannel|n",
"|wmy aliases|n",
"|wlocks|n",
"|wdescription|n",
maxwidth=_DEFAULT_WIDTH,
)
for chan in channels: for chan in channels:
clower = chan.key.lower() clower = chan.key.lower()
nicks = caller.nicks.get(category="channel", return_obj=True) nicks = caller.nicks.get(category="channel", return_obj=True)
@ -320,17 +375,30 @@ class CmdChannels(COMMAND_DEFAULT_CLASS):
substatus = "|rMuted|n" substatus = "|rMuted|n"
else: else:
substatus = "|gYes|n" substatus = "|gYes|n"
comtable.add_row(*[substatus, comtable.add_row(
"%s%s" % (chan.key, chan.aliases.all() and *[
"(%s)" % ",".join(chan.aliases.all()) or ""), substatus,
"%s" % ",".join(nick.db_key for nick in make_iter(nicks) "%s%s"
if nick.value[3].lower() == clower), % (
str(chan.locks), chan.key,
chan.db.desc]) chan.aliases.all() and "(%s)" % ",".join(chan.aliases.all()) or "",
),
"%s"
% ",".join(
nick.db_key
for nick in make_iter(nicks)
if nick.value[3].lower() == clower
),
str(chan.locks),
chan.db.desc,
]
)
comtable.reformat_column(0, width=9) comtable.reformat_column(0, width=9)
comtable.reformat_column(3, width=14) comtable.reformat_column(3, width=14)
self.msg("\n|wAvailable channels|n (use |wcomlist|n,|waddcom|n and |wdelcom|n" self.msg(
" to manage subscriptions):\n%s" % comtable) "\n|wAvailable channels|n (use |wcomlist|n,|waddcom|n and |wdelcom|n"
" to manage subscriptions):\n%s" % comtable
)
class CmdCdestroy(COMMAND_DEFAULT_CLASS): class CmdCdestroy(COMMAND_DEFAULT_CLASS):
@ -361,7 +429,7 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
if not channel: if not channel:
self.msg("Could not find channel %s." % self.args) self.msg("Could not find channel %s." % self.args)
return return
if not channel.access(caller, 'control'): if not channel.access(caller, "control"):
self.msg("You are not allowed to do that.") self.msg("You are not allowed to do that.")
return return
channel_key = channel.key channel_key = channel.key
@ -371,7 +439,10 @@ class CmdCdestroy(COMMAND_DEFAULT_CLASS):
channel.delete() channel.delete()
CHANNELHANDLER.update() CHANNELHANDLER.update()
self.msg("Channel '%s' was destroyed." % channel_key) self.msg("Channel '%s' was destroyed." % channel_key)
logger.log_sec('Channel Deleted: %s (Caller: %s, IP: %s).' % (channel_key, caller, self.session.address)) logger.log_sec(
"Channel Deleted: %s (Caller: %s, IP: %s)."
% (channel_key, caller, self.session.address)
)
class CmdCBoot(COMMAND_DEFAULT_CLASS): class CmdCBoot(COMMAND_DEFAULT_CLASS):
@ -410,9 +481,9 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
reason = "" reason = ""
if ":" in self.rhs: if ":" in self.rhs:
accountname, reason = self.rhs.rsplit(":", 1) accountname, reason = self.rhs.rsplit(":", 1)
searchstring = accountname.lstrip('*') searchstring = accountname.lstrip("*")
else: else:
searchstring = self.rhs.lstrip('*') searchstring = self.rhs.lstrip("*")
account = self.caller.search(searchstring, account=True) account = self.caller.search(searchstring, account=True)
if not account: if not account:
return return
@ -430,15 +501,19 @@ class CmdCBoot(COMMAND_DEFAULT_CLASS):
string = "%s boots %s from channel.%s" % (self.caller, account.key, reason) string = "%s boots %s from channel.%s" % (self.caller, account.key, reason)
channel.msg(string) channel.msg(string)
# find all account's nicks linked to this channel and delete them # find all account's nicks linked to this channel and delete them
for nick in [nick for nick in for nick in [
account.character.nicks.get(category="channel") or [] nick
if nick.value[3].lower() == channel.key]: for nick in account.character.nicks.get(category="channel") or []
if nick.value[3].lower() == channel.key
]:
nick.delete() nick.delete()
# disconnect account # disconnect account
channel.disconnect(account) channel.disconnect(account)
CHANNELHANDLER.update() CHANNELHANDLER.update()
logger.log_sec('Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s).' % ( logger.log_sec(
account, channel, reason, self.caller, self.session.address)) "Channel Boot: %s (Channel: %s, Reason: %s, Caller: %s, IP: %s)."
% (account, channel, reason, self.caller, self.session.address)
)
class CmdCemit(COMMAND_DEFAULT_CLASS): class CmdCemit(COMMAND_DEFAULT_CLASS):
@ -499,6 +574,7 @@ class CmdCWho(COMMAND_DEFAULT_CLASS):
List who is connected to a given channel you have access to. List who is connected to a given channel you have access to.
""" """
key = "cwho" key = "cwho"
locks = "cmd: not pperm(channel_banned)" locks = "cmd: not pperm(channel_banned)"
help_category = "Comms" help_category = "Comms"
@ -560,19 +636,16 @@ class CmdChannelCreate(COMMAND_DEFAULT_CLASS):
lhs = self.lhs lhs = self.lhs
channame = lhs channame = lhs
aliases = None aliases = None
if ';' in lhs: if ";" in lhs:
channame, aliases = lhs.split(';', 1) channame, aliases = lhs.split(";", 1)
aliases = [alias.strip().lower() for alias in aliases.split(';')] aliases = [alias.strip().lower() for alias in aliases.split(";")]
channel = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channame) channel = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channame)
if channel: if channel:
self.msg("A channel with that name already exists.") self.msg("A channel with that name already exists.")
return return
# Create and set the channel up # Create and set the channel up
lockstring = "send:all();listen:all();control:id(%s)" % caller.id lockstring = "send:all();listen:all();control:id(%s)" % caller.id
new_chan = create.create_channel(channame.strip(), new_chan = create.create_channel(channame.strip(), aliases, description, locks=lockstring)
aliases,
description,
locks=lockstring)
new_chan.connect(caller) new_chan.connect(caller)
CHANNELHANDLER.update() CHANNELHANDLER.update()
self.msg("Created channel %s and connected to it." % new_chan.key) self.msg("Created channel %s and connected to it." % new_chan.key)
@ -662,14 +735,13 @@ class CmdCdesc(COMMAND_DEFAULT_CLASS):
self.msg("Channel '%s' not found." % self.lhs) self.msg("Channel '%s' not found." % self.lhs)
return return
# check permissions # check permissions
if not channel.access(caller, 'control'): if not channel.access(caller, "control"):
self.msg("You cannot admin this channel.") self.msg("You cannot admin this channel.")
return return
# set the description # set the description
channel.db.desc = self.rhs channel.db.desc = self.rhs
channel.save() channel.save()
self.msg("Description of channel '%s' set to '%s'." % (channel.key, self.msg("Description of channel '%s' set to '%s'." % (channel.key, self.rhs))
self.rhs))
class CmdPage(COMMAND_DEFAULT_CLASS): class CmdPage(COMMAND_DEFAULT_CLASS):
@ -690,7 +762,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
""" """
key = "page" key = "page"
aliases = ['tell'] aliases = ["tell"]
switch_options = ("last", "list") switch_options = ("last", "list")
locks = "cmd:not pperm(page_banned)" locks = "cmd:not pperm(page_banned)"
help_category = "Comms" help_category = "Comms"
@ -709,7 +781,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
# get last messages we've got # get last messages we've got
pages_we_got = Msg.objects.get_messages_by_receiver(caller) pages_we_got = Msg.objects.get_messages_by_receiver(caller)
if 'last' in self.switches: if "last" in self.switches:
if pages_we_sent: if pages_we_sent:
recv = ",".join(obj.key for obj in pages_we_sent[-1].receivers) recv = ",".join(obj.key for obj in pages_we_sent[-1].receivers)
self.msg("You last paged |c%s|n:%s" % (recv, pages_we_sent[-1].message)) self.msg("You last paged |c%s|n:%s" % (recv, pages_we_sent[-1].message))
@ -735,11 +807,16 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
else: else:
lastpages = pages lastpages = pages
template = "|w%s|n |c%s|n to |c%s|n: %s" template = "|w%s|n |c%s|n to |c%s|n: %s"
lastpages = "\n ".join(template % lastpages = "\n ".join(
(utils.datetime_format(page.date_created), template
",".join(obj.key for obj in page.senders), % (
"|n,|c ".join([obj.name for obj in page.receivers]), utils.datetime_format(page.date_created),
page.message) for page in lastpages) ",".join(obj.key for obj in page.senders),
"|n,|c ".join([obj.name for obj in page.receivers]),
page.message,
)
for page in lastpages
)
if lastpages: if lastpages:
string = "Your latest pages:\n %s" % lastpages string = "Your latest pages:\n %s" % lastpages
@ -765,7 +842,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
for receiver in set(receivers): for receiver in set(receivers):
if isinstance(receiver, str): if isinstance(receiver, str):
pobj = caller.search(receiver) pobj = caller.search(receiver)
elif hasattr(receiver, 'character'): elif hasattr(receiver, "character"):
pobj = receiver pobj = receiver
else: else:
self.msg("Who do you want to page?") self.msg("Who do you want to page?")
@ -781,24 +858,25 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
# if message begins with a :, we assume it is a 'page-pose' # if message begins with a :, we assume it is a 'page-pose'
if message.startswith(":"): if message.startswith(":"):
message = "%s %s" % (caller.key, message.strip(':').strip()) message = "%s %s" % (caller.key, message.strip(":").strip())
# create the persistent message object # create the persistent message object
create.create_message(caller, message, create.create_message(caller, message, receivers=recobjs)
receivers=recobjs)
# tell the accounts they got a message. # tell the accounts they got a message.
received = [] received = []
rstrings = [] rstrings = []
for pobj in recobjs: for pobj in recobjs:
if not pobj.access(caller, 'msg'): if not pobj.access(caller, "msg"):
rstrings.append("You are not allowed to page %s." % pobj) rstrings.append("You are not allowed to page %s." % pobj)
continue continue
pobj.msg("%s %s" % (header, message)) pobj.msg("%s %s" % (header, message))
if hasattr(pobj, 'sessions') and not pobj.sessions.count(): if hasattr(pobj, "sessions") and not pobj.sessions.count():
received.append("|C%s|n" % pobj.name) received.append("|C%s|n" % pobj.name)
rstrings.append("%s is offline. They will see your message if they list their pages later." rstrings.append(
% received[-1]) "%s is offline. They will see your message if they list their pages later."
% received[-1]
)
else: else:
received.append("|c%s|n" % pobj.name) received.append("|c%s|n" % pobj.name)
if rstrings: if rstrings:
@ -816,13 +894,31 @@ def _list_bots(cmd):
bots (str): A table of bots or an error message. bots (str): A table of bots or an error message.
""" """
ircbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")] ircbots = [
bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="ircbot-")
]
if ircbots: if ircbots:
table = cmd.styled_table("|w#dbref|n", "|wbotname|n", "|wev-channel|n", table = cmd.styled_table(
"|wirc-channel|n", "|wSSL|n", maxwidth=_DEFAULT_WIDTH) "|w#dbref|n",
"|wbotname|n",
"|wev-channel|n",
"|wirc-channel|n",
"|wSSL|n",
maxwidth=_DEFAULT_WIDTH,
)
for ircbot in ircbots: for ircbot in ircbots:
ircinfo = "%s (%s:%s)" % (ircbot.db.irc_channel, ircbot.db.irc_network, ircbot.db.irc_port) ircinfo = "%s (%s:%s)" % (
table.add_row("#%i" % ircbot.id, ircbot.db.irc_botname, ircbot.db.ev_channel, ircinfo, ircbot.db.irc_ssl) ircbot.db.irc_channel,
ircbot.db.irc_network,
ircbot.db.irc_port,
)
table.add_row(
"#%i" % ircbot.id,
ircbot.db.irc_botname,
ircbot.db.ev_channel,
ircinfo,
ircbot.db.irc_ssl,
)
return table return table
else: else:
return "No irc bots found." return "No irc bots found."
@ -872,12 +968,12 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
self.msg(string) self.msg(string)
return return
if 'list' in self.switches: if "list" in self.switches:
# show all connections # show all connections
self.msg(_list_bots(self)) self.msg(_list_bots(self))
return return
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
botname = "ircbot-%s" % self.lhs botname = "ircbot-%s" % self.lhs
matches = AccountDB.objects.filter(db_is_bot=True, username=botname) matches = AccountDB.objects.filter(db_is_bot=True, username=botname)
dbref = utils.dbref(self.lhs) dbref = utils.dbref(self.lhs)
@ -892,16 +988,19 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
return return
if not self.args or not self.rhs: if not self.args or not self.rhs:
string = "Usage: irc2chan[/switches] <evennia_channel> =" \ string = (
" <ircnetwork> <port> <#irchannel> <botname>[:typeclass]" "Usage: irc2chan[/switches] <evennia_channel> ="
" <ircnetwork> <port> <#irchannel> <botname>[:typeclass]"
)
self.msg(string) self.msg(string)
return return
channel = self.lhs channel = self.lhs
self.rhs = self.rhs.replace('#', ' ') # to avoid Python comment issues self.rhs = self.rhs.replace("#", " ") # to avoid Python comment issues
try: try:
irc_network, irc_port, irc_channel, irc_botname = \ irc_network, irc_port, irc_channel, irc_botname = [
[part.strip() for part in self.rhs.split(None, 4)] part.strip() for part in self.rhs.split(None, 4)
]
irc_channel = "#%s" % irc_channel irc_channel = "#%s" % irc_channel
except Exception: except Exception:
string = "IRC bot definition '%s' is not valid." % self.rhs string = "IRC bot definition '%s' is not valid." % self.rhs
@ -930,8 +1029,14 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
except Exception as err: except Exception as err:
self.msg("|rError, could not create the bot:|n '%s'." % err) self.msg("|rError, could not create the bot:|n '%s'." % err)
return return
bot.start(ev_channel=channel, irc_botname=irc_botname, irc_channel=irc_channel, bot.start(
irc_network=irc_network, irc_port=irc_port, irc_ssl=irc_ssl) ev_channel=channel,
irc_botname=irc_botname,
irc_channel=irc_channel,
irc_network=irc_network,
irc_port=irc_port,
irc_ssl=irc_ssl,
)
self.msg("Connection created. Starting IRC bot.") self.msg("Connection created. Starting IRC bot.")
@ -953,6 +1058,7 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
messages sent to either channel will be lost. messages sent to either channel will be lost.
""" """
key = "ircstatus" key = "ircstatus"
locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))" locks = "cmd:serversetting(IRC_ENABLED) and perm(ircstatus) or perm(Builder))"
help_category = "Comms" help_category = "Comms"
@ -976,13 +1082,20 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
if utils.dbref(botname): if utils.dbref(botname):
matches = AccountDB.objects.filter(db_is_bot=True, id=utils.dbref(botname)) matches = AccountDB.objects.filter(db_is_bot=True, id=utils.dbref(botname))
if not matches: if not matches:
self.msg("No matching IRC-bot found. Use ircstatus without arguments to list active bots.") self.msg(
"No matching IRC-bot found. Use ircstatus without arguments to list active bots."
)
return return
ircbot = matches[0] ircbot = matches[0]
channel = ircbot.db.irc_channel channel = ircbot.db.irc_channel
network = ircbot.db.irc_network network = ircbot.db.irc_network
port = ircbot.db.irc_port port = ircbot.db.irc_port
chtext = "IRC bot '%s' on channel %s (%s:%s)" % (ircbot.db.irc_botname, channel, network, port) chtext = "IRC bot '%s' on channel %s (%s:%s)" % (
ircbot.db.irc_botname,
channel,
network,
port,
)
if option == "ping": if option == "ping":
# check connection by sending outself a ping through the server. # check connection by sending outself a ping through the server.
self.caller.msg("Pinging through %s." % chtext) self.caller.msg("Pinging through %s." % chtext)
@ -992,7 +1105,9 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
# an asynchronous call. # an asynchronous call.
self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port)) self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port))
ircbot.get_nicklist(self.caller) ircbot.get_nicklist(self.caller)
elif self.caller.locks.check_lockstring(self.caller, "dummy:perm(ircstatus) or perm(Developer)"): elif self.caller.locks.check_lockstring(
self.caller, "dummy:perm(ircstatus) or perm(Developer)"
):
# reboot the client # reboot the client
self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext) self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext)
ircbot.reconnect() ircbot.reconnect()
@ -1041,27 +1156,41 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
return return
try: try:
import feedparser import feedparser
assert feedparser # to avoid checker error of not being used assert feedparser # to avoid checker error of not being used
except ImportError: except ImportError:
string = "RSS requires python-feedparser (https://pypi.python.org/pypi/feedparser)." \ string = (
" Install before continuing." "RSS requires python-feedparser (https://pypi.python.org/pypi/feedparser)."
" Install before continuing."
)
self.msg(string) self.msg(string)
return return
if 'list' in self.switches: if "list" in self.switches:
# show all connections # show all connections
rssbots = [bot for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")] rssbots = [
bot
for bot in AccountDB.objects.filter(db_is_bot=True, username__startswith="rssbot-")
]
if rssbots: if rssbots:
table = self.styled_table("|wdbid|n", "|wupdate rate|n", "|wev-channel", table = self.styled_table(
"|wRSS feed URL|n", border="cells", maxwidth=_DEFAULT_WIDTH) "|wdbid|n",
"|wupdate rate|n",
"|wev-channel",
"|wRSS feed URL|n",
border="cells",
maxwidth=_DEFAULT_WIDTH,
)
for rssbot in rssbots: for rssbot in rssbots:
table.add_row(rssbot.id, rssbot.db.rss_rate, rssbot.db.ev_channel, rssbot.db.rss_url) table.add_row(
rssbot.id, rssbot.db.rss_rate, rssbot.db.ev_channel, rssbot.db.rss_url
)
self.msg(table) self.msg(table)
else: else:
self.msg("No rss bots found.") self.msg("No rss bots found.")
return return
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
botname = "rssbot-%s" % self.lhs botname = "rssbot-%s" % self.lhs
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname) matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
if not matches: if not matches:
@ -1133,12 +1262,20 @@ class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
if "list" in self.switches: if "list" in self.switches:
# show all connections # show all connections
gwbots = [bot for bot in gwbots = [
AccountDB.objects.filter(db_is_bot=True, bot
username__startswith="grapevinebot-")] for bot in AccountDB.objects.filter(
db_is_bot=True, username__startswith="grapevinebot-"
)
]
if gwbots: if gwbots:
table = self.styled_table("|wdbid|n", "|wev-channel", table = self.styled_table(
"|wgw-channel|n", border="cells", maxwidth=_DEFAULT_WIDTH) "|wdbid|n",
"|wev-channel",
"|wgw-channel|n",
border="cells",
maxwidth=_DEFAULT_WIDTH,
)
for gwbot in gwbots: for gwbot in gwbots:
table.add_row(gwbot.id, gwbot.db.ev_channel, gwbot.db.grapevine_channel) table.add_row(gwbot.id, gwbot.db.ev_channel, gwbot.db.grapevine_channel)
self.msg(table) self.msg(table)
@ -1146,7 +1283,7 @@ class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
self.msg("No grapevine bots found.") self.msg("No grapevine bots found.")
return return
if 'disconnect' in self.switches or 'remove' in self.switches or 'delete' in self.switches: if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
botname = "grapevinebot-%s" % self.lhs botname = "grapevinebot-%s" % self.lhs
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname) matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)

View file

@ -9,9 +9,20 @@ from evennia.typeclasses.attributes import NickTemplateInvalid
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdHome", "CmdLook", "CmdNick", __all__ = (
"CmdInventory", "CmdSetDesc", "CmdGet", "CmdDrop", "CmdGive", "CmdHome",
"CmdSay", "CmdWhisper", "CmdPose", "CmdAccess") "CmdLook",
"CmdNick",
"CmdInventory",
"CmdSetDesc",
"CmdGet",
"CmdDrop",
"CmdGive",
"CmdSay",
"CmdWhisper",
"CmdPose",
"CmdAccess",
)
class CmdHome(COMMAND_DEFAULT_CLASS): class CmdHome(COMMAND_DEFAULT_CLASS):
@ -52,6 +63,7 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
Observes your location or objects in your vicinity. Observes your location or objects in your vicinity.
""" """
key = "look" key = "look"
aliases = ["l", "ls"] aliases = ["l", "ls"]
locks = "cmd:all()" locks = "cmd:all()"
@ -71,7 +83,7 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
target = caller.search(self.args) target = caller.search(self.args)
if not target: if not target:
return return
self.msg((caller.at_look(target), {'type': 'look'}), options=None) self.msg((caller.at_look(target), {"type": "look"}), options=None)
class CmdNick(COMMAND_DEFAULT_CLASS): class CmdNick(COMMAND_DEFAULT_CLASS):
@ -116,6 +128,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
for everyone to use, you need build privileges and the alias command. for everyone to use, you need build privileges and the alias command.
""" """
key = "nick" key = "nick"
switch_options = ("inputline", "object", "account", "list", "delete", "clearall") switch_options = ("inputline", "object", "account", "list", "delete", "clearall")
aliases = ["nickname", "nicks"] aliases = ["nickname", "nicks"]
@ -148,11 +161,13 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
specified_nicktype = bool(nicktypes) specified_nicktype = bool(nicktypes)
nicktypes = nicktypes if specified_nicktype else ["inputline"] nicktypes = nicktypes if specified_nicktype else ["inputline"]
nicklist = (utils.make_iter(caller.nicks.get(category="inputline", return_obj=True) or []) + nicklist = (
utils.make_iter(caller.nicks.get(category="object", return_obj=True) or []) + utils.make_iter(caller.nicks.get(category="inputline", return_obj=True) or [])
utils.make_iter(caller.nicks.get(category="account", return_obj=True) or [])) + utils.make_iter(caller.nicks.get(category="object", return_obj=True) or [])
+ utils.make_iter(caller.nicks.get(category="account", return_obj=True) or [])
)
if 'list' in switches or self.cmdstring in ("nicks",): if "list" in switches or self.cmdstring in ("nicks",):
if not nicklist: if not nicklist:
string = "|wNo nicks defined.|n" string = "|wNo nicks defined.|n"
@ -160,18 +175,20 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
table = self.styled_table("#", "Type", "Nick match", "Replacement") table = self.styled_table("#", "Type", "Nick match", "Replacement")
for inum, nickobj in enumerate(nicklist): for inum, nickobj in enumerate(nicklist):
_, _, nickvalue, replacement = nickobj.value _, _, nickvalue, replacement = nickobj.value
table.add_row(str(inum + 1), nickobj.db_category, _cy(nickvalue), _cy(replacement)) table.add_row(
str(inum + 1), nickobj.db_category, _cy(nickvalue), _cy(replacement)
)
string = "|wDefined Nicks:|n\n%s" % table string = "|wDefined Nicks:|n\n%s" % table
caller.msg(string) caller.msg(string)
return return
if 'clearall' in switches: if "clearall" in switches:
caller.nicks.clear() caller.nicks.clear()
caller.account.nicks.clear() caller.account.nicks.clear()
caller.msg("Cleared all nicks.") caller.msg("Cleared all nicks.")
return return
if 'delete' in switches or 'del' in switches: if "delete" in switches or "del" in switches:
if not self.args or not self.lhs: if not self.args or not self.lhs:
caller.msg("usage nick/delete <nick> or <#num> ('nicks' for list)") caller.msg("usage nick/delete <nick> or <#num> ('nicks' for list)")
return return
@ -199,8 +216,10 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
nicktypestr = "%s-nick" % nicktype.capitalize() nicktypestr = "%s-nick" % nicktype.capitalize()
_, _, old_nickstring, old_replstring = oldnick.value _, _, old_nickstring, old_replstring = oldnick.value
caller.nicks.remove(old_nickstring, category=nicktype) caller.nicks.remove(old_nickstring, category=nicktype)
caller.msg("%s removed: '|w%s|n' -> |w%s|n." % ( caller.msg(
nicktypestr, old_nickstring, old_replstring)) "%s removed: '|w%s|n' -> |w%s|n."
% (nicktypestr, old_nickstring, old_replstring)
)
else: else:
caller.msg("No matching nicks to remove.") caller.msg("No matching nicks to remove.")
return return
@ -211,14 +230,19 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
if not specified_nicktype: if not specified_nicktype:
nicktypes = ("object", "account", "inputline") nicktypes = ("object", "account", "inputline")
for nicktype in nicktypes: for nicktype in nicktypes:
nicks = [nick for nick in nicks = [
utils.make_iter(caller.nicks.get(category=nicktype, return_obj=True)) nick
if nick] for nick in utils.make_iter(
caller.nicks.get(category=nicktype, return_obj=True)
)
if nick
]
for nick in nicks: for nick in nicks:
_, _, nick, repl = nick.value _, _, nick, repl = nick.value
if nick.startswith(self.lhs): if nick.startswith(self.lhs):
strings.append("{}-nick: '{}' -> '{}'".format( strings.append(
nicktype.capitalize(), nick, repl)) "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
)
if strings: if strings:
caller.msg("\n".join(strings)) caller.msg("\n".join(strings))
else: else:
@ -239,8 +263,9 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
for nick in nicks: for nick in nicks:
_, _, nick, repl = nick.value _, _, nick, repl = nick.value
if nick.startswith(self.lhs): if nick.startswith(self.lhs):
strings.append("{}-nick: '{}' -> '{}'".format( strings.append(
nicktype.capitalize(), nick, repl)) "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
)
if strings: if strings:
caller.msg("\n".join(strings)) caller.msg("\n".join(strings))
else: else:
@ -261,8 +286,9 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
for nick in nicks: for nick in nicks:
_, _, nick, repl = nick.value _, _, nick, repl = nick.value
if nick.startswith(self.lhs): if nick.startswith(self.lhs):
strings.append("{}-nick: '{}' -> '{}'".format( strings.append(
nicktype.capitalize(), nick, repl)) "{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
)
if strings: if strings:
caller.msg("\n".join(strings)) caller.msg("\n".join(strings))
else: else:
@ -301,17 +327,30 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
string += "\nIdentical %s already set." % nicktypestr.lower() string += "\nIdentical %s already set." % nicktypestr.lower()
else: else:
string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % ( string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % (
nicktypestr, old_nickstring, replstring) nicktypestr,
old_nickstring,
replstring,
)
else: else:
string += "\n%s '|w%s|n' mapped to '|w%s|n'." % (nicktypestr, nickstring, replstring) string += "\n%s '|w%s|n' mapped to '|w%s|n'." % (
nicktypestr,
nickstring,
replstring,
)
try: try:
caller.nicks.add(nickstring, replstring, category=nicktype) caller.nicks.add(nickstring, replstring, category=nicktype)
except NickTemplateInvalid: except NickTemplateInvalid:
caller.msg("You must use the same $-markers both in the nick and in the replacement.") caller.msg(
"You must use the same $-markers both in the nick and in the replacement."
)
return return
elif old_nickstring and old_replstring: elif old_nickstring and old_replstring:
# just looking at the nick # just looking at the nick
string += "\n%s '|w%s|n' maps to '|w%s|n'." % (nicktypestr, old_nickstring, old_replstring) string += "\n%s '|w%s|n' maps to '|w%s|n'." % (
nicktypestr,
old_nickstring,
old_replstring,
)
errstring = "" errstring = ""
string = errstring if errstring else string string = errstring if errstring else string
caller.msg(_cy(string)) caller.msg(_cy(string))
@ -327,6 +366,7 @@ class CmdInventory(COMMAND_DEFAULT_CLASS):
Shows your inventory. Shows your inventory.
""" """
key = "inventory" key = "inventory"
aliases = ["inv", "i"] aliases = ["inv", "i"]
locks = "cmd:all()" locks = "cmd:all()"
@ -355,6 +395,7 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
Picks up an object from your location and puts it in Picks up an object from your location and puts it in
your inventory. your inventory.
""" """
key = "get" key = "get"
aliases = "grab" aliases = "grab"
locks = "cmd:all()" locks = "cmd:all()"
@ -374,7 +415,7 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
if caller == obj: if caller == obj:
caller.msg("You can't get yourself.") caller.msg("You can't get yourself.")
return return
if not obj.access(caller, 'get'): if not obj.access(caller, "get"):
if obj.db.get_err_msg: if obj.db.get_err_msg:
caller.msg(obj.db.get_err_msg) caller.msg(obj.db.get_err_msg)
else: else:
@ -387,10 +428,7 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
obj.move_to(caller, quiet=True) obj.move_to(caller, quiet=True)
caller.msg("You pick up %s." % obj.name) caller.msg("You pick up %s." % obj.name)
caller.location.msg_contents("%s picks up %s." % caller.location.msg_contents("%s picks up %s." % (caller.name, obj.name), exclude=caller)
(caller.name,
obj.name),
exclude=caller)
# calling at_get hook method # calling at_get hook method
obj.at_get(caller) obj.at_get(caller)
@ -420,9 +458,12 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
# Because the DROP command by definition looks for items # Because the DROP command by definition looks for items
# in inventory, call the search function using location = caller # in inventory, call the search function using location = caller
obj = caller.search(self.args, location=caller, obj = caller.search(
nofound_string="You aren't carrying %s." % self.args, self.args,
multimatch_string="You carry more than one %s:" % self.args) location=caller,
nofound_string="You aren't carrying %s." % self.args,
multimatch_string="You carry more than one %s:" % self.args,
)
if not obj: if not obj:
return return
@ -432,9 +473,7 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
obj.move_to(caller.location, quiet=True) obj.move_to(caller.location, quiet=True)
caller.msg("You drop %s." % (obj.name,)) caller.msg("You drop %s." % (obj.name,))
caller.location.msg_contents("%s drops %s." % caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
(caller.name, obj.name),
exclude=caller)
# Call the object script's at_drop() method. # Call the object script's at_drop() method.
obj.at_drop(caller) obj.at_drop(caller)
@ -449,6 +488,7 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
Gives an items from your inventory to another character, Gives an items from your inventory to another character,
placing it in their inventory. placing it in their inventory.
""" """
key = "give" key = "give"
rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage. rhs_split = ("=", " to ") # Prefer = delimiter, but allow " to " usage.
locks = "cmd:all()" locks = "cmd:all()"
@ -461,9 +501,12 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
if not self.args or not self.rhs: if not self.args or not self.rhs:
caller.msg("Usage: give <inventory object> = <target>") caller.msg("Usage: give <inventory object> = <target>")
return return
to_give = caller.search(self.lhs, location=caller, to_give = caller.search(
nofound_string="You aren't carrying %s." % self.lhs, self.lhs,
multimatch_string="You carry more than one %s:" % self.lhs) location=caller,
nofound_string="You aren't carrying %s." % self.lhs,
multimatch_string="You carry more than one %s:" % self.lhs,
)
target = caller.search(self.rhs) target = caller.search(self.rhs)
if not (to_give and target): if not (to_give and target):
return return
@ -497,6 +540,7 @@ class CmdSetDesc(COMMAND_DEFAULT_CLASS):
will be visible to people when they will be visible to people when they
look at you. look at you.
""" """
key = "setdesc" key = "setdesc"
locks = "cmd:all()" locks = "cmd:all()"
arg_regex = r"\s|$" arg_regex = r"\s|$"
@ -606,6 +650,7 @@ class CmdPose(COMMAND_DEFAULT_CLASS):
Describe an action being taken. The pose text will Describe an action being taken. The pose text will
automatically begin with your name. automatically begin with your name.
""" """
key = "pose" key = "pose"
aliases = [":", "emote"] aliases = [":", "emote"]
locks = "cmd:all()" locks = "cmd:all()"
@ -630,8 +675,7 @@ class CmdPose(COMMAND_DEFAULT_CLASS):
self.caller.msg(msg) self.caller.msg(msg)
else: else:
msg = "%s%s" % (self.caller.name, self.args) msg = "%s%s" % (self.caller.name, self.args)
self.caller.location.msg_contents(text=(msg, {"type": "pose"}), self.caller.location.msg_contents(text=(msg, {"type": "pose"}), from_obj=self.caller)
from_obj=self.caller)
class CmdAccess(COMMAND_DEFAULT_CLASS): class CmdAccess(COMMAND_DEFAULT_CLASS):
@ -644,6 +688,7 @@ class CmdAccess(COMMAND_DEFAULT_CLASS):
This command shows you the permission hierarchy and This command shows you the permission hierarchy and
which permission groups you are a member of. which permission groups you are a member of.
""" """
key = "access" key = "access"
aliases = ["groups", "hierarchy"] aliases = ["groups", "hierarchy"]
locks = "cmd:all()" locks = "cmd:all()"
@ -665,6 +710,6 @@ class CmdAccess(COMMAND_DEFAULT_CLASS):
string += "\n|wYour access|n:" string += "\n|wYour access|n:"
string += "\nCharacter |c%s|n: %s" % (caller.key, cperms) string += "\nCharacter |c%s|n: %s" % (caller.key, cperms)
if hasattr(caller, 'account'): if hasattr(caller, "account"):
string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms) string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms)
caller.msg(string) caller.msg(string)

View file

@ -37,8 +37,9 @@ class CmdHelp(Command):
This will search for help on commands and other This will search for help on commands and other
topics related to the game. topics related to the game.
""" """
key = "help" key = "help"
aliases = ['?'] aliases = ["?"]
locks = "cmd:all()" locks = "cmd:all()"
arg_regex = r"\s|$" arg_regex = r"\s|$"
@ -128,7 +129,11 @@ class CmdHelp(Command):
string += "\n\n" + _SEP + "\n\r |COther help entries|n\n" + _SEP string += "\n\n" + _SEP + "\n\r |COther help entries|n\n" + _SEP
for category in sorted(hdict_db.keys()): for category in sorted(hdict_db.keys()):
string += "\n\r |w%s|n:\n" % (str(category).title()) string += "\n\r |w%s|n:\n" % (str(category).title())
string += "|G" + fill(", ".join(sorted([str(topic) for topic in hdict_db[category]]))) + "|n" string += (
"|G"
+ fill(", ".join(sorted([str(topic) for topic in hdict_db[category]])))
+ "|n"
)
return string return string
def check_show_help(self, cmd, caller): def check_show_help(self, cmd, caller):
@ -198,9 +203,15 @@ class CmdHelp(Command):
# retrieve all available commands and database topics # retrieve all available commands and database topics
all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)] all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)]
all_topics = [topic for topic in HelpEntry.objects.all() if topic.access(caller, 'view', default=True)] all_topics = [
all_categories = list(set([cmd.help_category.lower() for cmd in all_cmds] + [topic.help_category.lower() topic for topic in HelpEntry.objects.all() if topic.access(caller, "view", default=True)
for topic in all_topics])) ]
all_categories = list(
set(
[cmd.help_category.lower() for cmd in all_cmds]
+ [topic.help_category.lower() for topic in all_topics]
)
)
if query in ("list", "all"): if query in ("list", "all"):
# we want to list all available help entries, grouped by category # we want to list all available help entries, grouped by category
@ -222,13 +233,23 @@ class CmdHelp(Command):
# build vocabulary of suggestions and rate them by string similarity. # build vocabulary of suggestions and rate them by string similarity.
suggestions = None suggestions = None
if suggestion_maxnum > 0: if suggestion_maxnum > 0:
vocabulary = [cmd.key for cmd in all_cmds if cmd] + [topic.key for topic in all_topics] + all_categories vocabulary = (
[cmd.key for cmd in all_cmds if cmd]
+ [topic.key for topic in all_topics]
+ all_categories
)
[vocabulary.extend(cmd.aliases) for cmd in all_cmds] [vocabulary.extend(cmd.aliases) for cmd in all_cmds]
suggestions = [sugg for sugg in string_suggestions(query, set(vocabulary), cutoff=suggestion_cutoff, suggestions = [
maxnum=suggestion_maxnum) sugg
if sugg != query] for sugg in string_suggestions(
query, set(vocabulary), cutoff=suggestion_cutoff, maxnum=suggestion_maxnum
)
if sugg != query
]
if not suggestions: if not suggestions:
suggestions = [sugg for sugg in vocabulary if sugg != query and sugg.startswith(query)] suggestions = [
sugg for sugg in vocabulary if sugg != query and sugg.startswith(query)
]
# try an exact command auto-help match # try an exact command auto-help match
match = [cmd for cmd in all_cmds if cmd == query] match = [cmd for cmd in all_cmds if cmd == query]
@ -237,38 +258,52 @@ class CmdHelp(Command):
# try an inexact match with prefixes stripped from query and cmds # try an inexact match with prefixes stripped from query and cmds
_query = query[1:] if query[0] in CMD_IGNORE_PREFIXES else query _query = query[1:] if query[0] in CMD_IGNORE_PREFIXES else query
match = [cmd for cmd in all_cmds match = [
for m in cmd._matchset if m == _query or cmd
m[0] in CMD_IGNORE_PREFIXES and m[1:] == _query] for cmd in all_cmds
for m in cmd._matchset
if m == _query or m[0] in CMD_IGNORE_PREFIXES and m[1:] == _query
]
if len(match) == 1: if len(match) == 1:
formatted = self.format_help_entry(match[0].key, formatted = self.format_help_entry(
match[0].get_help(caller, cmdset), match[0].key,
aliases=match[0].aliases, match[0].get_help(caller, cmdset),
suggested=suggestions) aliases=match[0].aliases,
suggested=suggestions,
)
self.msg_help(formatted) self.msg_help(formatted)
return return
# try an exact database help entry match # try an exact database help entry match
match = list(HelpEntry.objects.find_topicmatch(query, exact=True)) match = list(HelpEntry.objects.find_topicmatch(query, exact=True))
if len(match) == 1: if len(match) == 1:
formatted = self.format_help_entry(match[0].key, formatted = self.format_help_entry(
match[0].entrytext, match[0].key,
aliases=match[0].aliases.all(), match[0].entrytext,
suggested=suggestions) aliases=match[0].aliases.all(),
suggested=suggestions,
)
self.msg_help(formatted) self.msg_help(formatted)
return return
# try to see if a category name was entered # try to see if a category name was entered
if query in all_categories: if query in all_categories:
self.msg_help(self.format_help_list({query: [cmd.key for cmd in all_cmds if cmd.help_category == query]}, self.msg_help(
{query: [topic.key for topic in all_topics self.format_help_list(
if topic.help_category == query]})) {query: [cmd.key for cmd in all_cmds if cmd.help_category == query]},
{query: [topic.key for topic in all_topics if topic.help_category == query]},
)
)
return return
# no exact matches found. Just give suggestions. # no exact matches found. Just give suggestions.
self.msg(self.format_help_entry("", f"No help entry found for '{query}'", self.msg(
None, suggested=suggestions), options={"type": "help"}) self.format_help_entry(
"", f"No help entry found for '{query}'", None, suggested=suggestions
),
options={"type": "help"},
)
def _loadhelp(caller): def _loadhelp(caller):
@ -317,6 +352,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
is to let everyone read the help file. is to let everyone read the help file.
""" """
key = "sethelp" key = "sethelp"
switch_options = ("edit", "replace", "append", "extend", "delete") switch_options = ("edit", "replace", "append", "extend", "delete")
locks = "cmd:perm(Helper)" locks = "cmd:perm(Helper)"
@ -329,7 +365,9 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
lhslist = self.lhslist lhslist = self.lhslist
if not self.args: if not self.args:
self.msg("Usage: sethelp[/switches] <topic>[;alias;alias][,category[,locks,..] = <text>") self.msg(
"Usage: sethelp[/switches] <topic>[;alias;alias][,category[,locks,..] = <text>"
)
return return
nlist = len(lhslist) nlist = len(lhslist)
@ -357,7 +395,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
lockstring = ",".join(lhslist[2:]) if nlist > 2 else "view:all()" lockstring = ",".join(lhslist[2:]) if nlist > 2 else "view:all()"
category = category.lower() category = category.lower()
if 'edit' in switches: if "edit" in switches:
# open the line editor to edit the helptext. No = is needed. # open the line editor to edit the helptext. No = is needed.
if old_entry: if old_entry:
topicstr = old_entry.key topicstr = old_entry.key
@ -366,17 +404,22 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
old_entry.entrytext += "\n%s" % self.rhs old_entry.entrytext += "\n%s" % self.rhs
helpentry = old_entry helpentry = old_entry
else: else:
helpentry = create.create_help_entry(topicstr, helpentry = create.create_help_entry(
self.rhs, category=category, topicstr, self.rhs, category=category, locks=lockstring, aliases=aliases
locks=lockstring, aliases=aliases) )
self.caller.db._editing_help = helpentry self.caller.db._editing_help = helpentry
EvEditor(self.caller, loadfunc=_loadhelp, savefunc=_savehelp, EvEditor(
quitfunc=_quithelp, key="topic {}".format(topicstr), self.caller,
persistent=True) loadfunc=_loadhelp,
savefunc=_savehelp,
quitfunc=_quithelp,
key="topic {}".format(topicstr),
persistent=True,
)
return return
if 'append' in switches or "merge" in switches or "extend" in switches: if "append" in switches or "merge" in switches or "extend" in switches:
# merge/append operations # merge/append operations
if not old_entry: if not old_entry:
self.msg("Could not find topic '%s'. You must give an exact name." % topicstr) self.msg("Could not find topic '%s'. You must give an exact name." % topicstr)
@ -384,14 +427,14 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
if not self.rhs: if not self.rhs:
self.msg("You must supply text to append/merge.") self.msg("You must supply text to append/merge.")
return return
if 'merge' in switches: if "merge" in switches:
old_entry.entrytext += " " + self.rhs old_entry.entrytext += " " + self.rhs
else: else:
old_entry.entrytext += "\n%s" % self.rhs old_entry.entrytext += "\n%s" % self.rhs
old_entry.aliases.add(aliases) old_entry.aliases.add(aliases)
self.msg("Entry updated:\n%s%s" % (old_entry.entrytext, aliastxt)) self.msg("Entry updated:\n%s%s" % (old_entry.entrytext, aliastxt))
return return
if 'delete' in switches or 'del' in switches: if "delete" in switches or "del" in switches:
# delete the help entry # delete the help entry
if not old_entry: if not old_entry:
self.msg("Could not find topic '%s'%s." % (topicstr, aliastxt)) self.msg("Could not find topic '%s'%s." % (topicstr, aliastxt))
@ -405,7 +448,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
self.msg("You must supply a help text to add.") self.msg("You must supply a help text to add.")
return return
if old_entry: if old_entry:
if 'replace' in switches: if "replace" in switches:
# overwrite old entry # overwrite old entry
old_entry.key = topicstr old_entry.key = topicstr
old_entry.entrytext = self.rhs old_entry.entrytext = self.rhs
@ -416,22 +459,30 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
old_entry.save() old_entry.save()
self.msg("Overwrote the old topic '%s'%s." % (topicstr, aliastxt)) self.msg("Overwrote the old topic '%s'%s." % (topicstr, aliastxt))
else: else:
self.msg("Topic '%s'%s already exists. Use /replace to overwrite " self.msg(
"or /append or /merge to add text to it." % (topicstr, aliastxt)) "Topic '%s'%s already exists. Use /replace to overwrite "
"or /append or /merge to add text to it." % (topicstr, aliastxt)
)
else: else:
# no old entry. Create a new one. # no old entry. Create a new one.
new_entry = create.create_help_entry(topicstr, new_entry = create.create_help_entry(
self.rhs, category=category, topicstr, self.rhs, category=category, locks=lockstring, aliases=aliases
locks=lockstring, aliases=aliases) )
if new_entry: if new_entry:
self.msg("Topic '%s'%s was successfully created." % (topicstr, aliastxt)) self.msg("Topic '%s'%s was successfully created." % (topicstr, aliastxt))
if 'edit' in switches: if "edit" in switches:
# open the line editor to edit the helptext # open the line editor to edit the helptext
self.caller.db._editing_help = new_entry self.caller.db._editing_help = new_entry
EvEditor(self.caller, loadfunc=_loadhelp, EvEditor(
savefunc=_savehelp, quitfunc=_quithelp, self.caller,
key="topic {}".format(new_entry.key), loadfunc=_loadhelp,
persistent=True) savefunc=_savehelp,
quitfunc=_quithelp,
key="topic {}".format(new_entry.key),
persistent=True,
)
return return
else: else:
self.msg("Error when creating topic '%s'%s! Contact an admin." % (topicstr, aliastxt)) self.msg(
"Error when creating topic '%s'%s! Contact an admin." % (topicstr, aliastxt)
)

View file

@ -121,38 +121,46 @@ class MuxCommand(Command):
switches = args[1:].split(None, 1) switches = args[1:].split(None, 1)
if len(switches) > 1: if len(switches) > 1:
switches, args = switches switches, args = switches
switches = switches.split('/') switches = switches.split("/")
else: else:
args = "" args = ""
switches = switches[0].split('/') switches = switches[0].split("/")
# If user-provides switches, parse them with parser switch options. # If user-provides switches, parse them with parser switch options.
if switches and self.switch_options: if switches and self.switch_options:
valid_switches, unused_switches, extra_switches = [], [], [] valid_switches, unused_switches, extra_switches = [], [], []
for element in switches: for element in switches:
option_check = [opt for opt in self.switch_options if opt == element] option_check = [opt for opt in self.switch_options if opt == element]
if not option_check: if not option_check:
option_check = [opt for opt in self.switch_options if opt.startswith(element)] option_check = [
opt for opt in self.switch_options if opt.startswith(element)
]
match_count = len(option_check) match_count = len(option_check)
if match_count > 1: if match_count > 1:
extra_switches.extend(option_check) # Either the option provided is ambiguous, extra_switches.extend(
option_check
) # Either the option provided is ambiguous,
elif match_count == 1: elif match_count == 1:
valid_switches.extend(option_check) # or it is a valid option abbreviation, valid_switches.extend(option_check) # or it is a valid option abbreviation,
elif match_count == 0: elif match_count == 0:
unused_switches.append(element) # or an extraneous option to be ignored. unused_switches.append(element) # or an extraneous option to be ignored.
if extra_switches: # User provided switches if extra_switches: # User provided switches
self.msg('|g%s|n: |wAmbiguous switch supplied: Did you mean /|C%s|w?' % self.msg(
(self.cmdstring, ' |nor /|C'.join(extra_switches))) "|g%s|n: |wAmbiguous switch supplied: Did you mean /|C%s|w?"
% (self.cmdstring, " |nor /|C".join(extra_switches))
)
if unused_switches: if unused_switches:
plural = '' if len(unused_switches) == 1 else 'es' plural = "" if len(unused_switches) == 1 else "es"
self.msg('|g%s|n: |wExtra switch%s "/|C%s|w" ignored.' % self.msg(
(self.cmdstring, plural, '|n, /|C'.join(unused_switches))) '|g%s|n: |wExtra switch%s "/|C%s|w" ignored.'
% (self.cmdstring, plural, "|n, /|C".join(unused_switches))
)
switches = valid_switches # Only include valid_switches in command function call switches = valid_switches # Only include valid_switches in command function call
arglist = [arg.strip() for arg in args.split()] arglist = [arg.strip() for arg in args.split()]
# check for arg1, arg2, ... = argA, argB, ... constructs # check for arg1, arg2, ... = argA, argB, ... constructs
lhs, rhs = args.strip(), None lhs, rhs = args.strip(), None
if lhs: if lhs:
if delimiters and hasattr(delimiters, '__iter__'): # If delimiter is iterable, if delimiters and hasattr(delimiters, "__iter__"): # If delimiter is iterable,
best_split = delimiters[0] # (default to first delimiter) best_split = delimiters[0] # (default to first delimiter)
for this_split in delimiters: # try each delimiter for this_split in delimiters: # try each delimiter
if this_split in lhs: # to find first successful split if this_split in lhs: # to find first successful split
@ -167,8 +175,8 @@ class MuxCommand(Command):
rhs = rhs.strip() if rhs is not None else None rhs = rhs.strip() if rhs is not None else None
lhs = lhs.strip() lhs = lhs.strip()
# Further split left/right sides by comma delimiter # Further split left/right sides by comma delimiter
lhslist = [arg.strip() for arg in lhs.split(',')] if lhs is not None else "" lhslist = [arg.strip() for arg in lhs.split(",")] if lhs is not None else ""
rhslist = [arg.strip() for arg in rhs.split(',')] if rhs is not None else "" rhslist = [arg.strip() for arg in rhs.split(",")] if rhs is not None else ""
# save to object properties: # save to object properties:
self.raw = raw self.raw = raw
self.switches = switches self.switches = switches
@ -200,7 +208,9 @@ class MuxCommand(Command):
by the cmdhandler right after self.parser() finishes, and so has access by the cmdhandler right after self.parser() finishes, and so has access
to all the variables defined therein. to all the variables defined therein.
""" """
variables = '\n'.join(" |w{}|n ({}): {}".format(key, type(val), val) for key, val in self.__dict__.items()) variables = "\n".join(
" |w{}|n ({}): {}".format(key, type(val), val) for key, val in self.__dict__.items()
)
string = f""" string = f"""
Command {self} has no defined `func()` - showing on-command variables: No child func() defined for {self} - available variables: Command {self} has no defined `func()` - showing on-command variables: No child func() defined for {self} - available variables:
{variables} {variables}

View file

@ -31,6 +31,7 @@ from evennia.commands.cmdhandler import CMD_CHANNEL
from evennia.utils import utils from evennia.utils import utils
from django.conf import settings from django.conf import settings
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
# Command called when there is no input at line # Command called when there is no input at line
@ -41,6 +42,7 @@ class SystemNoInput(COMMAND_DEFAULT_CLASS):
""" """
This is called when there is no input given This is called when there is no input given
""" """
key = CMD_NOINPUT key = CMD_NOINPUT
locks = "cmd:all()" locks = "cmd:all()"
@ -57,6 +59,7 @@ class SystemNoMatch(COMMAND_DEFAULT_CLASS):
""" """
No command was found matching the given input. No command was found matching the given input.
""" """
key = CMD_NOMATCH key = CMD_NOMATCH
locks = "cmd:all()" locks = "cmd:all()"
@ -86,6 +89,7 @@ class SystemMultimatch(COMMAND_DEFAULT_CLASS):
the `raw_cmdname` is the cmdname unmodified by eventual prefix-stripping. the `raw_cmdname` is the cmdname unmodified by eventual prefix-stripping.
""" """
key = CMD_MULTIMATCH key = CMD_MULTIMATCH
locks = "cmd:all()" locks = "cmd:all()"
@ -99,8 +103,7 @@ class SystemMultimatch(COMMAND_DEFAULT_CLASS):
# evennia.commands.cmdparse.create_match for more details. # evennia.commands.cmdparse.create_match for more details.
matches = self.matches matches = self.matches
# at_search_result will itself msg the multimatch options to the caller. # at_search_result will itself msg the multimatch options to the caller.
at_search_result( at_search_result([match[2] for match in matches], self.caller, query=matches[0][0])
[match[2] for match in matches], self.caller, query=matches[0][0])
# Command called when the command given at the command line # Command called when the command given at the command line
@ -108,6 +111,7 @@ class SystemMultimatch(COMMAND_DEFAULT_CLASS):
# channel named 'ooc' and the user wrote # channel named 'ooc' and the user wrote
# > ooc Hello! # > ooc Hello!
class SystemSendToChannel(COMMAND_DEFAULT_CLASS): class SystemSendToChannel(COMMAND_DEFAULT_CLASS):
""" """
This is a special command that the cmdhandler calls This is a special command that the cmdhandler calls
@ -119,7 +123,7 @@ class SystemSendToChannel(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()" locks = "cmd:all()"
def parse(self): def parse(self):
channelname, msg = self.args.split(':', 1) channelname, msg = self.args.split(":", 1)
self.args = channelname.strip(), msg.strip() self.args = channelname.strip(), msg.strip()
def func(self): def func(self):
@ -140,7 +144,7 @@ class SystemSendToChannel(COMMAND_DEFAULT_CLASS):
string = "You are not connected to channel '%s'." string = "You are not connected to channel '%s'."
caller.msg(string % channelkey) caller.msg(string % channelkey)
return return
if not channel.access(caller, 'send'): if not channel.access(caller, "send"):
string = "You are not permitted to send to channel '%s'." string = "You are not permitted to send to channel '%s'."
caller.msg(string % channelkey) caller.msg(string % channelkey)
return return

View file

@ -32,9 +32,18 @@ _RESOURCE = None
_IDMAPPER = None _IDMAPPER = None
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdReload", "CmdReset", "CmdShutdown", "CmdPy", __all__ = (
"CmdScripts", "CmdObjects", "CmdService", "CmdAbout", "CmdReload",
"CmdTime", "CmdServerLoad") "CmdReset",
"CmdShutdown",
"CmdPy",
"CmdScripts",
"CmdObjects",
"CmdService",
"CmdAbout",
"CmdTime",
"CmdServerLoad",
)
class CmdReload(COMMAND_DEFAULT_CLASS): class CmdReload(COMMAND_DEFAULT_CLASS):
@ -48,8 +57,9 @@ class CmdReload(COMMAND_DEFAULT_CLASS):
affected. Non-persistent scripts will survive a reload (use affected. Non-persistent scripts will survive a reload (use
reset to purge) and at_reload() hooks will be called. reset to purge) and at_reload() hooks will be called.
""" """
key = "reload" key = "reload"
aliases = ['restart'] aliases = ["restart"]
locks = "cmd:perm(reload) or perm(Developer)" locks = "cmd:perm(reload) or perm(Developer)"
help_category = "System" help_category = "System"
@ -84,8 +94,9 @@ class CmdReset(COMMAND_DEFAULT_CLASS):
cmdsets etc will be wiped. cmdsets etc will be wiped.
""" """
key = "reset" key = "reset"
aliases = ['reboot'] aliases = ["reboot"]
locks = "cmd:perm(reload) or perm(Developer)" locks = "cmd:perm(reload) or perm(Developer)"
help_category = "System" help_category = "System"
@ -107,6 +118,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
Gracefully shut down both Server and Portal. Gracefully shut down both Server and Portal.
""" """
key = "shutdown" key = "shutdown"
locks = "cmd:perm(shutdown) or perm(Developer)" locks = "cmd:perm(shutdown) or perm(Developer)"
help_category = "System" help_category = "System"
@ -116,11 +128,11 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
# Only allow shutdown if caller has session # Only allow shutdown if caller has session
if not self.caller.sessions.get(): if not self.caller.sessions.get():
return return
self.msg('Shutting down server ...') self.msg("Shutting down server ...")
announcement = "\nServer is being SHUT DOWN!\n" announcement = "\nServer is being SHUT DOWN!\n"
if self.args: if self.args:
announcement += "%s\n" % self.args announcement += "%s\n" % self.args
logger.log_info('Server shutdown by %s.' % self.caller.name) logger.log_info("Server shutdown by %s." % self.caller.name)
SESSIONS.announce_all(announcement) SESSIONS.announce_all(announcement)
SESSIONS.portal_shutdown() SESSIONS.portal_shutdown()
@ -135,13 +147,11 @@ def _py_code(caller, buf):
""" """
measure_time = caller.db._py_measure_time measure_time = caller.db._py_measure_time
client_raw = caller.db._py_clientraw client_raw = caller.db._py_clientraw
string = "Executing code%s ..." % ( string = "Executing code%s ..." % (" (measure timing)" if measure_time else "")
" (measure timing)" if measure_time else "")
caller.msg(string) caller.msg(string)
_run_code_snippet(caller, buf, mode="exec", _run_code_snippet(
measure_time=measure_time, caller, buf, mode="exec", measure_time=measure_time, client_raw=client_raw, show_input=False
client_raw=client_raw, )
show_input=False)
return True return True
@ -150,8 +160,9 @@ def _py_quit(caller):
caller.msg("Exited the code editor.") caller.msg("Exited the code editor.")
def _run_code_snippet(caller, pycode, mode="eval", measure_time=False, def _run_code_snippet(
client_raw=False, show_input=True): caller, pycode, mode="eval", measure_time=False, client_raw=False, show_input=True
):
""" """
Run code and try to display information to the caller. Run code and try to display information to the caller.
@ -173,14 +184,10 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
if show_input: if show_input:
for session in sessions: for session in sessions:
try: try:
caller.msg(">>> %s" % pycode, session=session, caller.msg(">>> %s" % pycode, session=session, options={"raw": True})
options={"raw": True})
except TypeError: except TypeError:
caller.msg(">>> %s" % pycode, options={"raw": True}) caller.msg(">>> %s" % pycode, options={"raw": True})
try: try:
# reroute standard output to game client console # reroute standard output to game client console
old_stdout = sys.stdout old_stdout = sys.stdout
@ -214,7 +221,7 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
ret = eval(pycode_compiled, {}, available_vars) ret = eval(pycode_compiled, {}, available_vars)
except Exception: except Exception:
errlist = traceback.format_exc().split('\n') errlist = traceback.format_exc().split("\n")
if len(errlist) > 4: if len(errlist) > 4:
errlist = errlist[4:] errlist = errlist[4:]
ret = "\n".join("%s" % line for line in errlist if line) ret = "\n".join("%s" % line for line in errlist if line)
@ -228,26 +235,25 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
for session in sessions: for session in sessions:
try: try:
caller.msg(ret, session=session, options={"raw": True, caller.msg(ret, session=session, options={"raw": True, "client_raw": client_raw})
"client_raw": client_raw})
except TypeError: except TypeError:
caller.msg(ret, options={"raw": True, caller.msg(ret, options={"raw": True, "client_raw": client_raw})
"client_raw": client_raw})
def evennia_local_vars(caller): def evennia_local_vars(caller):
"""Return Evennia local variables usable in the py command as a dictionary.""" """Return Evennia local variables usable in the py command as a dictionary."""
import evennia import evennia
return { return {
'self': caller, "self": caller,
'me': caller, "me": caller,
'here': getattr(caller, "location", None), "here": getattr(caller, "location", None),
'evennia': evennia, "evennia": evennia,
'ev': evennia, "ev": evennia,
'inherits_from': utils.inherits_from, "inherits_from": utils.inherits_from,
} }
class EvenniaPythonConsole(code.InteractiveConsole): class EvenniaPythonConsole(code.InteractiveConsole):
"""Evennia wrapper around a Python interactive console.""" """Evennia wrapper around a Python interactive console."""
@ -330,6 +336,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
should only be accessible by trusted server admins/superusers.|n should only be accessible by trusted server admins/superusers.|n
""" """
key = "py" key = "py"
aliases = ["!"] aliases = ["!"]
switch_options = ("time", "edit", "clientraw") switch_options = ("time", "edit", "clientraw")
@ -345,27 +352,40 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
if "edit" in self.switches: if "edit" in self.switches:
caller.db._py_measure_time = "time" in self.switches caller.db._py_measure_time = "time" in self.switches
caller.db._py_clientraw = "clientraw" in self.switches caller.db._py_clientraw = "clientraw" in self.switches
EvEditor(self.caller, loadfunc=_py_load, savefunc=_py_code, EvEditor(
quitfunc=_py_quit, key="Python exec: :w or :!", persistent=True, self.caller,
codefunc=_py_code) loadfunc=_py_load,
savefunc=_py_code,
quitfunc=_py_quit,
key="Python exec: :w or :!",
persistent=True,
codefunc=_py_code,
)
return return
if not pycode: if not pycode:
# Run in interactive mode # Run in interactive mode
console = EvenniaPythonConsole(self.caller) console = EvenniaPythonConsole(self.caller)
banner = (f"|gPython {sys.version} on {sys.platform}\n" banner = (
"Evennia interactive console mode - type 'exit()' to leave.|n") f"|gPython {sys.version} on {sys.platform}\n"
"Evennia interactive console mode - type 'exit()' to leave.|n"
)
self.msg(banner) self.msg(banner)
line = "" line = ""
prompt = ">>>" prompt = ">>>"
while line.lower() not in ("exit", "exit()"): while line.lower() not in ("exit", "exit()"):
line = yield(prompt) line = yield (prompt)
prompt = "..." if console.push(line) else ">>>" prompt = "..." if console.push(line) else ">>>"
self.msg("|gClosing the Python console.|n") self.msg("|gClosing the Python console.|n")
return return
_run_code_snippet(caller, self.args, measure_time="time" in self.switches, _run_code_snippet(
client_raw="clientraw" in self.switches) caller,
self.args,
measure_time="time" in self.switches,
client_raw="clientraw" in self.switches,
)
# helper function. Kept outside so it can be imported and run # helper function. Kept outside so it can be imported and run
# by other commands. # by other commands.
@ -376,9 +396,19 @@ def format_script_list(scripts):
if not scripts: if not scripts:
return "<No scripts>" return "<No scripts>"
table = EvTable("|wdbref|n", "|wobj|n", "|wkey|n", "|wintval|n", "|wnext|n", table = EvTable(
"|wrept|n", "|wdb", "|wtypeclass|n", "|wdesc|n", "|wdbref|n",
align='r', border="tablecols") "|wobj|n",
"|wkey|n",
"|wintval|n",
"|wnext|n",
"|wrept|n",
"|wdb",
"|wtypeclass|n",
"|wdesc|n",
align="r",
border="tablecols",
)
for script in scripts: for script in scripts:
nextrep = script.time_until_next_repeat() nextrep = script.time_until_next_repeat()
if nextrep is None: if nextrep is None:
@ -392,15 +422,17 @@ def format_script_list(scripts):
else: else:
rept = "-/-" rept = "-/-"
table.add_row(script.id, table.add_row(
script.obj.key if (hasattr(script, 'obj') and script.obj) else "<Global>", script.id,
script.key, script.obj.key if (hasattr(script, "obj") and script.obj) else "<Global>",
script.interval if script.interval > 0 else "--", script.key,
nextrep, script.interval if script.interval > 0 else "--",
rept, nextrep,
"*" if script.persistent else "-", rept,
script.typeclass_path.rsplit('.', 1)[-1], "*" if script.persistent else "-",
crop(script.desc, width=20)) script.typeclass_path.rsplit(".", 1)[-1],
crop(script.desc, width=20),
)
return "%s" % table return "%s" % table
@ -425,6 +457,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
Use script for managing commands on objects. Use script for managing commands on objects.
""" """
key = "scripts" key = "scripts"
aliases = ["globalscript", "listscripts"] aliases = ["globalscript", "listscripts"]
switch_options = ("start", "stop", "kill", "validate") switch_options = ("start", "stop", "kill", "validate")
@ -469,11 +502,11 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
caller.msg(string) caller.msg(string)
return return
if self.switches and self.switches[0] in ('stop', 'del', 'delete', 'kill'): if self.switches and self.switches[0] in ("stop", "del", "delete", "kill"):
# we want to delete something # we want to delete something
if len(scripts) == 1: if len(scripts) == 1:
# we have a unique match! # we have a unique match!
if 'kill' in self.switches: if "kill" in self.switches:
string = "Killing script '%s'" % scripts[0].key string = "Killing script '%s'" % scripts[0].key
scripts[0].stop(kill=True) scripts[0].stop(kill=True)
else: else:
@ -508,8 +541,9 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
a list of <nr> latest objects in database. If not a list of <nr> latest objects in database. If not
given, <nr> defaults to 10. given, <nr> defaults to 10.
""" """
key = "objects" key = "objects"
aliases = ["listobjects", "listobjs", 'stats', 'db'] aliases = ["listobjects", "listobjs", "stats", "db"]
locks = "cmd:perm(listobjects) or perm(Builder)" locks = "cmd:perm(listobjects) or perm(Builder)"
help_category = "System" help_category = "System"
@ -529,33 +563,49 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
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 # total object sum table
totaltable = self.styled_table("|wtype|n", "|wcomment|n", "|wcount|n", "|w%|n", totaltable = self.styled_table(
border="table", align="l") "|wtype|n", "|wcomment|n", "|wcount|n", "|w%|n", border="table", align="l"
totaltable.align = 'l' )
totaltable.add_row("Characters", "(BASE_CHARACTER_TYPECLASS + children)", totaltable.align = "l"
nchars, "%.2f" % ((float(nchars) / nobjs) * 100)) totaltable.add_row(
totaltable.add_row("Rooms", "(BASE_ROOM_TYPECLASS + children)", "Characters",
nrooms, "%.2f" % ((float(nrooms) / nobjs) * 100)) "(BASE_CHARACTER_TYPECLASS + children)",
totaltable.add_row("Exits", "(BASE_EXIT_TYPECLASS + children)", nchars,
nexits, "%.2f" % ((float(nexits) / nobjs) * 100)) "%.2f" % ((float(nchars) / nobjs) * 100),
)
totaltable.add_row(
"Rooms",
"(BASE_ROOM_TYPECLASS + children)",
nrooms,
"%.2f" % ((float(nrooms) / nobjs) * 100),
)
totaltable.add_row(
"Exits",
"(BASE_EXIT_TYPECLASS + children)",
nexits,
"%.2f" % ((float(nexits) / nobjs) * 100),
)
totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100)) totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100))
# typeclass table # typeclass table
typetable = self.styled_table("|wtypeclass|n", "|wcount|n", "|w%|n", typetable = self.styled_table(
border="table", align="l") "|wtypeclass|n", "|wcount|n", "|w%|n", border="table", align="l"
typetable.align = 'l' )
typetable.align = "l"
dbtotals = ObjectDB.objects.object_totals() dbtotals = ObjectDB.objects.object_totals()
for path, count in dbtotals.items(): for path, count in dbtotals.items():
typetable.add_row(path, count, "%.2f" % ((float(count) / nobjs) * 100)) typetable.add_row(path, count, "%.2f" % ((float(count) / nobjs) * 100))
# last N table # last N table
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim):] objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim) :]
latesttable = self.styled_table("|wcreated|n", "|wdbref|n", "|wname|n", latesttable = self.styled_table(
"|wtypeclass|n", align="l", border="table") "|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table"
latesttable.align = 'l' )
latesttable.align = "l"
for obj in objs: for obj in objs:
latesttable.add_row(utils.datetime_format(obj.date_created), latesttable.add_row(
obj.dbref, obj.key, obj.path) 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 subtype totals (out of %i Objects):|n\n%s" % (nobjs, totaltable)
string += "\n|wObject typeclass distribution:|n\n%s" % typetable string += "\n|wObject typeclass distribution:|n\n%s" % typetable
@ -578,9 +628,10 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
It will list the <nr> amount of latest registered accounts It will list the <nr> amount of latest registered accounts
If not given, <nr> defaults to 10. If not given, <nr> defaults to 10.
""" """
key = "accounts" key = "accounts"
aliases = ["account", "listaccounts"] aliases = ["account", "listaccounts"]
switch_options = ("delete", ) switch_options = ("delete",)
locks = "cmd:perm(listaccounts) or perm(Admin)" locks = "cmd:perm(listaccounts) or perm(Admin)"
help_category = "System" help_category = "System"
@ -618,11 +669,13 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
return return
username = account.username username = account.username
# ask for confirmation # ask for confirmation
confirm = ("It is often better to block access to an account rather than to delete it. " confirm = (
"|yAre you sure you want to permanently delete " "It is often better to block access to an account rather than to delete it. "
"account '|n{}|y'|n yes/[no]?".format(username)) "|yAre you sure you want to permanently delete "
answer = yield(confirm) "account '|n{}|y'|n yes/[no]?".format(username)
if answer.lower() not in ('y', 'yes'): )
answer = yield (confirm)
if answer.lower() not in ("y", "yes"):
caller.msg("Canceled deletion.") caller.msg("Canceled deletion.")
return return
@ -632,7 +685,10 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
if reason: if reason:
string += " Reason given:\n '%s'" % reason string += " Reason given:\n '%s'" % reason
account.msg(string) account.msg(string)
logger.log_sec("Account Deleted: %s (Reason: %s, Caller: %s, IP: %s)." % (account, reason, caller, self.session.address)) logger.log_sec(
"Account Deleted: %s (Reason: %s, Caller: %s, IP: %s)."
% (account, reason, caller, self.session.address)
)
account.delete() account.delete()
self.msg("Account %s was successfully deleted." % username) self.msg("Account %s was successfully deleted." % username)
return return
@ -647,14 +703,20 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
# typeclass table # typeclass table
dbtotals = AccountDB.objects.object_totals() dbtotals = AccountDB.objects.object_totals()
typetable = self.styled_table("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l") typetable = self.styled_table(
"|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l"
)
for path, count in dbtotals.items(): for path, count in dbtotals.items():
typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100)) typetable.add_row(path, count, "%.2f" % ((float(count) / naccounts) * 100))
# last N table # last N table
plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim):] plyrs = AccountDB.objects.all().order_by("db_date_created")[max(0, naccounts - nlim) :]
latesttable = self.styled_table("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l") latesttable = self.styled_table(
"|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l"
)
for ply in plyrs: for ply in plyrs:
latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path) latesttable.add_row(
utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path
)
string = "\n|wAccount typeclass distribution:|n\n%s" % typetable string = "\n|wAccount typeclass distribution:|n\n%s" % typetable
string += "\n|wLast %s Accounts created:|n\n%s" % (min(naccounts, nlim), latesttable) string += "\n|wLast %s Accounts created:|n\n%s" % (min(naccounts, nlim), latesttable)
@ -703,7 +765,9 @@ class CmdService(COMMAND_DEFAULT_CLASS):
if not switches or switches[0] == "list": if not switches or switches[0] == "list":
# Just display the list of installed services and their # Just display the list of installed services and their
# status, then exit. # status, then exit.
table = self.styled_table("|wService|n (use services/start|stop|delete)", "|wstatus", align="l") table = self.styled_table(
"|wService|n (use services/start|stop|delete)", "|wstatus", align="l"
)
for service in service_collection.services: 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(str(table)) caller.msg(str(table))
@ -714,8 +778,8 @@ class CmdService(COMMAND_DEFAULT_CLASS):
try: try:
service = service_collection.getServiceNamed(self.args) service = service_collection.getServiceNamed(self.args)
except Exception: except Exception:
string = 'Invalid service name. This command is case-sensitive. ' string = "Invalid service name. This command is case-sensitive. "
string += 'See service/list for valid service name (enter the full name exactly).' string += "See service/list for valid service name (enter the full name exactly)."
caller.msg(string) caller.msg(string)
return return
@ -725,9 +789,9 @@ class CmdService(COMMAND_DEFAULT_CLASS):
delmode = switches[0] == "delete" delmode = switches[0] == "delete"
if not service.running: if not service.running:
caller.msg('That service is not currently running.') caller.msg("That service is not currently running.")
return return
if service.name[:7] == 'Evennia': if service.name[:7] == "Evennia":
if delmode: if delmode:
caller.msg("You cannot remove a core Evennia service (named 'Evennia***').") caller.msg("You cannot remove a core Evennia service (named 'Evennia***').")
return return
@ -749,7 +813,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
if switches[0] == "start": if switches[0] == "start":
# Attempt to start a service. # Attempt to start a service.
if service.running: if service.running:
caller.msg('That service is already running.') caller.msg("That service is already running.")
return return
caller.msg("Starting service '%s'." % self.args) caller.msg("Starting service '%s'." % self.args)
service.startService() service.startService()
@ -789,11 +853,13 @@ class CmdAbout(COMMAND_DEFAULT_CLASS):
|wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com) |wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com)
|wMaintainer|n (2006-10) Greg Taylor |wMaintainer|n (2006-10) Greg Taylor
""".format(version=utils.get_evennia_version(), """.format(
os=os.name, version=utils.get_evennia_version(),
python=sys.version.split()[0], os=os.name,
twisted=twisted.version.short(), python=sys.version.split()[0],
django=django.get_version()) twisted=twisted.version.short(),
django=django.get_version(),
)
self.caller.msg(string) self.caller.msg(string)
@ -807,6 +873,7 @@ class CmdTime(COMMAND_DEFAULT_CLASS):
List Server time statistics such as uptime List Server time statistics such as uptime
and the current time stamp. and the current time stamp.
""" """
key = "time" key = "time"
aliases = "uptime" aliases = "uptime"
locks = "cmd:perm(time) or perm(Player)" locks = "cmd:perm(time) or perm(Player)"
@ -821,11 +888,19 @@ class CmdTime(COMMAND_DEFAULT_CLASS):
table1.add_row("First start", datetime.datetime.fromtimestamp(gametime.server_epoch())) table1.add_row("First start", datetime.datetime.fromtimestamp(gametime.server_epoch()))
table1.add_row("Current time", datetime.datetime.now()) table1.add_row("Current time", datetime.datetime.now())
table1.reformat_column(0, width=30) table1.reformat_column(0, width=30)
table2 = self.styled_table("|wIn-Game time", "|wReal time x %g" % gametime.TIMEFACTOR, align="l", width=77, border_top=0) table2 = self.styled_table(
"|wIn-Game time",
"|wReal time x %g" % gametime.TIMEFACTOR,
align="l",
width=77,
border_top=0,
)
epochtxt = "Epoch (%s)" % ("from settings" if settings.TIME_GAME_EPOCH else "server start") epochtxt = "Epoch (%s)" % ("from settings" if settings.TIME_GAME_EPOCH else "server start")
table2.add_row(epochtxt, datetime.datetime.fromtimestamp(gametime.game_epoch())) table2.add_row(epochtxt, datetime.datetime.fromtimestamp(gametime.game_epoch()))
table2.add_row("Total time passed:", utils.time_format(gametime.gametime(), 2)) table2.add_row("Total time passed:", utils.time_format(gametime.gametime(), 2))
table2.add_row("Current time ", datetime.datetime.fromtimestamp(gametime.gametime(absolute=True))) table2.add_row(
"Current time ", datetime.datetime.fromtimestamp(gametime.gametime(absolute=True))
)
table2.reformat_column(0, width=30) table2.reformat_column(0, width=30)
self.caller.msg(str(table1) + "\n" + str(table2)) self.caller.msg(str(table1) + "\n" + str(table2))
@ -866,6 +941,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
the released memory will instead be re-used by the program. the released memory will instead be re-used by the program.
""" """
key = "server" key = "server"
aliases = ["serverload", "serverprocess"] aliases = ["serverload", "serverprocess"]
switch_options = ("mem", "flushmem") switch_options = ("mem", "flushmem")
@ -884,8 +960,10 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
prev, _ = _IDMAPPER.cache_size() prev, _ = _IDMAPPER.cache_size()
nflushed = _IDMAPPER.flush_cache() nflushed = _IDMAPPER.flush_cache()
now, _ = _IDMAPPER.cache_size() now, _ = _IDMAPPER.cache_size()
string = "The Idmapper cache freed |w{idmapper}|n database objects.\n" \ string = (
"The Python garbage collector freed |w{gc}|n Python instances total." "The Idmapper cache freed |w{idmapper}|n database objects.\n"
"The Python garbage collector freed |w{gc}|n Python instances total."
)
self.caller.msg(string.format(idmapper=(prev - now), gc=nflushed)) self.caller.msg(string.format(idmapper=(prev - now), gc=nflushed))
return return
@ -900,6 +978,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
# unfortunately, since it's not specific to the process) /rant # unfortunately, since it's not specific to the process) /rant
try: try:
import psutil import psutil
has_psutil = True has_psutil = True
except ImportError: except ImportError:
has_psutil = False has_psutil = False
@ -920,8 +999,10 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
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), loadtable.add_row("Process ID", "%g" % pid),
else: else:
loadtable = "Not available on Windows without 'psutil' library " \ loadtable = (
"(install with |wpip install psutil|n)." "Not available on Windows without 'psutil' library "
"(install with |wpip install psutil|n)."
)
else: else:
# Linux / BSD (OSX) - proper pid-based statistics # Linux / BSD (OSX) - proper pid-based statistics
@ -931,9 +1012,15 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
import resource as _RESOURCE import resource as _RESOURCE
loadavg = os.getloadavg()[0] loadavg = os.getloadavg()[0]
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1000.0 # resident memory rmem = (
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1000.0 # virtual memory float(os.popen("ps -p %d -o %s | tail -1" % (pid, "rss")).read()) / 1000.0
pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # % of resident memory to total ) # 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()
) # % of resident memory to total
rusage = _RESOURCE.getrusage(_RESOURCE.RUSAGE_SELF) rusage = _RESOURCE.getrusage(_RESOURCE.RUSAGE_SELF)
if "mem" in self.switches: if "mem" in self.switches:
@ -947,16 +1034,28 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
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("Virtual address space", "")
loadtable.add_row("|x(resident+swap+caching)|n", "%g MB" % vmem) loadtable.add_row("|x(resident+swap+caching)|n", "%g MB" % vmem)
loadtable.add_row("CPU time used (total)", "%s (%gs)" loadtable.add_row(
% (utils.time_format(rusage.ru_utime), rusage.ru_utime)) "CPU time used (total)",
loadtable.add_row("CPU time used (user)", "%s (%gs)" "%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime),
% (utils.time_format(rusage.ru_stime), rusage.ru_stime)) )
loadtable.add_row("Page faults", "%g hard, %g soft, %g swapouts" loadtable.add_row(
% (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap)) "CPU time used (user)",
loadtable.add_row("Disk I/O", "%g reads, %g writes" % (rusage.ru_inblock, rusage.ru_oublock)) "%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("Network I/O", "%g in, %g out" % (rusage.ru_msgrcv, rusage.ru_msgsnd))
loadtable.add_row("Context switching", "%g vol, %g forced, %g signals" loadtable.add_row(
% (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals)) "Context switching",
"%g vol, %g forced, %g signals"
% (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals),
)
# os-generic # os-generic
@ -964,8 +1063,11 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
# object cache count (note that sys.getsiseof is not called so this works for pypy too. # object cache count (note that sys.getsiseof is not called so this works for pypy too.
total_num, cachedict = _IDMAPPER.cache_size() total_num, cachedict = _IDMAPPER.cache_size()
sorted_cache = sorted([(key, num) for key, num in cachedict.items() if num > 0], sorted_cache = sorted(
key=lambda tup: tup[1], reverse=True) [(key, num) for key, num in cachedict.items() if num > 0],
key=lambda tup: tup[1],
reverse=True,
)
memtable = self.styled_table("entity name", "number", "idmapper %", align="l") memtable = self.styled_table("entity name", "number", "idmapper %", align="l")
for tup in sorted_cache: for tup in sorted_cache:
memtable.add_row(tup[0], "%i" % tup[1], "%.2f" % (float(tup[1]) / total_num * 100)) memtable.add_row(tup[0], "%i" % tup[1], "%.2f" % (float(tup[1]) / total_num * 100))
@ -988,22 +1090,29 @@ class CmdTickers(COMMAND_DEFAULT_CLASS):
inspecting the current status. inspecting the current status.
""" """
key = "tickers" key = "tickers"
help_category = "System" help_category = "System"
locks = "cmd:perm(tickers) or perm(Builder)" locks = "cmd:perm(tickers) or perm(Builder)"
def func(self): def func(self):
from evennia import TICKER_HANDLER from evennia import TICKER_HANDLER
all_subs = TICKER_HANDLER.all_display() all_subs = TICKER_HANDLER.all_display()
if not all_subs: if not all_subs:
self.caller.msg("No tickers are currently active.") self.caller.msg("No tickers are currently active.")
return return
table = self.styled_table("interval (s)", "object", "path/methodname", "idstring", "db") table = self.styled_table("interval (s)", "object", "path/methodname", "idstring", "db")
for sub in all_subs: for sub in all_subs:
table.add_row(sub[3], table.add_row(
"%s%s" % (sub[0] or "[None]", sub[3],
sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or ""), "%s%s"
sub[1] if sub[1] else sub[2], % (
sub[4] or "[Unset]", sub[0] or "[None]",
"*" if sub[5] else "-") 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" + str(table)) self.caller.msg("|wActive tickers|n:\n" + str(table))

File diff suppressed because it is too large Load diff

View file

@ -14,8 +14,13 @@ from evennia.commands.cmdhandler import CMD_LOGINSTART
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate", __all__ = (
"CmdUnconnectedQuit", "CmdUnconnectedLook", "CmdUnconnectedHelp") "CmdUnconnectedConnect",
"CmdUnconnectedCreate",
"CmdUnconnectedQuit",
"CmdUnconnectedLook",
"CmdUnconnectedHelp",
)
MULTISESSION_MODE = settings.MULTISESSION_MODE MULTISESSION_MODE = settings.MULTISESSION_MODE
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
@ -45,7 +50,7 @@ def create_guest_account(session):
if account: if account:
return enabled, account return enabled, account
else: else:
session.msg("|R%s|n" % '\n'.join(errors)) session.msg("|R%s|n" % "\n".join(errors))
return enabled, None return enabled, None
@ -68,10 +73,12 @@ def create_normal_account(session, name, password):
# Match account name and check password # Match account name and check password
# authenticate() handles all its own throttling # authenticate() handles all its own throttling
account, errors = Account.authenticate(username=name, password=password, ip=address, session=session) account, errors = Account.authenticate(
username=name, password=password, ip=address, session=session
)
if not account: if not account:
# No accountname or password match # No accountname or password match
session.msg("|R%s|n" % '\n'.join(errors)) session.msg("|R%s|n" % "\n".join(errors))
return None return None
return account return account
@ -89,6 +96,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
If you have spaces in your name, enclose it in double quotes. If you have spaces in your name, enclose it in double quotes.
""" """
key = "connect" key = "connect"
aliases = ["conn", "con", "co"] aliases = ["conn", "con", "co"]
locks = "cmd:all()" # not really needed locks = "cmd:all()" # not really needed
@ -122,7 +130,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
session.sessionhandler.login(session, account) session.sessionhandler.login(session, account)
return return
else: else:
session.msg("|R%s|n" % '\n'.join(errors)) session.msg("|R%s|n" % "\n".join(errors))
return return
if len(parts) != 2: if len(parts) != 2:
@ -133,11 +141,13 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
name, password = parts name, password = parts
account, errors = Account.authenticate(username=name, password=password, ip=address, session=session) account, errors = Account.authenticate(
username=name, password=password, ip=address, session=session
)
if account: if account:
session.sessionhandler.login(session, account) session.sessionhandler.login(session, account)
else: else:
session.msg("|R%s|n" % '\n'.join(errors)) session.msg("|R%s|n" % "\n".join(errors))
class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS): class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
@ -152,6 +162,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
If you have spaces in your name, enclose it in double quotes. If you have spaces in your name, enclose it in double quotes.
""" """
key = "create" key = "create"
aliases = ["cre", "cr"] aliases = ["cre", "cr"]
locks = "cmd:all()" locks = "cmd:all()"
@ -174,25 +185,31 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
# this was (hopefully) due to no quotes being found # this was (hopefully) due to no quotes being found
parts = parts[0].split(None, 1) parts = parts[0].split(None, 1)
if len(parts) != 2: if len(parts) != 2:
string = "\n Usage (without <>): create <name> <password>" \ string = (
"\nIf <name> or <password> contains spaces, enclose it in double quotes." "\n Usage (without <>): create <name> <password>"
"\nIf <name> or <password> contains spaces, enclose it in double quotes."
)
session.msg(string) session.msg(string)
return return
username, password = parts username, password = parts
# everything's ok. Create the new account account. # everything's ok. Create the new account account.
account, errors = Account.create(username=username, password=password, ip=address, session=session) account, errors = Account.create(
username=username, password=password, ip=address, session=session
)
if account: if account:
# tell the caller everything went well. # tell the caller everything went well.
string = "A new account '%s' was created. Welcome!" string = "A new account '%s' was created. Welcome!"
if " " in username: if " " in username:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'." string += (
"\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
)
else: else:
string += "\n\nYou can now log with the command 'connect %s <your password>'." string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (username, username)) session.msg(string % (username, username))
else: else:
session.msg("|R%s|n" % '\n'.join(errors)) session.msg("|R%s|n" % "\n".join(errors))
class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS): class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS):
@ -206,6 +223,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS):
here for unconnected accounts for the sake of simplicity. The logged in here for unconnected accounts for the sake of simplicity. The logged in
version is a bit more complicated. version is a bit more complicated.
""" """
key = "quit" key = "quit"
aliases = ["q", "qu"] aliases = ["q", "qu"]
locks = "cmd:all()" locks = "cmd:all()"
@ -228,6 +246,7 @@ class CmdUnconnectedLook(COMMAND_DEFAULT_CLASS):
This is called by the server and kicks everything in gear. This is called by the server and kicks everything in gear.
All it does is display the connect screen. All it does is display the connect screen.
""" """
key = CMD_LOGINSTART key = CMD_LOGINSTART
aliases = ["look", "l"] aliases = ["look", "l"]
locks = "cmd:all()" locks = "cmd:all()"
@ -237,7 +256,7 @@ class CmdUnconnectedLook(COMMAND_DEFAULT_CLASS):
callables = utils.callables_from_module(CONNECTION_SCREEN_MODULE) callables = utils.callables_from_module(CONNECTION_SCREEN_MODULE)
if "connection_screen" in callables: if "connection_screen" in callables:
connection_screen = callables['connection_screen']() connection_screen = callables["connection_screen"]()
else: else:
connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE) connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE)
if not connection_screen: if not connection_screen:
@ -255,6 +274,7 @@ class CmdUnconnectedHelp(COMMAND_DEFAULT_CLASS):
This is an unconnected version of the help command, This is an unconnected version of the help command,
for simplicity. It shows a pane of info. for simplicity. It shows a pane of info.
""" """
key = "help" key = "help"
aliases = ["h", "?"] aliases = ["h", "?"]
locks = "cmd:all()" locks = "cmd:all()"
@ -262,8 +282,7 @@ class CmdUnconnectedHelp(COMMAND_DEFAULT_CLASS):
def func(self): def func(self):
"""Shows help""" """Shows help"""
string = \ string = """
"""
You are not yet logged into the game. Commands available at this point: You are not yet logged into the game. Commands available at this point:
|wcreate|n - create a new account |wcreate|n - create a new account
@ -283,7 +302,7 @@ You can use the |wlook|n command if you want to see the connect screen again.
""" """
if settings.STAFF_CONTACT_EMAIL: if settings.STAFF_CONTACT_EMAIL:
string += 'For support, please contact: %s' % settings.STAFF_CONTACT_EMAIL string += "For support, please contact: %s" % settings.STAFF_CONTACT_EMAIL
self.caller.msg(string) self.caller.msg(string)
@ -311,7 +330,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
""" """
key = "encoding" key = "encoding"
aliases = ("encode") aliases = "encode"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
@ -323,7 +342,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
return return
sync = False sync = False
if 'clear' in self.switches: if "clear" in self.switches:
# remove customization # remove customization
old_encoding = self.session.protocol_flags.get("ENCODING", None) old_encoding = self.session.protocol_flags.get("ENCODING", None)
if old_encoding: if old_encoding:
@ -337,10 +356,15 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
pencoding = self.session.protocol_flags.get("ENCODING", None) pencoding = self.session.protocol_flags.get("ENCODING", None)
string = "" string = ""
if pencoding: if pencoding:
string += "Default encoding: |g%s|n (change with |wencoding <encoding>|n)" % pencoding string += (
"Default encoding: |g%s|n (change with |wencoding <encoding>|n)" % pencoding
)
encodings = settings.ENCODINGS encodings = settings.ENCODINGS
if 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: if not string:
string = "No encodings found." string = "No encodings found."
else: else:
@ -350,11 +374,16 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
try: try:
codecs_lookup(encoding) codecs_lookup(encoding)
except LookupError: except LookupError:
string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n"\ string = (
% (encoding, old_encoding) "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n"
% (encoding, old_encoding)
)
else: else:
self.session.protocol_flags["ENCODING"] = encoding self.session.protocol_flags["ENCODING"] = encoding
string = "Your custom text encoding was changed from '|w%s|n' to '|w%s|n'." % (old_encoding, encoding) string = "Your custom text encoding was changed from '|w%s|n' to '|w%s|n'." % (
old_encoding,
encoding,
)
sync = True sync = True
if sync: if sync:
self.session.sessionhandler.session_portal_sync(self.session) self.session.sessionhandler.session_portal_sync(self.session)
@ -371,6 +400,7 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
Used to flip screenreader mode on and off before logging in (when Used to flip screenreader mode on and off before logging in (when
logged in, use option screenreader on). logged in, use option screenreader on).
""" """
key = "screenreader" key = "screenreader"
def func(self): def func(self):
@ -390,14 +420,20 @@ class CmdUnconnectedInfo(COMMAND_DEFAULT_CLASS):
was created by looking at the MUDINFO implementation in MUX2, TinyMUSH, Rhost, was created by looking at the MUDINFO implementation in MUX2, TinyMUSH, Rhost,
and PennMUSH. and PennMUSH.
""" """
key = "info" key = "info"
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
self.caller.msg("## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END INFO" % ( self.caller.msg(
settings.SERVERNAME, "## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END INFO"
datetime.datetime.fromtimestamp(gametime.SERVER_START_TIME).ctime(), % (
SESSIONS.account_count(), utils.get_evennia_version())) settings.SERVERNAME,
datetime.datetime.fromtimestamp(gametime.SERVER_START_TIME).ctime(),
SESSIONS.account_count(),
utils.get_evennia_version(),
)
)
def _create_account(session, accountname, password, permissions, typeclass=None, email=None): def _create_account(session, accountname, password, permissions, typeclass=None, email=None):
@ -405,10 +441,15 @@ def _create_account(session, accountname, password, permissions, typeclass=None,
Helper function, creates an account of the specified typeclass. Helper function, creates an account of the specified typeclass.
""" """
try: try:
new_account = create.create_account(accountname, email, password, permissions=permissions, typeclass=typeclass) new_account = create.create_account(
accountname, email, password, permissions=permissions, typeclass=typeclass
)
except Exception as e: except Exception as e:
session.msg("There was an error creating the Account:\n%s\n If this problem persists, contact an admin." % e) session.msg(
"There was an error creating the Account:\n%s\n If this problem persists, contact an admin."
% e
)
logger.log_trace() logger.log_trace()
return False return False
@ -431,13 +472,17 @@ def _create_character(session, new_account, typeclass, home, permissions):
This is meant for Guest and MULTISESSION_MODE < 2 situations. This is meant for Guest and MULTISESSION_MODE < 2 situations.
""" """
try: try:
new_character = create.create_object(typeclass, key=new_account.key, home=home, permissions=permissions) new_character = create.create_object(
typeclass, key=new_account.key, home=home, permissions=permissions
)
# set playable character list # set playable character list
new_account.db._playable_characters.append(new_character) new_account.db._playable_characters.append(new_character)
# allow only the character itself and the account to puppet this character (and Developers). # allow only the character itself and the account to puppet this character (and Developers).
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % new_character.locks.add(
(new_character.id, new_account.id)) "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)"
% (new_character.id, new_account.id)
)
# If no description is set, set a default description # If no description is set, set a default description
if not new_character.db.desc: if not new_character.db.desc:
@ -445,5 +490,8 @@ def _create_character(session, new_account, typeclass, home, permissions):
# We need to set this to have ic auto-connect to this character # We need to set this to have ic auto-connect to this character
new_account.db._last_puppet = new_character new_account.db._last_puppet = new_character
except Exception as e: except Exception as e:
session.msg("There was an error creating the Character:\n%s\n If this problem persists, contact an admin." % e) session.msg(
"There was an error creating the Character:\n%s\n If this problem persists, contact an admin."
% e
)
logger.log_trace() logger.log_trace()

View file

@ -12,6 +12,7 @@ from evennia.commands import cmdparser
# Testing-command sets # Testing-command sets
class _CmdA(Command): class _CmdA(Command):
key = "A" key = "A"
@ -80,6 +81,7 @@ class _CmdSetD(CmdSet):
self.add(_CmdC("D")) self.add(_CmdC("D"))
self.add(_CmdD("D")) self.add(_CmdD("D"))
# testing Command Sets # testing Command Sets
@ -263,6 +265,7 @@ class TestCmdSetMergers(TestCase):
cmdset_f = d + b + c + a # two last mergers duplicates=True cmdset_f = d + b + c + a # two last mergers duplicates=True
self.assertEqual(len(cmdset_f.commands), 10) self.assertEqual(len(cmdset_f.commands), 10)
# test cmdhandler functions # test cmdhandler functions
@ -279,7 +282,7 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
"Test the cmdhandler.get_and_merge_cmdsets function." "Test the cmdhandler.get_and_merge_cmdsets function."
def setUp(self): def setUp(self):
self.patch(sys.modules['evennia.server.sessionhandler'], 'delay', _mockdelay) self.patch(sys.modules["evennia.server.sessionhandler"], "delay", _mockdelay)
super().setUp() super().setUp()
self.cmdset_a = _CmdSetA() self.cmdset_a = _CmdSetA()
self.cmdset_b = _CmdSetB() self.cmdset_b = _CmdSetB()
@ -295,19 +298,25 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
a = self.cmdset_a a = self.cmdset_a
a.no_channels = True a.no_channels = True
self.set_cmdsets(self.session, a) self.set_cmdsets(self.session, a)
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, None, None, "session", "") deferred = cmdhandler.get_and_merge_cmdsets(
self.session, self.session, None, None, "session", ""
)
def _callback(cmdset): def _callback(cmdset):
self.assertEqual(cmdset.key, "A") self.assertEqual(cmdset.key, "A")
deferred.addCallback(_callback) deferred.addCallback(_callback)
return deferred return deferred
def test_from_account(self): def test_from_account(self):
from evennia.commands.default.cmdset_account import AccountCmdSet from evennia.commands.default.cmdset_account import AccountCmdSet
a = self.cmdset_a a = self.cmdset_a
a.no_channels = True a.no_channels = True
self.set_cmdsets(self.account, a) self.set_cmdsets(self.account, a)
deferred = cmdhandler.get_and_merge_cmdsets(self.account, None, self.account, None, "account", "") deferred = cmdhandler.get_and_merge_cmdsets(
self.account, None, self.account, None, "account", ""
)
# get_and_merge_cmdsets converts to lower-case internally. # get_and_merge_cmdsets converts to lower-case internally.
def _callback(cmdset): def _callback(cmdset):
@ -315,7 +324,8 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
pcmdset.at_cmdset_creation() pcmdset.at_cmdset_creation()
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"]
self.assertTrue(all(cmd.key in pcmds for cmd in cmdset.commands)) self.assertTrue(all(cmd.key in pcmds for cmd in cmdset.commands))
#_callback = lambda cmdset: self.assertEqual(sum(1 for cmd in cmdset.commands if cmd.key in ("a", "b", "c", "d")), 4)
# _callback = lambda cmdset: self.assertEqual(sum(1 for cmd in cmdset.commands if cmd.key in ("a", "b", "c", "d")), 4)
deferred.addCallback(_callback) deferred.addCallback(_callback)
return deferred return deferred
@ -324,7 +334,11 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "") deferred = cmdhandler.get_and_merge_cmdsets(self.obj1, None, None, self.obj1, "object", "")
# get_and_merge_cmdsets converts to lower-case internally. # get_and_merge_cmdsets converts to lower-case internally.
def _callback(cmdset): return self.assertEqual(sum(1 for cmd in cmdset.commands if cmd.key in ("a", "b", "c", "d")), 4) def _callback(cmdset):
return self.assertEqual(
sum(1 for cmd in cmdset.commands if cmd.key in ("a", "b", "c", "d")), 4
)
deferred.addCallback(_callback) deferred.addCallback(_callback)
return deferred return deferred
@ -340,6 +354,7 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
self.assertTrue(cmdset.no_exits) self.assertTrue(cmdset.no_exits)
self.assertTrue(cmdset.no_channels) self.assertTrue(cmdset.no_channels)
self.assertEqual(cmdset.key, "D") self.assertEqual(cmdset.key, "D")
deferred.addCallback(_callback) deferred.addCallback(_callback)
return deferred return deferred
@ -347,6 +362,7 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
import evennia import evennia
from evennia.commands.default.cmdset_account import AccountCmdSet from evennia.commands.default.cmdset_account import AccountCmdSet
from evennia.comms.channelhandler import CHANNEL_HANDLER from evennia.comms.channelhandler import CHANNEL_HANDLER
testchannel = evennia.create_channel("channeltest", locks="listen:all();send:all()") testchannel = evennia.create_channel("channeltest", locks="listen:all();send:all()")
CHANNEL_HANDLER.add(testchannel) CHANNEL_HANDLER.add(testchannel)
CHANNEL_HANDLER.update() CHANNEL_HANDLER.update()
@ -354,14 +370,19 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
self.assertTrue(testchannel.has_connection(self.account)) self.assertTrue(testchannel.has_connection(self.account))
a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d a, b, c, d = self.cmdset_a, self.cmdset_b, self.cmdset_c, self.cmdset_d
self.set_cmdsets(self.account, a, b, c, d) self.set_cmdsets(self.account, a, b, c, d)
deferred = cmdhandler.get_and_merge_cmdsets(self.session, self.session, self.account, self.char1, "session", "") deferred = cmdhandler.get_and_merge_cmdsets(
self.session, self.session, self.account, self.char1, "session", ""
)
def _callback(cmdset): def _callback(cmdset):
pcmdset = AccountCmdSet() pcmdset = AccountCmdSet()
pcmdset.at_cmdset_creation() pcmdset.at_cmdset_creation()
pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] + ["out"] pcmds = [cmd.key for cmd in pcmdset.commands] + ["a", "b", "c", "d"] + ["out"]
self.assertTrue(all(cmd.key or hasattr(cmd, "is_channel") in pcmds for cmd in cmdset.commands)) self.assertTrue(
all(cmd.key or hasattr(cmd, "is_channel") in pcmds for cmd in cmdset.commands)
)
self.assertTrue(any(hasattr(cmd, "is_channel") for cmd in cmdset.commands)) self.assertTrue(any(hasattr(cmd, "is_channel") for cmd in cmdset.commands))
deferred.addCallback(_callback) deferred.addCallback(_callback)
return deferred return deferred
@ -376,6 +397,7 @@ class TestGetAndMergeCmdSets(TwistedTestCase, EvenniaTest):
def _callback(cmdset): def _callback(cmdset):
self.assertEqual(len(cmdset.commands), 9) self.assertEqual(len(cmdset.commands), 9)
deferred.addCallback(_callback) deferred.addCallback(_callback)
return deferred return deferred
@ -384,6 +406,7 @@ class AccessableCommand(Command):
def access(*args, **kwargs): def access(*args, **kwargs):
return True return True
class _CmdTest1(AccessableCommand): class _CmdTest1(AccessableCommand):
key = "test1" key = "test1"
@ -402,6 +425,7 @@ class _CmdTest4(AccessableCommand):
class _CmdSetTest(CmdSet): class _CmdSetTest(CmdSet):
key = "test_cmdset" key = "test_cmdset"
def at_cmdset_creation(self): def at_cmdset_creation(self):
self.add(_CmdTest1) self.add(_CmdTest1)
self.add(_CmdTest2) self.add(_CmdTest2)
@ -409,15 +433,16 @@ class _CmdSetTest(CmdSet):
class TestCmdParser(TestCase): class TestCmdParser(TestCase):
def test_create_match(self): def test_create_match(self):
class DummyCmd: class DummyCmd:
pass pass
dummy = DummyCmd() dummy = DummyCmd()
self.assertEqual( self.assertEqual(
cmdparser.create_match("look at", "look at target", dummy, "look"), cmdparser.create_match("look at", "look at target", dummy, "look"),
("look at", " target", dummy, 7, 0.5, "look")) ("look at", " target", dummy, 7, 0.5, "look"),
)
@override_settings(CMD_IGNORE_PREFIXES="@&/+") @override_settings(CMD_IGNORE_PREFIXES="@&/+")
def test_build_matches(self): def test_build_matches(self):
@ -427,38 +452,38 @@ class TestCmdParser(TestCase):
# normal parsing # normal parsing
self.assertEqual( self.assertEqual(
cmdparser.build_matches("test1 rock", a_cmdset, include_prefixes=False), cmdparser.build_matches("test1 rock", a_cmdset, include_prefixes=False),
[("test1", " rock", bcmd, 5, 0.5, 'test1')] [("test1", " rock", bcmd, 5, 0.5, "test1")],
) )
# test prefix exclusion # test prefix exclusion
bcmd = [cmd for cmd in a_cmdset.commands if cmd.key == "another command"][0] bcmd = [cmd for cmd in a_cmdset.commands if cmd.key == "another command"][0]
self.assertEqual( self.assertEqual(
cmdparser.build_matches("@another command smiles to me ", cmdparser.build_matches(
a_cmdset, include_prefixes=False), "@another command smiles to me ", a_cmdset, include_prefixes=False
[("another command", " smiles to me ", bcmd, 15, 0.5, 'another command')] ),
) [("another command", " smiles to me ", bcmd, 15, 0.5, "another command")],
)
# test prefix exclusion on the cmd class # test prefix exclusion on the cmd class
bcmd = [cmd for cmd in a_cmdset.commands if cmd.key == "&the third command"][0] bcmd = [cmd for cmd in a_cmdset.commands if cmd.key == "&the third command"][0]
self.assertEqual( self.assertEqual(
cmdparser.build_matches("the third command", cmdparser.build_matches("the third command", a_cmdset, include_prefixes=False),
a_cmdset, include_prefixes=False), [("the third command", "", bcmd, 17, 1.0, "&the third command")],
[("the third command", "", bcmd, 17, 1.0, '&the third command')] )
)
@override_settings(SEARCH_MULTIMATCH_REGEX=r"(?P<number>[0-9]+)-(?P<name>.*)") @override_settings(SEARCH_MULTIMATCH_REGEX=r"(?P<number>[0-9]+)-(?P<name>.*)")
def test_num_prefixes(self): def test_num_prefixes(self):
self.assertEqual(cmdparser.try_num_prefixes("look me"), self.assertEqual(cmdparser.try_num_prefixes("look me"), (None, None))
(None, None)) self.assertEqual(cmdparser.try_num_prefixes("3-look me"), ("3", "look me"))
self.assertEqual(cmdparser.try_num_prefixes("3-look me"), self.assertEqual(cmdparser.try_num_prefixes("567-look me"), ("567", "look me"))
('3', "look me"))
self.assertEqual(cmdparser.try_num_prefixes("567-look me"),
('567', "look me"))
@override_settings(SEARCH_MULTIMATCH_REGEX=r"(?P<number>[0-9]+)-(?P<name>.*)", @override_settings(
CMD_IGNORE_PREFIXES="@&/+") SEARCH_MULTIMATCH_REGEX=r"(?P<number>[0-9]+)-(?P<name>.*)", CMD_IGNORE_PREFIXES="@&/+"
)
def test_cmdparser(self): def test_cmdparser(self):
a_cmdset = _CmdSetTest() a_cmdset = _CmdSetTest()
bcmd = [cmd for cmd in a_cmdset.commands if cmd.key == "test1"][0] bcmd = [cmd for cmd in a_cmdset.commands if cmd.key == "test1"][0]
self.assertEqual(cmdparser.cmdparser("test1hello", a_cmdset, None), self.assertEqual(
[("test1", "hello", bcmd, 5, 0.5, 'test1')]) cmdparser.cmdparser("test1hello", a_cmdset, None),
[("test1", "hello", bcmd, 5, 0.5, "test1")],
)

View file

@ -14,6 +14,7 @@ class ChannelAttributeInline(AttributeInline):
Inline display of Channel Attribute - experimental Inline display of Channel Attribute - experimental
""" """
model = ChannelDB.db_attributes.through model = ChannelDB.db_attributes.through
related_field = "channeldb" related_field = "channeldb"
@ -23,6 +24,7 @@ class ChannelTagInline(TagInline):
Inline display of Channel Tags - experimental Inline display of Channel Tags - experimental
""" """
model = ChannelDB.db_tags.through model = ChannelDB.db_tags.through
related_field = "channeldb" related_field = "channeldb"
@ -32,16 +34,26 @@ class MsgAdmin(admin.ModelAdmin):
Defines display for Msg objects Defines display for Msg objects
""" """
list_display = ('id', 'db_date_created', 'db_sender', 'db_receivers',
'db_channels', 'db_message', 'db_lock_storage') list_display = (
"id",
"db_date_created",
"db_sender",
"db_receivers",
"db_channels",
"db_message",
"db_lock_storage",
)
list_display_links = ("id",) list_display_links = ("id",)
ordering = ["db_date_created", 'db_sender', 'db_receivers', 'db_channels'] ordering = ["db_date_created", "db_sender", "db_receivers", "db_channels"]
#readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels'] # readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels']
search_fields = ['id', '^db_date_created', '^db_message'] search_fields = ["id", "^db_date_created", "^db_message"]
save_as = True save_as = True
save_on_top = True save_on_top = True
list_select_related = True list_select_related = True
#admin.site.register(Msg, MsgAdmin)
# admin.site.register(Msg, MsgAdmin)
class ChannelAdmin(admin.ModelAdmin): class ChannelAdmin(admin.ModelAdmin):
@ -49,17 +61,28 @@ class ChannelAdmin(admin.ModelAdmin):
Defines display for Channel objects Defines display for Channel objects
""" """
inlines = [ChannelTagInline, ChannelAttributeInline] inlines = [ChannelTagInline, ChannelAttributeInline]
list_display = ('id', 'db_key', 'db_lock_storage', "subscriptions") list_display = ("id", "db_key", "db_lock_storage", "subscriptions")
list_display_links = ("id", 'db_key') list_display_links = ("id", "db_key")
ordering = ["db_key"] ordering = ["db_key"]
search_fields = ['id', 'db_key', 'db_tags__db_key'] search_fields = ["id", "db_key", "db_tags__db_key"]
save_as = True save_as = True
save_on_top = True save_on_top = True
list_select_related = True list_select_related = True
raw_id_fields = ('db_object_subscriptions', 'db_account_subscriptions',) raw_id_fields = ("db_object_subscriptions", "db_account_subscriptions")
fieldsets = ( fieldsets = (
(None, {'fields': (('db_key',), 'db_lock_storage', 'db_account_subscriptions', 'db_object_subscriptions')}), (
None,
{
"fields": (
("db_key",),
"db_lock_storage",
"db_account_subscriptions",
"db_object_subscriptions",
)
},
),
) )
def subscriptions(self, obj): def subscriptions(self, obj):
@ -93,6 +116,7 @@ class ChannelAdmin(admin.ModelAdmin):
def response_add(self, request, obj, post_url_continue=None): def response_add(self, request, obj, post_url_continue=None):
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
return HttpResponseRedirect(reverse("admin:comms_channeldb_change", args=[obj.id])) return HttpResponseRedirect(reverse("admin:comms_channeldb_change", args=[obj.id]))

View file

@ -55,6 +55,7 @@ class ChannelCommand(command.Command):
{lower_channelkey}/history 30 {lower_channelkey}/history 30
""" """
# ^note that channeldesc and lower_channelkey will be filled # ^note that channeldesc and lower_channelkey will be filled
# automatically by ChannelHandler # automatically by ChannelHandler
@ -106,12 +107,12 @@ class ChannelCommand(command.Command):
string = _("You are not connected to channel '%s'.") string = _("You are not connected to channel '%s'.")
self.msg(string % channelkey) self.msg(string % channelkey)
return return
if not channel.access(caller, 'send'): if not channel.access(caller, "send"):
string = _("You are not permitted to send to channel '%s'.") string = _("You are not permitted to send to channel '%s'.")
self.msg(string % channelkey) self.msg(string % channelkey)
return return
if msg == "on": if msg == "on":
caller = caller if not hasattr(caller, 'account') else caller.account caller = caller if not hasattr(caller, "account") else caller.account
unmuted = channel.unmute(caller) unmuted = channel.unmute(caller)
if unmuted: if unmuted:
self.msg("You start listening to %s." % channel) self.msg("You start listening to %s." % channel)
@ -119,7 +120,7 @@ class ChannelCommand(command.Command):
self.msg("You were already listening to %s." % channel) self.msg("You were already listening to %s." % channel)
return return
if msg == "off": if msg == "off":
caller = caller if not hasattr(caller, 'account') else caller.account caller = caller if not hasattr(caller, "account") else caller.account
muted = channel.mute(caller) muted = channel.mute(caller)
if muted: if muted:
self.msg("You stop listening to %s." % channel) self.msg("You stop listening to %s." % channel)
@ -130,11 +131,14 @@ class ChannelCommand(command.Command):
# Try to view history # Try to view history
log_file = channel.attributes.get("log_file", default="channel_%s.log" % channel.key) log_file = channel.attributes.get("log_file", default="channel_%s.log" % channel.key)
def send_msg(lines): return self.msg("".join(line.split("[-]", 1)[1] def send_msg(lines):
if "[-]" in line else line for line in lines)) return self.msg(
"".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines)
)
tail_log_file(log_file, self.history_start, 20, callback=send_msg) tail_log_file(log_file, self.history_start, 20, callback=send_msg)
else: else:
caller = caller if not hasattr(caller, 'account') else caller.account caller = caller if not hasattr(caller, "account") else caller.account
if caller in channel.mutelist: if caller in channel.mutelist:
self.msg("You currently have %s muted." % channel) self.msg("You currently have %s muted." % channel)
return return
@ -216,16 +220,19 @@ class ChannelHandler(object):
locks="cmd:all();%s" % channel.locks, locks="cmd:all();%s" % channel.locks,
help_category="Channel names", help_category="Channel names",
obj=channel, obj=channel,
is_channel=True) is_channel=True,
)
# format the help entry # format the help entry
key = channel.key key = channel.key
cmd.__doc__ = cmd.__doc__.format(channelkey=key, cmd.__doc__ = cmd.__doc__.format(
lower_channelkey=key.strip().lower(), channelkey=key,
channeldesc=channel.attributes.get( lower_channelkey=key.strip().lower(),
"desc", default="").strip()) channeldesc=channel.attributes.get("desc", default="").strip(),
)
self._cached_channel_cmds[channel] = cmd self._cached_channel_cmds[channel] = cmd
self._cached_channels[key] = channel self._cached_channels[key] = channel
self._cached_cmdsets = {} self._cached_cmdsets = {}
add_channel = add # legacy alias add_channel = add # legacy alias
def remove(self, channel): def remove(self, channel):
@ -290,12 +297,15 @@ class ChannelHandler(object):
else: else:
# create a new cmdset holding all viable channels # create a new cmdset holding all viable channels
chan_cmdset = None chan_cmdset = None
chan_cmds = [channelcmd for channel, channelcmd in self._cached_channel_cmds.items() chan_cmds = [
if channel.subscriptions.has(source_object) and channelcmd
channelcmd.access(source_object, 'send')] for channel, channelcmd in self._cached_channel_cmds.items()
if channel.subscriptions.has(source_object)
and channelcmd.access(source_object, "send")
]
if chan_cmds: if chan_cmds:
chan_cmdset = cmdset.CmdSet() chan_cmdset = cmdset.CmdSet()
chan_cmdset.key = 'ChannelCmdSet' chan_cmdset.key = "ChannelCmdSet"
chan_cmdset.priority = 101 chan_cmdset.priority = 101
chan_cmdset.duplicates = True chan_cmdset.duplicates = True
for cmd in chan_cmds: for cmd in chan_cmds:

View file

@ -21,6 +21,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
create different types of communication channels. create different types of communication channels.
""" """
objects = ChannelManager() objects = ChannelManager()
def at_first_save(self): def at_first_save(self):
@ -105,7 +106,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
listening = [ob for ob in subs if ob.is_connected and ob not in muted] listening = [ob for ob in subs if ob.is_connected and ob not in muted]
if subs: if subs:
# display listening subscribers in bold # display listening subscribers in bold
string = ", ".join([account.key if account not in listening else "|w%s|n" % account.key for account in subs]) string = ", ".join(
[
account.key if account not in listening else "|w%s|n" % account.key
for account in subs
]
)
else: else:
string = "<None>" string = "<None>"
return string return string
@ -163,7 +169,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
# check access # check access
if not self.access(subscriber, 'listen'): if not self.access(subscriber, "listen"):
return False return False
# pre-join hook # pre-join hook
connect = self.pre_join_channel(subscriber) connect = self.pre_join_channel(subscriber)
@ -204,7 +210,14 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
self.post_leave_channel(subscriber) self.post_leave_channel(subscriber)
return True return True
def access(self, accessing_obj, access_type='listen', default=False, no_superuser_bypass=False, **kwargs): def access(
self,
accessing_obj,
access_type="listen",
default=False,
no_superuser_bypass=False,
**kwargs,
):
""" """
Determines if another object has permission to access. Determines if another object has permission to access.
@ -221,8 +234,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
return (bool): Result of lock check. return (bool): Result of lock check.
""" """
return self.locks.check(accessing_obj, access_type=access_type, return self.locks.check(
default=default, no_superuser_bypass=no_superuser_bypass) accessing_obj,
access_type=access_type,
default=default,
no_superuser_bypass=no_superuser_bypass,
)
@classmethod @classmethod
def create(cls, key, account=None, *args, **kwargs): def create(cls, key, account=None, *args, **kwargs):
@ -252,16 +269,18 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
errors = [] errors = []
obj = None obj = None
ip = kwargs.pop('ip', '') ip = kwargs.pop("ip", "")
try: try:
kwargs['desc'] = kwargs.pop('description', '') kwargs["desc"] = kwargs.pop("description", "")
kwargs['typeclass'] = kwargs.get('typeclass', cls) kwargs["typeclass"] = kwargs.get("typeclass", cls)
obj = create.create_channel(key, *args, **kwargs) obj = create.create_channel(key, *args, **kwargs)
# Record creator id and creation IP # Record creator id and creation IP
if ip: obj.db.creator_ip = ip if ip:
if account: obj.db.creator_id = account.id obj.db.creator_ip = ip
if account:
obj.db.creator_id = account.id
except Exception as exc: except Exception as exc:
errors.append("An error occurred while creating this '%s' object." % key) errors.append("An error occurred while creating this '%s' object." % key)
@ -278,10 +297,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
self.aliases.clear() self.aliases.clear()
super().delete() super().delete()
from evennia.comms.channelhandler import CHANNELHANDLER from evennia.comms.channelhandler import CHANNELHANDLER
CHANNELHANDLER.update() CHANNELHANDLER.update()
def message_transform(self, msgobj, emit=False, prefix=True, def message_transform(
sender_strings=None, external=False, **kwargs): self, msgobj, emit=False, prefix=True, sender_strings=None, external=False, **kwargs
):
""" """
Generates the formatted string sent to listeners on a channel. Generates the formatted string sent to listeners on a channel.
@ -333,16 +354,29 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
try: try:
# note our addition of the from_channel keyword here. This could be checked # note our addition of the from_channel keyword here. This could be checked
# by a custom account.msg() to treat channel-receives differently. # by a custom account.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: except AttributeError as e:
logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity)) logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity))
if msgobj.keep_log: if msgobj.keep_log:
# log to file # log to file
logger.log_file(msgobj.message, self.attributes.get("log_file") or "channel_%s.log" % self.key) logger.log_file(
msgobj.message, self.attributes.get("log_file") or "channel_%s.log" % self.key
)
def msg(self, msgobj, header=None, senders=None, sender_strings=None, def msg(
keep_log=None, online=False, emit=False, external=False): self,
msgobj,
header=None,
senders=None,
sender_strings=None,
keep_log=None,
online=False,
emit=False,
external=False,
):
""" """
Send the given message to all accounts connected to channel. Note that Send the given message to all accounts connected to channel. Note that
no permission-checking is done here; it is assumed to have been no permission-checking is done here; it is assumed to have been
@ -391,9 +425,9 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
msgobj = self.pre_send_message(msgobj) msgobj = self.pre_send_message(msgobj)
if not msgobj: if not msgobj:
return False return False
msgobj = self.message_transform(msgobj, emit=emit, msgobj = self.message_transform(
sender_strings=sender_strings, msgobj, emit=emit, sender_strings=sender_strings, external=external
external=external) )
self.distribute_message(msgobj, online=online) self.distribute_message(msgobj, online=online)
self.post_send_message(msgobj) self.post_send_message(msgobj)
return True return True
@ -427,7 +461,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
prefix (str): The created channel prefix. prefix (str): The created channel prefix.
""" """
return '' if emit else '[%s] ' % self.key return "" if emit else "[%s] " % self.key
def format_senders(self, senders=None, **kwargs): def format_senders(self, senders=None, **kwargs):
""" """
@ -448,8 +482,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
if not senders: if not senders:
return '' return ""
return ', '.join(senders) return ", ".join(senders)
def pose_transform(self, msgobj, sender_string, **kwargs): def pose_transform(self, msgobj, sender_string, **kwargs):
""" """
@ -472,16 +506,16 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
pose = False pose = False
message = msgobj.message message = msgobj.message
message_start = message.lstrip() message_start = message.lstrip()
if message_start.startswith((':', ';')): if message_start.startswith((":", ";")):
pose = True pose = True
message = message[1:] message = message[1:]
if not message.startswith((':', "'", ',')): if not message.startswith((":", "'", ",")):
if not message.startswith(' '): if not message.startswith(" "):
message = ' ' + message message = " " + message
if pose: if pose:
return '%s%s' % (sender_string, message) return "%s%s" % (sender_string, message)
else: else:
return '%s: %s' % (sender_string, message) return "%s: %s" % (sender_string, message)
def format_external(self, msgobj, senders, emit=False, **kwargs): def format_external(self, msgobj, senders, emit=False, **kwargs):
""" """
@ -503,7 +537,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
if emit or not senders: if emit or not senders:
return msgobj.message return msgobj.message
senders = ', '.join(senders) senders = ", ".join(senders)
return self.pose_transform(msgobj, senders) return self.pose_transform(msgobj, senders)
def format_message(self, msgobj, emit=False, **kwargs): def format_message(self, msgobj, emit=False, **kwargs):
@ -522,14 +556,14 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
# We don't want to count things like external sources as senders for # We don't want to count things like external sources as senders for
# the purpose of constructing the message string. # the purpose of constructing the message string.
senders = [sender for sender in msgobj.senders if hasattr(sender, 'key')] senders = [sender for sender in msgobj.senders if hasattr(sender, "key")]
if not senders: if not senders:
emit = True emit = True
if emit: if emit:
return msgobj.message return msgobj.message
else: else:
senders = [sender.key for sender in msgobj.senders] senders = [sender.key for sender in msgobj.senders]
senders = ', '.join(senders) senders = ", ".join(senders)
return self.pose_transform(msgobj, senders) return self.pose_transform(msgobj, senders)
def pre_join_channel(self, joiner, **kwargs): def pre_join_channel(self, joiner, **kwargs):
@ -643,8 +677,9 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
content_type = ContentType.objects.get_for_model(self.__class__) content_type = ContentType.objects.get_for_model(self.__class__)
return reverse("admin:%s_%s_change" % (content_type.app_label, return reverse(
content_type.model), args=(self.id,)) "admin:%s_%s_change" % (content_type.app_label, content_type.model), args=(self.id,)
)
@classmethod @classmethod
def web_get_create_url(cls): def web_get_create_url(cls):
@ -673,9 +708,9 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
try: try:
return reverse('%s-create' % slugify(cls._meta.verbose_name)) return reverse("%s-create" % slugify(cls._meta.verbose_name))
except: except:
return '#' return "#"
def web_get_detail_url(self): def web_get_detail_url(self):
""" """
@ -704,11 +739,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
try: try:
return reverse('%s-detail' % slugify(self._meta.verbose_name), return reverse(
kwargs={'slug': slugify(self.db_key)}) "%s-detail" % slugify(self._meta.verbose_name),
kwargs={"slug": slugify(self.db_key)},
)
except: except:
return '#' return "#"
def web_get_update_url(self): def web_get_update_url(self):
""" """
@ -737,10 +773,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
try: try:
return reverse('%s-update' % slugify(self._meta.verbose_name), return reverse(
kwargs={'slug': slugify(self.db_key)}) "%s-update" % slugify(self._meta.verbose_name),
kwargs={"slug": slugify(self.db_key)},
)
except: except:
return '#' return "#"
def web_get_delete_url(self): def web_get_delete_url(self):
""" """
@ -768,10 +806,12 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
""" """
try: try:
return reverse('%s-delete' % slugify(self._meta.verbose_name), return reverse(
kwargs={'slug': slugify(self.db_key)}) "%s-delete" % slugify(self._meta.verbose_name),
kwargs={"slug": slugify(self.db_key)},
)
except: except:
return '#' return "#"
# Used by Django Sites/Admin # Used by Django Sites/Admin
get_absolute_url = web_get_detail_url get_absolute_url = web_get_detail_url

View file

@ -6,7 +6,7 @@ Comm system components.
from django.db.models import Q from django.db.models import Q
from evennia.typeclasses.managers import (TypedObjectManager, TypeclassManager) from evennia.typeclasses.managers import TypedObjectManager, TypeclassManager
from evennia.utils import logger from evennia.utils import logger
_GA = object.__getattribute__ _GA = object.__getattribute__
@ -22,6 +22,7 @@ class CommError(Exception):
""" """
Raised by comm system, to allow feedback to player when caught. Raised by comm system, to allow feedback to player when caught.
""" """
pass pass
@ -29,6 +30,7 @@ class CommError(Exception):
# helper functions # helper functions
# #
def dbref(inp, reqhash=True): def dbref(inp, reqhash=True):
""" """
Valid forms of dbref (database reference number) are either a Valid forms of dbref (database reference number) are either a
@ -46,7 +48,7 @@ def dbref(inp, reqhash=True):
if reqhash and not (isinstance(inp, str) and inp.startswith("#")): if reqhash and not (isinstance(inp, str) and inp.startswith("#")):
return None return None
if isinstance(inp, str): if isinstance(inp, str):
inp = inp.lstrip('#') inp = inp.lstrip("#")
try: try:
if int(inp) < 0: if int(inp) < 0:
return None return None
@ -85,7 +87,7 @@ def identify_object(inp):
return inp, None return inp, None
def to_object(inp, objtype='account'): def to_object(inp, objtype="account"):
""" """
Locates the object related to the given accountname or channel key. Locates the object related to the given accountname or channel key.
If input was already the correct object, return it. If input was already the correct object, return it.
@ -101,28 +103,28 @@ def to_object(inp, objtype='account'):
obj, typ = identify_object(inp) obj, typ = identify_object(inp)
if typ == objtype: if typ == objtype:
return obj return obj
if objtype == 'account': if objtype == "account":
if typ == 'object': if typ == "object":
return obj.account return obj.account
if typ == 'string': if typ == "string":
return _AccountDB.objects.get(user_username__iexact=obj) return _AccountDB.objects.get(user_username__iexact=obj)
if typ == 'dbref': if typ == "dbref":
return _AccountDB.objects.get(id=obj) return _AccountDB.objects.get(id=obj)
logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp)))
raise CommError() raise CommError()
elif objtype == 'object': elif objtype == "object":
if typ == 'account': if typ == "account":
return obj.obj return obj.obj
if typ == 'string': if typ == "string":
return _ObjectDB.objects.get(db_key__iexact=obj) return _ObjectDB.objects.get(db_key__iexact=obj)
if typ == 'dbref': if typ == "dbref":
return _ObjectDB.objects.get(id=obj) return _ObjectDB.objects.get(id=obj)
logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp)))
raise CommError() raise CommError()
elif objtype == 'channel': elif objtype == "channel":
if typ == 'string': if typ == "string":
return _ChannelDB.objects.get(db_key__iexact=obj) return _ChannelDB.objects.get(db_key__iexact=obj)
if typ == 'dbref': if typ == "dbref":
return _ChannelDB.objects.get(id=obj) return _ChannelDB.objects.get(id=obj)
logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp))) logger.log_err("%s %s %s %s %s" % (objtype, inp, obj, typ, type(inp)))
raise CommError() raise CommError()
@ -134,6 +136,7 @@ def to_object(inp, objtype='account'):
# Msg manager # Msg manager
# #
class MsgManager(TypedObjectManager): class MsgManager(TypedObjectManager):
""" """
This MsgManager implements methods for searching and manipulating This MsgManager implements methods for searching and manipulating
@ -200,19 +203,25 @@ class MsgManager(TypedObjectManager):
obj, typ = identify_object(sender) obj, typ = identify_object(sender)
if exclude_channel_messages: if exclude_channel_messages:
# explicitly exclude channel recipients # explicitly exclude channel recipients
if typ == 'account': if typ == "account":
return list(self.filter(db_sender_accounts=obj, return list(
db_receivers_channels__isnull=True).exclude(db_hide_from_accounts=obj)) self.filter(db_sender_accounts=obj, db_receivers_channels__isnull=True).exclude(
elif typ == 'object': db_hide_from_accounts=obj
return list(self.filter(db_sender_objects=obj, )
db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj)) )
elif typ == "object":
return list(
self.filter(db_sender_objects=obj, db_receivers_channels__isnull=True).exclude(
db_hide_from_objects=obj
)
)
else: else:
raise CommError raise CommError
else: else:
# get everything, channel or not # get everything, channel or not
if typ == 'account': if typ == "account":
return list(self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj)) return list(self.filter(db_sender_accounts=obj).exclude(db_hide_from_accounts=obj))
elif typ == 'object': elif typ == "object":
return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj)) return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj))
else: else:
raise CommError raise CommError
@ -232,11 +241,11 @@ class MsgManager(TypedObjectManager):
""" """
obj, typ = identify_object(recipient) obj, typ = identify_object(recipient)
if typ == 'account': if typ == "account":
return list(self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj)) return list(self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj))
elif typ == 'object': elif typ == "object":
return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj)) return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj))
elif typ == 'channel': elif typ == "channel":
return list(self.filter(db_receivers_channels=obj).exclude(db_hide_from_channels=obj)) return list(self.filter(db_receivers_channels=obj).exclude(db_hide_from_channels=obj))
else: else:
raise CommError raise CommError
@ -287,20 +296,24 @@ class MsgManager(TypedObjectManager):
# filter by sender # filter by sender
sender, styp = identify_object(sender) sender, styp = identify_object(sender)
if styp == 'account': if styp == "account":
sender_restrict = Q(db_sender_accounts=sender) & ~Q(db_hide_from_accounts=sender) sender_restrict = Q(db_sender_accounts=sender) & ~Q(db_hide_from_accounts=sender)
elif styp == 'object': elif styp == "object":
sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender) sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender)
else: else:
sender_restrict = Q() sender_restrict = Q()
# filter by receiver # filter by receiver
receiver, rtyp = identify_object(receiver) receiver, rtyp = identify_object(receiver)
if rtyp == 'account': if rtyp == "account":
receiver_restrict = Q(db_receivers_accounts=receiver) & ~Q(db_hide_from_accounts=receiver) receiver_restrict = Q(db_receivers_accounts=receiver) & ~Q(
elif rtyp == 'object': db_hide_from_accounts=receiver
)
elif rtyp == "object":
receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver) receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver)
elif rtyp == 'channel': elif rtyp == "channel":
receiver_restrict = Q(db_receivers_channels=receiver) & ~Q(db_hide_from_channels=receiver) receiver_restrict = Q(db_receivers_channels=receiver) & ~Q(
db_hide_from_channels=receiver
)
else: else:
receiver_restrict = Q() receiver_restrict = Q()
# filter by full text # filter by full text
@ -310,6 +323,7 @@ class MsgManager(TypedObjectManager):
fulltext_restrict = Q() fulltext_restrict = Q()
# execute the query # execute the query
return list(self.filter(sender_restrict & receiver_restrict & fulltext_restrict)) return list(self.filter(sender_restrict & receiver_restrict & fulltext_restrict))
# back-compatibility alias # back-compatibility alias
message_search = search_message message_search = search_message
@ -318,6 +332,7 @@ class MsgManager(TypedObjectManager):
# Channel manager # Channel manager
# #
class ChannelDBManager(TypedObjectManager): class ChannelDBManager(TypedObjectManager):
""" """
This ChannelManager implements methods for searching and This ChannelManager implements methods for searching and
@ -361,9 +376,10 @@ class ChannelDBManager(TypedObjectManager):
return self.get(id=dbref) return self.get(id=dbref)
except self.model.DoesNotExist: except self.model.DoesNotExist:
pass pass
results = self.filter(Q(db_key__iexact=channelkey) | results = self.filter(
Q(db_tags__db_tagtype__iexact="alias", Q(db_key__iexact=channelkey)
db_tags__db_key__iexact=channelkey)).distinct() | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__iexact=channelkey)
).distinct()
return results[0] if results else None return results[0] if results else None
def get_subscriptions(self, subscriber): def get_subscriptions(self, subscriber):
@ -401,14 +417,17 @@ class ChannelDBManager(TypedObjectManager):
except self.model.DoesNotExist: except self.model.DoesNotExist:
pass pass
if exact: if exact:
channels = self.filter(Q(db_key__iexact=ostring) | channels = self.filter(
Q(db_tags__db_tagtype__iexact="alias", Q(db_key__iexact=ostring)
db_tags__db_key__iexact=ostring)).distinct() | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__iexact=ostring)
).distinct()
else: else:
channels = self.filter(Q(db_key__icontains=ostring) | channels = self.filter(
Q(db_tags__db_tagtype__iexact="alias", Q(db_key__icontains=ostring)
db_tags__db_key__icontains=ostring)).distinct() | Q(db_tags__db_tagtype__iexact="alias", db_tags__db_key__icontains=ostring)
).distinct()
return channels return channels
# back-compatibility alias # back-compatibility alias
channel_search = search_channel channel_search = search_channel
@ -417,4 +436,5 @@ class ChannelManager(ChannelDBManager, TypeclassManager):
""" """
Wrapper to group the typeclass manager to a consistent name. Wrapper to group the typeclass manager to a consistent name.
""" """
pass pass

View file

@ -6,39 +6,85 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = []
]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='ChannelDB', name="ChannelDB",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('db_key', models.CharField(max_length=255, verbose_name='key', db_index=True)), "id",
('db_typeclass_path', models.CharField(help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name='typeclass')), models.AutoField(
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name='creation date')), verbose_name="ID", serialize=False, auto_created=True, primary_key=True
('db_lock_storage', models.TextField(help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name='locks', blank=True)), ),
),
("db_key", models.CharField(max_length=255, verbose_name="key", db_index=True)),
(
"db_typeclass_path",
models.CharField(
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.",
max_length=255,
null=True,
verbose_name="typeclass",
),
),
(
"db_date_created",
models.DateTimeField(auto_now_add=True, verbose_name="creation date"),
),
(
"db_lock_storage",
models.TextField(
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.",
verbose_name="locks",
blank=True,
),
),
], ],
options={ options={"verbose_name": "Channel", "verbose_name_plural": "Channels"},
'verbose_name': 'Channel',
'verbose_name_plural': 'Channels',
},
bases=(models.Model,), bases=(models.Model,),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Msg', name="Msg",
fields=[ fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), (
('db_sender_external', models.CharField(help_text="identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).", max_length=255, null=True, verbose_name='external sender', db_index=True)), "id",
('db_header', models.TextField(null=True, verbose_name='header', blank=True)), models.AutoField(
('db_message', models.TextField(verbose_name='messsage')), verbose_name="ID", serialize=False, auto_created=True, primary_key=True
('db_date_sent', models.DateTimeField(auto_now_add=True, verbose_name='date sent', db_index=True)), ),
('db_lock_storage', models.TextField(help_text='access locks on this message.', verbose_name='locks', blank=True)), ),
('db_hide_from_channels', models.ManyToManyField(related_name='hide_from_channels_set', null=True, to='comms.ChannelDB')), (
"db_sender_external",
models.CharField(
help_text="identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).",
max_length=255,
null=True,
verbose_name="external sender",
db_index=True,
),
),
("db_header", models.TextField(null=True, verbose_name="header", blank=True)),
("db_message", models.TextField(verbose_name="messsage")),
(
"db_date_sent",
models.DateTimeField(
auto_now_add=True, verbose_name="date sent", db_index=True
),
),
(
"db_lock_storage",
models.TextField(
help_text="access locks on this message.", verbose_name="locks", blank=True
),
),
(
"db_hide_from_channels",
models.ManyToManyField(
related_name="hide_from_channels_set", null=True, to="comms.ChannelDB"
),
),
], ],
options={ options={"verbose_name": "Message"},
'verbose_name': 'Message',
},
bases=(models.Model,), bases=(models.Model,),
), ),
] ]

View file

@ -6,16 +6,15 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("objects", "0001_initial"), ("comms", "0001_initial")]
('objects', '0001_initial'),
('comms', '0001_initial'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_hide_from_objects', name="db_hide_from_objects",
field=models.ManyToManyField(related_name='hide_from_objects_set', null=True, to='objects.ObjectDB'), field=models.ManyToManyField(
related_name="hide_from_objects_set", null=True, to="objects.ObjectDB"
),
preserve_default=True, preserve_default=True,
), )
] ]

View file

@ -8,65 +8,108 @@ from django.conf import settings
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('objects', '0001_initial'), ("objects", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('typeclasses', '0001_initial'), ("typeclasses", "0001_initial"),
('comms', '0002_msg_db_hide_from_objects'), ("comms", "0002_msg_db_hide_from_objects"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_hide_from_accounts', name="db_hide_from_accounts",
field=models.ManyToManyField(related_name='hide_from_accounts_set', null=True, to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(
related_name="hide_from_accounts_set", null=True, to=settings.AUTH_USER_MODEL
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_receivers_channels', name="db_receivers_channels",
field=models.ManyToManyField(help_text='channel recievers', related_name='channel_set', null=True, to='comms.ChannelDB'), field=models.ManyToManyField(
help_text="channel recievers",
related_name="channel_set",
null=True,
to="comms.ChannelDB",
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_receivers_objects', name="db_receivers_objects",
field=models.ManyToManyField(help_text='object receivers', related_name='receiver_object_set', null=True, to='objects.ObjectDB'), field=models.ManyToManyField(
help_text="object receivers",
related_name="receiver_object_set",
null=True,
to="objects.ObjectDB",
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_receivers_accounts', name="db_receivers_accounts",
field=models.ManyToManyField(help_text='account receivers', related_name='receiver_account_set', null=True, to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(
help_text="account receivers",
related_name="receiver_account_set",
null=True,
to=settings.AUTH_USER_MODEL,
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_sender_objects', name="db_sender_objects",
field=models.ManyToManyField(related_name='sender_object_set', null=True, verbose_name='sender(object)', to='objects.ObjectDB', db_index=True), field=models.ManyToManyField(
related_name="sender_object_set",
null=True,
verbose_name="sender(object)",
to="objects.ObjectDB",
db_index=True,
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_sender_accounts', name="db_sender_accounts",
field=models.ManyToManyField(related_name='sender_account_set', null=True, verbose_name='sender(account)', to=settings.AUTH_USER_MODEL, db_index=True), field=models.ManyToManyField(
related_name="sender_account_set",
null=True,
verbose_name="sender(account)",
to=settings.AUTH_USER_MODEL,
db_index=True,
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='channeldb', model_name="channeldb",
name='db_attributes', name="db_attributes",
field=models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True), field=models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
null=True,
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='channeldb', model_name="channeldb",
name='db_subscriptions', name="db_subscriptions",
field=models.ManyToManyField(related_name='subscription_set', null=True, verbose_name='subscriptions', to=settings.AUTH_USER_MODEL, db_index=True), field=models.ManyToManyField(
related_name="subscription_set",
null=True,
verbose_name="subscriptions",
to=settings.AUTH_USER_MODEL,
db_index=True,
),
preserve_default=True, preserve_default=True,
), ),
migrations.AddField( migrations.AddField(
model_name='channeldb', model_name="channeldb",
name='db_tags', name="db_tags",
field=models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True), field=models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
null=True,
),
preserve_default=True, preserve_default=True,
), ),
] ]

View file

@ -13,10 +13,6 @@ def convert_defaults(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0003_auto_20140917_0756")]
('comms', '0003_auto_20140917_0756'),
]
operations = [ operations = [migrations.RunPython(convert_defaults)]
migrations.RunPython(convert_defaults),
]

View file

@ -17,10 +17,6 @@ def convert_channelnames(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0004_auto_20150118_1631")]
('comms', '0004_auto_20150118_1631'),
]
operations = [ operations = [migrations.RunPython(convert_channelnames)]
migrations.RunPython(convert_channelnames),
]

View file

@ -6,16 +6,19 @@ from django.db import models, migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("objects", "0004_auto_20150118_1622"), ("comms", "0005_auto_20150223_1517")]
('objects', '0004_auto_20150118_1622'),
('comms', '0005_auto_20150223_1517'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='channeldb', model_name="channeldb",
name='db_object_subscriptions', name="db_object_subscriptions",
field=models.ManyToManyField(related_name='object_subscription_set', null=True, verbose_name='subscriptions', to='objects.ObjectDB', db_index=True), field=models.ManyToManyField(
related_name="object_subscription_set",
null=True,
verbose_name="subscriptions",
to="objects.ObjectDB",
db_index=True,
),
preserve_default=True, preserve_default=True,
), )
] ]

View file

@ -7,14 +7,18 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('typeclasses', '0004_auto_20151101_1759'), ("typeclasses", "0004_auto_20151101_1759"),
('comms', '0006_channeldb_db_object_subscriptions'), ("comms", "0006_channeldb_db_object_subscriptions"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_tags', name="db_tags",
field=models.ManyToManyField(help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.', to='typeclasses.Tag', null=True), field=models.ManyToManyField(
), help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.",
to="typeclasses.Tag",
null=True,
),
)
] ]

View file

@ -7,18 +7,11 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0007_msg_db_tags")]
('comms', '0007_msg_db_tags'),
]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(name="msg", options={"verbose_name": "Msg"}),
name='msg',
options={'verbose_name': 'Msg'},
),
migrations.RenameField( migrations.RenameField(
model_name='msg', model_name="msg", old_name="db_date_sent", new_name="db_date_created"
old_name='db_date_sent',
new_name='db_date_created',
), ),
] ]

View file

@ -8,59 +8,110 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0008_auto_20160905_0902")]
('comms', '0008_auto_20160905_0902'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_hide_from_channels', name="db_hide_from_channels",
field=models.ManyToManyField(blank=True, null=True, related_name='hide_from_channels_set', to='comms.ChannelDB'), field=models.ManyToManyField(
blank=True, null=True, related_name="hide_from_channels_set", to="comms.ChannelDB"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_hide_from_objects', name="db_hide_from_objects",
field=models.ManyToManyField(blank=True, null=True, related_name='hide_from_objects_set', to='objects.ObjectDB'), field=models.ManyToManyField(
blank=True, null=True, related_name="hide_from_objects_set", to="objects.ObjectDB"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_hide_from_accounts', name="db_hide_from_accounts",
field=models.ManyToManyField(blank=True, null=True, related_name='hide_from_accounts_set', to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(
blank=True,
null=True,
related_name="hide_from_accounts_set",
to=settings.AUTH_USER_MODEL,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_channels', name="db_receivers_channels",
field=models.ManyToManyField(blank=True, help_text='channel recievers', null=True, related_name='channel_set', to='comms.ChannelDB'), field=models.ManyToManyField(
blank=True,
help_text="channel recievers",
null=True,
related_name="channel_set",
to="comms.ChannelDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_objects', name="db_receivers_objects",
field=models.ManyToManyField(blank=True, help_text='object receivers', null=True, related_name='receiver_object_set', to='objects.ObjectDB'), field=models.ManyToManyField(
blank=True,
help_text="object receivers",
null=True,
related_name="receiver_object_set",
to="objects.ObjectDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_accounts', name="db_receivers_accounts",
field=models.ManyToManyField(blank=True, help_text='account receivers', null=True, related_name='receiver_account_set', to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(
blank=True,
help_text="account receivers",
null=True,
related_name="receiver_account_set",
to=settings.AUTH_USER_MODEL,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_external', name="db_sender_external",
field=models.CharField(blank=True, db_index=True, help_text="identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).", max_length=255, null=True, verbose_name='external sender'), field=models.CharField(
blank=True,
db_index=True,
help_text="identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).",
max_length=255,
null=True,
verbose_name="external sender",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_objects', name="db_sender_objects",
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_object_set', to='objects.ObjectDB', verbose_name='sender(object)'), field=models.ManyToManyField(
blank=True,
db_index=True,
null=True,
related_name="sender_object_set",
to="objects.ObjectDB",
verbose_name="sender(object)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_accounts', name="db_sender_accounts",
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name='sender(account)'), field=models.ManyToManyField(
blank=True,
db_index=True,
null=True,
related_name="sender_account_set",
to=settings.AUTH_USER_MODEL,
verbose_name="sender(account)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_tags', name="db_tags",
field=models.ManyToManyField(blank=True, help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.', null=True, to='typeclasses.Tag'), field=models.ManyToManyField(
blank=True,
help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.",
null=True,
to="typeclasses.Tag",
),
), ),
] ]

View file

@ -8,19 +8,31 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0009_auto_20160921_1731")]
('comms', '0009_auto_20160921_1731'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_object_subscriptions', name="db_object_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name='subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
null=True,
related_name="object_subscription_set",
to="objects.ObjectDB",
verbose_name="subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_subscriptions', name="db_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='subscription_set', to=settings.AUTH_USER_MODEL, verbose_name='subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
null=True,
related_name="subscription_set",
to=settings.AUTH_USER_MODEL,
verbose_name="subscriptions",
),
), ),
] ]

View file

@ -7,20 +7,30 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("scripts", "0007_auto_20150403_2339"), ("comms", "0010_auto_20161206_1912")]
('scripts', '0007_auto_20150403_2339'),
('comms', '0010_auto_20161206_1912'),
]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_receivers_scripts', name="db_receivers_scripts",
field=models.ManyToManyField(blank=True, help_text='script_receivers', null=True, related_name='receiver_script_set', to='scripts.ScriptDB'), field=models.ManyToManyField(
blank=True,
help_text="script_receivers",
null=True,
related_name="receiver_script_set",
to="scripts.ScriptDB",
),
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_sender_scripts', name="db_sender_scripts",
field=models.ManyToManyField(blank=True, db_index=True, null=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name='sender(script)'), field=models.ManyToManyField(
blank=True,
db_index=True,
null=True,
related_name="sender_script_set",
to="scripts.ScriptDB",
verbose_name="sender(script)",
),
), ),
] ]

View file

@ -8,74 +8,127 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0010_auto_20161206_1912")]
('comms', '0010_auto_20161206_1912'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_attributes', name="db_attributes",
field=models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), field=models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_object_subscriptions', name="db_object_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name='subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="object_subscription_set",
to="objects.ObjectDB",
verbose_name="subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_subscriptions', name="db_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='subscription_set', to=settings.AUTH_USER_MODEL, verbose_name='subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="subscription_set",
to=settings.AUTH_USER_MODEL,
verbose_name="subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_tags', name="db_tags",
field=models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), field=models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_hide_from_channels', name="db_hide_from_channels",
field=models.ManyToManyField(blank=True, related_name='hide_from_channels_set', to='comms.ChannelDB'), field=models.ManyToManyField(
blank=True, related_name="hide_from_channels_set", to="comms.ChannelDB"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_hide_from_objects', name="db_hide_from_objects",
field=models.ManyToManyField(blank=True, related_name='hide_from_objects_set', to='objects.ObjectDB'), field=models.ManyToManyField(
blank=True, related_name="hide_from_objects_set", to="objects.ObjectDB"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_hide_from_accounts', name="db_hide_from_accounts",
field=models.ManyToManyField(blank=True, related_name='hide_from_accounts_set', to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(
blank=True, related_name="hide_from_accounts_set", to=settings.AUTH_USER_MODEL
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_channels', name="db_receivers_channels",
field=models.ManyToManyField(blank=True, help_text='channel recievers', related_name='channel_set', to='comms.ChannelDB'), field=models.ManyToManyField(
blank=True,
help_text="channel recievers",
related_name="channel_set",
to="comms.ChannelDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_objects', name="db_receivers_objects",
field=models.ManyToManyField(blank=True, help_text='object receivers', related_name='receiver_object_set', to='objects.ObjectDB'), field=models.ManyToManyField(
blank=True,
help_text="object receivers",
related_name="receiver_object_set",
to="objects.ObjectDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_accounts', name="db_receivers_accounts",
field=models.ManyToManyField(blank=True, help_text='account receivers', related_name='receiver_account_set', to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(
blank=True,
help_text="account receivers",
related_name="receiver_account_set",
to=settings.AUTH_USER_MODEL,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_objects', name="db_sender_objects",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_object_set', to='objects.ObjectDB', verbose_name='sender(object)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_object_set",
to="objects.ObjectDB",
verbose_name="sender(object)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_accounts', name="db_sender_accounts",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name='sender(account)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_account_set",
to=settings.AUTH_USER_MODEL,
verbose_name="sender(account)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_tags', name="db_tags",
field=models.ManyToManyField(blank=True, help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.', to='typeclasses.Tag'), field=models.ManyToManyField(
blank=True,
help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.",
to="typeclasses.Tag",
),
), ),
] ]

View file

@ -7,10 +7,6 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0011_auto_20170606_1731"), ("comms", "0011_auto_20170217_2039")]
('comms', '0011_auto_20170606_1731'),
('comms', '0011_auto_20170217_2039'),
]
operations = [ operations = []
]

View file

@ -13,72 +13,131 @@ def _table_exists(db_cursor, tablename):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('accounts', '0007_copy_player_to_account'), ("accounts", "0007_copy_player_to_account"),
('comms', '0012_merge_20170617_2017'), ("comms", "0012_merge_20170617_2017"),
] ]
db_cursor = connection.cursor() db_cursor = connection.cursor()
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='channeldb', model_name="channeldb",
name='db_account_subscriptions', name="db_account_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to='accounts.AccountDB', verbose_name='account subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="account_subscription_set",
to="accounts.AccountDB",
verbose_name="account subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_object_subscriptions', name="db_object_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name='object subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="object_subscription_set",
to="objects.ObjectDB",
verbose_name="object subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_scripts', name="db_receivers_scripts",
field=models.ManyToManyField(blank=True, help_text='script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'), field=models.ManyToManyField(
blank=True,
help_text="script_receivers",
related_name="receiver_script_set",
to="scripts.ScriptDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_scripts', name="db_sender_scripts",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name='sender(script)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_script_set",
to="scripts.ScriptDB",
verbose_name="sender(script)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_object_subscriptions', name="db_object_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name='object subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="object_subscription_set",
to="objects.ObjectDB",
verbose_name="object subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_scripts', name="db_receivers_scripts",
field=models.ManyToManyField(blank=True, help_text='script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'), field=models.ManyToManyField(
blank=True,
help_text="script_receivers",
related_name="receiver_script_set",
to="scripts.ScriptDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_scripts', name="db_sender_scripts",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name='sender(script)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_script_set",
to="scripts.ScriptDB",
verbose_name="sender(script)",
),
), ),
] ]
if _table_exists(db_cursor, 'comms_msg_db_hide_from_players'): if _table_exists(db_cursor, "comms_msg_db_hide_from_players"):
# OBS - this is run BEFORE migrations are run! # OBS - this is run BEFORE migrations are run!
# not a migration of an existing database # not a migration of an existing database
operations += [ operations += [
migrations.AddField( migrations.AddField(
model_name='channeldb', model_name="channeldb",
name='db_account_subscriptions', name="db_account_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to='accounts.AccountDB', verbose_name='account subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="account_subscription_set",
to="accounts.AccountDB",
verbose_name="account subscriptions",
),
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_hide_from_accounts', name="db_hide_from_accounts",
field=models.ManyToManyField(blank=True, related_name='hide_from_accounts_set', to='accounts.AccountDB'), field=models.ManyToManyField(
blank=True, related_name="hide_from_accounts_set", to="accounts.AccountDB"
),
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_receivers_accounts', name="db_receivers_accounts",
field=models.ManyToManyField(blank=True, help_text='account receivers', related_name='receiver_account_set', to='accounts.AccountDB'), field=models.ManyToManyField(
blank=True,
help_text="account receivers",
related_name="receiver_account_set",
to="accounts.AccountDB",
),
), ),
migrations.AddField( migrations.AddField(
model_name='msg', model_name="msg",
name='db_sender_accounts', name="db_sender_accounts",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to='accounts.AccountDB', verbose_name='sender(account)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_account_set",
to="accounts.AccountDB",
verbose_name="sender(account)",
),
), ),
] ]

View file

@ -8,15 +8,16 @@ from django.db import migrations
# the player->account transition. Now it will do nothing since players.PlayerDB # the player->account transition. Now it will do nothing since players.PlayerDB
# no longer exists. # no longer exists.
def forwards(apps, schema_editor): def forwards(apps, schema_editor):
try: try:
apps.get_model('players', 'PlayerDB') apps.get_model("players", "PlayerDB")
except LookupError: except LookupError:
return return
AccountDB = apps.get_model('accounts', 'AccountDB') AccountDB = apps.get_model("accounts", "AccountDB")
Msg = apps.get_model('comms', 'Msg') Msg = apps.get_model("comms", "Msg")
for msg in Msg.objects.all(): for msg in Msg.objects.all():
for player in msg.db_sender_players.all(): for player in msg.db_sender_players.all():
account = AccountDB.objects.get(id=player.id) account = AccountDB.objects.get(id=player.id)
@ -28,7 +29,7 @@ def forwards(apps, schema_editor):
account = AccountDB.objects.get(id=player.id) account = AccountDB.objects.get(id=player.id)
msg.db_hide_from_accounts.add(account) msg.db_hide_from_accounts.add(account)
ChannelDB = apps.get_model('comms', 'ChannelDB') ChannelDB = apps.get_model("comms", "ChannelDB")
for channel in ChannelDB.objects.all(): for channel in ChannelDB.objects.all():
for player in channel.db_subscriptions.all(): for player in channel.db_subscriptions.all():
account = AccountDB.objects.get(id=player.id) account = AccountDB.objects.get(id=player.id)
@ -37,10 +38,6 @@ def forwards(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0013_auto_20170705_1726")]
('comms', '0013_auto_20170705_1726'),
]
operations = [ operations = [migrations.RunPython(forwards)]
migrations.RunPython(forwards)
]

View file

@ -12,9 +12,7 @@ def _table_exists(db_cursor, tablename):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0014_auto_20170705_1736")]
('comms', '0014_auto_20170705_1736'),
]
db_cursor = connection.cursor() db_cursor = connection.cursor()
@ -24,15 +22,9 @@ class Migration(migrations.Migration):
else: else:
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(
model_name='channeldb', model_name="channeldb",
name='db_subscriptions', # this is now db_account_subscriptions name="db_subscriptions", # this is now db_account_subscriptions
),
migrations.RemoveField(
model_name='msg',
name='db_receivers_players',
),
migrations.RemoveField(
model_name='msg',
name='db_sender_players',
), ),
migrations.RemoveField(model_name="msg", name="db_receivers_players"),
migrations.RemoveField(model_name="msg", name="db_sender_players"),
] ]

View file

@ -7,18 +7,11 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0015_auto_20170706_2041")]
('comms', '0015_auto_20170706_2041'),
]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="channeldb", name="db_subscriptions"),
model_name='channeldb',
name='db_subscriptions',
),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg", name="db_message", field=models.TextField(verbose_name="message")
name='db_message',
field=models.TextField(verbose_name='message'),
), ),
] ]

View file

@ -7,114 +7,188 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [("comms", "0016_auto_20180925_1735")]
('comms', '0016_auto_20180925_1735'),
]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_account_subscriptions', name="db_account_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='account_subscription_set', to=settings.AUTH_USER_MODEL, verbose_name='account subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="account_subscription_set",
to=settings.AUTH_USER_MODEL,
verbose_name="account subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_attributes', name="db_attributes",
field=models.ManyToManyField(help_text='attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute'), field=models.ManyToManyField(
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
to="typeclasses.Attribute",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_date_created', name="db_date_created",
field=models.DateTimeField(auto_now_add=True, verbose_name='creation date'), field=models.DateTimeField(auto_now_add=True, verbose_name="creation date"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_key', name="db_key",
field=models.CharField(db_index=True, max_length=255, verbose_name='key'), field=models.CharField(db_index=True, max_length=255, verbose_name="key"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_lock_storage', name="db_lock_storage",
field=models.TextField(blank=True, help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name='locks'), field=models.TextField(
blank=True,
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.",
verbose_name="locks",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_object_subscriptions', name="db_object_subscriptions",
field=models.ManyToManyField(blank=True, db_index=True, related_name='object_subscription_set', to='objects.ObjectDB', verbose_name='object subscriptions'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="object_subscription_set",
to="objects.ObjectDB",
verbose_name="object subscriptions",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_tags', name="db_tags",
field=models.ManyToManyField(help_text='tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag'), field=models.ManyToManyField(
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
to="typeclasses.Tag",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='channeldb', model_name="channeldb",
name='db_typeclass_path', name="db_typeclass_path",
field=models.CharField(help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name='typeclass'), field=models.CharField(
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.",
max_length=255,
null=True,
verbose_name="typeclass",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_date_created', name="db_date_created",
field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='date sent'), field=models.DateTimeField(auto_now_add=True, db_index=True, verbose_name="date sent"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_header', name="db_header",
field=models.TextField(blank=True, null=True, verbose_name='header'), field=models.TextField(blank=True, null=True, verbose_name="header"),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_lock_storage', name="db_lock_storage",
field=models.TextField(blank=True, help_text='access locks on this message.', verbose_name='locks'), field=models.TextField(
blank=True, help_text="access locks on this message.", verbose_name="locks"
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg", name="db_message", field=models.TextField(verbose_name="message")
name='db_message',
field=models.TextField(verbose_name='message'),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_accounts', name="db_receivers_accounts",
field=models.ManyToManyField(blank=True, help_text='account receivers', related_name='receiver_account_set', to=settings.AUTH_USER_MODEL), field=models.ManyToManyField(
blank=True,
help_text="account receivers",
related_name="receiver_account_set",
to=settings.AUTH_USER_MODEL,
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_channels', name="db_receivers_channels",
field=models.ManyToManyField(blank=True, help_text='channel recievers', related_name='channel_set', to='comms.ChannelDB'), field=models.ManyToManyField(
blank=True,
help_text="channel recievers",
related_name="channel_set",
to="comms.ChannelDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_objects', name="db_receivers_objects",
field=models.ManyToManyField(blank=True, help_text='object receivers', related_name='receiver_object_set', to='objects.ObjectDB'), field=models.ManyToManyField(
blank=True,
help_text="object receivers",
related_name="receiver_object_set",
to="objects.ObjectDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_receivers_scripts', name="db_receivers_scripts",
field=models.ManyToManyField(blank=True, help_text='script_receivers', related_name='receiver_script_set', to='scripts.ScriptDB'), field=models.ManyToManyField(
blank=True,
help_text="script_receivers",
related_name="receiver_script_set",
to="scripts.ScriptDB",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_accounts', name="db_sender_accounts",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_account_set', to=settings.AUTH_USER_MODEL, verbose_name='sender(account)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_account_set",
to=settings.AUTH_USER_MODEL,
verbose_name="sender(account)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_external', name="db_sender_external",
field=models.CharField(blank=True, db_index=True, help_text="identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).", max_length=255, null=True, verbose_name='external sender'), field=models.CharField(
blank=True,
db_index=True,
help_text="identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).",
max_length=255,
null=True,
verbose_name="external sender",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_objects', name="db_sender_objects",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_object_set', to='objects.ObjectDB', verbose_name='sender(object)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_object_set",
to="objects.ObjectDB",
verbose_name="sender(object)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_sender_scripts', name="db_sender_scripts",
field=models.ManyToManyField(blank=True, db_index=True, related_name='sender_script_set', to='scripts.ScriptDB', verbose_name='sender(script)'), field=models.ManyToManyField(
blank=True,
db_index=True,
related_name="sender_script_set",
to="scripts.ScriptDB",
verbose_name="sender(script)",
),
), ),
migrations.AlterField( migrations.AlterField(
model_name='msg', model_name="msg",
name='db_tags', name="db_tags",
field=models.ManyToManyField(blank=True, help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.', to='typeclasses.Tag'), field=models.ManyToManyField(
blank=True,
help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.",
to="typeclasses.Tag",
),
), ),
] ]

View file

@ -37,11 +37,12 @@ _DA = object.__delattr__
_CHANNELHANDLER = None _CHANNELHANDLER = None
#------------------------------------------------------------ # ------------------------------------------------------------
# #
# Msg # Msg
# #
#------------------------------------------------------------ # ------------------------------------------------------------
class Msg(SharedMemoryModel): class Msg(SharedMemoryModel):
""" """
@ -68,6 +69,7 @@ class Msg(SharedMemoryModel):
- db_lock_storage: Internal storage of lock strings. - db_lock_storage: Internal storage of lock strings.
""" """
# #
# Msg database model setup # Msg database model setup
# #
@ -78,48 +80,94 @@ class Msg(SharedMemoryModel):
# Sender is either an account, an object or an external sender, like # Sender is either an account, an object or an external sender, like
# an IRC channel; normally there is only one, but if co-modification of # an IRC channel; normally there is only one, but if co-modification of
# a message is allowed, there may be more than one "author" # a message is allowed, there may be more than one "author"
db_sender_accounts = models.ManyToManyField("accounts.AccountDB", related_name='sender_account_set', db_sender_accounts = models.ManyToManyField(
blank=True, verbose_name='sender(account)', db_index=True) "accounts.AccountDB",
related_name="sender_account_set",
blank=True,
verbose_name="sender(account)",
db_index=True,
)
db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set', db_sender_objects = models.ManyToManyField(
blank=True, verbose_name='sender(object)', db_index=True) "objects.ObjectDB",
db_sender_scripts = models.ManyToManyField("scripts.ScriptDB", related_name='sender_script_set', related_name="sender_object_set",
blank=True, verbose_name='sender(script)', db_index=True) blank=True,
db_sender_external = models.CharField('external sender', max_length=255, null=True, blank=True, db_index=True, verbose_name="sender(object)",
help_text="identifier for external sender, for example a sender over an " db_index=True,
"IRC connection (i.e. someone who doesn't have an exixtence in-game).") )
db_sender_scripts = models.ManyToManyField(
"scripts.ScriptDB",
related_name="sender_script_set",
blank=True,
verbose_name="sender(script)",
db_index=True,
)
db_sender_external = models.CharField(
"external sender",
max_length=255,
null=True,
blank=True,
db_index=True,
help_text="identifier for external sender, for example a sender over an "
"IRC connection (i.e. someone who doesn't have an exixtence in-game).",
)
# The destination objects of this message. Stored as a # The destination objects of this message. Stored as a
# comma-separated string of object dbrefs. Can be defined along # comma-separated string of object dbrefs. Can be defined along
# with channels below. # with channels below.
db_receivers_accounts = models.ManyToManyField('accounts.AccountDB', related_name='receiver_account_set', db_receivers_accounts = models.ManyToManyField(
blank=True, help_text="account receivers") "accounts.AccountDB",
related_name="receiver_account_set",
blank=True,
help_text="account receivers",
)
db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set', db_receivers_objects = models.ManyToManyField(
blank=True, help_text="object receivers") "objects.ObjectDB",
db_receivers_scripts = models.ManyToManyField('scripts.ScriptDB', related_name='receiver_script_set', related_name="receiver_object_set",
blank=True, help_text="script_receivers") blank=True,
db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set', help_text="object receivers",
blank=True, help_text="channel recievers") )
db_receivers_scripts = models.ManyToManyField(
"scripts.ScriptDB",
related_name="receiver_script_set",
blank=True,
help_text="script_receivers",
)
db_receivers_channels = models.ManyToManyField(
"ChannelDB", related_name="channel_set", blank=True, help_text="channel recievers"
)
# header could be used for meta-info about the message if your system needs # header could be used for meta-info about the message if your system needs
# it, or as a separate store for the mail subject line maybe. # it, or as a separate store for the mail subject line maybe.
db_header = models.TextField('header', null=True, blank=True) db_header = models.TextField("header", null=True, blank=True)
# the message body itself # the message body itself
db_message = models.TextField('message') db_message = models.TextField("message")
# send date # send date
db_date_created = models.DateTimeField('date sent', editable=False, auto_now_add=True, db_index=True) db_date_created = models.DateTimeField(
"date sent", editable=False, auto_now_add=True, db_index=True
)
# lock storage # lock storage
db_lock_storage = models.TextField('locks', blank=True, db_lock_storage = models.TextField(
help_text='access locks on this message.') "locks", blank=True, help_text="access locks on this message."
)
# these can be used to filter/hide a given message from supplied objects/accounts/channels # these can be used to filter/hide a given message from supplied objects/accounts/channels
db_hide_from_accounts = models.ManyToManyField("accounts.AccountDB", related_name='hide_from_accounts_set', blank=True) db_hide_from_accounts = models.ManyToManyField(
"accounts.AccountDB", related_name="hide_from_accounts_set", blank=True
)
db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', blank=True) db_hide_from_objects = models.ManyToManyField(
db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', blank=True) "objects.ObjectDB", related_name="hide_from_objects_set", blank=True
)
db_hide_from_channels = models.ManyToManyField(
"ChannelDB", related_name="hide_from_channels_set", blank=True
)
db_tags = models.ManyToManyField(Tag, blank=True, db_tags = models.ManyToManyField(
help_text='tags on this message. Tags are simple string markers to identify, group and alias messages.') Tag,
blank=True,
help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.",
)
# Database manager # Database manager
objects = managers.MsgManager() objects = managers.MsgManager()
@ -150,15 +198,17 @@ class Msg(SharedMemoryModel):
# is the object in question). # is the object in question).
# sender property (wraps db_sender_*) # sender property (wraps db_sender_*)
#@property # @property
def __senders_get(self): def __senders_get(self):
"Getter. Allows for value = self.sender" "Getter. Allows for value = self.sender"
return list(self.db_sender_accounts.all()) + \ return (
list(self.db_sender_objects.all()) + \ list(self.db_sender_accounts.all())
list(self.db_sender_scripts.all()) + \ + list(self.db_sender_objects.all())
self.extra_senders + list(self.db_sender_scripts.all())
+ self.extra_senders
)
#@sender.setter # @sender.setter
def __senders_set(self, senders): def __senders_set(self, senders):
"Setter. Allows for self.sender = value" "Setter. Allows for self.sender = value"
for sender in make_iter(senders): for sender in make_iter(senders):
@ -179,7 +229,7 @@ class Msg(SharedMemoryModel):
elif clsname == "ScriptDB": elif clsname == "ScriptDB":
self.db_sender_scripts.add(sender) self.db_sender_scripts.add(sender)
#@sender.deleter # @sender.deleter
def __senders_del(self): def __senders_del(self):
"Deleter. Clears all senders" "Deleter. Clears all senders"
self.db_sender_accounts.clear() self.db_sender_accounts.clear()
@ -188,6 +238,7 @@ class Msg(SharedMemoryModel):
self.db_sender_external = "" self.db_sender_external = ""
self.extra_senders = [] self.extra_senders = []
self.save() self.save()
senders = property(__senders_get, __senders_set, __senders_del) senders = property(__senders_get, __senders_set, __senders_del)
def remove_sender(self, senders): def remove_sender(self, senders):
@ -215,18 +266,20 @@ class Msg(SharedMemoryModel):
self.db_sender_accounts.remove(sender) self.db_sender_accounts.remove(sender)
# receivers property # receivers property
#@property # @property
def __receivers_get(self): def __receivers_get(self):
""" """
Getter. Allows for value = self.receivers. Getter. Allows for value = self.receivers.
Returns four lists of receivers: accounts, objects, scripts and channels. Returns four lists of receivers: accounts, objects, scripts and channels.
""" """
return list(self.db_receivers_accounts.all()) + \ return (
list(self.db_receivers_objects.all()) + \ list(self.db_receivers_accounts.all())
list(self.db_receivers_scripts.all()) + \ + list(self.db_receivers_objects.all())
list(self.db_receivers_channels.all()) + list(self.db_receivers_scripts.all())
+ list(self.db_receivers_channels.all())
)
#@receivers.setter # @receivers.setter
def __receivers_set(self, receivers): def __receivers_set(self, receivers):
""" """
Setter. Allows for self.receivers = value. Setter. Allows for self.receivers = value.
@ -247,7 +300,7 @@ class Msg(SharedMemoryModel):
elif clsname == "ChannelDB": elif clsname == "ChannelDB":
self.db_receivers_channels.add(receiver) self.db_receivers_channels.add(receiver)
#@receivers.deleter # @receivers.deleter
def __receivers_del(self): def __receivers_del(self):
"Deleter. Clears all receivers" "Deleter. Clears all receivers"
self.db_receivers_accounts.clear() self.db_receivers_accounts.clear()
@ -255,6 +308,7 @@ class Msg(SharedMemoryModel):
self.db_receivers_scripts.clear() self.db_receivers_scripts.clear()
self.db_receivers_channels.clear() self.db_receivers_channels.clear()
self.save() self.save()
receivers = property(__receivers_get, __receivers_set, __receivers_del) receivers = property(__receivers_get, __receivers_set, __receivers_del)
def remove_receiver(self, receivers): def remove_receiver(self, receivers):
@ -281,12 +335,12 @@ class Msg(SharedMemoryModel):
self.db_receivers_channels.remove(receiver) self.db_receivers_channels.remove(receiver)
# channels property # channels property
#@property # @property
def __channels_get(self): def __channels_get(self):
"Getter. Allows for value = self.channels. Returns a list of channels." "Getter. Allows for value = self.channels. Returns a list of channels."
return self.db_receivers_channels.all() return self.db_receivers_channels.all()
#@channels.setter # @channels.setter
def __channels_set(self, value): def __channels_set(self, value):
""" """
Setter. Allows for self.channels = value. Setter. Allows for self.channels = value.
@ -295,11 +349,12 @@ class Msg(SharedMemoryModel):
for val in (v for v in make_iter(value) if v): for val in (v for v in make_iter(value) if v):
self.db_receivers_channels.add(val) self.db_receivers_channels.add(val)
#@channels.deleter # @channels.deleter
def __channels_del(self): def __channels_del(self):
"Deleter. Allows for del self.channels" "Deleter. Allows for del self.channels"
self.db_receivers_channels.clear() self.db_receivers_channels.clear()
self.save() self.save()
channels = property(__channels_get, __channels_set, __channels_del) channels = property(__channels_get, __channels_set, __channels_del)
def __hide_from_get(self): def __hide_from_get(self):
@ -307,9 +362,13 @@ class Msg(SharedMemoryModel):
Getter. Allows for value = self.hide_from. Getter. Allows for value = self.hide_from.
Returns 3 lists of accounts, objects and channels Returns 3 lists of accounts, objects and channels
""" """
return self.db_hide_from_accounts.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all() return (
self.db_hide_from_accounts.all(),
self.db_hide_from_objects.all(),
self.db_hide_from_channels.all(),
)
#@hide_from_sender.setter # @hide_from_sender.setter
def __hide_from_set(self, hiders): def __hide_from_set(self, hiders):
"Setter. Allows for self.hide_from = value. Will append to hiders" "Setter. Allows for self.hide_from = value. Will append to hiders"
for hider in make_iter(hiders): for hider in make_iter(hiders):
@ -325,13 +384,14 @@ class Msg(SharedMemoryModel):
elif clsname == "ChannelDB": elif clsname == "ChannelDB":
self.db_hide_from_channels.add(hider.__dbclass__) self.db_hide_from_channels.add(hider.__dbclass__)
#@hide_from_sender.deleter # @hide_from_sender.deleter
def __hide_from_del(self): def __hide_from_del(self):
"Deleter. Allows for del self.hide_from_senders" "Deleter. Allows for del self.hide_from_senders"
self.db_hide_from_accounts.clear() self.db_hide_from_accounts.clear()
self.db_hide_from_objects.clear() self.db_hide_from_objects.clear()
self.db_hide_from_channels.clear() self.db_hide_from_channels.clear()
self.save() self.save()
hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del) hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del)
# #
@ -341,10 +401,12 @@ class Msg(SharedMemoryModel):
def __str__(self): def __str__(self):
"This handles what is shown when e.g. printing the message" "This handles what is shown when e.g. printing the message"
senders = ",".join(obj.key for obj in self.senders) senders = ",".join(obj.key for obj in self.senders)
receivers = ",".join(["[%s]" % obj.key for obj in self.channels] + [obj.key for obj in self.receivers]) receivers = ",".join(
["[%s]" % obj.key for obj in self.channels] + [obj.key for obj in self.receivers]
)
return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40)) return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40))
def access(self, accessing_obj, access_type='read', default=False): def access(self, accessing_obj, access_type="read", default=False):
""" """
Checks lock access. Checks lock access.
@ -357,14 +419,14 @@ class Msg(SharedMemoryModel):
result (bool): If access was granted or not. result (bool): If access was granted or not.
""" """
return self.locks.check(accessing_obj, return self.locks.check(accessing_obj, access_type=access_type, default=default)
access_type=access_type, default=default)
#------------------------------------------------------------
# ------------------------------------------------------------
# #
# TempMsg # TempMsg
# #
#------------------------------------------------------------ # ------------------------------------------------------------
class TempMsg(object): class TempMsg(object):
@ -375,7 +437,17 @@ class TempMsg(object):
""" """
def __init__(self, senders=None, receivers=None, channels=None, message="", header="", type="", lockstring="", hide_from=None): def __init__(
self,
senders=None,
receivers=None,
channels=None,
message="",
header="",
type="",
lockstring="",
hide_from=None,
):
""" """
Creates the temp message. Creates the temp message.
@ -409,7 +481,9 @@ class TempMsg(object):
This handles what is shown when e.g. printing the message. This handles what is shown when e.g. printing the message.
""" """
senders = ",".join(obj.key for obj in self.senders) senders = ",".join(obj.key for obj in self.senders)
receivers = ",".join(["[%s]" % obj.key for obj in self.channels] + [obj.key for obj in self.receivers]) receivers = ",".join(
["[%s]" % obj.key for obj in self.channels] + [obj.key for obj in self.receivers]
)
return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40)) return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40))
def remove_sender(self, sender): def remove_sender(self, sender):
@ -440,7 +514,7 @@ class TempMsg(object):
except ValueError: except ValueError:
pass # nothing to remove pass # nothing to remove
def access(self, accessing_obj, access_type='read', default=False): def access(self, accessing_obj, access_type="read", default=False):
""" """
Checks lock access. Checks lock access.
@ -453,15 +527,15 @@ class TempMsg(object):
result (bool): If access was granted or not. result (bool): If access was granted or not.
""" """
return self.locks.check(accessing_obj, return self.locks.check(accessing_obj, access_type=access_type, default=default)
access_type=access_type, default=default)
#------------------------------------------------------------ # ------------------------------------------------------------
# #
# Channel # Channel
# #
#------------------------------------------------------------ # ------------------------------------------------------------
class SubscriptionHandler(object): class SubscriptionHandler(object):
""" """
@ -482,10 +556,18 @@ class SubscriptionHandler(object):
self._cache = None self._cache = None
def _recache(self): def _recache(self):
self._cache = {account: True for account in self.obj.db_account_subscriptions.all() self._cache = {
if hasattr(account, 'pk') and account.pk} account: True
self._cache.update({obj: True for obj in self.obj.db_object_subscriptions.all() for account in self.obj.db_account_subscriptions.all()
if hasattr(obj, 'pk') and obj.pk}) if hasattr(account, "pk") and account.pk
}
self._cache.update(
{
obj: True
for obj in self.obj.db_object_subscriptions.all()
if hasattr(obj, "pk") and obj.pk
}
)
def has(self, entity): def has(self, entity):
""" """
@ -568,6 +650,7 @@ class SubscriptionHandler(object):
if self._cache is None: if self._cache is None:
self._recache() self._recache()
return self._cache return self._cache
get = all # alias get = all # alias
def online(self): def online(self):
@ -581,8 +664,9 @@ class SubscriptionHandler(object):
recache_needed = False recache_needed = False
for obj in self.all(): for obj in self.all():
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
try: try:
if hasattr(obj, 'account') and obj.account: if hasattr(obj, "account") and obj.account:
obj = obj.account obj = obj.account
if not obj.is_connected: if not obj.is_connected:
continue continue
@ -617,11 +701,22 @@ class ChannelDB(TypedObject):
- db_object_subscriptions: The Object subscriptions. - db_object_subscriptions: The Object subscriptions.
""" """
db_account_subscriptions = models.ManyToManyField("accounts.AccountDB",
related_name="account_subscription_set", blank=True, verbose_name='account subscriptions', db_index=True)
db_object_subscriptions = models.ManyToManyField("objects.ObjectDB", db_account_subscriptions = models.ManyToManyField(
related_name="object_subscription_set", blank=True, verbose_name='object subscriptions', db_index=True) "accounts.AccountDB",
related_name="account_subscription_set",
blank=True,
verbose_name="account subscriptions",
db_index=True,
)
db_object_subscriptions = models.ManyToManyField(
"objects.ObjectDB",
related_name="object_subscription_set",
blank=True,
verbose_name="object subscriptions",
db_index=True,
)
# Database manager # Database manager
objects = managers.ChannelDBManager() objects = managers.ChannelDBManager()

View file

@ -1,13 +1,12 @@
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest
from evennia import DefaultChannel from evennia import DefaultChannel
class ObjectCreationTest(EvenniaTest): class ObjectCreationTest(EvenniaTest):
def test_channel_create(self): def test_channel_create(self):
description = "A place to talk about coffee." description = "A place to talk about coffee."
obj, errors = DefaultChannel.create('coffeetalk', description=description) obj, errors = DefaultChannel.create("coffeetalk", description=description)
self.assertTrue(obj, errors) self.assertTrue(obj, errors)
self.assertFalse(errors, errors) self.assertFalse(errors, errors)
self.assertEqual(description, obj.db.desc) self.assertEqual(description, obj.db.desc)

View file

@ -13,8 +13,8 @@ See README.md for more info.
# but even so, you will get clashes when both using the tutorialworld and your # but even so, you will get clashes when both using the tutorialworld and your
# own code, so somthing needs to be done here. See issue #766. /Griatch # own code, so somthing needs to be done here. See issue #766. /Griatch
#import evennia # import evennia
# evennia._init() # evennia._init()
#import barter, dice, extended_room, menu_login, talking_npc # import barter, dice, extended_room, menu_login, talking_npc
#import chargen, email_login, gendersub, menusystem, slow_exit # import chargen, email_login, gendersub, menusystem, slow_exit
#import tutorial_world, tutorial_examples # import tutorial_world, tutorial_examples

View file

@ -386,6 +386,7 @@ class TradeHandler(object):
# trading commands (will go into CmdsetTrade, initialized by the # trading commands (will go into CmdsetTrade, initialized by the
# CmdTrade command further down). # CmdTrade command further down).
class CmdTradeBase(Command): class CmdTradeBase(Command):
""" """
Base command for Trade commands to inherit from. Implements the Base command for Trade commands to inherit from. Implements the
@ -409,7 +410,7 @@ class CmdTradeBase(Command):
self.emote = "" self.emote = ""
self.str_caller = "Your trade action: %s" self.str_caller = "Your trade action: %s"
self.str_other = "%s:s trade action: " % self.caller.key + "%s" self.str_other = "%s:s trade action: " % self.caller.key + "%s"
if ':' in self.args: if ":" in self.args:
self.args, self.emote = [part.strip() for part in self.args.rsplit(":", 1)] self.args, self.emote = [part.strip() for part in self.args.rsplit(":", 1)]
self.str_caller = 'You say, "' + self.emote + '"\n [%s]' self.str_caller = 'You say, "' + self.emote + '"\n [%s]'
if self.caller.has_account: if self.caller.has_account:
@ -420,6 +421,7 @@ class CmdTradeBase(Command):
# trade help # trade help
class CmdTradeHelp(CmdTradeBase): class CmdTradeHelp(CmdTradeBase):
""" """
help command for the trade system. help command for the trade system.
@ -429,6 +431,7 @@ class CmdTradeHelp(CmdTradeBase):
Displays help for the trade commands. Displays help for the trade commands.
""" """
key = "trade help" key = "trade help"
locks = "cmd:all()" locks = "cmd:all()"
help_category = "Trade" help_category = "Trade"
@ -465,6 +468,7 @@ class CmdTradeHelp(CmdTradeBase):
# offer # offer
class CmdOffer(CmdTradeBase): class CmdOffer(CmdTradeBase):
""" """
offer one or more items in trade. offer one or more items in trade.
@ -475,6 +479,7 @@ class CmdOffer(CmdTradeBase):
Offer objects in trade. This will replace the currently Offer objects in trade. This will replace the currently
standing offer. standing offer.
""" """
key = "offer" key = "offer"
locks = "cmd:all()" locks = "cmd:all()"
help_category = "Trading" help_category = "Trading"
@ -491,7 +496,7 @@ class CmdOffer(CmdTradeBase):
return return
# gather all offers # gather all offers
offers = [part.strip() for part in self.args.split(',')] offers = [part.strip() for part in self.args.split(",")]
offerobjs = [] offerobjs = []
for offername in offers: for offername in offers:
obj = caller.search(offername) obj = caller.search(offername)
@ -502,7 +507,10 @@ class CmdOffer(CmdTradeBase):
# output # output
if len(offerobjs) > 1: if len(offerobjs) > 1:
objnames = ", ".join("|w%s|n" % obj.key for obj in offerobjs[:-1]) + " and |w%s|n" % offerobjs[-1].key objnames = (
", ".join("|w%s|n" % obj.key for obj in offerobjs[:-1])
+ " and |w%s|n" % offerobjs[-1].key
)
else: else:
objnames = "|w%s|n" % offerobjs[0].key objnames = "|w%s|n" % offerobjs[0].key
@ -512,6 +520,7 @@ class CmdOffer(CmdTradeBase):
# accept # accept
class CmdAccept(CmdTradeBase): class CmdAccept(CmdTradeBase):
""" """
accept the standing offer accept the standing offer
@ -525,6 +534,7 @@ class CmdAccept(CmdTradeBase):
your mind as long as the other party has not yet accepted. You can inspect your mind as long as the other party has not yet accepted. You can inspect
the current offer using the 'offers' command. the current offer using the 'offers' command.
""" """
key = "accept" key = "accept"
aliases = ["agree"] aliases = ["agree"]
locks = "cmd:all()" locks = "cmd:all()"
@ -538,17 +548,31 @@ class CmdAccept(CmdTradeBase):
return return
if self.tradehandler.accept(self.caller): if self.tradehandler.accept(self.caller):
# deal finished. Trade ended and cleaned. # deal finished. Trade ended and cleaned.
caller.msg(self.str_caller % "You |gaccept|n the deal. |gDeal is made and goods changed hands.|n") caller.msg(
self.msg_other(caller, self.str_other % "%s |gaccepts|n the deal." self.str_caller
" |gDeal is made and goods changed hands.|n" % caller.key) % "You |gaccept|n the deal. |gDeal is made and goods changed hands.|n"
)
self.msg_other(
caller,
self.str_other % "%s |gaccepts|n the deal."
" |gDeal is made and goods changed hands.|n" % caller.key,
)
else: else:
# a one-sided accept. # a one-sided accept.
caller.msg(self.str_caller % "You |Gaccept|n the offer. %s must now also accept." % self.other.key) caller.msg(
self.msg_other(caller, self.str_other % "%s |Gaccepts|n the offer. You must now also accept." % caller.key) self.str_caller
% "You |Gaccept|n the offer. %s must now also accept."
% self.other.key
)
self.msg_other(
caller,
self.str_other % "%s |Gaccepts|n the offer. You must now also accept." % caller.key,
)
# decline # decline
class CmdDecline(CmdTradeBase): class CmdDecline(CmdTradeBase):
""" """
decline the standing offer decline the standing offer
@ -561,6 +585,7 @@ class CmdDecline(CmdTradeBase):
has not yet accepted the deal. Also, changing the offer will automatically has not yet accepted the deal. Also, changing the offer will automatically
decline the old offer. decline the old offer.
""" """
key = "decline" key = "decline"
locks = "cmd:all()" locks = "cmd:all()"
help_category = "Trading" help_category = "Trading"
@ -578,8 +603,12 @@ class CmdDecline(CmdTradeBase):
if self.tradehandler.decline(self.caller): if self.tradehandler.decline(self.caller):
# changed a previous accept # changed a previous accept
caller.msg(self.str_caller % "You change your mind, |Rdeclining|n the current offer.") caller.msg(self.str_caller % "You change your mind, |Rdeclining|n the current offer.")
self.msg_other(caller, self.str_other self.msg_other(
% "%s changes their mind, |Rdeclining|n the current offer." % caller.key) caller,
self.str_other
% "%s changes their mind, |Rdeclining|n the current offer."
% caller.key,
)
else: else:
# no acceptance to change # no acceptance to change
caller.msg(self.str_caller % "You |Rdecline|n the current offer.") caller.msg(self.str_caller % "You |Rdecline|n the current offer.")
@ -593,6 +622,7 @@ class CmdDecline(CmdTradeBase):
# magical properties, ammo requirements or whatnot), then you need to add this # magical properties, ammo requirements or whatnot), then you need to add this
# here. # here.
class CmdEvaluate(CmdTradeBase): class CmdEvaluate(CmdTradeBase):
""" """
evaluate objects on offer evaluate objects on offer
@ -603,6 +633,7 @@ class CmdEvaluate(CmdTradeBase):
This allows you to examine any object currently on offer, to This allows you to examine any object currently on offer, to
determine if it's worth your while. determine if it's worth your while.
""" """
key = "evaluate" key = "evaluate"
aliases = ["eval"] aliases = ["eval"]
locks = "cmd:all()" locks = "cmd:all()"
@ -632,6 +663,7 @@ class CmdEvaluate(CmdTradeBase):
# status # status
class CmdStatus(CmdTradeBase): class CmdStatus(CmdTradeBase):
""" """
show a list of the current deal show a list of the current deal
@ -646,6 +678,7 @@ class CmdStatus(CmdTradeBase):
change your deal. You might also want to use 'say', 'emote' etc to change your deal. You might also want to use 'say', 'emote' etc to
try to influence the other part in the deal. try to influence the other part in the deal.
""" """
key = "status" key = "status"
aliases = ["offers", "deal"] aliases = ["offers", "deal"]
locks = "cmd:all()" locks = "cmd:all()"
@ -669,19 +702,27 @@ class CmdStatus(CmdTradeBase):
if not part_b_offerlist: if not part_b_offerlist:
part_b_offerlist = "\n <nothing>" part_b_offerlist = "\n <nothing>"
string = "|gOffered by %s:|n%s\n|yOffered by %s:|n%s" % (self.part_a.key, string = "|gOffered by %s:|n%s\n|yOffered by %s:|n%s" % (
"".join(part_a_offerlist), self.part_a.key,
self.part_b.key, "".join(part_a_offerlist),
"".join(part_b_offerlist)) self.part_b.key,
"".join(part_b_offerlist),
)
accept_a = self.tradehandler.part_a_accepted and "|gYes|n" or "|rNo|n" accept_a = self.tradehandler.part_a_accepted and "|gYes|n" or "|rNo|n"
accept_b = self.tradehandler.part_b_accepted and "|gYes|n" or "|rNo|n" accept_b = self.tradehandler.part_b_accepted and "|gYes|n" or "|rNo|n"
string += "\n\n%s agreed: %s, %s agreed: %s" % (self.part_a.key, accept_a, self.part_b.key, accept_b) string += "\n\n%s agreed: %s, %s agreed: %s" % (
self.part_a.key,
accept_a,
self.part_b.key,
accept_b,
)
string += "\n Use 'offer', 'eval' and 'accept'/'decline' to trade. See also 'trade help'." string += "\n Use 'offer', 'eval' and 'accept'/'decline' to trade. See also 'trade help'."
caller.msg(string) caller.msg(string)
# finish # finish
class CmdFinish(CmdTradeBase): class CmdFinish(CmdTradeBase):
""" """
end the trade prematurely end the trade prematurely
@ -693,6 +734,7 @@ class CmdFinish(CmdTradeBase):
This ends the trade prematurely. No trade will take place. This ends the trade prematurely. No trade will take place.
""" """
key = "end trade" key = "end trade"
aliases = "finish trade" aliases = "finish trade"
locks = "cmd:all()" locks = "cmd:all()"
@ -703,16 +745,20 @@ class CmdFinish(CmdTradeBase):
caller = self.caller caller = self.caller
self.tradehandler.finish(force=True) self.tradehandler.finish(force=True)
caller.msg(self.str_caller % "You |raborted|n trade. No deal was made.") caller.msg(self.str_caller % "You |raborted|n trade. No deal was made.")
self.msg_other(caller, self.str_other % "%s |raborted|n trade. No deal was made." % caller.key) self.msg_other(
caller, self.str_other % "%s |raborted|n trade. No deal was made." % caller.key
)
# custom Trading cmdset # custom Trading cmdset
class CmdsetTrade(CmdSet): class CmdsetTrade(CmdSet):
""" """
This cmdset is added when trade is initated. It is handled by the This cmdset is added when trade is initated. It is handled by the
trade event handler. trade event handler.
""" """
key = "cmdset_trade" key = "cmdset_trade"
def at_cmdset_creation(self): def at_cmdset_creation(self):
@ -729,6 +775,7 @@ class CmdsetTrade(CmdSet):
# access command - once both have given this, this will create the # access command - once both have given this, this will create the
# trading cmdset to start trade. # trading cmdset to start trade.
class CmdTrade(Command): class CmdTrade(Command):
""" """
Initiate trade with another party Initiate trade with another party
@ -745,6 +792,7 @@ class CmdTrade(Command):
optional say part works like the say command and allows you to add optional say part works like the say command and allows you to add
info to your choice. info to your choice.
""" """
key = "trade" key = "trade"
aliases = ["barter"] aliases = ["barter"]
locks = "cmd:all()" locks = "cmd:all()"
@ -764,7 +812,7 @@ class CmdTrade(Command):
# handle the emote manually here # handle the emote manually here
selfemote = "" selfemote = ""
theiremote = "" theiremote = ""
if ':' in self.args: if ":" in self.args:
self.args, emote = [part.strip() for part in self.args.rsplit(":", 1)] self.args, emote = [part.strip() for part in self.args.rsplit(":", 1)]
selfemote = 'You say, "%s"\n ' % emote selfemote = 'You say, "%s"\n ' % emote
if self.caller.has_account: if self.caller.has_account:
@ -776,12 +824,12 @@ class CmdTrade(Command):
# might not match the actual name in tradehandler (in the case of # might not match the actual name in tradehandler (in the case of
# using this command to accept/decline a trade invitation). # using this command to accept/decline a trade invitation).
part_a = self.caller part_a = self.caller
accept = 'accept' in self.args accept = "accept" in self.args
decline = 'decline' in self.args decline = "decline" in self.args
if accept: if accept:
part_b = self.args.rstrip('accept').strip() part_b = self.args.rstrip("accept").strip()
elif decline: elif decline:
part_b = self.args.rstrip('decline').strip() part_b = self.args.rstrip("decline").strip()
else: else:
part_b = self.args part_b = self.args
part_b = self.caller.search(part_b) part_b = self.caller.search(part_b)
@ -824,7 +872,9 @@ class CmdTrade(Command):
# accept a trade proposal from part_b (so roles are reversed) # accept a trade proposal from part_b (so roles are reversed)
if part_a.ndb.tradehandler: if part_a.ndb.tradehandler:
# already in a trade # already in a trade
part_a.msg("You are already in trade with %s. You need to end that first." % part_b.key) part_a.msg(
"You are already in trade with %s. You need to end that first." % part_b.key
)
return return
if part_b.ndb.tradehandler.join(part_a): if part_b.ndb.tradehandler.join(part_a):
part_b.msg(theiremote + str_start_a % part_a.key) part_b.msg(theiremote + str_start_a % part_a.key)

View file

@ -160,9 +160,10 @@ def _menu_savefunc(caller, buf):
def _menu_quitfunc(caller): def _menu_quitfunc(caller):
caller.cmdset.add(BuildingMenuCmdSet, caller.cmdset.add(
permanent=caller.ndb._building_menu and BuildingMenuCmdSet,
caller.ndb._building_menu.persistent or False) permanent=caller.ndb._building_menu and caller.ndb._building_menu.persistent or False,
)
if caller.ndb._building_menu: if caller.ndb._building_menu:
caller.ndb._building_menu.move(back=True) caller.ndb._building_menu.move(back=True)
@ -234,6 +235,7 @@ def _call_or_get(value, menu=None, choice=None, string=None, obj=None, caller=No
# Helper functions, to be used in menu choices # Helper functions, to be used in menu choices
def menu_setattr(menu, choice, obj, string): def menu_setattr(menu, choice, obj, string):
""" """
Set the value at the specified attribute. Set the value at the specified attribute.
@ -252,10 +254,16 @@ def menu_setattr(menu, choice, obj, string):
""" """
attr = getattr(choice, "attr", None) if choice else None attr = getattr(choice, "attr", None) if choice else None
if choice is None or string is None or attr is None or menu is None: if choice is None or string is None or attr is None or menu is None:
log_err(dedent(""" log_err(
dedent(
"""
The `menu_setattr` function was called to set the attribute {} of object {} to {}, The `menu_setattr` function was called to set the attribute {} of object {} to {},
but the choice {} of menu {} or another information is missing. but the choice {} of menu {} or another information is missing.
""".format(attr, obj, repr(string), choice, menu)).strip("\n")).strip() """.format(
attr, obj, repr(string), choice, menu
)
).strip("\n")
).strip()
return return
for part in attr.split(".")[:-1]: for part in attr.split(".")[:-1]:
@ -280,8 +288,10 @@ def menu_quit(caller, menu):
""" """
if caller is None or menu is None: if caller is None or menu is None:
log_err("The function `menu_quit` was called with missing " log_err(
"arguments: caller={}, menu={}".format(caller, menu)) "The function `menu_quit` was called with missing "
"arguments: caller={}, menu={}".format(caller, menu)
)
if caller.cmdset.has(BuildingMenuCmdSet): if caller.cmdset.has(BuildingMenuCmdSet):
menu.close() menu.close()
@ -303,12 +313,19 @@ def menu_edit(caller, choice, obj):
attr = choice.attr attr = choice.attr
caller.db._building_menu_to_edit = (obj, attr) caller.db._building_menu_to_edit = (obj, attr)
caller.cmdset.remove(BuildingMenuCmdSet) caller.cmdset.remove(BuildingMenuCmdSet)
EvEditor(caller, loadfunc=_menu_loadfunc, savefunc=_menu_savefunc, quitfunc=_menu_quitfunc, EvEditor(
key="editor", persistent=True) caller,
loadfunc=_menu_loadfunc,
savefunc=_menu_savefunc,
quitfunc=_menu_quitfunc,
key="editor",
persistent=True,
)
# Building menu commands and CmdSet # Building menu commands and CmdSet
class CmdNoInput(Command): class CmdNoInput(Command):
"""No input has been found.""" """No input has been found."""
@ -363,7 +380,9 @@ class CmdNoMatch(Command):
self.caller.msg(choice.format_text()) self.caller.msg(choice.format_text())
else: else:
for choice in self.menu.relevant_choices: for choice in self.menu.relevant_choices:
if choice.key.lower() == raw_string.lower() or any(raw_string.lower() == alias for alias in choice.aliases): if choice.key.lower() == raw_string.lower() or any(
raw_string.lower() == alias for alias in choice.aliases
):
self.menu.move(choice.key) self.menu.move(choice.key)
return return
@ -395,13 +414,26 @@ class BuildingMenuCmdSet(CmdSet):
# Menu classes # Menu classes
class Choice(object): class Choice(object):
"""A choice object, created by `add_choice`.""" """A choice object, created by `add_choice`."""
def __init__(self, title, key=None, aliases=None, attr=None, text=None, def __init__(
glance=None, on_enter=None, on_nomatch=None, on_leave=None, self,
menu=None, caller=None, obj=None): title,
key=None,
aliases=None,
attr=None,
text=None,
glance=None,
on_enter=None,
on_nomatch=None,
on_leave=None,
menu=None,
caller=None,
obj=None,
):
"""Constructor. """Constructor.
Args: Args:
@ -452,7 +484,9 @@ class Choice(object):
"""Format the choice text and return it, or an empty string.""" """Format the choice text and return it, or an empty string."""
text = "" text = ""
if self.text: if self.text:
text = _call_or_get(self.text, menu=self.menu, choice=self, string="", caller=self.caller, obj=self.obj) text = _call_or_get(
self.text, menu=self.menu, choice=self, string="", caller=self.caller, obj=self.obj
)
text = dedent(text.strip("\n")) text = dedent(text.strip("\n"))
text = text.format(obj=self.obj, caller=self.caller) text = text.format(obj=self.obj, caller=self.caller)
@ -466,8 +500,14 @@ class Choice(object):
""" """
if self.on_enter: if self.on_enter:
_call_or_get(self.on_enter, menu=self.menu, choice=self, string=string, _call_or_get(
caller=self.caller, obj=self.obj) self.on_enter,
menu=self.menu,
choice=self,
string=string,
caller=self.caller,
obj=self.obj,
)
def nomatch(self, string): def nomatch(self, string):
"""Called when the user entered something in the choice. """Called when the user entered something in the choice.
@ -482,8 +522,14 @@ class Choice(object):
""" """
if self.on_nomatch: if self.on_nomatch:
return _call_or_get(self.on_nomatch, menu=self.menu, choice=self, return _call_or_get(
string=string, caller=self.caller, obj=self.obj) self.on_nomatch,
menu=self.menu,
choice=self,
string=string,
caller=self.caller,
obj=self.obj,
)
return True return True
@ -495,8 +541,14 @@ class Choice(object):
""" """
if self.on_leave: if self.on_leave:
_call_or_get(self.on_leave, menu=self.menu, choice=self, _call_or_get(
string=string, caller=self.caller, obj=self.obj) self.on_leave,
menu=self.menu,
choice=self,
string=string,
caller=self.caller,
obj=self.obj,
)
class BuildingMenu(object): class BuildingMenu(object):
@ -527,8 +579,15 @@ class BuildingMenu(object):
joker_key = "*" # The special key meaning "anything" in a choice key joker_key = "*" # The special key meaning "anything" in a choice key
min_shortcut = 1 # The minimum length of shorcuts when `key` is not set min_shortcut = 1 # The minimum length of shorcuts when `key` is not set
def __init__(self, caller=None, obj=None, title="Building menu: {obj}", def __init__(
keys=None, parents=None, persistent=False): self,
caller=None,
obj=None,
title="Building menu: {obj}",
keys=None,
parents=None,
persistent=False,
):
"""Constructor, you shouldn't override. See `init` instead. """Constructor, you shouldn't override. See `init` instead.
Args: Args:
@ -650,12 +709,12 @@ class BuildingMenu(object):
if self.persistent: if self.persistent:
self.caller.db._building_menu = { self.caller.db._building_menu = {
"class": type(self).__module__ + "." + type(self).__name__, "class": type(self).__module__ + "." + type(self).__name__,
"obj": self.obj, "obj": self.obj,
"title": self.title, "title": self.title,
"keys": self.keys, "keys": self.keys,
"parents": self.parents, "parents": self.parents,
"persistent": self.persistent, "persistent": self.persistent,
} }
def _add_keys_choice(self): def _add_keys_choice(self):
@ -668,7 +727,7 @@ class BuildingMenu(object):
while length <= len(title): while length <= len(title):
i = 0 i = 0
while i < len(title) - length + 1: while i < len(title) - length + 1:
guess = title[i:i + length] guess = title[i : i + length]
if guess not in self.cmds: if guess not in self.cmds:
choice.key = guess choice.key = guess
break break
@ -698,8 +757,18 @@ class BuildingMenu(object):
""" """
pass pass
def add_choice(self, title, key=None, aliases=None, attr=None, text=None, glance=None, def add_choice(
on_enter=None, on_nomatch=None, on_leave=None): self,
title,
key=None,
aliases=None,
attr=None,
text=None,
glance=None,
on_enter=None,
on_nomatch=None,
on_leave=None,
):
""" """
Add a choice, a valid sub-menu, in the current builder menu. Add a choice, a valid sub-menu, in the current builder menu.
@ -762,8 +831,10 @@ class BuildingMenu(object):
on_nomatch = menu_setattr on_nomatch = menu_setattr
if key and key in self.cmds: if key and key in self.cmds:
raise ValueError("A conflict exists between {} and {}, both use " raise ValueError(
"key or alias {}".format(self.cmds[key], title, repr(key))) "A conflict exists between {} and {}, both use "
"key or alias {}".format(self.cmds[key], title, repr(key))
)
if attr: if attr:
if glance is None: if glance is None:
@ -777,12 +848,24 @@ class BuildingMenu(object):
Use |y{back}|n to go back to the main menu. Use |y{back}|n to go back to the main menu.
Current value: |c{{{obj_attr}}}|n Current value: |c{{{obj_attr}}}|n
""".format(attr=attr, obj_attr="obj." + attr, """.format(
back="|n or |y".join(self.keys_go_back)) attr=attr, obj_attr="obj." + attr, back="|n or |y".join(self.keys_go_back)
)
choice = Choice(title, key=key, aliases=aliases, attr=attr, text=text, glance=glance, choice = Choice(
on_enter=on_enter, on_nomatch=on_nomatch, on_leave=on_leave, title,
menu=self, caller=self.caller, obj=self.obj) key=key,
aliases=aliases,
attr=attr,
text=text,
glance=glance,
on_enter=on_enter,
on_nomatch=on_nomatch,
on_leave=on_leave,
menu=self,
caller=self.caller,
obj=self.obj,
)
self.choices.append(choice) self.choices.append(choice)
if key: if key:
self.cmds[key] = choice self.cmds[key] = choice
@ -792,8 +875,15 @@ class BuildingMenu(object):
return choice return choice
def add_choice_edit(self, title="description", key="d", aliases=None, attr="db.desc", def add_choice_edit(
glance="\n {obj.db.desc}", on_enter=None): self,
title="description",
key="d",
aliases=None,
attr="db.desc",
glance="\n {obj.db.desc}",
on_enter=None,
):
""" """
Add a simple choice to edit a given attribute in the EvEditor. Add a simple choice to edit a given attribute in the EvEditor.
@ -817,8 +907,9 @@ class BuildingMenu(object):
""" """
on_enter = on_enter or menu_edit on_enter = on_enter or menu_edit
return self.add_choice(title, key=key, aliases=aliases, attr=attr, return self.add_choice(
glance=glance, on_enter=on_enter, text="") title, key=key, aliases=aliases, attr=attr, glance=glance, on_enter=on_enter, text=""
)
def add_choice_quit(self, title="quit the menu", key="q", aliases=None, on_enter=None): def add_choice_quit(self, title="quit the menu", key="q", aliases=None, on_enter=None):
""" """
@ -883,17 +974,20 @@ class BuildingMenu(object):
try: try:
menu_class = class_from_module(parent_class) menu_class = class_from_module(parent_class)
except Exception: except Exception:
log_trace("BuildingMenu: attempting to load class {} failed".format( log_trace(
repr(parent_class))) "BuildingMenu: attempting to load class {} failed".format(repr(parent_class))
)
return return
# Create the parent menu # Create the parent menu
try: try:
building_menu = menu_class(self.caller, parent_obj, building_menu = menu_class(
keys=parent_keys, parents=tuple(parents)) self.caller, parent_obj, keys=parent_keys, parents=tuple(parents)
)
except Exception: except Exception:
log_trace("An error occurred while creating building menu {}".format( log_trace(
repr(parent_class))) "An error occurred while creating building menu {}".format(repr(parent_class))
)
return return
else: else:
return building_menu.open() return building_menu.open()
@ -928,14 +1022,18 @@ class BuildingMenu(object):
try: try:
menu_class = class_from_module(submenu_class) menu_class = class_from_module(submenu_class)
except Exception: except Exception:
log_trace("BuildingMenu: attempting to load class {} failed".format(repr(submenu_class))) log_trace(
"BuildingMenu: attempting to load class {} failed".format(repr(submenu_class))
)
return return
# Create the submenu # Create the submenu
try: try:
building_menu = menu_class(self.caller, submenu_obj, parents=parents) building_menu = menu_class(self.caller, submenu_obj, parents=parents)
except Exception: except Exception:
log_trace("An error occurred while creating building menu {}".format(repr(submenu_class))) log_trace(
"An error occurred while creating building menu {}".format(repr(submenu_class))
)
return return
else: else:
return building_menu.open() return building_menu.open()
@ -975,7 +1073,9 @@ class BuildingMenu(object):
self.keys.append(key) self.keys.append(key)
else: # Move backward else: # Move backward
if not self.keys: if not self.keys:
raise ValueError("you already are at the top of the tree, you cannot move backward.") raise ValueError(
"you already are at the top of the tree, you cannot move backward."
)
del self.keys[-1] del self.keys[-1]
@ -999,7 +1099,9 @@ class BuildingMenu(object):
# Display methods. Override for customization # Display methods. Override for customization
def display_title(self): def display_title(self):
"""Return the menu title to be displayed.""" """Return the menu title to be displayed."""
return _call_or_get(self.title, menu=self, obj=self.obj, caller=self.caller).format(obj=self.obj) return _call_or_get(self.title, menu=self, obj=self.obj, caller=self.caller).format(
obj=self.obj
)
def display_choice(self, choice): def display_choice(self, choice):
"""Display the specified choice. """Display the specified choice.
@ -1008,18 +1110,21 @@ class BuildingMenu(object):
choice (Choice): the menu choice. choice (Choice): the menu choice.
""" """
title = _call_or_get(choice.title, menu=self, choice=choice, obj=self.obj, caller=self.caller) title = _call_or_get(
choice.title, menu=self, choice=choice, obj=self.obj, caller=self.caller
)
clear_title = title.lower() clear_title = title.lower()
pos = clear_title.find(choice.key.lower()) pos = clear_title.find(choice.key.lower())
ret = " " ret = " "
if pos >= 0: if pos >= 0:
ret += title[:pos] + "[|y" + choice.key.title() + "|n]" + title[pos + len(choice.key):] ret += title[:pos] + "[|y" + choice.key.title() + "|n]" + title[pos + len(choice.key) :]
else: else:
ret += "[|y" + choice.key.title() + "|n] " + title ret += "[|y" + choice.key.title() + "|n] " + title
if choice.glance: if choice.glance:
glance = _call_or_get(choice.glance, menu=self, choice=choice, glance = _call_or_get(
caller=self.caller, string="", obj=self.obj) choice.glance, menu=self, choice=choice, caller=self.caller, string="", obj=self.obj
)
glance = glance.format(obj=self.obj, caller=self.caller) glance = glance.format(obj=self.obj, caller=self.caller)
ret += ": " + glance ret += ": " + glance
@ -1054,14 +1159,18 @@ class BuildingMenu(object):
if menu: if menu:
class_name = menu.get("class") class_name = menu.get("class")
if not class_name: if not class_name:
log_err("BuildingMenu: on caller {}, a persistent attribute holds building menu " log_err(
"data, but no class could be found to restore the menu".format(caller)) "BuildingMenu: on caller {}, a persistent attribute holds building menu "
"data, but no class could be found to restore the menu".format(caller)
)
return return
try: try:
menu_class = class_from_module(class_name) menu_class = class_from_module(class_name)
except Exception: except Exception:
log_trace("BuildingMenu: attempting to load class {} failed".format(repr(class_name))) log_trace(
"BuildingMenu: attempting to load class {} failed".format(repr(class_name))
)
return return
# Create the menu # Create the menu
@ -1071,10 +1180,13 @@ class BuildingMenu(object):
parents = menu.get("parents") parents = menu.get("parents")
persistent = menu.get("persistent", False) persistent = menu.get("persistent", False)
try: try:
building_menu = menu_class(caller, obj, title=title, keys=keys, building_menu = menu_class(
parents=parents, persistent=persistent) caller, obj, title=title, keys=keys, parents=parents, persistent=persistent
)
except Exception: except Exception:
log_trace("An error occurred while creating building menu {}".format(repr(class_name))) log_trace(
"An error occurred while creating building menu {}".format(repr(class_name))
)
return return
return building_menu return building_menu
@ -1102,7 +1214,12 @@ class GenericBuildingMenu(BuildingMenu):
call `add_choice_quit` to add this choice with different options. call `add_choice_quit` to add this choice with different options.
""" """
self.add_choice("key", key="k", attr="key", glance="{obj.key}", text=""" self.add_choice(
"key",
key="k",
attr="key",
glance="{obj.key}",
text="""
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
Editing the key of {{obj.key}}(#{{obj.id}}) Editing the key of {{obj.key}}(#{{obj.id}})
@ -1110,7 +1227,10 @@ class GenericBuildingMenu(BuildingMenu):
Use |y{back}|n to go back to the main menu. Use |y{back}|n to go back to the main menu.
Current key: |c{{obj.key}}|n Current key: |c{{obj.key}}|n
""".format(back="|n or |y".join(self.keys_go_back))) """.format(
back="|n or |y".join(self.keys_go_back)
),
)
self.add_choice_edit("description", key="d", attr="db.desc") self.add_choice_edit("description", key="d", attr="db.desc")

View file

@ -81,7 +81,9 @@ class CmdOOCLook(default_cmds.CmdLook):
if not avail_chars: if not avail_chars:
self.caller.msg("You have no characters to look at. Why not create one?") self.caller.msg("You have no characters to look at. Why not create one?")
return return
objs = managers.objects.get_objs_with_key_and_typeclass(self.args.strip(), CHARACTER_TYPECLASS) objs = managers.objects.get_objs_with_key_and_typeclass(
self.args.strip(), CHARACTER_TYPECLASS
)
objs = [obj for obj in objs if obj.id in avail_chars] objs = [obj for obj in objs if obj.id in avail_chars]
if not objs: if not objs:
self.caller.msg("You cannot see this Character.") self.caller.msg("You cannot see this Character.")
@ -101,15 +103,17 @@ class CmdOOCLook(default_cmds.CmdLook):
charlist += "\n\n Use |w@ic <character name>|n to switch to that Character." charlist += "\n\n Use |w@ic <character name>|n to switch to that Character."
else: else:
charlist = "You have no Characters." charlist = "You have no Characters."
string = \ string = """ You, %s, are an |wOOC ghost|n without form. The world is hidden
""" You, %s, are an |wOOC ghost|n without form. The world is hidden
from you and besides chatting on channels your options are limited. from you and besides chatting on channels your options are limited.
You need to have a Character in order to interact with the world. You need to have a Character in order to interact with the world.
%s %s
Use |wcreate <name>|n to create a new character and |whelp|n for a Use |wcreate <name>|n to create a new character and |whelp|n for a
list of available commands.""" % (self.caller.key, charlist) list of available commands.""" % (
self.caller.key,
charlist,
)
self.caller.msg(string) self.caller.msg(string)
else: else:
@ -159,11 +163,15 @@ class CmdOOCCharacterCreate(Command):
new_character = create_object(CHARACTER_TYPECLASS, key=charname) new_character = create_object(CHARACTER_TYPECLASS, key=charname)
if not new_character: if not new_character:
self.caller.msg("|rThe Character couldn't be created. This is a bug. Please contact an admin.") self.caller.msg(
"|rThe Character couldn't be created. This is a bug. Please contact an admin."
)
return return
# make sure to lock the character to only be puppeted by this account # make sure to lock the character to only be puppeted by this account
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" % new_character.locks.add(
(new_character.id, self.caller.id)) "puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)"
% (new_character.id, self.caller.id)
)
# save dbref # save dbref
avail_chars = self.caller.db._character_dbrefs avail_chars = self.caller.db._character_dbrefs

View file

@ -91,15 +91,21 @@ WEARSTYLE_MAXLENGTH = 50
# The order in which clothing types appear on the description. Untyped clothing or clothing # The order in which clothing types appear on the description. Untyped clothing or clothing
# with a type not given in this list goes last. # with a type not given in this list goes last.
CLOTHING_TYPE_ORDER = ['hat', 'jewelry', 'top', 'undershirt', 'gloves', 'fullbody', 'bottom', CLOTHING_TYPE_ORDER = [
'underpants', 'socks', 'shoes', 'accessory'] "hat",
"jewelry",
"top",
"undershirt",
"gloves",
"fullbody",
"bottom",
"underpants",
"socks",
"shoes",
"accessory",
]
# The maximum number of each type of clothes that can be worn. Unlimited if untyped or not specified. # The maximum number of each type of clothes that can be worn. Unlimited if untyped or not specified.
CLOTHING_TYPE_LIMIT = { CLOTHING_TYPE_LIMIT = {"hat": 1, "gloves": 1, "socks": 1, "shoes": 1}
'hat': 1,
'gloves': 1,
'socks': 1,
'shoes': 1
}
# The maximum number of clothing items that can be worn, or None for unlimited. # The maximum number of clothing items that can be worn, or None for unlimited.
CLOTHING_OVERALL_LIMIT = 20 CLOTHING_OVERALL_LIMIT = 20
# What types of clothes will automatically cover what other types of clothes when worn. # What types of clothes will automatically cover what other types of clothes when worn.
@ -107,17 +113,18 @@ CLOTHING_OVERALL_LIMIT = 20
# on that auto-covers it - for example, it's perfectly possible to have your underpants # on that auto-covers it - for example, it's perfectly possible to have your underpants
# showing if you put them on after your pants! # showing if you put them on after your pants!
CLOTHING_TYPE_AUTOCOVER = { CLOTHING_TYPE_AUTOCOVER = {
'top': ['undershirt'], "top": ["undershirt"],
'bottom': ['underpants'], "bottom": ["underpants"],
'fullbody': ['undershirt', 'underpants'], "fullbody": ["undershirt", "underpants"],
'shoes': ['socks'] "shoes": ["socks"],
} }
# Types of clothes that can't be used to cover other clothes. # Types of clothes that can't be used to cover other clothes.
CLOTHING_TYPE_CANT_COVER_WITH = ['jewelry'] CLOTHING_TYPE_CANT_COVER_WITH = ["jewelry"]
# HELPER FUNCTIONS START HERE # HELPER FUNCTIONS START HERE
def order_clothes_list(clothes_list): def order_clothes_list(clothes_list):
""" """
Orders a given clothes list by the order specified in CLOTHING_TYPE_ORDER. Orders a given clothes list by the order specified in CLOTHING_TYPE_ORDER.
@ -222,7 +229,6 @@ def single_type_count(clothes_list, type):
class Clothing(DefaultObject): class Clothing(DefaultObject):
def wear(self, wearer, wearstyle, quiet=False): def wear(self, wearer, wearstyle, quiet=False):
""" """
Sets clothes to 'worn' and optionally echoes to the room. Sets clothes to 'worn' and optionally echoes to the room.
@ -246,8 +252,10 @@ class Clothing(DefaultObject):
to_cover = [] to_cover = []
if self.db.clothing_type and self.db.clothing_type in CLOTHING_TYPE_AUTOCOVER: if self.db.clothing_type and self.db.clothing_type in CLOTHING_TYPE_AUTOCOVER:
for garment in get_worn_clothes(wearer): for garment in get_worn_clothes(wearer):
if garment.db.clothing_type and garment.db.clothing_type \ if (
in CLOTHING_TYPE_AUTOCOVER[self.db.clothing_type]: garment.db.clothing_type
and garment.db.clothing_type in CLOTHING_TYPE_AUTOCOVER[self.db.clothing_type]
):
to_cover.append(garment) to_cover.append(garment)
garment.db.covered_by = self garment.db.covered_by = self
# Return if quiet # Return if quiet
@ -282,7 +290,11 @@ class Clothing(DefaultObject):
thing.db.covered_by = False thing.db.covered_by = False
uncovered_list.append(thing.name) uncovered_list.append(thing.name)
if len(uncovered_list) > 0: if len(uncovered_list) > 0:
remove_message = "%s removes %s, revealing %s." % (wearer, self.name, list_to_string(uncovered_list)) remove_message = "%s removes %s, revealing %s." % (
wearer,
self.name,
list_to_string(uncovered_list),
)
# Echo a message to the room # Echo a message to the room
if not quiet: if not quiet:
wearer.location.msg_contents(remove_message) wearer.location.msg_contents(remove_message)
@ -345,6 +357,7 @@ class ClothedCharacter(DefaultCharacter):
# COMMANDS START HERE # COMMANDS START HERE
class CmdWear(MuxCommand): class CmdWear(MuxCommand):
""" """
Puts on an item of clothing you are holding. Puts on an item of clothing you are holding.
@ -390,7 +403,10 @@ class CmdWear(MuxCommand):
type_count = single_type_count(get_worn_clothes(self.caller), clothing.db.clothing_type) type_count = single_type_count(get_worn_clothes(self.caller), clothing.db.clothing_type)
if clothing.db.clothing_type in list(CLOTHING_TYPE_LIMIT.keys()): if clothing.db.clothing_type in list(CLOTHING_TYPE_LIMIT.keys()):
if type_count >= CLOTHING_TYPE_LIMIT[clothing.db.clothing_type]: if type_count >= CLOTHING_TYPE_LIMIT[clothing.db.clothing_type]:
self.caller.msg("You can't wear any more clothes of the type '%s'." % clothing.db.clothing_type) self.caller.msg(
"You can't wear any more clothes of the type '%s'."
% clothing.db.clothing_type
)
return return
if clothing.db.worn and len(self.arglist) == 1: if clothing.db.worn and len(self.arglist) == 1:
@ -399,9 +415,16 @@ class CmdWear(MuxCommand):
if len(self.arglist) > 1: # If wearstyle arguments given if len(self.arglist) > 1: # If wearstyle arguments given
wearstyle_list = self.arglist # Split arguments into a list of words wearstyle_list = self.arglist # Split arguments into a list of words
del wearstyle_list[0] # Leave first argument (the clothing item) out of the wearstyle del wearstyle_list[0] # Leave first argument (the clothing item) out of the wearstyle
wearstring = ' '.join(str(e) for e in wearstyle_list) # Join list of args back into one string wearstring = " ".join(
if WEARSTYLE_MAXLENGTH and len(wearstring) > WEARSTYLE_MAXLENGTH: # If length of wearstyle exceeds limit str(e) for e in wearstyle_list
self.caller.msg("Please keep your wear style message to less than %i characters." % WEARSTYLE_MAXLENGTH) ) # Join list of args back into one string
if (
WEARSTYLE_MAXLENGTH and len(wearstring) > WEARSTYLE_MAXLENGTH
): # If length of wearstyle exceeds limit
self.caller.msg(
"Please keep your wear style message to less than %i characters."
% WEARSTYLE_MAXLENGTH
)
else: else:
wearstyle = wearstring wearstyle = wearstring
clothing.wear(self.caller, wearstyle) clothing.wear(self.caller, wearstyle)
@ -489,11 +512,17 @@ class CmdCover(MuxCommand):
self.caller.msg("%s is covered by something else!" % cover_with.name) self.caller.msg("%s is covered by something else!" % cover_with.name)
return return
if to_cover.db.covered_by: if to_cover.db.covered_by:
self.caller.msg("%s is already covered by %s." % (cover_with.name, to_cover.db.covered_by.name)) self.caller.msg(
"%s is already covered by %s." % (cover_with.name, to_cover.db.covered_by.name)
)
return return
if not cover_with.db.worn: if not cover_with.db.worn:
cover_with.wear(self.caller, True) # Put on the item to cover with if it's not on already cover_with.wear(
self.caller.location.msg_contents("%s covers %s with %s." % (self.caller, to_cover.name, cover_with.name)) self.caller, True
) # Put on the item to cover with if it's not on already
self.caller.location.msg_contents(
"%s covers %s with %s." % (self.caller, to_cover.name, cover_with.name)
)
to_cover.db.covered_by = cover_with to_cover.db.covered_by = cover_with
@ -564,9 +593,12 @@ class CmdDrop(MuxCommand):
# Because the DROP command by definition looks for items # Because the DROP command by definition looks for items
# in inventory, call the search function using location = caller # in inventory, call the search function using location = caller
obj = caller.search(self.args, location=caller, obj = caller.search(
nofound_string="You aren't carrying %s." % self.args, self.args,
multimatch_string="You carry more than one %s:" % self.args) location=caller,
nofound_string="You aren't carrying %s." % self.args,
multimatch_string="You carry more than one %s:" % self.args,
)
if not obj: if not obj:
return return
@ -581,9 +613,7 @@ class CmdDrop(MuxCommand):
obj.move_to(caller.location, quiet=True) obj.move_to(caller.location, quiet=True)
caller.msg("You drop %s." % (obj.name,)) caller.msg("You drop %s." % (obj.name,))
caller.location.msg_contents("%s drops %s." % caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
(caller.name, obj.name),
exclude=caller)
# Call the object script's at_drop() method. # Call the object script's at_drop() method.
obj.at_drop(caller) obj.at_drop(caller)
@ -598,6 +628,7 @@ class CmdGive(MuxCommand):
Gives an items from your inventory to another character, Gives an items from your inventory to another character,
placing it in their inventory. placing it in their inventory.
""" """
key = "give" key = "give"
locks = "cmd:all()" locks = "cmd:all()"
arg_regex = r"\s|$" arg_regex = r"\s|$"
@ -609,9 +640,12 @@ class CmdGive(MuxCommand):
if not self.args or not self.rhs: if not self.args or not self.rhs:
caller.msg("Usage: give <inventory object> = <target>") caller.msg("Usage: give <inventory object> = <target>")
return return
to_give = caller.search(self.lhs, location=caller, to_give = caller.search(
nofound_string="You aren't carrying %s." % self.lhs, self.lhs,
multimatch_string="You carry more than one %s:" % self.lhs) location=caller,
nofound_string="You aren't carrying %s." % self.lhs,
multimatch_string="You carry more than one %s:" % self.lhs,
)
target = caller.search(self.rhs) target = caller.search(self.rhs)
if not (to_give and target): if not (to_give and target):
return return
@ -623,7 +657,9 @@ class CmdGive(MuxCommand):
return return
# This is new! Can't give away something that's worn. # This is new! Can't give away something that's worn.
if to_give.db.covered_by: if to_give.db.covered_by:
caller.msg("You can't give that away because it's covered by %s." % to_give.db.covered_by) caller.msg(
"You can't give that away because it's covered by %s." % to_give.db.covered_by
)
return return
# Remove clothes if they're given. # Remove clothes if they're given.
if to_give.db.worn: if to_give.db.worn:
@ -647,6 +683,7 @@ class CmdInventory(MuxCommand):
Shows your inventory. Shows your inventory.
""" """
# Alternate version of the inventory command which separates # Alternate version of the inventory command which separates
# worn and carried items. # worn and carried items.
@ -687,6 +724,7 @@ class ClothedCharacterCmdSet(default_cmds.CharacterCmdSet):
version of 'inventory' that differentiates between carried and worn version of 'inventory' that differentiates between carried and worn
items. items.
""" """
key = "DefaultCharacter" key = "DefaultCharacter"
def at_cmdset_creation(self): def at_cmdset_creation(self):

View file

@ -110,70 +110,65 @@ _ANSI_SPACE = " "
############################################################# #############################################################
CURLY_COLOR_ANSI_EXTRA_MAP = [ CURLY_COLOR_ANSI_EXTRA_MAP = [
(r'{n', _ANSI_NORMAL), # reset (r"{n", _ANSI_NORMAL), # reset
(r'{/', _ANSI_RETURN), # line break (r"{/", _ANSI_RETURN), # line break
(r'{-', _ANSI_TAB), # tab (r"{-", _ANSI_TAB), # tab
(r'{_', _ANSI_SPACE), # space (r"{_", _ANSI_SPACE), # space
(r'{*', _ANSI_INVERSE), # invert (r"{*", _ANSI_INVERSE), # invert
(r'{^', _ANSI_BLINK), # blinking text (very annoying and not supported by all clients) (r"{^", _ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'{u', _ANSI_UNDERLINE), # underline (r"{u", _ANSI_UNDERLINE), # underline
(r"{r", _ANSI_HILITE + _ANSI_RED),
(r'{r', _ANSI_HILITE + _ANSI_RED), (r"{g", _ANSI_HILITE + _ANSI_GREEN),
(r'{g', _ANSI_HILITE + _ANSI_GREEN), (r"{y", _ANSI_HILITE + _ANSI_YELLOW),
(r'{y', _ANSI_HILITE + _ANSI_YELLOW), (r"{b", _ANSI_HILITE + _ANSI_BLUE),
(r'{b', _ANSI_HILITE + _ANSI_BLUE), (r"{m", _ANSI_HILITE + _ANSI_MAGENTA),
(r'{m', _ANSI_HILITE + _ANSI_MAGENTA), (r"{c", _ANSI_HILITE + _ANSI_CYAN),
(r'{c', _ANSI_HILITE + _ANSI_CYAN), (r"{w", _ANSI_HILITE + _ANSI_WHITE), # pure white
(r'{w', _ANSI_HILITE + _ANSI_WHITE), # pure white (r"{x", _ANSI_HILITE + _ANSI_BLACK), # dark grey
(r'{x', _ANSI_HILITE + _ANSI_BLACK), # dark grey (r"{R", _ANSI_HILITE + _ANSI_RED),
(r"{G", _ANSI_HILITE + _ANSI_GREEN),
(r'{R', _ANSI_HILITE + _ANSI_RED), (r"{Y", _ANSI_HILITE + _ANSI_YELLOW),
(r'{G', _ANSI_HILITE + _ANSI_GREEN), (r"{B", _ANSI_HILITE + _ANSI_BLUE),
(r'{Y', _ANSI_HILITE + _ANSI_YELLOW), (r"{M", _ANSI_HILITE + _ANSI_MAGENTA),
(r'{B', _ANSI_HILITE + _ANSI_BLUE), (r"{C", _ANSI_HILITE + _ANSI_CYAN),
(r'{M', _ANSI_HILITE + _ANSI_MAGENTA), (r"{W", _ANSI_HILITE + _ANSI_WHITE), # light grey
(r'{C', _ANSI_HILITE + _ANSI_CYAN), (r"{X", _ANSI_HILITE + _ANSI_BLACK), # pure black
(r'{W', _ANSI_HILITE + _ANSI_WHITE), # light grey
(r'{X', _ANSI_HILITE + _ANSI_BLACK), # pure black
# hilight-able colors # hilight-able colors
(r'{h', _ANSI_HILITE), (r"{h", _ANSI_HILITE),
(r'{H', _ANSI_UNHILITE), (r"{H", _ANSI_UNHILITE),
(r"{!R", _ANSI_RED),
(r'{!R', _ANSI_RED), (r"{!G", _ANSI_GREEN),
(r'{!G', _ANSI_GREEN), (r"{!Y", _ANSI_YELLOW),
(r'{!Y', _ANSI_YELLOW), (r"{!B", _ANSI_BLUE),
(r'{!B', _ANSI_BLUE), (r"{!M", _ANSI_MAGENTA),
(r'{!M', _ANSI_MAGENTA), (r"{!C", _ANSI_CYAN),
(r'{!C', _ANSI_CYAN), (r"{!W", _ANSI_WHITE), # light grey
(r'{!W', _ANSI_WHITE), # light grey (r"{!X", _ANSI_BLACK), # pure black
(r'{!X', _ANSI_BLACK), # pure black
# normal ANSI backgrounds # normal ANSI backgrounds
(r'{[R', _ANSI_BACK_RED), (r"{[R", _ANSI_BACK_RED),
(r'{[G', _ANSI_BACK_GREEN), (r"{[G", _ANSI_BACK_GREEN),
(r'{[Y', _ANSI_BACK_YELLOW), (r"{[Y", _ANSI_BACK_YELLOW),
(r'{[B', _ANSI_BACK_BLUE), (r"{[B", _ANSI_BACK_BLUE),
(r'{[M', _ANSI_BACK_MAGENTA), (r"{[M", _ANSI_BACK_MAGENTA),
(r'{[C', _ANSI_BACK_CYAN), (r"{[C", _ANSI_BACK_CYAN),
(r'{[W', _ANSI_BACK_WHITE), # light grey background (r"{[W", _ANSI_BACK_WHITE), # light grey background
(r'{[X', _ANSI_BACK_BLACK), # pure black background (r"{[X", _ANSI_BACK_BLACK), # pure black background
] ]
CURLY_COLOR_XTERM256_EXTRA_FG = [r'\{([0-5])([0-5])([0-5])'] # |123 - foreground colour CURLY_COLOR_XTERM256_EXTRA_FG = [r"\{([0-5])([0-5])([0-5])"] # |123 - foreground colour
CURLY_COLOR_XTERM256_EXTRA_BG = [r'\{\[([0-5])([0-5])([0-5])'] # |[123 - background colour CURLY_COLOR_XTERM256_EXTRA_BG = [r"\{\[([0-5])([0-5])([0-5])"] # |[123 - background colour
CURLY_COLOR_XTERM256_EXTRA_GFG = [r'\{=([a-z])'] # |=a - greyscale foreground CURLY_COLOR_XTERM256_EXTRA_GFG = [r"\{=([a-z])"] # |=a - greyscale foreground
CURLY_COLOR_XTERM256_EXTRA_GBG = [r'\{\[=([a-z])'] # |[=a - greyscale background CURLY_COLOR_XTERM256_EXTRA_GBG = [r"\{\[=([a-z])"] # |[=a - greyscale background
CURLY_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [ CURLY_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [
(r'{[r', r'{[500'), (r"{[r", r"{[500"),
(r'{[g', r'{[050'), (r"{[g", r"{[050"),
(r'{[y', r'{[550'), (r"{[y", r"{[550"),
(r'{[b', r'{[005'), (r"{[b", r"{[005"),
(r'{[m', r'{[505'), (r"{[m", r"{[505"),
(r'{[c', r'{[055'), (r"{[c", r"{[055"),
(r'{[w', r'{[555'), # white background (r"{[w", r"{[555"), # white background
(r'{[x', r'{[222'), # dark grey background (r"{[x", r"{[222"), # dark grey background
] ]
@ -191,48 +186,46 @@ CURLY_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [
############################################################# #############################################################
MUX_COLOR_ANSI_EXTRA_MAP = [ MUX_COLOR_ANSI_EXTRA_MAP = [
(r'%cn', _ANSI_NORMAL), # reset (r"%cn", _ANSI_NORMAL), # reset
(r'%ch', _ANSI_HILITE), # highlight (r"%ch", _ANSI_HILITE), # highlight
(r'%r', _ANSI_RETURN), # line break (r"%r", _ANSI_RETURN), # line break
(r'%R', _ANSI_RETURN), # (r"%R", _ANSI_RETURN), #
(r'%t', _ANSI_TAB), # tab (r"%t", _ANSI_TAB), # tab
(r'%T', _ANSI_TAB), # (r"%T", _ANSI_TAB), #
(r'%b', _ANSI_SPACE), # space (r"%b", _ANSI_SPACE), # space
(r'%B', _ANSI_SPACE), (r"%B", _ANSI_SPACE),
(r'%cf', _ANSI_BLINK), # annoying and not supported by all clients (r"%cf", _ANSI_BLINK), # annoying and not supported by all clients
(r'%ci', _ANSI_INVERSE), # invert (r"%ci", _ANSI_INVERSE), # invert
(r"%cr", _ANSI_RED),
(r'%cr', _ANSI_RED), (r"%cg", _ANSI_GREEN),
(r'%cg', _ANSI_GREEN), (r"%cy", _ANSI_YELLOW),
(r'%cy', _ANSI_YELLOW), (r"%cb", _ANSI_BLUE),
(r'%cb', _ANSI_BLUE), (r"%cm", _ANSI_MAGENTA),
(r'%cm', _ANSI_MAGENTA), (r"%cc", _ANSI_CYAN),
(r'%cc', _ANSI_CYAN), (r"%cw", _ANSI_WHITE),
(r'%cw', _ANSI_WHITE), (r"%cx", _ANSI_BLACK),
(r'%cx', _ANSI_BLACK), (r"%cR", _ANSI_BACK_RED),
(r"%cG", _ANSI_BACK_GREEN),
(r'%cR', _ANSI_BACK_RED), (r"%cY", _ANSI_BACK_YELLOW),
(r'%cG', _ANSI_BACK_GREEN), (r"%cB", _ANSI_BACK_BLUE),
(r'%cY', _ANSI_BACK_YELLOW), (r"%cM", _ANSI_BACK_MAGENTA),
(r'%cB', _ANSI_BACK_BLUE), (r"%cC", _ANSI_BACK_CYAN),
(r'%cM', _ANSI_BACK_MAGENTA), (r"%cW", _ANSI_BACK_WHITE),
(r'%cC', _ANSI_BACK_CYAN), (r"%cX", _ANSI_BACK_BLACK),
(r'%cW', _ANSI_BACK_WHITE),
(r'%cX', _ANSI_BACK_BLACK)
] ]
MUX_COLOR_XTERM256_EXTRA_FG = [r'%c([0-5])([0-5])([0-5])'] # %c123 - foreground colour MUX_COLOR_XTERM256_EXTRA_FG = [r"%c([0-5])([0-5])([0-5])"] # %c123 - foreground colour
MUX_COLOR_XTERM256_EXTRA_BG = [r'%c\[([0-5])([0-5])([0-5])'] # %c[123 - background colour MUX_COLOR_XTERM256_EXTRA_BG = [r"%c\[([0-5])([0-5])([0-5])"] # %c[123 - background colour
MUX_COLOR_XTERM256_EXTRA_GFG = [r'%c=([a-z])'] # %c=a - greyscale foreground MUX_COLOR_XTERM256_EXTRA_GFG = [r"%c=([a-z])"] # %c=a - greyscale foreground
MUX_COLOR_XTERM256_EXTRA_GBG = [r'%c\[=([a-z])'] # %c[=a - greyscale background MUX_COLOR_XTERM256_EXTRA_GBG = [r"%c\[=([a-z])"] # %c[=a - greyscale background
MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [ MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP = [
(r'%ch%cR', r'%c[500'), (r"%ch%cR", r"%c[500"),
(r'%ch%cG', r'%c[050'), (r"%ch%cG", r"%c[050"),
(r'%ch%cY', r'%c[550'), (r"%ch%cY", r"%c[550"),
(r'%ch%cB', r'%c[005'), (r"%ch%cB", r"%c[005"),
(r'%ch%cM', r'%c[505'), (r"%ch%cM", r"%c[505"),
(r'%ch%cC', r'%c[055'), (r"%ch%cC", r"%c[055"),
(r'%ch%cW', r'%c[555'), # white background (r"%ch%cW", r"%c[555"), # white background
(r'%ch%cX', r'%c[222'), # dark grey background (r"%ch%cX", r"%c[222"), # dark grey background
] ]

View file

@ -38,22 +38,28 @@ from django.conf import settings
from evennia import DefaultScript from evennia import DefaultScript
from evennia.utils.create import create_script from evennia.utils.create import create_script
from evennia.utils import gametime from evennia.utils import gametime
# The game time speedup / slowdown relative real time # The game time speedup / slowdown relative real time
TIMEFACTOR = settings.TIME_FACTOR TIMEFACTOR = settings.TIME_FACTOR
# 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. # Each unit must be consistent and expressed in seconds.
UNITS = getattr(settings, "TIME_UNITS", { UNITS = getattr(
# default custom calendar settings,
"sec": 1, "TIME_UNITS",
"min": 60, {
"hr": 60 * 60, # default custom calendar
"hour": 60 * 60, "sec": 1,
"day": 60 * 60 * 24, "min": 60,
"week": 60 * 60 * 24 * 7, "hr": 60 * 60,
"month": 60 * 60 * 24 * 7 * 4, "hour": 60 * 60,
"yr": 60 * 60 * 24 * 7 * 4 * 12, "day": 60 * 60 * 24,
"year": 60 * 60 * 24 * 7 * 4 * 12, }) "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): def time_to_tuple(seconds, *divisors):
@ -111,8 +117,7 @@ def gametime_to_realtime(format=False, **kwargs):
name = name[:-1] name = name[:-1]
if name not in UNITS: if name not in UNITS:
raise ValueError("the unit {} isn't defined as a valid " raise ValueError("the unit {} isn't defined as a valid " "game time unit".format(name))
"game time unit".format(name))
rtime += value * UNITS[name] rtime += value * UNITS[name]
rtime /= TIMEFACTOR rtime /= TIMEFACTOR
if format: if format:
@ -120,8 +125,7 @@ def gametime_to_realtime(format=False, **kwargs):
return rtime return rtime
def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0, def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0, months=0, yrs=0, format=False):
months=0, yrs=0, format=False):
""" """
This method calculates how much in-game time a real-world time This method calculates how much in-game time a real-world time
interval would correspond to. This is usually a lot less interval would correspond to. This is usually a lot less
@ -139,8 +143,15 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0, weeks=0,
realtime_to_gametime(days=2) -> number of game-world seconds realtime_to_gametime(days=2) -> number of game-world seconds
""" """
gtime = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 + gtime = TIMEFACTOR * (
weeks * 604800 + months * 2628000 + yrs * 31536000) secs
+ mins * 60
+ hrs * 3600
+ days * 86400
+ weeks * 604800
+ months * 2628000
+ yrs * 31536000
)
if format: if format:
units = sorted(set(UNITS.values()), reverse=True) units = sorted(set(UNITS.values()), reverse=True)
# Remove seconds from the tuple # Remove seconds from the tuple
@ -258,14 +269,19 @@ def schedule(callback, repeat=False, **kwargs):
""" """
seconds = real_seconds_until(**kwargs) seconds = real_seconds_until(**kwargs)
script = create_script("evennia.contrib.custom_gametime.GametimeScript", script = create_script(
key="GametimeScript", desc="A timegame-sensitive script", "evennia.contrib.custom_gametime.GametimeScript",
interval=seconds, start_delay=True, key="GametimeScript",
repeats=-1 if repeat else 1) desc="A timegame-sensitive script",
interval=seconds,
start_delay=True,
repeats=-1 if repeat else 1,
)
script.db.callback = callback script.db.callback = callback
script.db.gametime = kwargs script.db.gametime = kwargs
return script return script
# Scripts dealing in gametime (use `schedule` to create it) # Scripts dealing in gametime (use `schedule` to create it)

View file

@ -95,7 +95,7 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F
if modifier: if modifier:
# make sure to check types well before eval # make sure to check types well before eval
mod, modvalue = modifier mod, modvalue = modifier
if mod not in ('+', '-', '*', '/'): if mod not in ("+", "-", "*", "/"):
raise TypeError("Non-supported dice modifier: %s" % mod) raise TypeError("Non-supported dice modifier: %s" % mod)
modvalue = int(modvalue) # for safety modvalue = int(modvalue) # for safety
result = eval("%s %s %s" % (result, mod, modvalue)) result = eval("%s %s %s" % (result, mod, modvalue))
@ -103,7 +103,7 @@ def roll_dice(dicenum, dicetype, modifier=None, conditional=None, return_tuple=F
if conditional: if conditional:
# make sure to check types well before eval # make sure to check types well before eval
cond, condvalue = conditional cond, condvalue = conditional
if cond not in ('>', '<', '>=', '<=', '!=', '=='): if cond not in (">", "<", ">=", "<=", "!=", "=="):
raise TypeError("Non-supported dice result conditional: %s" % conditional) raise TypeError("Non-supported dice result conditional: %s" % conditional)
condvalue = int(condvalue) # for safety condvalue = int(condvalue) # for safety
outcome = eval("%s %s %s" % (result, cond, condvalue)) # True/False outcome = eval("%s %s %s" % (result, cond, condvalue)) # True/False
@ -166,9 +166,11 @@ class CmdDice(default_cmds.MuxCommand):
modifier = None modifier = None
conditional = None conditional = None
if len_parts < 3 or parts[1] != 'd': if len_parts < 3 or parts[1] != "d":
self.caller.msg("You must specify the die roll(s) as <nr>d<sides>." self.caller.msg(
" For example, 2d6 means rolling a 6-sided die 2 times.") "You must specify the die roll(s) as <nr>d<sides>."
" For example, 2d6 means rolling a 6-sided die 2 times."
)
return return
# Limit the number of dice and sides a character can roll to prevent server slow down and crashes # Limit the number of dice and sides a character can roll to prevent server slow down and crashes
@ -184,7 +186,7 @@ class CmdDice(default_cmds.MuxCommand):
pass pass
elif len_parts == 5: elif len_parts == 5:
# either e.g. 1d6 + 3 or something like 1d6 > 3 # either e.g. 1d6 + 3 or something like 1d6 > 3
if parts[3] in ('+', '-', '*', '/'): if parts[3] in ("+", "-", "*", "/"):
modifier = (parts[3], parts[4]) modifier = (parts[3], parts[4])
else: # assume it is a conditional else: # assume it is a conditional
conditional = (parts[3], parts[4]) conditional = (parts[3], parts[4])
@ -198,14 +200,14 @@ class CmdDice(default_cmds.MuxCommand):
return return
# do the roll # do the roll
try: try:
result, outcome, diff, rolls = roll_dice(ndice, result, outcome, diff, rolls = roll_dice(
nsides, ndice, nsides, modifier=modifier, conditional=conditional, return_tuple=True
modifier=modifier, )
conditional=conditional,
return_tuple=True)
except ValueError: except ValueError:
self.caller.msg("You need to enter valid integer numbers, modifiers and operators." self.caller.msg(
" |w%s|n was not understood." % self.args) "You need to enter valid integer numbers, modifiers and operators."
" |w%s|n was not understood." % self.args
)
return return
# format output # format output
if len(rolls) > 1: if len(rolls) > 1:
@ -222,13 +224,13 @@ class CmdDice(default_cmds.MuxCommand):
roomrollstring = "%s rolls %s%s." roomrollstring = "%s rolls %s%s."
resultstring = " Roll(s): %s. Total result is |w%s|n." resultstring = " Roll(s): %s. Total result is |w%s|n."
if 'secret' in self.switches: if "secret" in self.switches:
# don't echo to the room at all # don't echo to the room at all
string = yourollstring % (argstring, " (secret, not echoed)") string = yourollstring % (argstring, " (secret, not echoed)")
string += "\n" + resultstring % (rolls, result) string += "\n" + resultstring % (rolls, result)
string += outcomestring + " (not echoed)" string += outcomestring + " (not echoed)"
self.caller.msg(string) self.caller.msg(string)
elif 'hidden' in self.switches: elif "hidden" in self.switches:
# announce the roll to the room, result only to caller # announce the roll to the room, result only to caller
string = yourollstring % (argstring, " (hidden)") string = yourollstring % (argstring, " (hidden)")
self.caller.msg(string) self.caller.msg(string)

View file

@ -39,11 +39,18 @@ from evennia.commands.cmdset import CmdSet
from evennia.utils import logger, utils, ansi from evennia.utils import logger, utils, ansi
from evennia.commands.default.muxcommand import MuxCommand from evennia.commands.default.muxcommand import MuxCommand
from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate from evennia.commands.default import (
unloggedin as default_unloggedin,
) # Used in CmdUnconnectedCreate
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate", __all__ = (
"CmdUnconnectedQuit", "CmdUnconnectedLook", "CmdUnconnectedHelp") "CmdUnconnectedConnect",
"CmdUnconnectedCreate",
"CmdUnconnectedQuit",
"CmdUnconnectedLook",
"CmdUnconnectedHelp",
)
MULTISESSION_MODE = settings.MULTISESSION_MODE MULTISESSION_MODE = settings.MULTISESSION_MODE
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
@ -54,8 +61,10 @@ except Exception:
# malformed connection screen or no screen given # malformed connection screen or no screen given
pass pass
if not CONNECTION_SCREEN: if not CONNECTION_SCREEN:
CONNECTION_SCREEN = "\nEvennia: Error in CONNECTION_SCREEN MODULE" \ CONNECTION_SCREEN = (
" (randomly picked connection screen variable is not a string). \nEnter 'help' for aid." "\nEvennia: Error in CONNECTION_SCREEN MODULE"
" (randomly picked connection screen variable is not a string). \nEnter 'help' for aid."
)
class CmdUnconnectedConnect(MuxCommand): class CmdUnconnectedConnect(MuxCommand):
@ -67,6 +76,7 @@ class CmdUnconnectedConnect(MuxCommand):
Use the create command to first create an account before logging in. Use the create command to first create an account before logging in.
""" """
key = "connect" key = "connect"
aliases = ["conn", "con", "co"] aliases = ["conn", "con", "co"]
locks = "cmd:all()" # not really needed locks = "cmd:all()" # not really needed
@ -105,8 +115,10 @@ class CmdUnconnectedConnect(MuxCommand):
# Check IP and/or name bans # Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans") bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == account.name for tup in bans) or if bans and (
any(tup[2].match(session.address[0]) for tup in bans if tup[2])): any(tup[0] == account.name for tup in bans)
or any(tup[2].match(session.address[0]) for tup in bans if tup[2])
):
# this is a banned IP or name! # this is a banned IP or name!
string = "|rYou have been banned and cannot continue from here." string = "|rYou have been banned and cannot continue from here."
string += "\nIf you feel this ban is in error, please email an admin.|x" string += "\nIf you feel this ban is in error, please email an admin.|x"
@ -128,6 +140,7 @@ class CmdUnconnectedCreate(MuxCommand):
This creates a new account account. This creates a new account account.
""" """
key = "create" key = "create"
aliases = ["cre", "cr"] aliases = ["cre", "cr"]
locks = "cmd:all()" locks = "cmd:all()"
@ -152,7 +165,7 @@ class CmdUnconnectedCreate(MuxCommand):
else: else:
accountname, email, password = self.arglist accountname, email, password = self.arglist
accountname = accountname.replace('"', '') # remove " accountname = accountname.replace('"', "") # remove "
accountname = accountname.replace("'", "") accountname = accountname.replace("'", "")
self.accountinfo = (accountname, email, password) self.accountinfo = (accountname, email, password)
@ -163,7 +176,7 @@ class CmdUnconnectedCreate(MuxCommand):
try: try:
accountname, email, password = self.accountinfo accountname, email, password = self.accountinfo
except ValueError: except ValueError:
string = "\n\r Usage (without <>): create \"<accountname>\" <email> <password>" string = '\n\r Usage (without <>): create "<accountname>" <email> <password>'
session.msg(string) session.msg(string)
return return
if not email or not password: if not email or not password:
@ -192,24 +205,32 @@ class CmdUnconnectedCreate(MuxCommand):
session.msg("Sorry, there is already an account with that email address.") session.msg("Sorry, there is already an account with that email address.")
return return
# Reserve accountnames found in GUEST_LIST # Reserve accountnames found in GUEST_LIST
if settings.GUEST_LIST and accountname.lower() in (guest.lower() for guest in settings.GUEST_LIST): if settings.GUEST_LIST and accountname.lower() in (
guest.lower() for guest in settings.GUEST_LIST
):
string = "\n\r That name is reserved. Please choose another Accountname." string = "\n\r That name is reserved. Please choose another Accountname."
session.msg(string) session.msg(string)
return return
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)): if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
string = "\n\r Password should be longer than 3 characters. Letters, spaces, digits and @/./+/-/_/' only." \ string = (
"\nFor best security, make it longer than 8 characters. You can also use a phrase of" \ "\n\r Password should be longer than 3 characters. Letters, spaces, digits and @/./+/-/_/' only."
"\nmany words if you enclose the password in double quotes." "\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) session.msg(string)
return return
# Check IP and/or name bans # Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans") bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == accountname.lower() for tup in bans) or if bans and (
any(tup[2].match(session.address) for tup in bans if tup[2])): any(tup[0] == accountname.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! # this is a banned IP or name!
string = "|rYou have been banned and cannot continue from here." \ string = (
"\nIf you feel this ban is in error, please email an admin.|x" "|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.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return return
@ -218,15 +239,21 @@ class CmdUnconnectedCreate(MuxCommand):
try: try:
permissions = settings.PERMISSION_ACCOUNT_DEFAULT permissions = settings.PERMISSION_ACCOUNT_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS typeclass = settings.BASE_CHARACTER_TYPECLASS
new_account = default_unloggedin._create_account(session, accountname, password, permissions, email=email) new_account = default_unloggedin._create_account(
session, accountname, password, permissions, email=email
)
if new_account: if new_account:
if MULTISESSION_MODE < 2: if MULTISESSION_MODE < 2:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
default_unloggedin._create_character(session, new_account, typeclass, default_home, permissions) default_unloggedin._create_character(
session, new_account, typeclass, default_home, permissions
)
# tell the caller everything went well. # tell the caller everything went well.
string = "A new account '%s' was created. Welcome!" string = "A new account '%s' was created. Welcome!"
if " " in accountname: if " " in accountname:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'." string += (
"\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
)
else: else:
string += "\n\nYou can now log with the command 'connect %s <your password>'." string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (accountname, email)) session.msg(string % (accountname, email))
@ -246,6 +273,7 @@ class CmdUnconnectedQuit(MuxCommand):
here for unconnected accounts for the sake of simplicity. The logged in here for unconnected accounts for the sake of simplicity. The logged in
version is a bit more complicated. version is a bit more complicated.
""" """
key = "quit" key = "quit"
aliases = ["q", "qu"] aliases = ["q", "qu"]
locks = "cmd:all()" locks = "cmd:all()"
@ -263,6 +291,7 @@ class CmdUnconnectedLook(MuxCommand):
This is called by the server and kicks everything in gear. This is called by the server and kicks everything in gear.
All it does is display the connect screen. All it does is display the connect screen.
""" """
key = CMD_LOGINSTART key = CMD_LOGINSTART
aliases = ["look", "l"] aliases = ["look", "l"]
locks = "cmd:all()" locks = "cmd:all()"
@ -277,6 +306,7 @@ class CmdUnconnectedHelp(MuxCommand):
This is an unconnected version of the help command, This is an unconnected version of the help command,
for simplicity. It shows a pane of info. for simplicity. It shows a pane of info.
""" """
key = "help" key = "help"
aliases = ["h", "?"] aliases = ["h", "?"]
locks = "cmd:all()" locks = "cmd:all()"
@ -284,8 +314,7 @@ class CmdUnconnectedHelp(MuxCommand):
def func(self): def func(self):
"""Shows help""" """Shows help"""
string = \ string = """
"""
You are not yet logged into the game. Commands available at this point: You are not yet logged into the game. Commands available at this point:
|wcreate, connect, look, help, quit|n |wcreate, connect, look, help, quit|n
@ -316,10 +345,12 @@ You can use the |wlook|n command if you want to see the connect screen again.
# command set for the mux-like login # command set for the mux-like login
class UnloggedinCmdSet(CmdSet): class UnloggedinCmdSet(CmdSet):
""" """
Sets up the unlogged cmdset. Sets up the unlogged cmdset.
""" """
key = "Unloggedin" key = "Unloggedin"
priority = 0 priority = 0

View file

@ -35,7 +35,7 @@ from evennia import syscmdkeys
from evennia.utils import variable_from_module from evennia.utils import variable_from_module
from .utils import create_evscaperoom_object from .utils import create_evscaperoom_object
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
_RE_ARGSPLIT = re.compile(r"\s(with|on|to|in|at)\s", re.I + re.U) _RE_ARGSPLIT = re.compile(r"\s(with|on|to|in|at)\s", re.I + re.U)
_RE_EMOTE_SPEECH = re.compile(r"(\".*?\")|(\'.*?\')") _RE_EMOTE_SPEECH = re.compile(r"(\".*?\")|(\'.*?\')")
@ -111,6 +111,7 @@ class CmdEvscapeRoom(Command):
command [<obj1>|<arg1>] [<prep> <obj2>|<arg2>] command [<obj1>|<arg1>] [<prep> <obj2>|<arg2>]
""" """
# always separate the command from any args with a space # always separate the command from any args with a space
arg_regex = r"(/\w+?(\s|$))|\s|$" arg_regex = r"(/\w+?(\s|$))|\s|$"
help_category = "Evscaperoom" help_category = "Evscaperoom"
@ -210,6 +211,7 @@ class CmdGiveUp(CmdEvscapeRoom):
Abandons your attempts at escaping and of ever winning the pie-eating contest. Abandons your attempts at escaping and of ever winning the pie-eating contest.
""" """
key = "give up" key = "give up"
aliases = ("abort", "chicken out", "quit", "q") aliases = ("abort", "chicken out", "quit", "q")
@ -224,10 +226,10 @@ class CmdGiveUp(CmdEvscapeRoom):
warning = _QUIT_WARNING_CAN_COME_BACK.format(roomname=self.room.name) warning = _QUIT_WARNING_CAN_COME_BACK.format(roomname=self.room.name)
warning = _QUIT_WARNING.format(warning=warning) warning = _QUIT_WARNING.format(warning=warning)
ret = yield(warning) ret = yield (warning)
if ret.upper() == "QUIT": if ret.upper() == "QUIT":
self.msg("|R ... Oh. Okay then. Off you go.|n\n") self.msg("|R ... Oh. Okay then. Off you go.|n\n")
yield(1) yield (1)
self.room.log(f"QUIT: {self.caller.key} used the quit command") self.room.log(f"QUIT: {self.caller.key} used the quit command")
@ -250,6 +252,7 @@ class CmdLook(CmdEvscapeRoom):
look [obj] look [obj]
""" """
key = "look" key = "look"
aliases = ["l", "ls"] aliases = ["l", "ls"]
obj1_search = None obj1_search = None
@ -276,6 +279,7 @@ class CmdWho(CmdEvscapeRoom, default_cmds.CmdWho):
server as a whole. server as a whole.
""" """
key = "who" key = "who"
obj1_search = False obj1_search = False
@ -285,7 +289,7 @@ class CmdWho(CmdEvscapeRoom, default_cmds.CmdWho):
caller = self.caller caller = self.caller
if self.args == 'all': if self.args == "all":
table = self.style_table("|wName", "|wRoom") table = self.style_table("|wName", "|wRoom")
sessions = SESSION_HANDLER.get_sessions() sessions = SESSION_HANDLER.get_sessions()
for session in sessions: for session in sessions:
@ -298,15 +302,19 @@ class CmdWho(CmdEvscapeRoom, default_cmds.CmdWho):
account = session.get_account() account = session.get_account()
table.add_row(account.get_display_name(caller), "(OOC)") table.add_row(account.get_display_name(caller), "(OOC)")
txt = (f"|cPlayers active on this server|n:\n{table}\n" txt = (
"(use 'who' to see only those in your room)") f"|cPlayers active on this server|n:\n{table}\n"
"(use 'who' to see only those in your room)"
)
else: else:
chars = [f"{obj.get_display_name(caller)} - {obj.db.desc.strip()}" chars = [
for obj in self.room.get_all_characters() f"{obj.get_display_name(caller)} - {obj.db.desc.strip()}"
if obj != caller] for obj in self.room.get_all_characters()
if obj != caller
]
chars = "\n".join([f"{caller.key} - {caller.db.desc.strip()} (you)"] + chars) chars = "\n".join([f"{caller.key} - {caller.db.desc.strip()} (you)"] + chars)
txt = (f"|cPlayers in this room (room-name '{self.room.name}')|n:\n {chars}") txt = f"|cPlayers in this room (room-name '{self.room.name}')|n:\n {chars}"
caller.msg(txt) caller.msg(txt)
@ -320,6 +328,7 @@ class CmdSpeak(Command):
shout shout
""" """
key = "say" key = "say"
aliases = [";", "shout", "whisper"] aliases = [";", "shout", "whisper"]
arg_regex = r"\w|\s|$" arg_regex = r"\w|\s|$"
@ -329,7 +338,7 @@ class CmdSpeak(Command):
args = self.args.strip() args = self.args.strip()
caller = self.caller caller = self.caller
action = self.cmdname action = self.cmdname
action = "say" if action == ';' else action action = "say" if action == ";" else action
room = self.caller.location room = self.caller.location
if not self.args: if not self.args:
@ -365,6 +374,7 @@ class CmdEmote(Command):
emote /me points to /box and /lever. emote /me points to /box and /lever.
""" """
key = "emote" key = "emote"
aliases = [":", "pose"] aliases = [":", "pose"]
arg_regex = r"\w|\s|$" arg_regex = r"\w|\s|$"
@ -379,7 +389,7 @@ class CmdEmote(Command):
emote = self.args.strip() emote = self.args.strip()
if not emote: if not emote:
self.caller.msg("Usage: emote /me points to /door, saying \"look over there!\"") self.caller.msg('Usage: emote /me points to /door, saying "look over there!"')
return return
speech_clr = "|c" speech_clr = "|c"
@ -438,6 +448,7 @@ class CmdFocus(CmdEvscapeRoom):
looks and what actions is available. looks and what actions is available.
""" """
key = "focus" key = "focus"
aliases = ["examine", "e", "ex", "unfocus"] aliases = ["examine", "e", "ex", "unfocus"]
@ -454,7 +465,9 @@ class CmdFocus(CmdEvscapeRoom):
return return
if self.focus != self.obj1: if self.focus != self.obj1:
self.room.msg_room(self.caller, f"~You ~examine *{self.obj1.key}.", skip_caller=True) self.room.msg_room(
self.caller, f"~You ~examine *{self.obj1.key}.", skip_caller=True
)
self.focus = self.obj1 self.focus = self.obj1
self.obj1.at_focus(self.caller) self.obj1.at_focus(self.caller)
elif not self.focus: elif not self.focus:
@ -473,11 +486,13 @@ class CmdOptions(CmdEvscapeRoom):
options options
""" """
key = "options" key = "options"
aliases = ["option"] aliases = ["option"]
def func(self): def func(self):
from .menu import run_option_menu from .menu import run_option_menu
run_option_menu(self.caller, self.session) run_option_menu(self.caller, self.session)
@ -486,6 +501,7 @@ class CmdGet(CmdEvscapeRoom):
Use focus / examine instead. Use focus / examine instead.
""" """
key = "get" key = "get"
aliases = ["inventory", "i", "inv", "give"] aliases = ["inventory", "i", "inv", "give"]
@ -501,6 +517,7 @@ class CmdRerouter(default_cmds.MuxCommand):
<action> [arg] <action> [arg]
""" """
# reroute commands from the default cmdset to the catch-all # reroute commands from the default cmdset to the catch-all
# focus function where needed. This allows us to override # focus function where needed. This allows us to override
# individual default commands without replacing the entire # individual default commands without replacing the entire
@ -512,9 +529,10 @@ class CmdRerouter(default_cmds.MuxCommand):
def func(self): def func(self):
# reroute to another command # reroute to another command
from evennia.commands import cmdhandler from evennia.commands import cmdhandler
cmdhandler.cmdhandler(self.session, self.raw_string,
cmdobj=CmdFocusInteraction(), cmdhandler.cmdhandler(
cmdobj_key=self.cmdname) self.session, self.raw_string, cmdobj=CmdFocusInteraction(), cmdobj_key=self.cmdname
)
class CmdFocusInteraction(CmdEvscapeRoom): class CmdFocusInteraction(CmdEvscapeRoom):
@ -532,6 +550,7 @@ class CmdFocusInteraction(CmdEvscapeRoom):
as keys into the method. as keys into the method.
""" """
# all commands not matching something else goes here. # all commands not matching something else goes here.
key = syscmdkeys.CMD_NOMATCH key = syscmdkeys.CMD_NOMATCH
@ -571,19 +590,18 @@ class CmdStand(CmdEvscapeRoom):
Stand up from whatever position you had. Stand up from whatever position you had.
""" """
key = "stand" key = "stand"
def func(self): def func(self):
# Positionable objects will set this flag on you. # Positionable objects will set this flag on you.
pos = self.caller.attributes.get( pos = self.caller.attributes.get("position", category=self.room.tagcategory)
"position", category=self.room.tagcategory)
if pos: if pos:
# we have a position, clean up. # we have a position, clean up.
obj, position = pos obj, position = pos
self.caller.attributes.remove( self.caller.attributes.remove("position", category=self.room.tagcategory)
"position", category=self.room.tagcategory)
del obj.db.positions[self.caller] del obj.db.positions[self.caller]
self.room.msg_room(self.caller, "~You ~are back standing on the floor again.") self.room.msg_room(self.caller, "~You ~are back standing on the floor again.")
else: else:
@ -598,8 +616,9 @@ class CmdHelp(CmdEvscapeRoom, default_cmds.CmdHelp):
help <topic> or <command> help <topic> or <command>
""" """
key = 'help'
aliases = ['?'] key = "help"
aliases = ["?"]
def func(self): def func(self):
if self.obj1: if self.obj1:
@ -608,8 +627,10 @@ class CmdHelp(CmdEvscapeRoom, default_cmds.CmdHelp):
if not helptxt: if not helptxt:
helptxt = f"There is no help to be had about {self.obj1.get_display_name(self.caller)}." helptxt = f"There is no help to be had about {self.obj1.get_display_name(self.caller)}."
else: else:
helptxt = (f"|y{self.obj1.get_display_name(self.caller)}|n is " helptxt = (
"likely |rnot|n part of any of the Jester's trickery.") f"|y{self.obj1.get_display_name(self.caller)}|n is "
"likely |rnot|n part of any of the Jester's trickery."
)
elif self.arg1: elif self.arg1:
# fall back to the normal help command # fall back to the normal help command
super().func() super().func()
@ -621,6 +642,7 @@ class CmdHelp(CmdEvscapeRoom, default_cmds.CmdHelp):
# Debug/help command # Debug/help command
class CmdCreateObj(CmdEvscapeRoom): class CmdCreateObj(CmdEvscapeRoom):
""" """
Create command, only for Admins during debugging. Create command, only for Admins during debugging.
@ -631,6 +653,7 @@ class CmdCreateObj(CmdEvscapeRoom):
Here, :typeclass is a class in evscaperoom.commands Here, :typeclass is a class in evscaperoom.commands
""" """
key = "createobj" key = "createobj"
aliases = ["cobj"] aliases = ["cobj"]
locks = "cmd:perm(Admin)" locks = "cmd:perm(Admin)"
@ -668,6 +691,7 @@ class CmdSetFlag(CmdEvscapeRoom):
flag <obj> with <flagname> flag <obj> with <flagname>
""" """
key = "flag" key = "flag"
aliases = ["setflag"] aliases = ["setflag"]
locks = "cmd:perm(Admin)" locks = "cmd:perm(Admin)"
@ -700,6 +724,7 @@ class CmdJumpState(CmdEvscapeRoom):
jumpstate <statename> jumpstate <statename>
""" """
key = "jumpstate" key = "jumpstate"
locks = "cmd:perm(Admin)" locks = "cmd:perm(Admin)"
@ -713,22 +738,26 @@ class CmdJumpState(CmdEvscapeRoom):
# Helper command to start the Evscaperoom menu # Helper command to start the Evscaperoom menu
class CmdEvscapeRoomStart(Command): class CmdEvscapeRoomStart(Command):
""" """
Go to the Evscaperoom start menu Go to the Evscaperoom start menu
""" """
key = "evscaperoom" key = "evscaperoom"
help_category = "EvscapeRoom" help_category = "EvscapeRoom"
def func(self): def func(self):
# need to import here to break circular import # need to import here to break circular import
from .menu import run_evscaperoom_menu from .menu import run_evscaperoom_menu
run_evscaperoom_menu(self.caller) run_evscaperoom_menu(self.caller)
# command sets # command sets
class CmdSetEvScapeRoom(CmdSet): class CmdSetEvScapeRoom(CmdSet):
priority = 1 priority = 1

View file

@ -55,7 +55,7 @@ def _move_to_room(caller, raw_string, **kwargs):
Helper to move a user to a room Helper to move a user to a room
""" """
room = kwargs['room'] room = kwargs["room"]
room.msg_char(caller, f"Entering room |c'{room.name}'|n ...") room.msg_char(caller, f"Entering room |c'{room.name}'|n ...")
room.msg_room(caller, f"~You |c~were just tricked in here too!|n") room.msg_room(caller, f"~You |c~were just tricked in here too!|n")
# we do a manual move since we don't want all hooks to fire. # we do a manual move since we don't want all hooks to fire.
@ -78,7 +78,9 @@ def _create_new_room(caller, raw_string, **kwargs):
_move_to_room(caller, "", room=room) _move_to_room(caller, "", room=room)
nrooms = EvscapeRoom.objects.all().count() nrooms = EvscapeRoom.objects.all().count()
logger.log_info(f"Evscaperoom: {caller.key} created room '{key}' (#{room.id}). Now {nrooms} room(s) active.") logger.log_info(
f"Evscaperoom: {caller.key} created room '{key}' (#{room.id}). Now {nrooms} room(s) active."
)
room.log(f"JOIN: {caller.key} created and joined room") room.log(f"JOIN: {caller.key} created and joined room")
return "node_quit", {"quiet": True} return "node_quit", {"quiet": True}
@ -96,10 +98,12 @@ def _get_all_rooms(caller):
if not room.pk or room.db.deleting: if not room.pk or room.db.deleting:
continue continue
stats = room.db.stats or {"progress": 0} stats = room.db.stats or {"progress": 0}
progress = int(stats['progress']) progress = int(stats["progress"])
nplayers = len(room.get_all_characters()) nplayers = len(room.get_all_characters())
desc = (f"Join room |c'{room.get_display_name(caller)}'|n " desc = (
f"(complete: {progress}%, players: {nplayers})") f"Join room |c'{room.get_display_name(caller)}'|n "
f"(complete: {progress}%, players: {nplayers})"
)
room_map[desc] = room room_map[desc] = room
room_option_descs.append(desc) room_option_descs.append(desc)
caller.ndb._menutree.room_map = room_map caller.ndb._menutree.room_map = room_map
@ -121,24 +125,34 @@ def node_start(caller, raw_string, **kwargs):
# build a list of available rooms # build a list of available rooms
options = ( options = (
{"key": ("|y[s]et your description|n", "set your description", {
"set", "desc", "description", "s"), "key": (
"goto": "node_set_desc"}, "|y[s]et your description|n",
{"key": ("|y[c]reate/join a new room|n", "create a new room", "create", "c"), "set your description",
"goto": "node_create_room"}, "set",
{"key": ("|r[q]uit the challenge", "quit", "q"), "desc",
"goto": "node_quit"}) "description",
"s",
),
"goto": "node_set_desc",
},
{
"key": ("|y[c]reate/join a new room|n", "create a new room", "create", "c"),
"goto": "node_create_room",
},
{"key": ("|r[q]uit the challenge", "quit", "q"), "goto": "node_quit"},
)
return text, options return text, options
def node_set_desc(caller, raw_string, **kwargs): def node_set_desc(caller, raw_string, **kwargs):
current_desc = kwargs.get('desc', caller.db.desc) current_desc = kwargs.get("desc", caller.db.desc)
text = ("Your current description is\n\n " text = (
f" \"{current_desc}\"" "Your current description is\n\n " f' "{current_desc}"' "\n\nEnter your new description!"
"\n\nEnter your new description!") )
def _temp_description(caller, raw_string, **kwargs): def _temp_description(caller, raw_string, **kwargs):
desc = raw_string.strip() desc = raw_string.strip()
@ -154,12 +168,10 @@ def node_set_desc(caller, raw_string, **kwargs):
return "node_start" return "node_start"
options = ( options = (
{"key": "_default", {"key": "_default", "goto": _temp_description},
"goto": _temp_description}, {"key": ("|g[a]ccept", "a"), "goto": (_set_description, {"desc": current_desc})},
{"key": ("|g[a]ccept", "a"), {"key": ("|r[c]ancel", "c"), "goto": "node_start"},
"goto": (_set_description, {"desc": current_desc})}, )
{"key": ("|r[c]ancel", "c"),
"goto": "node_start"})
return text, options return text, options
@ -168,34 +180,31 @@ def node_create_room(caller, raw_string, **kwargs):
text = _CREATE_ROOM_TEXT text = _CREATE_ROOM_TEXT
options = ( options = (
{"key": ("|g[c]reate new room and start game|n", "c"), {"key": ("|g[c]reate new room and start game|n", "c"), "goto": _create_new_room},
"goto": _create_new_room}, {"key": ("|r[a]bort and go back|n", "a"), "goto": "node_start"},
{"key": ("|r[a]bort and go back|n", "a"), )
"goto": "node_start"})
return text, options return text, options
def node_join_room(caller, raw_string, **kwargs): def node_join_room(caller, raw_string, **kwargs):
room = kwargs['room'] room = kwargs["room"]
stats = room.db.stats or {"progress": 0} stats = room.db.stats or {"progress": 0}
players = [char.key for char in room.get_all_characters()] players = [char.key for char in room.get_all_characters()]
text = _JOIN_EXISTING_ROOM_TEXT.format( text = _JOIN_EXISTING_ROOM_TEXT.format(
roomname=room.get_display_name(caller), roomname=room.get_display_name(caller),
percent=int(stats['progress']), percent=int(stats["progress"]),
nplayers=len(players), nplayers=len(players),
players=list_to_string(players) players=list_to_string(players),
) )
options = ( options = (
{"key": ("|g[a]ccept|n (default)", "a"), {"key": ("|g[a]ccept|n (default)", "a"), "goto": (_move_to_room, kwargs)},
"goto": (_move_to_room, kwargs)}, {"key": ("|r[c]ancel|n", "c"), "goto": "node_start"},
{"key": ("|r[c]ancel|n", "c"), {"key": "_default", "goto": (_move_to_room, kwargs)},
"goto": "node_start"}, )
{"key": "_default",
"goto": (_move_to_room, kwargs)})
return text, options return text, options
@ -210,9 +219,10 @@ def node_quit(caller, raw_string, **kwargs):
if caller.db.evscaperoom_standalone: if caller.db.evscaperoom_standalone:
from evennia.commands import cmdhandler from evennia.commands import cmdhandler
from evennia import default_cmds from evennia import default_cmds
cmdhandler.cmdhandler(caller.ndb._menutree._session, "",
cmdobj=default_cmds.CmdQuit(), cmdhandler.cmdhandler(
cmdobj_key="@quit") caller.ndb._menutree._session, "", cmdobj=default_cmds.CmdQuit(), cmdobj_key="@quit"
)
return text, None # empty options exit the menu return text, None # empty options exit the menu
@ -222,10 +232,11 @@ class EvscaperoomMenu(EvMenu):
Custom menu with a different formatting of options. Custom menu with a different formatting of options.
""" """
node_border_char = "~" node_border_char = "~"
def nodetext_formatter(self, text): def nodetext_formatter(self, text):
return justify(text.strip("\n").rstrip(), align='c', indent=1) return justify(text.strip("\n").rstrip(), align="c", indent=1)
def options_formatter(self, optionlist): def options_formatter(self, optionlist):
main_options = [] main_options = []
@ -237,9 +248,7 @@ class EvscaperoomMenu(EvMenu):
main_options.append(key) main_options.append(key)
main_options = " | ".join(main_options) main_options = " | ".join(main_options)
room_choices = super().options_formatter(room_choices) room_choices = super().options_formatter(room_choices)
return "{}{}{}".format(main_options, return "{}{}{}".format(main_options, "\n\n" if room_choices else "", room_choices)
"\n\n" if room_choices else "",
room_choices)
# access function # access function
@ -248,20 +257,22 @@ def run_evscaperoom_menu(caller):
Run room selection menu Run room selection menu
""" """
menutree = {"node_start": node_start, menutree = {
"node_quit": node_quit, "node_start": node_start,
"node_set_desc": node_set_desc, "node_quit": node_quit,
"node_create_room": node_create_room, "node_set_desc": node_set_desc,
"node_join_room": node_join_room} "node_create_room": node_create_room,
"node_join_room": node_join_room,
}
EvscaperoomMenu(caller, menutree, startnode="node_start", EvscaperoomMenu(caller, menutree, startnode="node_start", cmd_on_exit=None, auto_quit=True)
cmd_on_exit=None, auto_quit=True)
# ------------------------------------------------------------ # ------------------------------------------------------------
# In-game Options menu # In-game Options menu
# ------------------------------------------------------------ # ------------------------------------------------------------
def _set_thing_style(caller, raw_string, **kwargs): def _set_thing_style(caller, raw_string, **kwargs):
room = caller.location room = caller.location
options = caller.attributes.get("options", category=room.tagcategory, default={}) options = caller.attributes.get("options", category=room.tagcategory, default={})
@ -272,7 +283,7 @@ def _set_thing_style(caller, raw_string, **kwargs):
def _toggle_screen_reader(caller, raw_string, **kwargs): def _toggle_screen_reader(caller, raw_string, **kwargs):
session = kwargs['session'] session = kwargs["session"]
# flip old setting # flip old setting
session.protocol_flags["SCREENREADER"] = not session.protocol_flags.get("SCREENREADER", False) session.protocol_flags["SCREENREADER"] = not session.protocol_flags.get("SCREENREADER", False)
# sync setting with portal # sync setting with portal
@ -287,22 +298,33 @@ def node_options(caller, raw_string, **kwargs):
options = caller.attributes.get("options", category=room.tagcategory, default={}) options = caller.attributes.get("options", category=room.tagcategory, default={})
things_style = options.get("things_style", 2) things_style = options.get("things_style", 2)
session = kwargs['session'] # we give this as startnode_input when starting menu session = kwargs["session"] # we give this as startnode_input when starting menu
screenreader = session.protocol_flags.get("SCREENREADER", False) screenreader = session.protocol_flags.get("SCREENREADER", False)
options = ( options = (
{"desc": "{}No item markings (hard mode)".format( {
"|g(*)|n " if things_style == 0 else "( ) "), "desc": "{}No item markings (hard mode)".format(
"goto": (_set_thing_style, {"value": 0, 'session': session})}, "|g(*)|n " if things_style == 0 else "( ) "
{"desc": "{}Items marked as |yitem|n (with color)".format( ),
"|g(*)|n " if things_style == 1 else "( ) "), "goto": (_set_thing_style, {"value": 0, "session": session}),
"goto": (_set_thing_style, {"value": 1, 'session': session})}, },
{"desc": "{}Items are marked as |y[item]|n (screenreader friendly)".format( {
"|g(*)|n " if things_style == 2 else "( ) "), "desc": "{}Items marked as |yitem|n (with color)".format(
"goto": (_set_thing_style, {"value": 2, 'session': session})}, "|g(*)|n " if things_style == 1 else "( ) "
{"desc": "{}Screenreader mode".format( ),
"(*) " if screenreader else "( ) "), "goto": (_set_thing_style, {"value": 1, "session": session}),
"goto": (_toggle_screen_reader, kwargs)}) },
{
"desc": "{}Items are marked as |y[item]|n (screenreader friendly)".format(
"|g(*)|n " if things_style == 2 else "( ) "
),
"goto": (_set_thing_style, {"value": 2, "session": session}),
},
{
"desc": "{}Screenreader mode".format("(*) " if screenreader else "( ) "),
"goto": (_toggle_screen_reader, kwargs),
},
)
return text, options return text, options
@ -310,6 +332,7 @@ class OptionsMenu(EvMenu):
""" """
Custom display of Option menu Custom display of Option menu
""" """
def node_formatter(self, nodetext, optionstext): def node_formatter(self, nodetext, optionstext):
return f"{nodetext}\n\n{optionstext}" return f"{nodetext}\n\n{optionstext}"
@ -321,5 +344,11 @@ def run_option_menu(caller, session):
""" """
menutree = {"node_start": node_options} menutree = {"node_start": node_options}
OptionsMenu(caller, menutree, startnode="node_start", OptionsMenu(
cmd_on_exit="look", auto_quit=True, startnode_input=("", {"session": session})) caller,
menutree,
startnode="node_start",
cmd_on_exit="look",
auto_quit=True,
startnode_input=("", {"session": session}),
)

View file

@ -56,6 +56,7 @@ class EvscaperoomObject(DefaultObject):
Default object base for all objects related to the contrib. Default object base for all objects related to the contrib.
""" """
# these will be automatically filtered out by self.parse for # these will be automatically filtered out by self.parse for
# focus-commands using arguments like (`combine [with] object`) # focus-commands using arguments like (`combine [with] object`)
# override this per-class as necessary. # override this per-class as necessary.
@ -63,10 +64,7 @@ class EvscaperoomObject(DefaultObject):
# this mapping allows for prettier descriptions of our current # this mapping allows for prettier descriptions of our current
# position # position
position_prep_map = {"sit": "sitting", position_prep_map = {"sit": "sitting", "kneel": "kneeling", "lie": "lying", "climb": "standing"}
"kneel": "kneeling",
"lie": "lying",
"climb": "standing"}
def at_object_creation(self): def at_object_creation(self):
""" """
@ -86,8 +84,9 @@ class EvscaperoomObject(DefaultObject):
@property @property
def tagcategory(self): def tagcategory(self):
if not self._tagcategory: if not self._tagcategory:
self._tagcategory = (self.location.db.tagcategory self._tagcategory = (
if self.location else self.db.tagcategory) self.location.db.tagcategory if self.location else self.db.tagcategory
)
return self._tagcategory return self._tagcategory
@property @property
@ -162,16 +161,15 @@ class EvscaperoomObject(DefaultObject):
you = caller.key if caller else "they" you = caller.key if caller else "they"
first_person, third_person = parse_for_perspectives(string, you=you) first_person, third_person = parse_for_perspectives(string, you=you)
for char in self.room.get_all_characters(): for char in self.room.get_all_characters():
options = char.attributes.get( options = char.attributes.get("options", category=self.room.tagcategory, default={})
"options", category=self.room.tagcategory, default={})
style = options.get("things_style", 2) style = options.get("things_style", 2)
if char == caller: if char == caller:
if not skip_caller: if not skip_caller:
txt = parse_for_things(first_person, things_style=style) txt = parse_for_things(first_person, things_style=style)
char.msg((txt, {'type': 'your_action'})) char.msg((txt, {"type": "your_action"}))
else: else:
txt = parse_for_things(third_person, things_style=style) txt = parse_for_things(third_person, things_style=style)
char.msg((txt, {'type': 'others_action'})) char.msg((txt, {"type": "others_action"}))
def msg_char(self, caller, string, client_type="your_action"): def msg_char(self, caller, string, client_type="your_action"):
""" """
@ -180,8 +178,7 @@ class EvscaperoomObject(DefaultObject):
""" """
# we must clean away markers # we must clean away markers
first_person, _ = parse_for_perspectives(string) first_person, _ = parse_for_perspectives(string)
options = caller.attributes.get( options = caller.attributes.get("options", category=self.room.tagcategory, default={})
"options", category=self.room.tagcategory, default={})
style = options.get("things_style", 2) style = options.get("things_style", 2)
txt = parse_for_things(first_person, things_style=style) txt = parse_for_things(first_person, things_style=style)
caller.msg((txt, {"type": client_type})) caller.msg((txt, {"type": client_type}))
@ -227,8 +224,7 @@ class EvscaperoomObject(DefaultObject):
""" """
if new_position is None: if new_position is None:
# reset position # reset position
caller.attributes.remove( caller.attributes.remove("position", category=self.tagcategory)
"position", category=self.tagcategory)
if caller in self.db.positions: if caller in self.db.positions:
del self.db.positions[caller] del self.db.positions[caller]
else: else:
@ -279,8 +275,9 @@ class EvscaperoomObject(DefaultObject):
here. here.
""" """
args = re.sub(r"|".join(r"^{}\s".format(prep) for prep in self.action_prepositions), args = re.sub(
"", args) r"|".join(r"^{}\s".format(prep) for prep in self.action_prepositions), "", args
)
return args return args
def get_cmd_signatures(self): def get_cmd_signatures(self):
@ -307,11 +304,11 @@ class EvscaperoomObject(DefaultObject):
command_signatures = sorted(command_signatures) command_signatures = sorted(command_signatures)
if len(command_signatures) == 1: if len(command_signatures) == 1:
helpstr = (f"It looks like {self.key} may be " helpstr = f"It looks like {self.key} may be " "suitable to {callsigns}."
"suitable to {callsigns}.")
else: else:
helpstr = (f"At first glance, it looks like {self.key} might be " helpstr = (
"suitable to {callsigns}.") f"At first glance, it looks like {self.key} might be " "suitable to {callsigns}."
)
return command_signatures, helpstr return command_signatures, helpstr
def get_short_desc(self, full_desc): def get_short_desc(self, full_desc):
@ -319,7 +316,7 @@ class EvscaperoomObject(DefaultObject):
Extract the first sentence from the desc and use as the short desc. Extract the first sentence from the desc and use as the short desc.
""" """
mat = re.match(r"(^.*?[.?!])", full_desc.strip(), re.M+re.U+re.I+re.S) mat = re.match(r"(^.*?[.?!])", full_desc.strip(), re.M + re.U + re.I + re.S)
if mat: if mat:
return mat.group(0).strip() return mat.group(0).strip()
return full_desc return full_desc
@ -336,8 +333,7 @@ class EvscaperoomObject(DefaultObject):
callsigns = list_to_string(["*" + sig for sig in command_signatures], endsep="or") callsigns = list_to_string(["*" + sig for sig in command_signatures], endsep="or")
# parse for *thing markers (use these as items) # parse for *thing markers (use these as items)
options = caller.attributes.get( options = caller.attributes.get("options", category=self.room.tagcategory, default={})
"options", category=self.room.tagcategory, default={})
style = options.get("things_style", 2) style = options.get("things_style", 2)
helpstr = helpstr.format(callsigns=callsigns) helpstr = helpstr.format(callsigns=callsigns)
@ -354,7 +350,7 @@ class EvscaperoomObject(DefaultObject):
# accept a custom desc # accept a custom desc
desc = kwargs.get("desc", self.db.desc) desc = kwargs.get("desc", self.db.desc)
if kwargs.get('unfocused', False): if kwargs.get("unfocused", False):
# use the shorter description # use the shorter description
focused = "" focused = ""
desc = self.get_short_desc(desc) desc = self.get_short_desc(desc)
@ -364,8 +360,11 @@ class EvscaperoomObject(DefaultObject):
helptxt = kwargs.get("helptxt", f"\n\n({self.get_help(looker)})") helptxt = kwargs.get("helptxt", f"\n\n({self.get_help(looker)})")
obj, pos = self.get_position(looker) obj, pos = self.get_position(looker)
pos = (f" |w({self.position_prep_map[pos]} on " pos = (
f"{obj.get_display_name(looker)})" if obj else "") f" |w({self.position_prep_map[pos]} on " f"{obj.get_display_name(looker)})"
if obj
else ""
)
return f" ~~ |y{self.get_display_name(looker)}|n{focused}{pos}|n ~~\n\n{desc}{helptxt}" return f" ~~ |y{self.get_display_name(looker)}|n{focused}{pos}|n ~~\n\n{desc}{helptxt}"
@ -375,6 +374,7 @@ class Feelable(EvscaperoomObject):
Any object that you can feel the surface of. Any object that you can feel the surface of.
""" """
def at_focus_feel(self, caller, **kwargs): def at_focus_feel(self, caller, **kwargs):
self.msg_char(caller, f"You feel *{self.key}.") self.msg_char(caller, f"You feel *{self.key}.")
@ -384,6 +384,7 @@ class Listenable(EvscaperoomObject):
Any object one can listen to. Any object one can listen to.
""" """
def at_focus_listen(self, caller, **kwargs): def at_focus_listen(self, caller, **kwargs):
self.msg_char(caller, f"You listen to *{self.key}") self.msg_char(caller, f"You listen to *{self.key}")
@ -393,6 +394,7 @@ class Smellable(EvscaperoomObject):
Any object you can smell. Any object you can smell.
""" """
def at_focus_smell(self, caller, **kwargs): def at_focus_smell(self, caller, **kwargs):
self.msg_char(caller, f"You smell *{self.key}.") self.msg_char(caller, f"You smell *{self.key}.")
@ -402,6 +404,7 @@ class Rotatable(EvscaperoomObject):
Any object that you can lift up and look at from different angles Any object that you can lift up and look at from different angles
""" """
rotate_flag = "rotatable" rotate_flag = "rotatable"
start_rotatable = True start_rotatable = True
@ -417,6 +420,7 @@ class Rotatable(EvscaperoomObject):
self.at_rotate(caller) self.at_rotate(caller)
else: else:
self.at_cannot_rotate(caller) self.at_cannot_rotate(caller)
at_focus_turn = at_focus_rotate at_focus_turn = at_focus_rotate
def at_rotate(self, caller): def at_rotate(self, caller):
@ -432,6 +436,7 @@ class Openable(EvscaperoomObject):
a flag. a flag.
""" """
# this flag must be set for item to open. None for unlocked. # this flag must be set for item to open. None for unlocked.
unlock_flag = "unlocked" unlock_flag = "unlocked"
open_flag = "open" open_flag = "open"
@ -482,6 +487,7 @@ class Readable(EvscaperoomObject):
from a flag. from a flag.
""" """
# this must be set to be able to read. None to # this must be set to be able to read. None to
# always be able to read. # always be able to read.
@ -515,11 +521,7 @@ class IndexReadable(Readable):
""" """
# keys should be lower-key # keys should be lower-key
index = { index = {"page1": "This is page1", "page2": "This is page2", "page two": "page2"} # alias
"page1": "This is page1",
"page2": "This is page2",
"page two": "page2" # alias
}
def at_focus_read(self, caller, **kwargs): def at_focus_read(self, caller, **kwargs):
@ -536,8 +538,10 @@ class IndexReadable(Readable):
self.at_read(caller, topic, entry) self.at_read(caller, topic, entry)
def get_cmd_signatures(self): def get_cmd_signatures(self):
txt = (f"You don't have the time to read this from beginning to end. " txt = (
"Use *read <topic> to look up something in particular.") f"You don't have the time to read this from beginning to end. "
"Use *read <topic> to look up something in particular."
)
return [], txt return [], txt
def at_cannot_read(self, caller, topic, *args, **kwargs): def at_cannot_read(self, caller, topic, *args, **kwargs):
@ -556,10 +560,10 @@ class Movable(EvscaperoomObject):
change. change.
""" """
# these are the possible locations (or directions) to move to # these are the possible locations (or directions) to move to
# name: callable # name: callable
move_positions = {"left": "at_left", move_positions = {"left": "at_left", "right": "at_right"}
"right": "at_right"}
start_position = "left" start_position = "left"
def at_object_creation(self): def at_object_creation(self):
@ -571,7 +575,7 @@ class Movable(EvscaperoomObject):
return ["move", "push", "shove left/right"], txt return ["move", "push", "shove left/right"], txt
def at_focus_move(self, caller, **kwargs): def at_focus_move(self, caller, **kwargs):
pos = self.parse(kwargs['args']) pos = self.parse(kwargs["args"])
callfunc_name = self.move_positions.get(pos) callfunc_name = self.move_positions.get(pos)
if callfunc_name: if callfunc_name:
@ -610,6 +614,7 @@ class BaseConsumable(EvscaperoomObject):
a custom object if needed). a custom object if needed).
""" """
consume_flag = "consume" consume_flag = "consume"
# may only consume once # may only consume once
one_consume_only = True one_consume_only = True
@ -647,6 +652,7 @@ class Edible(BaseConsumable):
Any object specifically possible to eat. Any object specifically possible to eat.
""" """
consume_flag = "eat" consume_flag = "eat"
def at_focus_eat(self, caller, **kwargs): def at_focus_eat(self, caller, **kwargs):
@ -658,6 +664,7 @@ class Drinkable(BaseConsumable):
Any object specifically possible to drink. Any object specifically possible to drink.
""" """
consume_flag = "drink" consume_flag = "drink"
def at_focus_drink(self, caller, **kwargs): def at_focus_drink(self, caller, **kwargs):
@ -679,6 +686,7 @@ class BaseApplicable(EvscaperoomObject):
This acts an an abstract base class. This acts an an abstract base class.
""" """
# the target object this is to be used with must # the target object this is to be used with must
# have this flag. It'll likely be unique to this # have this flag. It'll likely be unique to this
# object combination. # object combination.
@ -689,7 +697,7 @@ class BaseApplicable(EvscaperoomObject):
Wrap this with the at_focus methods in the child classes Wrap this with the at_focus methods in the child classes
""" """
args = self.parse(kwargs['args']) args = self.parse(kwargs["args"])
if not args: if not args:
self.msg_char(caller, "You need to specify a target.") self.msg_char(caller, "You need to specify a target.")
return return
@ -717,6 +725,7 @@ class Usable(BaseApplicable):
Any object that can be used with another object. Any object that can be used with another object.
""" """
target_flag = "usable" target_flag = "usable"
def at_focus_use(self, caller, **kwargs): def at_focus_use(self, caller, **kwargs):
@ -736,6 +745,7 @@ class Insertable(BaseApplicable):
This would cover a key, for example. This would cover a key, for example.
""" """
# this would likely be a custom name # this would likely be a custom name
target_flag = "insertable" target_flag = "insertable"
@ -759,6 +769,7 @@ class Combinable(BaseApplicable):
a new one. a new one.
""" """
# the other object must have this flag to be able to be combined # the other object must have this flag to be able to be combined
# (this is likely unique for a given combination) # (this is likely unique for a given combination)
target_flag = "combinable" target_flag = "combinable"
@ -767,7 +778,8 @@ class Combinable(BaseApplicable):
new_create_dict = { new_create_dict = {
"typeclass": "evscaperoom.objects.Combinable", "typeclass": "evscaperoom.objects.Combinable",
"key": "sword", "key": "sword",
"aliases": ["combined"]} "aliases": ["combined"],
}
# if set, destroy the two components used to make the new one # if set, destroy the two components used to make the new one
destroy_components = True destroy_components = True
@ -784,11 +796,12 @@ class Combinable(BaseApplicable):
def at_apply(self, caller, action, other_obj): def at_apply(self, caller, action, other_obj):
create_dict = self.new_create_dict create_dict = self.new_create_dict
if "location" not in create_dict: if "location" not in create_dict:
create_dict['location'] = self.location create_dict["location"] = self.location
new_obj = create_evscaperoom_object(**create_dict) new_obj = create_evscaperoom_object(**create_dict)
if new_obj and self.destroy_components: if new_obj and self.destroy_components:
self.msg_char(caller, self.msg_char(
f"You combine *{self.key} with {other_obj.key} to make {new_obj.key}!") caller, f"You combine *{self.key} with {other_obj.key} to make {new_obj.key}!"
)
other_obj.delete() other_obj.delete()
self.delete() self.delete()
@ -800,14 +813,11 @@ class Mixable(EvscaperoomObject):
the ingredients should be 'used' with this object in order the ingredients should be 'used' with this object in order
mix, calling at_mix when they do. mix, calling at_mix when they do.
""" """
# ingredients can check for this before they allow to mix at all # ingredients can check for this before they allow to mix at all
mixer_flag = "mixer" mixer_flag = "mixer"
# ingredients must have these flags and this order # ingredients must have these flags and this order
ingredient_recipe = [ ingredient_recipe = ["ingredient1", "ingredient2", "ingredient3"]
"ingredient1",
"ingredient2",
"ingredient3"
]
def at_object_creation(self): def at_object_creation(self):
super().at_object_creation() super().at_object_creation()
@ -847,7 +857,9 @@ class Mixable(EvscaperoomObject):
if self.check_mixture(): if self.check_mixture():
self.at_mix_success(caller, ingredient, **kwargs) self.at_mix_success(caller, ingredient, **kwargs)
else: else:
self.room.log(f"{self.name} mix failure: Tried {' + '.join([ing.key for ing in self.db.ingredients if ing])}") self.room.log(
f"{self.name} mix failure: Tried {' + '.join([ing.key for ing in self.db.ingredients if ing])}"
)
self.db.ingredients = [] self.db.ingredients = []
self.at_mix_failure(caller, ingredient, **kwargs) self.at_mix_failure(caller, ingredient, **kwargs)
@ -866,25 +878,31 @@ class HasButtons(EvscaperoomObject):
Any object with buttons to push/press Any object with buttons to push/press
""" """
# mapping keys/aliases to calling method # mapping keys/aliases to calling method
buttons = {'green button': "at_green_button", buttons = {
'green': "at_green_button", "green button": "at_green_button",
'red button': "at_red_button", "green": "at_green_button",
'red': "at_red_button"} "red button": "at_red_button",
"red": "at_red_button",
}
def get_cmd_signatures(self): def get_cmd_signatures(self):
helptxt = ("It looks like you should be able to operate " helptxt = (
f"*{self.key} by means of " "It looks like you should be able to operate "
"{callsigns}.") f"*{self.key} by means of "
"{callsigns}."
)
return ["push", "press red/green button"], helptxt return ["push", "press red/green button"], helptxt
def at_focus_press(self, caller, **kwargs): def at_focus_press(self, caller, **kwargs):
arg = self.parse(kwargs['args']) arg = self.parse(kwargs["args"])
callfunc_name = self.buttons.get(arg) callfunc_name = self.buttons.get(arg)
if callfunc_name: if callfunc_name:
getattr(self, callfunc_name)(caller) getattr(self, callfunc_name)(caller)
else: else:
self.at_nomatch(caller) self.at_nomatch(caller)
at_focus_push = at_focus_press at_focus_push = at_focus_press
def at_nomatch(self, caller): def at_nomatch(self, caller):
@ -903,6 +921,7 @@ class CodeInput(EvscaperoomObject):
to have an effect happen. to have an effect happen.
""" """
# the code of this # the code of this
code = "PASSWORD" code = "PASSWORD"
code_hint = "eight letters A-Z" code_hint = "eight letters A-Z"
@ -912,7 +931,7 @@ class CodeInput(EvscaperoomObject):
def at_focus_code(self, caller, **kwargs): def at_focus_code(self, caller, **kwargs):
args = self.parse(kwargs['args'].strip()) args = self.parse(kwargs["args"].strip())
if not args: if not args:
self.at_no_code(caller) self.at_no_code(caller)
@ -967,6 +986,7 @@ class BasePositionable(EvscaperoomObject):
object or not. object or not.
""" """
def at_object_creation(self): def at_object_creation(self):
super().at_object_creation() super().at_object_creation()
# mapping {object: position}. # mapping {object: position}.
@ -992,11 +1012,16 @@ class BasePositionable(EvscaperoomObject):
self.at_position(caller, new_pos) self.at_position(caller, new_pos)
def at_cannot_position(self, caller, position, old_obj, old_pos): def at_cannot_position(self, caller, position, old_obj, old_pos):
self.msg_char(caller, f"You can't; you are currently {self.position_prep_map[old_pos]} on *{old_obj.key} " self.msg_char(
"(better |wstand|n first).") caller,
f"You can't; you are currently {self.position_prep_map[old_pos]} on *{old_obj.key} "
"(better |wstand|n first).",
)
def at_again_position(self, caller, position): def at_again_position(self, caller, position):
self.msg_char(caller, f"But you are already {self.position_prep_map[position]} on *{self.key}?") self.msg_char(
caller, f"But you are already {self.position_prep_map[position]} on *{self.key}?"
)
def at_position(self, caller, position): def at_position(self, caller, position):
self.msg_room(caller, f"~You ~{position} on *{self.key}.") self.msg_room(caller, f"~You ~{position} on *{self.key}.")
@ -1017,6 +1042,7 @@ class Liable(BasePositionable):
Any object you can lie down on. Any object you can lie down on.
""" """
def at_focus_lie(self, caller, **kwargs): def at_focus_lie(self, caller, **kwargs):
super().handle_position(caller, "lie", **kwargs) super().handle_position(caller, "lie", **kwargs)
@ -1026,6 +1052,7 @@ class Kneelable(BasePositionable):
Any object you can kneel on. Any object you can kneel on.
""" """
def at_focus_kneel(self, caller, **kwargs): def at_focus_kneel(self, caller, **kwargs):
super().handle_position(caller, "kneel", **kwargs) super().handle_position(caller, "kneel", **kwargs)
@ -1037,6 +1064,7 @@ class Climbable(BasePositionable):
command, which resets your position. command, which resets your position.
""" """
def at_focus_climb(self, caller, **kwargs): def at_focus_climb(self, caller, **kwargs):
super().handle_position(caller, "climb", **kwargs) super().handle_position(caller, "climb", **kwargs)
@ -1047,6 +1075,7 @@ class Positionable(Sittable, Liable, Kneelable, Climbable):
supported ways (sit, lie, kneel or climb) supported ways (sit, lie, kneel or climb)
""" """
def get_cmd_signatures(self): def get_cmd_signatures(self):
txt = "It looks like you can {callsigns} on it." txt = "It looks like you can {callsigns} on it."
return ["sit", "lie", "kneel", "climb"], txt return ["sit", "lie", "kneel", "climb"], txt

View file

@ -32,7 +32,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
super().at_object_creation() super().at_object_creation()
# starting state # starting state
self.db.state = None # name self.db.state = None # name
self.db.prev_state = None self.db.prev_state = None
# this is used for tagging of all objects belonging to this # this is used for tagging of all objects belonging to this
@ -43,11 +43,11 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
# room progress statistics # room progress statistics
self.db.stats = { self.db.stats = {
"progress": 0, # in percent "progress": 0, # in percent
"score": {}, # reason: score "score": {}, # reason: score
"max_score": 100, "max_score": 100,
"hints_used": 0, # total across all states "hints_used": 0, # total across all states
"hints_total": 41, "hints_total": 41,
"total_achievements": 14 "total_achievements": 14,
} }
self.cmdset.add(CmdSetEvScapeRoom, permanent=True) self.cmdset.add(CmdSetEvScapeRoom, permanent=True)
@ -69,21 +69,21 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
caller = f"[caller.key]: " if caller else "" caller = f"[caller.key]: " if caller else ""
logger.log_file( logger.log_file(
strip_ansi(f"{caller}{message.strip()}"), strip_ansi(f"{caller}{message.strip()}"), filename=self.tagcategory + ".log"
filename=self.tagcategory + ".log") )
def score(self, new_score, reason): def score(self, new_score, reason):
""" """
We don't score individually but for everyone in room together. We don't score individually but for everyone in room together.
You can only be scored for a given reason once.""" You can only be scored for a given reason once."""
if reason not in self.db.stats['score']: if reason not in self.db.stats["score"]:
self.log(f"score: {reason} ({new_score}pts)") self.log(f"score: {reason} ({new_score}pts)")
self.db.stats['score'][reason] = new_score self.db.stats["score"][reason] = new_score
def progress(self, new_progress): def progress(self, new_progress):
"Progress is what we set it to be (0-100%)" "Progress is what we set it to be (0-100%)"
self.log(f"progress: {new_progress}%") self.log(f"progress: {new_progress}%")
self.db.stats['progress'] = new_progress self.db.stats["progress"] = new_progress
def achievement(self, caller, achievement, subtext=""): def achievement(self, caller, achievement, subtext=""):
""" """
@ -96,8 +96,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
subtext (str, optional): Eventual subtext/explanation subtext (str, optional): Eventual subtext/explanation
of the achievement. of the achievement.
""" """
achievements = caller.attributes.get( achievements = caller.attributes.get("achievements", category=self.tagcategory)
"achievements", category=self.tagcategory)
if not achievements: if not achievements:
achievements = {} achievements = {}
if achievement not in achievements: if achievement not in achievements:
@ -173,6 +172,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
""" """
self.log(f"EXIT: {char} left room") self.log(f"EXIT: {char} left room")
from .menu import run_evscaperoom_menu from .menu import run_evscaperoom_menu
self.character_cleanup(char) self.character_cleanup(char)
char.location = char.home char.location = char.home
@ -223,15 +223,18 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom):
def return_appearance(self, looker, **kwargs): def return_appearance(self, looker, **kwargs):
obj, pos = self.get_position(looker) obj, pos = self.get_position(looker)
pos = (f"\n|x[{self.position_prep_map[pos]} on " pos = (
f"{obj.get_display_name(looker)}]|n" if obj else "") f"\n|x[{self.position_prep_map[pos]} on " f"{obj.get_display_name(looker)}]|n"
if obj
else ""
)
admin_only = "" admin_only = ""
if self.check_perm(looker, "Admin"): if self.check_perm(looker, "Admin"):
# only for admins # only for admins
objs = DefaultObject.objects.filter_family( objs = DefaultObject.objects.filter_family(db_location=self).exclude(id=looker.id)
db_location=self).exclude(id=looker.id) admin_only = "\n|xAdmin only: " + list_to_string(
admin_only = "\n|xAdmin only: " + \ [obj.get_display_name(looker) for obj in objs]
list_to_string([obj.get_display_name(looker) for obj in objs]) )
return f"{self.db.desc}{pos}{admin_only}" return f"{self.db.desc}{pos}{admin_only}"

View file

@ -13,7 +13,6 @@ from evscaperoom.room import EvscapeRoom
class CleanupScript(DefaultScript): class CleanupScript(DefaultScript):
def at_script_creation(self): def at_script_creation(self):
self.key = "evscaperoom_cleanup" self.key = "evscaperoom_cleanup"

View file

@ -35,11 +35,13 @@ _GA = object.__getattribute__
# handler for managing states on room # handler for managing states on room
class StateHandler(object): class StateHandler(object):
""" """
This sits on the room and is used to progress through the states. This sits on the room and is used to progress through the states.
""" """
def __init__(self, room): def __init__(self, room):
self.room = room self.room = room
self.current_state_name = room.db.state or _FIRST_STATE self.current_state_name = room.db.state or _FIRST_STATE
@ -109,12 +111,14 @@ class StateHandler(object):
# base state class # base state class
class BaseState(object): class BaseState(object):
""" """
Base object holding all callables for a state. This is here to Base object holding all callables for a state. This is here to
allow easy overriding for child states. allow easy overriding for child states.
""" """
next_state = "unset" next_state = "unset"
# a sequence of hints to describe this state. # a sequence of hints to describe this state.
hints = [] hints = []
@ -144,15 +148,20 @@ class BaseState(object):
Wrapper handling state method errors. Wrapper handling state method errors.
""" """
@wraps(method) @wraps(method)
def decorator(*args, **kwargs): def decorator(*args, **kwargs):
try: try:
return method(*args, **kwargs) return method(*args, **kwargs)
except Exception: except Exception:
logger.log_trace(f"Error in State {__name__}") logger.log_trace(f"Error in State {__name__}")
self.room.msg_room(None, f"|rThere was an unexpected error in State {__name__}. " self.room.msg_room(
"Please |wreport|r this as an issue.|n") None,
f"|rThere was an unexpected error in State {__name__}. "
"Please |wreport|r this as an issue.|n",
)
raise # TODO raise # TODO
return decorator return decorator
def __getattribute__(self, key): def __getattribute__(self, key):
@ -176,8 +185,10 @@ class BaseState(object):
# return the next hint in the sequence. # return the next hint in the sequence.
self.room.db.state_hint_level = next_level self.room.db.state_hint_level = next_level
self.room.db.stats["hints_used"] += 1 self.room.db.stats["hints_used"] += 1
self.room.log(f"HINT: {self.name.split('.')[-1]}, level {next_level + 1} " self.room.log(
f"(total used: {self.room.db.stats['hints_used']})") f"HINT: {self.name.split('.')[-1]}, level {next_level + 1} "
f"(total used: {self.room.db.stats['hints_used']})"
)
return self.hints[next_level] return self.hints[next_level]
else: else:
# no more hints for this state # no more hints for this state
@ -191,8 +202,7 @@ class BaseState(object):
if cinematic: if cinematic:
message = msg_cinematic(message, borders=borders) message = msg_cinematic(message, borders=borders)
if target: if target:
options = target.attributes.get( options = target.attributes.get("options", category=self.room.tagcategory, default={})
"options", category=self.room.tagcategory, default={})
style = options.get("things_style", 2) style = options.get("things_style", 2)
# we assume this is a char # we assume this is a char
target.msg(parse_for_things(message, things_style=style)) target.msg(parse_for_things(message, things_style=style))
@ -205,7 +215,7 @@ class BaseState(object):
""" """
self.msg(message, target=target, borders=True, cinematic=True) self.msg(message, target=target, borders=True, cinematic=True)
def create_object(self, typeclass=None, key='testobj', location=None, **kwargs): def create_object(self, typeclass=None, key="testobj", location=None, **kwargs):
""" """
This is a convenience-wrapper for quickly building EvscapeRoom objects. This is a convenience-wrapper for quickly building EvscapeRoom objects.
@ -223,8 +233,12 @@ class BaseState(object):
if not location: if not location:
location = self.room location = self.room
return create_evscaperoom_object( return create_evscaperoom_object(
typeclass=typeclass, key=key, location=location, typeclass=typeclass,
tags=[("room", self.room.tagcategory.lower())], **kwargs) key=key,
location=location,
tags=[("room", self.room.tagcategory.lower())],
**kwargs,
)
def get_object(self, key): def get_object(self, key):
""" """
@ -237,7 +251,8 @@ class BaseState(object):
""" """
match = EvscaperoomObject.objects.filter_family( match = EvscaperoomObject.objects.filter_family(
db_key__iexact=key, db_tags__db_category=self.room.tagcategory.lower()) db_key__iexact=key, db_tags__db_category=self.room.tagcategory.lower()
)
if not match: if not match:
logger.log_err(f"get_object: No match for '{key}' in state ") logger.log_err(f"get_object: No match for '{key}' in state ")
return None return None

View file

@ -40,6 +40,7 @@ class Door(objects.Openable):
The door leads out of the room. The door leads out of the room.
""" """
start_open = False start_open = False
def at_object_creation(self): def at_object_creation(self):
@ -108,7 +109,6 @@ On the wall is a button marked
class HelpButton(objects.EvscaperoomObject): class HelpButton(objects.EvscaperoomObject):
def at_focus_push(self, caller, **kwargs): def at_focus_push(self, caller, **kwargs):
"this adds the 'push' action to the button" "this adds the 'push' action to the button"
@ -116,8 +116,9 @@ class HelpButton(objects.EvscaperoomObject):
if hint is None: if hint is None:
self.msg_char(caller, "There are no more hints to be had.") self.msg_char(caller, "There are no more hints to be had.")
else: else:
self.msg_room(caller, f"{caller.key} pushes *button and gets the " self.msg_room(
f"hint:\n \"{hint.strip()}\"|n") caller, f"{caller.key} pushes *button and gets the " f'hint:\n "{hint.strip()}"|n'
)
# state # state
@ -144,9 +145,7 @@ class State(BaseState):
""" """
# this makes these hints available to the .get_hint method. # this makes these hints available to the .get_hint method.
hints = [STATE_HINT_LVL1, hints = [STATE_HINT_LVL1, STATE_HINT_LVL2, STATE_HINT_LVL3]
STATE_HINT_LVL2,
STATE_HINT_LVL3]
def character_enters(self, char): def character_enters(self, char):
"Called when char enters room at this state" "Called when char enters room at this state"
@ -159,16 +158,13 @@ class State(BaseState):
self.room.db.desc = ROOM_DESC self.room.db.desc = ROOM_DESC
# create the room objects # create the room objects
door = self.create_object( door = self.create_object(Door, key="door to the cabin", aliases=["door"])
Door, key="door to the cabin", aliases=["door"])
door.db.desc = DOOR_DESC.strip() door.db.desc = DOOR_DESC.strip()
key = self.create_object( key = self.create_object(Key, key="key", aliases=["room key"])
Key, key="key", aliases=["room key"])
key.db.desc = KEY_DESC.strip() key.db.desc = KEY_DESC.strip()
button = self.create_object( button = self.create_object(HelpButton, key="button", aliases=["help button"])
HelpButton, key="button", aliases=["help button"])
button.db.desc = BUTTON_DESC.strip() button.db.desc = BUTTON_DESC.strip()
def clean(self): def clean(self):

View file

@ -16,11 +16,9 @@ from . import utils
class TestEvscaperoomCommands(CommandTest): class TestEvscaperoomCommands(CommandTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.room1 = utils.create_evscaperoom_object( self.room1 = utils.create_evscaperoom_object("evscaperoom.room.EvscapeRoom", key="Testroom")
"evscaperoom.room.EvscapeRoom", key='Testroom')
self.char1.location = self.room1 self.char1.location = self.room1
self.obj1.location = self.room1 self.obj1.location = self.room1
@ -114,7 +112,7 @@ class TestEvscaperoomCommands(CommandTest):
self.assertEqual(cmd.obj1, None) self.assertEqual(cmd.obj1, None)
self.assertEqual(cmd.obj2, self.obj1) self.assertEqual(cmd.obj2, self.obj1)
self.assertEqual(cmd.arg1, 'foo') self.assertEqual(cmd.arg1, "foo")
self.assertEqual(cmd.arg2, None) self.assertEqual(cmd.arg2, None)
cmd = commands.CmdEvscapeRoom() cmd = commands.CmdEvscapeRoom()
@ -127,7 +125,7 @@ class TestEvscaperoomCommands(CommandTest):
self.assertEqual(cmd.obj1, self.obj1) self.assertEqual(cmd.obj1, self.obj1)
self.assertEqual(cmd.obj2, None) self.assertEqual(cmd.obj2, None)
self.assertEqual(cmd.arg1, None) self.assertEqual(cmd.arg1, None)
self.assertEqual(cmd.arg2, 'foo') self.assertEqual(cmd.arg2, "foo")
cmd = commands.CmdEvscapeRoom() cmd = commands.CmdEvscapeRoom()
cmd.caller = self.char1 cmd.caller = self.char1
@ -152,21 +150,20 @@ class TestEvscaperoomCommands(CommandTest):
cmd.caller = self.char1 cmd.caller = self.char1
cmd.room = self.room1 cmd.room = self.room1
cmd.focus = self.obj1 cmd.focus = self.obj1
self.assertEqual(self.char1.attributes.get( self.assertEqual(
"focus", category=self.room1.tagcategory), self.obj1) self.char1.attributes.get("focus", category=self.room1.tagcategory), self.obj1
)
def test_focus(self): def test_focus(self):
# don't focus on a non-room object # don't focus on a non-room object
self.call(commands.CmdFocus(), "obj") self.call(commands.CmdFocus(), "obj")
self.assertEqual(self.char1.attributes.get( self.assertEqual(self.char1.attributes.get("focus", category=self.room1.tagcategory), None)
"focus", category=self.room1.tagcategory), None)
# should focus correctly # should focus correctly
myobj = utils.create_evscaperoom_object( myobj = utils.create_evscaperoom_object(
objects.EvscaperoomObject, "mytestobj", location=self.room1) objects.EvscaperoomObject, "mytestobj", location=self.room1
)
self.call(commands.CmdFocus(), "mytestobj") self.call(commands.CmdFocus(), "mytestobj")
self.assertEqual(self.char1.attributes.get( self.assertEqual(self.char1.attributes.get("focus", category=self.room1.tagcategory), myobj)
"focus", category=self.room1.tagcategory), myobj)
def test_look(self): def test_look(self):
self.call(commands.CmdLook(), "at obj", "Obj") self.call(commands.CmdLook(), "at obj", "Obj")
@ -181,31 +178,31 @@ class TestEvscaperoomCommands(CommandTest):
self.call(commands.CmdSpeak(), "Hi.", "You whisper: Hi.", cmdstring="whisper") self.call(commands.CmdSpeak(), "Hi.", "You whisper: Hi.", cmdstring="whisper")
self.call(commands.CmdSpeak(), "HELLO!", "You shout: HELLO!", cmdstring="shout") self.call(commands.CmdSpeak(), "HELLO!", "You shout: HELLO!", cmdstring="shout")
self.call(commands.CmdSpeak(), "Hello to obj", self.call(commands.CmdSpeak(), "Hello to obj", "You say: Hello", cmdstring="say")
"You say: Hello", cmdstring="say") self.call(commands.CmdSpeak(), "Hello to obj", "You shout: Hello", cmdstring="shout")
self.call(commands.CmdSpeak(), "Hello to obj",
"You shout: Hello", cmdstring="shout")
def test_emote(self): def test_emote(self):
self.call(commands.CmdEmote(), self.call(
"/me smiles to /obj", commands.CmdEmote(),
f"Char(#{self.char1.id}) smiles to Obj(#{self.obj1.id})") "/me smiles to /obj",
f"Char(#{self.char1.id}) smiles to Obj(#{self.obj1.id})",
)
def test_focus_interaction(self): def test_focus_interaction(self):
self.call(commands.CmdFocusInteraction(), "", "Hm?") self.call(commands.CmdFocusInteraction(), "", "Hm?")
class TestUtils(EvenniaTest): class TestUtils(EvenniaTest):
def test_overwrite(self): def test_overwrite(self):
room = utils.create_evscaperoom_object( room = utils.create_evscaperoom_object("evscaperoom.room.EvscapeRoom", key="Testroom")
"evscaperoom.room.EvscapeRoom", key='Testroom')
obj1 = utils.create_evscaperoom_object( obj1 = utils.create_evscaperoom_object(
objects.EvscaperoomObject, key="testobj", location=room) objects.EvscaperoomObject, key="testobj", location=room
)
id1 = obj1.id id1 = obj1.id
obj2 = utils.create_evscaperoom_object( obj2 = utils.create_evscaperoom_object(
objects.EvscaperoomObject, key="testobj", location=room) objects.EvscaperoomObject, key="testobj", location=room
)
id2 = obj2.id id2 = obj2.id
# we should have created a new object, deleting the old same-named one # we should have created a new object, deleting the old same-named one
@ -231,14 +228,12 @@ class TestUtils(EvenniaTest):
self.assertEqual(utils.parse_for_things(string, 2), "Looking at |y[book]|n and |y[key]|n.") self.assertEqual(utils.parse_for_things(string, 2), "Looking at |y[book]|n and |y[key]|n.")
class TestEvScapeRoom(EvenniaTest): class TestEvScapeRoom(EvenniaTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.room = utils.create_evscaperoom_object( self.room = utils.create_evscaperoom_object(
"evscaperoom.room.EvscapeRoom", key='Testroom', "evscaperoom.room.EvscapeRoom", key="Testroom", home=self.room1
home=self.room1) )
self.roomtag = "evscaperoom_{}".format(self.room.key) self.roomtag = "evscaperoom_{}".format(self.room.key)
def tearDown(self): def tearDown(self):
@ -253,24 +248,21 @@ class TestEvScapeRoom(EvenniaTest):
self.assertEqual(list(room.get_all_characters()), [self.char1]) self.assertEqual(list(room.get_all_characters()), [self.char1])
room.tag_character(self.char1, "opened_door") room.tag_character(self.char1, "opened_door")
self.assertEqual(self.char1.tags.get( self.assertEqual(self.char1.tags.get("opened_door", category=self.roomtag), "opened_door")
"opened_door", category=self.roomtag), "opened_door")
room.tag_all_characters("tagged_all") room.tag_all_characters("tagged_all")
self.assertEqual(self.char1.tags.get( self.assertEqual(self.char1.tags.get("tagged_all", category=self.roomtag), "tagged_all")
"tagged_all", category=self.roomtag), "tagged_all")
room.character_cleanup(self.char1) room.character_cleanup(self.char1)
self.assertEqual(self.char1.tags.get(category=self.roomtag), None) self.assertEqual(self.char1.tags.get(category=self.roomtag), None)
class TestStates(EvenniaTest): class TestStates(EvenniaTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.room = utils.create_evscaperoom_object( self.room = utils.create_evscaperoom_object(
"evscaperoom.room.EvscapeRoom", key='Testroom', "evscaperoom.room.EvscapeRoom", key="Testroom", home=self.room1
home=self.room1) )
self.roomtag = "evscaperoom_#{}".format(self.room.id) self.roomtag = "evscaperoom_#{}".format(self.room.id)
def tearDown(self): def tearDown(self):
@ -280,7 +272,8 @@ class TestStates(EvenniaTest):
dirname = path.join(path.dirname(__file__), "states") dirname = path.join(path.dirname(__file__), "states")
states = [] states = []
for imp, module, ispackage in pkgutil.walk_packages( for imp, module, ispackage in pkgutil.walk_packages(
path=[dirname], prefix="evscaperoom.states."): path=[dirname], prefix="evscaperoom.states."
):
mod = mod_import(module) mod = mod_import(module)
states.append(mod) states.append(mod)
return states return states

View file

@ -12,12 +12,13 @@ from evennia import create_object, search_object
from evennia.utils import justify, inherits_from from evennia.utils import justify, inherits_from
_BASE_TYPECLASS_PATH = "evscaperoom.objects." _BASE_TYPECLASS_PATH = "evscaperoom.objects."
_RE_PERSPECTIVE = re.compile(r"~(\w+)", re.I+re.U+re.M) _RE_PERSPECTIVE = re.compile(r"~(\w+)", re.I + re.U + re.M)
_RE_THING = re.compile(r"\*(\w+)", re.I+re.U+re.M) _RE_THING = re.compile(r"\*(\w+)", re.I + re.U + re.M)
def create_evscaperoom_object(typeclass=None, key="testobj", location=None, def create_evscaperoom_object(
delete_duplicates=True, **kwargs): typeclass=None, key="testobj", location=None, delete_duplicates=True, **kwargs
):
""" """
This is a convenience-wrapper for quickly building EvscapeRoom objects. This This is a convenience-wrapper for quickly building EvscapeRoom objects. This
is called from the helper-method create_object on states, but is also useful is called from the helper-method create_object on states, but is also useful
@ -38,25 +39,29 @@ def create_evscaperoom_object(typeclass=None, key="testobj", location=None,
""" """
if not (callable(typeclass) or if not (
typeclass.startswith("evennia") or callable(typeclass)
typeclass.startswith("typeclasses") or or typeclass.startswith("evennia")
typeclass.startswith("evscaperoom")): or typeclass.startswith("typeclasses")
or typeclass.startswith("evscaperoom")
):
# unless we specify a full typeclass path or the class itself, # unless we specify a full typeclass path or the class itself,
# auto-complete it # auto-complete it
typeclass = _BASE_TYPECLASS_PATH + typeclass typeclass = _BASE_TYPECLASS_PATH + typeclass
if delete_duplicates: if delete_duplicates:
old_objs = [obj for obj in search_object(key) old_objs = [
if not inherits_from(obj, "evennia.objects.objects.DefaultCharacter")] obj
for obj in search_object(key)
if not inherits_from(obj, "evennia.objects.objects.DefaultCharacter")
]
if location: if location:
# delete only matching objects in the given location # delete only matching objects in the given location
[obj.delete() for obj in old_objs if obj.location == location] [obj.delete() for obj in old_objs if obj.location == location]
else: else:
[obj.delete() for obj in old_objs] [obj.delete() for obj in old_objs]
new_obj = create_object(typeclass=typeclass, key=key, new_obj = create_object(typeclass=typeclass, key=key, location=location, **kwargs)
location=location, **kwargs)
return new_obj return new_obj
@ -74,9 +79,11 @@ def create_fantasy_word(length=5, capitalize=True):
if not length: if not length:
return "" return ""
phonemes = ("ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua " phonemes = (
"uh uw a e i u y p b t d f v t dh " "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua "
"s z sh zh ch jh k ng g m n l r w").split() "uh uw a e i u y p b t d f v t dh "
"s z sh zh ch jh k ng g m n l r w"
).split()
word = [choice(phonemes)] word = [choice(phonemes)]
while len(word) < length: while len(word) < length:
word.append(choice(phonemes)) word.append(choice(phonemes))
@ -113,6 +120,7 @@ def parse_for_perspectives(string, you=None):
"~You ~open" "~You ~open"
-> "You open", "Bob opens" -> "You open", "Bob opens"
""" """
def _replace_third_person(match): def _replace_third_person(match):
match = match.group(1) match = match.group(1)
lmatch = match.lower() lmatch = match.lower()
@ -122,7 +130,7 @@ def parse_for_perspectives(string, you=None):
if match[0].isupper(): if match[0].isupper():
return irregulars[lmatch].capitalize() return irregulars[lmatch].capitalize()
return irregulars[lmatch] return irregulars[lmatch]
elif lmatch[-1] == 's': elif lmatch[-1] == "s":
return match + "es" return match + "es"
else: else:
return match + "s" # simple, most normal form return match + "s" # simple, most normal form
@ -181,7 +189,7 @@ def msg_cinematic(text, borders=True):
""" """
text = text.strip() text = text.strip()
text = justify(text, align='c', indent=1) text = justify(text, align="c", indent=1)
if borders: if borders:
text = add_msg_borders(text) text = add_msg_borders(text)
return text return text

View file

@ -94,7 +94,7 @@ from evennia import utils
from evennia import CmdSet from evennia import CmdSet
# error return function, needed by Extended Look command # error return function, needed by Extended Look command
_AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _AT_SEARCH_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
# regexes for in-desc replacements # regexes for in-desc replacements
RE_MORNING = re.compile(r"<morning>(.*?)</morning>", re.IGNORECASE) RE_MORNING = re.compile(r"<morning>(.*?)</morning>", re.IGNORECASE)
@ -103,10 +103,12 @@ RE_EVENING = re.compile(r"<evening>(.*?)</evening>", re.IGNORECASE)
RE_NIGHT = re.compile(r"<night>(.*?)</night>", re.IGNORECASE) RE_NIGHT = re.compile(r"<night>(.*?)</night>", re.IGNORECASE)
# this map is just a faster way to select the right regexes (the first # this map is just a faster way to select the right regexes (the first
# regex in each tuple will be parsed, the following will always be weeded out) # regex in each tuple will be parsed, the following will always be weeded out)
REGEXMAP = {"morning": (RE_MORNING, RE_AFTERNOON, RE_EVENING, RE_NIGHT), REGEXMAP = {
"afternoon": (RE_AFTERNOON, RE_MORNING, RE_EVENING, RE_NIGHT), "morning": (RE_MORNING, RE_AFTERNOON, RE_EVENING, RE_NIGHT),
"evening": (RE_EVENING, RE_MORNING, RE_AFTERNOON, RE_NIGHT), "afternoon": (RE_AFTERNOON, RE_MORNING, RE_EVENING, RE_NIGHT),
"night": (RE_NIGHT, RE_MORNING, RE_AFTERNOON, RE_EVENING)} "evening": (RE_EVENING, RE_MORNING, RE_AFTERNOON, RE_NIGHT),
"night": (RE_NIGHT, RE_MORNING, RE_AFTERNOON, RE_EVENING),
}
# set up the seasons and time slots. This assumes gametime started at the # set up the seasons and time slots. This assumes gametime started at the
# beginning of the year (so month 1 is equivalent to January), and that # beginning of the year (so month 1 is equivalent to January), and that
@ -119,6 +121,7 @@ DAY_BOUNDARIES = (0, 6 / 24.0, 12 / 24.0, 18 / 24.0)
# implements the Extended Room # implements the Extended Room
class ExtendedRoom(DefaultRoom): class ExtendedRoom(DefaultRoom):
""" """
This room implements a more advanced `look` functionality depending on This room implements a more advanced `look` functionality depending on
@ -265,7 +268,6 @@ class ExtendedRoom(DefaultRoom):
if self.db.details and detailkey.lower() in self.db.details: if self.db.details and detailkey.lower() in self.db.details:
del self.db.details[detailkey.lower()] del self.db.details[detailkey.lower()]
def return_appearance(self, looker, **kwargs): def return_appearance(self, looker, **kwargs):
""" """
This is called when e.g. the look command wants to retrieve This is called when e.g. the look command wants to retrieve
@ -321,6 +323,7 @@ class ExtendedRoom(DefaultRoom):
# Custom Look command supporting Room details. Add this to # Custom Look command supporting Room details. Add this to
# the Default cmdset to use. # the Default cmdset to use.
class CmdExtendedRoomLook(default_cmds.CmdLook): class CmdExtendedRoomLook(default_cmds.CmdLook):
""" """
look look
@ -341,15 +344,21 @@ class CmdExtendedRoomLook(default_cmds.CmdLook):
caller = self.caller caller = self.caller
args = self.args args = self.args
if args: if args:
looking_at_obj = caller.search(args, looking_at_obj = caller.search(
candidates=caller.location.contents + caller.contents, args,
use_nicks=True, candidates=caller.location.contents + caller.contents,
quiet=True) use_nicks=True,
quiet=True,
)
if not looking_at_obj: if not looking_at_obj:
# no object found. Check if there is a matching # no object found. Check if there is a matching
# detail at location. # detail at location.
location = caller.location location = caller.location
if location and hasattr(location, "return_detail") and callable(location.return_detail): if (
location
and hasattr(location, "return_detail")
and callable(location.return_detail)
):
detail = location.return_detail(args) detail = location.return_detail(args)
if detail: if detail:
# we found a detail instead. Show that. # we found a detail instead. Show that.
@ -367,7 +376,7 @@ class CmdExtendedRoomLook(default_cmds.CmdLook):
caller.msg("You have no location to look at!") caller.msg("You have no location to look at!")
return return
if not hasattr(looking_at_obj, 'return_appearance'): if not hasattr(looking_at_obj, "return_appearance"):
# this is likely due to us having an account instead # this is likely due to us having an account instead
looking_at_obj = looking_at_obj.character looking_at_obj = looking_at_obj.character
if not looking_at_obj.access(caller, "view"): if not looking_at_obj.access(caller, "view"):
@ -382,6 +391,7 @@ class CmdExtendedRoomLook(default_cmds.CmdLook):
# Custom build commands for setting seasonal descriptions # Custom build commands for setting seasonal descriptions
# and detailing extended rooms. # and detailing extended rooms.
class CmdExtendedRoomDesc(default_cmds.CmdDesc): class CmdExtendedRoomDesc(default_cmds.CmdDesc):
""" """
`desc` - describe an object or room. `desc` - describe an object or room.
@ -411,6 +421,7 @@ class CmdExtendedRoomDesc(default_cmds.CmdDesc):
version of the `desc` command. version of the `desc` command.
""" """
aliases = ["describe"] aliases = ["describe"]
switch_options = () # Inherits from default_cmds.CmdDesc, but unused here switch_options = () # Inherits from default_cmds.CmdDesc, but unused here
@ -442,13 +453,13 @@ class CmdExtendedRoomDesc(default_cmds.CmdDesc):
if not location: if not location:
caller.msg("No location was found!") caller.msg("No location was found!")
return return
if switch == 'spring': if switch == "spring":
location.db.spring_desc = self.args location.db.spring_desc = self.args
elif switch == 'summer': elif switch == "summer":
location.db.summer_desc = self.args location.db.summer_desc = self.args
elif switch == 'autumn': elif switch == "autumn":
location.db.autumn_desc = self.args location.db.autumn_desc = self.args
elif switch == 'winter': elif switch == "winter":
location.db.winter_desc = self.args location.db.winter_desc = self.args
# clear flag to force an update # clear flag to force an update
self.reset_times(location) self.reset_times(location)
@ -498,6 +509,7 @@ class CmdExtendedRoomDetail(default_cmds.MuxCommand):
To remove one or several details, use the @detail/del switch. To remove one or several details, use the @detail/del switch.
""" """
key = "@detail" key = "@detail"
locks = "cmd:perm(Builder)" locks = "cmd:perm(Builder)"
help_category = "Building" help_category = "Building"
@ -537,6 +549,7 @@ class CmdExtendedRoomDetail(default_cmds.MuxCommand):
# Simple command to view the current time and season # Simple command to view the current time and season
class CmdExtendedRoomGameTime(default_cmds.MuxCommand): class CmdExtendedRoomGameTime(default_cmds.MuxCommand):
""" """
Check the game time Check the game time
@ -546,6 +559,7 @@ class CmdExtendedRoomGameTime(default_cmds.MuxCommand):
Shows the current in-game time and season. Shows the current in-game time and season.
""" """
key = "time" key = "time"
locks = "cmd:all()" locks = "cmd:all()"
help_category = "General" help_category = "General"
@ -565,11 +579,13 @@ class CmdExtendedRoomGameTime(default_cmds.MuxCommand):
# CmdSet for easily install all commands # CmdSet for easily install all commands
class ExtendedRoomCmdSet(CmdSet): class ExtendedRoomCmdSet(CmdSet):
""" """
Groups the extended-room commands. Groups the extended-room commands.
""" """
def at_cmdset_creation(self): def at_cmdset_creation(self):
self.add(CmdExtendedRoomLook) self.add(CmdExtendedRoomLook)
self.add(CmdExtendedRoomDesc) self.add(CmdExtendedRoomDesc)

View file

@ -166,9 +166,18 @@ class FieldEvMenu(evmenu.EvMenu):
return nodetext return nodetext
def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="", def init_fill_field(
submitcmd="submit", borderstyle="cells", formhelptext=None, formtemplate,
persistent=False, initial_formdata=None): caller,
formcallback,
pretext="",
posttext="",
submitcmd="submit",
borderstyle="cells",
formhelptext=None,
persistent=False,
initial_formdata=None,
):
""" """
Initializes a menu presenting a player with a fillable form - once the form Initializes a menu presenting a player with a fillable form - once the form
is submitted, the data will be passed as a dictionary to your chosen is submitted, the data will be passed as a dictionary to your chosen
@ -201,13 +210,14 @@ def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="",
# Provide default help text if none given # Provide default help text if none given
if formhelptext is None: if formhelptext is None:
formhelptext = ( formhelptext = (
"Available commands:|/" "Available commands:|/"
"|w<field> = <new value>:|n Set given field to new value, replacing the old value|/" "|w<field> = <new value>:|n Set given field to new value, replacing the old value|/"
"|wclear <field>:|n Clear the value in the given field, making it blank|/" "|wclear <field>:|n Clear the value in the given field, making it blank|/"
"|wlook|n: Show the form's current values|/" "|wlook|n: Show the form's current values|/"
"|whelp|n: Display this help screen|/" "|whelp|n: Display this help screen|/"
"|wquit|n: Quit the form menu without submitting|/" "|wquit|n: Quit the form menu without submitting|/"
"|w%s|n: Submit this form and quit the menu" % submitcmd) "|w%s|n: Submit this form and quit the menu" % submitcmd
)
# Pass kwargs to store data needed in the menu # Pass kwargs to store data needed in the menu
kwargs = { kwargs = {
@ -218,12 +228,18 @@ def init_fill_field(formtemplate, caller, formcallback, pretext="", posttext="",
"posttext": posttext, "posttext": posttext,
"submitcmd": submitcmd, "submitcmd": submitcmd,
"borderstyle": borderstyle, "borderstyle": borderstyle,
"formhelptext": formhelptext "formhelptext": formhelptext,
} }
# Initialize menu of selections # Initialize menu of selections
FieldEvMenu(caller, "evennia.contrib.fieldfill", startnode="menunode_fieldfill", FieldEvMenu(
auto_look=False, persistent=persistent, **kwargs) caller,
"evennia.contrib.fieldfill",
startnode="menunode_fieldfill",
auto_look=False,
persistent=persistent,
**kwargs,
)
def menunode_fieldfill(caller, raw_string, **kwargs): def menunode_fieldfill(caller, raw_string, **kwargs):
@ -254,13 +270,19 @@ def menunode_fieldfill(caller, raw_string, **kwargs):
formhelptext = caller.db._menutree.formhelptext formhelptext = caller.db._menutree.formhelptext
# Syntax error # Syntax error
syntax_err = "Syntax: <field> = <new value>|/Or: clear <field>, help, look, quit|/'%s' to submit form" % submitcmd syntax_err = (
"Syntax: <field> = <new value>|/Or: clear <field>, help, look, quit|/'%s' to submit form"
% submitcmd
)
# Display current form data # Display current form data
text = (display_formdata(formtemplate, formdata, pretext=pretext, text = (
posttext=posttext, borderstyle=borderstyle), formhelptext) display_formdata(
options = ({"key": "_default", formtemplate, formdata, pretext=pretext, posttext=posttext, borderstyle=borderstyle
"goto": "menunode_fieldfill"}) ),
formhelptext,
)
options = {"key": "_default", "goto": "menunode_fieldfill"}
if raw_string: if raw_string:
# Test for given 'submit' command # Test for given 'submit' command
@ -275,7 +297,10 @@ def menunode_fieldfill(caller, raw_string, **kwargs):
blank_and_required.append(field["fieldname"]) blank_and_required.append(field["fieldname"])
if len(blank_and_required) > 0: if len(blank_and_required) > 0:
# List the required fields left empty to the player # List the required fields left empty to the player
caller.msg("The following blank fields require a value: %s" % list_to_string(blank_and_required)) caller.msg(
"The following blank fields require a value: %s"
% list_to_string(blank_and_required)
)
text = (None, formhelptext) text = (None, formhelptext)
return text, options return text, options
@ -377,12 +402,18 @@ def menunode_fieldfill(caller, raw_string, **kwargs):
# Test for max/min # Test for max/min
if max_value is not None: if max_value is not None:
if len(newvalue) > max_value: if len(newvalue) > max_value:
caller.msg("Field '%s' has a maximum length of %i characters." % (matched_field, max_value)) caller.msg(
"Field '%s' has a maximum length of %i characters."
% (matched_field, max_value)
)
text = (None, formhelptext) text = (None, formhelptext)
return text, options return text, options
if min_value is not None: if min_value is not None:
if len(newvalue) < min_value: if len(newvalue) < min_value:
caller.msg("Field '%s' reqiures a minimum length of %i characters." % (matched_field, min_value)) caller.msg(
"Field '%s' reqiures a minimum length of %i characters."
% (matched_field, min_value)
)
text = (None, formhelptext) text = (None, formhelptext)
return text, options return text, options
@ -402,14 +433,18 @@ def menunode_fieldfill(caller, raw_string, **kwargs):
return text, options return text, options
if min_value is not None: if min_value is not None:
if newvalue < min_value: if newvalue < min_value:
caller.msg("Field '%s' reqiures a minimum value of %i." % (matched_field, min_value)) caller.msg(
"Field '%s' reqiures a minimum value of %i." % (matched_field, min_value)
)
text = (None, formhelptext) text = (None, formhelptext)
return text, options return text, options
# Field type bool verification # Field type bool verification
if fieldtype == "bool": if fieldtype == "bool":
if newvalue.lower() != truestr.lower() and newvalue.lower() != falsestr.lower(): if newvalue.lower() != truestr.lower() and newvalue.lower() != falsestr.lower():
caller.msg("Please enter '%s' or '%s' for field '%s'." % (truestr, falsestr, matched_field)) caller.msg(
"Please enter '%s' or '%s' for field '%s'." % (truestr, falsestr, matched_field)
)
text = (None, formhelptext) text = (None, formhelptext)
return text, options return text, options
if newvalue.lower() == truestr.lower(): if newvalue.lower() == truestr.lower():
@ -474,8 +509,7 @@ def form_template_to_dict(formtemplate):
return formdata return formdata
def display_formdata(formtemplate, formdata, def display_formdata(formtemplate, formdata, pretext="", posttext="", borderstyle="cells"):
pretext="", posttext="", borderstyle="cells"):
""" """
Displays a form's current data as a table. Used in the form menu. Displays a form's current data as a table. Used in the form menu.
@ -568,35 +602,40 @@ def verify_online_player(caller, value):
# besides strings and integers in the 'formdata' dictionary this way! # besides strings and integers in the 'formdata' dictionary this way!
return matched_character return matched_character
# Form template for the example 'delayed message' form # Form template for the example 'delayed message' form
SAMPLE_FORM = [ SAMPLE_FORM = [
{"fieldname": "Character", {
"fieldtype": "text", "fieldname": "Character",
"max": 30, "fieldtype": "text",
"blankmsg": "(Name of an online player)", "max": 30,
"required": True, "blankmsg": "(Name of an online player)",
"verifyfunc": verify_online_player "required": True,
}, "verifyfunc": verify_online_player,
{"fieldname": "Delay", },
"fieldtype": "number", {
"min": 3, "fieldname": "Delay",
"max": 30, "fieldtype": "number",
"default": 10, "min": 3,
"cantclear": True "max": 30,
}, "default": 10,
{"fieldname": "Message", "cantclear": True,
"fieldtype": "text", },
"min": 3, {
"max": 200, "fieldname": "Message",
"blankmsg": "(Message up to 200 characters)" "fieldtype": "text",
}, "min": 3,
{"fieldname": "Anonymous", "max": 200,
"fieldtype": "bool", "blankmsg": "(Message up to 200 characters)",
"truestr": "Yes", },
"falsestr": "No", {
"default": False "fieldname": "Anonymous",
} "fieldtype": "bool",
] "truestr": "Yes",
"falsestr": "No",
"default": False,
},
]
class CmdTestMenu(Command): class CmdTestMenu(Command):
@ -621,15 +660,25 @@ class CmdTestMenu(Command):
""" """
This performs the actual command. This performs the actual command.
""" """
pretext = "|cSend a delayed message to another player ---------------------------------------|n" pretext = (
posttext = ("|c--------------------------------------------------------------------------------|n|/" "|cSend a delayed message to another player ---------------------------------------|n"
"Syntax: type |c<field> = <new value>|n to change the values of the form. Given|/" )
"player must be currently logged in, delay is given in seconds. When you are|/" posttext = (
"finished, type '|csend|n' to send the message.|/") "|c--------------------------------------------------------------------------------|n|/"
"Syntax: type |c<field> = <new value>|n to change the values of the form. Given|/"
"player must be currently logged in, delay is given in seconds. When you are|/"
"finished, type '|csend|n' to send the message.|/"
)
init_fill_field(SAMPLE_FORM, self.caller, init_delayed_message, init_fill_field(
pretext=pretext, posttext=posttext, SAMPLE_FORM,
submitcmd="send", borderstyle="none") self.caller,
init_delayed_message,
pretext=pretext,
posttext=posttext,
submitcmd="send",
borderstyle="none",
)
def sendmessage(obj, text): def sendmessage(obj, text):

View file

@ -33,24 +33,13 @@ from evennia import Command
# gender maps # gender maps
_GENDER_PRONOUN_MAP = {"male": {"s": "he", _GENDER_PRONOUN_MAP = {
"o": "him", "male": {"s": "he", "o": "him", "p": "his", "a": "his"},
"p": "his", "female": {"s": "she", "o": "her", "p": "her", "a": "hers"},
"a": "his"}, "neutral": {"s": "it", "o": "it", "p": "its", "a": "its"},
"female": {"s": "she", "ambiguous": {"s": "they", "o": "them", "p": "their", "a": "theirs"},
"o": "her", }
"p": "her", _RE_GENDER_PRONOUN = re.compile(r"(?<!\|)\|(?!\|)[sSoOpPaA]")
"a": "hers"},
"neutral": {"s": "it",
"o": "it",
"p": "its",
"a": "its"},
"ambiguous": {"s": "they",
"o": "them",
"p": "their",
"a": "theirs"}
}
_RE_GENDER_PRONOUN = re.compile(r'(?<!\|)\|(?!\|)[sSoOpPaA]')
# in-game command for setting the gender # in-game command for setting the gender
@ -63,6 +52,7 @@ class SetGender(Command):
@gender male||female||neutral||ambiguous @gender male||female||neutral||ambiguous
""" """
key = "@gender" key = "@gender"
aliases = "@sex" aliases = "@sex"
locks = "call:all()" locks = "call:all()"
@ -82,6 +72,7 @@ class SetGender(Command):
# Gender-aware character class # Gender-aware character class
class GenderCharacter(DefaultCharacter): class GenderCharacter(DefaultCharacter):
""" """
This is a Character class aware of gender. This is a Character class aware of gender.

View file

@ -22,11 +22,19 @@ below 0, rendering them as a completely full or empty bar with the
values displayed within. values displayed within.
""" """
def display_meter(cur_value, max_value,
length=30, fill_color=["R", "Y", "G"], def display_meter(
empty_color="B", text_color="w", cur_value,
align="left", pre_text="", post_text="", max_value,
show_values=True): length=30,
fill_color=["R", "Y", "G"],
empty_color="B",
text_color="w",
align="left",
pre_text="",
post_text="",
show_values=True,
):
""" """
Represents a current and maximum value given as a "bar" rendered with Represents a current and maximum value given as a "bar" rendered with
ANSI or xterm256 background colors. ANSI or xterm256 background colors.
@ -70,34 +78,43 @@ def display_meter(cur_value, max_value,
bar_base_str = bar_base_str.center(length, " ") bar_base_str = bar_base_str.center(length, " ")
else: else:
bar_base_str = bar_base_str.ljust(length, " ") bar_base_str = bar_base_str.ljust(length, " ")
if max_value < 1: # Prevent divide by zero if max_value < 1: # Prevent divide by zero
max_value = 1 max_value = 1
if cur_value < 0: # Prevent weirdly formatted 'negative bars' if cur_value < 0: # Prevent weirdly formatted 'negative bars'
cur_value = 0 cur_value = 0
if cur_value > max_value: # Display overfull bars correctly if cur_value > max_value: # Display overfull bars correctly
cur_value = max_value cur_value = max_value
# Now it's time to determine where to put the color codes. # Now it's time to determine where to put the color codes.
percent_full = float(cur_value) / float(max_value) percent_full = float(cur_value) / float(max_value)
split_index = round(float(length) * percent_full) split_index = round(float(length) * percent_full)
# Determine point at which to split the bar # Determine point at which to split the bar
split_index = int(split_index) split_index = int(split_index)
# Separate the bar string into full and empty portions # Separate the bar string into full and empty portions
full_portion = bar_base_str[:split_index] full_portion = bar_base_str[:split_index]
empty_portion = bar_base_str[split_index:] empty_portion = bar_base_str[split_index:]
# Pick which fill color to use based on how full the bar is # Pick which fill color to use based on how full the bar is
fillcolor_index = (float(len(fill_color)) * percent_full) fillcolor_index = float(len(fill_color)) * percent_full
fillcolor_index = max(0, int(round(fillcolor_index)) - 1) fillcolor_index = max(0, int(round(fillcolor_index)) - 1)
fillcolor_code = "|[" + fill_color[fillcolor_index] fillcolor_code = "|[" + fill_color[fillcolor_index]
# Make color codes for empty bar portion and text_color # Make color codes for empty bar portion and text_color
emptycolor_code = "|[" + empty_color emptycolor_code = "|[" + empty_color
textcolor_code = "|" + text_color textcolor_code = "|" + text_color
# Assemble the final bar # Assemble the final bar
final_bar = fillcolor_code + textcolor_code + full_portion + "|n" + emptycolor_code + textcolor_code + empty_portion + "|n" final_bar = (
fillcolor_code
+ textcolor_code
+ full_portion
+ "|n"
+ emptycolor_code
+ textcolor_code
+ empty_portion
+ "|n"
)
return final_bar return final_bar

View file

@ -99,8 +99,11 @@ class CallbackHandler(object):
""" """
handler = type(self).script handler = type(self).script
if handler: if handler:
return self.format_callback(handler.add_callback(self.obj, callback_name, code, return self.format_callback(
author=author, valid=valid, parameters=parameters)) handler.add_callback(
self.obj, callback_name, code, author=author, valid=valid, parameters=parameters
)
)
def edit(self, callback_name, number, code, author=None, valid=False): def edit(self, callback_name, number, code, author=None, valid=False):
""" """
@ -122,8 +125,11 @@ class CallbackHandler(object):
""" """
handler = type(self).script handler = type(self).script
if handler: if handler:
return self.format_callback(handler.edit_callback(self.obj, callback_name, return self.format_callback(
number, code, author=author, valid=valid)) handler.edit_callback(
self.obj, callback_name, number, code, author=author, valid=valid
)
)
def remove(self, callback_name, number): def remove(self, callback_name, number):
""" """
@ -202,5 +208,18 @@ class CallbackHandler(object):
return Callback(**callback) return Callback(**callback)
Callback = namedtuple("Callback", ("obj", "name", "number", "code", "author", Callback = namedtuple(
"valid", "parameters", "created_on", "updated_by", "updated_on")) "Callback",
(
"obj",
"name",
"number",
"code",
"author",
"valid",
"parameters",
"created_on",
"updated_by",
"updated_on",
),
)

View file

@ -16,8 +16,7 @@ COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
# Permissions # Permissions
WITH_VALIDATION = getattr(settings, "callbackS_WITH_VALIDATION", None) WITH_VALIDATION = getattr(settings, "callbackS_WITH_VALIDATION", None)
WITHOUT_VALIDATION = getattr(settings, "callbackS_WITHOUT_VALIDATION", WITHOUT_VALIDATION = getattr(settings, "callbackS_WITHOUT_VALIDATION", "developer")
"developer")
VALIDATING = getattr(settings, "callbackS_VALIDATING", "developer") VALIDATING = getattr(settings, "callbackS_VALIDATING", "developer")
# Split help text # Split help text
@ -38,13 +37,9 @@ BASIC_SWITCHES = [
"tasks - show the list of differed tasks", "tasks - show the list of differed tasks",
] ]
VALIDATOR_USAGES = [ VALIDATOR_USAGES = ["@call/accept [object name = <callback name> [callback number]]"]
"@call/accept [object name = <callback name> [callback number]]",
]
VALIDATOR_SWITCHES = [ VALIDATOR_SWITCHES = ["accept - show callbacks to be validated or accept one"]
"accept - show callbacks to be validated or accept one",
]
BASIC_TEXT = """ BASIC_TEXT = """
This command is used to manipulate callbacks. A callback can be linked to This command is used to manipulate callbacks. A callback can be linked to
@ -129,8 +124,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
caller = self.caller caller = self.caller
lock = "perm({}) or perm(events_validating)".format(VALIDATING) lock = "perm({}) or perm(events_validating)".format(VALIDATING)
validator = caller.locks.check_lockstring(caller, lock) validator = caller.locks.check_lockstring(caller, lock)
lock = "perm({}) or perm(events_without_validation)".format( lock = "perm({}) or perm(events_without_validation)".format(WITHOUT_VALIDATION)
WITHOUT_VALIDATION)
autovalid = caller.locks.check_lockstring(caller, lock) autovalid = caller.locks.check_lockstring(caller, lock)
# First and foremost, get the callback handler and set other variables # First and foremost, get the callback handler and set other variables
@ -142,8 +136,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
self.is_validator = validator self.is_validator = validator
self.autovalid = autovalid self.autovalid = autovalid
if self.handler is None: if self.handler is None:
caller.msg("The event handler is not running, can't " caller.msg("The event handler is not running, can't " "access the event system.")
"access the event system.")
return return
# Before the equal sign, there is an object name or nothing # Before the equal sign, there is an object name or nothing
@ -171,8 +164,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
elif switch in ["tasks", "task"]: elif switch in ["tasks", "task"]:
self.list_tasks() self.list_tasks()
else: else:
caller.msg("Mutually exclusive or invalid switches were " caller.msg("Mutually exclusive or invalid switches were " "used, cannot proceed.")
"used, cannot proceed.")
def list_callbacks(self): def list_callbacks(self):
"""Display the list of callbacks connected to the object.""" """Display the list of callbacks connected to the object."""
@ -186,8 +178,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# Check that the callback name can be found in this object # Check that the callback name can be found in this object
created = callbacks.get(callback_name) created = callbacks.get(callback_name)
if created is None: if created is None:
self.msg("No callback {} has been set on {}.".format(callback_name, self.msg("No callback {} has been set on {}.".format(callback_name, obj))
obj))
return return
if parameters: if parameters:
@ -197,8 +188,11 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
assert number >= 0 assert number >= 0
callback = callbacks[callback_name][number] callback = callbacks[callback_name][number]
except (ValueError, AssertionError, IndexError): except (ValueError, AssertionError, IndexError):
self.msg("The callback {} {} cannot be found in {}.".format( self.msg(
callback_name, parameters, obj)) "The callback {} {} cannot be found in {}.".format(
callback_name, parameters, obj
)
)
return return
# Display the callback's details # Display the callback's details
@ -207,9 +201,13 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
updated_by = callback.get("updated_by") updated_by = callback.get("updated_by")
updated_by = updated_by.key if updated_by else "|gUnknown|n" updated_by = updated_by.key if updated_by else "|gUnknown|n"
created_on = callback.get("created_on") created_on = callback.get("created_on")
created_on = created_on.strftime("%Y-%m-%d %H:%M:%S") if created_on else "|gUnknown|n" created_on = (
created_on.strftime("%Y-%m-%d %H:%M:%S") if created_on else "|gUnknown|n"
)
updated_on = callback.get("updated_on") updated_on = callback.get("updated_on")
updated_on = updated_on.strftime("%Y-%m-%d %H:%M:%S") if updated_on else "|gUnknown|n" updated_on = (
updated_on.strftime("%Y-%m-%d %H:%M:%S") if updated_on else "|gUnknown|n"
)
msg = "Callback {} {} of {}:".format(callback_name, parameters, obj) msg = "Callback {} {} of {}:".format(callback_name, parameters, obj)
msg += "\nCreated by {} on {}.".format(author, created_on) msg += "\nCreated by {} on {}.".format(author, created_on)
msg += "\nUpdated by {} on {}".format(updated_by, updated_on) msg += "\nUpdated by {} on {}".format(updated_by, updated_on)
@ -241,9 +239,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
updated_on = callback.get("created_on") updated_on = callback.get("created_on")
if updated_on: if updated_on:
updated_on = "{} ago".format(time_format( updated_on = "{} ago".format(
(now - updated_on).total_seconds(), time_format((now - updated_on).total_seconds(), 4).capitalize()
4).capitalize()) )
else: else:
updated_on = "|gUnknown|n" updated_on = "|gUnknown|n"
parameters = callback.get("parameters", "") parameters = callback.get("parameters", "")
@ -256,8 +254,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
self.msg(str(table)) self.msg(str(table))
else: else:
names = list(set(list(types.keys()) + list(callbacks.keys()))) names = list(set(list(types.keys()) + list(callbacks.keys())))
table = EvTable("Callback name", "Number", "Description", table = EvTable("Callback name", "Number", "Description", valign="t", width=78)
valign="t", width=78)
table.reformat_column(0, width=20) table.reformat_column(0, width=20)
table.reformat_column(1, width=10, align="r") table.reformat_column(1, width=10, align="r")
table.reformat_column(2, width=48) table.reformat_column(2, width=48)
@ -279,8 +276,10 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# Check that the callback exists # Check that the callback exists
if not callback_name.startswith("chain_") and callback_name not in types: if not callback_name.startswith("chain_") and callback_name not in types:
self.msg("The callback name {} can't be found in {} of " self.msg(
"typeclass {}.".format(callback_name, obj, type(obj))) "The callback name {} can't be found in {} of "
"typeclass {}.".format(callback_name, obj, type(obj))
)
return return
definition = types.get(callback_name, (None, "Chained event.")) definition = types.get(callback_name, (None, "Chained event."))
@ -288,17 +287,24 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
self.msg(raw(description.strip("\n"))) self.msg(raw(description.strip("\n")))
# Open the editor # Open the editor
callback = self.handler.add_callback(obj, callback_name, "", callback = self.handler.add_callback(
self.caller, False, parameters=self.parameters) obj, callback_name, "", self.caller, False, parameters=self.parameters
)
# Lock this callback right away # Lock this callback right away
self.handler.db.locked.append((obj, callback_name, callback["number"])) self.handler.db.locked.append((obj, callback_name, callback["number"]))
# Open the editor for this callback # Open the editor for this callback
self.caller.db._callback = callback self.caller.db._callback = callback
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save, EvEditor(
quitfunc=_ev_quit, key="Callback {} of {}".format( self.caller,
callback_name, obj), persistent=True, codefunc=_ev_save) loadfunc=_ev_load,
savefunc=_ev_save,
quitfunc=_ev_quit,
key="Callback {} of {}".format(callback_name, obj),
persistent=True,
codefunc=_ev_save,
)
def edit_callback(self): def edit_callback(self):
"""Edit a callback.""" """Edit a callback."""
@ -315,8 +321,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# Check that the callback exists # Check that the callback exists
if callback_name not in callbacks: if callback_name not in callbacks:
self.msg("The callback name {} can't be found in {}.".format( self.msg("The callback name {} can't be found in {}.".format(callback_name, obj))
callback_name, obj))
return return
# If there's only one callback, just edit it # If there's only one callback, just edit it
@ -335,8 +340,11 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
assert number >= 0 assert number >= 0
callback = callbacks[callback_name][number] callback = callbacks[callback_name][number]
except (ValueError, AssertionError, IndexError): except (ValueError, AssertionError, IndexError):
self.msg("The callback {} {} cannot be found in {}.".format( self.msg(
callback_name, parameters, obj)) "The callback {} {} cannot be found in {}.".format(
callback_name, parameters, obj
)
)
return return
# If caller can't edit without validation, forbid editing # If caller can't edit without validation, forbid editing
@ -360,9 +368,15 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# Open the editor # Open the editor
callback = dict(callback) callback = dict(callback)
self.caller.db._callback = callback self.caller.db._callback = callback
EvEditor(self.caller, loadfunc=_ev_load, savefunc=_ev_save, EvEditor(
quitfunc=_ev_quit, key="Callback {} of {}".format( self.caller,
callback_name, obj), persistent=True, codefunc=_ev_save) loadfunc=_ev_load,
savefunc=_ev_save,
quitfunc=_ev_quit,
key="Callback {} of {}".format(callback_name, obj),
persistent=True,
codefunc=_ev_save,
)
def del_callback(self): def del_callback(self):
"""Delete a callback.""" """Delete a callback."""
@ -379,8 +393,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# Check that the callback exists # Check that the callback exists
if callback_name not in callbacks: if callback_name not in callbacks:
self.msg("The callback name {} can't be found in {}.".format( self.msg("The callback name {} can't be found in {}.".format(callback_name, obj))
callback_name, obj))
return return
# If there's only one callback, just delete it # If there's only one callback, just delete it
@ -389,8 +402,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
callback = callbacks[callback_name][0] callback = callbacks[callback_name][0]
else: else:
if not parameters: if not parameters:
self.msg("Which callback do you wish to delete? Specify " self.msg("Which callback do you wish to delete? Specify " "a number.")
"a number.")
self.list_callbacks() self.list_callbacks()
return return
@ -400,8 +412,11 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
assert number >= 0 assert number >= 0
callback = callbacks[callback_name][number] callback = callbacks[callback_name][number]
except (ValueError, AssertionError, IndexError): except (ValueError, AssertionError, IndexError):
self.msg("The callback {} {} cannot be found in {}.".format( self.msg(
callback_name, parameters, obj)) "The callback {} {} cannot be found in {}.".format(
callback_name, parameters, obj
)
)
return return
# If caller can't edit without validation, forbid deleting # If caller can't edit without validation, forbid deleting
@ -417,8 +432,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# Delete the callback # Delete the callback
self.handler.del_callback(obj, callback_name, number) self.handler.del_callback(obj, callback_name, number)
self.msg("The callback {}[{}] of {} was deleted.".format( self.msg("The callback {}[{}] of {} was deleted.".format(callback_name, number + 1, obj))
callback_name, number + 1, obj))
def accept_callback(self): def accept_callback(self):
"""Accept a callback.""" """Accept a callback."""
@ -428,8 +442,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# If no object, display the list of callbacks to be checked # If no object, display the list of callbacks to be checked
if obj is None: if obj is None:
table = EvTable("ID", "Type", "Object", "Name", "Updated by", table = EvTable("ID", "Type", "Object", "Name", "Updated by", "On", width=78)
"On", width=78)
table.reformat_column(0, align="r") table.reformat_column(0, align="r")
now = datetime.now() now = datetime.now()
for obj, name, number in self.handler.db.to_valid: for obj, name, number in self.handler.db.to_valid:
@ -450,9 +463,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
updated_on = callback.get("created_on") updated_on = callback.get("created_on")
if updated_on: if updated_on:
updated_on = "{} ago".format(time_format( updated_on = "{} ago".format(
(now - updated_on).total_seconds(), time_format((now - updated_on).total_seconds(), 4).capitalize()
4).capitalize()) )
else: else:
updated_on = "|gUnknown|n" updated_on = "|gUnknown|n"
@ -471,8 +484,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
# Check that the callback exists # Check that the callback exists
if callback_name not in callbacks: if callback_name not in callbacks:
self.msg("The callback name {} can't be found in {}.".format( self.msg("The callback name {} can't be found in {}.".format(callback_name, obj))
callback_name, obj))
return return
if not parameters: if not parameters:
@ -486,8 +498,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
assert number >= 0 assert number >= 0
callback = callbacks[callback_name][number] callback = callbacks[callback_name][number]
except (ValueError, AssertionError, IndexError): except (ValueError, AssertionError, IndexError):
self.msg("The callback {} {} cannot be found in {}.".format( self.msg(
callback_name, parameters, obj)) "The callback {} {} cannot be found in {}.".format(callback_name, parameters, obj)
)
return return
# Accept the callback # Accept the callback
@ -495,8 +508,9 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
self.msg("This callback has already been accepted.") self.msg("This callback has already been accepted.")
else: else:
self.handler.accept_callback(obj, callback_name, number) self.handler.accept_callback(obj, callback_name, number)
self.msg("The callback {} {} of {} has been accepted.".format( self.msg(
callback_name, parameters, obj)) "The callback {} {} of {} has been accepted.".format(callback_name, parameters, obj)
)
def list_tasks(self): def list_tasks(self):
"""List the active tasks.""" """List the active tasks."""
@ -520,6 +534,7 @@ class CmdCallback(COMMAND_DEFAULT_CLASS):
self.msg(str(table)) self.msg(str(table))
# Private functions to handle editing # Private functions to handle editing
@ -529,36 +544,40 @@ def _ev_load(caller):
def _ev_save(caller, buf): def _ev_save(caller, buf):
"""Save and add the callback.""" """Save and add the callback."""
lock = "perm({}) or perm(events_without_validation)".format( lock = "perm({}) or perm(events_without_validation)".format(WITHOUT_VALIDATION)
WITHOUT_VALIDATION)
autovalid = caller.locks.check_lockstring(caller, lock) autovalid = caller.locks.check_lockstring(caller, lock)
callback = caller.db._callback callback = caller.db._callback
handler = get_event_handler() handler = get_event_handler()
if not handler or not callback or not all(key in callback for key in if (
("obj", "name", "number", "valid")): not handler
or not callback
or not all(key in callback for key in ("obj", "name", "number", "valid"))
):
caller.msg("Couldn't save this callback.") caller.msg("Couldn't save this callback.")
return False return False
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked: if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
handler.db.locked.remove((callback["obj"], callback["name"], handler.db.locked.remove((callback["obj"], callback["name"], callback["number"]))
callback["number"]))
handler.edit_callback(callback["obj"], callback["name"], callback["number"], buf, handler.edit_callback(
caller, valid=autovalid) callback["obj"], callback["name"], callback["number"], buf, caller, valid=autovalid
)
return True return True
def _ev_quit(caller): def _ev_quit(caller):
callback = caller.db._callback callback = caller.db._callback
handler = get_event_handler() handler = get_event_handler()
if not handler or not callback or not all(key in callback for key in if (
("obj", "name", "number", "valid")): not handler
or not callback
or not all(key in callback for key in ("obj", "name", "number", "valid"))
):
caller.msg("Couldn't save this callback.") caller.msg("Couldn't save this callback.")
return False return False
if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked: if (callback["obj"], callback["name"], callback["number"]) in handler.db.locked:
handler.db.locked.remove((callback["obj"], callback["name"], handler.db.locked.remove((callback["obj"], callback["name"], callback["number"]))
callback["number"]))
del caller.db._callback del caller.db._callback
caller.msg("Exited the code editor.") caller.msg("Exited the code editor.")

View file

@ -87,6 +87,7 @@ class EventHandler(DefaultScript):
# Place the script in the CallbackHandler # Place the script in the CallbackHandler
from evennia.contrib.ingame_python import typeclasses from evennia.contrib.ingame_python import typeclasses
CallbackHandler.script = self CallbackHandler.script = self
DefaultObject.callbacks = typeclasses.EventObject.callbacks DefaultObject.callbacks = typeclasses.EventObject.callbacks
@ -94,8 +95,11 @@ class EventHandler(DefaultScript):
try: try:
self.ndb.channel = ChannelDB.objects.get(db_key="everror") self.ndb.channel = ChannelDB.objects.get(db_key="everror")
except ChannelDB.DoesNotExist: except ChannelDB.DoesNotExist:
self.ndb.channel = create_channel("everror", desc="Event errors", self.ndb.channel = create_channel(
locks="control:false();listen:perm(Builders);send:false()") "everror",
desc="Event errors",
locks="control:false();listen:perm(Builders);send:false()",
)
def get_events(self, obj): def get_events(self, obj):
""" """
@ -200,8 +204,7 @@ class EventHandler(DefaultScript):
return callbacks return callbacks
def add_callback(self, obj, callback_name, code, author=None, valid=False, def add_callback(self, obj, callback_name, code, author=None, valid=False, parameters=""):
parameters=""):
""" """
Add the specified callback. Add the specified callback.
@ -228,21 +231,22 @@ class EventHandler(DefaultScript):
callbacks = obj_callbacks[callback_name] callbacks = obj_callbacks[callback_name]
# Add the callback in the list # Add the callback in the list
callbacks.append({ callbacks.append(
"created_on": datetime.now(), {
"author": author, "created_on": datetime.now(),
"valid": valid, "author": author,
"code": code, "valid": valid,
"parameters": parameters, "code": code,
}) "parameters": parameters,
}
)
# If not valid, set it in 'to_valid' # If not valid, set it in 'to_valid'
if not valid: if not valid:
self.db.to_valid.append((obj, callback_name, len(callbacks) - 1)) self.db.to_valid.append((obj, callback_name, len(callbacks) - 1))
# Call the custom_add if needed # Call the custom_add if needed
custom_add = self.get_events(obj).get( custom_add = self.get_events(obj).get(callback_name, [None, None, None, None])[3]
callback_name, [None, None, None, None])[3]
if custom_add: if custom_add:
custom_add(obj, callback_name, len(callbacks) - 1, parameters) custom_add(obj, callback_name, len(callbacks) - 1, parameters)
@ -253,8 +257,7 @@ class EventHandler(DefaultScript):
definition["number"] = len(callbacks) - 1 definition["number"] = len(callbacks) - 1
return definition return definition
def edit_callback(self, obj, callback_name, number, code, author=None, def edit_callback(self, obj, callback_name, number, code, author=None, valid=False):
valid=False):
""" """
Edit the specified callback. Edit the specified callback.
@ -288,12 +291,9 @@ class EventHandler(DefaultScript):
raise RuntimeError("this callback is locked.") raise RuntimeError("this callback is locked.")
# Edit the callback # Edit the callback
callbacks[number].update({ callbacks[number].update(
"updated_on": datetime.now(), {"updated_on": datetime.now(), "updated_by": author, "valid": valid, "code": code}
"updated_by": author, )
"valid": valid,
"code": code,
})
# If not valid, set it in 'to_valid' # If not valid, set it in 'to_valid'
if not valid and (obj, callback_name, number) not in self.db.to_valid: if not valid and (obj, callback_name, number) not in self.db.to_valid:
@ -334,8 +334,9 @@ class EventHandler(DefaultScript):
except IndexError: except IndexError:
return return
else: else:
logger.log_info("Deleting callback {} {} of {}:\n{}".format( logger.log_info(
callback_name, number, obj, code)) "Deleting callback {} {} of {}:\n{}".format(callback_name, number, obj, code)
)
del callbacks[number] del callbacks[number]
# Change IDs of callbacks to be validated # Change IDs of callbacks to be validated
@ -349,8 +350,7 @@ class EventHandler(DefaultScript):
i -= 1 i -= 1
elif t_number > number: elif t_number > number:
# Change the ID for this callback # Change the ID for this callback
self.db.to_valid.insert(i, (t_obj, t_callback_name, self.db.to_valid.insert(i, (t_obj, t_callback_name, t_number - 1))
t_number - 1))
del self.db.to_valid[i + 1] del self.db.to_valid[i + 1]
i += 1 i += 1
@ -415,13 +415,16 @@ class EventHandler(DefaultScript):
# Errors should not pass silently # Errors should not pass silently
allowed = ("number", "parameters", "locals") allowed = ("number", "parameters", "locals")
if any(k for k in kwargs if k not in allowed): if any(k for k in kwargs if k not in allowed):
raise TypeError("Unknown keyword arguments were specified " raise TypeError(
"to call callbacks: {}".format(kwargs)) "Unknown keyword arguments were specified " "to call callbacks: {}".format(kwargs)
)
event = self.get_events(obj).get(callback_name) event = self.get_events(obj).get(callback_name)
if locals is None and not event: if locals is None and not event:
logger.log_err("The callback {} for the object {} (typeclass " logger.log_err(
"{}) can't be found".format(callback_name, obj, type(obj))) "The callback {} for the object {} (typeclass "
"{}) can't be found".format(callback_name, obj, type(obj))
)
return False return False
# Prepare the locals if necessary # Prepare the locals if necessary
@ -431,9 +434,10 @@ class EventHandler(DefaultScript):
try: try:
locals[variable] = args[i] locals[variable] = args[i]
except IndexError: except IndexError:
logger.log_trace("callback {} of {} ({}): need variable " logger.log_trace(
"{} in position {}".format(callback_name, obj, "callback {} of {} ({}): need variable "
type(obj), variable, i)) "{} in position {}".format(callback_name, obj, type(obj), variable, i)
)
return False return False
else: else:
locals = {key: value for key, value in locals.items()} locals = {key: value for key, value in locals.items()}
@ -483,9 +487,10 @@ class EventHandler(DefaultScript):
number = callback["number"] number = callback["number"]
obj = callback["obj"] obj = callback["obj"]
oid = obj.id oid = obj.id
logger.log_err("An error occurred during the callback {} of " logger.log_err(
"{} (#{}), number {}\n{}".format(callback_name, obj, "An error occurred during the callback {} of "
oid, number + 1, "\n".join(trace))) "{} (#{}), number {}\n{}".format(callback_name, obj, oid, number + 1, "\n".join(trace))
)
# Create the error message # Create the error message
line = "|runknown|n" line = "|runknown|n"
@ -505,9 +510,9 @@ class EventHandler(DefaultScript):
break break
exc = raw(trace[-1].strip("\n").splitlines()[-1]) exc = raw(trace[-1].strip("\n").splitlines()[-1])
err_msg = "Error in {} of {} (#{})[{}], line {}:" \ err_msg = "Error in {} of {} (#{})[{}], line {}:" " {}\n{}".format(
" {}\n{}".format(callback_name, obj, callback_name, obj, oid, number + 1, lineno, line, exc
oid, number + 1, lineno, line, exc) )
# Inform the last updater if connected # Inform the last updater if connected
updater = callback.get("updated_by") updater = callback.get("updated_by")
@ -517,9 +522,9 @@ class EventHandler(DefaultScript):
if updater and updater.sessions.all(): if updater and updater.sessions.all():
updater.msg(err_msg) updater.msg(err_msg)
else: else:
err_msg = "Error in {} of {} (#{})[{}], line {}:" \ err_msg = "Error in {} of {} (#{})[{}], line {}:" " {}\n {}".format(
" {}\n {}".format(callback_name, obj, callback_name, obj, oid, number + 1, lineno, line, exc
oid, number + 1, lineno, line, exc) )
self.ndb.channel.msg(err_msg) self.ndb.channel.msg(err_msg)
def add_event(self, typeclass, name, variables, help_text, custom_call, custom_add): def add_event(self, typeclass, name, variables, help_text, custom_call, custom_add):
@ -656,8 +661,7 @@ def complete_task(task_id):
return return
if task_id not in script.db.tasks: if task_id not in script.db.tasks:
logger.log_err("The task #{} was scheduled, but it cannot be " logger.log_err("The task #{} was scheduled, but it cannot be " "found".format(task_id))
"found".format(task_id))
return return
delta, obj, callback_name, locals = script.db.tasks.pop(task_id) delta, obj, callback_name, locals = script.db.tasks.pop(task_id)

View file

@ -31,8 +31,7 @@ class TestEventHandler(EvenniaTest):
def setUp(self): def setUp(self):
"""Create the event handler.""" """Create the event handler."""
super().setUp() super().setUp()
self.handler = create_script( self.handler = create_script("evennia.contrib.ingame_python.scripts.EventHandler")
"evennia.contrib.ingame_python.scripts.EventHandler")
# Copy old events if necessary # Copy old events if necessary
if OLD_EVENTS: if OLD_EVENTS:
@ -64,8 +63,9 @@ class TestEventHandler(EvenniaTest):
def test_add_validation(self): def test_add_validation(self):
"""Add a callback while needing validation.""" """Add a callback while needing validation."""
author = self.char1 author = self.char1
self.handler.add_callback(self.room1, "dummy", self.handler.add_callback(
"character.db.strength = 40", author=author, valid=False) self.room1, "dummy", "character.db.strength = 40", author=author, valid=False
)
callback = self.handler.get_callbacks(self.room1).get("dummy") callback = self.handler.get_callbacks(self.room1).get("dummy")
callback = callback[0] callback = callback[0]
self.assertIsNotNone(callback) self.assertIsNotNone(callback)
@ -78,19 +78,20 @@ class TestEventHandler(EvenniaTest):
# Run this dummy callback (shouldn't do anything) # Run this dummy callback (shouldn't do anything)
self.char1.db.strength = 10 self.char1.db.strength = 10
locals = {"character": self.char1} locals = {"character": self.char1}
self.assertTrue(self.handler.call( self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals))
self.room1, "dummy", locals=locals))
self.assertEqual(self.char1.db.strength, 10) self.assertEqual(self.char1.db.strength, 10)
def test_edit(self): def test_edit(self):
"""Test editing a callback.""" """Test editing a callback."""
author = self.char1 author = self.char1
self.handler.add_callback(self.room1, "dummy", self.handler.add_callback(
"character.db.strength = 60", author=author, valid=True) self.room1, "dummy", "character.db.strength = 60", author=author, valid=True
)
# Edit it right away # Edit it right away
self.handler.edit_callback(self.room1, "dummy", 0, self.handler.edit_callback(
"character.db.strength = 65", author=self.char2, valid=True) self.room1, "dummy", 0, "character.db.strength = 65", author=self.char2, valid=True
)
# Check that the callback was written # Check that the callback was written
callback = self.handler.get_callbacks(self.room1).get("dummy") callback = self.handler.get_callbacks(self.room1).get("dummy")
@ -103,36 +104,39 @@ class TestEventHandler(EvenniaTest):
# Run this dummy callback # Run this dummy callback
self.char1.db.strength = 10 self.char1.db.strength = 10
locals = {"character": self.char1} locals = {"character": self.char1}
self.assertTrue(self.handler.call( self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals))
self.room1, "dummy", locals=locals))
self.assertEqual(self.char1.db.strength, 65) self.assertEqual(self.char1.db.strength, 65)
def test_edit_validation(self): def test_edit_validation(self):
"""Edit a callback when validation isn't automatic.""" """Edit a callback when validation isn't automatic."""
author = self.char1 author = self.char1
self.handler.add_callback(self.room1, "dummy", self.handler.add_callback(
"character.db.strength = 70", author=author, valid=True) self.room1, "dummy", "character.db.strength = 70", author=author, valid=True
)
# Edit it right away # Edit it right away
self.handler.edit_callback(self.room1, "dummy", 0, self.handler.edit_callback(
"character.db.strength = 80", author=self.char2, valid=False) self.room1, "dummy", 0, "character.db.strength = 80", author=self.char2, valid=False
)
# Run this dummy callback (shouldn't do anything) # Run this dummy callback (shouldn't do anything)
self.char1.db.strength = 10 self.char1.db.strength = 10
locals = {"character": self.char1} locals = {"character": self.char1}
self.assertTrue(self.handler.call( self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals))
self.room1, "dummy", locals=locals))
self.assertEqual(self.char1.db.strength, 10) self.assertEqual(self.char1.db.strength, 10)
def test_del(self): def test_del(self):
"""Try to delete a callback.""" """Try to delete a callback."""
# Add 3 callbacks # Add 3 callbacks
self.handler.add_callback(self.room1, "dummy", self.handler.add_callback(
"character.db.strength = 5", author=self.char1, valid=True) self.room1, "dummy", "character.db.strength = 5", author=self.char1, valid=True
self.handler.add_callback(self.room1, "dummy", )
"character.db.strength = 8", author=self.char2, valid=False) self.handler.add_callback(
self.handler.add_callback(self.room1, "dummy", self.room1, "dummy", "character.db.strength = 8", author=self.char2, valid=False
"character.db.strength = 9", author=self.char1, valid=True) )
self.handler.add_callback(
self.room1, "dummy", "character.db.strength = 9", author=self.char1, valid=True
)
# Note that the second callback isn't valid # Note that the second callback isn't valid
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid) self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
@ -160,17 +164,18 @@ class TestEventHandler(EvenniaTest):
# Call the remaining callback # Call the remaining callback
self.char1.db.strength = 10 self.char1.db.strength = 10
locals = {"character": self.char1} locals = {"character": self.char1}
self.assertTrue(self.handler.call( self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals))
self.room1, "dummy", locals=locals))
self.assertEqual(self.char1.db.strength, 9) self.assertEqual(self.char1.db.strength, 9)
def test_accept(self): def test_accept(self):
"""Accept an callback.""" """Accept an callback."""
# Add 2 callbacks # Add 2 callbacks
self.handler.add_callback(self.room1, "dummy", self.handler.add_callback(
"character.db.strength = 5", author=self.char1, valid=True) self.room1, "dummy", "character.db.strength = 5", author=self.char1, valid=True
self.handler.add_callback(self.room1, "dummy", )
"character.db.strength = 8", author=self.char2, valid=False) self.handler.add_callback(
self.room1, "dummy", "character.db.strength = 8", author=self.char2, valid=False
)
# Note that the second callback isn't valid # Note that the second callback isn't valid
self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid) self.assertIn((self.room1, "dummy", 1), self.handler.db.to_valid)
@ -185,8 +190,7 @@ class TestEventHandler(EvenniaTest):
# Call the dummy callback # Call the dummy callback
self.char1.db.strength = 10 self.char1.db.strength = 10
locals = {"character": self.char1} locals = {"character": self.char1}
self.assertTrue(self.handler.call( self.assertTrue(self.handler.call(self.room1, "dummy", locals=locals))
self.room1, "dummy", locals=locals))
self.assertEqual(self.char1.db.strength, 8) self.assertEqual(self.char1.db.strength, 8)
def test_call(self): def test_call(self):
@ -195,21 +199,22 @@ class TestEventHandler(EvenniaTest):
self.char2.key = "two" self.char2.key = "two"
# Add an callback # Add an callback
code = dedent(""" code = dedent(
"""
if character.key == "one": if character.key == "one":
character.db.health = 50 character.db.health = 50
else: else:
character.db.health = 0 character.db.health = 0
""".strip("\n")) """.strip(
self.handler.add_callback(self.room1, "dummy", code, "\n"
author=self.char1, valid=True) )
)
self.handler.add_callback(self.room1, "dummy", code, author=self.char1, valid=True)
# Call the dummy callback # Call the dummy callback
self.assertTrue(self.handler.call( self.assertTrue(self.handler.call(self.room1, "dummy", locals={"character": self.char1}))
self.room1, "dummy", locals={"character": self.char1}))
self.assertEqual(self.char1.db.health, 50) self.assertEqual(self.char1.db.health, 50)
self.assertTrue(self.handler.call( self.assertTrue(self.handler.call(self.room1, "dummy", locals={"character": self.char2}))
self.room1, "dummy", locals={"character": self.char2}))
self.assertEqual(self.char2.db.health, 0) self.assertEqual(self.char2.db.health, 0)
def test_handler(self): def test_handler(self):
@ -217,8 +222,7 @@ class TestEventHandler(EvenniaTest):
self.assertIsNotNone(self.char1.callbacks) self.assertIsNotNone(self.char1.callbacks)
# Add an callback # Add an callback
callback = self.room1.callbacks.add("dummy", "pass", author=self.char1, callback = self.room1.callbacks.add("dummy", "pass", author=self.char1, valid=True)
valid=True)
self.assertEqual(callback.obj, self.room1) self.assertEqual(callback.obj, self.room1)
self.assertEqual(callback.name, "dummy") self.assertEqual(callback.name, "dummy")
self.assertEqual(callback.code, "pass") self.assertEqual(callback.code, "pass")
@ -227,14 +231,14 @@ class TestEventHandler(EvenniaTest):
self.assertIn([callback], list(self.room1.callbacks.all().values())) self.assertIn([callback], list(self.room1.callbacks.all().values()))
# Edit this very callback # Edit this very callback
new = self.room1.callbacks.edit("dummy", 0, "character.db.say = True", new = self.room1.callbacks.edit(
author=self.char1, valid=True) "dummy", 0, "character.db.say = True", author=self.char1, valid=True
)
self.assertIn([new], list(self.room1.callbacks.all().values())) self.assertIn([new], list(self.room1.callbacks.all().values()))
self.assertNotIn([callback], list(self.room1.callbacks.all().values())) self.assertNotIn([callback], list(self.room1.callbacks.all().values()))
# Try to call this callback # Try to call this callback
self.assertTrue(self.room1.callbacks.call("dummy", self.assertTrue(self.room1.callbacks.call("dummy", locals={"character": self.char2}))
locals={"character": self.char2}))
self.assertTrue(self.char2.db.say) self.assertTrue(self.char2.db.say)
# Delete the callback # Delete the callback
@ -249,8 +253,7 @@ class TestCmdCallback(CommandTest):
def setUp(self): def setUp(self):
"""Create the callback handler.""" """Create the callback handler."""
super().setUp() super().setUp()
self.handler = create_script( self.handler = create_script("evennia.contrib.ingame_python.scripts.EventHandler")
"evennia.contrib.ingame_python.scripts.EventHandler")
# Copy old events if necessary # Copy old events if necessary
if OLD_EVENTS: if OLD_EVENTS:
@ -269,7 +272,8 @@ class TestCmdCallback(CommandTest):
OLD_EVENTS.update(self.handler.ndb.events) OLD_EVENTS.update(self.handler.ndb.events)
self.handler.stop() self.handler.stop()
for script in ScriptDB.objects.filter( for script in ScriptDB.objects.filter(
db_typeclass_path="evennia.contrib.ingame_python.scripts.TimeEventScript"): db_typeclass_path="evennia.contrib.ingame_python.scripts.TimeEventScript"
):
script.stop() script.stop()
CallbackHandler.script = None CallbackHandler.script = None
@ -287,8 +291,7 @@ class TestCmdCallback(CommandTest):
self.assertIn(cols[2].strip(), ("0 (0)", "")) self.assertIn(cols[2].strip(), ("0 (0)", ""))
# Add some callback # Add some callback
self.handler.add_callback(self.exit, "traverse", "pass", self.handler.add_callback(self.exit, "traverse", "pass", author=self.char1, valid=True)
author=self.char1, valid=True)
# Try to obtain more details on a specific callback on exit # Try to obtain more details on a specific callback on exit
table = self.call(CmdCallback(), "out = traverse") table = self.call(CmdCallback(), "out = traverse")
@ -322,13 +325,19 @@ class TestCmdCallback(CommandTest):
self.assertIsNotNone(editor) self.assertIsNotNone(editor)
# Edit the callback # Edit the callback
editor.update_buffer(dedent(""" editor.update_buffer(
dedent(
"""
if character.key == "one": if character.key == "one":
character.msg("You can pass.") character.msg("You can pass.")
else: else:
character.msg("You can't pass.") character.msg("You can't pass.")
deny() deny()
""".strip("\n"))) """.strip(
"\n"
)
)
)
editor.save_buffer() editor.save_buffer()
editor.quit() editor.quit()
callback = self.exit.callbacks.get("traverse")[0] callback = self.exit.callbacks.get("traverse")[0]
@ -343,9 +352,15 @@ class TestCmdCallback(CommandTest):
self.assertIsNotNone(editor) self.assertIsNotNone(editor)
# Edit the callback # Edit the callback
editor.update_buffer(dedent(""" editor.update_buffer(
dedent(
"""
character.msg("No way.") character.msg("No way.")
""".strip("\n"))) """.strip(
"\n"
)
)
)
editor.save_buffer() editor.save_buffer()
editor.quit() editor.quit()
callback = self.exit.callbacks.get("traverse")[1] callback = self.exit.callbacks.get("traverse")[1]
@ -355,19 +370,16 @@ class TestCmdCallback(CommandTest):
def test_del(self): def test_del(self):
"""Add and remove an callback.""" """Add and remove an callback."""
self.handler.add_callback(self.exit, "traverse", "pass", self.handler.add_callback(self.exit, "traverse", "pass", author=self.char1, valid=True)
author=self.char1, valid=True)
# Try to delete the callback # Try to delete the callback
# char2 shouldn't be allowed to do so (that's not HIS callback) # char2 shouldn't be allowed to do so (that's not HIS callback)
self.call(CmdCallback(), "/del out = traverse 1", caller=self.char2) self.call(CmdCallback(), "/del out = traverse 1", caller=self.char2)
self.assertTrue(len(self.handler.get_callbacks(self.exit).get( self.assertTrue(len(self.handler.get_callbacks(self.exit).get("traverse", [])) == 1)
"traverse", [])) == 1)
# Now, char1 should be allowed to delete it # Now, char1 should be allowed to delete it
self.call(CmdCallback(), "/del out = traverse 1") self.call(CmdCallback(), "/del out = traverse 1")
self.assertTrue(len(self.handler.get_callbacks(self.exit).get( self.assertTrue(len(self.handler.get_callbacks(self.exit).get("traverse", [])) == 0)
"traverse", [])) == 0)
def test_lock(self): def test_lock(self):
"""Test the lock of multiple editing.""" """Test the lock of multiple editing."""
@ -388,9 +400,15 @@ class TestCmdCallback(CommandTest):
self.assertIsNotNone(editor) self.assertIsNotNone(editor)
# Edit the callback # Edit the callback
editor.update_buffer(dedent(""" editor.update_buffer(
dedent(
"""
room.msg_contents("It's 8 PM, everybody up!") room.msg_contents("It's 8 PM, everybody up!")
""".strip("\n"))) """.strip(
"\n"
)
)
)
editor.save_buffer() editor.save_buffer()
editor.quit() editor.quit()
callback = self.room1.callbacks.get("time")[0] callback = self.room1.callbacks.get("time")[0]
@ -414,8 +432,7 @@ class TestDefaultCallbacks(CommandTest):
def setUp(self): def setUp(self):
"""Create the callback handler.""" """Create the callback handler."""
super().setUp() super().setUp()
self.handler = create_script( self.handler = create_script("evennia.contrib.ingame_python.scripts.EventHandler")
"evennia.contrib.ingame_python.scripts.EventHandler")
# Copy old events if necessary # Copy old events if necessary
if OLD_EVENTS: if OLD_EVENTS:
@ -439,33 +456,36 @@ class TestDefaultCallbacks(CommandTest):
def test_exit(self): def test_exit(self):
"""Test the callbacks of an exit.""" """Test the callbacks of an exit."""
self.char1.key = "char1" self.char1.key = "char1"
code = dedent(""" code = dedent(
"""
if character.key == "char1": if character.key == "char1":
character.msg("You can leave.") character.msg("You can leave.")
else: else:
character.msg("You cannot leave.") character.msg("You cannot leave.")
deny() deny()
""".strip("\n")) """.strip(
"\n"
)
)
# Enforce self.exit.destination since swapping typeclass lose it # Enforce self.exit.destination since swapping typeclass lose it
self.exit.destination = self.room2 self.exit.destination = self.room2
# Try the can_traverse callback # Try the can_traverse callback
self.handler.add_callback(self.exit, "can_traverse", code, self.handler.add_callback(self.exit, "can_traverse", code, author=self.char1, valid=True)
author=self.char1, valid=True)
# Have char1 move through the exit # Have char1 move through the exit
self.call(ExitCommand(), "", "You can leave.", obj=self.exit) self.call(ExitCommand(), "", "You can leave.", obj=self.exit)
self.assertIs(self.char1.location, self.room2) self.assertIs(self.char1.location, self.room2)
# Have char2 move through this exit # Have char2 move through this exit
self.call(ExitCommand(), "", "You cannot leave.", obj=self.exit, self.call(ExitCommand(), "", "You cannot leave.", obj=self.exit, caller=self.char2)
caller=self.char2)
self.assertIs(self.char2.location, self.room1) self.assertIs(self.char2.location, self.room1)
# Try the traverse callback # Try the traverse callback
self.handler.del_callback(self.exit, "can_traverse", 0) self.handler.del_callback(self.exit, "can_traverse", 0)
self.handler.add_callback(self.exit, "traverse", "character.msg('Fine!')", self.handler.add_callback(
author=self.char1, valid=True) self.exit, "traverse", "character.msg('Fine!')", author=self.char1, valid=True
)
# Have char2 move through the exit # Have char2 move through the exit
self.call(ExitCommand(), "", obj=self.exit, caller=self.char2) self.call(ExitCommand(), "", obj=self.exit, caller=self.char2)
@ -478,16 +498,17 @@ class TestDefaultCallbacks(CommandTest):
# Test msg_arrive and msg_leave # Test msg_arrive and msg_leave
code = 'message = "{character} goes out."' code = 'message = "{character} goes out."'
self.handler.add_callback(self.exit, "msg_leave", code, self.handler.add_callback(self.exit, "msg_leave", code, author=self.char1, valid=True)
author=self.char1, valid=True)
# Have char1 move through the exit # Have char1 move through the exit
old_msg = self.char2.msg old_msg = self.char2.msg
try: try:
self.char2.msg = Mock() self.char2.msg = Mock()
self.call(ExitCommand(), "", obj=self.exit) self.call(ExitCommand(), "", obj=self.exit)
stored_msg = [args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs)) stored_msg = [
for name, args, kwargs in self.char2.msg.mock_calls] args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs))
for name, args, kwargs in self.char2.msg.mock_calls
]
# Get the first element of a tuple if msg received a tuple instead of a string # 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] stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True) returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)
@ -496,19 +517,24 @@ class TestDefaultCallbacks(CommandTest):
self.char2.msg = old_msg self.char2.msg = old_msg
# Create a return exit # Create a return exit
back = create_object("evennia.objects.objects.DefaultExit", back = create_object(
key="in", location=self.room2, destination=self.room1) "evennia.objects.objects.DefaultExit",
key="in",
location=self.room2,
destination=self.room1,
)
code = 'message = "{character} goes in."' code = 'message = "{character} goes in."'
self.handler.add_callback(self.exit, "msg_arrive", code, self.handler.add_callback(self.exit, "msg_arrive", code, author=self.char1, valid=True)
author=self.char1, valid=True)
# Have char1 move through the exit # Have char1 move through the exit
old_msg = self.char2.msg old_msg = self.char2.msg
try: try:
self.char2.msg = Mock() self.char2.msg = Mock()
self.call(ExitCommand(), "", obj=back) self.call(ExitCommand(), "", obj=back)
stored_msg = [args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs)) stored_msg = [
for name, args, kwargs in self.char2.msg.mock_calls] args[0] if args and args[0] else kwargs.get("text", utils.to_str(kwargs))
for name, args, kwargs in self.char2.msg.mock_calls
]
# Get the first element of a tuple if msg received a tuple instead of a string # 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] stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True) returned_msg = ansi.parse_ansi("\n".join(stored_msg), strip_ansi=True)

View file

@ -212,15 +212,16 @@ class EventCharacter(DefaultCharacter):
# Get the exit from location to destination # Get the exit from location to destination
location = self.location location = self.location
exits = [o for o in location.contents if o.location is location and o.destination is destination] exits = [
o for o in location.contents if o.location is location and o.destination is destination
]
mapping = mapping or {} mapping = mapping or {}
mapping.update({ mapping.update({"character": self})
"character": self,
})
if exits: if exits:
exits[0].callbacks.call("msg_leave", self, exits[0], exits[0].callbacks.call(
location, destination, string, mapping) "msg_leave", self, exits[0], location, destination, string, mapping
)
string = exits[0].callbacks.get_variable("message") string = exits[0].callbacks.get_variable("message")
mapping = exits[0].callbacks.get_variable("mapping") mapping = exits[0].callbacks.get_variable("mapping")
@ -267,15 +268,18 @@ class EventCharacter(DefaultCharacter):
destination = self.location destination = self.location
exits = [] exits = []
mapping = mapping or {} mapping = mapping or {}
mapping.update({ mapping.update({"character": self})
"character": self,
})
if origin: if origin:
exits = [o for o in destination.contents if o.location is destination and o.destination is origin] exits = [
o
for o in destination.contents
if o.location is destination and o.destination is origin
]
if exits: if exits:
exits[0].callbacks.call("msg_arrive", self, exits[0], exits[0].callbacks.call(
origin, destination, string, mapping) "msg_arrive", self, exits[0], origin, destination, string, mapping
)
string = exits[0].callbacks.get_variable("message") string = exits[0].callbacks.get_variable("message")
mapping = exits[0].callbacks.get_variable("mapping") mapping = exits[0].callbacks.get_variable("mapping")
@ -305,14 +309,16 @@ class EventCharacter(DefaultCharacter):
origin = self.location origin = self.location
Room = DefaultRoom Room = DefaultRoom
if isinstance(origin, Room) and isinstance(destination, Room): if isinstance(origin, Room) and isinstance(destination, Room):
can = self.callbacks.call("can_move", self, can = self.callbacks.call("can_move", self, origin, destination)
origin, destination)
if can: if can:
can = origin.callbacks.call("can_move", self, origin) can = origin.callbacks.call("can_move", self, origin)
if can: if can:
# Call other character's 'can_part' event # Call other character's 'can_part' event
for present in [o for o in origin.contents if isinstance( for present in [
o, DefaultCharacter) and o is not self]: o
for o in origin.contents
if isinstance(o, DefaultCharacter) and o is not self
]:
can = present.callbacks.call("can_part", present, self) can = present.callbacks.call("can_part", present, self)
if not can: if not can:
break break
@ -344,8 +350,9 @@ class EventCharacter(DefaultCharacter):
destination.callbacks.call("move", self, origin, destination) destination.callbacks.call("move", self, origin, destination)
# Call the 'greet' event of characters in the location # Call the 'greet' event of characters in the location
for present in [o for o in destination.contents if isinstance( for present in [
o, DefaultCharacter) and o is not self]: o for o in destination.contents if isinstance(o, DefaultCharacter) and o is not self
]:
present.callbacks.call("greet", present, self) present.callbacks.call("greet", present, self)
def at_object_delete(self): def at_object_delete(self):
@ -427,7 +434,11 @@ class EventCharacter(DefaultCharacter):
""" """
# First, try the location # First, try the location
location = getattr(self, "location", None) location = getattr(self, "location", None)
location = location if location and inherits_from(location, "evennia.objects.objects.DefaultRoom") else None location = (
location
if location and inherits_from(location, "evennia.objects.objects.DefaultRoom")
else None
)
if location and not kwargs.get("whisper", False): if location and not kwargs.get("whisper", False):
allow = location.callbacks.call("can_say", self, location, message, parameters=message) allow = location.callbacks.call("can_say", self, location, message, parameters=message)
message = location.callbacks.get_variable("message") message = location.callbacks.get_variable("message")
@ -436,7 +447,9 @@ class EventCharacter(DefaultCharacter):
# Browse all the room's other characters # Browse all the room's other characters
for obj in location.contents: for obj in location.contents:
if obj is self or not inherits_from(obj, "evennia.objects.objects.DefaultCharacter"): if obj is self or not inherits_from(
obj, "evennia.objects.objects.DefaultCharacter"
):
continue continue
allow = obj.callbacks.call("can_say", self, obj, message, parameters=message) allow = obj.callbacks.call("can_say", self, obj, message, parameters=message)
@ -490,14 +503,22 @@ class EventCharacter(DefaultCharacter):
super().at_say(message, **kwargs) super().at_say(message, **kwargs)
location = getattr(self, "location", None) location = getattr(self, "location", None)
location = location if location and inherits_from(location, "evennia.objects.objects.DefaultRoom") else None location = (
location
if location and inherits_from(location, "evennia.objects.objects.DefaultRoom")
else None
)
if location and not kwargs.get("whisper", False): if location and not kwargs.get("whisper", False):
location.callbacks.call("say", self, location, message, location.callbacks.call("say", self, location, message, parameters=message)
parameters=message)
# Call the other characters' "say" event # Call the other characters' "say" event
presents = [obj for obj in location.contents if obj is not self and inherits_from(obj, "evennia.objects.objects.DefaultCharacter")] presents = [
obj
for obj in location.contents
if obj is not self
and inherits_from(obj, "evennia.objects.objects.DefaultCharacter")
]
for present in presents: for present in presents:
present.callbacks.call("say", self, present, message, parameters=message) present.callbacks.call("say", self, present, message, parameters=message)
@ -602,8 +623,14 @@ class EventExit(DefaultExit):
_events = { _events = {
"can_traverse": (["character", "exit", "room"], EXIT_CAN_TRAVERSE), "can_traverse": (["character", "exit", "room"], EXIT_CAN_TRAVERSE),
"msg_arrive": (["character", "exit", "origin", "destination", "message", "mapping"], EXIT_MSG_ARRIVE), "msg_arrive": (
"msg_leave": (["character", "exit", "origin", "destination", "message", "mapping"], EXIT_MSG_LEAVE), ["character", "exit", "origin", "destination", "message", "mapping"],
EXIT_MSG_ARRIVE,
),
"msg_leave": (
["character", "exit", "origin", "destination", "message", "mapping"],
EXIT_MSG_LEAVE,
),
"time": (["exit"], EXIT_TIME, None, time_event), "time": (["exit"], EXIT_TIME, None, time_event),
"traverse": (["character", "exit", "origin", "destination"], EXIT_TRAVERSE), "traverse": (["character", "exit", "origin", "destination"], EXIT_TRAVERSE),
} }
@ -630,8 +657,7 @@ class EventExit(DefaultExit):
""" """
is_character = inherits_from(traversing_object, DefaultCharacter) is_character = inherits_from(traversing_object, DefaultCharacter)
if is_character: if is_character:
allow = self.callbacks.call("can_traverse", traversing_object, allow = self.callbacks.call("can_traverse", traversing_object, self, self.location)
self, self.location)
if not allow: if not allow:
return return
@ -639,8 +665,9 @@ class EventExit(DefaultExit):
# After traversing # After traversing
if is_character: if is_character:
self.callbacks.call("traverse", traversing_object, self.callbacks.call(
self, self.location, self.destination) "traverse", traversing_object, self, self.location, self.destination
)
# Object help # Object help

View file

@ -85,6 +85,7 @@ def register_events(path_or_typeclass):
return typeclass return typeclass
# Custom callbacks for specific event types # Custom callbacks for specific event types
@ -108,8 +109,10 @@ def get_next_wait(format):
""" """
calendar = getattr(settings, "EVENTS_CALENDAR", None) calendar = getattr(settings, "EVENTS_CALENDAR", None)
if calendar is None: if calendar is None:
logger.log_err("A time-related event has been set whereas " logger.log_err(
"the gametime calendar has not been set in the settings.") "A time-related event has been set whereas "
"the gametime calendar has not been set in the settings."
)
return return
elif calendar == "standard": elif calendar == "standard":
rsu = standard_rsu rsu = standard_rsu
@ -135,8 +138,9 @@ def get_next_wait(format):
break break
if not piece.isdigit(): if not piece.isdigit():
logger.log_trace("The time specified '{}' in {} isn't " logger.log_trace(
"a valid number".format(piece, format)) "The time specified '{}' in {} isn't " "a valid number".format(piece, format)
)
return return
# Convert the piece to int # Convert the piece to int
@ -171,7 +175,9 @@ def time_event(obj, event_name, number, parameters):
""" """
seconds, usual, key = get_next_wait(parameters) seconds, usual, key = get_next_wait(parameters)
script = create_script("evennia.contrib.ingame_python.scripts.TimeEventScript", interval=seconds, obj=obj) script = create_script(
"evennia.contrib.ingame_python.scripts.TimeEventScript", interval=seconds, obj=obj
)
script.key = key script.key = key
script.desc = "event on {}".format(key) script.desc = "event on {}".format(key)
script.db.time_format = parameters script.db.time_format = parameters

View file

@ -80,6 +80,7 @@ class CmdMail(default_cmds.MuxAccountCommand):
@mail/reply 9=Thanks for the info! @mail/reply 9=Thanks for the info!
""" """
key = "@mail" key = "@mail"
aliases = ["mail"] aliases = ["mail"]
lock = "cmd:all()" lock = "cmd:all()"
@ -92,8 +93,9 @@ class CmdMail(default_cmds.MuxAccountCommand):
""" """
super().parse() super().parse()
self.caller_is_account = bool(inherits_from(self.caller, self.caller_is_account = bool(
"evennia.accounts.accounts.DefaultAccount")) inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount")
)
def search_targets(self, namelist): def search_targets(self, namelist):
""" """
@ -143,9 +145,9 @@ class CmdMail(default_cmds.MuxAccountCommand):
""" """
for recipient in recipients: for recipient in recipients:
recipient.msg("You have received a new @mail from %s" % caller) recipient.msg("You have received a new @mail from %s" % caller)
new_message = create.create_message(self.caller, message, new_message = create.create_message(
receivers=recipient, self.caller, message, receivers=recipient, header=subject
header=subject) )
new_message.tags.add("new", category="mail") new_message.tags.add("new", category="mail")
if recipients: if recipients:
@ -176,7 +178,7 @@ class CmdMail(default_cmds.MuxAccountCommand):
if all_mail[mind]: if all_mail[mind]:
mail = all_mail[mind] mail = all_mail[mind]
question = "Delete message {} ({}) [Y]/N?".format(mind + 1, mail.header) question = "Delete message {} ({}) [Y]/N?".format(mind + 1, mail.header)
ret = yield(question) ret = yield (question)
# handle not ret, it will be None during unit testing # handle not ret, it will be None during unit testing
if not ret or ret.strip().upper() not in ("N", "No"): if not ret or ret.strip().upper() not in ("N", "No"):
all_mail[mind].delete() all_mail[mind].delete()
@ -192,8 +194,9 @@ class CmdMail(default_cmds.MuxAccountCommand):
elif "forward" in self.switches or "fwd" in self.switches: elif "forward" in self.switches or "fwd" in self.switches:
try: try:
if not self.rhs: if not self.rhs:
self.caller.msg("Cannot forward a message without a target list. " self.caller.msg(
"Please try again.") "Cannot forward a message without a target list. " "Please try again."
)
return return
elif not self.lhs: elif not self.lhs:
self.caller.msg("You must define a message to forward.") self.caller.msg("You must define a message to forward.")
@ -208,9 +211,14 @@ class CmdMail(default_cmds.MuxAccountCommand):
if all_mail[mind]: if all_mail[mind]:
old_message = all_mail[mind] old_message = all_mail[mind]
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header, self.send_mail(
message + "\n---- Original Message ----\n" + old_message.message, self.search_targets(self.lhslist),
self.caller) "FWD: " + old_message.header,
message
+ "\n---- Original Message ----\n"
+ old_message.message,
self.caller,
)
self.caller.msg("Message forwarded.") self.caller.msg("Message forwarded.")
else: else:
raise IndexError raise IndexError
@ -218,8 +226,12 @@ class CmdMail(default_cmds.MuxAccountCommand):
mind = max(0, min(mind_max, int(self.rhs) - 1)) mind = max(0, min(mind_max, int(self.rhs) - 1))
if all_mail[mind]: if all_mail[mind]:
old_message = all_mail[mind] old_message = all_mail[mind]
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header, self.send_mail(
"\n---- Original Message ----\n" + old_message.message, self.caller) self.search_targets(self.lhslist),
"FWD: " + old_message.header,
"\n---- Original Message ----\n" + old_message.message,
self.caller,
)
self.caller.msg("Message forwarded.") self.caller.msg("Message forwarded.")
old_message.tags.remove("new", category="mail") old_message.tags.remove("new", category="mail")
old_message.tags.add("fwd", category="mail") old_message.tags.add("fwd", category="mail")
@ -243,8 +255,12 @@ class CmdMail(default_cmds.MuxAccountCommand):
mind = max(0, min(mind_max, int(self.lhs) - 1)) mind = max(0, min(mind_max, int(self.lhs) - 1))
if all_mail[mind]: if all_mail[mind]:
old_message = all_mail[mind] old_message = all_mail[mind]
self.send_mail(old_message.senders, "RE: " + old_message.header, self.send_mail(
self.rhs + "\n---- Original Message ----\n" + old_message.message, self.caller) old_message.senders,
"RE: " + old_message.header,
self.rhs + "\n---- Original Message ----\n" + old_message.message,
self.caller,
)
old_message.tags.remove("new", category="mail") old_message.tags.remove("new", category="mail")
old_message.tags.add("-", category="mail") old_message.tags.add("-", category="mail")
return return
@ -275,10 +291,15 @@ class CmdMail(default_cmds.MuxAccountCommand):
messageForm = [] messageForm = []
if message: if message:
messageForm.append(_HEAD_CHAR * _WIDTH) messageForm.append(_HEAD_CHAR * _WIDTH)
messageForm.append("|wFrom:|n %s" % (message.senders[0].get_display_name(self.caller))) messageForm.append(
"|wFrom:|n %s" % (message.senders[0].get_display_name(self.caller))
)
# note that we cannot use %-d format here since Windows does not support it # note that we cannot use %-d format here since Windows does not support it
day = message.db_date_created.day day = message.db_date_created.day
messageForm.append("|wSent:|n %s" % message.db_date_created.strftime(f"%b {day}, %Y - %H:%M:%S")) messageForm.append(
"|wSent:|n %s"
% message.db_date_created.strftime(f"%b {day}, %Y - %H:%M:%S")
)
messageForm.append("|wSubject:|n %s" % message.header) messageForm.append("|wSubject:|n %s" % message.header)
messageForm.append(_SUB_HEAD_CHAR * _WIDTH) messageForm.append(_SUB_HEAD_CHAR * _WIDTH)
messageForm.append(message.message) messageForm.append(message.message)
@ -292,20 +313,30 @@ class CmdMail(default_cmds.MuxAccountCommand):
messages = self.get_all_mail() messages = self.get_all_mail()
if messages: if messages:
table = evtable.EvTable("|wID|n", "|wFrom|n", "|wSubject|n", table = evtable.EvTable(
"|wArrived|n", "", "|wID|n",
table=None, border="header", "|wFrom|n",
header_line_char=_SUB_HEAD_CHAR, width=_WIDTH) "|wSubject|n",
"|wArrived|n",
"",
table=None,
border="header",
header_line_char=_SUB_HEAD_CHAR,
width=_WIDTH,
)
index = 1 index = 1
for message in messages: for message in messages:
status = str(message.db_tags.last().db_key.upper()) status = str(message.db_tags.last().db_key.upper())
if status == "NEW": if status == "NEW":
status = "|gNEW|n" status = "|gNEW|n"
table.add_row(index, message.senders[0].get_display_name(self.caller), table.add_row(
message.header, index,
datetime_format(message.db_date_created), message.senders[0].get_display_name(self.caller),
status) message.header,
datetime_format(message.db_date_created),
status,
)
index += 1 index += 1
table.reformat_column(0, width=6) table.reformat_column(0, width=6)
@ -323,5 +354,6 @@ class CmdMail(default_cmds.MuxAccountCommand):
# character - level version of the command # character - level version of the command
class CmdMailCharacter(CmdMail): class CmdMailCharacter(CmdMail):
account_caller = False account_caller = False

View file

@ -133,9 +133,11 @@ def example1_build_mountains(x, y, **kwargs):
room = create_object(rooms.Room, key="mountains" + str(x) + str(y)) room = create_object(rooms.Room, key="mountains" + str(x) + str(y))
# Generate a description by randomly selecting an entry from a list. # Generate a description by randomly selecting an entry from a list.
room_desc = ["Mountains as far as the eye can see", room_desc = [
"Your path is surrounded by sheer cliffs", "Mountains as far as the eye can see",
"Haven't you seen that rock before?"] "Your path is surrounded by sheer cliffs",
"Haven't you seen that rock before?",
]
room.db.desc = random.choice(room_desc) room.db.desc = random.choice(room_desc)
# Create a random number of objects to populate the room. # Create a random number of objects to populate the room.
@ -157,15 +159,17 @@ def example1_build_temple(x, y, **kwargs):
room = create_object(rooms.Room, key="temple" + str(x) + str(y)) room = create_object(rooms.Room, key="temple" + str(x) + str(y))
# Set the description. # Set the description.
room.db.desc = ("In what, from the outside, appeared to be a grand and " room.db.desc = (
"ancient temple you've somehow found yourself in the the " "In what, from the outside, appeared to be a grand and "
"Evennia Inn! It consists of one large room filled with " "ancient temple you've somehow found yourself in the the "
"tables. The bardisk extends along the east wall, where " "Evennia Inn! It consists of one large room filled with "
"multiple barrels and bottles line the shelves. The " "tables. The bardisk extends along the east wall, where "
"barkeep seems busy handing out ale and chatting with " "multiple barrels and bottles line the shelves. The "
"the patrons, which are a rowdy and cheerful lot, " "barkeep seems busy handing out ale and chatting with "
"keeping the sound level only just below thunderous. " "the patrons, which are a rowdy and cheerful lot, "
"This is a rare spot of mirth on this dread moor.") "keeping the sound level only just below thunderous. "
"This is a rare spot of mirth on this dread moor."
)
# Send a message to the account # Send a message to the account
kwargs["caller"].msg(room.key + " " + room.dbref) kwargs["caller"].msg(room.key + " " + room.dbref)
@ -175,9 +179,11 @@ def example1_build_temple(x, y, **kwargs):
# Include your trigger characters and build functions in a legend dict. # Include your trigger characters and build functions in a legend dict.
EXAMPLE1_LEGEND = {("", ""): example1_build_forest, EXAMPLE1_LEGEND = {
("", "n"): example1_build_mountains, ("", ""): example1_build_forest,
(""): example1_build_temple} ("", "n"): example1_build_mountains,
(""): example1_build_temple,
}
# ---------- EXAMPLE 2 ---------- # # ---------- EXAMPLE 2 ---------- #
# @mapbuilder/two evennia.contrib.mapbuilder.EXAMPLE2_MAP EXAMPLE2_LEGEND # @mapbuilder/two evennia.contrib.mapbuilder.EXAMPLE2_MAP EXAMPLE2_LEGEND
@ -185,11 +191,11 @@ EXAMPLE1_LEGEND = {("♣", "♠"): example1_build_forest,
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Add the necessary imports for your instructions here. # Add the necessary imports for your instructions here.
#from evennia import create_object # from evennia import create_object
#from typeclasses import rooms, exits # from typeclasses import rooms, exits
#from evennia.utils import utils # from evennia.utils import utils
#from random import randint # from random import randint
#import random # import random
# This is the same layout as Example 1 but included are characters for exits. # This is the same layout as Example 1 but included are characters for exits.
# We can use these characters to determine which rooms should be connected. # We can use these characters to determine which rooms should be connected.
@ -230,16 +236,15 @@ def example2_build_verticle_exit(x, y, **kwargs):
south_room = kwargs["room_dict"][(x, y + 1)] south_room = kwargs["room_dict"][(x, y + 1)]
# create exits in the rooms # create exits in the rooms
create_object(exits.Exit, key="south", create_object(
aliases=["s"], location=north_room, exits.Exit, key="south", aliases=["s"], location=north_room, destination=south_room
destination=south_room) )
create_object(exits.Exit, key="north", create_object(
aliases=["n"], location=south_room, exits.Exit, key="north", aliases=["n"], location=south_room, destination=north_room
destination=north_room) )
kwargs["caller"].msg("Connected: " + north_room.key + kwargs["caller"].msg("Connected: " + north_room.key + " & " + south_room.key)
" & " + south_room.key)
def example2_build_horizontal_exit(x, y, **kwargs): def example2_build_horizontal_exit(x, y, **kwargs):
@ -251,22 +256,19 @@ def example2_build_horizontal_exit(x, y, **kwargs):
west_room = kwargs["room_dict"][(x - 1, y)] west_room = kwargs["room_dict"][(x - 1, y)]
east_room = kwargs["room_dict"][(x + 1, y)] east_room = kwargs["room_dict"][(x + 1, y)]
create_object(exits.Exit, key="east", create_object(exits.Exit, key="east", aliases=["e"], location=west_room, destination=east_room)
aliases=["e"], location=west_room,
destination=east_room)
create_object(exits.Exit, key="west", create_object(exits.Exit, key="west", aliases=["w"], location=east_room, destination=west_room)
aliases=["w"], location=east_room,
destination=west_room)
kwargs["caller"].msg("Connected: " + west_room.key + kwargs["caller"].msg("Connected: " + west_room.key + " & " + east_room.key)
" & " + east_room.key)
# Include your trigger characters and build functions in a legend dict. # Include your trigger characters and build functions in a legend dict.
EXAMPLE2_LEGEND = {("", ""): example2_build_forest, EXAMPLE2_LEGEND = {
("|"): example2_build_verticle_exit, ("", ""): example2_build_forest,
("-"): example2_build_horizontal_exit} ("|"): example2_build_verticle_exit,
("-"): example2_build_horizontal_exit,
}
# ---------- END OF EXAMPLES ---------- # # ---------- END OF EXAMPLES ---------- #
@ -285,7 +287,7 @@ def _map_to_list(game_map):
list (list): The map split into rows list (list): The map split into rows
""" """
return game_map.split('\n') return game_map.split("\n")
def build_map(caller, game_map, legend, iterations=1, build_exits=True): def build_map(caller, game_map, legend, iterations=1, build_exits=True):
@ -325,9 +327,9 @@ def build_map(caller, game_map, legend, iterations=1, build_exits=True):
for key in legend: for key in legend:
# obs - we must use == for strings # obs - we must use == for strings
if game_map[y][x] == key: if game_map[y][x] == key:
room = legend[key](x, y, iteration=iteration, room = legend[key](
room_dict=room_dict, x, y, iteration=iteration, room_dict=room_dict, caller=caller
caller=caller) )
if iteration == 0: if iteration == 0:
room_dict[(x, y)] = room room_dict[(x, y)] = room
@ -341,33 +343,50 @@ def build_map(caller, game_map, legend, iterations=1, build_exits=True):
# north # north
if (x, y - 1) in room_dict: if (x, y - 1) in room_dict:
if room_dict[(x, y - 1)]: if room_dict[(x, y - 1)]:
create_object(exits.Exit, key="north", create_object(
aliases=["n"], location=location, exits.Exit,
destination=room_dict[(x, y - 1)]) key="north",
aliases=["n"],
location=location,
destination=room_dict[(x, y - 1)],
)
# east # east
if (x + 1, y) in room_dict: if (x + 1, y) in room_dict:
if room_dict[(x + 1, y)]: if room_dict[(x + 1, y)]:
create_object(exits.Exit, key="east", create_object(
aliases=["e"], location=location, exits.Exit,
destination=room_dict[(x + 1, y)]) key="east",
aliases=["e"],
location=location,
destination=room_dict[(x + 1, y)],
)
# south # south
if (x, y + 1) in room_dict: if (x, y + 1) in room_dict:
if room_dict[(x, y + 1)]: if room_dict[(x, y + 1)]:
create_object(exits.Exit, key="south", create_object(
aliases=["s"], location=location, exits.Exit,
destination=room_dict[(x, y + 1)]) key="south",
aliases=["s"],
location=location,
destination=room_dict[(x, y + 1)],
)
# west # west
if (x - 1, y) in room_dict: if (x - 1, y) in room_dict:
if room_dict[(x - 1, y)]: if room_dict[(x - 1, y)]:
create_object(exits.Exit, key="west", create_object(
aliases=["w"], location=location, exits.Exit,
destination=room_dict[(x - 1, y)]) key="west",
aliases=["w"],
location=location,
destination=room_dict[(x - 1, y)],
)
caller.msg("Map Created.") caller.msg("Map Created.")
# access command # access command
@ -399,6 +418,7 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
automatically generated but is turned off by switches which also determines automatically generated but is turned off by switches which also determines
how many times the map is iterated over. how many times the map is iterated over.
""" """
key = "@mapbuilder" key = "@mapbuilder"
aliases = ["@buildmap"] aliases = ["@buildmap"]
locks = "cmd:superuser()" locks = "cmd:superuser()"
@ -412,8 +432,7 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
# Check if arguments passed. # Check if arguments passed.
if not self.args or (len(args) != 2): if not self.args or (len(args) != 2):
caller.msg("Usage: @mapbuilder <path.to.module.VARNAME> " caller.msg("Usage: @mapbuilder <path.to.module.VARNAME> " "<path.to.module.MAP_LEGEND>")
"<path.to.module.MAP_LEGEND>")
return return
# Set up base variables. # Set up base variables.
@ -424,17 +443,18 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
# Breaks down path_to_map into [PATH, VARIABLE] # Breaks down path_to_map into [PATH, VARIABLE]
path_to_map = args[0] path_to_map = args[0]
path_to_map = path_to_map.rsplit('.', 1) path_to_map = path_to_map.rsplit(".", 1)
try: try:
# Retrieves map variable from module or raises error. # Retrieves map variable from module or raises error.
game_map = utils.variable_from_module(path_to_map[0], game_map = utils.variable_from_module(path_to_map[0], path_to_map[1])
path_to_map[1])
if not game_map: if not game_map:
raise ValueError("Command Aborted!\n" raise ValueError(
"Path to map variable failed.\n" "Command Aborted!\n"
"Usage: @mapbuilder <path.to.module." "Path to map variable failed.\n"
"VARNAME> <path.to.module.MAP_LEGEND>") "Usage: @mapbuilder <path.to.module."
"VARNAME> <path.to.module.MAP_LEGEND>"
)
except Exception as exc: except Exception as exc:
# Or relays error message if fails. # Or relays error message if fails.
@ -445,7 +465,7 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
# Breaks down path_to_legend into [PATH, VARIABLE] # Breaks down path_to_legend into [PATH, VARIABLE]
path_to_legend = args[1] path_to_legend = args[1]
path_to_legend = path_to_legend.rsplit('.', 1) path_to_legend = path_to_legend.rsplit(".", 1)
# If no path given default to path_to_map's path # If no path given default to path_to_map's path
if len(path_to_legend) == 1: if len(path_to_legend) == 1:
@ -453,13 +473,14 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
try: try:
# Retrieves legend variable from module or raises error if fails. # Retrieves legend variable from module or raises error if fails.
legend = utils.variable_from_module(path_to_legend[0], legend = utils.variable_from_module(path_to_legend[0], path_to_legend[1])
path_to_legend[1])
if not legend: if not legend:
raise ValueError("Command Aborted!\n" raise ValueError(
"Path to legend variable failed.\n" "Command Aborted!\n"
"Usage: @mapbuilder <path.to.module." "Path to legend variable failed.\n"
"VARNAME> <path.to.module.MAP_LEGEND>") "Usage: @mapbuilder <path.to.module."
"VARNAME> <path.to.module.MAP_LEGEND>"
)
except Exception as exc: except Exception as exc:
# Or relays error message if fails. # Or relays error message if fails.

View file

@ -24,25 +24,27 @@ from django.conf import settings
from evennia import Command, CmdSet from evennia import Command, CmdSet
from evennia import syscmdkeys from evennia import syscmdkeys
from evennia.utils.evmenu import EvMenu from evennia.utils.evmenu import EvMenu
from evennia.utils.utils import ( from evennia.utils.utils import random_string_from_module, class_from_module, callables_from_module
random_string_from_module, class_from_module, callables_from_module)
_CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE _CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
_GUEST_ENABLED = settings.GUEST_ENABLED _GUEST_ENABLED = settings.GUEST_ENABLED
_ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS) _ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
_GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS) _GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS)
_ACCOUNT_HELP = ("Enter the name you used to log into the game before, " _ACCOUNT_HELP = (
"or a new account-name if you are new.") "Enter the name you used to log into the game before, " "or a new account-name if you are new."
_PASSWORD_HELP = ("Password should be a minimum of 8 characters (preferably longer) and " )
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only.") _PASSWORD_HELP = (
"Password should be a minimum of 8 characters (preferably longer) and "
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
)
# Menu nodes # Menu nodes
def _show_help(caller, raw_string, **kwargs): def _show_help(caller, raw_string, **kwargs):
"""Echo help message, then re-run node that triggered it""" """Echo help message, then re-run node that triggered it"""
help_entry = kwargs['help_entry'] help_entry = kwargs["help_entry"]
caller.msg(help_entry) caller.msg(help_entry)
return None # re-run calling node return None # re-run calling node
@ -53,6 +55,7 @@ def node_enter_username(caller, raw_text, **kwargs):
Start login by displaying the connection screen and ask for a user name. Start login by displaying the connection screen and ask for a user name.
""" """
def _check_input(caller, username, **kwargs): def _check_input(caller, username, **kwargs):
""" """
'Goto-callable', set up to be called from the _default option below. 'Goto-callable', set up to be called from the _default option below.
@ -65,9 +68,9 @@ def node_enter_username(caller, raw_text, **kwargs):
and what kwarg it will be called with. and what kwarg it will be called with.
""" """
username = username.rstrip('\n') username = username.rstrip("\n")
if username == 'guest' and _GUEST_ENABLED: if username == "guest" and _GUEST_ENABLED:
# do an immediate guest login # do an immediate guest login
session = caller session = caller
address = session.address address = session.address
@ -76,7 +79,7 @@ def node_enter_username(caller, raw_text, **kwargs):
return "node_quit_or_login", {"login": True, "account": account} return "node_quit_or_login", {"login": True, "account": account}
else: else:
session.msg("|R{}|n".format("\n".join(errors))) session.msg("|R{}|n".format("\n".join(errors)))
return None # re-run the username node return None # re-run the username node
try: try:
_ACCOUNT.objects.get(username__iexact=username) _ACCOUNT.objects.get(username__iexact=username)
@ -86,28 +89,26 @@ def node_enter_username(caller, raw_text, **kwargs):
new_user = False new_user = False
# pass username/new_user into next node as kwargs # pass username/new_user into next node as kwargs
return "node_enter_password", {'new_user': new_user, 'username': username} return "node_enter_password", {"new_user": new_user, "username": username}
callables = callables_from_module(_CONNECTION_SCREEN_MODULE) callables = callables_from_module(_CONNECTION_SCREEN_MODULE)
if "connection_screen" in callables: if "connection_screen" in callables:
connection_screen = callables['connection_screen']() connection_screen = callables["connection_screen"]()
else: else:
connection_screen = random_string_from_module(_CONNECTION_SCREEN_MODULE) connection_screen = random_string_from_module(_CONNECTION_SCREEN_MODULE)
if _GUEST_ENABLED: if _GUEST_ENABLED:
text = "Enter a new or existing user name to login (write 'guest' for a guest login):" text = "Enter a new or existing user name to login (write 'guest' for a guest login):"
else: else:
text = "Enter a new or existing user name to login:" text = "Enter a new or existing user name to login:"
text = "{}\n\n{}".format(connection_screen, text) text = "{}\n\n{}".format(connection_screen, text)
options = ({"key": "", options = (
"goto": "node_enter_username"}, {"key": "", "goto": "node_enter_username"},
{"key": ("quit", "q"), {"key": ("quit", "q"), "goto": "node_quit_or_login"},
"goto": "node_quit_or_login"}, {"key": ("help", "h"), "goto": (_show_help, {"help_entry": _ACCOUNT_HELP, **kwargs})},
{"key": ("help", "h"), {"key": "_default", "goto": _check_input},
"goto": (_show_help, {"help_entry": _ACCOUNT_HELP, **kwargs})}, )
{"key": "_default",
"goto": _check_input})
return text, options return text, options
@ -116,6 +117,7 @@ def node_enter_password(caller, raw_string, **kwargs):
Handle password input. Handle password input.
""" """
def _check_input(caller, password, **kwargs): def _check_input(caller, password, **kwargs):
""" """
'Goto-callable', set up to be called from the _default option below. 'Goto-callable', set up to be called from the _default option below.
@ -129,20 +131,22 @@ def node_enter_password(caller, raw_string, **kwargs):
""" """
# these flags were set by the goto-callable # these flags were set by the goto-callable
username = kwargs['username'] username = kwargs["username"]
new_user = kwargs['new_user'] new_user = kwargs["new_user"]
password = password.rstrip('\n') password = password.rstrip("\n")
session = caller session = caller
address = session.address address = session.address
if new_user: if new_user:
# create a new account # create a new account
account, errors = _ACCOUNT.create(username=username, password=password, account, errors = _ACCOUNT.create(
ip=address, session=session) username=username, password=password, ip=address, session=session
)
else: else:
# check password against existing account # check password against existing account
account, errors = _ACCOUNT.authenticate(username=username, password=password, account, errors = _ACCOUNT.authenticate(
ip=address, session=session) username=username, password=password, ip=address, session=session
)
if account: if account:
if new_user: if new_user:
@ -152,32 +156,31 @@ def node_enter_password(caller, raw_string, **kwargs):
else: else:
# restart due to errors # restart due to errors
session.msg("|R{}".format("\n".join(errors))) session.msg("|R{}".format("\n".join(errors)))
kwargs['retry_password'] = True kwargs["retry_password"] = True
return "node_enter_password", kwargs return "node_enter_password", kwargs
def _restart_login(caller, *args, **kwargs): def _restart_login(caller, *args, **kwargs):
caller.msg("|yCancelled login.|n") caller.msg("|yCancelled login.|n")
return "node_enter_username" return "node_enter_username"
username = kwargs['username'] username = kwargs["username"]
if kwargs["new_user"]: if kwargs["new_user"]:
if kwargs.get('retry_password'): if kwargs.get("retry_password"):
# Attempting to fix password # Attempting to fix password
text = "Enter a new password:" text = "Enter a new password:"
else: else:
text = ("Creating a new account |c{}|n. " text = "Creating a new account |c{}|n. " "Enter a password (empty to abort):".format(
"Enter a password (empty to abort):".format(username)) username
)
else: else:
text = "Enter the password for account |c{}|n (empty to abort):".format(username) text = "Enter the password for account |c{}|n (empty to abort):".format(username)
options = ({"key": "", options = (
"goto": _restart_login}, {"key": "", "goto": _restart_login},
{"key": ("quit", "q"), {"key": ("quit", "q"), "goto": "node_quit_or_login"},
"goto": "node_quit_or_login"}, {"key": ("help", "h"), "goto": (_show_help, {"help_entry": _PASSWORD_HELP, **kwargs})},
{"key": ("help", "h"), {"key": "_default", "goto": (_check_input, kwargs)},
"goto": (_show_help, {"help_entry": _PASSWORD_HELP, **kwargs})}, )
{"key": "_default",
"goto": (_check_input, kwargs)})
return text, options return text, options
@ -198,6 +201,7 @@ def node_quit_or_login(caller, raw_text, **kwargs):
# EvMenu helper function # EvMenu helper function
def _node_formatter(nodetext, optionstext, caller=None): def _node_formatter(nodetext, optionstext, caller=None):
"""Do not display the options, only the text. """Do not display the options, only the text.
@ -211,6 +215,7 @@ def _node_formatter(nodetext, optionstext, caller=None):
# Commands and CmdSets # Commands and CmdSets
class UnloggedinCmdSet(CmdSet): class UnloggedinCmdSet(CmdSet):
"Cmdset for the unloggedin state" "Cmdset for the unloggedin state"
key = "DefaultUnloggedin" key = "DefaultUnloggedin"
@ -228,6 +233,7 @@ class CmdUnloggedinLook(Command):
to the menu's own look command. to the menu's own look command.
""" """
key = syscmdkeys.CMD_LOGINSTART key = syscmdkeys.CMD_LOGINSTART
locks = "cmd:all()" locks = "cmd:all()"
arg_regex = r"^$" arg_regex = r"^$"
@ -237,6 +243,12 @@ class CmdUnloggedinLook(Command):
Run the menu using the nodes in this module. Run the menu using the nodes in this module.
""" """
EvMenu(self.caller, "evennia.contrib.menu_login", EvMenu(
startnode="node_enter_username", auto_look=False, auto_quit=False, self.caller,
cmd_on_exit=None, node_formatter=_node_formatter) "evennia.contrib.menu_login",
startnode="node_enter_username",
auto_look=False,
auto_quit=False,
cmd_on_exit=None,
node_formatter=_node_formatter,
)

View file

@ -38,6 +38,7 @@ _RE_KEYS = re.compile(r"([\w\s]+)(?:\+*?)", re.U + re.I)
# Helper functions for the Command # Helper functions for the Command
class DescValidateError(ValueError): class DescValidateError(ValueError):
"Used for tracebacks from desc systems" "Used for tracebacks from desc systems"
pass pass
@ -95,6 +96,7 @@ def _update_store(caller, key=None, desc=None, delete=False, swapkey=None):
else: else:
raise DescValidateError("No description was set.") raise DescValidateError("No description was set.")
# eveditor save/load/quit functions # eveditor save/load/quit functions
@ -123,6 +125,7 @@ def _quit_editor(caller):
# The actual command class # The actual command class
class CmdMultiDesc(default_cmds.MuxCommand): class CmdMultiDesc(default_cmds.MuxCommand):
""" """
Manage multiple descriptions Manage multiple descriptions
@ -144,6 +147,7 @@ class CmdMultiDesc(default_cmds.MuxCommand):
paragraph and + + or ansi space ||_ to add extra whitespace. paragraph and + + or ansi space ||_ to add extra whitespace.
""" """
key = "+desc" key = "+desc"
aliases = ["desc"] aliases = ["desc"]
locks = "cmd:all()" locks = "cmd:all()"
@ -166,11 +170,14 @@ class CmdMultiDesc(default_cmds.MuxCommand):
_update_store(caller) _update_store(caller)
do_crop = "full" not in switches do_crop = "full" not in switches
if do_crop: if do_crop:
outtext = ["|w%s:|n %s" % (key, crop(desc)) outtext = [
for key, desc in caller.db.multidesc] "|w%s:|n %s" % (key, crop(desc)) for key, desc in caller.db.multidesc
]
else: else:
outtext = ["\n|w%s:|n|n\n%s\n%s" % (key, "-" * (len(key) + 1), desc) outtext = [
for key, desc in caller.db.multidesc] "\n|w%s:|n|n\n%s\n%s" % (key, "-" * (len(key) + 1), desc)
for key, desc in caller.db.multidesc
]
caller.msg("|wStored descs:|n\n" + "\n".join(outtext)) caller.msg("|wStored descs:|n\n" + "\n".join(outtext))
return return
@ -184,8 +191,14 @@ class CmdMultiDesc(default_cmds.MuxCommand):
# this is used by the editor to know what to edit; it's deleted automatically # this is used by the editor to know what to edit; it's deleted automatically
caller.db._multidesc_editkey = args caller.db._multidesc_editkey = args
# start the editor # start the editor
EvEditor(caller, loadfunc=_load_editor, savefunc=_save_editor, EvEditor(
quitfunc=_quit_editor, key="multidesc editor", persistent=True) caller,
loadfunc=_load_editor,
savefunc=_save_editor,
quitfunc=_quit_editor,
key="multidesc editor",
persistent=True,
)
elif "delete" in switches or "del" in switches: elif "delete" in switches or "del" in switches:
# delete a multidesc entry. # delete a multidesc entry.

View file

@ -81,17 +81,18 @@ from evennia.utils import search, utils, logger
from evennia.prototypes.spawner import spawn from evennia.prototypes.spawner import spawn
# Tag used by puzzles # Tag used by puzzles
_PUZZLES_TAG_CATEGORY = 'puzzles' _PUZZLES_TAG_CATEGORY = "puzzles"
_PUZZLES_TAG_RECIPE = 'puzzle_recipe' _PUZZLES_TAG_RECIPE = "puzzle_recipe"
# puzzle part and puzzle result # puzzle part and puzzle result
_PUZZLES_TAG_MEMBER = 'puzzle_member' _PUZZLES_TAG_MEMBER = "puzzle_member"
_PUZZLE_DEFAULT_FAIL_USE_MESSAGE = 'You try to utilize %s but nothing happens ... something amiss?' _PUZZLE_DEFAULT_FAIL_USE_MESSAGE = "You try to utilize %s but nothing happens ... something amiss?"
_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = 'You are a Genius!!!' _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = "You are a Genius!!!"
_PUZZLE_DEFAULT_SUCCESS_USE_LOCATION_MESSAGE = "|c{caller}|n performs some kind of tribal dance and |y{result_names}|n seems to appear from thin air" _PUZZLE_DEFAULT_SUCCESS_USE_LOCATION_MESSAGE = "|c{caller}|n performs some kind of tribal dance and |y{result_names}|n seems to appear from thin air"
# ----------- UTILITY FUNCTIONS ------------ # ----------- UTILITY FUNCTIONS ------------
def proto_def(obj, with_tags=True): def proto_def(obj, with_tags=True):
""" """
Basic properties needed to spawn Basic properties needed to spawn
@ -99,20 +100,20 @@ def proto_def(obj, with_tags=True):
""" """
protodef = { protodef = {
# TODO: Don't we need to honor ALL properties? attributes, contents, etc. # TODO: Don't we need to honor ALL properties? attributes, contents, etc.
'prototype_key': '%s(%s)' % (obj.key, obj.dbref), "prototype_key": "%s(%s)" % (obj.key, obj.dbref),
'key': obj.key, "key": obj.key,
'typeclass': obj.typeclass_path, "typeclass": obj.typeclass_path,
'desc': obj.db.desc, "desc": obj.db.desc,
'location': obj.location, "location": obj.location,
'home': obj.home, "home": obj.home,
'locks': ';'.join(obj.locks.all()), "locks": ";".join(obj.locks.all()),
'permissions': obj.permissions.all()[:], "permissions": obj.permissions.all()[:],
} }
if with_tags: if with_tags:
tags = obj.tags.all(return_key_and_category=True) tags = obj.tags.all(return_key_and_category=True)
tags = [(t[0], t[1], None) for t in tags] tags = [(t[0], t[1], None) for t in tags]
tags.append((_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY, None)) tags.append((_PUZZLES_TAG_MEMBER, _PUZZLES_TAG_CATEGORY, None))
protodef['tags'] = tags protodef["tags"] = tags
return protodef return protodef
@ -130,14 +131,15 @@ def maskout_protodef(protodef, mask):
# Colorize the default success message # Colorize the default success message
def _colorize_message(msg): def _colorize_message(msg):
_i = 0 _i = 0
_colors = ['|r', '|g', '|y'] _colors = ["|r", "|g", "|y"]
_msg = [] _msg = []
for l in msg: for l in msg:
_msg += _colors[_i] + l _msg += _colors[_i] + l
_i = (_i + 1) % len(_colors) _i = (_i + 1) % len(_colors)
msg = ''.join(_msg) + '|n' msg = "".join(_msg) + "|n"
return msg return msg
_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = _colorize_message(_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE) _PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE = _colorize_message(_PUZZLE_DEFAULT_SUCCESS_USE_MESSAGE)
# ------------------------------------------ # ------------------------------------------
@ -182,13 +184,13 @@ class CmdCreatePuzzleRecipe(MuxCommand):
""" """
key = '@puzzle' key = "@puzzle"
aliases = '@puzzlerecipe' aliases = "@puzzlerecipe"
locks = 'cmd:perm(puzzle) or perm(Builder)' locks = "cmd:perm(puzzle) or perm(Builder)"
help_category = 'Puzzles' help_category = "Puzzles"
confirm = True confirm = True
default_confirm = 'no' default_confirm = "no"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -200,27 +202,26 @@ class CmdCreatePuzzleRecipe(MuxCommand):
puzzle_name = self.lhslist[0] puzzle_name = self.lhslist[0]
if len(puzzle_name) == 0: if len(puzzle_name) == 0:
caller.msg('Invalid puzzle name %r.' % puzzle_name) caller.msg("Invalid puzzle name %r." % puzzle_name)
return return
# if there is another puzzle with same name # if there is another puzzle with same name
# warn user that parts and results will be # warn user that parts and results will be
# interchangable # interchangable
_puzzles = search.search_script_attribute( _puzzles = search.search_script_attribute(key="puzzle_name", value=puzzle_name)
key='puzzle_name',
value=puzzle_name
)
_puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles)) _puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles))
if _puzzles: if _puzzles:
confirm = 'There are %d puzzles with the same name.\n' % len(_puzzles) \ confirm = (
+ 'Its parts and results will be interchangeable.\n' \ "There are %d puzzles with the same name.\n" % len(_puzzles)
+ 'Continue yes/[no]? ' + "Its parts and results will be interchangeable.\n"
answer = '' + "Continue yes/[no]? "
while answer.strip().lower() not in ('y', 'yes', 'n', 'no'): )
answer = yield(confirm) answer = ""
answer = self.default_confirm if answer == '' else answer while answer.strip().lower() not in ("y", "yes", "n", "no"):
if answer.strip().lower() in ('n', 'no'): answer = yield (confirm)
caller.msg('Cancelled: no puzzle created.') answer = self.default_confirm if answer == "" else answer
if answer.strip().lower() in ("n", "no"):
caller.msg("Cancelled: no puzzle created.")
return return
def is_valid_obj_location(obj): def is_valid_obj_location(obj):
@ -235,7 +236,7 @@ class CmdCreatePuzzleRecipe(MuxCommand):
# located. # located.
# Parts and results may have different valid locations # Parts and results may have different valid locations
if not inherits_from(obj.location, DefaultRoom): if not inherits_from(obj.location, DefaultRoom):
caller.msg('Invalid location for %s' % (obj.key)) caller.msg("Invalid location for %s" % (obj.key))
valid = False valid = False
return valid return valid
@ -246,20 +247,20 @@ class CmdCreatePuzzleRecipe(MuxCommand):
return is_valid_obj_location(part) return is_valid_obj_location(part)
def is_valid_inheritance(obj): def is_valid_inheritance(obj):
valid = not inherits_from(obj, DefaultCharacter) \ valid = (
and not inherits_from(obj, DefaultRoom) \ not inherits_from(obj, DefaultCharacter)
and not inherits_from(obj, DefaultExit) and not inherits_from(obj, DefaultRoom)
and not inherits_from(obj, DefaultExit)
)
if not valid: if not valid:
caller.msg('Invalid typeclass for %s' % (obj)) caller.msg("Invalid typeclass for %s" % (obj))
return valid return valid
def is_valid_part(part): def is_valid_part(part):
return is_valid_inheritance(part) \ return is_valid_inheritance(part) and is_valid_part_location(part)
and is_valid_part_location(part)
def is_valid_result(result): def is_valid_result(result):
return is_valid_inheritance(result) \ return is_valid_inheritance(result) and is_valid_result_location(result)
and is_valid_result_location(result)
parts = [] parts = []
for objname in self.lhslist[1:]: for objname in self.lhslist[1:]:
@ -280,26 +281,28 @@ class CmdCreatePuzzleRecipe(MuxCommand):
results.append(obj) results.append(obj)
for part in parts: for part in parts:
caller.msg('Part %s(%s)' % (part.name, part.dbref)) caller.msg("Part %s(%s)" % (part.name, part.dbref))
for result in results: for result in results:
caller.msg('Result %s(%s)' % (result.name, result.dbref)) caller.msg("Result %s(%s)" % (result.name, result.dbref))
proto_parts = [proto_def(obj) for obj in parts] proto_parts = [proto_def(obj) for obj in parts]
proto_results = [proto_def(obj) for obj in results] proto_results = [proto_def(obj) for obj in results]
puzzle = create_script(PuzzleRecipe, key=puzzle_name) puzzle = create_script(PuzzleRecipe, key=puzzle_name)
puzzle.save_recipe(puzzle_name, proto_parts, proto_results) puzzle.save_recipe(puzzle_name, proto_parts, proto_results)
puzzle.locks.add('control:id(%s) or perm(Builder)' % caller.dbref[1:]) puzzle.locks.add("control:id(%s) or perm(Builder)" % caller.dbref[1:])
caller.msg( caller.msg(
"Puzzle |y'%s' |w%s(%s)|n has been created |gsuccessfully|n." "Puzzle |y'%s' |w%s(%s)|n has been created |gsuccessfully|n."
% (puzzle.db.puzzle_name, puzzle.name, puzzle.dbref)) % (puzzle.db.puzzle_name, puzzle.name, puzzle.dbref)
)
caller.msg( caller.msg(
'You may now dispose of all parts and results. \n' "You may now dispose of all parts and results. \n"
'Use @puzzleedit #{dbref} to customize this puzzle further. \n' "Use @puzzleedit #{dbref} to customize this puzzle further. \n"
'Use @armpuzzle #{dbref} to arm a new puzzle instance.'.format(dbref=puzzle.dbref)) "Use @armpuzzle #{dbref} to arm a new puzzle instance.".format(dbref=puzzle.dbref)
)
class CmdEditPuzzle(MuxCommand): class CmdEditPuzzle(MuxCommand):
@ -331,9 +334,9 @@ class CmdEditPuzzle(MuxCommand):
""" """
key = '@puzzleedit' key = "@puzzleedit"
locks = 'cmd:perm(puzzleedit) or perm(Builder)' locks = "cmd:perm(puzzleedit) or perm(Builder)"
help_category = 'Puzzles' help_category = "Puzzles"
def func(self): def func(self):
self._USAGE = "Usage: @puzzleedit[/switches] <dbref>[/attribute = <value>]" self._USAGE = "Usage: @puzzleedit[/switches] <dbref>[/attribute = <value>]"
@ -343,8 +346,8 @@ class CmdEditPuzzle(MuxCommand):
caller.msg(self._USAGE) caller.msg(self._USAGE)
return return
if '/' in self.lhslist[0]: if "/" in self.lhslist[0]:
recipe_dbref, attr = self.lhslist[0].split('/') recipe_dbref, attr = self.lhslist[0].split("/")
else: else:
recipe_dbref = self.lhslist[0] recipe_dbref = self.lhslist[0]
@ -354,75 +357,73 @@ class CmdEditPuzzle(MuxCommand):
puzzle = search.search_script(recipe_dbref) puzzle = search.search_script(recipe_dbref)
if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe): if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe):
caller.msg('%s(%s) is not a puzzle' % (puzzle[0].name, recipe_dbref)) caller.msg("%s(%s) is not a puzzle" % (puzzle[0].name, recipe_dbref))
return return
puzzle = puzzle[0] puzzle = puzzle[0]
puzzle_name_id = '%s(%s)' % (puzzle.name, puzzle.dbref) puzzle_name_id = "%s(%s)" % (puzzle.name, puzzle.dbref)
if 'delete' in self.switches: if "delete" in self.switches:
if not (puzzle.access(caller, 'control') or puzzle.access(caller, 'delete')): if not (puzzle.access(caller, "control") or puzzle.access(caller, "delete")):
caller.msg("You don't have permission to delete %s." % puzzle_name_id) caller.msg("You don't have permission to delete %s." % puzzle_name_id)
return return
puzzle.delete() puzzle.delete()
caller.msg('%s was deleted' % puzzle_name_id) caller.msg("%s was deleted" % puzzle_name_id)
return return
elif 'addpart' in self.switches: elif "addpart" in self.switches:
objs = self._get_objs() objs = self._get_objs()
if objs: if objs:
added = self._add_parts(objs, puzzle) added = self._add_parts(objs, puzzle)
caller.msg('%s were added to parts' % (', '.join(added))) caller.msg("%s were added to parts" % (", ".join(added)))
return return
elif 'delpart' in self.switches: elif "delpart" in self.switches:
objs = self._get_objs() objs = self._get_objs()
if objs: if objs:
removed = self._remove_parts(objs, puzzle) removed = self._remove_parts(objs, puzzle)
caller.msg('%s were removed from parts' % (', '.join(removed))) caller.msg("%s were removed from parts" % (", ".join(removed)))
return return
elif 'addresult' in self.switches: elif "addresult" in self.switches:
objs = self._get_objs() objs = self._get_objs()
if objs: if objs:
added = self._add_results(objs, puzzle) added = self._add_results(objs, puzzle)
caller.msg('%s were added to results' % (', '.join(added))) caller.msg("%s were added to results" % (", ".join(added)))
return return
elif 'delresult' in self.switches: elif "delresult" in self.switches:
objs = self._get_objs() objs = self._get_objs()
if objs: if objs:
removed = self._remove_results(objs, puzzle) removed = self._remove_results(objs, puzzle)
caller.msg('%s were removed from results' % (', '.join(removed))) caller.msg("%s were removed from results" % (", ".join(removed)))
return return
else: else:
# edit attributes # edit attributes
if not (puzzle.access(caller, 'control') or puzzle.access(caller, 'edit')): if not (puzzle.access(caller, "control") or puzzle.access(caller, "edit")):
caller.msg("You don't have permission to edit %s." % puzzle_name_id) caller.msg("You don't have permission to edit %s." % puzzle_name_id)
return return
if attr == 'use_success_message': if attr == "use_success_message":
puzzle.db.use_success_message = self.rhs puzzle.db.use_success_message = self.rhs
caller.msg( caller.msg(
"%s use_success_message = %s\n" % ( "%s use_success_message = %s\n"
puzzle_name_id, puzzle.db.use_success_message) % (puzzle_name_id, puzzle.db.use_success_message)
) )
return return
elif attr == 'use_success_location_message': elif attr == "use_success_location_message":
puzzle.db.use_success_location_message = self.rhs puzzle.db.use_success_location_message = self.rhs
caller.msg( caller.msg(
"%s use_success_location_message = %s\n" % ( "%s use_success_location_message = %s\n"
puzzle_name_id, puzzle.db.use_success_location_message) % (puzzle_name_id, puzzle.db.use_success_location_message)
) )
return return
elif attr == 'mask': elif attr == "mask":
puzzle.db.mask = tuple(self.rhslist) puzzle.db.mask = tuple(self.rhslist)
caller.msg( caller.msg("%s mask = %r\n" % (puzzle_name_id, puzzle.db.mask))
"%s mask = %r\n" % (puzzle_name_id, puzzle.db.mask)
)
return return
def _get_objs(self): def _get_objs(self):
@ -491,9 +492,9 @@ class CmdArmPuzzle(MuxCommand):
""" """
key = '@armpuzzle' key = "@armpuzzle"
locks = 'cmd:perm(armpuzzle) or perm(Builder)' locks = "cmd:perm(armpuzzle) or perm(Builder)"
help_category = 'Puzzles' help_category = "Puzzles"
def func(self): def func(self):
caller = self.caller caller = self.caller
@ -504,18 +505,21 @@ class CmdArmPuzzle(MuxCommand):
puzzle = search.search_script(self.args) puzzle = search.search_script(self.args)
if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe): if not puzzle or not inherits_from(puzzle[0], PuzzleRecipe):
caller.msg('Invalid puzzle %r' % (self.args)) caller.msg("Invalid puzzle %r" % (self.args))
return return
puzzle = puzzle[0] puzzle = puzzle[0]
caller.msg( caller.msg(
"Puzzle Recipe %s(%s) '%s' found.\nSpawning %d parts ..." % ( "Puzzle Recipe %s(%s) '%s' found.\nSpawning %d parts ..."
puzzle.name, puzzle.dbref, puzzle.db.puzzle_name, len(puzzle.db.parts))) % (puzzle.name, puzzle.dbref, puzzle.db.puzzle_name, len(puzzle.db.parts))
)
for proto_part in puzzle.db.parts: for proto_part in puzzle.db.parts:
part = spawn(proto_part)[0] part = spawn(proto_part)[0]
caller.msg("Part %s(%s) spawned and placed at %s(%s)" % ( caller.msg(
part.name, part.dbref, part.location, part.location.dbref)) "Part %s(%s) spawned and placed at %s(%s)"
% (part.name, part.dbref, part.location, part.location.dbref)
)
part.tags.add(puzzle.db.puzzle_name, category=_PUZZLES_TAG_CATEGORY) part.tags.add(puzzle.db.puzzle_name, category=_PUZZLES_TAG_CATEGORY)
part.db.puzzle_name = puzzle.db.puzzle_name part.db.puzzle_name = puzzle.db.puzzle_name
@ -531,7 +535,7 @@ def _lookups_parts_puzzlenames_protodefs(parts):
parts_dict[part.dbref] = part parts_dict[part.dbref] = part
protodef = proto_def(part, with_tags=False) protodef = proto_def(part, with_tags=False)
# remove 'prototype_key' as it will prevent equality # remove 'prototype_key' as it will prevent equality
del(protodef['prototype_key']) del protodef["prototype_key"]
puzzle_ingredients[part.dbref] = protodef puzzle_ingredients[part.dbref] = protodef
tags_categories = part.tags.all(return_key_and_category=True) tags_categories = part.tags.all(return_key_and_category=True)
for tag, category in tags_categories: for tag, category in tags_categories:
@ -547,10 +551,7 @@ def _puzzles_by_names(names):
# Find all puzzles by puzzle name (i.e. tag name) # Find all puzzles by puzzle name (i.e. tag name)
puzzles = [] puzzles = []
for puzzle_name in names: for puzzle_name in names:
_puzzles = search.search_script_attribute( _puzzles = search.search_script_attribute(key="puzzle_name", value=puzzle_name)
key='puzzle_name',
value=puzzle_name
)
_puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles)) _puzzles = list(filter(lambda p: isinstance(p, PuzzleRecipe), _puzzles))
if not _puzzles: if not _puzzles:
continue continue
@ -567,8 +568,8 @@ def _matching_puzzles(puzzles, puzzlename_tags_dict, puzzle_ingredients):
puzzle_mask = puzzle.db.mask[:] puzzle_mask = puzzle.db.mask[:]
# remove tags and prototype_key as they prevent equality # remove tags and prototype_key as they prevent equality
for i, puzzle_protopart in enumerate(puzzle_protoparts[:]): for i, puzzle_protopart in enumerate(puzzle_protoparts[:]):
del(puzzle_protopart['tags']) del puzzle_protopart["tags"]
del(puzzle_protopart['prototype_key']) del puzzle_protopart["prototype_key"]
puzzle_protopart = maskout_protodef(puzzle_protopart, puzzle_mask) puzzle_protopart = maskout_protodef(puzzle_protopart, puzzle_mask)
puzzle_protoparts[i] = puzzle_protopart puzzle_protoparts[i] = puzzle_protopart
@ -596,19 +597,19 @@ class CmdUsePuzzleParts(MuxCommand):
use <part1[,part2,...>] use <part1[,part2,...>]
""" """
key = 'use' key = "use"
aliases = 'combine' aliases = "combine"
locks = 'cmd:pperm(use) or pperm(Player)' locks = "cmd:pperm(use) or pperm(Player)"
help_category = 'Puzzles' help_category = "Puzzles"
def func(self): def func(self):
caller = self.caller caller = self.caller
if not self.lhs: if not self.lhs:
caller.msg('Use what?') caller.msg("Use what?")
return return
many = 'these' if len(self.lhslist) > 1 else 'this' many = "these" if len(self.lhslist) > 1 else "this"
# either all are parts, or abort finding matching puzzles # either all are parts, or abort finding matching puzzles
parts = [] parts = []
@ -616,8 +617,8 @@ class CmdUsePuzzleParts(MuxCommand):
for partname in partnames: for partname in partnames:
part = caller.search( part = caller.search(
partname, partname,
multimatch_string='Which %s. There are many.\n' % (partname), multimatch_string="Which %s. There are many.\n" % (partname),
nofound_string='There is no %s around.' % (partname) nofound_string="There is no %s around." % (partname),
) )
if not part: if not part:
@ -626,15 +627,16 @@ class CmdUsePuzzleParts(MuxCommand):
if not part.tags.get(_PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY): if not part.tags.get(_PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY):
# not a puzzle part ... abort # not a puzzle part ... abort
caller.msg('You have no idea how %s can be used' % (many)) caller.msg("You have no idea how %s can be used" % (many))
return return
# a valid part # a valid part
parts.append(part) parts.append(part)
# Create lookup dicts by part's dbref and by puzzle_name(tags) # Create lookup dicts by part's dbref and by puzzle_name(tags)
parts_dict, puzzlename_tags_dict, puzzle_ingredients = \ parts_dict, puzzlename_tags_dict, puzzle_ingredients = _lookups_parts_puzzlenames_protodefs(
_lookups_parts_puzzlenames_protodefs(parts) parts
)
# Find all puzzles by puzzle name (i.e. tag name) # Find all puzzles by puzzle name (i.e. tag name)
puzzles = _puzzles_by_names(puzzlename_tags_dict.keys()) puzzles = _puzzles_by_names(puzzlename_tags_dict.keys())
@ -644,8 +646,7 @@ class CmdUsePuzzleParts(MuxCommand):
# Create lookup dict of puzzles by dbref # Create lookup dict of puzzles by dbref
puzzles_dict = dict((puzzle.dbref, puzzle) for puzzle in puzzles) puzzles_dict = dict((puzzle.dbref, puzzle) for puzzle in puzzles)
# Check if parts can be combined to solve a puzzle # Check if parts can be combined to solve a puzzle
matched_puzzles = _matching_puzzles( matched_puzzles = _matching_puzzles(puzzles, puzzlename_tags_dict, puzzle_ingredients)
puzzles, puzzlename_tags_dict, puzzle_ingredients)
if len(matched_puzzles) == 0: if len(matched_puzzles) == 0:
# TODO: we could use part.fail_message instead, if there was one # TODO: we could use part.fail_message instead, if there was one
@ -671,7 +672,7 @@ class CmdUsePuzzleParts(MuxCommand):
# just hint how many. # just hint how many.
if len(largest_puzzles) > 1: if len(largest_puzzles) > 1:
caller.msg( caller.msg(
'Your gears start turning and %d different ideas come to your mind ...\n' "Your gears start turning and %d different ideas come to your mind ...\n"
% (len(largest_puzzles)) % (len(largest_puzzles))
) )
puzzletuple = choice(largest_puzzles) puzzletuple = choice(largest_puzzles)
@ -690,12 +691,11 @@ class CmdUsePuzzleParts(MuxCommand):
for dbref in matched_dbrefparts: for dbref in matched_dbrefparts:
parts_dict[dbref].delete() parts_dict[dbref].delete()
result_names = ', '.join(result_names) result_names = ", ".join(result_names)
caller.msg(puzzle.db.use_success_message) caller.msg(puzzle.db.use_success_message)
caller.location.msg_contents( caller.location.msg_contents(
puzzle.db.use_success_location_message.format( puzzle.db.use_success_location_message.format(caller=caller, result_names=result_names),
caller=caller, result_names=result_names), exclude=(caller,),
exclude=(caller,)
) )
@ -707,15 +707,14 @@ class CmdListPuzzleRecipes(MuxCommand):
@lspuzzlerecipes @lspuzzlerecipes
""" """
key = '@lspuzzlerecipes' key = "@lspuzzlerecipes"
locks = 'cmd:perm(lspuzzlerecipes) or perm(Builder)' locks = "cmd:perm(lspuzzlerecipes) or perm(Builder)"
help_category = 'Puzzles' help_category = "Puzzles"
def func(self): def func(self):
caller = self.caller caller = self.caller
recipes = search.search_script_tag( recipes = search.search_script_tag(_PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY)
_PUZZLES_TAG_RECIPE, category=_PUZZLES_TAG_CATEGORY)
div = "-" * 60 div = "-" * 60
text = [div] text = [div]
@ -723,26 +722,28 @@ class CmdListPuzzleRecipes(MuxCommand):
msgf_item = "%2s|c%15s|n: |w%s|n" msgf_item = "%2s|c%15s|n: |w%s|n"
for recipe in recipes: for recipe in recipes:
text.append(msgf_recipe % (recipe.db.puzzle_name, recipe.name, recipe.dbref)) text.append(msgf_recipe % (recipe.db.puzzle_name, recipe.name, recipe.dbref))
text.append('Success Caller message:\n' + recipe.db.use_success_message + '\n') text.append("Success Caller message:\n" + recipe.db.use_success_message + "\n")
text.append('Success Location message:\n' + recipe.db.use_success_location_message + '\n') text.append(
text.append('Mask:\n' + str(recipe.db.mask) + '\n') "Success Location message:\n" + recipe.db.use_success_location_message + "\n"
text.append('Parts') )
text.append("Mask:\n" + str(recipe.db.mask) + "\n")
text.append("Parts")
for protopart in recipe.db.parts[:]: for protopart in recipe.db.parts[:]:
mark = '-' mark = "-"
for k, v in protopart.items(): for k, v in protopart.items():
text.append(msgf_item % (mark, k, v)) text.append(msgf_item % (mark, k, v))
mark = '' mark = ""
text.append('Results') text.append("Results")
for protoresult in recipe.db.results[:]: for protoresult in recipe.db.results[:]:
mark = '-' mark = "-"
for k, v in protoresult.items(): for k, v in protoresult.items():
text.append(msgf_item % (mark, k, v)) text.append(msgf_item % (mark, k, v))
mark = '' mark = ""
else: else:
text.append(div) text.append(div)
text.append('Found |r%d|n puzzle(s).' % (len(recipes))) text.append("Found |r%d|n puzzle(s)." % (len(recipes)))
text.append(div) text.append(div)
caller.msg('\n'.join(text)) caller.msg("\n".join(text))
class CmdListArmedPuzzles(MuxCommand): class CmdListArmedPuzzles(MuxCommand):
@ -753,35 +754,34 @@ class CmdListArmedPuzzles(MuxCommand):
@lsarmedpuzzles @lsarmedpuzzles
""" """
key = '@lsarmedpuzzles' key = "@lsarmedpuzzles"
locks = 'cmd:perm(lsarmedpuzzles) or perm(Builder)' locks = "cmd:perm(lsarmedpuzzles) or perm(Builder)"
help_category = 'Puzzles' help_category = "Puzzles"
def func(self): def func(self):
caller = self.caller caller = self.caller
armed_puzzles = search.search_tag( armed_puzzles = search.search_tag(_PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY)
_PUZZLES_TAG_MEMBER, category=_PUZZLES_TAG_CATEGORY)
armed_puzzles = dict((k, list(g)) for k, g in itertools.groupby( armed_puzzles = dict(
armed_puzzles, (k, list(g)) for k, g in itertools.groupby(armed_puzzles, lambda ap: ap.db.puzzle_name)
lambda ap: ap.db.puzzle_name)) )
div = '-' * 60 div = "-" * 60
msgf_pznm = "Puzzle name: |y%s|n" msgf_pznm = "Puzzle name: |y%s|n"
msgf_item = "|m%25s|w(%s)|n at |c%25s|w(%s)|n" msgf_item = "|m%25s|w(%s)|n at |c%25s|w(%s)|n"
text = [div] text = [div]
for pzname, items in armed_puzzles.items(): for pzname, items in armed_puzzles.items():
text.append(msgf_pznm % (pzname)) text.append(msgf_pznm % (pzname))
for item in items: for item in items:
text.append(msgf_item % ( text.append(
item.name, item.dbref, msgf_item % (item.name, item.dbref, item.location.name, item.location.dbref)
item.location.name, item.location.dbref)) )
else: else:
text.append(div) text.append(div)
text.append('Found |r%d|n armed puzzle(s).' % (len(armed_puzzles))) text.append("Found |r%d|n armed puzzle(s)." % (len(armed_puzzles)))
text.append(div) text.append(div)
caller.msg('\n'.join(text)) caller.msg("\n".join(text))
class PuzzleSystemCmdSet(CmdSet): class PuzzleSystemCmdSet(CmdSet):

View file

@ -156,7 +156,9 @@ class RandomStringGenerator(object):
self._find_elements(regex) self._find_elements(regex)
def __repr__(self): def __repr__(self):
return "<evennia.contrib.random_string_generator.RandomStringGenerator for {}>".format(self.name) return "<evennia.contrib.random_string_generator.RandomStringGenerator for {}>".format(
self.name
)
def _get_script(self): def _get_script(self):
"""Get or create the script.""" """Get or create the script."""
@ -191,7 +193,9 @@ class RandomStringGenerator(object):
# If `.`, break here # If `.`, break here
if name == "any": if name == "any":
raise RejectedRegex("the . definition is too broad, specify what you need more precisely") raise RejectedRegex(
"the . definition is too broad, specify what you need more precisely"
)
elif name == "at": elif name == "at":
# Either the beginning or end, we ignore it # Either the beginning or end, we ignore it
continue continue
@ -332,8 +336,11 @@ class RandomStringGenerator(object):
script = self._get_script() script = self._get_script()
generated = script.db.generated.get(self.name, []) generated = script.db.generated.get(self.name, [])
if element not in generated: if element not in generated:
raise ValueError("the string {} isn't stored as generated by the generator {}".format( raise ValueError(
element, self.name)) "the string {} isn't stored as generated by the generator {}".format(
element, self.name
)
)
generated.remove(element) generated.remove(element)

View file

@ -97,15 +97,17 @@ from evennia import DefaultScript
from evennia.utils import logger from evennia.utils import logger
#------------------------------------------------------------ # ------------------------------------------------------------
# #
# Obfuscate language # Obfuscate language
# #
#------------------------------------------------------------ # ------------------------------------------------------------
# default language grammar # default language grammar
_PHONEMES = "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh " \ _PHONEMES = (
"s z sh zh ch jh k ng g m n l r w" "ea oh ae aa eh ah ao aw ai er ey ow ia ih iy oy ua uh uw a e i u y p b t d f v t dh "
"s z sh zh ch jh k ng g m n l r w"
)
_VOWELS = "eaoiuy" _VOWELS = "eaoiuy"
# these must be able to be constructed from phonemes (so for example, # these must be able to be constructed from phonemes (so for example,
# if you have v here, there must exist at least one single-character # if you have v here, there must exist at least one single-character
@ -114,8 +116,8 @@ _GRAMMAR = "v cv vc cvv vcc vcv cvcc vccv cvccv cvcvcc cvccvcv vccvccvc cvcvccvv
_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.DOTALL + re.UNICODE _RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.DOTALL + re.UNICODE
_RE_GRAMMAR = re.compile(r"vv|cc|v|c", _RE_FLAGS) _RE_GRAMMAR = re.compile(r"vv|cc|v|c", _RE_FLAGS)
_RE_WORD = re.compile(r'\w+', _RE_FLAGS) _RE_WORD = re.compile(r"\w+", _RE_FLAGS)
_RE_EXTRA_CHARS = re.compile(r'\s+(?=\W)|[,.?;](?=[,.?;]|\s+[,.?;])', _RE_FLAGS) _RE_EXTRA_CHARS = re.compile(r"\s+(?=\W)|[,.?;](?=[,.?;]|\s+[,.?;])", _RE_FLAGS)
class LanguageError(RuntimeError): class LanguageError(RuntimeError):
@ -159,13 +161,20 @@ class LanguageHandler(DefaultScript):
self.persistent = True self.persistent = True
self.db.language_storage = {} self.db.language_storage = {}
def add(self, key="default", phonemes=_PHONEMES, def add(
grammar=_GRAMMAR, word_length_variance=0, self,
noun_translate=False, key="default",
noun_prefix="", phonemes=_PHONEMES,
noun_postfix="", grammar=_GRAMMAR,
vowels=_VOWELS, manual_translations=None, word_length_variance=0,
auto_translations=None, force=False): noun_translate=False,
noun_prefix="",
noun_postfix="",
vowels=_VOWELS,
manual_translations=None,
auto_translations=None,
force=False,
):
""" """
Add a new language. Note that you generally only need to do Add a new language. Note that you generally only need to do
this once per language and that adding an existing language this once per language and that adding an existing language
@ -229,7 +238,8 @@ class LanguageHandler(DefaultScript):
if key in self.db.language_storage and not force: if key in self.db.language_storage and not force:
raise LanguageExistsError( raise LanguageExistsError(
"Language is already created. Re-adding it will re-build" "Language is already created. Re-adding it will re-build"
" its dictionary map. Use 'force=True' keyword if you are sure.") " its dictionary map. Use 'force=True' keyword if you are sure."
)
# create grammar_component->phoneme mapping # create grammar_component->phoneme mapping
# {"vv": ["ea", "oh", ...], ...} # {"vv": ["ea", "oh", ...], ...}
@ -244,25 +254,25 @@ class LanguageHandler(DefaultScript):
gramdict = defaultdict(list) gramdict = defaultdict(list)
for gram in grammar.split(): for gram in grammar.split():
if re.search("\W|(!=[cv])", gram): if re.search("\W|(!=[cv])", gram):
raise LanguageError("The grammar '%s' is invalid (only 'c' and 'v' are allowed)" % gram) raise LanguageError(
"The grammar '%s' is invalid (only 'c' and 'v' are allowed)" % gram
)
gramdict[len(gram)].append(gram) gramdict[len(gram)].append(gram)
grammar = dict(gramdict) grammar = dict(gramdict)
# create automatic translation # create automatic translation
translation = {} translation = {}
if auto_translations: if auto_translations:
if isinstance(auto_translations, str): if isinstance(auto_translations, str):
# path to a file rather than a list # path to a file rather than a list
with open(auto_translations, 'r') as f: with open(auto_translations, "r") as f:
auto_translations = f.readlines() auto_translations = f.readlines()
for word in auto_translations: for word in auto_translations:
word = word.strip() word = word.strip()
lword = len(word) lword = len(word)
new_word = "" new_word = ""
wlen = max(0, lword + sum(randint(-1, 1) for i wlen = max(0, lword + sum(randint(-1, 1) for i in range(word_length_variance)))
in range(word_length_variance)))
if wlen not in grammar: if wlen not in grammar:
# always create a translation, use random length # always create a translation, use random length
structure = choice(grammar[choice(list(grammar))]) structure = choice(grammar[choice(list(grammar))])
@ -275,16 +285,20 @@ class LanguageHandler(DefaultScript):
if manual_translations: if manual_translations:
# update with manual translations # update with manual translations
translation.update(dict((key.lower(), value.lower()) for key, value in manual_translations.items())) translation.update(
dict((key.lower(), value.lower()) for key, value in manual_translations.items())
)
# store data # store data
storage = {"translation": translation, storage = {
"grammar": grammar, "translation": translation,
"grammar2phonemes": dict(grammar2phonemes), "grammar": grammar,
"word_length_variance": word_length_variance, "grammar2phonemes": dict(grammar2phonemes),
"noun_translate": noun_translate, "word_length_variance": word_length_variance,
"noun_prefix": noun_prefix, "noun_translate": noun_translate,
"noun_postfix": noun_postfix} "noun_prefix": noun_prefix,
"noun_postfix": noun_postfix,
}
self.db.language_storage[key] = storage self.db.language_storage[key] = storage
def _translate_sub(self, match): def _translate_sub(self, match):
@ -321,14 +335,17 @@ class LanguageHandler(DefaultScript):
# make up translation on the fly. Length can # make up translation on the fly. Length can
# vary from un-translated word. # vary from un-translated word.
wlen = max(0, lword + sum(randint(-1, 1) for i wlen = max(
in range(self.language["word_length_variance"]))) 0,
lword
+ sum(randint(-1, 1) for i in range(self.language["word_length_variance"])),
)
grammar = self.language["grammar"] grammar = self.language["grammar"]
if wlen not in grammar: if wlen not in grammar:
if randint(0, 1) == 0: if randint(0, 1) == 0:
# this word has no direct translation! # this word has no direct translation!
wlen = 0 wlen = 0
new_word = '' new_word = ""
else: else:
# use random word length # use random word length
wlen = choice(list(grammar.keys())) wlen = choice(list(grammar.keys()))
@ -341,14 +358,16 @@ class LanguageHandler(DefaultScript):
try: try:
new_word += choice(grammar2phonemes[match.group()]) new_word += choice(grammar2phonemes[match.group()])
except KeyError: except KeyError:
logger.log_trace("You need to supply at least one example of each of " logger.log_trace(
"the four base phonemes (c, v, cc, vv)") "You need to supply at least one example of each of "
"the four base phonemes (c, v, cc, vv)"
)
# abort translation here # abort translation here
new_word = '' new_word = ""
break break
if word.istitle(): if word.istitle():
title_word = '' title_word = ""
if not start_sentence and not self.language.get("noun_translate", False): if not start_sentence and not self.language.get("noun_translate", False):
# don't translate what we identify as proper nouns (names) # don't translate what we identify as proper nouns (names)
title_word = word title_word = word
@ -357,9 +376,11 @@ class LanguageHandler(DefaultScript):
if title_word: if title_word:
# Regardless of if we translate or not, we will add the custom prefix/postfixes # Regardless of if we translate or not, we will add the custom prefix/postfixes
new_word = "%s%s%s" % (self.language["noun_prefix"], new_word = "%s%s%s" % (
title_word.capitalize(), self.language["noun_prefix"],
self.language["noun_postfix"]) title_word.capitalize(),
self.language["noun_postfix"],
)
if len(word) > 1 and word.isupper(): if len(word) > 1 and word.isupper():
# keep LOUD words loud also when translated # keep LOUD words loud also when translated
@ -427,6 +448,7 @@ def obfuscate_language(text, level=0.0, language="default"):
except LanguageHandler.DoesNotExist: except LanguageHandler.DoesNotExist:
if not _LANGUAGE_HANDLER: if not _LANGUAGE_HANDLER:
from evennia import create_script from evennia import create_script
_LANGUAGE_HANDLER = create_script(LanguageHandler) _LANGUAGE_HANDLER = create_script(LanguageHandler)
return _LANGUAGE_HANDLER.translate(text, level=level, language=language) return _LANGUAGE_HANDLER.translate(text, level=level, language=language)
@ -444,6 +466,7 @@ def add_language(**kwargs):
except LanguageHandler.DoesNotExist: except LanguageHandler.DoesNotExist:
if not _LANGUAGE_HANDLER: if not _LANGUAGE_HANDLER:
from evennia import create_script from evennia import create_script
_LANGUAGE_HANDLER = create_script(LanguageHandler) _LANGUAGE_HANDLER = create_script(LanguageHandler)
_LANGUAGE_HANDLER.add(**kwargs) _LANGUAGE_HANDLER.add(**kwargs)
@ -463,11 +486,12 @@ def available_languages():
except LanguageHandler.DoesNotExist: except LanguageHandler.DoesNotExist:
if not _LANGUAGE_HANDLER: if not _LANGUAGE_HANDLER:
from evennia import create_script from evennia import create_script
_LANGUAGE_HANDLER = create_script(LanguageHandler) _LANGUAGE_HANDLER = create_script(LanguageHandler)
return list(_LANGUAGE_HANDLER.attributes.get("language_storage", {})) return list(_LANGUAGE_HANDLER.attributes.get("language_storage", {}))
#------------------------------------------------------------ # ------------------------------------------------------------
# #
# Whisper obscuration # Whisper obscuration
# #
@ -479,24 +503,25 @@ def available_languages():
# give a user some idea of the sentence structure. Then the word # give a user some idea of the sentence structure. Then the word
# lengths are also obfuscated and finally the whisper # length itself. # lengths are also obfuscated and finally the whisper # length itself.
# #
#------------------------------------------------------------ # ------------------------------------------------------------
_RE_WHISPER_OBSCURE = [ _RE_WHISPER_OBSCURE = [
re.compile(r"^$", _RE_FLAGS), # This is a Test! #0 full whisper re.compile(r"^$", _RE_FLAGS), # This is a Test! #0 full whisper
re.compile(r"[ae]", _RE_FLAGS), # This -s - Test! #1 add uy re.compile(r"[ae]", _RE_FLAGS), # This -s - Test! #1 add uy
re.compile(r"[aeuy]", _RE_FLAGS), # This -s - Test! #2 add oue re.compile(r"[aeuy]", _RE_FLAGS), # This -s - Test! #2 add oue
re.compile(r"[aeiouy]", _RE_FLAGS), # Th-s -s - T-st! #3 add all consonants re.compile(r"[aeiouy]", _RE_FLAGS), # Th-s -s - T-st! #3 add all consonants
re.compile(r"[aeiouybdhjlmnpqrv]", _RE_FLAGS), # T--s -s - T-st! #4 add hard consonants re.compile(r"[aeiouybdhjlmnpqrv]", _RE_FLAGS), # T--s -s - T-st! #4 add hard consonants
re.compile(r"[a-eg-rt-z]", _RE_FLAGS), # T--s -s - T-s-! #5 add all capitals re.compile(r"[a-eg-rt-z]", _RE_FLAGS), # T--s -s - T-s-! #5 add all capitals
re.compile(r"[A-EG-RT-Za-eg-rt-z]", _RE_FLAGS), # ---s -s - --s-! #6 add f re.compile(r"[A-EG-RT-Za-eg-rt-z]", _RE_FLAGS), # ---s -s - --s-! #6 add f
re.compile(r"[A-EG-RT-Za-rt-z]", _RE_FLAGS), # ---s -s - --s-! #7 add s re.compile(r"[A-EG-RT-Za-rt-z]", _RE_FLAGS), # ---s -s - --s-! #7 add s
re.compile(r"[A-EG-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #8 add capital F re.compile(r"[A-EG-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #8 add capital F
re.compile(r"[A-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #9 add capital S re.compile(r"[A-RT-Za-z]", _RE_FLAGS), # ---- -- - ----! #9 add capital S
re.compile(r"[\w]", _RE_FLAGS), # ---- -- - ----! #10 non-alphanumerals re.compile(r"[\w]", _RE_FLAGS), # ---- -- - ----! #10 non-alphanumerals
re.compile(r"[\S]", _RE_FLAGS), # ---- -- - ---- #11 words re.compile(r"[\S]", _RE_FLAGS), # ---- -- - ---- #11 words
re.compile(r"[\w\W]", _RE_FLAGS), # -------------- #12 whisper length re.compile(r"[\w\W]", _RE_FLAGS), # -------------- #12 whisper length
re.compile(r".*", _RE_FLAGS)] # ... #13 (always same length) re.compile(r".*", _RE_FLAGS),
] # ... #13 (always same length)
def obfuscate_whisper(whisper, level=0.0): def obfuscate_whisper(whisper, level=0.0):
@ -517,4 +542,4 @@ def obfuscate_whisper(whisper, level=0.0):
if olevel == 13: if olevel == 13:
return "..." return "..."
else: else:
return _RE_WHISPER_OBSCURE[olevel].sub('-', whisper) return _RE_WHISPER_OBSCURE[olevel].sub("-", whisper)

View file

@ -103,10 +103,10 @@ from evennia import Command, CmdSet
from evennia import ansi from evennia import ansi
from evennia.utils.utils import lazy_property, make_iter, variable_from_module from evennia.utils.utils import lazy_property, make_iter, variable_from_module
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1)) _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
#------------------------------------------------------------ # ------------------------------------------------------------
# Emote parser # Emote parser
#------------------------------------------------------------ # ------------------------------------------------------------
# Settings # Settings
@ -124,11 +124,9 @@ _NUM_SEP = "-"
# Texts # Texts
_EMOTE_NOMATCH_ERROR = \ _EMOTE_NOMATCH_ERROR = """|RNo match for |r{ref}|R.|n"""
"""|RNo match for |r{ref}|R.|n"""
_EMOTE_MULTIMATCH_ERROR = \ _EMOTE_MULTIMATCH_ERROR = """|RMultiple possibilities for {ref}:
"""|RMultiple possibilities for {ref}:
|r{reflist}|n""" |r{reflist}|n"""
_RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE _RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE
@ -139,8 +137,7 @@ _RE_PREFIX = re.compile(r"^%s" % _PREFIX, re.UNICODE)
# separate multimatches from one another and word is the first word in the # separate multimatches from one another and word is the first word in the
# marker. So entering "/tall man" will return groups ("", "tall") # marker. So entering "/tall man" will return groups ("", "tall")
# and "/2-tall man" will return groups ("2", "tall"). # and "/2-tall man" will return groups ("2", "tall").
_RE_OBJ_REF_START = re.compile(r"%s(?:([0-9]+)%s)*(\w+)" % _RE_OBJ_REF_START = re.compile(r"%s(?:([0-9]+)%s)*(\w+)" % (_PREFIX, _NUM_SEP), _RE_FLAGS)
(_PREFIX, _NUM_SEP), _RE_FLAGS)
_RE_LEFT_BRACKETS = re.compile(r"\{+", _RE_FLAGS) _RE_LEFT_BRACKETS = re.compile(r"\{+", _RE_FLAGS)
_RE_RIGHT_BRACKETS = re.compile(r"\}+", _RE_FLAGS) _RE_RIGHT_BRACKETS = re.compile(r"\}+", _RE_FLAGS)
@ -233,7 +230,10 @@ def ordered_permutation_regex(sentence):
elif comb: elif comb:
break break
if comb: if comb:
solution.append(_PREFIX + r"[0-9]*%s*%s(?=\W|$)+" % (_NUM_SEP, re_escape(" ".join(comb)).rstrip("\\"))) solution.append(
_PREFIX
+ r"[0-9]*%s*%s(?=\W|$)+" % (_NUM_SEP, re_escape(" ".join(comb)).rstrip("\\"))
)
# combine into a match regex, first matching the longest down to the shortest components # combine into a match regex, first matching the longest down to the shortest components
regex = r"|".join(sorted(set(solution), key=lambda item: (-len(item), item))) regex = r"|".join(sorted(set(solution), key=lambda item: (-len(item), item)))
@ -257,9 +257,11 @@ def regex_tuple_from_key_alias(obj):
(ordered_permutation_regex, obj, key/alias) (ordered_permutation_regex, obj, key/alias)
""" """
return (re.compile(ordered_permutation_regex(" ".join([obj.key] + obj.aliases.all())), _RE_FLAGS), return (
obj, re.compile(ordered_permutation_regex(" ".join([obj.key] + obj.aliases.all())), _RE_FLAGS),
obj.key) obj,
obj.key,
)
def parse_language(speaker, emote): def parse_language(speaker, emote):
@ -357,14 +359,20 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False):
""" """
# Load all candidate regex tuples [(regex, obj, sdesc/recog),...] # Load all candidate regex tuples [(regex, obj, sdesc/recog),...]
candidate_regexes = \ candidate_regexes = (
([(_RE_SELF_REF, sender, sender.sdesc.get())] if hasattr(sender, "sdesc") else []) + \ ([(_RE_SELF_REF, sender, sender.sdesc.get())] if hasattr(sender, "sdesc") else [])
([sender.recog.get_regex_tuple(obj) for obj in candidates] if hasattr(sender, "recog") else []) + \ + (
[obj.sdesc.get_regex_tuple() [sender.recog.get_regex_tuple(obj) for obj in candidates]
for obj in candidates if hasattr(obj, "sdesc")] + \ if hasattr(sender, "recog")
[regex_tuple_from_key_alias(obj) # handle objects without sdescs else []
for obj in candidates if not (hasattr(obj, "recog") and )
hasattr(obj, "sdesc"))] + [obj.sdesc.get_regex_tuple() for obj in candidates if hasattr(obj, "sdesc")]
+ [
regex_tuple_from_key_alias(obj) # handle objects without sdescs
for obj in candidates
if not (hasattr(obj, "recog") and hasattr(obj, "sdesc"))
]
)
# filter out non-found data # filter out non-found data
candidate_regexes = [tup for tup in candidate_regexes if tup] candidate_regexes = [tup for tup in candidate_regexes if tup]
@ -434,16 +442,26 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False):
errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group())) errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group()))
elif nmatches == 1: elif nmatches == 1:
key = "#%i" % obj.id key = "#%i" % obj.id
string = string[:istart0] + "{%s}" % key + string[istart + maxscore:] string = string[:istart0] + "{%s}" % key + string[istart + maxscore :]
mapping[key] = obj mapping[key] = obj
else: else:
refname = marker_match.group() refname = marker_match.group()
reflist = ["%s%s%s (%s%s)" % (inum + 1, _NUM_SEP, reflist = [
_RE_PREFIX.sub("", refname), text, "%s%s%s (%s%s)"
" (%s)" % sender.key if sender == ob else "") % (
for inum, (ob, text) in enumerate(obj)] inum + 1,
errors.append(_EMOTE_MULTIMATCH_ERROR.format( _NUM_SEP,
ref=marker_match.group(), reflist="\n ".join(reflist))) _RE_PREFIX.sub("", refname),
text,
" (%s)" % sender.key if sender == ob else "",
)
for inum, (ob, text) in enumerate(obj)
]
errors.append(
_EMOTE_MULTIMATCH_ERROR.format(
ref=marker_match.group(), reflist="\n ".join(reflist)
)
)
if search_mode: if search_mode:
# return list of object(s) matching # return list of object(s) matching
if nmatches == 0: if nmatches == 0:
@ -495,8 +513,8 @@ def send_emote(sender, receivers, emote, anonymous_add="first"):
# no self-reference in the emote - add to the end # no self-reference in the emote - add to the end
key = "#%i" % sender.id key = "#%i" % sender.id
obj_mapping[key] = sender obj_mapping[key] = sender
if anonymous_add == 'first': if anonymous_add == "first":
possessive = "" if emote.startswith('\'') else " " possessive = "" if emote.startswith("'") else " "
emote = "%s%s%s" % ("{{%s}}" % key, possessive, emote) emote = "%s%s%s" % ("{{%s}}" % key, possessive, emote)
else: else:
emote = "%s [%s]" % (emote, "{{%s}}" % key) emote = "%s [%s]" % (emote, "{{%s}}" % key)
@ -529,11 +547,19 @@ def send_emote(sender, receivers, emote, anonymous_add="first"):
try: try:
recog_get = receiver.recog.get recog_get = receiver.recog.get
receiver_sdesc_mapping = dict((ref, process_recog(recog_get(obj), obj)) for ref, obj in obj_mapping.items()) receiver_sdesc_mapping = dict(
(ref, process_recog(recog_get(obj), obj)) for ref, obj in obj_mapping.items()
)
except AttributeError: except AttributeError:
receiver_sdesc_mapping = dict((ref, process_sdesc(obj.sdesc.get(), obj) receiver_sdesc_mapping = dict(
if hasattr(obj, "sdesc") else process_sdesc(obj.key, obj)) (
for ref, obj in obj_mapping.items()) ref,
process_sdesc(obj.sdesc.get(), obj)
if hasattr(obj, "sdesc")
else process_sdesc(obj.key, obj),
)
for ref, obj in obj_mapping.items()
)
# make sure receiver always sees their real name # make sure receiver always sees their real name
rkey = "#%i" % receiver.id rkey = "#%i" % receiver.id
if rkey in receiver_sdesc_mapping: if rkey in receiver_sdesc_mapping:
@ -542,9 +568,10 @@ def send_emote(sender, receivers, emote, anonymous_add="first"):
# do the template replacement of the sdesc/recog {#num} markers # do the template replacement of the sdesc/recog {#num} markers
receiver.msg(sendemote.format(**receiver_sdesc_mapping)) receiver.msg(sendemote.format(**receiver_sdesc_mapping))
#------------------------------------------------------------
# ------------------------------------------------------------
# Handlers for sdesc and recog # Handlers for sdesc and recog
#------------------------------------------------------------ # ------------------------------------------------------------
class SdescHandler(object): class SdescHandler(object):
@ -601,11 +628,13 @@ class SdescHandler(object):
""" """
# strip emote components from sdesc # strip emote components from sdesc
sdesc = _RE_REF.sub(r"\1", sdesc = _RE_REF.sub(
_RE_REF_LANG.sub(r"\1", r"\1",
_RE_SELF_REF.sub(r"", _RE_REF_LANG.sub(
_RE_LANGUAGE.sub(r"", r"\1",
_RE_OBJ_REF_START.sub(r"", sdesc))))) _RE_SELF_REF.sub(r"", _RE_LANGUAGE.sub(r"", _RE_OBJ_REF_START.sub(r"", sdesc))),
),
)
# make an sdesc clean of ANSI codes # make an sdesc clean of ANSI codes
cleaned_sdesc = ansi.strip_ansi(sdesc) cleaned_sdesc = ansi.strip_ansi(sdesc)
@ -614,7 +643,10 @@ class SdescHandler(object):
raise SdescError("Short desc cannot be empty.") raise SdescError("Short desc cannot be empty.")
if len(cleaned_sdesc) > max_length: if len(cleaned_sdesc) > max_length:
raise SdescError("Short desc can max be %i chars long (was %i chars)." % (max_length, len(cleaned_sdesc))) raise SdescError(
"Short desc can max be %i chars long (was %i chars)."
% (max_length, len(cleaned_sdesc))
)
# store to attributes # store to attributes
sdesc_regex = ordered_permutation_regex(cleaned_sdesc) sdesc_regex = ordered_permutation_regex(cleaned_sdesc)
@ -681,10 +713,10 @@ class RecogHandler(object):
self.ref2recog = self.obj.attributes.get("_recog_ref2recog", default={}) self.ref2recog = self.obj.attributes.get("_recog_ref2recog", default={})
obj2regex = self.obj.attributes.get("_recog_obj2regex", default={}) obj2regex = self.obj.attributes.get("_recog_obj2regex", default={})
obj2recog = self.obj.attributes.get("_recog_obj2recog", default={}) obj2recog = self.obj.attributes.get("_recog_obj2recog", default={})
self.obj2regex = dict((obj, re.compile(regex, _RE_FLAGS)) self.obj2regex = dict(
for obj, regex in obj2regex.items() if obj) (obj, re.compile(regex, _RE_FLAGS)) for obj, regex in obj2regex.items() if obj
self.obj2recog = dict((obj, recog) )
for obj, recog in obj2recog.items() if obj) self.obj2recog = dict((obj, recog) for obj, recog in obj2recog.items() if obj)
def add(self, obj, recog, max_length=60): def add(self, obj, recog, max_length=60):
""" """
@ -711,10 +743,12 @@ class RecogHandler(object):
# strip emote components from recog # strip emote components from recog
recog = _RE_REF.sub( recog = _RE_REF.sub(
r"\1", _RE_REF_LANG.sub( r"\1",
r"\1", _RE_SELF_REF.sub( _RE_REF_LANG.sub(
r"", _RE_LANGUAGE.sub( r"\1",
r"", _RE_OBJ_REF_START.sub(r"", recog))))) _RE_SELF_REF.sub(r"", _RE_LANGUAGE.sub(r"", _RE_OBJ_REF_START.sub(r"", recog))),
),
)
# make an recog clean of ANSI codes # make an recog clean of ANSI codes
cleaned_recog = ansi.strip_ansi(recog) cleaned_recog = ansi.strip_ansi(recog)
@ -723,7 +757,10 @@ class RecogHandler(object):
raise SdescError("Recog string cannot be empty.") raise SdescError("Recog string cannot be empty.")
if len(cleaned_recog) > max_length: if len(cleaned_recog) > max_length:
raise RecogError("Recog string cannot be longer than %i chars (was %i chars)" % (max_length, len(cleaned_recog))) raise RecogError(
"Recog string cannot be longer than %i chars (was %i chars)"
% (max_length, len(cleaned_recog))
)
# mapping #dbref:obj # mapping #dbref:obj
key = "#%i" % obj.id key = "#%i" % obj.id
@ -756,8 +793,7 @@ class RecogHandler(object):
# check an eventual recog_masked lock on the object # check an eventual recog_masked lock on the object
# to avoid revealing masked characters. If lock # to avoid revealing masked characters. If lock
# does not exist, pass automatically. # does not exist, pass automatically.
return self.obj2recog.get(obj, obj.sdesc.get() return self.obj2recog.get(obj, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key)
if hasattr(obj, "sdesc") else obj.key)
else: else:
# recog_mask log not passed, disable recog # recog_mask log not passed, disable recog
return obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key return obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key
@ -784,9 +820,10 @@ class RecogHandler(object):
return self.obj2regex[obj], obj, self.obj2regex[obj] return self.obj2regex[obj], obj, self.obj2regex[obj]
return None return None
#------------------------------------------------------------
# ------------------------------------------------------------
# RP Commands # RP Commands
#------------------------------------------------------------ # ------------------------------------------------------------
class RPCommand(Command): class RPCommand(Command):
@ -818,6 +855,7 @@ class CmdEmote(RPCommand): # replaces the main emote
a different language. a different language.
""" """
key = "emote" key = "emote"
aliases = [":"] aliases = [":"]
locks = "cmd:all()" locks = "cmd:all()"
@ -832,7 +870,7 @@ class CmdEmote(RPCommand): # replaces the main emote
targets = self.caller.location.contents targets = self.caller.location.contents
if not emote.endswith((".", "?", "!")): # If emote is not punctuated, if not emote.endswith((".", "?", "!")): # If emote is not punctuated,
emote = "%s." % emote # add a full-stop for good measure. emote = "%s." % emote # add a full-stop for good measure.
send_emote(self.caller, targets, emote, anonymous_add='first') send_emote(self.caller, targets, emote, anonymous_add="first")
class CmdSay(RPCommand): # replaces standard say class CmdSay(RPCommand): # replaces standard say
@ -861,7 +899,7 @@ class CmdSay(RPCommand): # replaces standard say
# calling the speech hook on the location # calling the speech hook on the location
speech = caller.location.at_before_say(self.args) speech = caller.location.at_before_say(self.args)
# preparing the speech with sdesc/speech parsing. # preparing the speech with sdesc/speech parsing.
speech = "/me says, \"{speech}\"".format(speech=speech) speech = '/me says, "{speech}"'.format(speech=speech)
targets = self.caller.location.contents targets = self.caller.location.contents
send_emote(self.caller, targets, speech, anonymous_add=None) send_emote(self.caller, targets, speech, anonymous_add=None)
@ -876,6 +914,7 @@ class CmdSdesc(RPCommand): # set/look at own sdesc
Assigns a short description to yourself. Assigns a short description to yourself.
""" """
key = "sdesc" key = "sdesc"
locks = "cmd:all()" locks = "cmd:all()"
@ -921,6 +960,7 @@ class CmdPose(RPCommand): # set current pose and default pose
sdesc in the emote, regardless of who is seeing it. sdesc in the emote, regardless of who is seeing it.
""" """
key = "pose" key = "pose"
def parse(self): def parse(self):
@ -981,8 +1021,10 @@ class CmdPose(RPCommand): # set current pose and default pose
else: else:
# set the pose. We do one-time ref->sdesc mapping here. # set the pose. We do one-time ref->sdesc mapping here.
parsed, mapping = parse_sdescs_and_recogs(caller, caller.location.contents, pose) parsed, mapping = parse_sdescs_and_recogs(caller, caller.location.contents, pose)
mapping = dict((ref, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key) mapping = dict(
for ref, obj in mapping.items()) (ref, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key)
for ref, obj in mapping.items()
)
pose = parsed.format(**mapping) pose = parsed.format(**mapping)
if len(target_name) + len(pose) > 60: if len(target_name) + len(pose) > 60:
@ -1010,6 +1052,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
forget said alias. forget said alias.
""" """
key = "recog" key = "recog"
aliases = ["recognize", "forget"] aliases = ["recognize", "forget"]
@ -1040,10 +1083,17 @@ class CmdRecog(RPCommand): # assign personal alias to object in room
if nmatches == 0: if nmatches == 0:
caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc)) caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc))
elif nmatches > 1: elif nmatches > 1:
reflist = ["%s%s%s (%s%s)" % (inum + 1, _NUM_SEP, reflist = [
_RE_PREFIX.sub("", sdesc), caller.recog.get(obj), "%s%s%s (%s%s)"
" (%s)" % caller.key if caller == obj else "") % (
for inum, obj in enumerate(matches)] inum + 1,
_NUM_SEP,
_RE_PREFIX.sub("", sdesc),
caller.recog.get(obj),
" (%s)" % caller.key if caller == obj else "",
)
for inum, obj in enumerate(matches)
]
caller.msg(_EMOTE_MULTIMATCH_ERROR.format(ref=sdesc, reflist="\n ".join(reflist))) caller.msg(_EMOTE_MULTIMATCH_ERROR.format(ref=sdesc, reflist="\n ".join(reflist)))
else: else:
obj = matches[0] obj = matches[0]
@ -1078,6 +1128,7 @@ class CmdMask(RPCommand):
people's recognitions of you will be disabled. people's recognitions of you will be disabled.
""" """
key = "mask" key = "mask"
aliases = ["unmask"] aliases = ["unmask"]
@ -1126,9 +1177,10 @@ class RPSystemCmdSet(CmdSet):
self.add(CmdMask()) self.add(CmdMask())
#------------------------------------------------------------ # ------------------------------------------------------------
# RP typeclasses # RP typeclasses
#------------------------------------------------------------ # ------------------------------------------------------------
class ContribRPObject(DefaultObject): class ContribRPObject(DefaultObject):
""" """
@ -1146,18 +1198,21 @@ class ContribRPObject(DefaultObject):
self.db.pose = "" self.db.pose = ""
self.db.pose_default = "is here." self.db.pose_default = "is here."
def search(self, searchdata, def search(
global_search=False, self,
use_nicks=True, searchdata,
typeclass=None, global_search=False,
location=None, use_nicks=True,
attribute_name=None, typeclass=None,
quiet=False, location=None,
exact=False, attribute_name=None,
candidates=None, quiet=False,
nofound_string=None, exact=False,
multimatch_string=None, candidates=None,
use_dbref=None): nofound_string=None,
multimatch_string=None,
use_dbref=None,
):
""" """
Returns an Object matching a search string/condition, taking Returns an Object matching a search string/condition, taking
sdescs into account. sdescs into account.
@ -1228,17 +1283,23 @@ class ContribRPObject(DefaultObject):
if is_string: if is_string:
# searchdata is a string; wrap some common self-references # searchdata is a string; wrap some common self-references
if searchdata.lower() in ("here", ): if searchdata.lower() in ("here",):
return [self.location] if quiet else self.location return [self.location] if quiet else self.location
if searchdata.lower() in ("me", "self",): if searchdata.lower() in ("me", "self"):
return [self] if quiet else self return [self] if quiet else self
if use_nicks: if use_nicks:
# do nick-replacement on search # do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "account"), include_account=True) searchdata = self.nicks.nickreplace(
searchdata, categories=("object", "account"), include_account=True
)
if(global_search or (is_string and searchdata.startswith("#") and if global_search or (
len(searchdata) > 1 and searchdata[1:].isdigit())): is_string
and searchdata.startswith("#")
and len(searchdata) > 1
and searchdata[1:].isdigit()
):
# only allow exact matching if searching the entire database # only allow exact matching if searching the entire database
# or unique #dbrefs # or unique #dbrefs
exact = True exact = True
@ -1268,17 +1329,19 @@ class ContribRPObject(DefaultObject):
def search_obj(string): def search_obj(string):
"helper wrapper for searching" "helper wrapper for searching"
return ObjectDB.objects.object_search(string, return ObjectDB.objects.object_search(
attribute_name=attribute_name, string,
typeclass=typeclass, attribute_name=attribute_name,
candidates=candidates, typeclass=typeclass,
exact=exact, candidates=candidates,
use_dbref=use_dbref) exact=exact,
use_dbref=use_dbref,
)
if candidates: if candidates:
candidates = parse_sdescs_and_recogs(self, candidates, candidates = parse_sdescs_and_recogs(
_PREFIX + searchdata, self, candidates, _PREFIX + searchdata, search_mode=True
search_mode=True) )
results = [] results = []
for candidate in candidates: for candidate in candidates:
# we search by candidate keys here; this allows full error # we search by candidate keys here; this allows full error
@ -1286,8 +1349,7 @@ class ContribRPObject(DefaultObject):
# in eventual error reporting later (not their keys). Doing # in eventual error reporting later (not their keys). Doing
# it like this e.g. allows for use of the typeclass kwarg # it like this e.g. allows for use of the typeclass kwarg
# limiter. # limiter.
results.extend([obj for obj in search_obj(candidate.key) results.extend([obj for obj in search_obj(candidate.key) if obj not in results])
if obj not in results])
if not results and is_builder: if not results and is_builder:
# builders get a chance to search only by key+alias # builders get a chance to search only by key+alias
@ -1300,9 +1362,13 @@ class ContribRPObject(DefaultObject):
if quiet: if quiet:
return results return results
return _AT_SEARCH_RESULT(results, self, query=searchdata, return _AT_SEARCH_RESULT(
nofound_string=nofound_string, results,
multimatch_string=multimatch_string) self,
query=searchdata,
nofound_string=nofound_string,
multimatch_string=multimatch_string,
)
def get_display_name(self, looker, **kwargs): def get_display_name(self, looker, **kwargs):
""" """
@ -1325,7 +1391,7 @@ class ContribRPObject(DefaultObject):
The RPObject version doesn't add color to its display. The RPObject version doesn't add color to its display.
""" """
idstr = "(#%s)" % self.id if self.access(looker, access_type='control') else "" idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else ""
if looker == self: if looker == self:
sdesc = self.key sdesc = self.key
else: else:
@ -1348,8 +1414,7 @@ class ContribRPObject(DefaultObject):
if not looker: if not looker:
return "" return ""
# get and identify all objects # get and identify all objects
visible = (con for con in self.contents if con != looker and visible = (con for con in self.contents if con != looker and con.access(looker, "view"))
con.access(looker, "view"))
exits, users, things = [], [], [] exits, users, things = [], [], []
for con in visible: for con in visible:
key = con.get_display_name(looker, pose=True) key = con.get_display_name(looker, pose=True)
@ -1375,6 +1440,7 @@ class ContribRPRoom(ContribRPObject):
""" """
Dummy inheritance for rooms. Dummy inheritance for rooms.
""" """
pass pass
@ -1382,6 +1448,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
""" """
This is a character class that has poses, sdesc and recog. This is a character class that has poses, sdesc and recog.
""" """
# Handlers # Handlers
@lazy_property @lazy_property
def sdesc(self): def sdesc(self):
@ -1413,7 +1480,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
characters stand out from other objects. characters stand out from other objects.
""" """
idstr = "(#%s)" % self.id if self.access(looker, access_type='control') else "" idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else ""
if looker == self: if looker == self:
sdesc = self.key sdesc = self.key
else: else:
@ -1499,4 +1566,3 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
""" """
return "%s|w%s|n" % ("|W(%s)" % language if language else "", text) return "%s|w%s|n" % ("|W(%s)" % language if language else "", text)

View file

@ -31,7 +31,7 @@ def to_file(data):
""" """
# Bucket logs by day and remove objects before serialization # Bucket logs by day and remove objects before serialization
bucket = data.pop('objects')['time'].strftime('%Y-%m-%d') bucket = data.pop("objects")["time"].strftime("%Y-%m-%d")
# Write it # Write it
log_file(json.dumps(data), filename="audit_%s.log" % bucket) log_file(json.dumps(data), filename="audit_%s.log" % bucket)
@ -54,7 +54,7 @@ def to_syslog(data):
""" """
# Remove objects before serialization # Remove objects before serialization
data.pop('objects') data.pop("objects")
# Write it out # Write it out
syslog.syslog(json.dumps(data)) syslog.syslog(json.dumps(data))

View file

@ -15,31 +15,36 @@ from evennia.utils import utils, logger, mod_import, get_evennia_version
from evennia.server.serversession import ServerSession from evennia.server.serversession import ServerSession
# Attributes governing auditing of commands and where to send log objects # Attributes governing auditing of commands and where to send log objects
AUDIT_CALLBACK = getattr(ev_settings, 'AUDIT_CALLBACK', AUDIT_CALLBACK = getattr(
'evennia.contrib.security.auditing.outputs.to_file') ev_settings, "AUDIT_CALLBACK", "evennia.contrib.security.auditing.outputs.to_file"
AUDIT_IN = getattr(ev_settings, 'AUDIT_IN', False) )
AUDIT_OUT = getattr(ev_settings, 'AUDIT_OUT', False) AUDIT_IN = getattr(ev_settings, "AUDIT_IN", False)
AUDIT_ALLOW_SPARSE = getattr(ev_settings, 'AUDIT_ALLOW_SPARSE', False) AUDIT_OUT = getattr(ev_settings, "AUDIT_OUT", False)
AUDIT_ALLOW_SPARSE = getattr(ev_settings, "AUDIT_ALLOW_SPARSE", False)
AUDIT_MASKS = [ AUDIT_MASKS = [
{'connect': r"^[@\s]*[connect]{5,8}\s+(\".+?\"|[^\s]+)\s+(?P<secret>.+)"}, {"connect": r"^[@\s]*[connect]{5,8}\s+(\".+?\"|[^\s]+)\s+(?P<secret>.+)"},
{'connect': r"^[@\s]*[connect]{5,8}\s+(?P<secret>[\w]+)"}, {"connect": r"^[@\s]*[connect]{5,8}\s+(?P<secret>[\w]+)"},
{'create': r"^[^@]?[create]{5,6}\s+(\w+|\".+?\")\s+(?P<secret>[\w]+)"}, {"create": r"^[^@]?[create]{5,6}\s+(\w+|\".+?\")\s+(?P<secret>[\w]+)"},
{'create': r"^[^@]?[create]{5,6}\s+(?P<secret>[\w]+)"}, {"create": r"^[^@]?[create]{5,6}\s+(?P<secret>[\w]+)"},
{'userpassword': r"^[@\s]*[userpassword]{11,14}\s+(\w+|\".+?\")\s+=*\s*(?P<secret>[\w]+)"}, {"userpassword": r"^[@\s]*[userpassword]{11,14}\s+(\w+|\".+?\")\s+=*\s*(?P<secret>[\w]+)"},
{'userpassword': r"^.*new password set to '(?P<secret>[^']+)'\."}, {"userpassword": r"^.*new password set to '(?P<secret>[^']+)'\."},
{'userpassword': r"^.* has changed your password to '(?P<secret>[^']+)'\."}, {"userpassword": r"^.* has changed your password to '(?P<secret>[^']+)'\."},
{'password': r"^[@\s]*[password]{6,9}\s+(?P<secret>.*)"}, {"password": r"^[@\s]*[password]{6,9}\s+(?P<secret>.*)"},
] + getattr(ev_settings, 'AUDIT_MASKS', []) ] + getattr(ev_settings, "AUDIT_MASKS", [])
if AUDIT_CALLBACK: if AUDIT_CALLBACK:
try: try:
AUDIT_CALLBACK = getattr( AUDIT_CALLBACK = getattr(
mod_import('.'.join(AUDIT_CALLBACK.split('.')[:-1])), AUDIT_CALLBACK.split('.')[-1]) mod_import(".".join(AUDIT_CALLBACK.split(".")[:-1])), AUDIT_CALLBACK.split(".")[-1]
)
logger.log_sec("Auditing module online.") logger.log_sec("Auditing module online.")
logger.log_sec("Audit record User input: {}, output: {}.\n" logger.log_sec(
"Audit sparse recording: {}, Log callback: {}".format( "Audit record User input: {}, output: {}.\n"
AUDIT_IN, AUDIT_OUT, AUDIT_ALLOW_SPARSE, AUDIT_CALLBACK)) "Audit sparse recording: {}, Log callback: {}".format(
AUDIT_IN, AUDIT_OUT, AUDIT_ALLOW_SPARSE, AUDIT_CALLBACK
)
)
except Exception as e: except Exception as e:
logger.log_err("Failed to activate Auditing module. %s" % e) logger.log_err("Failed to activate Auditing module. %s" % e)
@ -59,6 +64,7 @@ class AuditedServerSession(ServerSession):
See README.md for installation/configuration instructions. See README.md for installation/configuration instructions.
""" """
def audit(self, **kwargs): def audit(self, **kwargs):
""" """
Extracts messages and system data from a Session object upon message Extracts messages and system data from a Session object upon message
@ -79,7 +85,7 @@ class AuditedServerSession(ServerSession):
time_str = str(time_obj) time_str = str(time_obj)
session = self session = self
src = kwargs.pop('src', '?') src = kwargs.pop("src", "?")
bytecount = 0 bytecount = 0
# Do not log empty lines # Do not log empty lines
@ -91,22 +97,22 @@ class AuditedServerSession(ServerSession):
# Capture Account name and dbref together # Capture Account name and dbref together
account = session.get_account() account = session.get_account()
account_token = '' account_token = ""
if account: if account:
account_token = '%s%s' % (account.key, account.dbref) account_token = "%s%s" % (account.key, account.dbref)
# Capture Character name and dbref together # Capture Character name and dbref together
char = session.get_puppet() char = session.get_puppet()
char_token = '' char_token = ""
if char: if char:
char_token = '%s%s' % (char.key, char.dbref) char_token = "%s%s" % (char.key, char.dbref)
# Capture Room name and dbref together # Capture Room name and dbref together
room = None room = None
room_token = '' room_token = ""
if char: if char:
room = char.location room = char.location
room_token = '%s%s' % (room.key, room.dbref) room_token = "%s%s" % (room.key, room.dbref)
# Try to compile an input/output string # Try to compile an input/output string
def drill(obj, bucket): def drill(obj, bucket):
@ -119,44 +125,44 @@ class AuditedServerSession(ServerSession):
bucket.append(obj) bucket.append(obj)
return bucket return bucket
text = kwargs.pop('text', '') text = kwargs.pop("text", "")
if utils.is_iter(text): if utils.is_iter(text):
text = '|'.join(drill(text, [])) text = "|".join(drill(text, []))
# Mask any PII in message, where possible # Mask any PII in message, where possible
bytecount = len(text.encode('utf-8')) bytecount = len(text.encode("utf-8"))
text = self.mask(text) text = self.mask(text)
# Compile the IP, Account, Character, Room, and the message. # Compile the IP, Account, Character, Room, and the message.
log = { log = {
'time': time_str, "time": time_str,
'hostname': socket.getfqdn(), "hostname": socket.getfqdn(),
'application': '%s' % ev_settings.SERVERNAME, "application": "%s" % ev_settings.SERVERNAME,
'version': get_evennia_version(), "version": get_evennia_version(),
'pid': os.getpid(), "pid": os.getpid(),
'direction': 'SND' if src == 'server' else 'RCV', "direction": "SND" if src == "server" else "RCV",
'protocol': self.protocol_key, "protocol": self.protocol_key,
'ip': client_ip, "ip": client_ip,
'session': 'session#%s' % self.sessid, "session": "session#%s" % self.sessid,
'account': account_token, "account": account_token,
'character': char_token, "character": char_token,
'room': room_token, "room": room_token,
'text': text.strip(), "text": text.strip(),
'bytes': bytecount, "bytes": bytecount,
'data': kwargs, "data": kwargs,
'objects': { "objects": {
'time': time_obj, "time": time_obj,
'session': self, "session": self,
'account': account, "account": account,
'character': char, "character": char,
'room': room, "room": room,
} },
} }
# Remove any keys with blank values # Remove any keys with blank values
if AUDIT_ALLOW_SPARSE is False: if AUDIT_ALLOW_SPARSE is False:
log['data'] = {k: v for k, v in log['data'].items() if v} log["data"] = {k: v for k, v in log["data"].items() if v}
log['objects'] = {k: v for k, v in log['objects'].items() if v} log["objects"] = {k: v for k, v in log["objects"].items() if v}
log = {k: v for k, v in log.items() if v} log = {k: v for k, v in log.items() if v}
return log return log
@ -178,7 +184,7 @@ class AuditedServerSession(ServerSession):
is_embedded = False is_embedded = False
match = re.match(".*Command.*'(.+)'.*is not available.*", msg, flags=re.IGNORECASE) match = re.match(".*Command.*'(.+)'.*is not available.*", msg, flags=re.IGNORECASE)
if match: if match:
msg = match.group(1).replace('\\', '') msg = match.group(1).replace("\\", "")
submsg = msg submsg = msg
is_embedded = True is_embedded = True
@ -192,11 +198,13 @@ class AuditedServerSession(ServerSession):
continue continue
if match: if match:
term = match.group('secret') term = match.group("secret")
masked = re.sub(term, '*' * len(term.zfill(8)), msg) masked = re.sub(term, "*" * len(term.zfill(8)), msg)
if is_embedded: if is_embedded:
msg = re.sub(submsg, '%s <Masked: %s>' % (masked, command), _msg, flags=re.IGNORECASE) msg = re.sub(
submsg, "%s <Masked: %s>" % (masked, command), _msg, flags=re.IGNORECASE
)
else: else:
msg = masked msg = masked
@ -214,7 +222,7 @@ class AuditedServerSession(ServerSession):
""" """
if AUDIT_CALLBACK and AUDIT_OUT: if AUDIT_CALLBACK and AUDIT_OUT:
try: try:
log = self.audit(src='server', **kwargs) log = self.audit(src="server", **kwargs)
if log: if log:
AUDIT_CALLBACK(log) AUDIT_CALLBACK(log)
except Exception as e: except Exception as e:
@ -232,7 +240,7 @@ class AuditedServerSession(ServerSession):
""" """
if AUDIT_CALLBACK and AUDIT_IN: if AUDIT_CALLBACK and AUDIT_IN:
try: try:
log = self.audit(src='client', **kwargs) log = self.audit(src="client", **kwargs)
if log: if log:
AUDIT_CALLBACK(log) AUDIT_CALLBACK(log)
except Exception as e: except Exception as e:

Some files were not shown because too many files have changed in this diff Show more