mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Resolve merge conflicts
This commit is contained in:
commit
4bfaa154d9
31 changed files with 1228 additions and 595 deletions
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: griatch
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: https://www.paypal.me/GriatchEvennia
|
||||
|
|
@ -13,9 +13,10 @@
|
|||
- Added `content_types` indexing to DefaultObject's ContentsHandler. (volund)
|
||||
- Made most of the networking classes such as Protocols and the SessionHandlers
|
||||
replaceable via `settings.py` for modding enthusiasts. (volund)
|
||||
- The `initial_setup.py` file can now be substituted in `settings.py` to customize
|
||||
initial game database state. (volund)
|
||||
- Added new Traits contrib, converted and expanded from Ainneve project.
|
||||
|
||||
|
||||
### Already in master
|
||||
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
|
||||
- `py` command now reroutes stdout to output results in-game client. `py`
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ from evennia.server.signals import (
|
|||
SIGNAL_OBJECT_POST_PUPPET,
|
||||
SIGNAL_OBJECT_POST_UNPUPPET,
|
||||
)
|
||||
from evennia.typeclasses.attributes import NickHandler
|
||||
from evennia.typeclasses.attributes import NickHandler, ModelAttributeBackend
|
||||
from evennia.scripts.scripthandler import ScriptHandler
|
||||
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||
from evennia.utils.optionhandler import OptionHandler
|
||||
|
|
@ -199,7 +199,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
@lazy_property
|
||||
def nicks(self):
|
||||
return NickHandler(self)
|
||||
return NickHandler(self, ModelAttributeBackend)
|
||||
|
||||
@lazy_property
|
||||
def sessions(self):
|
||||
|
|
@ -275,11 +275,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
raise RuntimeError("Session not found")
|
||||
if self.get_puppet(session) == obj:
|
||||
# already puppeting this object
|
||||
self.msg("You are already puppeting this object.")
|
||||
self.msg(_("You are already puppeting this object."))
|
||||
return
|
||||
if not obj.access(self, "puppet"):
|
||||
# no access
|
||||
self.msg(f"You don't have permission to puppet '{obj.key}'.")
|
||||
self.msg(_("You don't have permission to puppet '{key}'.").format(key=obj.key))
|
||||
return
|
||||
if obj.account:
|
||||
# object already puppeted
|
||||
|
|
@ -295,12 +295,12 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
else:
|
||||
txt1 = f"Taking over |c{obj.name}|n from another of your sessions."
|
||||
txt2 = f"|c{obj.name}|n|R is now acted from another of your sessions.|n"
|
||||
self.msg(txt1, session=session)
|
||||
self.msg(txt2, session=obj.sessions.all())
|
||||
self.msg(_(txt1), session=session)
|
||||
self.msg(_(txt2), session=obj.sessions.all())
|
||||
self.unpuppet_object(obj.sessions.get())
|
||||
elif obj.account.is_connected:
|
||||
# controlled by another account
|
||||
self.msg(f"|c{obj.key}|R is already puppeted by another Account.")
|
||||
self.msg(_("|c{key}|R is already puppeted by another Account.").format(key=obj.key))
|
||||
return
|
||||
|
||||
# do the puppeting
|
||||
|
|
@ -496,7 +496,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
# See if authentication is currently being throttled
|
||||
if ip and LOGIN_THROTTLE.check(ip):
|
||||
errors.append("Too many login failures; please try again in a few minutes.")
|
||||
errors.append(_("Too many login failures; please try again in a few minutes."))
|
||||
|
||||
# With throttle active, do not log continued hits-- it is a
|
||||
# waste of storage and can be abused to make your logs harder to
|
||||
|
|
@ -508,8 +508,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
if banned:
|
||||
# this is a banned IP or name!
|
||||
errors.append(
|
||||
"|rYou have been banned and cannot continue from here."
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
_(
|
||||
"|rYou have been banned and cannot continue from here."
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
)
|
||||
)
|
||||
logger.log_sec(f"Authentication Denied (Banned): {username} (IP: {ip}).")
|
||||
LOGIN_THROTTLE.update(ip, "Too many sightings of banned artifact.")
|
||||
|
|
@ -519,7 +521,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
account = authenticate(username=username, password=password)
|
||||
if not account:
|
||||
# User-facing message
|
||||
errors.append("Username and/or password is incorrect.")
|
||||
errors.append(_("Username and/or password is incorrect."))
|
||||
|
||||
# Log auth failures while throttle is inactive
|
||||
logger.log_sec(f"Authentication Failure: {username} (IP: {ip}).")
|
||||
|
|
@ -688,7 +690,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
ip = kwargs.get("ip", "")
|
||||
if ip and CREATION_THROTTLE.check(ip):
|
||||
errors.append(
|
||||
"You are creating too many accounts. Please log into an existing account."
|
||||
_("You are creating too many accounts. Please log into an existing account.")
|
||||
)
|
||||
return None, errors
|
||||
|
||||
|
|
@ -716,7 +718,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
banned = cls.is_banned(username=username, ip=ip)
|
||||
if banned:
|
||||
# this is a banned IP or name!
|
||||
string = (
|
||||
string = _(
|
||||
"|rYou have been banned and cannot continue from here."
|
||||
"\nIf you feel this ban is in error, please email an admin.|x"
|
||||
)
|
||||
|
|
@ -733,7 +735,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
except Exception as e:
|
||||
errors.append(
|
||||
"There was an error creating the Account. If this problem persists, contact an admin."
|
||||
_(
|
||||
"There was an error creating the Account. If this problem persists, contact an admin."
|
||||
)
|
||||
)
|
||||
logger.log_trace()
|
||||
return None, errors
|
||||
|
|
@ -785,7 +789,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
# 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.
|
||||
errors.append("An error occurred. Please e-mail an admin if the problem persists.")
|
||||
errors.append(_("An error occurred. Please e-mail an admin if the problem persists."))
|
||||
logger.log_trace()
|
||||
|
||||
# Update the throttle to indicate a new account was created from this IP
|
||||
|
|
@ -1253,21 +1257,21 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
if session:
|
||||
session.msg(logged_in={})
|
||||
|
||||
self._send_to_connect_channel(f"|G{self.key} connected|n")
|
||||
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 conneted object, if any
|
||||
try:
|
||||
self.puppet_object(session, self.db._last_puppet)
|
||||
except RuntimeError:
|
||||
self.msg("The Character does not exist.")
|
||||
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.")
|
||||
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
|
||||
|
|
@ -1305,7 +1309,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
"""
|
||||
reason = f" ({reason if reason else ''})"
|
||||
self._send_to_connect_channel(f"|R{self.key} disconnected{reason}|n")
|
||||
self._send_to_connect_channel(
|
||||
_("|R{key} disconnected{reason}|n").format(key=self.key, reason=reason)
|
||||
)
|
||||
|
||||
def at_post_disconnect(self, **kwargs):
|
||||
"""
|
||||
|
|
@ -1411,7 +1417,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
if hasattr(target, "return_appearance"):
|
||||
return target.return_appearance(self)
|
||||
else:
|
||||
return "{} has no in-game appearance.".format(target)
|
||||
return _("{target} has no in-game appearance.").format(target=target)
|
||||
else:
|
||||
# list of targets - make list to disconnect from db
|
||||
characters = list(tar for tar in target if tar) if target else []
|
||||
|
|
@ -1454,7 +1460,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
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."
|
||||
_(
|
||||
"\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one."
|
||||
)
|
||||
)
|
||||
else:
|
||||
result.append("\n |w@charcreate <name> [=description]|n - create new character")
|
||||
|
|
@ -1534,7 +1542,7 @@ class DefaultGuest(DefaultAccount):
|
|||
|
||||
# check if guests are enabled.
|
||||
if not settings.GUEST_ENABLED:
|
||||
errors.append("Guest accounts are not enabled on this server.")
|
||||
errors.append(_("Guest accounts are not enabled on this server."))
|
||||
return None, errors
|
||||
|
||||
try:
|
||||
|
|
@ -1544,7 +1552,7 @@ class DefaultGuest(DefaultAccount):
|
|||
username = name
|
||||
break
|
||||
if not username:
|
||||
errors.append("All guest accounts are in use. Please try again later.")
|
||||
errors.append(_("All guest accounts are in use. Please try again later."))
|
||||
if ip:
|
||||
LOGIN_THROTTLE.update(ip, "Too many requests for Guest access.")
|
||||
return None, errors
|
||||
|
|
@ -1572,7 +1580,7 @@ class DefaultGuest(DefaultAccount):
|
|||
# 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.
|
||||
errors.append("An error occurred. Please e-mail an admin if the problem persists.")
|
||||
errors.append(_("An error occurred. Please e-mail an admin if the problem persists."))
|
||||
logger.log_trace()
|
||||
return None, errors
|
||||
|
||||
|
|
@ -1589,7 +1597,7 @@ class DefaultGuest(DefaultAccount):
|
|||
overriding the call (unused by default).
|
||||
|
||||
"""
|
||||
self._send_to_connect_channel(f"|G{self.key} connected|n")
|
||||
self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key))
|
||||
self.puppet_object(session, self.db._last_puppet)
|
||||
|
||||
def at_server_shutdown(self):
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from evennia.accounts.accounts import DefaultAccount
|
|||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.utils import search
|
||||
from evennia.utils import utils
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
|
||||
|
|
@ -328,7 +329,9 @@ class IRCBot(Bot):
|
|||
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
|
||||
nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower()))
|
||||
for obj in self._nicklist_callers:
|
||||
obj.msg(f"Nicks at {chstr}:\n {nicklist}")
|
||||
obj.msg(
|
||||
_("Nicks at {chstr}:\n {nicklist}").format(chstr=chstr, nicklist=nicklist)
|
||||
)
|
||||
self._nicklist_callers = []
|
||||
return
|
||||
|
||||
|
|
@ -337,7 +340,11 @@ class IRCBot(Bot):
|
|||
if hasattr(self, "_ping_callers") and self._ping_callers:
|
||||
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
|
||||
for obj in self._ping_callers:
|
||||
obj.msg(f"IRC ping return from {chstr} took {kwargs['timing']}s.")
|
||||
obj.msg(
|
||||
_("IRC ping return from {chstr} took {time}s.").format(
|
||||
chstr=chstr, time=kwargs["timing"]
|
||||
)
|
||||
)
|
||||
self._ping_callers = []
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -743,7 +743,9 @@ def cmdhandler(
|
|||
sysarg = raw_string
|
||||
else:
|
||||
# fallback to default error text
|
||||
sysarg = _("Command '%s' is not available.") % raw_string
|
||||
sysarg = _("Command '{command}' is not available.").format(
|
||||
command=raw_string
|
||||
)
|
||||
suggestions = string_suggestions(
|
||||
raw_string,
|
||||
cmdset.get_all_cmd_keys_and_aliases(caller),
|
||||
|
|
@ -751,8 +753,8 @@ def cmdhandler(
|
|||
maxnum=3,
|
||||
)
|
||||
if suggestions:
|
||||
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(
|
||||
suggestions, _("or"), addquote=True
|
||||
sysarg += _(" Maybe you meant {command}?").format(
|
||||
command=utils.list_to_string(suggestions, _("or"), addquote=True)
|
||||
)
|
||||
else:
|
||||
sysarg += _(' Type "help" for help.')
|
||||
|
|
|
|||
|
|
@ -184,7 +184,9 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
raise exc.with_traceback(tb)
|
||||
else:
|
||||
# try next suggested path
|
||||
errstring += _("\n(Unsuccessfully tried '%s')." % python_path)
|
||||
errstring += _("\n(Unsuccessfully tried '{path}').").format(
|
||||
path=python_path
|
||||
)
|
||||
continue
|
||||
try:
|
||||
cmdsetclass = getattr(module, classname)
|
||||
|
|
@ -194,7 +196,9 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
dum, dum, tb = sys.exc_info()
|
||||
raise exc.with_traceback(tb)
|
||||
else:
|
||||
errstring += _("\n(Unsuccessfully tried '%s')." % python_path)
|
||||
errstring += _("\n(Unsuccessfully tried '{path}').").format(
|
||||
path=python_path
|
||||
)
|
||||
continue
|
||||
_CACHED_CMDSETS[python_path] = cmdsetclass
|
||||
|
||||
|
|
|
|||
|
|
@ -119,17 +119,17 @@ class ChannelCommand(command.Command):
|
|||
caller = caller if not hasattr(caller, "account") else caller.account
|
||||
unmuted = channel.unmute(caller)
|
||||
if unmuted:
|
||||
self.msg("You start listening to %s." % channel)
|
||||
self.msg(_("You start listening to %s.") % channel)
|
||||
return
|
||||
self.msg("You were already listening to %s." % channel)
|
||||
self.msg(_("You were already listening to %s.") % channel)
|
||||
return
|
||||
if msg == "off":
|
||||
caller = caller if not hasattr(caller, "account") else caller.account
|
||||
muted = channel.mute(caller)
|
||||
if muted:
|
||||
self.msg("You stop listening to %s." % channel)
|
||||
self.msg(_("You stop listening to %s.") % channel)
|
||||
return
|
||||
self.msg("You were already not listening to %s." % channel)
|
||||
self.msg(_("You were already not listening to %s.") % channel)
|
||||
return
|
||||
if self.history_start is not None:
|
||||
# Try to view history
|
||||
|
|
@ -144,7 +144,7 @@ class ChannelCommand(command.Command):
|
|||
else:
|
||||
caller = caller if not hasattr(caller, "account") else caller.account
|
||||
if caller in channel.mutelist:
|
||||
self.msg("You currently have %s muted." % channel)
|
||||
self.msg(_("You currently have %s muted.") % channel)
|
||||
return
|
||||
channel.msg(msg, senders=self.caller, online=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ class CmdCreatePuzzleRecipe(MuxCommand):
|
|||
proto_parts = [proto_def(obj) for obj in parts]
|
||||
proto_results = [proto_def(obj) for obj in results]
|
||||
|
||||
puzzle = create_script(PuzzleRecipe, key=puzzle_name)
|
||||
puzzle = create_script(PuzzleRecipe, key=puzzle_name, persistent=True)
|
||||
puzzle.save_recipe(puzzle_name, proto_parts, proto_results)
|
||||
puzzle.locks.add("control:id(%s) or perm(Builder)" % caller.dbref[1:])
|
||||
|
||||
|
|
@ -488,7 +488,7 @@ class CmdArmPuzzle(MuxCommand):
|
|||
|
||||
Notes:
|
||||
Create puzzles with `@puzzle`; get list of
|
||||
defined puzzles using `@lspuzlerecipies`.
|
||||
defined puzzles using `@lspuzzlerecipes`.
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -131,7 +131,9 @@ class HelpEntryManager(TypedObjectManager):
|
|||
for topic in topics:
|
||||
topic.help_category = default_category
|
||||
topic.save()
|
||||
string = "Help database moved to category %s" % default_category
|
||||
string = _("Help database moved to category {default_category}").format(
|
||||
default_category=default_category
|
||||
)
|
||||
logger.log_info(string)
|
||||
|
||||
def search_help(self, ostring, help_category=None):
|
||||
|
|
|
|||
BIN
evennia/locale/ru/LC_MESSAGES/django.mo
Normal file
BIN
evennia/locale/ru/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
299
evennia/locale/ru/LC_MESSAGES/django.po
Normal file
299
evennia/locale/ru/LC_MESSAGES/django.po
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Evennia Russian Translation v0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-02-20 12:13+0000\n"
|
||||
"PO-Revision-Date: 2020-04-19 18:32+0000\n"
|
||||
"Last-Translator: 3eluk\n"
|
||||
"Language-Team: Russian (Russia)\n"
|
||||
"Language: ru-RU\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10 >= 2 && "
|
||||
"n%10<=4 &&(n%100<10||n%100 >= 20)? 1 : 2);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Loco-Source-Locale: ru_RU\n"
|
||||
"X-Generator: Loco https://localise.biz/\n"
|
||||
"X-Loco-Parser: loco_parse_po"
|
||||
|
||||
#: accounts/accounts.py:440
|
||||
msgid "Account being deleted."
|
||||
msgstr "Аккаунт удаляется."
|
||||
|
||||
#: commands/cmdhandler.py:681
|
||||
msgid "There were multiple matches."
|
||||
msgstr "Здесь было несколько совпадений."
|
||||
|
||||
#: commands/cmdhandler.py:704
|
||||
#, python-format
|
||||
msgid "Command '%s' is not available."
|
||||
msgstr "Команда '%s' недоступна."
|
||||
|
||||
#: commands/cmdhandler.py:709
|
||||
#, python-format
|
||||
msgid " Maybe you meant %s?"
|
||||
msgstr "Возможно, вы имели ввиду %s?"
|
||||
|
||||
#: commands/cmdhandler.py:709
|
||||
msgid "or"
|
||||
msgstr "или"
|
||||
|
||||
#: commands/cmdhandler.py:711
|
||||
msgid " Type \"help\" for help."
|
||||
msgstr " Введи \"справка\" для получения помощи."
|
||||
|
||||
#: commands/cmdsethandler.py:89
|
||||
msgid ""
|
||||
"{traceback}\n"
|
||||
"Error loading cmdset '{path}'\n"
|
||||
"(Traceback was logged {timestamp})"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:94
|
||||
msgid ""
|
||||
"Error loading cmdset: No cmdset class '{classname}' in '{path}'.\n"
|
||||
"(Traceback was logged {timestamp})"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:98
|
||||
msgid ""
|
||||
"{traceback}\n"
|
||||
"SyntaxError encountered when loading cmdset '{path}'.\n"
|
||||
"(Traceback was logged {timestamp})"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:103
|
||||
msgid ""
|
||||
"{traceback}\n"
|
||||
"Compile/Run error when loading cmdset '{path}'.\",\n"
|
||||
"(Traceback was logged {timestamp})"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:108
|
||||
msgid ""
|
||||
"\n"
|
||||
"Error encountered for cmdset at path '{path}'.\n"
|
||||
"Replacing with fallback '{fallback_path}'.\n"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:114
|
||||
msgid "Fallback path '{fallback_path}' failed to generate a cmdset."
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:182 commands/cmdsethandler.py:192
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
"(Unsuccessfully tried '%s')."
|
||||
msgstr ""
|
||||
"\n"
|
||||
"(Безуспешно пробую '%s')."
|
||||
|
||||
#: commands/cmdsethandler.py:311
|
||||
msgid "custom {mergetype} on cmdset '{cmdset}'"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:314
|
||||
msgid " <Merged {mergelist} {mergetype}, prio {prio}>: {current}"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:322
|
||||
msgid ""
|
||||
" <{key} ({mergetype}, prio {prio}, {permstring})>:\n"
|
||||
" {keylist}"
|
||||
msgstr ""
|
||||
|
||||
#: commands/cmdsethandler.py:426
|
||||
msgid "Only CmdSets can be added to the cmdsethandler!"
|
||||
msgstr ""
|
||||
|
||||
#: comms/channelhandler.py:100
|
||||
msgid "Say what?"
|
||||
msgstr "Сказать что?"
|
||||
|
||||
#: comms/channelhandler.py:105
|
||||
#, python-format
|
||||
msgid "Channel '%s' not found."
|
||||
msgstr "Канал '%s' не обнаружен."
|
||||
|
||||
#: comms/channelhandler.py:108
|
||||
#, python-format
|
||||
msgid "You are not connected to channel '%s'."
|
||||
msgstr "Ты не соединён с каналом '%s'."
|
||||
|
||||
#: comms/channelhandler.py:112
|
||||
#, python-format
|
||||
msgid "You are not permitted to send to channel '%s'."
|
||||
msgstr "У тебя нет разрешения слать в канал '%s'."
|
||||
|
||||
#: comms/channelhandler.py:155
|
||||
msgid " (channel)"
|
||||
msgstr " (канал)"
|
||||
|
||||
#: locks/lockhandler.py:236
|
||||
#, python-format
|
||||
msgid "Lock: lock-function '%s' is not available."
|
||||
msgstr ""
|
||||
|
||||
#: locks/lockhandler.py:249
|
||||
#, python-format
|
||||
msgid "Lock: definition '%s' has syntax errors."
|
||||
msgstr ""
|
||||
|
||||
#: locks/lockhandler.py:253
|
||||
#, python-format
|
||||
msgid ""
|
||||
"LockHandler on %(obj)s: access type '%(access_type)s' changed from '%(source)"
|
||||
"s' to '%(goal)s' "
|
||||
msgstr ""
|
||||
|
||||
#: locks/lockhandler.py:320
|
||||
msgid "Lock: '{lockdef}' contains no colon (:)."
|
||||
msgstr ""
|
||||
|
||||
#: locks/lockhandler.py:328
|
||||
msgid "Lock: '{lockdef}' has no access_type (left-side of colon is empty)."
|
||||
msgstr ""
|
||||
|
||||
#: locks/lockhandler.py:336
|
||||
msgid "Lock: '{lockdef}' has mismatched parentheses."
|
||||
msgstr ""
|
||||
|
||||
#: locks/lockhandler.py:343
|
||||
msgid "Lock: '{lockdef}' has no valid lock functions."
|
||||
msgstr ""
|
||||
|
||||
#: objects/objects.py:732
|
||||
#, python-format
|
||||
msgid "Couldn't perform move ('%s'). Contact an admin."
|
||||
msgstr "Не удалось выполнить действие ('%s'). Свяжитесь с администратором."
|
||||
|
||||
#: objects/objects.py:742
|
||||
msgid "The destination doesn't exist."
|
||||
msgstr "Такой точки назначения нету."
|
||||
|
||||
#: objects/objects.py:833
|
||||
#, python-format
|
||||
msgid "Could not find default home '(#%d)'."
|
||||
msgstr "Не обнаружен дом по умолчанию '(#%d)'."
|
||||
|
||||
#: objects/objects.py:849
|
||||
msgid "Something went wrong! You are dumped into nowhere. Contact an admin."
|
||||
msgstr ""
|
||||
"Что-то пошло не так! Тебя выбрасывает в пустоту. Свяжитесь с администратором."
|
||||
|
||||
#: objects/objects.py:915
|
||||
#, python-format
|
||||
msgid "Your character %s has been destroyed."
|
||||
msgstr "Ваш персонаж %s был уничтожен."
|
||||
|
||||
#: scripts/scripthandler.py:53
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
" '%(key)s' (%(next_repeat)s/%(interval)s, %(repeats)s repeats): %(desc)s"
|
||||
msgstr ""
|
||||
|
||||
#: scripts/scripts.py:205
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Script %(key)s(#%(dbid)s) of type '%(cname)s': at_repeat() error '%(err)s'."
|
||||
msgstr ""
|
||||
|
||||
#: server/initial_setup.py:28
|
||||
msgid ""
|
||||
"\n"
|
||||
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
|
||||
"you need\n"
|
||||
"help, want to contribute, report issues or just join the community.\n"
|
||||
"As Account #1 you can create a demo/tutorial area with |w@batchcommand "
|
||||
"tutorial_world.build|n.\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"Добро пожаловать в твою новую игру, основанную на |wEvennia|n! Посети http:"
|
||||
"//www.evennia.com\n"
|
||||
"если тебе нужна помощь, хочешь помочь, сообщить об ошибках, lили просто "
|
||||
"присоединиться к сообществу.\n"
|
||||
"Как Аккаунт №1, ты можешь создать зону для демонстрации/обучения командой "
|
||||
"|w@batchcommand tutorial_world.build|n.\n"
|
||||
" "
|
||||
|
||||
#: server/initial_setup.py:92
|
||||
msgid "This is User #1."
|
||||
msgstr "Это Пользователь №1."
|
||||
|
||||
#: server/initial_setup.py:105
|
||||
msgid "Limbo"
|
||||
msgstr "Лимб"
|
||||
|
||||
#: server/server.py:139
|
||||
msgid "idle timeout exceeded"
|
||||
msgstr "время бездействия превышено"
|
||||
|
||||
#: server/sessionhandler.py:386
|
||||
msgid " ... Server restarted."
|
||||
msgstr " ... Сервер перезапущен."
|
||||
|
||||
#: server/sessionhandler.py:606
|
||||
msgid "Logged in from elsewhere. Disconnecting."
|
||||
msgstr "Выполнено соединение в другом месте. Отключение."
|
||||
|
||||
#: server/sessionhandler.py:634
|
||||
msgid "Idle timeout exceeded, disconnecting."
|
||||
msgstr "Время бездействия превышено, отключение."
|
||||
|
||||
#: server/validators.py:50
|
||||
#, python-format
|
||||
msgid ""
|
||||
"%s From a terminal client, you can also use a phrase of multiple words if "
|
||||
"you enclose the password in double quotes."
|
||||
msgstr ""
|
||||
"%s Если вы используете терминал, вы можете использовать фразу из нескольких "
|
||||
"слов если возьмёте пароль в двойные скобки."
|
||||
|
||||
#: utils/evmenu.py:192
|
||||
msgid ""
|
||||
"Menu node '{nodename}' is either not implemented or caused an error. Make "
|
||||
"another choice."
|
||||
msgstr ""
|
||||
|
||||
#: utils/evmenu.py:194
|
||||
msgid "Error in menu node '{nodename}'."
|
||||
msgstr ""
|
||||
|
||||
#: utils/evmenu.py:195
|
||||
msgid "No description."
|
||||
msgstr "Нет описания."
|
||||
|
||||
#: utils/evmenu.py:196
|
||||
msgid "Commands: <menu option>, help, quit"
|
||||
msgstr "Команды: <menu option>, справка, выход"
|
||||
|
||||
#: utils/evmenu.py:197
|
||||
msgid "Commands: <menu option>, help"
|
||||
msgstr "Команды: <menu option>, справка"
|
||||
|
||||
#: utils/evmenu.py:198
|
||||
msgid "Commands: help, quit"
|
||||
msgstr ""
|
||||
|
||||
#: utils/evmenu.py:199
|
||||
msgid "Commands: help"
|
||||
msgstr "Команды: справка"
|
||||
|
||||
#: utils/evmenu.py:200
|
||||
msgid "Choose an option or try 'help'."
|
||||
msgstr "Выберите опцию или введите \"справка\"."
|
||||
|
||||
#: utils/utils.py:1866
|
||||
#, python-format
|
||||
msgid "Could not find '%s'."
|
||||
msgstr "Не обнаружено '%s'."
|
||||
|
||||
#: utils/utils.py:1873
|
||||
#, python-format
|
||||
msgid ""
|
||||
"More than one match for '%s' (please narrow target):\n"
|
||||
msgstr ""
|
||||
"Больше одного подходящего варианта для '%s' (уточните цель):\n"
|
||||
|
|
@ -246,7 +246,11 @@ class LockHandler(object):
|
|||
evalstring = " ".join(_RE_OK.findall(evalstring))
|
||||
eval(evalstring % tuple(True for func in funclist), {}, {})
|
||||
except Exception:
|
||||
elist.append(_("Lock: definition '%s' has syntax errors.") % raw_lockstring)
|
||||
elist.append(
|
||||
_("Lock: definition '{lock_string}' has syntax errors.").format(
|
||||
lock_string=raw_lockstring
|
||||
)
|
||||
)
|
||||
continue
|
||||
if access_type in locks:
|
||||
duplicates += 1
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from django.contrib import admin
|
|||
from evennia.typeclasses.admin import AttributeInline, TagInline
|
||||
from evennia.objects.models import ObjectDB
|
||||
from django.contrib.admin.utils import flatten_fieldsets
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
class ObjectAttributeInline(AttributeInline):
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ class ObjectDBManager(TypedObjectManager):
|
|||
typeclasses (list, optional): Python pats to restrict matches with.
|
||||
|
||||
Returns:
|
||||
matches (list): Objects fullfilling both the `attribute_name` and
|
||||
matches (query): Objects fullfilling both the `attribute_name` and
|
||||
`attribute_value` criterions.
|
||||
|
||||
Notes:
|
||||
|
|
@ -273,7 +273,7 @@ class ObjectDBManager(TypedObjectManager):
|
|||
to exclude from the match.
|
||||
|
||||
Returns:
|
||||
contents (list): Matching contents, without excludeobj, if given.
|
||||
contents (query): Matching contents, without excludeobj, if given.
|
||||
"""
|
||||
exclude_restriction = (
|
||||
Q(pk__in=[_GA(obj, "id") for obj in make_iter(excludeobj)]) if excludeobj else Q()
|
||||
|
|
@ -291,7 +291,7 @@ class ObjectDBManager(TypedObjectManager):
|
|||
typeclasses (list): Only match objects with typeclasses having thess path strings.
|
||||
|
||||
Returns:
|
||||
matches (list): A list of matches of length 0, 1 or more.
|
||||
matches (query): A list of matches of length 0, 1 or more.
|
||||
"""
|
||||
if not isinstance(ostring, str):
|
||||
if hasattr(ostring, "key"):
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from collections import defaultdict
|
|||
from django.conf import settings
|
||||
|
||||
from evennia.typeclasses.models import TypeclassBase
|
||||
from evennia.typeclasses.attributes import NickHandler
|
||||
from evennia.typeclasses.attributes import NickHandler, ModelAttributeBackend
|
||||
from evennia.objects.manager import ObjectManager
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.scripts.scripthandler import ScriptHandler
|
||||
|
|
@ -225,7 +225,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
@lazy_property
|
||||
def nicks(self):
|
||||
return NickHandler(self)
|
||||
return NickHandler(self, ModelAttributeBackend)
|
||||
|
||||
@lazy_property
|
||||
def sessions(self):
|
||||
|
|
@ -503,7 +503,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
)
|
||||
|
||||
if quiet:
|
||||
return results
|
||||
return list(results)
|
||||
return _AT_SEARCH_RESULT(
|
||||
results,
|
||||
self,
|
||||
|
|
@ -1059,7 +1059,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# See if we need to kick the account off.
|
||||
|
||||
for session in self.sessions.all():
|
||||
session.msg(_("Your character %s has been destroyed.") % self.key)
|
||||
session.msg(_("Your character {key} has been destroyed.").format(key=self.key))
|
||||
# no need to disconnect, Account just jumps to OOC mode.
|
||||
# sever the connection (important!)
|
||||
if self.account:
|
||||
|
|
|
|||
|
|
@ -2262,7 +2262,7 @@ def main():
|
|||
if option in ("makemessages", "compilemessages"):
|
||||
# some commands don't require the presence of a game directory to work
|
||||
need_gamedir = False
|
||||
if option in ("shell", "check", "makemigrations"):
|
||||
if option in ("shell", "check", "makemigrations", "createsuperuser"):
|
||||
# some django commands requires the database to exist,
|
||||
# or evennia._init to have run before they work right.
|
||||
check_db = True
|
||||
|
|
|
|||
|
|
@ -465,11 +465,16 @@ def getKeyPair(pubkeyfile, privkeyfile):
|
|||
|
||||
if not (os.path.exists(pubkeyfile) and os.path.exists(privkeyfile)):
|
||||
# No keypair exists. Generate a new RSA keypair
|
||||
from Crypto.PublicKey import RSA
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
|
||||
rsa_key = Key(RSA.generate(_KEY_LENGTH))
|
||||
public_key_string = rsa_key.public().toString(type="OPENSSH")
|
||||
private_key_string = rsa_key.toString(type="OPENSSH")
|
||||
rsa_key = Key(
|
||||
rsa.generate_private_key(
|
||||
public_exponent=65537, key_size=_KEY_LENGTH, backend=default_backend()
|
||||
)
|
||||
)
|
||||
public_key_string = rsa_key.public().toString(type="OPENSSH").decode()
|
||||
private_key_string = rsa_key.toString(type="OPENSSH").decode()
|
||||
|
||||
# save keys for the future.
|
||||
with open(privkeyfile, "wt") as pfile:
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import django
|
|||
django.setup()
|
||||
|
||||
import evennia
|
||||
import importlib
|
||||
|
||||
evennia._init()
|
||||
|
||||
|
|
@ -31,7 +32,6 @@ from django.conf import settings
|
|||
from evennia.accounts.models import AccountDB
|
||||
from evennia.scripts.models import ScriptDB
|
||||
from evennia.server.models import ServerConfig
|
||||
from evennia.server import initial_setup
|
||||
|
||||
from evennia.utils.utils import get_evennia_version, mod_import, make_iter
|
||||
from evennia.utils import logger
|
||||
|
|
@ -105,6 +105,7 @@ _IDMAPPER_CACHE_MAXSIZE = settings.IDMAPPER_CACHE_MAXSIZE
|
|||
_GAMETIME_MODULE = None
|
||||
|
||||
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
_LAST_SERVER_TIME_SNAPSHOT = 0
|
||||
|
||||
|
||||
def _server_maintenance():
|
||||
|
|
@ -113,6 +114,8 @@ def _server_maintenance():
|
|||
the server needs to do. It is called every minute.
|
||||
"""
|
||||
global EVENNIA, _MAINTENANCE_COUNT, _FLUSH_CACHE, _GAMETIME_MODULE
|
||||
global _LAST_SERVER_TIME_SNAPSHOT
|
||||
|
||||
if not _FLUSH_CACHE:
|
||||
from evennia.utils.idmapper.models import conditional_flush as _FLUSH_CACHE
|
||||
if not _GAMETIME_MODULE:
|
||||
|
|
@ -125,8 +128,13 @@ def _server_maintenance():
|
|||
# first call after a reload
|
||||
_GAMETIME_MODULE.SERVER_START_TIME = now
|
||||
_GAMETIME_MODULE.SERVER_RUNTIME = ServerConfig.objects.conf("runtime", default=0.0)
|
||||
_LAST_SERVER_TIME_SNAPSHOT = now
|
||||
else:
|
||||
_GAMETIME_MODULE.SERVER_RUNTIME += 60.0
|
||||
# adjust the runtime not with 60s but with the actual elapsed time
|
||||
# in case this may varies slightly from 60s.
|
||||
_GAMETIME_MODULE.SERVER_RUNTIME += (now - _LAST_SERVER_TIME_SNAPSHOT)
|
||||
_LAST_SERVER_TIME_SNAPSHOT = now
|
||||
|
||||
# update game time and save it across reloads
|
||||
_GAMETIME_MODULE.SERVER_RUNTIME_LAST_UPDATED = now
|
||||
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.SERVER_RUNTIME)
|
||||
|
|
@ -333,6 +341,7 @@ class Evennia(object):
|
|||
Once finished the last_initial_setup_step is set to -1.
|
||||
"""
|
||||
global INFO_DICT
|
||||
initial_setup = importlib.import_module(settings.INITIAL_SETUP_MODULE)
|
||||
last_initial_setup_step = ServerConfig.objects.conf("last_initial_setup_step")
|
||||
if not last_initial_setup_step:
|
||||
# None is only returned if the config does not exist,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ connection actually happens (so it's the same for telnet, web, ssh etc).
|
|||
It is stored on the Server side (as opposed to protocol-specific sessions which
|
||||
are stored on the Portal side)
|
||||
"""
|
||||
import weakref
|
||||
import time
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
|
@ -16,6 +15,7 @@ from evennia.utils.utils import make_iter, lazy_property
|
|||
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||
from evennia.server.session import Session
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
from evennia.typeclasses.attributes import AttributeHandler, InMemoryAttributeBackend, DbHolder
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
|
|
@ -25,124 +25,6 @@ _ANSI = None
|
|||
# i18n
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# Handlers for Session.db/ndb operation
|
||||
|
||||
|
||||
class NDbHolder(object):
|
||||
"""Holder for allowing property access of attributes"""
|
||||
|
||||
def __init__(self, obj, name, manager_name="attributes"):
|
||||
_SA(self, name, _GA(obj, manager_name))
|
||||
_SA(self, "name", name)
|
||||
|
||||
def __getattribute__(self, attrname):
|
||||
if attrname == "all":
|
||||
# we allow to overload our default .all
|
||||
attr = _GA(self, _GA(self, "name")).get("all")
|
||||
return attr if attr else _GA(self, "all")
|
||||
return _GA(self, _GA(self, "name")).get(attrname)
|
||||
|
||||
def __setattr__(self, attrname, value):
|
||||
_GA(self, _GA(self, "name")).add(attrname, value)
|
||||
|
||||
def __delattr__(self, attrname):
|
||||
_GA(self, _GA(self, "name")).remove(attrname)
|
||||
|
||||
def get_all(self):
|
||||
return _GA(self, _GA(self, "name")).all()
|
||||
|
||||
all = property(get_all)
|
||||
|
||||
|
||||
class NAttributeHandler(object):
|
||||
"""
|
||||
NAttributeHandler version without recache protection.
|
||||
This stand-alone handler manages non-database saving.
|
||||
It is similar to `AttributeHandler` and is used
|
||||
by the `.ndb` handler in the same way as `.db` does
|
||||
for the `AttributeHandler`.
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
"""
|
||||
Initialized on the object
|
||||
"""
|
||||
self._store = {}
|
||||
self.obj = weakref.proxy(obj)
|
||||
|
||||
def has(self, key):
|
||||
"""
|
||||
Check if object has this attribute or not.
|
||||
|
||||
Args:
|
||||
key (str): The Nattribute key to check.
|
||||
|
||||
Returns:
|
||||
has_nattribute (bool): If Nattribute is set or not.
|
||||
|
||||
"""
|
||||
return key in self._store
|
||||
|
||||
def get(self, key, default=None):
|
||||
"""
|
||||
Get the named key value.
|
||||
|
||||
Args:
|
||||
key (str): The Nattribute key to get.
|
||||
|
||||
Returns:
|
||||
the value of the Nattribute.
|
||||
|
||||
"""
|
||||
return self._store.get(key, default)
|
||||
|
||||
def add(self, key, value):
|
||||
"""
|
||||
Add new key and value.
|
||||
|
||||
Args:
|
||||
key (str): The name of Nattribute to add.
|
||||
value (any): The value to store.
|
||||
|
||||
"""
|
||||
self._store[key] = value
|
||||
|
||||
def remove(self, key):
|
||||
"""
|
||||
Remove Nattribute from storage.
|
||||
|
||||
Args:
|
||||
key (str): The name of the Nattribute to remove.
|
||||
|
||||
"""
|
||||
if key in self._store:
|
||||
del self._store[key]
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Remove all NAttributes from handler.
|
||||
|
||||
"""
|
||||
self._store = {}
|
||||
|
||||
def all(self, return_tuples=False):
|
||||
"""
|
||||
List the contents of the handler.
|
||||
|
||||
Args:
|
||||
return_tuples (bool, optional): Defines if the Nattributes
|
||||
are returns as a list of keys or as a list of `(key, value)`.
|
||||
|
||||
Returns:
|
||||
nattributes (list): A list of keys `[key, key, ...]` or a
|
||||
list of tuples `[(key, value), ...]` depending on the
|
||||
setting of `return_tuples`.
|
||||
|
||||
"""
|
||||
if return_tuples:
|
||||
return [(key, value) for (key, value) in self._store.items() if not key.startswith("_")]
|
||||
return [key for key in self._store if not key.startswith("_")]
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
# Server Session
|
||||
|
|
@ -175,6 +57,10 @@ class ServerSession(Session):
|
|||
|
||||
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.sessid
|
||||
|
||||
def at_sync(self):
|
||||
"""
|
||||
This is called whenever a session has been resynced with the
|
||||
|
|
@ -490,7 +376,7 @@ class ServerSession(Session):
|
|||
|
||||
@lazy_property
|
||||
def nattributes(self):
|
||||
return NAttributeHandler(self)
|
||||
return AttributeHandler(self, InMemoryAttributeBackend)
|
||||
|
||||
@lazy_property
|
||||
def attributes(self):
|
||||
|
|
@ -508,7 +394,7 @@ class ServerSession(Session):
|
|||
try:
|
||||
return self._ndb_holder
|
||||
except AttributeError:
|
||||
self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes")
|
||||
self._ndb_holder = DbHolder(self, "nattrhandler", manager_name="nattributes")
|
||||
return self._ndb_holder
|
||||
|
||||
# @ndb.setter
|
||||
|
|
|
|||
|
|
@ -101,7 +101,9 @@ class Session(object):
|
|||
the keys given by self._attrs_to_sync.
|
||||
|
||||
"""
|
||||
return {attr: getattr(self, attr, None) for attr in settings.SESSION_SYNC_ATTRS}
|
||||
return {
|
||||
attr: getattr(self, attr) for attr in settings.SESSION_SYNC_ATTRS if hasattr(self, attr)
|
||||
}
|
||||
|
||||
def load_sync_data(self, sessdata):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ class TestServer(TestCase):
|
|||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=3600 - 1,
|
||||
_LAST_SERVER_TIME_SNAPSHOT=0,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
|
|
@ -84,6 +85,7 @@ class TestServer(TestCase):
|
|||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=3700 - 1,
|
||||
_LAST_SERVER_TIME_SNAPSHOT=0,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
|
|
@ -101,6 +103,7 @@ class TestServer(TestCase):
|
|||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=(3600 * 7) - 1,
|
||||
_LAST_SERVER_TIME_SNAPSHOT=0,
|
||||
ServerConfig=DEFAULT,
|
||||
) as mocks:
|
||||
mocks["connection"].close = MagicMock()
|
||||
|
|
@ -117,6 +120,7 @@ class TestServer(TestCase):
|
|||
connection=DEFAULT,
|
||||
_IDMAPPER_CACHE_MAXSIZE=1000,
|
||||
_MAINTENANCE_COUNT=(3600 * 7) - 1,
|
||||
_LAST_SERVER_TIME_SNAPSHOT=0,
|
||||
SESSIONS=DEFAULT,
|
||||
_IDLE_TIMEOUT=10,
|
||||
time=DEFAULT,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Master configuration file for Evennia.
|
|||
NOTE: NO MODIFICATIONS SHOULD BE MADE TO THIS FILE!
|
||||
|
||||
All settings changes should be done by copy-pasting the variable and
|
||||
its value to <gamedir>/conf/settings.py.
|
||||
its value to <gamedir>/server/conf/settings.py.
|
||||
|
||||
Hint: Don't copy&paste over more from this file than you actually want
|
||||
to change. Anything you don't copy&paste will thus retain its default
|
||||
|
|
@ -332,6 +332,10 @@ CONNECTION_SCREEN_MODULE = "server.conf.connection_screens"
|
|||
# cause issues with menu-logins and autoconnects since the menu will not have
|
||||
# started when the autoconnects starts sending menu commands.
|
||||
DELAY_CMD_LOGINSTART = 0.3
|
||||
# A module that must exist - this holds the instructions Evennia will use to
|
||||
# first prepare the database for use. Generally should not be changed. If this
|
||||
# cannot be imported, bad things will happen.
|
||||
INITIAL_SETUP_MODULE = "evennia.server.initial_setup"
|
||||
# An optional module that, if existing, must hold a function
|
||||
# named at_initial_setup(). This hook method can be used to customize
|
||||
# the server's initial setup sequence (the very first startup of the system).
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -31,7 +31,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
|
||||
# Attribute manager methods
|
||||
def get_attribute(
|
||||
self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None
|
||||
self, key=None, category=None, value=None, strvalue=None, obj=None, attrtype=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Return Attribute objects by key, by category, by value, by
|
||||
|
|
@ -55,6 +55,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
attrype (str, optional): An attribute-type to search for.
|
||||
By default this is either `None` (normal Attributes) or
|
||||
`"nick"`.
|
||||
kwargs (any): Currently unused. Reserved for future use.
|
||||
|
||||
Returns:
|
||||
attributes (list): The matching Attributes.
|
||||
|
|
@ -102,7 +103,9 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
key=key, category=category, value=value, strvalue=strvalue, obj=obj
|
||||
)
|
||||
|
||||
def get_by_attribute(self, key=None, category=None, value=None, strvalue=None, attrtype=None):
|
||||
def get_by_attribute(
|
||||
self, key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Return objects having attributes with the given key, category,
|
||||
value, strvalue or combination of those criteria.
|
||||
|
|
@ -122,6 +125,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
attrype (str, optional): An attribute-type to search for.
|
||||
By default this is either `None` (normal Attributes) or
|
||||
`"nick"`.
|
||||
kwargs (any): Currently unused. Reserved for future use.
|
||||
|
||||
Returns:
|
||||
obj (list): Objects having the matching Attributes.
|
||||
|
|
@ -488,12 +492,12 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
|||
def get_typeclass_totals(self, *args, **kwargs) -> object:
|
||||
"""
|
||||
Returns a queryset of typeclass composition statistics.
|
||||
|
||||
|
||||
Returns:
|
||||
qs (Queryset): A queryset of dicts containing the typeclass (name),
|
||||
qs (Queryset): A queryset of dicts containing the typeclass (name),
|
||||
the count of objects with that typeclass and a float representing
|
||||
the percentage of objects associated with the typeclass.
|
||||
|
||||
|
||||
"""
|
||||
return (
|
||||
self.values("db_typeclass_path")
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ from django.urls import reverse
|
|||
from django.utils.encoding import smart_str
|
||||
from django.utils.text import slugify
|
||||
|
||||
from evennia.typeclasses.attributes import Attribute, AttributeHandler, NAttributeHandler
|
||||
from evennia.typeclasses.attributes import Attribute, AttributeHandler, ModelAttributeBackend, InMemoryAttributeBackend
|
||||
from evennia.typeclasses.attributes import DbHolder
|
||||
from evennia.typeclasses.tags import Tag, TagHandler, AliasHandler, PermissionHandler
|
||||
|
||||
from evennia.utils.idmapper.models import SharedMemoryModel, SharedMemoryModelBase
|
||||
|
|
@ -121,33 +122,6 @@ class TypeclassBase(SharedMemoryModelBase):
|
|||
signals.pre_delete.connect(remove_attributes_on_delete, sender=new_class)
|
||||
return new_class
|
||||
|
||||
|
||||
class DbHolder(object):
|
||||
"Holder for allowing property access of attributes"
|
||||
|
||||
def __init__(self, obj, name, manager_name="attributes"):
|
||||
_SA(self, name, _GA(obj, manager_name))
|
||||
_SA(self, "name", name)
|
||||
|
||||
def __getattribute__(self, attrname):
|
||||
if attrname == "all":
|
||||
# we allow to overload our default .all
|
||||
attr = _GA(self, _GA(self, "name")).get("all")
|
||||
return attr if attr else _GA(self, "all")
|
||||
return _GA(self, _GA(self, "name")).get(attrname)
|
||||
|
||||
def __setattr__(self, attrname, value):
|
||||
_GA(self, _GA(self, "name")).add(attrname, value)
|
||||
|
||||
def __delattr__(self, attrname):
|
||||
_GA(self, _GA(self, "name")).remove(attrname)
|
||||
|
||||
def get_all(self):
|
||||
return _GA(self, _GA(self, "name")).all()
|
||||
|
||||
all = property(get_all)
|
||||
|
||||
|
||||
#
|
||||
# Main TypedObject abstraction
|
||||
#
|
||||
|
|
@ -301,7 +275,7 @@ class TypedObject(SharedMemoryModel):
|
|||
# initialize all handlers in a lazy fashion
|
||||
@lazy_property
|
||||
def attributes(self):
|
||||
return AttributeHandler(self)
|
||||
return AttributeHandler(self, ModelAttributeBackend)
|
||||
|
||||
@lazy_property
|
||||
def locks(self):
|
||||
|
|
@ -321,7 +295,7 @@ class TypedObject(SharedMemoryModel):
|
|||
|
||||
@lazy_property
|
||||
def nattributes(self):
|
||||
return NAttributeHandler(self)
|
||||
return AttributeHandler(self, InMemoryAttributeBackend)
|
||||
|
||||
class Meta(object):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -26,12 +26,12 @@ class TestAttributes(EvenniaTest):
|
|||
key = "testattr"
|
||||
value = "test attr value "
|
||||
self.obj1.attributes.add(key, value)
|
||||
self.assertFalse(self.obj1.attributes._cache)
|
||||
self.assertFalse(self.obj1.attributes.backend._cache)
|
||||
|
||||
self.assertEqual(self.obj1.attributes.get(key), value)
|
||||
self.obj1.db.testattr = value
|
||||
self.assertEqual(self.obj1.db.testattr, value)
|
||||
self.assertFalse(self.obj1.attributes._cache)
|
||||
self.assertFalse(self.obj1.attributes.backend._cache)
|
||||
|
||||
def test_weird_text_save(self):
|
||||
"test 'weird' text type (different in py2 vs py3)"
|
||||
|
|
|
|||
|
|
@ -501,6 +501,8 @@ def create_account(
|
|||
report_to (Object): An object with a msg() method to report
|
||||
errors to. If not given, errors will be logged.
|
||||
|
||||
Returns:
|
||||
Account: The newly created Account.
|
||||
Raises:
|
||||
ValueError: If `key` already exists in database.
|
||||
|
||||
|
|
|
|||
|
|
@ -205,27 +205,35 @@ help_entries = search_help
|
|||
# not the attribute object itself (this is usually what you want)
|
||||
|
||||
|
||||
def search_object_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
def search_object_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
return ObjectDB.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
||||
|
||||
def search_account_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
def search_account_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
return AccountDB.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
||||
|
||||
def search_script_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
def search_script_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
return ScriptDB.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
||||
|
||||
def search_channel_attribute(key=None, category=None, value=None, strvalue=None):
|
||||
def search_channel_attribute(
|
||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||
):
|
||||
return Channel.objects.get_by_attribute(
|
||||
key=key, category=category, value=value, strvalue=strvalue
|
||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -243,7 +251,7 @@ search_attribute_object = ObjectDB.objects.get_attribute
|
|||
# object itself (this is usually what you want)
|
||||
|
||||
|
||||
def search_object_by_tag(key=None, category=None):
|
||||
def search_object_by_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
"""
|
||||
Find object based on tag or category.
|
||||
|
||||
|
|
@ -252,6 +260,11 @@ def search_object_by_tag(key=None, category=None):
|
|||
category (str, optional): The category of tag
|
||||
to search for. If not set, uncategorized
|
||||
tags will be searched.
|
||||
tagtype (str, optional): 'type' of Tag, by default
|
||||
this is either `None` (a normal Tag), `alias` or
|
||||
`permission`. This always apply to all queried tags.
|
||||
kwargs (any): Other optional parameter that may be supported
|
||||
by the manager method.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Objects with tags matching
|
||||
|
|
@ -259,13 +272,13 @@ def search_object_by_tag(key=None, category=None):
|
|||
matches were found.
|
||||
|
||||
"""
|
||||
return ObjectDB.objects.get_by_tag(key=key, category=category)
|
||||
return ObjectDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
||||
search_tag = search_object_by_tag # this is the most common case
|
||||
|
||||
|
||||
def search_account_tag(key=None, category=None):
|
||||
def search_account_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
"""
|
||||
Find account based on tag or category.
|
||||
|
||||
|
|
@ -274,6 +287,11 @@ def search_account_tag(key=None, category=None):
|
|||
category (str, optional): The category of tag
|
||||
to search for. If not set, uncategorized
|
||||
tags will be searched.
|
||||
tagtype (str, optional): 'type' of Tag, by default
|
||||
this is either `None` (a normal Tag), `alias` or
|
||||
`permission`. This always apply to all queried tags.
|
||||
kwargs (any): Other optional parameter that may be supported
|
||||
by the manager method.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Accounts with tags matching
|
||||
|
|
@ -281,10 +299,10 @@ def search_account_tag(key=None, category=None):
|
|||
matches were found.
|
||||
|
||||
"""
|
||||
return AccountDB.objects.get_by_tag(key=key, category=category)
|
||||
return AccountDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
||||
def search_script_tag(key=None, category=None):
|
||||
def search_script_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
"""
|
||||
Find script based on tag or category.
|
||||
|
||||
|
|
@ -293,6 +311,11 @@ def search_script_tag(key=None, category=None):
|
|||
category (str, optional): The category of tag
|
||||
to search for. If not set, uncategorized
|
||||
tags will be searched.
|
||||
tagtype (str, optional): 'type' of Tag, by default
|
||||
this is either `None` (a normal Tag), `alias` or
|
||||
`permission`. This always apply to all queried tags.
|
||||
kwargs (any): Other optional parameter that may be supported
|
||||
by the manager method.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Scripts with tags matching
|
||||
|
|
@ -300,10 +323,10 @@ def search_script_tag(key=None, category=None):
|
|||
matches were found.
|
||||
|
||||
"""
|
||||
return ScriptDB.objects.get_by_tag(key=key, category=category)
|
||||
return ScriptDB.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
||||
def search_channel_tag(key=None, category=None):
|
||||
def search_channel_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||
"""
|
||||
Find channel based on tag or category.
|
||||
|
||||
|
|
@ -312,6 +335,11 @@ def search_channel_tag(key=None, category=None):
|
|||
category (str, optional): The category of tag
|
||||
to search for. If not set, uncategorized
|
||||
tags will be searched.
|
||||
tagtype (str, optional): 'type' of Tag, by default
|
||||
this is either `None` (a normal Tag), `alias` or
|
||||
`permission`. This always apply to all queried tags.
|
||||
kwargs (any): Other optional parameter that may be supported
|
||||
by the manager method.
|
||||
|
||||
Returns:
|
||||
matches (list): List of Channels with tags matching
|
||||
|
|
@ -319,7 +347,7 @@ def search_channel_tag(key=None, category=None):
|
|||
matches were found.
|
||||
|
||||
"""
|
||||
return Channel.objects.get_by_tag(key=key, category=category)
|
||||
return Channel.objects.get_by_tag(key=key, category=category, tagtype=tagtype, **kwargs)
|
||||
|
||||
|
||||
# search for tag objects (not the objects they are attached to
|
||||
|
|
|
|||
|
|
@ -157,3 +157,15 @@ class EvenniaTest(TestCase):
|
|||
self.account.delete()
|
||||
self.account2.delete()
|
||||
super().tearDown()
|
||||
|
||||
class LocalEvenniaTest(EvenniaTest):
|
||||
"""
|
||||
This test class is intended for inheriting in mygame tests.
|
||||
It helps ensure your tests are run with your own objects.
|
||||
"""
|
||||
account_typeclass = settings.BASE_ACCOUNT_TYPECLASS
|
||||
object_typeclass = settings.BASE_OBJECT_TYPECLASS
|
||||
character_typeclass = settings.BASE_CHARACTER_TYPECLASS
|
||||
exit_typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
script_typeclass = settings.BASE_SCRIPT_TYPECLASS
|
||||
|
|
|
|||
|
|
@ -2101,7 +2101,9 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
|||
if multimatch_string:
|
||||
error = "%s\n" % multimatch_string
|
||||
else:
|
||||
error = _("More than one match for '%s' (please narrow target):\n" % query)
|
||||
error = _("More than one match for '{query}' (please narrow target):\n").format(
|
||||
query=query
|
||||
)
|
||||
|
||||
for num, result in enumerate(matches):
|
||||
# we need to consider Commands, where .aliases is a list
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from django.core.exceptions import ValidationError as _error
|
|||
from django.core.validators import validate_email as _val_email
|
||||
from evennia.utils.ansi import strip_ansi
|
||||
from evennia.utils.utils import string_partial_matching as _partial
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}
|
||||
|
||||
|
|
@ -58,7 +59,7 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs)
|
|||
|
||||
"""
|
||||
if not entry:
|
||||
raise ValueError(f"No {option_key} entered!")
|
||||
raise ValueError(_("No {option_key} entered!").format(option_key=option_key))
|
||||
if not from_tz:
|
||||
from_tz = _pytz.UTC
|
||||
if account:
|
||||
|
|
@ -66,7 +67,11 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs)
|
|||
try:
|
||||
from_tz = _pytz.timezone(acct_tz)
|
||||
except Exception as err:
|
||||
raise ValueError(f"Timezone string '{acct_tz}' is not a valid timezone ({err})")
|
||||
raise ValueError(
|
||||
_("Timezone string '{acct_tz}' is not a valid timezone ({err})").format(
|
||||
acct_tz=acct_tz, err=err
|
||||
)
|
||||
)
|
||||
else:
|
||||
from_tz = _pytz.UTC
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue