Merge branch 'develop' into contrib/evadventure

This commit is contained in:
Griatch 2022-09-17 13:23:13 +02:00
commit f05add7c5b
13 changed files with 381 additions and 357 deletions

View file

@ -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

View file

@ -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.

View file

@ -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(

View file

@ -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"):

View file

@ -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(),

View file

@ -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()

View file

@ -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):

View file

@ -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):

View file

@ -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):
"""

View file

@ -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

View file

@ -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()

View file

@ -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

View file

@ -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,
)