mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Merge branch 'develop' into contrib/evadventure
This commit is contained in:
commit
f05add7c5b
13 changed files with 381 additions and 357 deletions
|
|
@ -194,6 +194,15 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10
|
|||
startup modes. Used for more generic overriding (volund)
|
||||
- New `search` lock type used to completely hide an object from being found by
|
||||
the `DefaultObject.search` (`caller.search`) method. (CloudKeeper)
|
||||
- Change setting `MULTISESSION_MODE` to now only control sessions, not how many
|
||||
characters can be puppeted simultaneously. New settings now control that.
|
||||
- Add new setting `AUTO_CREATE_CHARACTER_WITH_ACCOUNT`, a boolean deciding if
|
||||
the new account should also get a matching character (legacy MUD style).
|
||||
- Add new setting `AUTO_PUPPET_ON_LOGIN`, boolean deciding if one should
|
||||
automatically puppet the last/available character on connection (legacy MUD style)
|
||||
- Add new setting `MAX_NR_SIMULTANEUS_PUPPETS` - how many puppets the account
|
||||
can run at the same time. Used to limit multi-playing.
|
||||
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
|
||||
|
||||
## Evennia 0.9.5
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ _SESSIONS = None
|
|||
|
||||
_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1))
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
_AUTO_CREATE_CHARACTER_WITH_ACCOUNT = settings.AUTO_CREATE_CHARACTER_WITH_ACCOUNT
|
||||
_AUTO_PUPPET_ON_LOGIN = settings.AUTO_PUPPET_ON_LOGIN
|
||||
_MAX_NR_SIMULTANEOUS_PUPPETS = settings.MAX_NR_SIMULTANEOUS_PUPPETS
|
||||
_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
|
||||
_CMDSET_ACCOUNT = settings.CMDSET_ACCOUNT
|
||||
_MUDINFO_CHANNEL = None
|
||||
|
|
@ -338,7 +341,6 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
self.msg(_("|c{key}|R is already puppeted by another Account.").format(key=obj.key))
|
||||
return
|
||||
|
||||
# do the puppeting
|
||||
if session.puppet:
|
||||
# cleanly unpuppet eventual previous object puppeted by this session
|
||||
self.unpuppet_object(session)
|
||||
|
|
@ -346,6 +348,21 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
# was left with a lingering account/session reference from an unclean
|
||||
# server kill or similar
|
||||
|
||||
# check so we are not puppeting too much already
|
||||
if _MAX_NR_SIMULTANEOUS_PUPPETS is not None:
|
||||
already_puppeted = self.get_all_puppets()
|
||||
if (
|
||||
not self.is_superuser
|
||||
and not self.check_permstring("Developer")
|
||||
and obj not in already_puppeted
|
||||
and len(self.get_all_puppets()) >= _MAX_NR_SIMULTANEOUS_PUPPETS
|
||||
):
|
||||
self.msg(
|
||||
_(f"You cannot control any more puppets (max {_MAX_NR_SIMULTANEOUS_PUPPETS})")
|
||||
)
|
||||
return
|
||||
|
||||
# do the puppeting
|
||||
obj.at_pre_puppet(self, session=session)
|
||||
|
||||
# do the connection
|
||||
|
|
@ -452,7 +469,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
"""
|
||||
|
||||
ip = kwargs.get("ip", "").strip()
|
||||
ip = kwargs.get("ip", "")
|
||||
if isinstance(ip, (tuple, list)):
|
||||
ip = ip[0]
|
||||
ip = ip.strip()
|
||||
username = kwargs.get("username", "").lower().strip()
|
||||
|
||||
# Check IP and/or name bans
|
||||
|
|
@ -772,6 +792,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
typeclass = kwargs.get("typeclass", cls)
|
||||
|
||||
ip = kwargs.get("ip", "")
|
||||
if isinstance(ip, (tuple, list)):
|
||||
ip = ip[0]
|
||||
|
||||
if ip and CREATION_THROTTLE.check(ip):
|
||||
errors.append(
|
||||
_("You are creating too many accounts. Please log into an existing account.")
|
||||
|
|
@ -843,7 +866,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
errors.append(string)
|
||||
logger.log_err(string)
|
||||
|
||||
if account and settings.MULTISESSION_MODE < 2:
|
||||
if account and _AUTO_CREATE_CHARACTER_WITH_ACCOUNT:
|
||||
# Auto-create a character to go with this account
|
||||
|
||||
character, errs = account.create_character(
|
||||
|
|
@ -1237,7 +1260,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
"""
|
||||
# set an (empty) attribute holding the characters this account has
|
||||
lockstring = "attrread:perm(Admins);attredit:perm(Admins);" "attrcreate:perm(Admins);"
|
||||
lockstring = "attrread:perm(Admins);attredit:perm(Admins);attrcreate:perm(Admins);"
|
||||
self.attributes.add("_playable_characters", [], lockstring=lockstring)
|
||||
self.attributes.add("_saved_protocol_flags", {}, lockstring=lockstring)
|
||||
|
||||
|
|
@ -1434,7 +1457,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
Notes:
|
||||
This is called *before* an eventual Character's
|
||||
`at_post_login` hook. By default it is used to set up
|
||||
auto-puppeting based on `MULTISESSION_MODE`.
|
||||
auto-puppeting based on `MULTISESSION_MODE`
|
||||
|
||||
"""
|
||||
# if we have saved protocol flags on ourselves, load them here.
|
||||
|
|
@ -1447,23 +1470,15 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
session.msg(logged_in={})
|
||||
|
||||
self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key))
|
||||
if _MULTISESSION_MODE == 0:
|
||||
# in this mode we should have only one character available. We
|
||||
# try to auto-connect to our last connected object, if any
|
||||
if _AUTO_PUPPET_ON_LOGIN:
|
||||
# in this mode we try to auto-connect to our last connected object, if any
|
||||
try:
|
||||
self.puppet_object(session, self.db._last_puppet)
|
||||
except RuntimeError:
|
||||
self.msg(_("The Character does not exist."))
|
||||
return
|
||||
elif _MULTISESSION_MODE == 1:
|
||||
# in this mode all sessions connect to the same puppet.
|
||||
try:
|
||||
self.puppet_object(session, self.db._last_puppet)
|
||||
except RuntimeError:
|
||||
self.msg(_("The Character does not exist."))
|
||||
return
|
||||
elif _MULTISESSION_MODE in (2, 3):
|
||||
# In this mode we by default end up at a character selection
|
||||
else:
|
||||
# In this mode we don't auto-connect but by default end up at a character selection
|
||||
# screen. We execute look on the account.
|
||||
# we make sure to clean up the _playable_characters list in case
|
||||
# any was deleted in the interim.
|
||||
|
|
@ -1583,6 +1598,24 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
"""
|
||||
pass
|
||||
|
||||
ooc_appearance_template = """
|
||||
--------------------------------------------------------------------
|
||||
{header}
|
||||
|
||||
{sessions}
|
||||
|
||||
|whelp|n - more commands
|
||||
|wpublic <text>|n - talk on public channel
|
||||
|wcharcreate <name> [=description]|n - create new character
|
||||
|wchardelete <name>|n - delete a character
|
||||
|wic <name>|n - enter the game as character (|wooc|n to get back here)
|
||||
|wic|n - enter the game as latest character controlled.
|
||||
|
||||
{characters}
|
||||
{footer}
|
||||
--------------------------------------------------------------------
|
||||
""".strip()
|
||||
|
||||
def at_look(self, target=None, session=None, **kwargs):
|
||||
"""
|
||||
Called when this object executes a look. It allows to customize
|
||||
|
|
@ -1590,7 +1623,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
Args:
|
||||
target (Object or list, optional): An object or a list
|
||||
objects to inspect.
|
||||
objects to inspect. This is normally a list of characters.
|
||||
session (Session, optional): The session doing this look.
|
||||
**kwargs (dict): Arbitrary, optional arguments for users
|
||||
overriding the call (unused by default).
|
||||
|
|
@ -1607,94 +1640,75 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
return target.return_appearance(self)
|
||||
else:
|
||||
return f"{target} has no in-game appearance."
|
||||
else:
|
||||
# list of targets - make list to disconnect from db
|
||||
characters = list(tar for tar in target if tar) if target else []
|
||||
sessions = self.sessions.all()
|
||||
if not sessions:
|
||||
# no sessions, nothing to report
|
||||
return ""
|
||||
is_su = self.is_superuser
|
||||
|
||||
# text shown when looking in the ooc area
|
||||
result = [f"Account |g{self.key}|n (you are Out-of-Character)"]
|
||||
# multiple targets - this is a list of characters
|
||||
characters = list(tar for tar in target if tar) if target else []
|
||||
ncars = len(characters)
|
||||
sessions = self.sessions.all()
|
||||
nsess = len(sessions)
|
||||
|
||||
nsess = len(sessions)
|
||||
result.append(
|
||||
nsess == 1
|
||||
and "\n\n|wConnected session:|n"
|
||||
or f"\n\n|wConnected sessions ({nsess}):|n"
|
||||
if not nsess:
|
||||
# no sessions, nothing to report
|
||||
return ""
|
||||
|
||||
# header text
|
||||
txt_header = f"Account |g{self.name}|n (you are Out-of-Character)"
|
||||
|
||||
# sessions
|
||||
sess_strings = []
|
||||
for isess, sess in enumerate(sessions):
|
||||
ip_addr = sess.address[0] if isinstance(sess.address, tuple) else sess.address
|
||||
addr = f"{sess.protocol_key} ({ip_addr})"
|
||||
sess_str = (
|
||||
f"|w* {isess + 1}|n"
|
||||
if session and session.sessid == sess.sessid
|
||||
else f" {isess + 1}"
|
||||
)
|
||||
for isess, sess in enumerate(sessions):
|
||||
csessid = sess.sessid
|
||||
addr = "%s (%s)" % (
|
||||
sess.protocol_key,
|
||||
isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address),
|
||||
)
|
||||
result.append(
|
||||
"\n %s %s"
|
||||
% (
|
||||
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 |wpublic <Text>|n - talk on public channel")
|
||||
|
||||
charmax = _MAX_NR_CHARACTERS
|
||||
sess_strings.append(f"{sess_str} {addr}")
|
||||
|
||||
if is_su or len(characters) < charmax:
|
||||
if not characters:
|
||||
result.append(
|
||||
"\n\n You don't have any characters yet. See |whelp charcreate|n for "
|
||||
"creating one."
|
||||
)
|
||||
txt_sessions = "|wConnected session(s):|n\n" + "\n".join(sess_strings)
|
||||
|
||||
if not characters:
|
||||
txt_characters = "You don't have a character yet. Use |wcharcreate|n."
|
||||
else:
|
||||
max_chars = (
|
||||
"unlimited"
|
||||
if self.is_superuser or _MAX_NR_CHARACTERS is None
|
||||
else _MAX_NR_CHARACTERS
|
||||
)
|
||||
|
||||
char_strings = []
|
||||
for char in characters:
|
||||
csessions = char.sessions.all()
|
||||
if csessions:
|
||||
for sess in csessions:
|
||||
# character is already puppeted
|
||||
sid = sess in sessions and sessions.index(sess) + 1
|
||||
if sess and sid:
|
||||
char_strings.append(
|
||||
f" - |G{char.name}|n [{', '.join(char.permissions.all())}] "
|
||||
f"(played by you in session {sid})"
|
||||
)
|
||||
else:
|
||||
char_strings.append(
|
||||
f" - |R{char.name}|n [{', '.join(char.permissions.all())}] "
|
||||
"(played by someone else)"
|
||||
)
|
||||
else:
|
||||
result.append("\n |wcharcreate <name> [=description]|n - create new character")
|
||||
result.append(
|
||||
"\n |wchardelete <name>|n - delete a character (cannot be undone!)"
|
||||
)
|
||||
# character is "free to puppet"
|
||||
char_strings.append(f" - {char.name} [{', '.join(char.permissions.all())}]")
|
||||
|
||||
if characters:
|
||||
string_s_ending = len(characters) > 1 and "s" or ""
|
||||
result.append("\n |wic <character>|n - enter the game (|wooc|n to get back here)")
|
||||
if is_su:
|
||||
result.append(
|
||||
f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):"
|
||||
)
|
||||
else:
|
||||
result.append(
|
||||
"\n\nAvailable character%s%s:"
|
||||
% (
|
||||
string_s_ending,
|
||||
charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "",
|
||||
)
|
||||
)
|
||||
|
||||
for char in characters:
|
||||
csessions = char.sessions.all()
|
||||
if csessions:
|
||||
for sess in csessions:
|
||||
# character is already puppeted
|
||||
sid = sess in sessions and sessions.index(sess) + 1
|
||||
if sess and sid:
|
||||
result.append(
|
||||
f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] "
|
||||
f"(played by you in session {sid})"
|
||||
)
|
||||
else:
|
||||
result.append(
|
||||
f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] "
|
||||
"(played by someone else)"
|
||||
)
|
||||
else:
|
||||
# character is "free to puppet"
|
||||
result.append(f"\n - {char.key} [{', '.join(char.permissions.all())}]")
|
||||
look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68)
|
||||
return look_string
|
||||
txt_characters = (
|
||||
f"Available character(s) ({ncars}/{max_chars}, |wic <name>|n to play):|n\n"
|
||||
+ "\n".join(char_strings)
|
||||
)
|
||||
return self.ooc_appearance_template.format(
|
||||
header=txt_header,
|
||||
sessions=txt_sessions,
|
||||
characters=txt_characters,
|
||||
footer="",
|
||||
)
|
||||
|
||||
|
||||
class DefaultGuest(DefaultAccount):
|
||||
|
|
@ -1789,8 +1803,9 @@ class DefaultGuest(DefaultAccount):
|
|||
|
||||
def at_post_login(self, session=None, **kwargs):
|
||||
"""
|
||||
In theory, guests only have one character regardless of which
|
||||
MULTISESSION_MODE we're in. They don't get a choice.
|
||||
By default, Guests only have one character regardless of which
|
||||
MAX_NR_CHARACTERS we use. They also always auto-puppet a matching
|
||||
character and don't get a choice.
|
||||
|
||||
Args:
|
||||
session (Session, optional): Session connecting.
|
||||
|
|
|
|||
|
|
@ -16,22 +16,16 @@ account info and OOC account configuration variables etc.
|
|||
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
from django.utils.encoding import smart_str
|
||||
|
||||
from evennia.accounts.manager import AccountDBManager
|
||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_RENAME
|
||||
from evennia.typeclasses.models import TypedObject
|
||||
from evennia.utils.utils import make_iter
|
||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_RENAME
|
||||
|
||||
__all__ = ("AccountDB",)
|
||||
|
||||
# _ME = _("me")
|
||||
# _SELF = _("self")
|
||||
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
_DA = object.__delattr__
|
||||
|
|
@ -94,8 +88,10 @@ class AccountDB(TypedObject, AbstractUser):
|
|||
"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.",
|
||||
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
|
||||
db_is_bot = models.BooleanField(
|
||||
|
|
|
|||
|
|
@ -20,14 +20,15 @@ method. Otherwise all text will be returned to all connected sessions.
|
|||
"""
|
||||
import time
|
||||
from codecs import lookup as codecs_lookup
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.utils import utils, create, logger, search
|
||||
from evennia.utils import create, logger, search, utils
|
||||
|
||||
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
||||
_MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
_AUTO_PUPPET_ON_LOGIN = settings.AUTO_PUPPET_ON_LOGIN
|
||||
|
||||
# limit symbol import for API
|
||||
__all__ = (
|
||||
|
|
@ -59,11 +60,6 @@ class MuxAccountLookCommand(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
super().parse()
|
||||
|
||||
if _MULTISESSION_MODE < 2:
|
||||
# only one character allowed - not used in this mode
|
||||
self.playable = None
|
||||
return
|
||||
|
||||
playable = self.account.db._playable_characters
|
||||
if playable is not None:
|
||||
# clean up list if character object was deleted in between
|
||||
|
|
@ -111,8 +107,14 @@ class CmdOOCLook(MuxAccountLookCommand):
|
|||
def func(self):
|
||||
"""implement the ooc look command"""
|
||||
|
||||
if _MULTISESSION_MODE < 2:
|
||||
# only one character allowed
|
||||
if self.session.puppet:
|
||||
# if we are puppeting, this is only reached in the case the that puppet
|
||||
# has no look command on its own.
|
||||
self.msg("You currently have no ability to look around.")
|
||||
return
|
||||
|
||||
if _AUTO_PUPPET_ON_LOGIN and _MAX_NR_CHARACTERS == 1 and self.playable:
|
||||
# only one exists and is allowed - simplify
|
||||
self.msg("You are out-of-character (OOC).\nUse |wic|n to get back into the game.")
|
||||
return
|
||||
|
||||
|
|
@ -149,14 +151,16 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
|||
key = self.lhs
|
||||
desc = self.rhs
|
||||
|
||||
charmax = _MAX_NR_CHARACTERS
|
||||
|
||||
if not account.is_superuser and (
|
||||
account.db._playable_characters and len(account.db._playable_characters) >= charmax
|
||||
):
|
||||
plural = "" if charmax == 1 else "s"
|
||||
self.msg(f"You may only create a maximum of {charmax} character{plural}.")
|
||||
return
|
||||
if _MAX_NR_CHARACTERS is not None:
|
||||
if (
|
||||
not account.is_superuser
|
||||
and not account.check_permstring("Developer")
|
||||
and account.db._playable_characters
|
||||
and len(account.db._playable_characters) >= _MAX_NR_CHARACTERS
|
||||
):
|
||||
plural = "" if _MAX_NR_CHARACTERS == 1 else "s"
|
||||
self.msg(f"You may only have a maximum of {_MAX_NR_CHARACTERS} character{plural}.")
|
||||
return
|
||||
from evennia.objects.models import ObjectDB
|
||||
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
|
|
@ -177,8 +181,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
|||
)
|
||||
# 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.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)
|
||||
if desc:
|
||||
|
|
@ -228,7 +232,8 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
elif len(match) > 1:
|
||||
self.msg(
|
||||
"Aborting - there are two characters with the same name. Ask an admin to delete the right one."
|
||||
"Aborting - there are two characters with the same name. Ask an admin to delete the"
|
||||
" right one."
|
||||
)
|
||||
return
|
||||
else: # one match
|
||||
|
|
@ -419,8 +424,8 @@ class CmdOOC(MuxAccountLookCommand):
|
|||
account.unpuppet_object(session)
|
||||
self.msg("\n|GYou go OOC.|n\n")
|
||||
|
||||
if _MULTISESSION_MODE < 2:
|
||||
# only one character allowed
|
||||
if _AUTO_PUPPET_ON_LOGIN and _MAX_NR_CHARACTERS == 1 and self.playable:
|
||||
# only one character exists and is allowed - simplify
|
||||
self.msg("You are out-of-character (OOC).\nUse |wic|n to get back into the game.")
|
||||
return
|
||||
|
||||
|
|
@ -917,7 +922,10 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
|
|||
% (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib))
|
||||
)
|
||||
table = self.table_format(table)
|
||||
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):"
|
||||
string = (
|
||||
"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)
|
||||
table = [[], [], [], [], [], [], [], [], [], [], [], []]
|
||||
for ibatch in range(4):
|
||||
|
|
@ -985,9 +993,7 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
|
|||
"""Perform the command"""
|
||||
account = self.account
|
||||
permstr = (
|
||||
account.is_superuser
|
||||
and " (superuser)"
|
||||
or "(%s)" % (", ".join(account.permissions.all()))
|
||||
account.is_superuser and " (superuser)" or "(%s)" % ", ".join(account.permissions.all())
|
||||
)
|
||||
if self.cmdstring in ("unquell", "unquell"):
|
||||
if not account.attributes.get("_quell"):
|
||||
|
|
|
|||
|
|
@ -12,41 +12,34 @@ main test suite started with
|
|||
|
||||
"""
|
||||
import datetime
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
from anything import Anything
|
||||
|
||||
from parameterized import parameterized
|
||||
from django.conf import settings
|
||||
from twisted.internet import task
|
||||
from unittest.mock import patch, Mock, MagicMock
|
||||
|
||||
from evennia import DefaultRoom, DefaultExit, ObjectDB
|
||||
from evennia.commands.default.cmdset_character import CharacterCmdSet
|
||||
from evennia.utils.test_resources import (
|
||||
BaseEvenniaTest,
|
||||
BaseEvenniaCommandTest,
|
||||
EvenniaCommandTest,
|
||||
) # noqa
|
||||
from evennia.commands.default import (
|
||||
help as help_module,
|
||||
general,
|
||||
system,
|
||||
admin,
|
||||
account,
|
||||
building,
|
||||
batchprocess,
|
||||
comms,
|
||||
unloggedin,
|
||||
syscommands,
|
||||
from django.test import override_settings
|
||||
from evennia import (
|
||||
DefaultCharacter,
|
||||
DefaultExit,
|
||||
DefaultObject,
|
||||
DefaultRoom,
|
||||
ObjectDB,
|
||||
search_object,
|
||||
)
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.commands.command import Command, InterruptCommand
|
||||
from evennia.commands import cmdparser
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.utils import utils, gametime, create
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia import search_object
|
||||
from evennia import DefaultObject, DefaultCharacter
|
||||
from evennia.commands.command import Command, InterruptCommand
|
||||
from evennia.commands.default import account, admin, batchprocess, building, comms, general
|
||||
from evennia.commands.default import help as help_module
|
||||
from evennia.commands.default import syscommands, system, unloggedin
|
||||
from evennia.commands.default.cmdset_character import CharacterCmdSet
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.prototypes import prototypes as protlib
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.utils import create, gametime, utils
|
||||
from evennia.utils.test_resources import BaseEvenniaCommandTest # noqa
|
||||
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaCommandTest
|
||||
from parameterized import parameterized
|
||||
from twisted.internet import task
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Command testing
|
||||
|
|
@ -232,19 +225,16 @@ class TestHelp(BaseEvenniaCommandTest):
|
|||
),
|
||||
(
|
||||
"test/extra/subsubtopic", # partial subsub-match
|
||||
"Help for test/creating extra stuff/subsubtopic\n\n" "A subsubtopic text",
|
||||
"Help for test/creating extra stuff/subsubtopic\n\nA subsubtopic text",
|
||||
),
|
||||
(
|
||||
"test/creating extra/subsub", # partial subsub-match
|
||||
"Help for test/creating extra stuff/subsubtopic\n\n" "A subsubtopic text",
|
||||
"Help for test/creating extra stuff/subsubtopic\n\nA subsubtopic text",
|
||||
),
|
||||
("test/Something else", "Help for test/something else\n\n" "Something else"), # case
|
||||
("test/Something else", "Help for test/something else\n\nSomething else"), # case
|
||||
(
|
||||
"test/More", # case
|
||||
"Help for test/more\n\n"
|
||||
"Another text\n\n"
|
||||
"Subtopics:\n"
|
||||
" test/more/second-more",
|
||||
"Help for test/more\n\nAnother text\n\nSubtopics:\n test/more/second-more",
|
||||
),
|
||||
(
|
||||
"test/More/Second-more",
|
||||
|
|
@ -264,11 +254,11 @@ class TestHelp(BaseEvenniaCommandTest):
|
|||
),
|
||||
(
|
||||
"test/more/second/more again",
|
||||
"Help for test/more/second-more/more again\n\n" "Even more text.\n",
|
||||
"Help for test/more/second-more/more again\n\nEven more text.\n",
|
||||
),
|
||||
(
|
||||
"test/more/second/third",
|
||||
"Help for test/more/second-more/third more\n\n" "Third more text\n",
|
||||
"Help for test/more/second-more/third more\n\nThird more text\n",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
@ -520,7 +510,7 @@ class TestCmdTasks(BaseEvenniaCommandTest):
|
|||
|
||||
def test_misformed_command(self):
|
||||
wanted_msg = (
|
||||
"Task command misformed.|Proper format tasks[/switch] " "[function name or task id]"
|
||||
"Task command misformed.|Proper format tasks[/switch] [function name or task id]"
|
||||
)
|
||||
self.call(system.CmdTasks(), f"/cancel", wanted_msg)
|
||||
|
||||
|
|
@ -557,18 +547,49 @@ class TestAdmin(BaseEvenniaCommandTest):
|
|||
|
||||
|
||||
class TestAccount(BaseEvenniaCommandTest):
|
||||
def test_ooc_look(self):
|
||||
if settings.MULTISESSION_MODE < 2:
|
||||
self.call(
|
||||
account.CmdOOCLook(), "", "You are out-of-character (OOC).", caller=self.account
|
||||
)
|
||||
if settings.MULTISESSION_MODE == 2:
|
||||
self.call(
|
||||
account.CmdOOCLook(),
|
||||
"",
|
||||
"Account TestAccount (you are OutofCharacter)",
|
||||
caller=self.account,
|
||||
)
|
||||
"""
|
||||
Test different account-specific modes
|
||||
|
||||
"""
|
||||
|
||||
@parameterized.expand(
|
||||
# multisession-mode, auto-puppet, max_nr_characters
|
||||
[
|
||||
(0, True, 1, "You are out-of-character"),
|
||||
(1, True, 1, "You are out-of-character"),
|
||||
(2, True, 1, "You are out-of-character"),
|
||||
(3, True, 1, "You are out-of-character"),
|
||||
(0, False, 1, "Account TestAccount"),
|
||||
(1, False, 1, "Account TestAccount"),
|
||||
(2, False, 1, "Account TestAccount"),
|
||||
(3, False, 1, "Account TestAccount"),
|
||||
(0, True, 2, "Account TestAccount"),
|
||||
(1, True, 2, "Account TestAccount"),
|
||||
(2, True, 2, "Account TestAccount"),
|
||||
(3, True, 2, "Account TestAccount"),
|
||||
(0, False, 2, "Account TestAccount"),
|
||||
(1, False, 2, "Account TestAccount"),
|
||||
(2, False, 2, "Account TestAccount"),
|
||||
(3, False, 2, "Account TestAccount"),
|
||||
]
|
||||
)
|
||||
def test_ooc_look(self, multisession_mode, auto_puppet, max_nr_chars, expected_result):
|
||||
|
||||
self.account.db._playable_characters = [self.char1]
|
||||
self.account.unpuppet_all()
|
||||
|
||||
with self.settings(MULTISESSION=multisession_mode):
|
||||
# we need to patch the module header instead of settings
|
||||
with patch("evennia.commands.default.account._MAX_NR_CHARACTERS", new=max_nr_chars):
|
||||
with patch(
|
||||
"evennia.commands.default.account._AUTO_PUPPET_ON_LOGIN", new=auto_puppet
|
||||
):
|
||||
self.call(
|
||||
account.CmdOOCLook(),
|
||||
"",
|
||||
expected_result,
|
||||
caller=self.account,
|
||||
)
|
||||
|
||||
def test_ooc(self):
|
||||
self.call(account.CmdOOC(), "", "You go OOC.", caller=self.account)
|
||||
|
|
@ -901,7 +922,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdSetAttribute(),
|
||||
"Obj/test2[+'three']",
|
||||
"Attribute Obj/test2[+'three'] [category:None] does not exist. (Nested lookups attempted)",
|
||||
"Attribute Obj/test2[+'three'] [category:None] does not exist. (Nested lookups"
|
||||
" attempted)",
|
||||
)
|
||||
self.call(
|
||||
building.CmdSetAttribute(),
|
||||
|
|
@ -1091,7 +1113,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdSetAttribute(),
|
||||
"Obj/test4[0]['one']",
|
||||
"Attribute Obj/test4[0]['one'] [category:None] does not exist. (Nested lookups attempted)",
|
||||
"Attribute Obj/test4[0]['one'] [category:None] does not exist. (Nested lookups"
|
||||
" attempted)",
|
||||
)
|
||||
|
||||
def test_split_nested_attr(self):
|
||||
|
|
@ -1339,7 +1362,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdTypeclass(),
|
||||
"Obj = evennia.objects.objects.DefaultExit",
|
||||
"Obj already has the typeclass 'evennia.objects.objects.DefaultExit'. Use /force to override.",
|
||||
"Obj already has the typeclass 'evennia.objects.objects.DefaultExit'. Use /force to"
|
||||
" override.",
|
||||
)
|
||||
self.call(
|
||||
building.CmdTypeclass(),
|
||||
|
|
@ -1355,9 +1379,9 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdTypeclass(),
|
||||
"Obj",
|
||||
"Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n"
|
||||
"Only the at_object_creation hook was run (update mode). Attributes set before swap were not removed\n"
|
||||
"(use `swap` or `type/reset` to clear all).",
|
||||
"Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\nOnly the"
|
||||
" at_object_creation hook was run (update mode). Attributes set before swap were not"
|
||||
" removed\n(use `swap` or `type/reset` to clear all).",
|
||||
cmdstring="update",
|
||||
)
|
||||
self.call(
|
||||
|
|
@ -1560,9 +1584,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdTeleport(),
|
||||
"Obj = Room2",
|
||||
"Obj(#{}) is leaving Room(#{}), heading for Room2(#{}).|Teleported Obj -> Room2.".format(
|
||||
oid, rid, rid2
|
||||
),
|
||||
"Obj(#{}) is leaving Room(#{}), heading for Room2(#{}).|Teleported Obj -> Room2."
|
||||
.format(oid, rid, rid2),
|
||||
)
|
||||
self.call(building.CmdTeleport(), "NotFound = Room", "Could not find 'NotFound'.")
|
||||
self.call(
|
||||
|
|
@ -1598,7 +1621,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdTag(),
|
||||
"Obj",
|
||||
"Tags on Obj: 'testtag', 'testtag2', " "'testtag2' (category: category1), 'testtag3'",
|
||||
"Tags on Obj: 'testtag', 'testtag2', 'testtag2' (category: category1), 'testtag3'",
|
||||
)
|
||||
|
||||
self.call(building.CmdTag(), "/search NotFound", "No objects found with tag 'NotFound'.")
|
||||
|
|
@ -1654,7 +1677,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
|
||||
self.call(
|
||||
building.CmdSpawn(),
|
||||
"/save {'key':'Test Char', " "'typeclass':'evennia.objects.objects.DefaultCharacter'}",
|
||||
"/save {'key':'Test Char', 'typeclass':'evennia.objects.objects.DefaultCharacter'}",
|
||||
"A prototype_key must be given, either as `prototype_key = <prototype>` or as "
|
||||
"a key 'prototype_key' inside the prototype structure.",
|
||||
)
|
||||
|
|
@ -1678,7 +1701,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
building.CmdSpawn(),
|
||||
"{'prototype_key':'GOBLIN', 'typeclass':'evennia.objects.objects.DefaultCharacter', "
|
||||
"'key':'goblin', 'location':'%s'}" % spawnLoc.dbref,
|
||||
"'key':'goblin', 'location':'%s'}"
|
||||
% spawnLoc.dbref,
|
||||
"Spawned goblin",
|
||||
)
|
||||
goblin = get_object(self, "goblin")
|
||||
|
|
@ -1725,7 +1749,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
# Location should be the specified location.
|
||||
self.call(
|
||||
building.CmdSpawn(),
|
||||
"/noloc {'prototype_parent':'TESTBALL', 'key': 'Ball', 'prototype_key': 'foo', 'location':'%s'}"
|
||||
"/noloc {'prototype_parent':'TESTBALL', 'key': 'Ball', 'prototype_key': 'foo',"
|
||||
" 'location':'%s'}"
|
||||
% spawnLoc.dbref,
|
||||
"Spawned Ball",
|
||||
)
|
||||
|
|
@ -1785,8 +1810,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
|||
|
||||
|
||||
import evennia.commands.default.comms as cmd_comms # noqa
|
||||
from evennia.utils.create import create_channel # noqa
|
||||
from evennia.comms.comms import DefaultChannel # noqa
|
||||
from evennia.utils.create import create_channel # noqa
|
||||
|
||||
|
||||
@patch("evennia.commands.default.comms.CHANNEL_DEFAULT_TYPECLASS", DefaultChannel)
|
||||
|
|
@ -1986,7 +2011,8 @@ class TestBatchProcess(BaseEvenniaCommandTest):
|
|||
self.call(
|
||||
batchprocess.CmdBatchCommands(),
|
||||
"batchprocessor.example_batch_cmds",
|
||||
"Running Batch-command processor - Automatic mode for batchprocessor.example_batch_cmds",
|
||||
"Running Batch-command processor - Automatic mode for"
|
||||
" batchprocessor.example_batch_cmds",
|
||||
)
|
||||
# we make sure to delete the button again here to stop the running reactor
|
||||
confirm = building.CmdDestroy.confirm
|
||||
|
|
@ -2018,7 +2044,8 @@ class TestUnconnectedCommand(BaseEvenniaCommandTest):
|
|||
# instead of using SERVER_START_TIME (0), we use 86400 because Windows won't let us use anything lower
|
||||
gametime.SERVER_START_TIME = 86400
|
||||
expected = (
|
||||
"## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END INFO"
|
||||
"## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END"
|
||||
" INFO"
|
||||
% (
|
||||
settings.SERVERNAME,
|
||||
datetime.datetime.fromtimestamp(gametime.SERVER_START_TIME).ctime(),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
"""
|
||||
Commands that are available from the connect screen.
|
||||
|
||||
"""
|
||||
import re
|
||||
import datetime
|
||||
import re
|
||||
from codecs import lookup as codecs_lookup
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.comms.models import ChannelDB
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
|
||||
from evennia.utils import class_from_module, create, logger, utils, gametime
|
||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.utils import class_from_module, create, gametime, logger, utils
|
||||
|
||||
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
||||
|
|
@ -25,7 +26,6 @@ __all__ = (
|
|||
"CmdUnconnectedScreenreader",
|
||||
)
|
||||
|
||||
MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
||||
|
||||
|
||||
|
|
@ -215,7 +215,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
|||
session.msg("Aborted. If your user name contains spaces, surround it by quotes.")
|
||||
return
|
||||
|
||||
# everything's ok. Create the new account account.
|
||||
# everything's ok. Create the new player account.
|
||||
account, errors = Account.create(
|
||||
username=username, password=password, ip=address, session=session
|
||||
)
|
||||
|
|
@ -447,7 +447,8 @@ class CmdUnconnectedInfo(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
def func(self):
|
||||
self.caller.msg(
|
||||
"## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END INFO"
|
||||
"## BEGIN INFO 1.1\nName: %s\nUptime: %s\nConnected: %d\nVersion: Evennia %s\n## END"
|
||||
" INFO"
|
||||
% (
|
||||
settings.SERVERNAME,
|
||||
datetime.datetime.fromtimestamp(gametime.SERVER_START_TIME).ctime(),
|
||||
|
|
@ -468,8 +469,8 @@ def _create_account(session, accountname, password, permissions, typeclass=None,
|
|||
|
||||
except Exception as e:
|
||||
session.msg(
|
||||
"There was an error creating the Account:\n%s\n If this problem persists, contact an admin."
|
||||
% e
|
||||
"There was an error creating the Account:\n%s\n If this problem persists, contact an"
|
||||
" admin." % e
|
||||
)
|
||||
logger.log_trace()
|
||||
return False
|
||||
|
|
@ -490,7 +491,7 @@ def _create_account(session, accountname, password, permissions, typeclass=None,
|
|||
def _create_character(session, new_account, typeclass, home, permissions):
|
||||
"""
|
||||
Helper function, creates a character based on an account's name.
|
||||
This is meant for Guest and MULTISESSION_MODE < 2 situations.
|
||||
This is meant for Guest and AUTO_CREATRE_CHARACTER_WITH_ACCOUNT=True situations.
|
||||
"""
|
||||
try:
|
||||
new_character = create.create_object(
|
||||
|
|
@ -512,7 +513,7 @@ def _create_character(session, new_account, typeclass, home, permissions):
|
|||
new_account.db._last_puppet = new_character
|
||||
except Exception as e:
|
||||
session.msg(
|
||||
"There was an error creating the Character:\n%s\n If this problem persists, contact an admin."
|
||||
% e
|
||||
"There was an error creating the Character:\n%s\n If this problem persists, contact an"
|
||||
" admin." % e
|
||||
)
|
||||
logger.log_trace()
|
||||
|
|
|
|||
|
|
@ -31,19 +31,14 @@ after this change. The login splashscreen is taken from strings in
|
|||
the module given by settings.CONNECTION_SCREEN_MODULE.
|
||||
|
||||
"""
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.server.models import ServerConfig
|
||||
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.utils import logger, utils, ansi
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.commands.default import (
|
||||
unloggedin as default_unloggedin,
|
||||
) # Used in CmdUnconnectedCreate
|
||||
from evennia.commands.cmdset import CmdSet
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.server.models import ServerConfig
|
||||
from evennia.utils import ansi, class_from_module, utils
|
||||
|
||||
# limit symbol import for API
|
||||
__all__ = (
|
||||
|
|
@ -54,7 +49,6 @@ __all__ = (
|
|||
"CmdUnconnectedHelp",
|
||||
)
|
||||
|
||||
MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
|
||||
CONNECTION_SCREEN = ""
|
||||
try:
|
||||
|
|
@ -162,21 +156,24 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
# this means we have a multi_word accountname. pop from the back.
|
||||
password = self.arglist.pop()
|
||||
email = self.arglist.pop()
|
||||
# what remains is the accountname.
|
||||
accountname = " ".join(self.arglist)
|
||||
# what remains is the username.
|
||||
username = " ".join(self.arglist)
|
||||
else:
|
||||
accountname, email, password = self.arglist
|
||||
username, email, password = self.arglist
|
||||
|
||||
accountname = accountname.replace('"', "") # remove "
|
||||
accountname = accountname.replace("'", "")
|
||||
self.accountinfo = (accountname, email, password)
|
||||
username = username.replace('"', "") # remove "
|
||||
username = username.replace("'", "")
|
||||
self.accountinfo = (username, email, password)
|
||||
|
||||
def func(self):
|
||||
"""Do checks and create account"""
|
||||
|
||||
Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||
address = self.session.address
|
||||
|
||||
session = self.caller
|
||||
try:
|
||||
accountname, email, password = self.accountinfo
|
||||
username, email, password = self.accountinfo
|
||||
except ValueError:
|
||||
string = '\n\r Usage (without <>): create "<accountname>" <email> <password>'
|
||||
session.msg(string)
|
||||
|
|
@ -188,85 +185,41 @@ class CmdUnconnectedCreate(MuxCommand):
|
|||
# check so the email at least looks ok.
|
||||
session.msg("'%s' is not a valid e-mail address." % email)
|
||||
return
|
||||
# sanity checks
|
||||
if not re.findall(r"^[\w. @+\-']+$", accountname) or not (0 < len(accountname) <= 30):
|
||||
# this echoes the restrictions made by django's auth
|
||||
# module (except not allowing spaces, for convenience of
|
||||
# logging in).
|
||||
string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
|
||||
session.msg(string)
|
||||
return
|
||||
# strip excessive spaces in accountname
|
||||
accountname = re.sub(r"\s+", " ", accountname).strip()
|
||||
if AccountDB.objects.filter(username__iexact=accountname):
|
||||
# account already exists (we also ignore capitalization here)
|
||||
session.msg("Sorry, there is already an account with the name '%s'." % accountname)
|
||||
return
|
||||
if AccountDB.objects.get_account_from_email(email):
|
||||
# email already set on an account
|
||||
session.msg("Sorry, there is already an account with that email address.")
|
||||
return
|
||||
# Reserve accountnames found in 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."
|
||||
session.msg(string)
|
||||
return
|
||||
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
|
||||
string = (
|
||||
"\n\r Password should be longer than 3 characters. Letters, spaces, digits and @/./+/-/_/' only."
|
||||
"\nFor best security, make it longer than 8 characters. You can also use a phrase of"
|
||||
"\nmany words if you enclose the password in double quotes."
|
||||
)
|
||||
session.msg(string)
|
||||
return
|
||||
|
||||
# Check IP and/or name bans
|
||||
bans = ServerConfig.objects.conf("server_bans")
|
||||
if bans and (
|
||||
any(tup[0] == 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!
|
||||
string = (
|
||||
"|rYou have been banned and cannot continue from here."
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
# pre-normalize username so the user know what they get
|
||||
non_normalized_username = username
|
||||
username = Account.normalize_username(username)
|
||||
if non_normalized_username != username:
|
||||
session.msg(
|
||||
"Note: your username was normalized to strip spaces and remove characters "
|
||||
"that could be visually confusing."
|
||||
)
|
||||
session.msg(string)
|
||||
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
|
||||
|
||||
# have the user verify their new account was what they intended
|
||||
answer = yield (
|
||||
f"You want to create an account '{username}' with email '{email}' and password "
|
||||
f"'{password}'.\nIs this what you intended? [Y]/N?"
|
||||
)
|
||||
if answer.lower() in ("n", "no"):
|
||||
session.msg("Aborted. If your user name contains spaces, surround it by quotes.")
|
||||
return
|
||||
|
||||
# everything's ok. Create the new player account.
|
||||
try:
|
||||
permissions = settings.PERMISSION_ACCOUNT_DEFAULT
|
||||
typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
new_account = default_unloggedin._create_account(
|
||||
session, accountname, password, permissions, email=email
|
||||
)
|
||||
if new_account:
|
||||
if MULTISESSION_MODE < 2:
|
||||
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
|
||||
default_unloggedin._create_character(
|
||||
session, new_account, typeclass, default_home, permissions
|
||||
)
|
||||
# tell the caller everything went well.
|
||||
string = "A new account '%s' was created. Welcome!"
|
||||
if " " in accountname:
|
||||
string += (
|
||||
"\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
|
||||
)
|
||||
else:
|
||||
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
||||
session.msg(string % (accountname, email))
|
||||
|
||||
except Exception:
|
||||
# We are in the middle between logged in and -not, so we have
|
||||
# to handle tracebacks ourselves at this point. If we don't,
|
||||
# we won't see any errors at all.
|
||||
session.msg("An error occurred. Please e-mail an admin if the problem persists.")
|
||||
logger.log_trace()
|
||||
raise
|
||||
account, errors = Account.create(
|
||||
username=username, email=email, password=password, ip=address, session=session
|
||||
)
|
||||
if account:
|
||||
# tell the caller everything went well.
|
||||
string = "A new account '%s' was created. Welcome!"
|
||||
if " " in username:
|
||||
string += (
|
||||
"\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
|
||||
)
|
||||
else:
|
||||
string += "\n\nYou can now log with the command 'connect %s <your password>'."
|
||||
session.msg(string % (username, username))
|
||||
else:
|
||||
session.msg("|R%s|n" % "\n".join(errors))
|
||||
|
||||
|
||||
class CmdUnconnectedQuit(MuxCommand):
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ Test email login.
|
|||
"""
|
||||
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
|
||||
from . import email_login
|
||||
|
||||
|
||||
|
|
@ -13,17 +14,20 @@ class TestEmailLogin(BaseEvenniaCommandTest):
|
|||
email_login.CmdUnconnectedConnect(),
|
||||
"mytest@test.com test",
|
||||
"The email 'mytest@test.com' does not match any accounts.",
|
||||
inputs=["Y"],
|
||||
)
|
||||
self.call(
|
||||
email_login.CmdUnconnectedCreate(),
|
||||
'"mytest" mytest@test.com test11111',
|
||||
"A new account 'mytest' was created. Welcome!",
|
||||
inputs=["Y"],
|
||||
)
|
||||
self.call(
|
||||
email_login.CmdUnconnectedConnect(),
|
||||
"mytest@test.com test11111",
|
||||
"",
|
||||
caller=self.account.sessions.get()[0],
|
||||
inputs=["Y"],
|
||||
)
|
||||
|
||||
def test_quit(self):
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ def check_errors(settings):
|
|||
raise DeprecationWarning(deprstring % ("CMDSET_OOC", "CMDSET_ACCOUNT"))
|
||||
if settings.WEBSERVER_ENABLED and not isinstance(settings.WEBSERVER_PORTS[0], tuple):
|
||||
raise DeprecationWarning(
|
||||
"settings.WEBSERVER_PORTS must be on the form " "[(proxyport, serverport), ...]"
|
||||
"settings.WEBSERVER_PORTS must be on the form [(proxyport, serverport), ...]"
|
||||
)
|
||||
if hasattr(settings, "BASE_COMM_TYPECLASS"):
|
||||
raise DeprecationWarning(deprstring % ("BASE_COMM_TYPECLASS", "BASE_CHANNEL_TYPECLASS"))
|
||||
|
|
@ -43,7 +43,7 @@ def check_errors(settings):
|
|||
"(see evennia/settings_default.py)."
|
||||
)
|
||||
deprstring = (
|
||||
"settings.%s is now merged into settings.TYPECLASS_PATHS. " "Update your settings file."
|
||||
"settings.%s is now merged into settings.TYPECLASS_PATHS. Update your settings file."
|
||||
)
|
||||
if hasattr(settings, "OBJECT_TYPECLASS_PATHS"):
|
||||
raise DeprecationWarning(deprstring % "OBJECT_TYPECLASS_PATHS")
|
||||
|
|
@ -147,6 +147,13 @@ def check_errors(settings):
|
|||
" 2. Rename your existing `static_overrides` folder to `static` instead."
|
||||
)
|
||||
|
||||
if settings.MULTISESSION_MODE < 2 and settings.MAX_NR_SIMULTANEOUS_PUPPETS > 1:
|
||||
raise DeprecationWarning(
|
||||
f"settings.MULTISESSION_MODE={settings.MULTISESSION_MODE} is not compatible with "
|
||||
f"settings.MAX_NR_SIMULTANEOUS_PUPPETS={settings.MAX_NR_SIMULTANEOUS_PUPPETS}. "
|
||||
"To allow multiple simultaneous puppets, the multi-session mode must be higher than 1."
|
||||
)
|
||||
|
||||
|
||||
def check_warnings(settings):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -13,22 +13,20 @@ There are two similar but separate stores of sessions:
|
|||
|
||||
"""
|
||||
import time
|
||||
from codecs import decode as codecs_decode
|
||||
|
||||
from django.conf import settings
|
||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.utils.logger import log_trace
|
||||
from evennia.utils.utils import (
|
||||
is_iter,
|
||||
make_iter,
|
||||
delay,
|
||||
callables_from_module,
|
||||
class_from_module,
|
||||
)
|
||||
from evennia.server.portal import amp
|
||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT
|
||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
|
||||
from codecs import decode as codecs_decode
|
||||
from django.utils.translation import gettext as _
|
||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.server.portal import amp
|
||||
from evennia.server.signals import (
|
||||
SIGNAL_ACCOUNT_POST_FIRST_LOGIN,
|
||||
SIGNAL_ACCOUNT_POST_LAST_LOGOUT,
|
||||
SIGNAL_ACCOUNT_POST_LOGIN,
|
||||
SIGNAL_ACCOUNT_POST_LOGOUT,
|
||||
)
|
||||
from evennia.utils.logger import log_trace
|
||||
from evennia.utils.utils import callables_from_module, class_from_module, delay, is_iter, make_iter
|
||||
|
||||
_FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED
|
||||
_BROADCAST_SERVER_RESTART_MESSAGES = settings.BROADCAST_SERVER_RESTART_MESSAGES
|
||||
|
|
|
|||
|
|
@ -249,7 +249,7 @@ EXTRA_LAUNCHER_COMMANDS = {}
|
|||
MAX_CHAR_LIMIT = 6000
|
||||
# The warning to echo back to users if they enter a very large string
|
||||
MAX_CHAR_LIMIT_WARNING = (
|
||||
"You entered a string that was too long. " "Please break it up into multiple parts."
|
||||
"You entered a string that was too long. Please break it up into multiple parts."
|
||||
)
|
||||
# If this is true, errors and tracebacks from the engine will be
|
||||
# echoed as text in-game as well as to the log. This can speed up
|
||||
|
|
@ -538,8 +538,6 @@ BASE_SCRIPT_TYPECLASS = "typeclasses.scripts.Script"
|
|||
# is Limbo (#2).
|
||||
DEFAULT_HOME = "#2"
|
||||
# The start position for new characters. Default is Limbo (#2).
|
||||
# MULTISESSION_MODE = 0, 1 - used by default unloggedin create command
|
||||
# MULTISESSION_MODE = 2, 3 - used by default character_create command
|
||||
START_LOCATION = "#2"
|
||||
# Lookups of Attributes, Tags, Nicks, Aliases can be aggressively
|
||||
# cached to avoid repeated database hits. This often gives noticeable
|
||||
|
|
@ -709,21 +707,31 @@ GLOBAL_SCRIPTS = {
|
|||
######################################################################
|
||||
|
||||
# Different Multisession modes allow a player (=account) to connect to the
|
||||
# game simultaneously with multiple clients (=sessions). In modes 0,1 there is
|
||||
# only one character created to the same name as the account at first login.
|
||||
# In modes 2,3 no default character will be created and the MAX_NR_CHARACTERS
|
||||
# value (below) defines how many characters the default char_create command
|
||||
# allow per account.
|
||||
# 0 - single session, one account, one character, when a new session is
|
||||
# connected, the old one is disconnected
|
||||
# 1 - multiple sessions, one account, one character, each session getting
|
||||
# the same data
|
||||
# 2 - multiple sessions, one account, many characters, one session per
|
||||
# character (disconnects multiplets)
|
||||
# 3 - like mode 2, except multiple sessions can puppet one character, each
|
||||
# game simultaneously with multiple clients (=sessions).
|
||||
# 0 - single session per account (if reconnecting, disconnect old session)
|
||||
# 1 - multiple sessions per account, all sessions share output
|
||||
# 2 - multiple sessions per account, one session allowed per puppet
|
||||
# 3 - multiple sessions per account, multiple sessions per puppet (share output)
|
||||
# session getting the same data.
|
||||
MULTISESSION_MODE = 0
|
||||
# The maximum number of characters allowed by the default ooc char-creation command
|
||||
# Whether we should create a character with the same name as the account when
|
||||
# a new account is created. Together with AUTO_PUPPET_ON_LOGIN, this mimics
|
||||
# a legacy MUD, where there is no difference between account and character.
|
||||
AUTO_CREATE_CHARACTER_WITH_ACCOUNT = True
|
||||
# Whether an account should auto-puppet the last puppeted puppet when logging in. This
|
||||
# will only work if the session/puppet combination can be determined (usually
|
||||
# MULTISESSION_MODE 0 or 1), otherwise, the player will end up OOC. Use
|
||||
# MULTISESSION_MODE=0, AUTO_CREATE_CHARACTER_WITH_ACCOUNT=True and this value to
|
||||
# mimic a legacy mud with minimal difference between Account and Character. Disable
|
||||
# this and AUTO_PUPPET to get a chargen/character select screen on login.
|
||||
AUTO_PUPPET_ON_LOGIN = True
|
||||
# How many *different* characters an account can puppet *at the same time*. A value
|
||||
# above 1 only makes a difference together with MULTISESSION_MODE > 1.
|
||||
MAX_NR_SIMULTANEOUS_PUPPETS = 1
|
||||
# The maximum number of characters allowed by be created by the default ooc
|
||||
# char-creation command. This can be seen as how big of a 'stable' of characters
|
||||
# an account can have (not how many you can puppet at the same time). Set to
|
||||
# None for no limit.
|
||||
MAX_NR_CHARACTERS = 1
|
||||
# The access hierarchy, in climbing order. A higher permission in the
|
||||
# hierarchy includes access of all levels below it. Used by the perm()/pperm()
|
||||
|
|
|
|||
|
|
@ -22,26 +22,25 @@ Other:
|
|||
helper. Used by the command-test classes, but can be used for making a customt test class.
|
||||
|
||||
"""
|
||||
import sys
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
from twisted.internet.defer import Deferred
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase, override_settings
|
||||
from mock import Mock, patch, MagicMock
|
||||
from evennia.objects.objects import DefaultObject, DefaultCharacter, DefaultRoom, DefaultExit
|
||||
from evennia import settings_default
|
||||
from evennia.accounts.accounts import DefaultAccount
|
||||
from evennia.commands.command import InterruptCommand
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
|
||||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.server.serversession import ServerSession
|
||||
from evennia.server.sessionhandler import SESSIONS
|
||||
from evennia.utils import create
|
||||
from evennia.utils import ansi, create
|
||||
from evennia.utils.idmapper.models import flush_cache
|
||||
from evennia.utils.utils import all_from_module, to_str
|
||||
from evennia.utils import ansi
|
||||
from evennia import settings_default
|
||||
from evennia.commands.default.muxcommand import MuxCommand
|
||||
from evennia.commands.command import InterruptCommand
|
||||
|
||||
from mock import MagicMock, Mock, patch
|
||||
from twisted.internet.defer import Deferred
|
||||
|
||||
_RE_STRIP_EVMENU = re.compile(r"^\+|-+\+|\+-+|--+|\|(?:\s|$)", re.MULTILINE)
|
||||
|
||||
|
|
@ -382,7 +381,8 @@ class EvenniaCommandTestMixin:
|
|||
inputs (list, optional): A list of strings to pass to functions that pause to
|
||||
take input from the user (normally using `@interactive` and
|
||||
`ret = yield(question)` or `evmenu.get_input`). Each element of the
|
||||
list will be passed into the command as if the user wrote that at the prompt.
|
||||
list will be passed into the command as if the user answered each prompt
|
||||
in that order.
|
||||
raw_string (str, optional): Normally the `.raw_string` property is set as
|
||||
a combination of your `key/cmdname` and `input_args`. This allows
|
||||
direct control of what this is, for example for testing edge cases
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
from django.conf import settings
|
||||
from django.utils.text import slugify
|
||||
from django.test import Client, override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from evennia.help import filehelp
|
||||
from evennia.utils import class_from_module
|
||||
from evennia.utils.create import create_help_entry
|
||||
from evennia.utils.test_resources import BaseEvenniaTest
|
||||
from evennia.help import filehelp
|
||||
|
||||
_FILE_HELP_ENTRIES = None
|
||||
|
||||
|
|
@ -216,7 +216,7 @@ class CharacterCreateView(EvenniaWebTest):
|
|||
url_name = "character-create"
|
||||
unauthenticated_response = 302
|
||||
|
||||
@override_settings(MULTISESSION_MODE=0)
|
||||
@override_settings(MAX_NR_CHARACTERS=1)
|
||||
def test_valid_access_multisession_0(self):
|
||||
"Account1 with no characters should be able to create a new one"
|
||||
self.account.db._playable_characters = []
|
||||
|
|
@ -237,10 +237,9 @@ class CharacterCreateView(EvenniaWebTest):
|
|||
% self.account.db._playable_characters,
|
||||
)
|
||||
|
||||
@override_settings(MULTISESSION_MODE=2)
|
||||
@override_settings(MAX_NR_CHARACTERS=10)
|
||||
@override_settings(MAX_NR_CHARACTERS=5)
|
||||
def test_valid_access_multisession_2(self):
|
||||
"Account1 should be able to create a new character"
|
||||
"Account1 should be able to create multiple new characters"
|
||||
# Login account
|
||||
self.login()
|
||||
|
||||
|
|
@ -275,7 +274,8 @@ class CharacterPuppetView(EvenniaWebTest):
|
|||
response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True)
|
||||
self.assertTrue(
|
||||
response.status_code >= 400,
|
||||
"Invalid access should return a 4xx code-- either obj not found or permission denied! (Returned %s)"
|
||||
"Invalid access should return a 4xx code-- either obj not found or permission denied!"
|
||||
" (Returned %s)"
|
||||
% response.status_code,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue