Break up/extend MULTISESSION_MODE into several new settings

This commit is contained in:
Griatch 2022-09-14 23:46:10 +02:00
parent e9cce46d66
commit a0a0a248a0
13 changed files with 392 additions and 368 deletions

View file

@ -12,32 +12,32 @@ instead for most things).
"""
import re
import time
from random import getrandbits
from django.conf import settings
from django.contrib.auth import authenticate, password_validation
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils import timezone
from django.utils.module_loading import import_string
from evennia.typeclasses.models import TypeclassBase
from django.utils.translation import gettext as _
from evennia.accounts.manager import AccountManager
from evennia.accounts.models import AccountDB
from evennia.objects.models import ObjectDB
from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.comms.models import ChannelDB
from evennia.objects.models import ObjectDB
from evennia.scripts.scripthandler import ScriptHandler
from evennia.server.models import ServerConfig
from evennia.server.throttle import Throttle
from evennia.utils import class_from_module, create, logger
from evennia.utils.utils import lazy_property, to_str, make_iter, is_iter, variable_from_module
from evennia.server.signals import (
SIGNAL_ACCOUNT_POST_CREATE,
SIGNAL_OBJECT_POST_PUPPET,
SIGNAL_OBJECT_POST_UNPUPPET,
)
from evennia.typeclasses.attributes import NickHandler, ModelAttributeBackend
from evennia.scripts.scripthandler import ScriptHandler
from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.server.throttle import Throttle
from evennia.typeclasses.attributes import ModelAttributeBackend, NickHandler
from evennia.typeclasses.models import TypeclassBase
from evennia.utils import class_from_module, create, logger
from evennia.utils.optionhandler import OptionHandler
from django.utils.translation import gettext as _
from random import getrandbits
from evennia.utils.utils import is_iter, lazy_property, make_iter, to_str, variable_from_module
__all__ = ("DefaultAccount", "DefaultGuest")
@ -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
@ -322,7 +325,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)
@ -330,6 +332,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
@ -436,7 +453,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
@ -756,6 +776,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.")
@ -827,7 +850,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(
@ -1221,7 +1244,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)
@ -1418,7 +1441,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.
@ -1431,23 +1454,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.
@ -1567,6 +1582,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
@ -1574,7 +1607,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).
@ -1591,94 +1624,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):
@ -1773,8 +1787,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(