mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Run black reformatter on code
This commit is contained in:
parent
4582eb4085
commit
bd3e31bf3c
178 changed files with 4511 additions and 3385 deletions
|
|
@ -11,15 +11,15 @@ from os import rename
|
|||
|
||||
def _rst2md(filename_rst):
|
||||
|
||||
with open(filename_rst, 'r') as fil:
|
||||
with open(filename_rst, "r") as fil:
|
||||
# read rst file, reformat and save
|
||||
txt = fil.read()
|
||||
with open(filename_rst, 'w') as fil:
|
||||
with open(filename_rst, "w") as fil:
|
||||
txt = "```{eval-rst}\n" + txt + "\n```"
|
||||
fil.write(txt)
|
||||
|
||||
# rename .rst file to .md file
|
||||
filename, _ = filename_rst.rsplit('.', 1)
|
||||
filename, _ = filename_rst.rsplit(".", 1)
|
||||
filename_md = filename + ".md"
|
||||
rename(filename_rst, filename_md)
|
||||
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ def auto_link_remapper(no_autodoc=False):
|
|||
|
||||
for strip_prefix in _STRIP_PREFIX:
|
||||
if url.startswith(strip_prefix):
|
||||
url = url[len(strip_prefix):]
|
||||
url = url[len(strip_prefix) :]
|
||||
|
||||
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
|
||||
# skip regular http/s urls etc
|
||||
|
|
@ -157,10 +157,10 @@ def auto_link_remapper(no_autodoc=False):
|
|||
|
||||
if url.startswith("evennia."):
|
||||
# api link - we want to remove legacy #reference and remove .md
|
||||
if '#' in url:
|
||||
_, url = url.rsplit('#', 1)
|
||||
if "#" in url:
|
||||
_, url = url.rsplit("#", 1)
|
||||
if url.endswith(".md"):
|
||||
url, _ = url.rsplit('.', 1)
|
||||
url, _ = url.rsplit(".", 1)
|
||||
return f"[{txt}]({url})"
|
||||
|
||||
fname, *part = url.rsplit("/", 1)
|
||||
|
|
@ -174,7 +174,9 @@ def auto_link_remapper(no_autodoc=False):
|
|||
|
||||
if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]:
|
||||
cfilename = _CURRFILE.rsplit("/", 1)[-1]
|
||||
urlout = docref_map[_CURRFILE][fname] + ".md" + ("#" + anchor[0].lower() if anchor else "")
|
||||
urlout = (
|
||||
docref_map[_CURRFILE][fname] + ".md" + ("#" + anchor[0].lower() if anchor else "")
|
||||
)
|
||||
if urlout != url:
|
||||
print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})")
|
||||
else:
|
||||
|
|
@ -193,7 +195,7 @@ def auto_link_remapper(no_autodoc=False):
|
|||
|
||||
for strip_prefix in _STRIP_PREFIX:
|
||||
if url.startswith(strip_prefix):
|
||||
url = url[len(strip_prefix):]
|
||||
url = url[len(strip_prefix) :]
|
||||
|
||||
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
|
||||
return f"[{txt}]: {url}"
|
||||
|
|
@ -202,8 +204,8 @@ def auto_link_remapper(no_autodoc=False):
|
|||
urlout = url
|
||||
elif url.startswith("evennia."):
|
||||
# api link - we want to remove legacy #reference
|
||||
if '#' in url:
|
||||
_, urlout = url.rsplit('#', 1)
|
||||
if "#" in url:
|
||||
_, urlout = url.rsplit("#", 1)
|
||||
else:
|
||||
fname, *part = url.rsplit("/", 1)
|
||||
fname = part[0] if part else fname
|
||||
|
|
|
|||
|
|
@ -50,15 +50,11 @@ tutorials are found here. Also the home of the Tutorial World demo adventure.
|
|||
"utils": """
|
||||
Miscellaneous, optional tools for manipulating text, auditing connections
|
||||
and more.
|
||||
"""
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
_FILENAME_MAP = {
|
||||
"rpsystem": "RPSystem",
|
||||
"xyzgrid": "XYZGrid",
|
||||
"awsstorage": "AWSStorage"
|
||||
}
|
||||
_FILENAME_MAP = {"rpsystem": "RPSystem", "xyzgrid": "XYZGrid", "awsstorage": "AWSStorage"}
|
||||
|
||||
HEADER = """# Contribs
|
||||
|
||||
|
|
@ -145,10 +141,14 @@ def readmes2docs(directory=_SOURCE_DIR):
|
|||
|
||||
pypath = f"evennia.contrib.{category}.{name}"
|
||||
|
||||
filename = "Contrib-" + "-".join(
|
||||
_FILENAME_MAP.get(
|
||||
part, part.capitalize() if part[0].islower() else part)
|
||||
for part in name.split("_")) + ".md"
|
||||
filename = (
|
||||
"Contrib-"
|
||||
+ "-".join(
|
||||
_FILENAME_MAP.get(part, part.capitalize() if part[0].islower() else part)
|
||||
for part in name.split("_")
|
||||
)
|
||||
+ ".md"
|
||||
)
|
||||
outfile = pathjoin(_OUT_DIR, filename)
|
||||
|
||||
with open(file_path) as fil:
|
||||
|
|
@ -163,7 +163,7 @@ def readmes2docs(directory=_SOURCE_DIR):
|
|||
except IndexError:
|
||||
blurb = name
|
||||
|
||||
with open(outfile, 'w') as fil:
|
||||
with open(outfile, "w") as fil:
|
||||
fil.write(data)
|
||||
|
||||
categories[category].append((name, credits, blurb, filename, pypath))
|
||||
|
|
@ -179,11 +179,7 @@ def readmes2docs(directory=_SOURCE_DIR):
|
|||
for tup in sorted(contrib_tups, key=lambda tup: tup[0].lower()):
|
||||
catlines.append(
|
||||
BLURB.format(
|
||||
name=tup[0],
|
||||
credits=tup[1],
|
||||
blurb=tup[2],
|
||||
filename=tup[3],
|
||||
code_location=tup[4]
|
||||
name=tup[0], credits=tup[1], blurb=tup[2], filename=tup[3], code_location=tup[4]
|
||||
)
|
||||
)
|
||||
filenames.append(f"{tup[3]}")
|
||||
|
|
@ -193,17 +189,15 @@ def readmes2docs(directory=_SOURCE_DIR):
|
|||
category=category,
|
||||
category_desc=_CATEGORY_DESCS[category].strip(),
|
||||
blurbs="\n".join(catlines),
|
||||
toctree=toctree
|
||||
toctree=toctree,
|
||||
)
|
||||
)
|
||||
|
||||
text = _FILE_STRUCTURE.format(
|
||||
header=HEADER,
|
||||
categories="\n".join(category_sections),
|
||||
footer=INDEX_FOOTER
|
||||
header=HEADER, categories="\n".join(category_sections), footer=INDEX_FOOTER
|
||||
)
|
||||
|
||||
with open(_OUT_INDEX_FILE, 'w') as fil:
|
||||
with open(_OUT_INDEX_FILE, "w") as fil:
|
||||
fil.write(text)
|
||||
|
||||
print(f" -- Converted Contrib READMEs to {ncount} doc pages + index.")
|
||||
|
|
|
|||
|
|
@ -26,7 +26,11 @@ if __name__ == "__main__":
|
|||
filepaths = glob.glob(args.files, recursive=True)
|
||||
width = args.width
|
||||
|
||||
wrapper = textwrap.TextWrapper(width=width, break_long_words=False, expand_tabs=True,)
|
||||
wrapper = textwrap.TextWrapper(
|
||||
width=width,
|
||||
break_long_words=False,
|
||||
expand_tabs=True,
|
||||
)
|
||||
|
||||
count = 0
|
||||
for filepath in filepaths:
|
||||
|
|
|
|||
|
|
@ -6,11 +6,9 @@
|
|||
#
|
||||
|
||||
from os.path import dirname, abspath, join as pathjoin
|
||||
from evennia.utils.utils import (
|
||||
mod_import, variable_from_module, callables_from_module
|
||||
)
|
||||
from evennia.utils.utils import mod_import, variable_from_module, callables_from_module
|
||||
|
||||
__all__ = ("run_update")
|
||||
__all__ = "run_update"
|
||||
|
||||
|
||||
PAGE = """
|
||||
|
|
@ -33,6 +31,7 @@ with [EvEditor](EvEditor), flipping pages in [EvMore](EvMore) or using the
|
|||
|
||||
"""
|
||||
|
||||
|
||||
def run_update(no_autodoc=False):
|
||||
|
||||
if no_autodoc:
|
||||
|
|
@ -71,7 +70,8 @@ def run_update(no_autodoc=False):
|
|||
for modname in cmd_modules:
|
||||
module = mod_import(modname)
|
||||
cmds_per_module[module] = [
|
||||
cmd for cmd in callables_from_module(module).values() if cmd.__name__.startswith("Cmd")]
|
||||
cmd for cmd in callables_from_module(module).values() if cmd.__name__.startswith("Cmd")
|
||||
]
|
||||
for cmd in cmds_per_module[module]:
|
||||
cmd_to_module_map[cmd] = module
|
||||
cmds_alphabetically.append(cmd)
|
||||
|
|
@ -79,8 +79,9 @@ def run_update(no_autodoc=False):
|
|||
|
||||
cmd_infos = []
|
||||
for cmd in cmds_alphabetically:
|
||||
aliases = [alias[1:] if alias and alias[0] == "@" else alias
|
||||
for alias in sorted(cmd.aliases)]
|
||||
aliases = [
|
||||
alias[1:] if alias and alias[0] == "@" else alias for alias in sorted(cmd.aliases)
|
||||
]
|
||||
aliases = f" [{', '.join(sorted(cmd.aliases))}]" if aliases else ""
|
||||
cmdlink = f"[**{cmd.key}**{aliases}]({cmd.__module__}.{cmd.__name__})"
|
||||
category = f"help-category: _{cmd.help_category.capitalize()}_"
|
||||
|
|
@ -98,12 +99,13 @@ def run_update(no_autodoc=False):
|
|||
txt = PAGE.format(
|
||||
ncommands=len(cmd_to_cmdset_map),
|
||||
nfiles=len(cmds_per_module),
|
||||
alphabetical="\n".join(f"- {info}" for info in cmd_infos))
|
||||
alphabetical="\n".join(f"- {info}" for info in cmd_infos),
|
||||
)
|
||||
|
||||
outdir = pathjoin(dirname(dirname(abspath(__file__))), "source", "Components")
|
||||
fname = pathjoin(outdir, "Default-Commands.md")
|
||||
|
||||
with open(fname, 'w') as fil:
|
||||
with open(fname, "w") as fil:
|
||||
fil.write(txt)
|
||||
|
||||
print(" -- Updated Default Command index.")
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ DOCDIR = pathjoin(ROOTDIR, "docs")
|
|||
DOCSRCDIR = pathjoin(DOCDIR, "source")
|
||||
EVENNIADIR = pathjoin(ROOTDIR, "evennia")
|
||||
|
||||
|
||||
def update_changelog():
|
||||
"""
|
||||
Plain CHANGELOG copy
|
||||
|
|
@ -22,7 +23,7 @@ def update_changelog():
|
|||
with open(sourcefile) as fil:
|
||||
txt = fil.read()
|
||||
|
||||
with open(targetfile, 'w') as fil:
|
||||
with open(targetfile, "w") as fil:
|
||||
fil.write(txt)
|
||||
|
||||
print(" -- Updated Changelog.md")
|
||||
|
|
@ -62,7 +63,7 @@ if settings.SERVERNAME == "Evennia":
|
|||
{txt}
|
||||
```
|
||||
"""
|
||||
with open(targetfile, 'w') as fil:
|
||||
with open(targetfile, "w") as fil:
|
||||
fil.write(txt)
|
||||
|
||||
print(" -- Updated Settings-Default.md")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
# from recommonmark.transform import AutoStructify
|
||||
from sphinx.util.osutil import cd
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ extensions = [
|
|||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.todo",
|
||||
"sphinx.ext.githubpages",
|
||||
"myst_parser"
|
||||
"myst_parser",
|
||||
]
|
||||
|
||||
source_suffix = [".md", ".rst"]
|
||||
|
|
@ -145,9 +146,11 @@ _github_code_root = "https://github.com/evennia/evennia/blob/"
|
|||
_github_doc_root = "https://github.com/evennia/tree/master/docs/sources/"
|
||||
_github_issue_choose = "https://github.com/evennia/evennia/issues/new/choose"
|
||||
_ref_regex = re.compile( # normal reference-links [txt](url)
|
||||
r"\[(?P<txt>[\w -\[\]\`\n]+?)\]\((?P<url>.+?)\)", re.I + re.S + re.U + re.M)
|
||||
r"\[(?P<txt>[\w -\[\]\`\n]+?)\]\((?P<url>.+?)\)", re.I + re.S + re.U + re.M
|
||||
)
|
||||
_ref_doc_regex = re.compile( # in-document bottom references [txt]: url
|
||||
r"\[(?P<txt>[\w -\`]+?)\\n]:\s+?(?P<url>.+?)(?=$|\n)", re.I + re.S + re.U + re.M)
|
||||
r"\[(?P<txt>[\w -\`]+?)\\n]:\s+?(?P<url>.+?)(?=$|\n)", re.I + re.S + re.U + re.M
|
||||
)
|
||||
|
||||
|
||||
def url_resolver(app, docname, source):
|
||||
|
|
@ -165,10 +168,11 @@ def url_resolver(app, docname, source):
|
|||
|
||||
|
||||
"""
|
||||
|
||||
def _url_remap(url):
|
||||
|
||||
# determine depth in tree of current document
|
||||
docdepth = docname.count('/') + 1
|
||||
docdepth = docname.count("/") + 1
|
||||
relative_path = "../".join("" for _ in range(docdepth))
|
||||
|
||||
if url.endswith(_choose_issue):
|
||||
|
|
@ -176,14 +180,14 @@ def url_resolver(app, docname, source):
|
|||
return _github_issue_choose
|
||||
elif _githubstart in url:
|
||||
# github:develop/... shortcut
|
||||
urlpath = url[url.index(_githubstart) + len(_githubstart):]
|
||||
urlpath = url[url.index(_githubstart) + len(_githubstart) :]
|
||||
if not (urlpath.startswith("develop/") or urlpath.startswith("master")):
|
||||
urlpath = "master/" + urlpath
|
||||
return _github_code_root + urlpath
|
||||
elif _sourcestart in url:
|
||||
ind = url.index(_sourcestart)
|
||||
|
||||
modpath, *inmodule = url[ind + len(_sourcestart):].rsplit("#", 1)
|
||||
modpath, *inmodule = url[ind + len(_sourcestart) :].rsplit("#", 1)
|
||||
modpath = "/".join(modpath.split("."))
|
||||
inmodule = "#" + inmodule[0] if inmodule else ""
|
||||
modpath = modpath + ".html" + inmodule
|
||||
|
|
@ -194,13 +198,13 @@ def url_resolver(app, docname, source):
|
|||
return url
|
||||
|
||||
def _re_ref_sub(match):
|
||||
txt = match.group('txt')
|
||||
url = _url_remap(match.group('url'))
|
||||
txt = match.group("txt")
|
||||
url = _url_remap(match.group("url"))
|
||||
return f"[{txt}]({url})"
|
||||
|
||||
def _re_docref_sub(match):
|
||||
txt = match.group('txt')
|
||||
url = _url_remap(match.group('url'))
|
||||
txt = match.group("txt")
|
||||
url = _url_remap(match.group("url"))
|
||||
return f"[{txt}]: {url}"
|
||||
|
||||
src = source[0]
|
||||
|
|
@ -248,7 +252,7 @@ autodoc_default_options = {
|
|||
"show-inheritance": True,
|
||||
"special-members": "__init__",
|
||||
"enable_eval_rst": True,
|
||||
"inherited_members": True
|
||||
"inherited_members": True,
|
||||
}
|
||||
|
||||
autodoc_member_order = "bysource"
|
||||
|
|
@ -345,8 +349,12 @@ def setup(app):
|
|||
|
||||
# build toctree file
|
||||
sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||
from docs.pylib import (auto_link_remapper, update_default_cmd_index,
|
||||
contrib_readmes2docs, update_dynamic_pages)
|
||||
from docs.pylib import (
|
||||
auto_link_remapper,
|
||||
update_default_cmd_index,
|
||||
contrib_readmes2docs,
|
||||
update_dynamic_pages,
|
||||
)
|
||||
|
||||
_no_autodoc = os.environ.get("NOAUTODOC")
|
||||
update_default_cmd_index.run_update(no_autodoc=_no_autodoc)
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ MONITOR_HANDLER = None
|
|||
GLOBAL_SCRIPTS = None
|
||||
OPTION_CLASSES = None
|
||||
|
||||
|
||||
def _create_version():
|
||||
"""
|
||||
Helper function for building the version string
|
||||
|
|
|
|||
|
|
@ -54,11 +54,12 @@ _CMDHANDLER = None
|
|||
|
||||
# Create throttles for too many account-creations and login attempts
|
||||
CREATION_THROTTLE = Throttle(
|
||||
name='creation', limit=settings.CREATION_THROTTLE_LIMIT,
|
||||
timeout=settings.CREATION_THROTTLE_TIMEOUT
|
||||
name="creation",
|
||||
limit=settings.CREATION_THROTTLE_LIMIT,
|
||||
timeout=settings.CREATION_THROTTLE_TIMEOUT,
|
||||
)
|
||||
LOGIN_THROTTLE = Throttle(
|
||||
name='login', limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT
|
||||
name="login", limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -802,8 +803,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
except Exception:
|
||||
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
|
||||
|
||||
|
|
@ -879,7 +883,6 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
super().delete(*args, **kwargs)
|
||||
return True
|
||||
|
||||
|
||||
# methods inherited from database model
|
||||
|
||||
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
|
||||
|
|
@ -968,9 +971,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
sessions = self.sessions.get()
|
||||
session = sessions[0] if sessions else None
|
||||
|
||||
return _CMDHANDLER(
|
||||
self, raw_string, callertype="account", session=session, **kwargs
|
||||
)
|
||||
return _CMDHANDLER(self, raw_string, callertype="account", session=session, **kwargs)
|
||||
|
||||
# channel receive hooks
|
||||
|
||||
|
|
@ -1000,11 +1001,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
"""
|
||||
if senders:
|
||||
sender_string = ', '.join(sender.get_display_name(self) for sender in senders)
|
||||
sender_string = ", ".join(sender.get_display_name(self) for sender in senders)
|
||||
message_lstrip = message.lstrip()
|
||||
if message_lstrip.startswith((':', ';')):
|
||||
if message_lstrip.startswith((":", ";")):
|
||||
# this is a pose, should show as e.g. "User1 smiles to channel"
|
||||
spacing = "" if message_lstrip[1:].startswith((':', '\'', ',')) else " "
|
||||
spacing = "" if message_lstrip[1:].startswith((":", "'", ",")) else " "
|
||||
message = f"{sender_string}{spacing}{message_lstrip[1:]}"
|
||||
else:
|
||||
# normal message
|
||||
|
|
@ -1035,8 +1036,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
to customize the message for the receiver on the channel-level.
|
||||
|
||||
"""
|
||||
self.msg(text=(message, {"from_channel": channel.id}),
|
||||
from_obj=senders, options={"from_channel": channel.id})
|
||||
self.msg(
|
||||
text=(message, {"from_channel": channel.id}),
|
||||
from_obj=senders,
|
||||
options={"from_channel": channel.id},
|
||||
)
|
||||
|
||||
def at_post_channel_msg(self, message, channel, senders=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -1373,8 +1377,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
if _MUDINFO_CHANNEL is None:
|
||||
if settings.CHANNEL_MUDINFO:
|
||||
try:
|
||||
_MUDINFO_CHANNEL = ChannelDB.objects.get(
|
||||
db_key=settings.CHANNEL_MUDINFO["key"])
|
||||
_MUDINFO_CHANNEL = ChannelDB.objects.get(db_key=settings.CHANNEL_MUDINFO["key"])
|
||||
except ChannelDB.DoesNotExist:
|
||||
logger.log_trace()
|
||||
else:
|
||||
|
|
@ -1383,7 +1386,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
if settings.CHANNEL_CONNECTINFO:
|
||||
try:
|
||||
_CONNECT_CHANNEL = ChannelDB.objects.get(
|
||||
db_key=settings.CHANNEL_CONNECTINFO["key"])
|
||||
db_key=settings.CHANNEL_CONNECTINFO["key"]
|
||||
)
|
||||
except ChannelDB.DoesNotExist:
|
||||
logger.log_trace()
|
||||
else:
|
||||
|
|
@ -1661,7 +1665,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
if sess and sid:
|
||||
result.append(
|
||||
f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] "
|
||||
f"(played by you in session {sid})")
|
||||
f"(played by you in session {sid})"
|
||||
)
|
||||
else:
|
||||
result.append(
|
||||
f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] "
|
||||
|
|
|
|||
|
|
@ -329,9 +329,7 @@ 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(
|
||||
"Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist)
|
||||
)
|
||||
obj.msg("Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist))
|
||||
self._nicklist_callers = []
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -276,8 +276,11 @@ class AccountDBManager(TypedObjectManager, UserManager):
|
|||
new_account.set_password(password)
|
||||
|
||||
new_account._createdict = dict(
|
||||
locks=locks, permissions=permissions,
|
||||
report_to=report_to, tags=tags, attributes=attributes
|
||||
locks=locks,
|
||||
permissions=permissions,
|
||||
report_to=report_to,
|
||||
tags=tags,
|
||||
attributes=attributes,
|
||||
)
|
||||
# saving will trigger the signal that calls the
|
||||
# at_first_save hook on the typeclass, where the _createdict
|
||||
|
|
|
|||
|
|
@ -80,47 +80,63 @@ _SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit
|
|||
# is the normal "production message to echo to the account.
|
||||
|
||||
_ERROR_UNTRAPPED = (
|
||||
_("""
|
||||
_(
|
||||
"""
|
||||
An untrapped error occurred.
|
||||
"""),
|
||||
_("""
|
||||
"""
|
||||
),
|
||||
_(
|
||||
"""
|
||||
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
)
|
||||
|
||||
_ERROR_CMDSETS = (
|
||||
_("""
|
||||
_(
|
||||
"""
|
||||
A cmdset merger-error occurred. This is often due to a syntax
|
||||
error in one of the cmdsets to merge.
|
||||
"""),
|
||||
_("""
|
||||
"""
|
||||
),
|
||||
_(
|
||||
"""
|
||||
A cmdset merger-error occurred. Please file a bug report detailing the
|
||||
steps to reproduce.
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
)
|
||||
|
||||
_ERROR_NOCMDSETS = (
|
||||
_("""
|
||||
_(
|
||||
"""
|
||||
No command sets found! This is a critical bug that can have
|
||||
multiple causes.
|
||||
"""),
|
||||
_("""
|
||||
"""
|
||||
),
|
||||
_(
|
||||
"""
|
||||
No command sets found! This is a sign of a critical bug. If
|
||||
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
||||
the server admin through" some other means for assistance.
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
)
|
||||
|
||||
_ERROR_CMDHANDLER = (
|
||||
_("""
|
||||
_(
|
||||
"""
|
||||
A command handler bug occurred. If this is not due to a local change,
|
||||
please file a bug report with the Evennia project, including the
|
||||
traceback and steps to reproduce.
|
||||
"""),
|
||||
_("""
|
||||
"""
|
||||
),
|
||||
_(
|
||||
"""
|
||||
A command handler bug occurred. Please notify staff - they should
|
||||
likely file a bug report with the Evennia project.
|
||||
"""),
|
||||
"""
|
||||
),
|
||||
)
|
||||
|
||||
_ERROR_RECURSION_LIMIT = _(
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ def build_matches(raw_string, cmdset, include_prefixes=False):
|
|||
for cmdname in [cmd.key] + cmd.aliases
|
||||
if cmdname
|
||||
and l_raw_string.startswith(cmdname.lower())
|
||||
and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):]))
|
||||
and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :]))
|
||||
]
|
||||
)
|
||||
else:
|
||||
|
|
@ -90,7 +90,7 @@ def build_matches(raw_string, cmdset, include_prefixes=False):
|
|||
if (
|
||||
cmdname
|
||||
and l_raw_string.startswith(cmdname.lower())
|
||||
and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname):]))
|
||||
and (not cmd.arg_regex or cmd.arg_regex.match(l_raw_string[len(cmdname) :]))
|
||||
):
|
||||
matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname))
|
||||
except Exception:
|
||||
|
|
@ -125,7 +125,10 @@ def try_num_differentiators(raw_string):
|
|||
# the user might be trying to identify the command
|
||||
# with a #num-command style syntax. We expect the regex to
|
||||
# contain the groups "number" and "name".
|
||||
mindex, new_raw_string = (num_ref_match.group("number"), num_ref_match.group("name") + num_ref_match.group("args"))
|
||||
mindex, new_raw_string = (
|
||||
num_ref_match.group("number"),
|
||||
num_ref_match.group("name") + num_ref_match.group("args"),
|
||||
)
|
||||
return int(mindex), new_raw_string
|
||||
else:
|
||||
return None, None
|
||||
|
|
@ -182,9 +185,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
|||
|
||||
if not matches and _CMD_IGNORE_PREFIXES:
|
||||
# still no match. Try to strip prefixes
|
||||
raw_string = (
|
||||
raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string
|
||||
)
|
||||
raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else raw_string
|
||||
matches = build_matches(raw_string, cmdset, include_prefixes=False)
|
||||
|
||||
# only select command matches we are actually allowed to call.
|
||||
|
|
|
|||
|
|
@ -358,14 +358,18 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
|
||||
"""
|
||||
perm = "perm" if self.persistent else "non-perm"
|
||||
options = ", ".join([
|
||||
"{}:{}".format(opt, "T" if getattr(self, opt) else "F")
|
||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||
if getattr(self, opt) is not None
|
||||
])
|
||||
options = ", ".join(
|
||||
[
|
||||
"{}:{}".format(opt, "T" if getattr(self, opt) else "F")
|
||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||
if getattr(self, opt) is not None
|
||||
]
|
||||
)
|
||||
options = (", " + options) if options else ""
|
||||
return (f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: "
|
||||
+ ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)]))
|
||||
return (
|
||||
f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: "
|
||||
+ ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
|
|
@ -519,10 +523,12 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
try:
|
||||
cmdset = self._instantiate(cmdset)
|
||||
except RuntimeError:
|
||||
err = ("Adding cmdset {cmdset} to {cls} lead to an "
|
||||
"infinite loop. When adding a cmdset to another, "
|
||||
"make sure they are not themself cyclically added to "
|
||||
"the new cmdset somewhere in the chain.")
|
||||
err = (
|
||||
"Adding cmdset {cmdset} to {cls} lead to an "
|
||||
"infinite loop. When adding a cmdset to another, "
|
||||
"make sure they are not themself cyclically added to "
|
||||
"the new cmdset somewhere in the chain."
|
||||
)
|
||||
raise RuntimeError(_(err.format(cmdset=cmdset, cls=self.__class__)))
|
||||
cmds = cmdset.commands
|
||||
elif is_iter(cmd):
|
||||
|
|
|
|||
|
|
@ -423,8 +423,7 @@ class CmdSetHandler(object):
|
|||
self.mergetype_stack.append(new_current.actual_mergetype)
|
||||
self.current = new_current
|
||||
|
||||
def add(self, cmdset, emit_to_obj=None, persistent=False, default_cmdset=False,
|
||||
**kwargs):
|
||||
def add(self, cmdset, emit_to_obj=None, persistent=False, default_cmdset=False, **kwargs):
|
||||
"""
|
||||
Add a cmdset to the handler, on top of the old ones, unless it
|
||||
is set as the default one (it will then end up at the bottom of the stack)
|
||||
|
|
@ -451,9 +450,11 @@ class CmdSetHandler(object):
|
|||
|
||||
"""
|
||||
if "permanent" in kwargs:
|
||||
logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to "
|
||||
"'persistent' and now defaults to True.")
|
||||
persistent = kwargs['permanent'] if persistent is None else persistent
|
||||
logger.log_dep(
|
||||
"obj.cmdset.add() kwarg 'permanent' has changed name to "
|
||||
"'persistent' and now defaults to True."
|
||||
)
|
||||
persistent = kwargs["permanent"] if persistent is None else persistent
|
||||
|
||||
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
||||
string = _("Only CmdSets can be added to the cmdsethandler!")
|
||||
|
|
@ -491,9 +492,10 @@ class CmdSetHandler(object):
|
|||
|
||||
"""
|
||||
if "permanent" in kwargs:
|
||||
logger.log_dep("obj.cmdset.add_default() kwarg 'permanent' has changed name to "
|
||||
"'persistent'.")
|
||||
persistent = kwargs['permanent'] if persistent is None else persistent
|
||||
logger.log_dep(
|
||||
"obj.cmdset.add_default() kwarg 'permanent' has changed name to 'persistent'."
|
||||
)
|
||||
persistent = kwargs["permanent"] if persistent is None else persistent
|
||||
self.add(cmdset, emit_to_obj=emit_to_obj, persistent=persistent, default_cmdset=True)
|
||||
|
||||
def remove(self, cmdset=None, default_cmdset=False):
|
||||
|
|
|
|||
|
|
@ -102,16 +102,16 @@ def _init_command(cls, **kwargs):
|
|||
# pre-prepare a help index entry for quicker lookup
|
||||
# strip the @- etc to allow help to be agnostic
|
||||
stripped_key = cls.key[1:] if cls.key and cls.key[0] in CMD_IGNORE_PREFIXES else ""
|
||||
stripped_aliases = (
|
||||
" ".join(al[1:] if al and al[0] in CMD_IGNORE_PREFIXES else al
|
||||
for al in cls.aliases))
|
||||
stripped_aliases = " ".join(
|
||||
al[1:] if al and al[0] in CMD_IGNORE_PREFIXES else al for al in cls.aliases
|
||||
)
|
||||
cls.search_index_entry = {
|
||||
"key": cls.key,
|
||||
"aliases": " ".join(cls.aliases),
|
||||
"no_prefix": f"{stripped_key} {stripped_aliases}",
|
||||
"category": cls.help_category,
|
||||
"text": cls.__doc__,
|
||||
"tags": ""
|
||||
"tags": "",
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -562,7 +562,7 @@ Command {self} has no defined `func()` - showing on-command variables:
|
|||
"""
|
||||
try:
|
||||
return reverse(
|
||||
'help-entry-detail',
|
||||
"help-entry-detail",
|
||||
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
|
||||
)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
|||
ipregex = re.compile(r"%s" % ipregex)
|
||||
bantup = ("", ban, ipregex, now, reason)
|
||||
|
||||
ret = yield(f"Are you sure you want to {typ}-ban '|w{ban}|n' [Y]/N?")
|
||||
ret = yield (f"Are you sure you want to {typ}-ban '|w{ban}|n' [Y]/N?")
|
||||
if str(ret).lower() in ("no", "n"):
|
||||
self.caller.msg("Aborted.")
|
||||
return
|
||||
|
|
@ -273,7 +273,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
|||
ban = banlist[num - 1]
|
||||
value = (" ".join([s for s in ban[:2]])).strip()
|
||||
|
||||
ret = yield(f"Are you sure you want to unban {num}: '|w{value}|n' [Y]/N?")
|
||||
ret = yield (f"Are you sure you want to unban {num}: '|w{value}|n' [Y]/N?")
|
||||
if str(ret).lower() in ("n", "no"):
|
||||
self.caller.msg("Aborted.")
|
||||
return
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ from evennia.utils.utils import (
|
|||
class_from_module,
|
||||
get_all_typeclasses,
|
||||
variable_from_module,
|
||||
dbref, crop,
|
||||
dbref,
|
||||
crop,
|
||||
interactive,
|
||||
list_to_string,
|
||||
display_len,
|
||||
|
|
@ -1498,9 +1499,11 @@ class CmdOpen(ObjManipCommand):
|
|||
super().parse()
|
||||
self.location = self.caller.location
|
||||
if not self.args or not self.rhs:
|
||||
self.caller.msg("Usage: open <new exit>[;alias...][:typeclass]"
|
||||
"[,<return exit>[;alias..][:typeclass]]] "
|
||||
"= <destination>")
|
||||
self.caller.msg(
|
||||
"Usage: open <new exit>[;alias...][:typeclass]"
|
||||
"[,<return exit>[;alias..][:typeclass]]] "
|
||||
"= <destination>"
|
||||
)
|
||||
raise InterruptCommand
|
||||
if not self.location:
|
||||
self.caller.msg("You cannot create an exit from a None-location.")
|
||||
|
|
@ -1519,8 +1522,9 @@ class CmdOpen(ObjManipCommand):
|
|||
as well as the self.create_exit() method.
|
||||
"""
|
||||
# Create exit
|
||||
ok = self.create_exit(self.exit_name, self.location, self.destination,
|
||||
self.exit_aliases, self.exit_typeclass)
|
||||
ok = self.create_exit(
|
||||
self.exit_name, self.location, self.destination, self.exit_aliases, self.exit_typeclass
|
||||
)
|
||||
if not ok:
|
||||
# an error; the exit was not created, so we quit.
|
||||
return
|
||||
|
|
@ -1529,8 +1533,13 @@ class CmdOpen(ObjManipCommand):
|
|||
back_exit_name = self.lhs_objs[1]["name"]
|
||||
back_exit_aliases = self.lhs_objs[1]["aliases"]
|
||||
back_exit_typeclass = self.lhs_objs[1]["option"]
|
||||
self.create_exit(back_exit_name, self.destination, self.location, back_exit_aliases,
|
||||
back_exit_typeclass)
|
||||
self.create_exit(
|
||||
back_exit_name,
|
||||
self.destination,
|
||||
self.location,
|
||||
back_exit_aliases,
|
||||
back_exit_typeclass,
|
||||
)
|
||||
|
||||
|
||||
def _convert_from_string(cmd, strobj):
|
||||
|
|
@ -1740,8 +1749,10 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
obj.attributes.remove(attr, category=category)
|
||||
return f"\nDeleted attribute {obj.name}/|w{attr}|n [category:{category}]."
|
||||
else:
|
||||
return (f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] "
|
||||
"was found to delete.")
|
||||
return (
|
||||
f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] "
|
||||
"was found to delete."
|
||||
)
|
||||
error = f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] was found to delete."
|
||||
if nested:
|
||||
error += " (Nested lookups attempted)"
|
||||
|
|
@ -1813,7 +1824,7 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
except AttributeError:
|
||||
# we set empty buffer on nonexisting Attribute because otherwise
|
||||
# we'd always have the string "None" in the buffer to start with
|
||||
old_value = ''
|
||||
old_value = ""
|
||||
return str(old_value) # we already confirmed we are ok with this
|
||||
|
||||
def save(caller, buf):
|
||||
|
|
@ -1825,11 +1836,12 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
try:
|
||||
old_value = obj.attributes.get(attr, raise_exception=True)
|
||||
if not isinstance(old_value, str):
|
||||
answer = yield(
|
||||
answer = yield (
|
||||
f"|rWarning: Attribute |w{attr}|r is of type |w{type(old_value).__name__}|r. "
|
||||
"\nTo continue editing, it must be converted to (and saved as) a string. "
|
||||
"Continue? [Y]/N?")
|
||||
if answer.lower() in ('n', 'no'):
|
||||
"Continue? [Y]/N?"
|
||||
)
|
||||
if answer.lower() in ("n", "no"):
|
||||
self.caller.msg("Aborted edit.")
|
||||
return
|
||||
except AttributeError:
|
||||
|
|
@ -1903,9 +1915,11 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
caller.msg("The Line editor can only be applied " "to one attribute at a time.")
|
||||
return
|
||||
if not attrs:
|
||||
caller.msg("Use `set/edit <objname>/<attr>` to define the Attribute to edit.\nTo "
|
||||
"edit the current room description, use `set/edit here/desc` (or "
|
||||
"use the `desc` command).")
|
||||
caller.msg(
|
||||
"Use `set/edit <objname>/<attr>` to define the Attribute to edit.\nTo "
|
||||
"edit the current room description, use `set/edit here/desc` (or "
|
||||
"use the `desc` command)."
|
||||
)
|
||||
return
|
||||
self.edit_handler(obj, attrs[0], caller)
|
||||
return
|
||||
|
|
@ -1936,8 +1950,10 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
global _ATTRFUNCPARSER
|
||||
if not _ATTRFUNCPARSER:
|
||||
_ATTRFUNCPARSER = funcparser.FuncParser(
|
||||
{"dbref": funcparser.funcparser_callable_search,
|
||||
"search": funcparser.funcparser_callable_search}
|
||||
{
|
||||
"dbref": funcparser.funcparser_callable_search,
|
||||
"search": funcparser.funcparser_callable_search,
|
||||
}
|
||||
)
|
||||
|
||||
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
||||
|
|
@ -1951,10 +1967,13 @@ class CmdSetAttribute(ObjManipCommand):
|
|||
if hasattr(parsed_value, "access"):
|
||||
# if this is an object we must have the right to read it, if so,
|
||||
# we will not convert it to a string
|
||||
if not (parsed_value.access(caller, "control")
|
||||
or parsed_value.access(self.caller, "edit")):
|
||||
caller.msg("You don't have permission to set "
|
||||
f"object with identifier '{value}'.")
|
||||
if not (
|
||||
parsed_value.access(caller, "control")
|
||||
or parsed_value.access(self.caller, "edit")
|
||||
):
|
||||
caller.msg(
|
||||
"You don't have permission to set " f"object with identifier '{value}'."
|
||||
)
|
||||
continue
|
||||
value = parsed_value
|
||||
else:
|
||||
|
|
@ -2038,7 +2057,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
obj = caller.search(query)
|
||||
if not obj:
|
||||
return
|
||||
elif (self.account and self.account.__dbclass__ == dbclass):
|
||||
elif self.account and self.account.__dbclass__ == dbclass:
|
||||
# applying account while caller is object
|
||||
caller.msg(f"Trying to search {new_typeclass} with query '{self.lhs}'.")
|
||||
obj = self.account.search(query)
|
||||
|
|
@ -2071,7 +2090,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
caller = self.caller
|
||||
|
||||
if "list" in self.switches or self.cmdname in ('typeclasses', '@typeclasses'):
|
||||
if "list" in self.switches or self.cmdname in ("typeclasses", "@typeclasses"):
|
||||
tclasses = get_all_typeclasses()
|
||||
contribs = [key for key in sorted(tclasses) if key.startswith("evennia.contrib")] or [
|
||||
"<None loaded>"
|
||||
|
|
@ -2188,8 +2207,10 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
is_same = obj.is_typeclass(new_typeclass, exact=True)
|
||||
if is_same and "force" not in self.switches:
|
||||
string = (f"{obj.name} already has the typeclass '{new_typeclass}'. "
|
||||
"Use /force to override.")
|
||||
string = (
|
||||
f"{obj.name} already has the typeclass '{new_typeclass}'. "
|
||||
"Use /force to override."
|
||||
)
|
||||
else:
|
||||
update = "update" in self.switches
|
||||
reset = "reset" in self.switches
|
||||
|
|
@ -2220,7 +2241,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if "prototype" in self.switches:
|
||||
modified = spawner.batch_update_objects_with_prototype(
|
||||
prototype, objects=[obj], caller=self.caller)
|
||||
prototype, objects=[obj], caller=self.caller
|
||||
)
|
||||
prototype_success = modified > 0
|
||||
if not prototype_success:
|
||||
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
||||
|
|
@ -2543,9 +2565,7 @@ class CmdExamine(ObjManipCommand):
|
|||
def format_locks(self, obj):
|
||||
locks = str(obj.locks)
|
||||
if locks:
|
||||
return utils.fill(
|
||||
"; ".join([lock for lock in locks.split(";")]), indent=2
|
||||
)
|
||||
return utils.fill("; ".join([lock for lock in locks.split(";")]), indent=2)
|
||||
return "Default"
|
||||
|
||||
def format_scripts(self, obj):
|
||||
|
|
@ -2572,6 +2592,7 @@ class CmdExamine(ObjManipCommand):
|
|||
if value:
|
||||
return f"{string}: T"
|
||||
return f"{string}: F"
|
||||
|
||||
txt = ", ".join(
|
||||
_truefalse(opt, getattr(cmdset, opt))
|
||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||
|
|
@ -2607,13 +2628,18 @@ class CmdExamine(ObjManipCommand):
|
|||
# we only show the first session's cmdset here (it is -in principle- possible
|
||||
# that different sessions have different cmdsets but for admins who want such
|
||||
# madness it is better that they overload with their own CmdExamine to handle it).
|
||||
all_cmdsets.extend([(cmdset.key, cmdset)
|
||||
for cmdset in obj.account.sessions.all()[0].cmdset.all()])
|
||||
all_cmdsets.extend(
|
||||
[(cmdset.key, cmdset) for cmdset in obj.account.sessions.all()[0].cmdset.all()]
|
||||
)
|
||||
else:
|
||||
try:
|
||||
# we have to protect this since many objects don't have sessions.
|
||||
all_cmdsets.extend([(cmdset.key, cmdset)
|
||||
for cmdset in obj.get_session(obj.sessions.get()).cmdset.all()])
|
||||
all_cmdsets.extend(
|
||||
[
|
||||
(cmdset.key, cmdset)
|
||||
for cmdset in obj.get_session(obj.sessions.get()).cmdset.all()
|
||||
]
|
||||
)
|
||||
except (TypeError, AttributeError):
|
||||
# an error means we are merging an object without a session
|
||||
pass
|
||||
|
|
@ -2659,8 +2685,10 @@ class CmdExamine(ObjManipCommand):
|
|||
typ = f" |B[type: {typ}]|n" if typ else ""
|
||||
value = utils.to_str(value)
|
||||
value = _FUNCPARSER.parse(ansi_raw(value), escape=True)
|
||||
return (f"Attribute {obj.name}/{self.header_color}{key}|n "
|
||||
f"[category={category}]{typ}:\n\n{value}")
|
||||
return (
|
||||
f"Attribute {obj.name}/{self.header_color}{key}|n "
|
||||
f"[category={category}]{typ}:\n\n{value}"
|
||||
)
|
||||
|
||||
def format_single_attribute(self, attr):
|
||||
global _FUNCPARSER
|
||||
|
|
@ -2680,8 +2708,7 @@ class CmdExamine(ObjManipCommand):
|
|||
|
||||
def format_attributes(self, obj):
|
||||
output = "\n " + "\n ".join(
|
||||
sorted(self.format_single_attribute(attr)
|
||||
for attr in obj.db_attributes.all())
|
||||
sorted(self.format_single_attribute(attr) for attr in obj.db_attributes.all())
|
||||
)
|
||||
if output.strip():
|
||||
# we don't want just an empty line
|
||||
|
|
@ -2695,8 +2722,7 @@ class CmdExamine(ObjManipCommand):
|
|||
|
||||
if ndb_attr and ndb_attr[0]:
|
||||
return "\n " + " \n".join(
|
||||
sorted(self.format_single_attribute(attr)
|
||||
for attr, value in ndb_attr)
|
||||
sorted(self.format_single_attribute(attr) for attr, value in ndb_attr)
|
||||
)
|
||||
|
||||
def format_exits(self, obj):
|
||||
|
|
@ -2706,14 +2732,16 @@ class CmdExamine(ObjManipCommand):
|
|||
|
||||
def format_chars(self, obj):
|
||||
if hasattr(obj, "contents"):
|
||||
chars = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents
|
||||
if obj.account)
|
||||
chars = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents if obj.account)
|
||||
return chars if chars else None
|
||||
|
||||
def format_things(self, obj):
|
||||
if hasattr(obj, "contents"):
|
||||
things = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents
|
||||
if not obj.account and not obj.destination)
|
||||
things = ", ".join(
|
||||
f"{obj.name}({obj.dbref})"
|
||||
for obj in obj.contents
|
||||
if not obj.account and not obj.destination
|
||||
)
|
||||
return things if things else None
|
||||
|
||||
def format_script_desc(self, obj):
|
||||
|
|
@ -2736,8 +2764,10 @@ class CmdExamine(ObjManipCommand):
|
|||
remaining_repeats = obj.remaining_repeats()
|
||||
remaining_repeats = 0 if remaining_repeats is None else remaining_repeats
|
||||
repeats = f" - {remaining_repeats}/{obj.db_repeats} remain"
|
||||
return (f"{active} - interval: {interval}s "
|
||||
f"(next: {next_repeat}{repeats}, start_delay: {start_delay})")
|
||||
return (
|
||||
f"{active} - interval: {interval}s "
|
||||
f"(next: {next_repeat}{repeats}, start_delay: {start_delay})"
|
||||
)
|
||||
|
||||
def format_channel_sub_totals(self, obj):
|
||||
if hasattr(obj, "db_account_subscriptions"):
|
||||
|
|
@ -2752,14 +2782,16 @@ class CmdExamine(ObjManipCommand):
|
|||
account_subs = obj.db_account_subscriptions.all()
|
||||
if account_subs:
|
||||
return "\n " + "\n ".join(
|
||||
format_grid([sub.key for sub in account_subs], sep=' ', width=_DEFAULT_WIDTH))
|
||||
format_grid([sub.key for sub in account_subs], sep=" ", width=_DEFAULT_WIDTH)
|
||||
)
|
||||
|
||||
def format_channel_object_subs(self, obj):
|
||||
if hasattr(obj, "db_object_subscriptions"):
|
||||
object_subs = obj.db_object_subscriptions.all()
|
||||
if object_subs:
|
||||
return "\n " + "\n ".join(
|
||||
format_grid([sub.key for sub in object_subs], sep=' ', width=_DEFAULT_WIDTH))
|
||||
format_grid([sub.key for sub in object_subs], sep=" ", width=_DEFAULT_WIDTH)
|
||||
)
|
||||
|
||||
def get_formatted_obj_data(self, obj, current_cmdset):
|
||||
"""
|
||||
|
|
@ -2781,13 +2813,14 @@ class CmdExamine(ObjManipCommand):
|
|||
objdata["Destination"] = self.format_destination(obj)
|
||||
objdata["Permissions"] = self.format_permissions(obj)
|
||||
objdata["Locks"] = self.format_locks(obj)
|
||||
if (current_cmdset
|
||||
and not (len(obj.cmdset.all()) == 1
|
||||
and obj.cmdset.current.key == "_EMPTY_CMDSET")):
|
||||
if current_cmdset and not (
|
||||
len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"
|
||||
):
|
||||
objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj)
|
||||
objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset)
|
||||
objdata[f"Commands vailable to {obj.key} (result of Merged Cmdset(s))"] = (
|
||||
self.format_current_cmds(obj, current_cmdset))
|
||||
objdata[
|
||||
f"Commands vailable to {obj.key} (result of Merged Cmdset(s))"
|
||||
] = self.format_current_cmds(obj, current_cmdset)
|
||||
if self.object_type == "script":
|
||||
objdata["Description"] = self.format_script_desc(obj)
|
||||
objdata["Persistent"] = self.format_script_is_persistent(obj)
|
||||
|
|
@ -2859,10 +2892,11 @@ class CmdExamine(ObjManipCommand):
|
|||
obj = None
|
||||
elif len(obj) > 1:
|
||||
err = "Multiple {objtype} found with key {obj_name}:\n{matches}"
|
||||
self.caller.msg(err.format(
|
||||
obj_name=obj_name,
|
||||
matches=", ".join(f"{ob.key}(#{ob.id})" for ob in obj)
|
||||
))
|
||||
self.caller.msg(
|
||||
err.format(
|
||||
obj_name=obj_name, matches=", ".join(f"{ob.key}(#{ob.id})" for ob in obj)
|
||||
)
|
||||
)
|
||||
obj = None
|
||||
else:
|
||||
obj = obj[0]
|
||||
|
|
@ -2887,13 +2921,16 @@ class CmdExamine(ObjManipCommand):
|
|||
# is not so common anyway.
|
||||
|
||||
obj = None
|
||||
obj_name = objdef["name"] # name
|
||||
obj_name = objdef["name"] # name
|
||||
obj_attrs = objdef["attrs"] # /attrs
|
||||
|
||||
# identify object type, in prio account - script - channel
|
||||
object_type = "object"
|
||||
if (utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount")
|
||||
or "account" in self.switches or obj_name.startswith("*")):
|
||||
if (
|
||||
utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount")
|
||||
or "account" in self.switches
|
||||
or obj_name.startswith("*")
|
||||
):
|
||||
object_type = "account"
|
||||
elif "script" in self.switches:
|
||||
object_type = "script"
|
||||
|
|
@ -3293,7 +3330,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
"start": "|gStarted|n",
|
||||
"stop": "|RStopped|n",
|
||||
"pause": "|Paused|n",
|
||||
"delete": "|rDeleted|n"
|
||||
"delete": "|rDeleted|n",
|
||||
}
|
||||
|
||||
def _search_script(self, args):
|
||||
|
|
@ -3307,7 +3344,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
return scripts
|
||||
if "-" in args:
|
||||
# may be a dbref-range
|
||||
val1, val2 = (dbref(part.strip()) for part in args.split('-', 1))
|
||||
val1, val2 = (dbref(part.strip()) for part in args.split("-", 1))
|
||||
if val1 and val2:
|
||||
scripts = ScriptDB.objects.filter(id__in=(range(val1, val2 + 1)))
|
||||
if scripts:
|
||||
|
|
@ -3348,11 +3385,14 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
if obj.scripts.add(self.rhs, autostart=True):
|
||||
caller.msg(
|
||||
f"Script |w{self.rhs}|n successfully added and "
|
||||
f"started on {obj.get_display_name(caller)}.")
|
||||
f"started on {obj.get_display_name(caller)}."
|
||||
)
|
||||
else:
|
||||
caller.msg(f"Script {self.rhs} could not be added and/or started "
|
||||
f"on {obj.get_display_name(caller)} (or it started and "
|
||||
"immediately shut down).")
|
||||
caller.msg(
|
||||
f"Script {self.rhs} could not be added and/or started "
|
||||
f"on {obj.get_display_name(caller)} (or it started and "
|
||||
"immediately shut down)."
|
||||
)
|
||||
else:
|
||||
# just show all scripts on object
|
||||
scripts = ScriptDB.objects.filter(db_obj=obj)
|
||||
|
|
@ -3374,12 +3414,15 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
new_script = None
|
||||
|
||||
if new_script:
|
||||
caller.msg(f"Global Script Created - "
|
||||
f"{new_script.key} ({new_script.typeclass_path})")
|
||||
caller.msg(
|
||||
f"Global Script Created - "
|
||||
f"{new_script.key} ({new_script.typeclass_path})"
|
||||
)
|
||||
ScriptEvMore(caller, [new_script], session=self.session)
|
||||
else:
|
||||
caller.msg(f"Global Script |rNOT|n Created |r(see log)|n - "
|
||||
f"arguments: {self.args}")
|
||||
caller.msg(
|
||||
f"Global Script |rNOT|n Created |r(see log)|n - " f"arguments: {self.args}"
|
||||
)
|
||||
|
||||
elif scripts or obj:
|
||||
# modification switches - must operate on existing scripts
|
||||
|
|
@ -3388,9 +3431,11 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
scripts = ScriptDB.objects.filter(db_obj=obj)
|
||||
|
||||
if scripts.count() > 1:
|
||||
ret = yield(f"Multiple scripts found: {scripts}. Are you sure you want to "
|
||||
"operate on all of them? [Y]/N? ")
|
||||
if ret.lower() in ('n', 'no'):
|
||||
ret = yield (
|
||||
f"Multiple scripts found: {scripts}. Are you sure you want to "
|
||||
"operate on all of them? [Y]/N? "
|
||||
)
|
||||
if ret.lower() in ("n", "no"):
|
||||
caller.msg("Aborted.")
|
||||
return
|
||||
|
||||
|
|
@ -3406,11 +3451,14 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
|||
getattr(script, switch)()
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
msgs.append(f"{scripttype} |rNOT|n {verb} |r(see log)|n - "
|
||||
f"{script_key} ({script_typeclass_path})|n")
|
||||
msgs.append(
|
||||
f"{scripttype} |rNOT|n {verb} |r(see log)|n - "
|
||||
f"{script_key} ({script_typeclass_path})|n"
|
||||
)
|
||||
else:
|
||||
msgs.append(f"{scripttype} {verb} - "
|
||||
f"{script_key} ({script_typeclass_path})")
|
||||
msgs.append(
|
||||
f"{scripttype} {verb} - " f"{script_key} ({script_typeclass_path})"
|
||||
)
|
||||
caller.msg("\n".join(msgs))
|
||||
if "delete" not in self.switches:
|
||||
ScriptEvMore(caller, [script], session=self.session)
|
||||
|
|
@ -3488,7 +3536,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
|||
)
|
||||
|
||||
# last N table
|
||||
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim): ]
|
||||
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim) :]
|
||||
latesttable = self.styled_table(
|
||||
"|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table"
|
||||
)
|
||||
|
|
@ -3620,14 +3668,18 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# check any locks
|
||||
if not (caller.permissions.check("Admin") or obj_to_teleport.access(caller, "teleport")):
|
||||
caller.msg(f"{obj_to_teleport} 'teleport'-lock blocks you from teleporting "
|
||||
"it anywhere.")
|
||||
caller.msg(
|
||||
f"{obj_to_teleport} 'teleport'-lock blocks you from teleporting " "it anywhere."
|
||||
)
|
||||
return
|
||||
|
||||
if not (caller.permissions.check("Admin")
|
||||
or destination.access(obj_to_teleport, "teleport_here")):
|
||||
caller.msg(f"{destination} 'teleport_here'-lock blocks {obj_to_teleport} from "
|
||||
"moving there.")
|
||||
if not (
|
||||
caller.permissions.check("Admin")
|
||||
or destination.access(obj_to_teleport, "teleport_here")
|
||||
):
|
||||
caller.msg(
|
||||
f"{destination} 'teleport_here'-lock blocks {obj_to_teleport} from " "moving there."
|
||||
)
|
||||
return
|
||||
|
||||
# try the teleport
|
||||
|
|
@ -3636,8 +3688,11 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
|||
obj_to_teleport.location = destination
|
||||
caller.msg(f"Teleported {obj_to_teleport} None -> {destination}")
|
||||
elif obj_to_teleport.move_to(
|
||||
destination, quiet="quiet" in self.switches,
|
||||
emit_to_obj=caller, use_destination="intoexit" not in self.switches):
|
||||
destination,
|
||||
quiet="quiet" in self.switches,
|
||||
emit_to_obj=caller,
|
||||
use_destination="intoexit" not in self.switches,
|
||||
):
|
||||
|
||||
if obj_to_teleport == caller:
|
||||
caller.msg(f"Teleported to {destination}.")
|
||||
|
|
@ -3995,7 +4050,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
|||
self.caller.msg("No prototypes found.")
|
||||
|
||||
def _list_prototypes(self, key=None, tags=None):
|
||||
"""Display prototypes as a list, optionally limited by key/tags. """
|
||||
"""Display prototypes as a list, optionally limited by key/tags."""
|
||||
protlib.list_prototypes(self.caller, key=key, tags=tags, session=self.session)
|
||||
|
||||
@interactive
|
||||
|
|
@ -4039,7 +4094,9 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
try:
|
||||
n_updated = spawner.batch_update_objects_with_prototype(
|
||||
prototype, objects=existing_objects, caller=caller,
|
||||
prototype,
|
||||
objects=existing_objects,
|
||||
caller=caller,
|
||||
)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
|
|
|||
|
|
@ -20,16 +20,15 @@ from evennia.utils.evmenu import ask_yes_no
|
|||
|
||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
CHANNEL_DEFAULT_TYPECLASS = class_from_module(
|
||||
settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS)
|
||||
settings.BASE_CHANNEL_TYPECLASS, fallback=settings.FALLBACK_CHANNEL_TYPECLASS
|
||||
)
|
||||
|
||||
|
||||
# limit symbol import for API
|
||||
__all__ = (
|
||||
"CmdChannel",
|
||||
"CmdObjectChannel",
|
||||
|
||||
"CmdPage",
|
||||
|
||||
"CmdIRC2Chan",
|
||||
"CmdIRCStatus",
|
||||
"CmdRSS2Chan",
|
||||
|
|
@ -207,6 +206,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
|
||||
|
||||
"""
|
||||
|
||||
key = "@channel"
|
||||
aliases = ["@chan", "@channels"]
|
||||
help_category = "Comms"
|
||||
|
|
@ -215,8 +215,25 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
# the manage: lock controls access to /create/destroy/desc/lock/unlock switches
|
||||
locks = "cmd:not pperm(channel_banned);admin:all();manage:all();changelocks:perm(Admin)"
|
||||
switch_options = (
|
||||
"list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias",
|
||||
"create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",)
|
||||
"list",
|
||||
"all",
|
||||
"history",
|
||||
"sub",
|
||||
"unsub",
|
||||
"mute",
|
||||
"unmute",
|
||||
"alias",
|
||||
"unalias",
|
||||
"create",
|
||||
"destroy",
|
||||
"desc",
|
||||
"lock",
|
||||
"unlock",
|
||||
"boot",
|
||||
"ban",
|
||||
"unban",
|
||||
"who",
|
||||
)
|
||||
# disable this in child command classes if wanting on-character channels
|
||||
account_caller = True
|
||||
|
||||
|
|
@ -253,17 +270,24 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname, exact=exact)
|
||||
|
||||
# check permissions
|
||||
channels = [channel for channel in channels
|
||||
if channel.access(caller, 'listen') or channel.access(caller, 'control')]
|
||||
channels = [
|
||||
channel
|
||||
for channel in channels
|
||||
if channel.access(caller, "listen") or channel.access(caller, "control")
|
||||
]
|
||||
|
||||
if handle_errors:
|
||||
if not channels:
|
||||
self.msg(f"No channel found matching '{channelname}' "
|
||||
"(could also be due to missing access).")
|
||||
self.msg(
|
||||
f"No channel found matching '{channelname}' "
|
||||
"(could also be due to missing access)."
|
||||
)
|
||||
return None
|
||||
elif len(channels) > 1:
|
||||
self.msg("Multiple possible channel matches/alias for "
|
||||
"'{channelname}':\n" + ", ".join(chan.key for chan in channels))
|
||||
self.msg(
|
||||
"Multiple possible channel matches/alias for "
|
||||
"'{channelname}':\n" + ", ".join(chan.key for chan in channels)
|
||||
)
|
||||
return None
|
||||
return channels[0]
|
||||
else:
|
||||
|
|
@ -312,6 +336,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
return self.msg(
|
||||
"".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines)
|
||||
)
|
||||
|
||||
# asynchronously tail the log file
|
||||
tail_log_file(log_file, start_index, 20, callback=send_msg)
|
||||
|
||||
|
|
@ -491,7 +516,8 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
|
||||
|
||||
new_chan = create.create_channel(
|
||||
name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass)
|
||||
name, aliases=aliases, desc=description, locks=lockstring, typeclass=typeclass
|
||||
)
|
||||
self.sub_to_channel(new_chan)
|
||||
return new_chan, ""
|
||||
|
||||
|
|
@ -514,14 +540,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
channel_key = channel.key
|
||||
if message is None:
|
||||
message = (f"|rChannel {channel_key} is being destroyed. "
|
||||
"Make sure to clean any channel aliases.|n")
|
||||
message = (
|
||||
f"|rChannel {channel_key} is being destroyed. "
|
||||
"Make sure to clean any channel aliases.|n"
|
||||
)
|
||||
if message:
|
||||
channel.msg(message, senders=caller, bypass_mute=True)
|
||||
channel.delete()
|
||||
logger.log_sec(
|
||||
"Channel {} was deleted by {}".format(channel_key, caller)
|
||||
)
|
||||
logger.log_sec("Channel {} was deleted by {}".format(channel_key, caller))
|
||||
|
||||
def set_lock(self, channel, lockstring):
|
||||
"""
|
||||
|
|
@ -610,8 +636,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
if not quiet:
|
||||
channel.msg(f"{target.key} was booted from channel by {self.caller.key}.{reason}")
|
||||
|
||||
logger.log_sec(f"Channel Boot: {target} (Channel: {channel}, "
|
||||
f"Reason: {reason.strip()}, Caller: {self.caller}")
|
||||
logger.log_sec(
|
||||
f"Channel Boot: {target} (Channel: {channel}, "
|
||||
f"Reason: {reason.strip()}, Caller: {self.caller}"
|
||||
)
|
||||
return True, ""
|
||||
|
||||
def ban_user(self, channel, target, quiet=False, reason=""):
|
||||
|
|
@ -684,7 +712,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
caller = self.caller
|
||||
mute_list = list(channel.mutelist)
|
||||
online_list = channel.subscriptions.online()
|
||||
if channel.access(caller, 'control'):
|
||||
if channel.access(caller, "control"):
|
||||
# for those with channel control, show also offline users
|
||||
all_subs = list(channel.subscriptions.all())
|
||||
else:
|
||||
|
|
@ -694,8 +722,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
who_list = []
|
||||
for subscriber in all_subs:
|
||||
name = subscriber.get_display_name(caller)
|
||||
conditions = ("muting" if subscriber in mute_list else "",
|
||||
"offline" if subscriber not in online_list else "")
|
||||
conditions = (
|
||||
"muting" if subscriber in mute_list else "",
|
||||
"offline" if subscriber not in online_list else "",
|
||||
)
|
||||
conditions = [cond for cond in conditions if cond]
|
||||
cond_text = "(" + ", ".join(conditions) + ")" if conditions else ""
|
||||
who_list.append(f"{name}{cond_text}")
|
||||
|
|
@ -743,7 +773,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
"locks",
|
||||
"description",
|
||||
align="l",
|
||||
maxwidth=_DEFAULT_WIDTH
|
||||
maxwidth=_DEFAULT_WIDTH,
|
||||
)
|
||||
for chan in subscribed:
|
||||
|
||||
|
|
@ -756,14 +786,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
||||
comtable.add_row(
|
||||
*(
|
||||
chanid,
|
||||
"{key}{aliases}".format(
|
||||
key=chan.key,
|
||||
aliases=";"+ ";".join(chan.aliases.all()) if chan.aliases.all() else ""
|
||||
),
|
||||
my_aliases,
|
||||
locks,
|
||||
chan.db.desc
|
||||
chanid,
|
||||
"{key}{aliases}".format(
|
||||
key=chan.key,
|
||||
aliases=";" + ";".join(chan.aliases.all()) if chan.aliases.all() else "",
|
||||
),
|
||||
my_aliases,
|
||||
locks,
|
||||
chan.db.desc,
|
||||
)
|
||||
)
|
||||
return comtable
|
||||
|
|
@ -799,11 +829,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
substatus = "|gYes|n"
|
||||
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
||||
comtable.add_row(
|
||||
*(substatus,
|
||||
chan.key,
|
||||
",".join(chan.aliases.all()) if chan.aliases.all() else "",
|
||||
my_aliases,
|
||||
chan.db.desc))
|
||||
*(
|
||||
substatus,
|
||||
chan.key,
|
||||
",".join(chan.aliases.all()) if chan.aliases.all() else "",
|
||||
my_aliases,
|
||||
chan.db.desc,
|
||||
)
|
||||
)
|
||||
comtable.reformat_column(0, width=8)
|
||||
|
||||
return comtable
|
||||
|
|
@ -818,16 +851,17 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
switches = self.switches
|
||||
channel_names = [name for name in self.lhslist if name]
|
||||
|
||||
#from evennia import set_trace;set_trace()
|
||||
# from evennia import set_trace;set_trace()
|
||||
|
||||
if 'all' in switches:
|
||||
if "all" in switches:
|
||||
# show all available channels
|
||||
subscribed, available = self.list_channels()
|
||||
table = self.display_all_channels(subscribed, available)
|
||||
|
||||
self.msg(
|
||||
"\n|wAvailable channels|n (use no argument to "
|
||||
f"only show your subscriptions)\n{table}")
|
||||
f"only show your subscriptions)\n{table}"
|
||||
)
|
||||
return
|
||||
|
||||
if not channel_names:
|
||||
|
|
@ -835,15 +869,16 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
subscribed, _ = self.list_channels()
|
||||
table = self.display_subbed_channels(subscribed)
|
||||
|
||||
self.msg("\n|wChannel subscriptions|n "
|
||||
f"(use |w/all|n to see all available):\n{table}")
|
||||
self.msg(
|
||||
"\n|wChannel subscriptions|n " f"(use |w/all|n to see all available):\n{table}"
|
||||
)
|
||||
return
|
||||
|
||||
if not self.switches and not self.args:
|
||||
self.msg("Usage[/switches]: channel [= message]")
|
||||
return
|
||||
|
||||
if 'create' in switches:
|
||||
if "create" in switches:
|
||||
# create a new channel
|
||||
|
||||
if not self.access(caller, "manage"):
|
||||
|
|
@ -865,7 +900,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(err)
|
||||
return
|
||||
|
||||
if 'unalias' in switches:
|
||||
if "unalias" in switches:
|
||||
# remove a personal alias (no channel needed)
|
||||
alias = self.args.strip()
|
||||
if not alias:
|
||||
|
|
@ -884,12 +919,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
# channels without a space in their name), we need to check if the
|
||||
# first 'channel name' is in fact 'channelname text'
|
||||
no_rhs_channel_name = self.args.split(" ", 1)[0]
|
||||
possible_lhs_message = self.args[len(no_rhs_channel_name):]
|
||||
if possible_lhs_message.strip() == '=':
|
||||
possible_lhs_message = self.args[len(no_rhs_channel_name) :]
|
||||
if possible_lhs_message.strip() == "=":
|
||||
possible_lhs_message = ""
|
||||
channel_names.append(no_rhs_channel_name)
|
||||
|
||||
|
||||
channels = []
|
||||
errors = []
|
||||
for channel_name in channel_names:
|
||||
|
|
@ -897,16 +931,20 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
# 'listen/control' perms.
|
||||
found_channels = self.search_channel(channel_name, exact=False, handle_errors=False)
|
||||
if not found_channels:
|
||||
errors.append(f"No channel found matching '{channel_name}' "
|
||||
"(could also be due to missing access).")
|
||||
errors.append(
|
||||
f"No channel found matching '{channel_name}' "
|
||||
"(could also be due to missing access)."
|
||||
)
|
||||
elif len(found_channels) > 1:
|
||||
errors.append("Multiple possible channel matches/alias for "
|
||||
"'{channel_name}':\n" + ", ".join(chan.key for chan in found_channels))
|
||||
errors.append(
|
||||
"Multiple possible channel matches/alias for "
|
||||
"'{channel_name}':\n" + ", ".join(chan.key for chan in found_channels)
|
||||
)
|
||||
else:
|
||||
channels.append(found_channels[0])
|
||||
|
||||
if not channels:
|
||||
self.msg('\n'.join(errors))
|
||||
self.msg("\n".join(errors))
|
||||
return
|
||||
|
||||
# we have at least one channel at this point
|
||||
|
|
@ -925,30 +963,35 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
if channel in subscribed:
|
||||
table = self.display_subbed_channels([channel])
|
||||
header = f"Channel |w{channel.key}|n"
|
||||
self.msg(f"{header}\n(use |w{channel.key} <msg>|n (or a channel-alias) "
|
||||
f"to chat and the 'channel' command "
|
||||
f"to customize)\n{table}")
|
||||
self.msg(
|
||||
f"{header}\n(use |w{channel.key} <msg>|n (or a channel-alias) "
|
||||
f"to chat and the 'channel' command "
|
||||
f"to customize)\n{table}"
|
||||
)
|
||||
elif channel in available:
|
||||
table = self.display_all_channels([], [channel])
|
||||
self.msg(
|
||||
"\n|wNot subscribed to this channel|n (use /list to "
|
||||
f"show all subscriptions)\n{table}")
|
||||
f"show all subscriptions)\n{table}"
|
||||
)
|
||||
return
|
||||
|
||||
if 'history' in switches or 'hist' in switches:
|
||||
if "history" in switches or "hist" in switches:
|
||||
# view channel history
|
||||
|
||||
index = self.rhs or 0
|
||||
try:
|
||||
index = max(0, int(index))
|
||||
except ValueError:
|
||||
self.msg("The history index (describing how many lines to go back) "
|
||||
"must be an integer >= 0.")
|
||||
self.msg(
|
||||
"The history index (describing how many lines to go back) "
|
||||
"must be an integer >= 0."
|
||||
)
|
||||
return
|
||||
self.get_channel_history(channel, start_index=index)
|
||||
return
|
||||
|
||||
if 'sub' in switches:
|
||||
if "sub" in switches:
|
||||
# subscribe to a channel
|
||||
aliases = []
|
||||
if self.rhs:
|
||||
|
|
@ -957,26 +1000,29 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
if success:
|
||||
for alias in aliases:
|
||||
self.add_alias(channel, alias)
|
||||
alias_txt = ', '.join(aliases)
|
||||
alias_txt = f" using alias(es) {alias_txt}" if aliases else ''
|
||||
self.msg("You are now subscribed "
|
||||
f"to the channel {channel.key}{alias_txt}. Use /alias to "
|
||||
"add additional aliases for referring to the channel.")
|
||||
alias_txt = ", ".join(aliases)
|
||||
alias_txt = f" using alias(es) {alias_txt}" if aliases else ""
|
||||
self.msg(
|
||||
"You are now subscribed "
|
||||
f"to the channel {channel.key}{alias_txt}. Use /alias to "
|
||||
"add additional aliases for referring to the channel."
|
||||
)
|
||||
else:
|
||||
self.msg(err)
|
||||
return
|
||||
|
||||
if 'unsub' in switches:
|
||||
if "unsub" in switches:
|
||||
# un-subscribe from a channel
|
||||
success, err = self.unsub_from_channel(channel)
|
||||
if success:
|
||||
self.msg(f"You un-subscribed from channel {channel.key}. "
|
||||
"All aliases were cleared.")
|
||||
self.msg(
|
||||
f"You un-subscribed from channel {channel.key}. " "All aliases were cleared."
|
||||
)
|
||||
else:
|
||||
self.msg(err)
|
||||
return
|
||||
|
||||
if 'alias' in switches:
|
||||
if "alias" in switches:
|
||||
# create a new personal alias for a channel
|
||||
alias = self.rhs
|
||||
if not alias:
|
||||
|
|
@ -986,7 +1032,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(f"Added/updated your alias '{alias}' for channel {channel.key}.")
|
||||
return
|
||||
|
||||
if 'mute' in switches:
|
||||
if "mute" in switches:
|
||||
# mute a given channel
|
||||
success, err = self.mute_channel(channel)
|
||||
if success:
|
||||
|
|
@ -995,7 +1041,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(err)
|
||||
return
|
||||
|
||||
if 'unmute' in switches:
|
||||
if "unmute" in switches:
|
||||
# unmute a given channel
|
||||
success, err = self.unmute_channel(channel)
|
||||
if success:
|
||||
|
|
@ -1004,7 +1050,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(err)
|
||||
return
|
||||
|
||||
if 'destroy' in switches or 'delete' in switches:
|
||||
if "destroy" in switches or "delete" in switches:
|
||||
# destroy a channel we control
|
||||
|
||||
if not self.access(caller, "manage"):
|
||||
|
|
@ -1028,10 +1074,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
"remove all users' aliases. {options}?",
|
||||
yes_action=_perform_delete,
|
||||
no_action="Aborted.",
|
||||
default="N"
|
||||
default="N",
|
||||
)
|
||||
|
||||
if 'desc' in switches:
|
||||
if "desc" in switches:
|
||||
# set channel description
|
||||
|
||||
if not self.access(caller, "manage"):
|
||||
|
|
@ -1051,7 +1097,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.set_desc(channel, desc)
|
||||
self.msg("Updated channel description.")
|
||||
|
||||
if 'lock' in switches:
|
||||
if "lock" in switches:
|
||||
# add a lockstring to channel
|
||||
|
||||
if not self.access(caller, "changelocks"):
|
||||
|
|
@ -1075,7 +1121,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(f"Could not add/update lock: {err}")
|
||||
return
|
||||
|
||||
if 'unlock' in switches:
|
||||
if "unlock" in switches:
|
||||
# remove/update lockstring from channel
|
||||
|
||||
if not self.access(caller, "changelocks"):
|
||||
|
|
@ -1099,7 +1145,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(f"Could not remove lock: {err}")
|
||||
return
|
||||
|
||||
if 'boot' in switches:
|
||||
if "boot" in switches:
|
||||
# boot a user from channel(s)
|
||||
|
||||
if not self.access(caller, "admin"):
|
||||
|
|
@ -1134,8 +1180,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}")
|
||||
|
||||
channames = ", ".join(chan.key for chan in channels)
|
||||
reasonwarn = (". Also note that your reason will be echoed to the channel"
|
||||
if reason else '')
|
||||
reasonwarn = (
|
||||
". Also note that your reason will be echoed to the channel" if reason else ""
|
||||
)
|
||||
ask_yes_no(
|
||||
caller,
|
||||
prompt=f"Are you sure you want to boot user {target.key} from "
|
||||
|
|
@ -1143,11 +1190,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
"{options}?",
|
||||
yes_action=_boot_user,
|
||||
no_action="Aborted.",
|
||||
default="Y"
|
||||
default="Y",
|
||||
)
|
||||
return
|
||||
|
||||
if 'ban' in switches:
|
||||
if "ban" in switches:
|
||||
# ban a user from channel(s)
|
||||
|
||||
if not self.access(caller, "admin"):
|
||||
|
|
@ -1161,8 +1208,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(f"You need 'control'-access to view bans on channel {channel.key}")
|
||||
return
|
||||
|
||||
bans = ["Channel bans "
|
||||
"(to ban, use channel/ban channel[,channel,...] = username [:reason]"]
|
||||
bans = [
|
||||
"Channel bans "
|
||||
"(to ban, use channel/ban channel[,channel,...] = username [:reason]"
|
||||
]
|
||||
bans.extend(self.channel_list_bans(channel))
|
||||
self.msg("\n".join(bans))
|
||||
return
|
||||
|
|
@ -1191,8 +1240,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}")
|
||||
|
||||
channames = ", ".join(chan.key for chan in channels)
|
||||
reasonwarn = (". Also note that your reason will be echoed to the channel"
|
||||
if reason else '')
|
||||
reasonwarn = (
|
||||
". Also note that your reason will be echoed to the channel" if reason else ""
|
||||
)
|
||||
ask_yes_no(
|
||||
caller,
|
||||
f"Are you sure you want to ban user {target.key} from "
|
||||
|
|
@ -1203,7 +1253,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
|||
)
|
||||
return
|
||||
|
||||
if 'unban' in switches:
|
||||
if "unban" in switches:
|
||||
# unban a previously banned user from channel
|
||||
|
||||
if not self.access(caller, "admin"):
|
||||
|
|
@ -1414,7 +1464,6 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
|||
receiver=receiver,
|
||||
message=page.message,
|
||||
)
|
||||
|
||||
)
|
||||
lastpages = "\n ".join(listing)
|
||||
|
||||
|
|
@ -1465,6 +1514,7 @@ def _list_bots(cmd):
|
|||
else:
|
||||
return "No irc bots found."
|
||||
|
||||
|
||||
class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
Link an evennia channel to an external IRC channel
|
||||
|
|
|
|||
|
|
@ -382,10 +382,13 @@ class CmdInventory(COMMAND_DEFAULT_CLASS):
|
|||
string = "You are not carrying anything."
|
||||
else:
|
||||
from evennia.utils.ansi import raw as raw_ansi
|
||||
|
||||
table = self.styled_table(border="header")
|
||||
for item in items:
|
||||
table.add_row(f"|C{item.name}|n",
|
||||
"{}|n".format(utils.crop(raw_ansi(item.db.desc or ""), width=50) or ""))
|
||||
table.add_row(
|
||||
f"|C{item.name}|n",
|
||||
"{}|n".format(utils.crop(raw_ansi(item.db.desc or ""), width=50) or ""),
|
||||
)
|
||||
string = f"|wYou are carrying:\n{table}"
|
||||
self.caller.msg(string)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,11 +19,7 @@ from evennia.utils import create, evmore
|
|||
from evennia.utils.ansi import ANSIString
|
||||
from evennia.help.filehelp import FILE_HELP_ENTRIES
|
||||
from evennia.utils.eveditor import EvEditor
|
||||
from evennia.utils.utils import (
|
||||
class_from_module,
|
||||
inherits_from,
|
||||
format_grid, pad
|
||||
)
|
||||
from evennia.utils.utils import class_from_module, inherits_from, format_grid, pad
|
||||
from evennia.help.utils import help_search_with_index, parse_entry_for_subcategories
|
||||
|
||||
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
|
||||
|
|
@ -35,12 +31,14 @@ HELP_CLICKABLE_TOPICS = settings.HELP_CLICKABLE_TOPICS
|
|||
# limit symbol import for API
|
||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||
|
||||
|
||||
@dataclass
|
||||
class HelpCategory:
|
||||
"""
|
||||
Mock 'help entry' to search categories with the same code.
|
||||
|
||||
"""
|
||||
|
||||
key: str
|
||||
|
||||
@property
|
||||
|
|
@ -113,7 +111,10 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
if type(self).help_more:
|
||||
usemore = True
|
||||
|
||||
if self.session and self.session.protocol_key in ("websocket", "ajax/comet",):
|
||||
if self.session and self.session.protocol_key in (
|
||||
"websocket",
|
||||
"ajax/comet",
|
||||
):
|
||||
try:
|
||||
options = self.account.db._saved_webclient_options
|
||||
if options and options["helppopup"]:
|
||||
|
|
@ -127,8 +128,15 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
self.msg(text=(text, {"type": "help"}))
|
||||
|
||||
def format_help_entry(self, topic="", help_text="", aliases=None, suggested=None,
|
||||
subtopics=None, click_topics=True):
|
||||
def format_help_entry(
|
||||
self,
|
||||
topic="",
|
||||
help_text="",
|
||||
aliases=None,
|
||||
suggested=None,
|
||||
subtopics=None,
|
||||
click_topics=True,
|
||||
):
|
||||
"""This visually formats the help entry.
|
||||
This method can be overriden to customize the way a help
|
||||
entry is displayed.
|
||||
|
|
@ -152,28 +160,24 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
title = f"|CHelp for |w{topic}|n" if topic else "|rNo help found|n"
|
||||
|
||||
if aliases:
|
||||
aliases = (
|
||||
" |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases))
|
||||
)
|
||||
aliases = " |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases))
|
||||
else:
|
||||
aliases = ''
|
||||
aliases = ""
|
||||
|
||||
help_text = "\n" + dedent(help_text.strip('\n')) if help_text else ""
|
||||
help_text = "\n" + dedent(help_text.strip("\n")) if help_text else ""
|
||||
|
||||
if subtopics:
|
||||
if click_topics:
|
||||
subtopics = [
|
||||
f"|lchelp {topic}/{subtop}|lt|w{topic}/{subtop}|n|le"
|
||||
for subtop in subtopics
|
||||
]
|
||||
f"|lchelp {topic}/{subtop}|lt|w{topic}/{subtop}|n|le" for subtop in subtopics
|
||||
]
|
||||
else:
|
||||
subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics]
|
||||
subtopics = (
|
||||
"\n|CSubtopics:|n\n {}".format(
|
||||
"\n ".join(format_grid(subtopics, width=self.client_width())))
|
||||
subtopics = "\n|CSubtopics:|n\n {}".format(
|
||||
"\n ".join(format_grid(subtopics, width=self.client_width()))
|
||||
)
|
||||
else:
|
||||
subtopics = ''
|
||||
subtopics = ""
|
||||
|
||||
if suggested:
|
||||
suggested = sorted(suggested)
|
||||
|
|
@ -181,12 +185,11 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
suggested = [f"|lchelp {sug}|lt|w{sug}|n|le" for sug in suggested]
|
||||
else:
|
||||
suggested = [f"|w{sug}|n" for sug in suggested]
|
||||
suggested = (
|
||||
"\n|COther topic suggestions:|n\n{}".format(
|
||||
"\n ".join(format_grid(suggested, width=self.client_width())))
|
||||
suggested = "\n|COther topic suggestions:|n\n{}".format(
|
||||
"\n ".join(format_grid(suggested, width=self.client_width()))
|
||||
)
|
||||
else:
|
||||
suggested = ''
|
||||
suggested = ""
|
||||
|
||||
end = start
|
||||
|
||||
|
|
@ -194,8 +197,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
return "\n".join(part.rstrip() for part in partorder if part)
|
||||
|
||||
def format_help_index(self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False,
|
||||
click_topics=True):
|
||||
def format_help_index(
|
||||
self, cmd_help_dict=None, db_help_dict=None, title_lone_category=False, click_topics=True
|
||||
):
|
||||
"""Output a category-ordered g for displaying the main help, grouped by
|
||||
category.
|
||||
|
||||
|
|
@ -219,6 +223,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
commands and topics.
|
||||
|
||||
"""
|
||||
|
||||
def _group_by_category(help_dict):
|
||||
grid = []
|
||||
verbatim_elements = []
|
||||
|
|
@ -231,9 +236,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# make the help topics clickable
|
||||
if click_topics:
|
||||
entries = [
|
||||
f'|lchelp {entry}|lt{entry}|le' for entry in entries
|
||||
]
|
||||
entries = [f"|lchelp {entry}|lt{entry}|le" for entry in entries]
|
||||
|
||||
# add the entries to the grid
|
||||
grid.extend(entries)
|
||||
|
|
@ -243,7 +246,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
category_str = f"-- {category.title()} "
|
||||
grid.append(
|
||||
ANSIString(
|
||||
self.index_category_clr + category_str
|
||||
self.index_category_clr
|
||||
+ category_str
|
||||
+ "-" * (width - len(category_str))
|
||||
+ self.index_topic_clr
|
||||
)
|
||||
|
|
@ -255,9 +259,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# make the help topics clickable
|
||||
if click_topics:
|
||||
entries = [
|
||||
f'|lchelp {entry}|lt{entry}|le' for entry in entries
|
||||
]
|
||||
entries = [f"|lchelp {entry}|lt{entry}|le" for entry in entries]
|
||||
|
||||
# add the entries to the grid
|
||||
grid.extend(entries)
|
||||
|
|
@ -272,18 +274,22 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
if any(cmd_help_dict.values()):
|
||||
# get the command-help entries by-category
|
||||
sep1 = (self.index_type_separator_clr
|
||||
+ pad("Commands", width=width, fillchar='-')
|
||||
+ self.index_topic_clr)
|
||||
sep1 = (
|
||||
self.index_type_separator_clr
|
||||
+ pad("Commands", width=width, fillchar="-")
|
||||
+ self.index_topic_clr
|
||||
)
|
||||
grid, verbatim_elements = _group_by_category(cmd_help_dict)
|
||||
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
||||
cmd_grid = ANSIString("\n").join(gridrows) if gridrows else ""
|
||||
|
||||
if any(db_help_dict.values()):
|
||||
# get db-based help entries by-category
|
||||
sep2 = (self.index_type_separator_clr
|
||||
+ pad("Game & World", width=width, fillchar='-')
|
||||
+ self.index_topic_clr)
|
||||
sep2 = (
|
||||
self.index_type_separator_clr
|
||||
+ pad("Game & World", width=width, fillchar="-")
|
||||
+ self.index_topic_clr
|
||||
)
|
||||
grid, verbatim_elements = _group_by_category(db_help_dict)
|
||||
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
||||
db_grid = ANSIString("\n").join(gridrows) if gridrows else ""
|
||||
|
|
@ -316,9 +322,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
"""
|
||||
if inherits_from(cmd_or_topic, "evennia.commands.command.Command"):
|
||||
return cmd_or_topic.auto_help and cmd_or_topic.access(caller, 'read', default=True)
|
||||
return cmd_or_topic.auto_help and cmd_or_topic.access(caller, "read", default=True)
|
||||
else:
|
||||
return cmd_or_topic.access(caller, 'read', default=True)
|
||||
return cmd_or_topic.access(caller, "read", default=True)
|
||||
|
||||
def can_list_topic(self, cmd_or_topic, caller):
|
||||
"""
|
||||
|
|
@ -355,12 +361,12 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
)
|
||||
|
||||
if has_view:
|
||||
return cmd_or_topic.access(caller, 'view', default=True)
|
||||
return cmd_or_topic.access(caller, "view", default=True)
|
||||
else:
|
||||
# no explicit 'view' lock - use the 'read' lock
|
||||
return cmd_or_topic.access(caller, 'read', default=True)
|
||||
return cmd_or_topic.access(caller, "read", default=True)
|
||||
|
||||
def collect_topics(self, caller, mode='list'):
|
||||
def collect_topics(self, caller, mode="list"):
|
||||
"""
|
||||
Collect help topics from all sources (cmd/db/file).
|
||||
|
||||
|
|
@ -383,43 +389,45 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
cmdset.make_unique(caller)
|
||||
# retrieve all available commands and database / file-help topics.
|
||||
# also check the 'cmd:' lock here
|
||||
cmd_help_topics = [cmd for cmd in cmdset if cmd and cmd.access(caller, 'cmd')]
|
||||
cmd_help_topics = [cmd for cmd in cmdset if cmd and cmd.access(caller, "cmd")]
|
||||
# get all file-based help entries, checking perms
|
||||
file_help_topics = {
|
||||
topic.key.lower().strip(): topic
|
||||
for topic in FILE_HELP_ENTRIES.all()
|
||||
}
|
||||
file_help_topics = {topic.key.lower().strip(): topic for topic in FILE_HELP_ENTRIES.all()}
|
||||
# get db-based help entries, checking perms
|
||||
db_help_topics = {
|
||||
topic.key.lower().strip(): topic
|
||||
for topic in HelpEntry.objects.all()
|
||||
}
|
||||
if mode == 'list':
|
||||
db_help_topics = {topic.key.lower().strip(): topic for topic in HelpEntry.objects.all()}
|
||||
if mode == "list":
|
||||
# check the view lock for all help entries/commands and determine key
|
||||
cmd_help_topics = {
|
||||
cmd.auto_help_display_key
|
||||
if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||
for cmd in cmd_help_topics if self.can_list_topic(cmd, caller)}
|
||||
cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||
for cmd in cmd_help_topics
|
||||
if self.can_list_topic(cmd, caller)
|
||||
}
|
||||
db_help_topics = {
|
||||
key: entry for key, entry in db_help_topics.items()
|
||||
key: entry
|
||||
for key, entry in db_help_topics.items()
|
||||
if self.can_list_topic(entry, caller)
|
||||
}
|
||||
file_help_topics = {
|
||||
key: entry for key, entry in file_help_topics.items()
|
||||
if self.can_list_topic(entry, caller)}
|
||||
key: entry
|
||||
for key, entry in file_help_topics.items()
|
||||
if self.can_list_topic(entry, caller)
|
||||
}
|
||||
else:
|
||||
# query - check the read lock on entries
|
||||
cmd_help_topics = {
|
||||
cmd.auto_help_display_key
|
||||
if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||
for cmd in cmd_help_topics if self.can_read_topic(cmd, caller)}
|
||||
cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||
for cmd in cmd_help_topics
|
||||
if self.can_read_topic(cmd, caller)
|
||||
}
|
||||
db_help_topics = {
|
||||
key: entry for key, entry in db_help_topics.items()
|
||||
key: entry
|
||||
for key, entry in db_help_topics.items()
|
||||
if self.can_read_topic(entry, caller)
|
||||
}
|
||||
file_help_topics = {
|
||||
key: entry for key, entry in file_help_topics.items()
|
||||
if self.can_read_topic(entry, caller)}
|
||||
key: entry
|
||||
for key, entry in file_help_topics.items()
|
||||
if self.can_read_topic(entry, caller)
|
||||
}
|
||||
|
||||
return cmd_help_topics, db_help_topics, file_help_topics
|
||||
|
||||
|
|
@ -452,9 +460,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
# return of this will either be a HelpCategory, a Command or a
|
||||
# HelpEntry/FileHelpEntry.
|
||||
matches, suggestions = help_search_with_index(
|
||||
match_query, entries,
|
||||
suggestion_maxnum=self.suggestion_maxnum,
|
||||
fields=search_fields
|
||||
match_query, entries, suggestion_maxnum=self.suggestion_maxnum, fields=search_fields
|
||||
)
|
||||
if matches:
|
||||
match = matches[0]
|
||||
|
|
@ -478,8 +484,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
# parse the query
|
||||
|
||||
if self.args:
|
||||
self.subtopics = [part.strip().lower()
|
||||
for part in self.args.split(self.subtopic_separator_char)]
|
||||
self.subtopics = [
|
||||
part.strip().lower() for part in self.args.split(self.subtopic_separator_char)
|
||||
]
|
||||
self.topic = self.subtopics.pop(0)
|
||||
else:
|
||||
self.topic = ""
|
||||
|
|
@ -505,7 +512,6 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
return key[1:]
|
||||
return key
|
||||
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Run the dynamic help entry creator.
|
||||
|
|
@ -518,8 +524,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
# list all available help entries, grouped by category. We want to
|
||||
# build dictionaries {category: [topic, topic, ...], ...}
|
||||
|
||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
||||
self.collect_topics(caller, mode='list')
|
||||
cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics(
|
||||
caller, mode="list"
|
||||
)
|
||||
|
||||
# db-topics override file-based ones
|
||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
||||
|
|
@ -538,21 +545,21 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
file_db_help_by_category[entry.help_category].append(key)
|
||||
|
||||
# generate the index and display
|
||||
output = self.format_help_index(cmd_help_by_category,
|
||||
file_db_help_by_category,
|
||||
click_topics=clickable_topics)
|
||||
output = self.format_help_index(
|
||||
cmd_help_by_category, file_db_help_by_category, click_topics=clickable_topics
|
||||
)
|
||||
self.msg_help(output)
|
||||
|
||||
return
|
||||
|
||||
# search for a specific entry. We need to check for 'read' access here before
|
||||
# building the set of possibilities.
|
||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
||||
self.collect_topics(caller, mode='query')
|
||||
cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics(
|
||||
caller, mode="query"
|
||||
)
|
||||
|
||||
# get a collection of all keys + aliases to be able to strip prefixes like @
|
||||
key_and_aliases = set(
|
||||
chain(*(cmd._keyaliases for cmd in cmd_help_topics.values())))
|
||||
key_and_aliases = set(chain(*(cmd._keyaliases for cmd in cmd_help_topics.values())))
|
||||
|
||||
# db-help topics takes priority over file-help
|
||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
||||
|
|
@ -561,8 +568,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
all_topics = {**file_db_help_topics, **cmd_help_topics}
|
||||
|
||||
# get all categories
|
||||
all_categories = list(set(
|
||||
HelpCategory(topic.help_category) for topic in all_topics.values()))
|
||||
all_categories = list(
|
||||
set(HelpCategory(topic.help_category) for topic in all_topics.values())
|
||||
)
|
||||
|
||||
# all available help options - will be searched in order. We also check # the
|
||||
# read-permission here.
|
||||
|
|
@ -586,23 +594,26 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
for match_query in [query, f"{query}*", f"*{query}"]:
|
||||
_, suggestions = help_search_with_index(
|
||||
match_query, entries,
|
||||
match_query,
|
||||
entries,
|
||||
suggestion_maxnum=self.suggestion_maxnum,
|
||||
fields=search_fields
|
||||
fields=search_fields,
|
||||
)
|
||||
if suggestions:
|
||||
help_text += (
|
||||
"\n... But matches where found within the help "
|
||||
"texts of the suggestions below.")
|
||||
suggestions = [self.strip_cmd_prefix(sugg, key_and_aliases)
|
||||
for sugg in suggestions]
|
||||
"texts of the suggestions below."
|
||||
)
|
||||
suggestions = [
|
||||
self.strip_cmd_prefix(sugg, key_and_aliases) for sugg in suggestions
|
||||
]
|
||||
break
|
||||
|
||||
output = self.format_help_entry(
|
||||
topic=None, # this will give a no-match style title
|
||||
help_text=help_text,
|
||||
suggested=suggestions,
|
||||
click_topics=clickable_topics
|
||||
click_topics=clickable_topics,
|
||||
)
|
||||
|
||||
self.msg_help(output)
|
||||
|
|
@ -612,14 +623,20 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
# no subtopics for categories - these are just lists of topics
|
||||
category = match.key
|
||||
category_lower = category.lower()
|
||||
cmds_in_category = [key for key, cmd in cmd_help_topics.items()
|
||||
if category_lower == cmd.help_category]
|
||||
topics_in_category = [key for key, topic in file_db_help_topics.items()
|
||||
if category_lower == topic.help_category]
|
||||
output = self.format_help_index({category: cmds_in_category},
|
||||
{category: topics_in_category},
|
||||
title_lone_category=True,
|
||||
click_topics=clickable_topics)
|
||||
cmds_in_category = [
|
||||
key for key, cmd in cmd_help_topics.items() if category_lower == cmd.help_category
|
||||
]
|
||||
topics_in_category = [
|
||||
key
|
||||
for key, topic in file_db_help_topics.items()
|
||||
if category_lower == topic.help_category
|
||||
]
|
||||
output = self.format_help_index(
|
||||
{category: cmds_in_category},
|
||||
{category: topics_in_category},
|
||||
title_lone_category=True,
|
||||
click_topics=clickable_topics,
|
||||
)
|
||||
self.msg_help(output)
|
||||
return
|
||||
|
||||
|
|
@ -674,7 +691,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
topic=topic,
|
||||
help_text=f"No help entry found for '{checked_topic}'",
|
||||
subtopics=subtopic_index,
|
||||
click_topics=clickable_topics
|
||||
click_topics=clickable_topics,
|
||||
)
|
||||
self.msg_help(output)
|
||||
return
|
||||
|
|
@ -702,7 +719,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
|||
aliases=aliases,
|
||||
subtopics=subtopic_index,
|
||||
suggested=suggested,
|
||||
click_topics=clickable_topics
|
||||
click_topics=clickable_topics,
|
||||
)
|
||||
|
||||
self.msg_help(output)
|
||||
|
|
@ -829,15 +846,17 @@ class CmdSetHelp(CmdHelp):
|
|||
|
||||
# check if we have an old entry with the same name
|
||||
|
||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
||||
self.collect_topics(self.caller, mode='query')
|
||||
cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics(
|
||||
self.caller, mode="query"
|
||||
)
|
||||
# db-help topics takes priority over file-help
|
||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
||||
# commands take priority over the other types
|
||||
all_topics = {**file_db_help_topics, **cmd_help_topics}
|
||||
# get all categories
|
||||
all_categories = list(set(
|
||||
HelpCategory(topic.help_category) for topic in all_topics.values()))
|
||||
all_categories = list(
|
||||
set(HelpCategory(topic.help_category) for topic in all_topics.values())
|
||||
)
|
||||
# all available help options - will be searched in order. We also check # the
|
||||
# read-permission here.
|
||||
entries = list(all_topics.values()) + all_categories
|
||||
|
|
@ -853,29 +872,35 @@ class CmdSetHelp(CmdHelp):
|
|||
if match:
|
||||
warning = None
|
||||
if isinstance(match, HelpCategory):
|
||||
warning = (f"'{querystr}' matches (or partially matches) the name of "
|
||||
"help-category '{match.key}'. If you continue, your help entry will "
|
||||
"take precedence and the category (or part of its name) *may* not "
|
||||
"be usable for grouping help entries anymore.")
|
||||
warning = (
|
||||
f"'{querystr}' matches (or partially matches) the name of "
|
||||
"help-category '{match.key}'. If you continue, your help entry will "
|
||||
"take precedence and the category (or part of its name) *may* not "
|
||||
"be usable for grouping help entries anymore."
|
||||
)
|
||||
elif inherits_from(match, "evennia.commands.command.Command"):
|
||||
warning = (f"'{querystr}' matches (or partially matches) the key/alias of "
|
||||
"Command '{match.key}'. Command-help take precedence over other "
|
||||
"help entries so your help *may* be impossible to reach for those "
|
||||
"with access to that command.")
|
||||
warning = (
|
||||
f"'{querystr}' matches (or partially matches) the key/alias of "
|
||||
"Command '{match.key}'. Command-help take precedence over other "
|
||||
"help entries so your help *may* be impossible to reach for those "
|
||||
"with access to that command."
|
||||
)
|
||||
elif inherits_from(match, "evennia.help.filehelp.FileHelpEntry"):
|
||||
warning = (f"'{querystr}' matches (or partially matches) the name/alias of the "
|
||||
f"file-based help topic '{match.key}'. File-help entries cannot be "
|
||||
"modified from in-game (they are files on-disk). If you continue, "
|
||||
"your help entry may shadow the file-based one's name partly or "
|
||||
"completely.")
|
||||
warning = (
|
||||
f"'{querystr}' matches (or partially matches) the name/alias of the "
|
||||
f"file-based help topic '{match.key}'. File-help entries cannot be "
|
||||
"modified from in-game (they are files on-disk). If you continue, "
|
||||
"your help entry may shadow the file-based one's name partly or "
|
||||
"completely."
|
||||
)
|
||||
if warning:
|
||||
# show a warning for a clashing help-entry type. Even if user accepts this
|
||||
# we don't break here since we may need to show warnings for other inputs.
|
||||
# We don't count this as an old-entry hit because we can't edit these
|
||||
# types of entries.
|
||||
self.msg(f"|rWarning:\n|r{warning}|n")
|
||||
repl = yield("|wDo you still want to continue? Y/[N]?|n")
|
||||
if repl.lower() not in ('y', 'yes'):
|
||||
repl = yield ("|wDo you still want to continue? Y/[N]?|n")
|
||||
if repl.lower() not in ("y", "yes"):
|
||||
self.msg("Aborted.")
|
||||
return
|
||||
else:
|
||||
|
|
@ -897,7 +922,11 @@ class CmdSetHelp(CmdHelp):
|
|||
helpentry = old_entry
|
||||
else:
|
||||
helpentry = create.create_help_entry(
|
||||
topicstr, self.rhs, category=category, locks=lockstring, aliases=aliases,
|
||||
topicstr,
|
||||
self.rhs,
|
||||
category=category,
|
||||
locks=lockstring,
|
||||
aliases=aliases,
|
||||
)
|
||||
self.caller.db._editing_help = helpentry
|
||||
|
||||
|
|
@ -976,6 +1005,4 @@ class CmdSetHelp(CmdHelp):
|
|||
)
|
||||
return
|
||||
else:
|
||||
self.msg(
|
||||
f"Error when creating topic '{topicstr}'{aliastxt}! Contact an admin."
|
||||
)
|
||||
self.msg(f"Error when creating topic '{topicstr}'{aliastxt}! Contact an admin.")
|
||||
|
|
|
|||
|
|
@ -593,13 +593,15 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
|||
if delmode:
|
||||
caller.msg("You cannot remove a core Evennia service (named 'Evennia*').")
|
||||
return
|
||||
string = ("|RYou seem to be shutting down a core Evennia "
|
||||
"service (named 'Evennia*').\nNote that stopping "
|
||||
"some TCP port services will *not* disconnect users "
|
||||
"*already* connected on those ports, but *may* "
|
||||
"instead cause spurious errors for them.\nTo safely "
|
||||
"and permanently remove ports, change settings file "
|
||||
"and restart the server.|n\n")
|
||||
string = (
|
||||
"|RYou seem to be shutting down a core Evennia "
|
||||
"service (named 'Evennia*').\nNote that stopping "
|
||||
"some TCP port services will *not* disconnect users "
|
||||
"*already* connected on those ports, but *may* "
|
||||
"instead cause spurious errors for them.\nTo safely "
|
||||
"and permanently remove ports, change settings file "
|
||||
"and restart the server.|n\n"
|
||||
)
|
||||
caller.msg(string)
|
||||
|
||||
if delmode:
|
||||
|
|
@ -611,9 +613,11 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
|||
try:
|
||||
service.stopService()
|
||||
except Exception as err:
|
||||
caller.msg(f"|rErrors were reported when stopping this service{err}.\n"
|
||||
"If there are remaining problems, try reloading "
|
||||
"or rebooting the server.")
|
||||
caller.msg(
|
||||
f"|rErrors were reported when stopping this service{err}.\n"
|
||||
"If there are remaining problems, try reloading "
|
||||
"or rebooting the server."
|
||||
)
|
||||
caller.msg("|g... Stopped service '%s'.|n" % self.args)
|
||||
return
|
||||
|
||||
|
|
@ -626,9 +630,11 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
|||
try:
|
||||
service.startService()
|
||||
except Exception as err:
|
||||
caller.msg(f"|rErrors were reported when starting this service{err}.\n"
|
||||
"If there are remaining problems, try reloading the server, changing the "
|
||||
"settings if it's a non-standard service.|n")
|
||||
caller.msg(
|
||||
f"|rErrors were reported when starting this service{err}.\n"
|
||||
"If there are remaining problems, try reloading the server, changing the "
|
||||
"settings if it's a non-standard service.|n"
|
||||
)
|
||||
caller.msg("|gService started.|n")
|
||||
|
||||
|
||||
|
|
@ -973,8 +979,8 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
@staticmethod
|
||||
def coll_date_func(task):
|
||||
"""Replace regex characters in date string and collect deferred function name."""
|
||||
t_comp_date = str(task[0]).replace('-', '/')
|
||||
t_func_name = str(task[1]).split(' ')
|
||||
t_comp_date = str(task[0]).replace("-", "/")
|
||||
t_func_name = str(task[1]).split(" ")
|
||||
t_func_mem_ref = t_func_name[3] if len(t_func_name) >= 4 else None
|
||||
return t_comp_date, t_func_mem_ref
|
||||
|
||||
|
|
@ -994,19 +1000,19 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
# verify manipulating the correct task
|
||||
task_args = _TASK_HANDLER.tasks.get(task_id, False)
|
||||
if not task_args: # check if the task is still active
|
||||
self.msg('Task completed while waiting for input.')
|
||||
self.msg("Task completed while waiting for input.")
|
||||
return
|
||||
else:
|
||||
# make certain a task with matching IDs has not been created
|
||||
t_comp_date, t_func_mem_ref = self.coll_date_func(task_args)
|
||||
if self.t_comp_date != t_comp_date or self.t_func_mem_ref != t_func_mem_ref:
|
||||
self.msg('Task completed while waiting for input.')
|
||||
self.msg("Task completed while waiting for input.")
|
||||
return
|
||||
|
||||
# Do the action requested by command caller
|
||||
action_return = self.task_action()
|
||||
self.msg(f'{self.action_request} request completed.')
|
||||
self.msg(f'The task function {self.action_request} returned: {action_return}')
|
||||
self.msg(f"{self.action_request} request completed.")
|
||||
self.msg(f"The task function {self.action_request} returned: {action_return}")
|
||||
|
||||
def func(self):
|
||||
# get a reference of the global task handler
|
||||
|
|
@ -1015,9 +1021,9 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
|
||||
# handle no tasks active.
|
||||
if not _TASK_HANDLER.tasks:
|
||||
self.msg('There are no active tasks.')
|
||||
self.msg("There are no active tasks.")
|
||||
if self.switches or self.args:
|
||||
self.msg('Likely the task has completed and been removed.')
|
||||
self.msg("Likely the task has completed and been removed.")
|
||||
return
|
||||
|
||||
# handle caller's request to manipulate a task(s)
|
||||
|
|
@ -1033,8 +1039,8 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
# if the argument is a task id, proccess the action on a single task
|
||||
if arg_is_id:
|
||||
|
||||
err_arg_msg = 'Switch and task ID are required when manipulating a task.'
|
||||
task_comp_msg = 'Task completed while processing request.'
|
||||
err_arg_msg = "Switch and task ID are required when manipulating a task."
|
||||
task_comp_msg = "Task completed while processing request."
|
||||
|
||||
# handle missing arguments or switches
|
||||
if not self.switches and self.lhs:
|
||||
|
|
@ -1047,14 +1053,16 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# handle task no longer existing
|
||||
if not task.exists():
|
||||
self.msg(f'Task {task_id} does not exist.')
|
||||
self.msg(f"Task {task_id} does not exist.")
|
||||
return
|
||||
|
||||
# get a reference of the function caller requested
|
||||
switch_action = getattr(task, action_request, False)
|
||||
if not switch_action:
|
||||
self.msg(f'{self.switches[0]}, is not an acceptable task action or ' \
|
||||
f'{task_comp_msg.lower()}')
|
||||
self.msg(
|
||||
f"{self.switches[0]}, is not an acceptable task action or "
|
||||
f"{task_comp_msg.lower()}"
|
||||
)
|
||||
|
||||
# verify manipulating the correct task
|
||||
if task_id in _TASK_HANDLER.tasks:
|
||||
|
|
@ -1064,25 +1072,29 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
return
|
||||
else:
|
||||
t_comp_date, t_func_mem_ref = self.coll_date_func(task_args)
|
||||
t_func_name = str(task_args[1]).split(' ')
|
||||
t_func_name = str(task_args[1]).split(" ")
|
||||
t_func_name = t_func_name[1] if len(t_func_name) >= 2 else None
|
||||
|
||||
if task.exists(): # make certain the task has not been called yet.
|
||||
prompt = (f'{action_request.capitalize()} task {task_id} with completion date '
|
||||
f'{t_comp_date} ({t_func_name}) {{options}}?')
|
||||
no_msg = f'No {action_request} processed.'
|
||||
prompt = (
|
||||
f"{action_request.capitalize()} task {task_id} with completion date "
|
||||
f"{t_comp_date} ({t_func_name}) {{options}}?"
|
||||
)
|
||||
no_msg = f"No {action_request} processed."
|
||||
# record variables for use in do_task_action method
|
||||
self.task_id = task_id
|
||||
self.t_comp_date = t_comp_date
|
||||
self.t_func_mem_ref = t_func_mem_ref
|
||||
self.task_action = switch_action
|
||||
self.action_request = action_request
|
||||
ask_yes_no(self.caller,
|
||||
prompt=prompt,
|
||||
yes_action=self.do_task_action,
|
||||
no_action=no_msg,
|
||||
default="Y",
|
||||
allow_abort=True)
|
||||
ask_yes_no(
|
||||
self.caller,
|
||||
prompt=prompt,
|
||||
yes_action=self.do_task_action,
|
||||
no_action=no_msg,
|
||||
default="Y",
|
||||
allow_abort=True,
|
||||
)
|
||||
return True
|
||||
else:
|
||||
self.msg(task_comp_msg)
|
||||
|
|
@ -1102,7 +1114,7 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# call requested action on all tasks with the function name
|
||||
for task_id, task_args in current_tasks.items():
|
||||
t_func_name = str(task_args[1]).split(' ')
|
||||
t_func_name = str(task_args[1]).split(" ")
|
||||
t_func_name = t_func_name[1] if len(t_func_name) >= 2 else None
|
||||
# skip this task if it is not for the function desired
|
||||
if arg_func_name != t_func_name:
|
||||
|
|
@ -1112,33 +1124,39 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
switch_action = getattr(task, action_request, False)
|
||||
if switch_action:
|
||||
action_return = switch_action()
|
||||
self.msg(f'Task action {action_request} completed on task ID {task_id}.')
|
||||
self.msg(f'The task function {action_request} returned: {action_return}')
|
||||
self.msg(f"Task action {action_request} completed on task ID {task_id}.")
|
||||
self.msg(f"The task function {action_request} returned: {action_return}")
|
||||
|
||||
# provide a message if not tasks of the function name was found
|
||||
if not name_match_found:
|
||||
self.msg(f'No tasks deferring function name {arg_func_name} found.')
|
||||
self.msg(f"No tasks deferring function name {arg_func_name} found.")
|
||||
return
|
||||
return True
|
||||
|
||||
# check if an maleformed request was created
|
||||
elif self.switches or self.lhs:
|
||||
self.msg('Task command misformed.')
|
||||
self.msg('Proper format tasks[/switch] [function name or task id]')
|
||||
self.msg("Task command misformed.")
|
||||
self.msg("Proper format tasks[/switch] [function name or task id]")
|
||||
return
|
||||
|
||||
# No task manupilation requested, build a table of tasks and display it
|
||||
# get the width of screen in characters
|
||||
width = self.client_width()
|
||||
# create table header and list to hold tasks data and actions
|
||||
tasks_header = ('Task ID', 'Completion Date', 'Function', 'Arguments', 'KWARGS',
|
||||
'persistent')
|
||||
tasks_header = (
|
||||
"Task ID",
|
||||
"Completion Date",
|
||||
"Function",
|
||||
"Arguments",
|
||||
"KWARGS",
|
||||
"persistent",
|
||||
)
|
||||
# empty list of lists, the size of the header
|
||||
tasks_list = [list() for i in range(len(tasks_header))]
|
||||
for task_id, task in _TASK_HANDLER.tasks.items():
|
||||
# collect data from the task
|
||||
t_comp_date, t_func_mem_ref = self.coll_date_func(task)
|
||||
t_func_name = str(task[1]).split(' ')
|
||||
t_func_name = str(task[1]).split(" ")
|
||||
t_func_name = t_func_name[1] if len(t_func_name) >= 2 else None
|
||||
t_args = str(task[2])
|
||||
t_kwargs = str(task[3])
|
||||
|
|
@ -1148,8 +1166,9 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
|||
for i in range(len(tasks_header)):
|
||||
tasks_list[i].append(task_data[i])
|
||||
# create and display the table
|
||||
tasks_table = EvTable(*tasks_header, table=tasks_list, maxwidth=width, border='cells',
|
||||
align='center')
|
||||
actions = (f'/{switch}' for switch in self.switch_options)
|
||||
tasks_table = EvTable(
|
||||
*tasks_header, table=tasks_list, maxwidth=width, border="cells", align="center"
|
||||
)
|
||||
actions = (f"/{switch}" for switch in self.switch_options)
|
||||
helptxt = f"\nActions: {iter_to_str(actions)}"
|
||||
self.msg(str(tasks_table) + helptxt)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -201,13 +201,17 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
|
|||
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(
|
||||
"Note: your username was normalized to strip spaces and remove characters "
|
||||
"that could be visually confusing."
|
||||
)
|
||||
|
||||
# have the user verify their new account was what they intended
|
||||
answer = yield(f"You want to create an account '{username}' with password '{password}'."
|
||||
"\nIs this what you intended? [Y]/N?")
|
||||
if answer.lower() in ('n', 'no'):
|
||||
answer = yield (
|
||||
f"You want to create an account '{username}' with password '{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
|
||||
|
||||
|
|
@ -344,7 +348,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
If you don't submit an encoding, the current encoding will be displayed
|
||||
instead.
|
||||
"""
|
||||
"""
|
||||
|
||||
key = "encoding"
|
||||
aliases = "encode"
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ class TestOptionTransferTrue(TestCase):
|
|||
b.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, high prio
|
||||
cmdset_f = d + c + b + a # reverse, high prio
|
||||
self.assertTrue(cmdset_f.no_exits)
|
||||
self.assertTrue(cmdset_f.no_objs)
|
||||
self.assertTrue(cmdset_f.no_channels)
|
||||
|
|
@ -407,7 +407,7 @@ class TestOptionTransferTrue(TestCase):
|
|||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = False
|
||||
c.no_channels = None # passthrough
|
||||
c.no_channels = None # passthrough
|
||||
b.no_objs = False
|
||||
d.duplicates = False
|
||||
# higher-prio sets will change the option up the chain
|
||||
|
|
@ -639,7 +639,7 @@ class TestOptionTransferFalse(TestCase):
|
|||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = d + c + b + a # reverse, high prio
|
||||
cmdset_f = d + c + b + a # reverse, high prio
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
|
|
@ -663,7 +663,7 @@ class TestOptionTransferFalse(TestCase):
|
|||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
cmdset_f = a + b + c + d # forward, high prio, never happens
|
||||
cmdset_f = a + b + c + d # forward, high prio, never happens
|
||||
self.assertFalse(cmdset_f.no_exits)
|
||||
self.assertFalse(cmdset_f.no_objs)
|
||||
self.assertFalse(cmdset_f.no_channels)
|
||||
|
|
@ -707,7 +707,7 @@ class TestOptionTransferFalse(TestCase):
|
|||
c.priority = 1
|
||||
d.priority = 2
|
||||
c.no_exits = True
|
||||
c.no_channels = None # passthrough
|
||||
c.no_channels = None # passthrough
|
||||
b.no_objs = True
|
||||
d.duplicates = True
|
||||
# higher-prio sets will change the option up the chain
|
||||
|
|
@ -908,6 +908,7 @@ class TestOptionTransferReplace(TestCase):
|
|||
"""
|
||||
Test option transfer through more complex merge types.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.cmdset_a = _CmdSetA()
|
||||
|
|
@ -1182,7 +1183,6 @@ class TestCmdSetNesting(BaseEvenniaTest):
|
|||
"""
|
||||
|
||||
def test_nest(self):
|
||||
|
||||
class CmdA(Command):
|
||||
key = "a"
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
"""
|
||||
if not self._log_file:
|
||||
self._log_file = self.attributes.get(
|
||||
"log_file", self.log_file.format(channelname=self.key.lower()))
|
||||
"log_file", self.log_file.format(channelname=self.key.lower())
|
||||
)
|
||||
return self._log_file
|
||||
|
||||
def set_log_filename(self, filename):
|
||||
|
|
@ -455,8 +456,13 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
# needing to use the `channel` command explicitly.
|
||||
msg_nick_pattern = self.channel_msg_nick_pattern.format(alias=alias)
|
||||
msg_nick_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key)
|
||||
user.nicks.add(msg_nick_pattern, msg_nick_replacement, category="inputline",
|
||||
pattern_is_regex=True, **kwargs)
|
||||
user.nicks.add(
|
||||
msg_nick_pattern,
|
||||
msg_nick_replacement,
|
||||
category="inputline",
|
||||
pattern_is_regex=True,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
if chan_key != alias:
|
||||
# this allows for using the alias for general channel lookups
|
||||
|
|
@ -546,7 +552,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
if not bypass_mute:
|
||||
receivers = [receiver for receiver in receivers if receiver not in self.mutelist]
|
||||
|
||||
send_kwargs = {'senders': senders, 'bypass_mute': bypass_mute, **kwargs}
|
||||
send_kwargs = {"senders": senders, "bypass_mute": bypass_mute, **kwargs}
|
||||
|
||||
# pre-send hook
|
||||
message = self.at_pre_msg(message, **send_kwargs)
|
||||
|
|
@ -826,27 +832,37 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
|
||||
# TODO Evennia 1.0+ removed hooks. Remove in 1.1.
|
||||
def message_transform(self, *args, **kwargs):
|
||||
raise RuntimeError("Channel.message_transform is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead.")
|
||||
raise RuntimeError(
|
||||
"Channel.message_transform is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead."
|
||||
)
|
||||
|
||||
def distribute_message(self, msgobj, online=False, **kwargs):
|
||||
raise RuntimeError("Channel.distribute_message is no longer used in 1.0+.")
|
||||
|
||||
def format_senders(self, senders=None, **kwargs):
|
||||
raise RuntimeError("Channel.format_senders is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead.")
|
||||
raise RuntimeError(
|
||||
"Channel.format_senders is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead."
|
||||
)
|
||||
|
||||
def pose_transform(self, msgobj, sender_string, **kwargs):
|
||||
raise RuntimeError("Channel.pose_transform is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead.")
|
||||
raise RuntimeError(
|
||||
"Channel.pose_transform is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead."
|
||||
)
|
||||
|
||||
def format_external(self, msgobj, senders, emit=False, **kwargs):
|
||||
raise RuntimeError("Channel.format_external is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead.")
|
||||
raise RuntimeError(
|
||||
"Channel.format_external is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead."
|
||||
)
|
||||
|
||||
def format_message(self, msgobj, emit=False, **kwargs):
|
||||
raise RuntimeError("Channel.format_message is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead.")
|
||||
raise RuntimeError(
|
||||
"Channel.format_message is no longer used in 1.0+. "
|
||||
"Use Account/Object.at_pre_channel_msg instead."
|
||||
)
|
||||
|
||||
def pre_send_message(self, msg, **kwargs):
|
||||
raise RuntimeError("Channel.pre_send_message was renamed to Channel.at_pre_msg.")
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ class MsgManager(TypedObjectManager):
|
|||
return self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj)
|
||||
elif typ == "object":
|
||||
return self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj)
|
||||
elif typ == 'script':
|
||||
elif typ == "script":
|
||||
return self.filter(db_receivers_scripts=obj)
|
||||
else:
|
||||
raise CommError
|
||||
|
|
@ -257,7 +257,7 @@ class MsgManager(TypedObjectManager):
|
|||
sender_restrict = Q(db_sender_accounts__pk=spk) & ~Q(db_hide_from_accounts__pk=spk)
|
||||
elif styp == "object":
|
||||
sender_restrict = Q(db_sender_objects__pk=spk) & ~Q(db_hide_from_objects__pk=spk)
|
||||
elif styp == 'script':
|
||||
elif styp == "script":
|
||||
sender_restrict = Q(db_sender_scripts__pk=spk)
|
||||
else:
|
||||
sender_restrict = Q()
|
||||
|
|
@ -266,16 +266,16 @@ class MsgManager(TypedObjectManager):
|
|||
if receiver:
|
||||
rpk = receiver.pk
|
||||
if rtyp == "account":
|
||||
receiver_restrict = (
|
||||
Q(db_receivers_accounts__pk=rpk) & ~Q(db_hide_from_accounts__pk=rpk))
|
||||
receiver_restrict = Q(db_receivers_accounts__pk=rpk) & ~Q(db_hide_from_accounts__pk=rpk)
|
||||
elif rtyp == "object":
|
||||
receiver_restrict = Q(db_receivers_objects__pk=rpk) & ~Q(db_hide_from_objects__pk=rpk)
|
||||
elif rtyp == 'script':
|
||||
elif rtyp == "script":
|
||||
receiver_restrict = Q(db_receivers_scripts__pk=rpk)
|
||||
elif rtyp == "channel":
|
||||
raise DeprecationWarning(
|
||||
"Msg.objects.search don't accept channel recipients since "
|
||||
"Channels no longer accepts Msg objects.")
|
||||
"Channels no longer accepts Msg objects."
|
||||
)
|
||||
else:
|
||||
receiver_restrict = Q()
|
||||
# filter by full text
|
||||
|
|
@ -289,8 +289,9 @@ class MsgManager(TypedObjectManager):
|
|||
# back-compatibility alias
|
||||
message_search = search_message
|
||||
|
||||
def create_message(self, senderobj, message, receivers=None, locks=None, tags=None,
|
||||
header=None, **kwargs):
|
||||
def create_message(
|
||||
self, senderobj, message, receivers=None, locks=None, tags=None, header=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Create a new communication Msg. Msgs represent a unit of
|
||||
database-persistent communication between entites.
|
||||
|
|
@ -315,7 +316,7 @@ class MsgManager(TypedObjectManager):
|
|||
it's up to the command definitions to limit this as desired.
|
||||
|
||||
"""
|
||||
if 'channels' in kwargs:
|
||||
if "channels" in kwargs:
|
||||
raise DeprecationWarning(
|
||||
"create_message() does not accept 'channel' kwarg anymore "
|
||||
"- channels no longer accept Msg objects."
|
||||
|
|
@ -339,6 +340,7 @@ class MsgManager(TypedObjectManager):
|
|||
new_message.save()
|
||||
return new_message
|
||||
|
||||
|
||||
#
|
||||
# Channel manager
|
||||
#
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class Msg(SharedMemoryModel):
|
|||
blank=True,
|
||||
db_index=True,
|
||||
help_text="Identifier for single external sender, for use with senders "
|
||||
"not represented by a regular database model."
|
||||
"not represented by a regular database model.",
|
||||
)
|
||||
|
||||
db_receivers_accounts = models.ManyToManyField(
|
||||
|
|
@ -137,7 +137,7 @@ class Msg(SharedMemoryModel):
|
|||
blank=True,
|
||||
db_index=True,
|
||||
help_text="Identifier for single external receiver, for use with recievers "
|
||||
"not represented by a regular database model."
|
||||
"not represented by a regular database model.",
|
||||
)
|
||||
|
||||
# header could be used for meta-info about the message if your system needs
|
||||
|
|
@ -286,7 +286,7 @@ class Msg(SharedMemoryModel):
|
|||
"""
|
||||
if isinstance(receivers, str):
|
||||
self.db_receiver_external = receivers
|
||||
self.save(update_fields=['db_receiver_external'])
|
||||
self.save(update_fields=["db_receiver_external"])
|
||||
return
|
||||
|
||||
for receiver in make_iter(receivers):
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ class ObjectCreationTest(BaseEvenniaTest):
|
|||
class ChannelWholistTests(BaseEvenniaTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.default_channel, _ = DefaultChannel.create("coffeetalk", description="A place to talk about coffee.")
|
||||
self.default_channel, _ = DefaultChannel.create(
|
||||
"coffeetalk", description="A place to talk about coffee."
|
||||
)
|
||||
self.default_channel.connect(self.obj1)
|
||||
|
||||
def test_wholist_shows_subscribed_objects(self):
|
||||
|
|
@ -31,7 +33,9 @@ class ChannelWholistTests(BaseEvenniaTest):
|
|||
|
||||
def test_wholist_shows_none_when_empty(self):
|
||||
# No one hates dogs
|
||||
empty_channel, _ = DefaultChannel.create("doghaters", description="A place where dog haters unite.")
|
||||
empty_channel, _ = DefaultChannel.create(
|
||||
"doghaters", description="A place where dog haters unite."
|
||||
)
|
||||
expected = "<None>"
|
||||
result = empty_channel.wholist
|
||||
self.assertEqual(expected, result)
|
||||
|
|
|
|||
|
|
@ -165,7 +165,9 @@ def check_location(storage):
|
|||
correct = storage.location.lstrip("/")
|
||||
raise ImproperlyConfigured(
|
||||
"{}.location cannot begin with a leading slash. Found '{}'. Use '{}' instead.".format(
|
||||
storage.__class__.__name__, storage.location, correct,
|
||||
storage.__class__.__name__,
|
||||
storage.location,
|
||||
correct,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
|
||||
obj = self.storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content, ExtraArgs={"ContentType": "text/plain", "ACL": self.storage.default_acl,}
|
||||
content,
|
||||
ExtraArgs={
|
||||
"ContentType": "text/plain",
|
||||
"ACL": self.storage.default_acl,
|
||||
},
|
||||
)
|
||||
|
||||
def test_storage_save_with_acl(self):
|
||||
|
|
@ -136,7 +140,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
|
||||
obj = self.storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content, ExtraArgs={"ContentType": "text/plain", "ACL": "private",}
|
||||
content,
|
||||
ExtraArgs={
|
||||
"ContentType": "text/plain",
|
||||
"ACL": "private",
|
||||
},
|
||||
)
|
||||
|
||||
def test_content_type(self):
|
||||
|
|
@ -151,7 +159,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
|
||||
obj = self.storage.bucket.Object.return_value
|
||||
obj.upload_fileobj.assert_called_with(
|
||||
content, ExtraArgs={"ContentType": "image/jpeg", "ACL": self.storage.default_acl,}
|
||||
content,
|
||||
ExtraArgs={
|
||||
"ContentType": "image/jpeg",
|
||||
"ACL": self.storage.default_acl,
|
||||
},
|
||||
)
|
||||
|
||||
def test_storage_save_gzipped(self):
|
||||
|
|
@ -379,7 +391,10 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
self.assertEqual(uploaded_content, written_content)
|
||||
multipart.complete.assert_called_once_with(
|
||||
MultipartUpload={
|
||||
"Parts": [{"ETag": "123", "PartNumber": 1}, {"ETag": "456", "PartNumber": 2},]
|
||||
"Parts": [
|
||||
{"ETag": "123", "PartNumber": 1},
|
||||
{"ETag": "456", "PartNumber": 2},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -394,7 +409,10 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
)
|
||||
self.storage._get_or_create_bucket("testbucketname")
|
||||
Bucket.create.assert_called_once_with(
|
||||
ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
|
||||
ACL="public-read",
|
||||
CreateBucketConfiguration={
|
||||
"LocationConstraint": "sa-east-1",
|
||||
},
|
||||
)
|
||||
|
||||
def test_auto_creating_bucket_with_acl(self):
|
||||
|
|
@ -409,22 +427,28 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
)
|
||||
self.storage._get_or_create_bucket("testbucketname")
|
||||
Bucket.create.assert_called_once_with(
|
||||
ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
|
||||
ACL="public-read",
|
||||
CreateBucketConfiguration={
|
||||
"LocationConstraint": "sa-east-1",
|
||||
},
|
||||
)
|
||||
|
||||
def test_storage_exists(self):
|
||||
self.assertTrue(self.storage.exists("file.txt"))
|
||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
||||
Bucket=self.storage.bucket_name, Key="file.txt",
|
||||
Bucket=self.storage.bucket_name,
|
||||
Key="file.txt",
|
||||
)
|
||||
|
||||
def test_storage_exists_false(self):
|
||||
self.storage.connection.meta.client.head_object.side_effect = ClientError(
|
||||
{"Error": {"Code": "404", "Message": "Not Found"}}, "HeadObject",
|
||||
{"Error": {"Code": "404", "Message": "Not Found"}},
|
||||
"HeadObject",
|
||||
)
|
||||
self.assertFalse(self.storage.exists("file.txt"))
|
||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
||||
Bucket=self.storage.bucket_name, Key="file.txt",
|
||||
Bucket=self.storage.bucket_name,
|
||||
Key="file.txt",
|
||||
)
|
||||
|
||||
def test_storage_exists_doesnt_create_bucket(self):
|
||||
|
|
@ -445,8 +469,14 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
# 4.txt
|
||||
pages = [
|
||||
{
|
||||
"CommonPrefixes": [{"Prefix": "some"}, {"Prefix": "other"},],
|
||||
"Contents": [{"Key": "2.txt"}, {"Key": "4.txt"},],
|
||||
"CommonPrefixes": [
|
||||
{"Prefix": "some"},
|
||||
{"Prefix": "other"},
|
||||
],
|
||||
"Contents": [
|
||||
{"Key": "2.txt"},
|
||||
{"Key": "4.txt"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
|
|
@ -465,7 +495,14 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
|||
# some/path/1.txt
|
||||
# some/2.txt
|
||||
pages = [
|
||||
{"CommonPrefixes": [{"Prefix": "some/path"},], "Contents": [{"Key": "some/2.txt"},],},
|
||||
{
|
||||
"CommonPrefixes": [
|
||||
{"Prefix": "some/path"},
|
||||
],
|
||||
"Contents": [
|
||||
{"Key": "some/2.txt"},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
paginator = mock.MagicMock()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Building menu tests.
|
|||
"""
|
||||
|
||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||
from . building_menu import BuildingMenu, CmdNoMatch
|
||||
from .building_menu import BuildingMenu, CmdNoMatch
|
||||
|
||||
|
||||
class Submenu(BuildingMenu):
|
||||
|
|
|
|||
|
|
@ -151,8 +151,9 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=1, weeks=1, months=1, yrs=0
|
|||
|
||||
"""
|
||||
if days <= 0 or weeks <= 0 or months <= 0:
|
||||
raise ValueError("realtime_to_gametime: days/weeks/months cannot be set <= 0, "
|
||||
"they start from 1.")
|
||||
raise ValueError(
|
||||
"realtime_to_gametime: days/weeks/months cannot be set <= 0, " "they start from 1."
|
||||
)
|
||||
|
||||
# days/weeks/months start from 1, we need to adjust them to work mathematically.
|
||||
days, weeks, months = days - 1, weeks - 1, months - 1
|
||||
|
|
|
|||
|
|
@ -31,17 +31,27 @@ class TestEventHandler(BaseEvenniaTest):
|
|||
def setUp(self):
|
||||
"""Create the event handler."""
|
||||
super().setUp()
|
||||
self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler")
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.EventHandler"
|
||||
)
|
||||
|
||||
# Copy old events if necessary
|
||||
if OLD_EVENTS:
|
||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||
|
||||
# Alter typeclasses
|
||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.char1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.char2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.room1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.room2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -253,17 +263,27 @@ class TestCmdCallback(BaseEvenniaCommandTest):
|
|||
def setUp(self):
|
||||
"""Create the callback handler."""
|
||||
super().setUp()
|
||||
self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler")
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.EventHandler"
|
||||
)
|
||||
|
||||
# Copy old events if necessary
|
||||
if OLD_EVENTS:
|
||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||
|
||||
# Alter typeclasses
|
||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.char1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.char2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.room1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.room2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -432,17 +452,27 @@ class TestDefaultCallbacks(BaseEvenniaCommandTest):
|
|||
def setUp(self):
|
||||
"""Create the callback handler."""
|
||||
super().setUp()
|
||||
self.handler = create_script("evennia.contrib.base_systems.ingame_python.scripts.EventHandler")
|
||||
self.handler = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.EventHandler"
|
||||
)
|
||||
|
||||
# Copy old events if necessary
|
||||
if OLD_EVENTS:
|
||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||
|
||||
# Alter typeclasses
|
||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
||||
self.room1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.room2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom")
|
||||
self.char1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.char2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter"
|
||||
)
|
||||
self.room1.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.room2.swap_typeclass(
|
||||
"evennia.contrib.base_systems.ingame_python.typeclasses.EventRoom"
|
||||
)
|
||||
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||
|
||||
def tearDown(self):
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
|
|||
from evennia import ScriptDB
|
||||
from evennia.utils.utils import delay, inherits_from, lazy_property
|
||||
from evennia.contrib.base_systems.ingame_python.callbackhandler import CallbackHandler
|
||||
from evennia.contrib.base_systems.ingame_python.utils import register_events, time_event, phrase_event
|
||||
from evennia.contrib.base_systems.ingame_python.utils import (
|
||||
register_events,
|
||||
time_event,
|
||||
phrase_event,
|
||||
)
|
||||
|
||||
# Character help
|
||||
CHARACTER_CAN_DELETE = """
|
||||
|
|
|
|||
|
|
@ -173,7 +173,9 @@ def time_event(obj, event_name, number, parameters):
|
|||
"""
|
||||
seconds, usual, key = get_next_wait(parameters)
|
||||
script = create_script(
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.TimeEventScript", interval=seconds, obj=obj
|
||||
"evennia.contrib.base_systems.ingame_python.scripts.TimeEventScript",
|
||||
interval=seconds,
|
||||
obj=obj,
|
||||
)
|
||||
script.key = key
|
||||
script.desc = "event on {}".format(key)
|
||||
|
|
|
|||
|
|
@ -31,8 +31,7 @@ _GUEST_ENABLED = settings.GUEST_ENABLED
|
|||
_ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||
_GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS)
|
||||
|
||||
_ACCOUNT_HELP = (
|
||||
"Enter a new or existing login name.")
|
||||
_ACCOUNT_HELP = "Enter a new or existing login name."
|
||||
_PASSWORD_HELP = (
|
||||
"Password should be a minimum of 8 characters (preferably longer) and "
|
||||
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ class CmdDelCom(CmdChannel):
|
|||
account_caller = True
|
||||
|
||||
def func(self):
|
||||
"""Implementing the command. """
|
||||
"""Implementing the command."""
|
||||
|
||||
caller = self.caller
|
||||
|
||||
|
|
@ -207,8 +207,7 @@ class CmdAllCom(CmdChannel):
|
|||
args = self.args
|
||||
if not args:
|
||||
subscribed, available = self.list_channels()
|
||||
self.msg(
|
||||
"\n|wAvailable channels:\n{table}")
|
||||
self.msg("\n|wAvailable channels:\n{table}")
|
||||
return
|
||||
return
|
||||
|
||||
|
|
@ -353,7 +352,7 @@ class CmdCBoot(CmdChannel):
|
|||
self.msg(string)
|
||||
return
|
||||
|
||||
success, err = self.boot_user(target, quiet='quiet' in self.switches)
|
||||
success, err = self.boot_user(target, quiet="quiet" in self.switches)
|
||||
if success:
|
||||
self.msg(f"Booted {target.key} from {channel.key}")
|
||||
logger.log_sec(
|
||||
|
|
|
|||
|
|
@ -343,7 +343,7 @@ class EvscaperoomObject(DefaultObject):
|
|||
# Evennia hooks
|
||||
|
||||
def return_appearance(self, looker, **kwargs):
|
||||
""" Could be modified per state. We generally don't worry about the
|
||||
"""Could be modified per state. We generally don't worry about the
|
||||
contents of the object by default.
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -667,8 +667,8 @@ class CraftingRecipe(CraftingRecipeBase):
|
|||
consumable_kwargs = {}
|
||||
|
||||
if location:
|
||||
tool_kwargs['location'] = location
|
||||
consumable_kwargs['location'] = location
|
||||
tool_kwargs["location"] = location
|
||||
consumable_kwargs["location"] = location
|
||||
|
||||
tool_key = tool_kwargs.pop("key", None)
|
||||
cons_key = consumable_kwargs.pop("key", None)
|
||||
|
|
@ -966,6 +966,7 @@ class CmdCraft(Command):
|
|||
things in the current location, like a furnace, windmill or anvil.
|
||||
|
||||
"""
|
||||
|
||||
key = "craft"
|
||||
locks = "cmd:all()"
|
||||
help_category = "General"
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ from .crafting import craft, CraftingRecipe, CraftingValidationError
|
|||
# Sword recipe
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class PigIronRecipe(CraftingRecipe):
|
||||
"""
|
||||
Pig iron is a high-carbon result of melting iron in a blast furnace.
|
||||
|
|
@ -331,9 +332,9 @@ class SwordRecipe(_SwordSmithingBaseRecipe):
|
|||
exact_consumable_order = True
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
# Recipes for spell casting
|
||||
#------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
|
||||
|
||||
class _MagicRecipe(CraftingRecipe):
|
||||
|
|
@ -348,6 +349,7 @@ class _MagicRecipe(CraftingRecipe):
|
|||
We also assume that the crafter has skills set on itself as plain Attributes.
|
||||
|
||||
"""
|
||||
|
||||
name = ""
|
||||
# all spells require a spellbook and a wand (so there!)
|
||||
tool_tags = ["spellbook", "wand"]
|
||||
|
|
@ -390,15 +392,15 @@ class _MagicRecipe(CraftingRecipe):
|
|||
skill_value = crafter.attributes.get(skill_name)
|
||||
|
||||
if skill_value is None or skill_value < min_value:
|
||||
self.msg(self.error_too_low_skill_level.format(skill_name=skill_name,
|
||||
spell=self.name))
|
||||
self.msg(
|
||||
self.error_too_low_skill_level.format(skill_name=skill_name, spell=self.name)
|
||||
)
|
||||
raise CraftingValidationError
|
||||
|
||||
# get the value of the skill to roll
|
||||
self.skill_roll_value = self.crafter.attributes.get(self.skill_roll)
|
||||
if self.skill_roll_value is None:
|
||||
self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll,
|
||||
spell=self.name))
|
||||
self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll, spell=self.name))
|
||||
raise CraftingValidationError
|
||||
|
||||
def do_craft(self, **kwargs):
|
||||
|
|
@ -446,12 +448,13 @@ class FireballRecipe(_MagicRecipe):
|
|||
need to be created to understand what they mean when used.
|
||||
|
||||
"""
|
||||
|
||||
name = "fireball"
|
||||
skill_requirements = [('firemagic', 10)] # skill 'firemagic' lvl 10 or higher
|
||||
skill_requirements = [("firemagic", 10)] # skill 'firemagic' lvl 10 or higher
|
||||
skill_roll = "firemagic"
|
||||
success_message = "A ball of flame appears!"
|
||||
desired_effects = [('target_fire_damage', 25), ('ranged_attack', -2), ('mana_cost', 12)]
|
||||
failure_effects = [('self_fire_damage', 5), ('mana_cost', 5)]
|
||||
desired_effects = [("target_fire_damage", 25), ("ranged_attack", -2), ("mana_cost", 12)]
|
||||
failure_effects = [("self_fire_damage", 5), ("mana_cost", 5)]
|
||||
|
||||
|
||||
class HealingRecipe(_MagicRecipe):
|
||||
|
|
@ -462,11 +465,12 @@ class HealingRecipe(_MagicRecipe):
|
|||
need to be created to understand what they mean.
|
||||
|
||||
"""
|
||||
|
||||
name = "heal"
|
||||
skill_requirements = [('bodymagic', 5), ("empathy", 10)]
|
||||
skill_requirements = [("bodymagic", 5), ("empathy", 10)]
|
||||
skill_roll = "bodymagic"
|
||||
success_message = "You successfully extend your healing aura."
|
||||
desired_effects = [('healing', 15), ('mana_cost', 5)]
|
||||
desired_effects = [("healing", 15), ("mana_cost", 5)]
|
||||
failure_effects = []
|
||||
|
||||
|
||||
|
|
@ -478,7 +482,8 @@ class CmdCast(Command):
|
|||
cast <spell> <target>
|
||||
|
||||
"""
|
||||
key = 'cast'
|
||||
|
||||
key = "cast"
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
|
|
@ -488,8 +493,8 @@ class CmdCast(Command):
|
|||
"""
|
||||
args = self.args.strip().lower()
|
||||
target = None
|
||||
if ' ' in args:
|
||||
self.spellname, *target = args.split(' ', 1)
|
||||
if " " in args:
|
||||
self.spellname, *target = args.split(" ", 1)
|
||||
else:
|
||||
self.spellname = args
|
||||
|
||||
|
|
@ -512,8 +517,9 @@ class CmdCast(Command):
|
|||
try:
|
||||
# if this completes without an exception, the caster will have
|
||||
# a new magic_effect set on themselves, ready to use or apply in some way.
|
||||
success, effects = craft(self.caller, self.spellname, *possible_tools,
|
||||
raise_exception=True)
|
||||
success, effects = craft(
|
||||
self.caller, self.spellname, *possible_tools, raise_exception=True
|
||||
)
|
||||
except CraftingValidationError:
|
||||
return
|
||||
except KeyError:
|
||||
|
|
@ -527,5 +533,7 @@ class CmdCast(Command):
|
|||
# (which could be yourself) by a number of health points given by the recipe.
|
||||
effect_txt = ", ".join(f"{eff[0]}({eff[1]})" for eff in effects)
|
||||
success_txt = "|gsucceeded|n" if success else "|rfailed|n"
|
||||
self.caller.msg(f"Casting the spell {self.spellname} on {self.target} {success_txt}, "
|
||||
f"causing the following effects: {effect_txt}.")
|
||||
self.caller.msg(
|
||||
f"Casting the spell {self.spellname} on {self.target} {success_txt}, "
|
||||
f"causing the following effects: {effect_txt}."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
pass
|
||||
|
||||
def test_error_format(self):
|
||||
"""Test the automatic error formatter """
|
||||
"""Test the automatic error formatter"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
|
|
@ -428,7 +428,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
self.assertIsNotNone(self.tool2.pk)
|
||||
|
||||
def test_craft_tool_order__fail(self):
|
||||
"""Strict tool-order recipe fail """
|
||||
"""Strict tool-order recipe fail"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter, self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
||||
)
|
||||
|
|
@ -451,7 +451,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
|||
self.assertIsNotNone(self.tool2.pk)
|
||||
|
||||
def test_craft_cons_order__fail(self):
|
||||
"""Strict tool-order recipe fail """
|
||||
"""Strict tool-order recipe fail"""
|
||||
recipe = _MockRecipe(
|
||||
self.crafter, self.tool1, self.tool2, self.cons3, self.cons2, self.cons1
|
||||
)
|
||||
|
|
@ -653,7 +653,10 @@ class TestCraftSword(BaseEvenniaTestCase):
|
|||
|
||||
|
||||
@mock.patch("evennia.contrib.game_systems.crafting.crafting._load_recipes", new=mock.MagicMock())
|
||||
@mock.patch("evennia.contrib.game_systems.crafting.crafting._RECIPE_CLASSES", new={"testrecipe": _MockRecipe})
|
||||
@mock.patch(
|
||||
"evennia.contrib.game_systems.crafting.crafting._RECIPE_CLASSES",
|
||||
new={"testrecipe": _MockRecipe},
|
||||
)
|
||||
@override_settings(CRAFT_RECIPE_MODULES=[])
|
||||
class TestCraftCommand(BaseEvenniaCommandTest):
|
||||
"""Test the crafting command"""
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ class TestGenderSub(BaseEvenniaCommandTest):
|
|||
self.assertEqual(
|
||||
gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender"
|
||||
)
|
||||
with patch("evennia.contrib.game_systems.gendersub.gendersub.DefaultCharacter.msg") as mock_msg:
|
||||
with patch(
|
||||
"evennia.contrib.game_systems.gendersub.gendersub.DefaultCharacter.msg"
|
||||
) as mock_msg:
|
||||
char.db.gender = "female"
|
||||
char.msg("Test |p gender")
|
||||
mock_msg.assert_called_with("Test her gender", from_obj=None, session=None)
|
||||
|
|
|
|||
|
|
@ -304,6 +304,7 @@ class TBBasicCharacter(DefaultCharacter):
|
|||
A character able to participate in turn-based combat. Has attributes for current
|
||||
and maximum HP, and access to combat commands.
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
def at_object_creation(self):
|
||||
|
|
|
|||
|
|
@ -243,6 +243,7 @@ class TBEquipTurnHandler(tb_basic.TBBasicTurnHandler):
|
|||
Fights persist until only one participant is left with any HP or all
|
||||
remaining participants choose to end the combat with the 'disengage' command.
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
|
||||
|
|
@ -258,6 +259,7 @@ class TBEWeapon(DefaultObject):
|
|||
A weapon which can be wielded in combat with the 'wield' command.
|
||||
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
def at_object_creation(self):
|
||||
|
|
@ -513,8 +515,9 @@ class CmdWield(Command):
|
|||
weapon = self.caller.search(self.args, candidates=self.caller.contents)
|
||||
if not weapon:
|
||||
return
|
||||
if not weapon.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon",
|
||||
exact=True):
|
||||
if not weapon.is_typeclass(
|
||||
"evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon", exact=True
|
||||
):
|
||||
self.caller.msg("That's not a weapon!")
|
||||
# Remember to update the path to the weapon typeclass if you move this module!
|
||||
return
|
||||
|
|
@ -597,8 +600,9 @@ class CmdDon(Command):
|
|||
armor = self.caller.search(self.args, candidates=self.caller.contents)
|
||||
if not armor:
|
||||
return
|
||||
if not armor.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor",
|
||||
exact=True):
|
||||
if not armor.is_typeclass(
|
||||
"evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor", exact=True
|
||||
):
|
||||
self.caller.msg("That's not armor!")
|
||||
# Remember to update the path to the armor typeclass if you move this module!
|
||||
return
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ COMBAT FUNCTIONS START HERE
|
|||
|
||||
|
||||
class ItemCombatRules(tb_basic.BasicCombatRules):
|
||||
|
||||
def get_attack(self, attacker, defender):
|
||||
"""
|
||||
Returns a value for an attack roll.
|
||||
|
|
@ -699,7 +698,7 @@ AMULET_OF_MIGHT = {
|
|||
AMULET_OF_WEAKNESS = {
|
||||
"key": "The Amulet of Weakness",
|
||||
"desc": "The one who holds this amulet can call upon its power to gain great weakness. "
|
||||
"It's not a terribly useful artifact.",
|
||||
"It's not a terribly useful artifact.",
|
||||
"item_func": "add_condition",
|
||||
"item_selfonly": True,
|
||||
"item_kwargs": {"conditions": [("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]},
|
||||
|
|
|
|||
|
|
@ -93,8 +93,8 @@ These functions also all accept **kwargs, and how these are used is specified
|
|||
in the docstring for each function.
|
||||
"""
|
||||
|
||||
class MagicCombatRules(tb_basic.BasicCombatRules):
|
||||
|
||||
class MagicCombatRules(tb_basic.BasicCombatRules):
|
||||
def spell_healing(self, caster, spell_name, targets, cost, **kwargs):
|
||||
"""
|
||||
Spell that restores HP to a target or targets.
|
||||
|
|
@ -325,11 +325,7 @@ SPELLS = {
|
|||
"attack_name": ("A jet of flame", "jets of flame"),
|
||||
"damage_range": (25, 35),
|
||||
},
|
||||
"cure wounds": {
|
||||
"spellfunc": COMBAT_RULES.spell_healing,
|
||||
"target": "anychar",
|
||||
"cost": 5
|
||||
},
|
||||
"cure wounds": {"spellfunc": COMBAT_RULES.spell_healing, "target": "anychar", "cost": 5},
|
||||
"mass cure wounds": {
|
||||
"spellfunc": COMBAT_RULES.spell_healing,
|
||||
"target": "anychar",
|
||||
|
|
@ -376,6 +372,7 @@ class TBMagicCharacter(tb_basic.TBBasicCharacter):
|
|||
and maximum HP, access to combat commands and magic.
|
||||
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
def at_object_creation(self):
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ COMBAT FUNCTIONS START HERE
|
|||
|
||||
|
||||
class RangedCombatRules(tb_basic.BasicCombatRules):
|
||||
|
||||
def get_attack(self, attacker, defender, attack_type):
|
||||
"""
|
||||
Returns a value for an attack roll.
|
||||
|
|
@ -154,7 +153,7 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
|||
attack_value -= 15
|
||||
return attack_value
|
||||
|
||||
def get_defense(self, attacker, defender, attack_type='melee'):
|
||||
def get_defense(self, attacker, defender, attack_type="melee"):
|
||||
"""
|
||||
Returns a value for defense, which an attack roll must equal or exceed in order
|
||||
for an attack to hit.
|
||||
|
|
@ -284,8 +283,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
|||
if thing != mover and thing != target:
|
||||
# Move away from each object closer to the target than you, if it's also closer to
|
||||
# you than you are to the target.
|
||||
if (self.get_range(mover, thing) >= self.get_range(target, thing)
|
||||
and self.get_range(mover, thing) < self.get_range(mover, target)):
|
||||
if self.get_range(mover, thing) >= self.get_range(target, thing) and self.get_range(
|
||||
mover, thing
|
||||
) < self.get_range(mover, target):
|
||||
self.distance_inc(mover, thing)
|
||||
# Move away from anything your target is engaged with
|
||||
if self.get_range(target, thing) == 0:
|
||||
|
|
@ -296,8 +296,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
|||
# Then, move away from your target.
|
||||
self.distance_inc(mover, target)
|
||||
|
||||
def resolve_attack(self, attacker, defender, attack_value=None, defense_value=None,
|
||||
attack_type='melee'):
|
||||
def resolve_attack(
|
||||
self, attacker, defender, attack_value=None, defense_value=None, attack_type="melee"
|
||||
):
|
||||
"""
|
||||
Resolves an attack and outputs the result.
|
||||
|
||||
|
|
@ -496,6 +497,7 @@ class TBRangeCharacter(tb_basic.TBBasicCharacter):
|
|||
A character able to participate in turn-based combat. Has attributes for current
|
||||
and maximum HP, and access to combat commands.
|
||||
"""
|
||||
|
||||
rules = COMBAT_RULES
|
||||
|
||||
|
||||
|
|
@ -797,15 +799,17 @@ class CmdShoot(Command):
|
|||
in_melee = []
|
||||
for target in attacker.db.combat_range:
|
||||
# Object is engaged and has HP
|
||||
if (self.rules.get_range(attacker, defender) == 0
|
||||
and target.db.hp and target != self.caller):
|
||||
if (
|
||||
self.rules.get_range(attacker, defender) == 0
|
||||
and target.db.hp
|
||||
and target != self.caller
|
||||
):
|
||||
in_melee.append(target) # Add to list of targets in melee
|
||||
|
||||
if len(in_melee) > 0:
|
||||
self.caller.msg(
|
||||
"You can't shoot because there are fighters engaged with you (%s) - you need "
|
||||
"to retreat! (see: help withdraw)"
|
||||
% ", ".join(obj.key for obj in in_melee)
|
||||
"to retreat! (see: help withdraw)" % ", ".join(obj.key for obj in in_melee)
|
||||
)
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -133,8 +133,9 @@ class TestTurnBattleBasicFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_basic.COMBAT_RULES.resolve_attack(self.attacker, self.defender,
|
||||
attack_value=20, defense_value=10)
|
||||
tb_basic.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
@ -227,7 +228,9 @@ class TestTurnBattleEquipFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_equip.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20, defense_value=10)
|
||||
tb_equip.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
@ -305,12 +308,14 @@ class TestTurnBattleRangeFunc(BaseEvenniaTest):
|
|||
initiative = tb_range.COMBAT_RULES.roll_init(self.attacker)
|
||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||
# Attack roll
|
||||
attack_roll = tb_range.COMBAT_RULES.get_attack(self.attacker, self.defender,
|
||||
attack_type="test")
|
||||
attack_roll = tb_range.COMBAT_RULES.get_attack(
|
||||
self.attacker, self.defender, attack_type="test"
|
||||
)
|
||||
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
||||
# Defense roll
|
||||
defense_roll = tb_range.COMBAT_RULES.get_defense(self.attacker, self.defender,
|
||||
attack_type="test")
|
||||
defense_roll = tb_range.COMBAT_RULES.get_defense(
|
||||
self.attacker, self.defender, attack_type="test"
|
||||
)
|
||||
self.assertTrue(defense_roll == 50)
|
||||
# Damage roll
|
||||
damage_roll = tb_range.COMBAT_RULES.get_damage(self.attacker, self.defender)
|
||||
|
|
@ -436,8 +441,9 @@ class TestTurnBattleItemsFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_items.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20,
|
||||
defense_value=10)
|
||||
tb_items.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
@ -555,8 +561,9 @@ class TestTurnBattleMagicFunc(BaseEvenniaTest):
|
|||
self.assertTrue(self.defender.db.hp == 7)
|
||||
# Resolve attack
|
||||
self.defender.db.hp = 40
|
||||
tb_magic.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20,
|
||||
defense_value=10)
|
||||
tb_magic.COMBAT_RULES.resolve_attack(
|
||||
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||
)
|
||||
self.assertTrue(self.defender.db.hp < 40)
|
||||
# Combat cleanup
|
||||
self.attacker.db.Combat_attribute = True
|
||||
|
|
|
|||
|
|
@ -2,4 +2,3 @@
|
|||
Contribs related to moving in and manipulating the game world and grid.
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -18,17 +18,18 @@ import random
|
|||
# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an
|
||||
# island surrounded by water (≈). By giving no instructions for the water
|
||||
# characters we effectively skip it and create no rooms for those squares.
|
||||
EXAMPLE1_MAP = '''\
|
||||
EXAMPLE1_MAP = """\
|
||||
≈≈≈≈≈
|
||||
≈♣n♣≈
|
||||
≈∩▲∩≈
|
||||
≈♠n♠≈
|
||||
≈≈≈≈≈
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def example1_build_forest(x, y, **kwargs):
|
||||
'''A basic example of build instructions. Make sure to include **kwargs
|
||||
in the arguments and return an instance of the room for exit generation.'''
|
||||
"""A basic example of build instructions. Make sure to include **kwargs
|
||||
in the arguments and return an instance of the room for exit generation."""
|
||||
|
||||
# Create a room and provide a basic description.
|
||||
room = create_object(rooms.Room, key="forest" + str(x) + str(y))
|
||||
|
|
@ -42,7 +43,7 @@ def example1_build_forest(x, y, **kwargs):
|
|||
|
||||
|
||||
def example1_build_mountains(x, y, **kwargs):
|
||||
'''A room that is a little more advanced'''
|
||||
"""A room that is a little more advanced"""
|
||||
|
||||
# Create the room.
|
||||
room = create_object(rooms.Room, key="mountains" + str(x) + str(y))
|
||||
|
|
@ -68,7 +69,7 @@ def example1_build_mountains(x, y, **kwargs):
|
|||
|
||||
|
||||
def example1_build_temple(x, y, **kwargs):
|
||||
'''A unique room that does not need to be as general'''
|
||||
"""A unique room that does not need to be as general"""
|
||||
|
||||
# Create the room.
|
||||
room = create_object(rooms.Room, key="temple" + str(x) + str(y))
|
||||
|
|
@ -115,7 +116,7 @@ EXAMPLE1_LEGEND = {
|
|||
|
||||
# This is the same layout as Example 1 but included are characters for exits.
|
||||
# We can use these characters to determine which rooms should be connected.
|
||||
EXAMPLE2_MAP = '''\
|
||||
EXAMPLE2_MAP = """\
|
||||
≈ ≈ ≈ ≈ ≈
|
||||
|
||||
≈ ♣-♣-♣ ≈
|
||||
|
|
@ -125,11 +126,11 @@ EXAMPLE2_MAP = '''\
|
|||
≈ ♣-♣-♣ ≈
|
||||
|
||||
≈ ≈ ≈ ≈ ≈
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
def example2_build_forest(x, y, **kwargs):
|
||||
'''A basic room'''
|
||||
"""A basic room"""
|
||||
# If on anything other than the first iteration - Do nothing.
|
||||
if kwargs["iteration"] > 0:
|
||||
return None
|
||||
|
|
@ -143,7 +144,7 @@ def example2_build_forest(x, y, **kwargs):
|
|||
|
||||
|
||||
def example2_build_verticle_exit(x, y, **kwargs):
|
||||
'''Creates two exits to and from the two rooms north and south.'''
|
||||
"""Creates two exits to and from the two rooms north and south."""
|
||||
# If on the first iteration - Do nothing.
|
||||
if kwargs["iteration"] == 0:
|
||||
return
|
||||
|
|
@ -164,7 +165,7 @@ def example2_build_verticle_exit(x, y, **kwargs):
|
|||
|
||||
|
||||
def example2_build_horizontal_exit(x, y, **kwargs):
|
||||
'''Creates two exits to and from the two rooms east and west.'''
|
||||
"""Creates two exits to and from the two rooms east and west."""
|
||||
# If on the first iteration - Do nothing.
|
||||
if kwargs["iteration"] == 0:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Wilderness contrib - titeuf87, 2017
|
|||
|
||||
from .wilderness import create_wilderness # noqa
|
||||
from .wilderness import enter_wilderness # noqa
|
||||
from .wilderness import get_new_coordinates # noqa
|
||||
from .wilderness import get_new_coordinates # noqa
|
||||
from .wilderness import WildernessScript # noqa
|
||||
from .wilderness import WildernessExit # noqa
|
||||
from .wilderness import WildernessRoom # noqa
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class CmdXYZTeleport(building.CmdTeleport):
|
|||
are given, the target is a location on the XYZGrid.
|
||||
|
||||
"""
|
||||
|
||||
def _search_by_xyz(self, inp):
|
||||
inp = inp.strip("()")
|
||||
X, Y, *Z = inp.split(",", 2)
|
||||
|
|
@ -69,8 +70,10 @@ class CmdXYZTeleport(building.CmdTeleport):
|
|||
try:
|
||||
xyz = self.caller.xyz
|
||||
except AttributeError:
|
||||
self.caller.msg("Z-coordinate is also required since you are not currently "
|
||||
"in a room with a Z coordinate of its own.")
|
||||
self.caller.msg(
|
||||
"Z-coordinate is also required since you are not currently "
|
||||
"in a room with a Z coordinate of its own."
|
||||
)
|
||||
raise InterruptCommand
|
||||
else:
|
||||
Z = xyz[2]
|
||||
|
|
@ -134,9 +137,11 @@ class CmdXYZOpen(building.CmdOpen):
|
|||
|
||||
self.location = self.caller.location
|
||||
if not self.args or not self.rhs:
|
||||
self.caller.msg("Usage: open <new exit>[;alias...][:typeclass]"
|
||||
"[,<return exit>[;alias..][:typeclass]]] "
|
||||
"= <destination or (X,Y,Z)>")
|
||||
self.caller.msg(
|
||||
"Usage: open <new exit>[;alias...][:typeclass]"
|
||||
"[,<return exit>[;alias..][:typeclass]]] "
|
||||
"= <destination or (X,Y,Z)>"
|
||||
)
|
||||
raise InterruptCommand
|
||||
if not self.location:
|
||||
self.caller.msg("You cannot create an exit from a None-location.")
|
||||
|
|
@ -184,6 +189,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
Builders can optionally specify a specific grid coordinate (X,Y) to go to.
|
||||
|
||||
"""
|
||||
|
||||
key = "goto"
|
||||
aliases = "path"
|
||||
help_category = "General"
|
||||
|
|
@ -207,11 +213,19 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
def _search_by_key_and_alias(self, inp, xyz_start):
|
||||
Z = xyz_start[2]
|
||||
candidates = list(XYZRoom.objects.filter_xyz(xyz=('*', '*', Z)))
|
||||
candidates = list(XYZRoom.objects.filter_xyz(xyz=("*", "*", Z)))
|
||||
return self.caller.search(inp, candidates=candidates)
|
||||
|
||||
def _auto_step(self, caller, session, target=None,
|
||||
xymap=None, directions=None, step_sequence=None, step=True):
|
||||
def _auto_step(
|
||||
self,
|
||||
caller,
|
||||
session,
|
||||
target=None,
|
||||
xymap=None,
|
||||
directions=None,
|
||||
step_sequence=None,
|
||||
step=True,
|
||||
):
|
||||
|
||||
path_data = caller.ndb.xy_path_data
|
||||
|
||||
|
|
@ -221,8 +235,12 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
# stop any old task in its tracks
|
||||
path_data.task.cancel()
|
||||
path_data = caller.ndb.xy_path_data = PathData(
|
||||
target=target, xymap=xymap, directions=directions,
|
||||
step_sequence=step_sequence, task=None)
|
||||
target=target,
|
||||
xymap=xymap,
|
||||
directions=directions,
|
||||
step_sequence=step_sequence,
|
||||
task=None,
|
||||
)
|
||||
|
||||
if step and path_data:
|
||||
|
||||
|
|
@ -285,7 +303,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
xymap=path_data.xymap,
|
||||
directions=directions,
|
||||
step_sequence=step_sequence,
|
||||
task=None
|
||||
task=None,
|
||||
)
|
||||
# the map can itself tell the stepper to stop the auto-step prematurely
|
||||
interrupt_node_or_link = None
|
||||
|
|
@ -301,7 +319,8 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
# the exit name does not need to be the same as the cardinal direction!
|
||||
exit_name, *_ = first_link.spawn_aliases.get(
|
||||
direction, current_node.direction_spawn_defaults.get(direction, ('unknown', )))
|
||||
direction, current_node.direction_spawn_defaults.get(direction, ("unknown",))
|
||||
)
|
||||
|
||||
exit_obj = caller.search(exit_name)
|
||||
if not exit_obj:
|
||||
|
|
@ -315,13 +334,15 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
# premature stop of pathfind-step because of map node/link of interrupt type
|
||||
if hasattr(interrupt_node_or_link, "node_index"):
|
||||
message = exit_obj.destination.attributes.get(
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg)
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg
|
||||
)
|
||||
# we move into the node/room and then stop
|
||||
caller.execute_cmd(exit_name, session=session)
|
||||
else:
|
||||
# if the link is interrupted we don't cross it at all
|
||||
message = exit_obj.attributes.get(
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg)
|
||||
"xyz_path_interrupt_msg", default=self.default_xyz_path_interrupt_msg
|
||||
)
|
||||
caller.msg(message)
|
||||
return
|
||||
|
||||
|
|
@ -335,7 +356,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
xymap=path_data.xymap,
|
||||
directions=path_data.directions,
|
||||
step_sequence=path_data.step_sequence,
|
||||
task=delay(self.auto_step_delay, self._auto_step, caller, session)
|
||||
task=delay(self.auto_step_delay, self._auto_step, caller, session),
|
||||
)
|
||||
|
||||
def func(self):
|
||||
|
|
@ -344,7 +365,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
"""
|
||||
|
||||
caller = self.caller
|
||||
goto_mode = self.cmdname == 'goto'
|
||||
goto_mode = self.cmdname == "goto"
|
||||
|
||||
# check if we have an existing path
|
||||
path_data = caller.ndb.xy_path_data
|
||||
|
|
@ -359,8 +380,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
caller.msg(f"Aborted auto-walking to {target_name}.")
|
||||
return
|
||||
# goto/path-command will show current path
|
||||
current_path = list_to_string(
|
||||
[f"|w{step}|n" for step in path_data.directions])
|
||||
current_path = list_to_string([f"|w{step}|n" for step in path_data.directions])
|
||||
moving = "(moving)" if task and task.active() else ""
|
||||
caller.msg(f"Path to {target_name}{moving}: {current_path}")
|
||||
else:
|
||||
|
|
@ -405,12 +425,21 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
|||
xy_end = xyz_end[:2]
|
||||
directions, step_sequence = xymap.get_shortest_path(xy_start, xy_end)
|
||||
|
||||
caller.msg(f"There are {len(directions)} steps to {target.get_display_name(caller)}: "
|
||||
f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n")
|
||||
caller.msg(
|
||||
f"There are {len(directions)} steps to {target.get_display_name(caller)}: "
|
||||
f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n"
|
||||
)
|
||||
|
||||
# create data for display and start stepping if we used goto
|
||||
self._auto_step(caller, self.session, target=target, xymap=xymap,
|
||||
directions=directions, step_sequence=step_sequence, step=goto_mode)
|
||||
self._auto_step(
|
||||
caller,
|
||||
self.session,
|
||||
target=target,
|
||||
xymap=xymap,
|
||||
directions=directions,
|
||||
step_sequence=step_sequence,
|
||||
step=goto_mode,
|
||||
)
|
||||
|
||||
|
||||
class CmdMap(COMMAND_DEFAULT_CLASS):
|
||||
|
|
@ -424,6 +453,7 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
|
|||
This is a builder-command.
|
||||
|
||||
"""
|
||||
|
||||
key = "map"
|
||||
locks = "cmd:perm(Builders)"
|
||||
|
||||
|
|
@ -453,8 +483,10 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
xymap = xyzgrid.get_map(Z)
|
||||
if not xymap:
|
||||
self.caller.msg(f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
|
||||
"available maps/Zcoords.")
|
||||
self.caller.msg(
|
||||
f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
|
||||
"available maps/Zcoords."
|
||||
)
|
||||
return
|
||||
|
||||
self.caller.msg(ansi.raw(xymap.mapstring))
|
||||
|
|
@ -465,6 +497,7 @@ class XYZGridCmdSet(CmdSet):
|
|||
Cmdset for easily adding the above cmds to the character cmdset.
|
||||
|
||||
"""
|
||||
|
||||
key = "xyzgrid_cmdset"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
|
|
|
|||
|
|
@ -81,14 +81,13 @@ class TransitionToCave(xymap_legend.TransitionMapNode):
|
|||
into a room but only acts as a target for finding the exit's destination.
|
||||
|
||||
"""
|
||||
symbol = 'T'
|
||||
target_map_xyz = (1, 0, 'the small cave')
|
||||
|
||||
symbol = "T"
|
||||
target_map_xyz = (1, 0, "the small cave")
|
||||
|
||||
|
||||
# extends the default legend
|
||||
LEGEND_MAP1 = {
|
||||
'T': TransitionToCave
|
||||
}
|
||||
LEGEND_MAP1 = {"T": TransitionToCave}
|
||||
|
||||
|
||||
# link coordinates to rooms
|
||||
|
|
@ -96,70 +95,62 @@ PROTOTYPES_MAP1 = {
|
|||
# node/room prototypes
|
||||
(3, 0): {
|
||||
"key": "Dungeon Entrance",
|
||||
"desc": "To the east, a narrow opening leads into darkness."
|
||||
"desc": "To the east, a narrow opening leads into darkness.",
|
||||
},
|
||||
(4, 1): {
|
||||
"key": "Under the foilage of a giant tree",
|
||||
"desc": "High above the branches of a giant tree blocks out the sunlight. A slide "
|
||||
"leading down from the upper branches ends here."
|
||||
"leading down from the upper branches ends here.",
|
||||
},
|
||||
(4, 4): {
|
||||
"key": "The slide",
|
||||
"desc": "A slide leads down to the ground from here. It looks like a one-way trip."
|
||||
"desc": "A slide leads down to the ground from here. It looks like a one-way trip.",
|
||||
},
|
||||
(6, 1): {
|
||||
"key": "Thorny path",
|
||||
"desc": "To the east is a pathway of thorns. If you get through, you don't think you'll be "
|
||||
"able to get back here the same way."
|
||||
},
|
||||
(8, 1): {
|
||||
"key": "By a large tree",
|
||||
"desc": "You are standing at the root of a great tree."
|
||||
},
|
||||
(8, 3): {
|
||||
"key": "At the top of the tree",
|
||||
"desc": "You are at the top of the tree."
|
||||
"able to get back here the same way.",
|
||||
},
|
||||
(8, 1): {"key": "By a large tree", "desc": "You are standing at the root of a great tree."},
|
||||
(8, 3): {"key": "At the top of the tree", "desc": "You are at the top of the tree."},
|
||||
(3, 7): {
|
||||
"key": "Dense foilage",
|
||||
"desc": "The foilage to the east is extra dense. It will take forever to get through it."
|
||||
"desc": "The foilage to the east is extra dense. It will take forever to get through it.",
|
||||
},
|
||||
(5, 6): {
|
||||
"key": "On a huge branch",
|
||||
"desc": "To the east is a glowing light, may be a teleporter to a higher branch."
|
||||
"desc": "To the east is a glowing light, may be a teleporter to a higher branch.",
|
||||
},
|
||||
(9, 7): {
|
||||
"key": "On an enormous branch",
|
||||
"desc": "To the west is a glowing light. It may be a teleporter to a lower branch."
|
||||
"desc": "To the west is a glowing light. It may be a teleporter to a lower branch.",
|
||||
},
|
||||
(10, 8): {
|
||||
"key": "A gorgeous view",
|
||||
"desc": "The view from here is breathtaking, showing the forest stretching far and wide."
|
||||
"desc": "The view from here is breathtaking, showing the forest stretching far and wide.",
|
||||
},
|
||||
# default rooms
|
||||
('*', '*'): {
|
||||
("*", "*"): {
|
||||
"key": "Among the branches of a giant tree",
|
||||
"desc": "These branches are wide enough to easily walk on. There's green all around."
|
||||
"desc": "These branches are wide enough to easily walk on. There's green all around.",
|
||||
},
|
||||
# directional prototypes
|
||||
(3, 0, 'e'): {
|
||||
"desc": "A dark passage into the underworld."
|
||||
},
|
||||
(3, 0, "e"): {"desc": "A dark passage into the underworld."},
|
||||
}
|
||||
|
||||
for key, prot in PROTOTYPES_MAP1.items():
|
||||
if len(key) == 2:
|
||||
# we don't want to give exits the room typeclass!
|
||||
prot['prototype_parent'] = ROOM_PARENT
|
||||
prot["prototype_parent"] = ROOM_PARENT
|
||||
else:
|
||||
prot['prototype_parent'] = EXIT_PARENT
|
||||
prot["prototype_parent"] = EXIT_PARENT
|
||||
|
||||
|
||||
XYMAP_DATA_MAP1 = {
|
||||
"zcoord": "the large tree",
|
||||
"map": MAP1,
|
||||
"legend": LEGEND_MAP1,
|
||||
"prototypes": PROTOTYPES_MAP1
|
||||
"prototypes": PROTOTYPES_MAP1,
|
||||
}
|
||||
|
||||
# -------------------------------------- map2
|
||||
|
|
@ -188,14 +179,13 @@ class TransitionToLargeTree(xymap_legend.TransitionMapNode):
|
|||
into a room by only acts as a target for finding the exit's destination.
|
||||
|
||||
"""
|
||||
symbol = 'T'
|
||||
target_map_xyz = (3, 0, 'the large tree')
|
||||
|
||||
symbol = "T"
|
||||
target_map_xyz = (3, 0, "the large tree")
|
||||
|
||||
|
||||
# this extends the default legend (that defines #,-+ etc)
|
||||
LEGEND_MAP2 = {
|
||||
"T": TransitionToLargeTree
|
||||
}
|
||||
LEGEND_MAP2 = {"T": TransitionToLargeTree}
|
||||
|
||||
# prototypes for specific locations
|
||||
PROTOTYPES_MAP2 = {
|
||||
|
|
@ -203,64 +193,54 @@ PROTOTYPES_MAP2 = {
|
|||
(1, 0): {
|
||||
"key": "The entrance",
|
||||
"desc": "This is the entrance to a small cave leading into the ground. "
|
||||
"Light sifts in from the outside, while cavernous passages disappear "
|
||||
"into darkness."
|
||||
"Light sifts in from the outside, while cavernous passages disappear "
|
||||
"into darkness.",
|
||||
},
|
||||
(2, 0): {
|
||||
"key": "A gruesome sight.",
|
||||
"desc": "Something was killed here recently. The smell is unbearable."
|
||||
"desc": "Something was killed here recently. The smell is unbearable.",
|
||||
},
|
||||
(1, 1): {
|
||||
"key": "A dark pathway",
|
||||
"desc": "The path splits three ways here. To the north a faint light can be seen."
|
||||
"desc": "The path splits three ways here. To the north a faint light can be seen.",
|
||||
},
|
||||
(3, 2): {
|
||||
"key": "Stagnant water",
|
||||
"desc": "A pool of stagnant, black water dominates this small chamber. To the nortwest "
|
||||
"a faint light can be seen."
|
||||
},
|
||||
(0, 2): {
|
||||
"key": "A dark alcove",
|
||||
"desc": "This alcove is empty."
|
||||
"a faint light can be seen.",
|
||||
},
|
||||
(0, 2): {"key": "A dark alcove", "desc": "This alcove is empty."},
|
||||
(1, 2): {
|
||||
"key": "South-west corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones."
|
||||
"between the stones.",
|
||||
},
|
||||
(2, 2): {
|
||||
"key": "South-east corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones."
|
||||
"between the stones.",
|
||||
},
|
||||
(1, 3): {
|
||||
"key": "North-west corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones."
|
||||
"between the stones.",
|
||||
},
|
||||
(2, 3): {
|
||||
"key": "North-east corner of the atrium",
|
||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||
"between the stones. To the east is a dark passage."
|
||||
"between the stones. To the east is a dark passage.",
|
||||
},
|
||||
(3, 3): {
|
||||
"key": "Craggy crevice",
|
||||
"desc": "This is the deepest part of the dungeon. The path shrinks away and there "
|
||||
"is no way to continue deeper."
|
||||
"is no way to continue deeper.",
|
||||
},
|
||||
# default fallback for undefined nodes
|
||||
('*', '*'): {
|
||||
"key": "A dark room",
|
||||
"desc": "A dark, but empty, room."
|
||||
},
|
||||
("*", "*"): {"key": "A dark room", "desc": "A dark, but empty, room."},
|
||||
# directional prototypes
|
||||
(1, 0, 'w'): {
|
||||
"desc": "A narrow path to the fresh air of the outside world."
|
||||
},
|
||||
(1, 0, "w"): {"desc": "A narrow path to the fresh air of the outside world."},
|
||||
# directional fallbacks for unset directions
|
||||
('*', '*', '*'): {
|
||||
"desc": "A dark passage"
|
||||
}
|
||||
("*", "*", "*"): {"desc": "A dark passage"},
|
||||
}
|
||||
|
||||
# this is required by the prototypes, but we add it all at once so we don't
|
||||
|
|
@ -268,9 +248,9 @@ PROTOTYPES_MAP2 = {
|
|||
for key, prot in PROTOTYPES_MAP2.items():
|
||||
if len(key) == 2:
|
||||
# we don't want to give exits the room typeclass!
|
||||
prot['prototype_parent'] = ROOM_PARENT
|
||||
prot["prototype_parent"] = ROOM_PARENT
|
||||
else:
|
||||
prot['prototype_parent'] = EXIT_PARENT
|
||||
prot["prototype_parent"] = EXIT_PARENT
|
||||
|
||||
|
||||
XYMAP_DATA_MAP2 = {
|
||||
|
|
@ -278,14 +258,8 @@ XYMAP_DATA_MAP2 = {
|
|||
"zcoord": "the small cave",
|
||||
"legend": LEGEND_MAP2,
|
||||
"prototypes": PROTOTYPES_MAP2,
|
||||
"options": {
|
||||
"map_visual_range": 1,
|
||||
"map_mode": 'scan'
|
||||
}
|
||||
"options": {"map_visual_range": 1, "map_mode": "scan"},
|
||||
}
|
||||
|
||||
# This is read by the parser
|
||||
XYMAP_DATA_LIST = [
|
||||
XYMAP_DATA_MAP1,
|
||||
XYMAP_DATA_MAP2
|
||||
]
|
||||
XYMAP_DATA_LIST = [XYMAP_DATA_MAP1, XYMAP_DATA_MAP2]
|
||||
|
|
|
|||
|
|
@ -160,11 +160,12 @@ _TOPICS_MAP = {
|
|||
"add": _HELP_ADD,
|
||||
"spawn": _HELP_SPAWN,
|
||||
"initpath": _HELP_INITPATH,
|
||||
"delete": _HELP_DELETE
|
||||
"delete": _HELP_DELETE,
|
||||
}
|
||||
|
||||
evennia._init()
|
||||
|
||||
|
||||
def _option_help(*suboptions):
|
||||
"""
|
||||
Show help <command> aid.
|
||||
|
|
@ -188,6 +189,7 @@ def _option_list(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
xyzgrid.log = _log
|
||||
|
||||
xymap_data = xyzgrid.grid
|
||||
|
|
@ -210,7 +212,7 @@ def _option_list(*suboptions):
|
|||
if not xymap:
|
||||
print(f"No XYMap with Z='{zcoord}' was found on grid.")
|
||||
else:
|
||||
nrooms = xyzgrid.get_room(('*', '*', zcoord)).count()
|
||||
nrooms = xyzgrid.get_room(("*", "*", zcoord)).count()
|
||||
nnodes = len(xymap.node_index_map)
|
||||
print("\n" + str(repr(xymap)) + ":\n")
|
||||
checkwarning = True
|
||||
|
|
@ -218,22 +220,29 @@ def _option_list(*suboptions):
|
|||
print(f"{nrooms} / {nnodes} rooms are spawned.")
|
||||
checkwarning = False
|
||||
elif nrooms < nnodes:
|
||||
print(f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Transitional nodes are *not* spawned (they just point \n"
|
||||
"to another map), so the 'missing room(s)' may just be from such nodes.")
|
||||
print(
|
||||
f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Transitional nodes are *not* spawned (they just point \n"
|
||||
"to another map), so the 'missing room(s)' may just be from such nodes."
|
||||
)
|
||||
elif nrooms > nnodes:
|
||||
print(f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync.")
|
||||
print(
|
||||
f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync."
|
||||
)
|
||||
else:
|
||||
print(f"{nrooms} / {nnodes} rooms are spawned\n")
|
||||
|
||||
if checkwarning:
|
||||
print("Note: This check is not complete; it does not consider changed map "
|
||||
"topology\nlike relocated nodes/rooms and new/removed links/exits - this "
|
||||
"is calculated only during a spawn.")
|
||||
print(
|
||||
"Note: This check is not complete; it does not consider changed map "
|
||||
"topology\nlike relocated nodes/rooms and new/removed links/exits - this "
|
||||
"is calculated only during a spawn."
|
||||
)
|
||||
print("\nDisplayed map (as appearing in-game):\n\n" + ansi.parse_ansi(str(xymap)))
|
||||
print("\nRaw map string (including axes and invisible nodes/links):\n"
|
||||
+ str(xymap.mapstring))
|
||||
print(
|
||||
"\nRaw map string (including axes and invisible nodes/links):\n" + str(xymap.mapstring)
|
||||
)
|
||||
print(f"\nCustom map options: {xymap.options}\n")
|
||||
legend = []
|
||||
for key, node_or_link in xymap.legend.items():
|
||||
|
|
@ -260,6 +269,7 @@ def _option_add(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
xymap_data_list = []
|
||||
|
|
@ -290,35 +300,44 @@ def _option_spawn(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
if suboptions:
|
||||
opts = ''.join(suboptions).strip('()')
|
||||
opts = "".join(suboptions).strip("()")
|
||||
# coordinate tuple
|
||||
try:
|
||||
x, y, z = (part.strip() for part in opts.split(","))
|
||||
except ValueError:
|
||||
print("spawn coordinate must be given as (X, Y, Z) tuple, where '*' act "
|
||||
"wild cards and Z is the mapname/z-coord of the map to load.")
|
||||
print(
|
||||
"spawn coordinate must be given as (X, Y, Z) tuple, where '*' act "
|
||||
"wild cards and Z is the mapname/z-coord of the map to load."
|
||||
)
|
||||
return
|
||||
else:
|
||||
x, y, z = '*', '*', '*'
|
||||
x, y, z = "*", "*", "*"
|
||||
|
||||
if x == y == z == '*':
|
||||
inp = input("This will (re)spawn the entire grid. If it was built before, it may spawn \n"
|
||||
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
|
||||
"continue? [Y]/N? ")
|
||||
if x == y == z == "*":
|
||||
inp = input(
|
||||
"This will (re)spawn the entire grid. If it was built before, it may spawn \n"
|
||||
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
|
||||
"continue? [Y]/N? "
|
||||
)
|
||||
else:
|
||||
inp = input("This will spawn/delete objects in the database matching grid coordinates \n"
|
||||
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? ")
|
||||
if inp.lower() in ('no', 'n'):
|
||||
inp = input(
|
||||
"This will spawn/delete objects in the database matching grid coordinates \n"
|
||||
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? "
|
||||
)
|
||||
if inp.lower() in ("no", "n"):
|
||||
print("Aborted.")
|
||||
return
|
||||
|
||||
print("Beginner-Tutorial spawn ...")
|
||||
grid.spawn(xyz=(x, y, z))
|
||||
print("... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
|
||||
"modified an existing grid.")
|
||||
print(
|
||||
"... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
|
||||
"modified an existing grid."
|
||||
)
|
||||
|
||||
|
||||
def _option_initpath(*suboptions):
|
||||
|
|
@ -331,6 +350,7 @@ def _option_initpath(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
xymaps = grid.all_maps()
|
||||
|
|
@ -354,19 +374,24 @@ def _option_delete(*suboptions):
|
|||
# override grid's logger to echo directly to console
|
||||
def _log(msg):
|
||||
print(msg)
|
||||
|
||||
grid.log = _log
|
||||
|
||||
if not suboptions:
|
||||
repl = input("WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
|
||||
if repl.lower() not in ('yes', 'y'):
|
||||
repl = input(
|
||||
"WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? "
|
||||
)
|
||||
if repl.lower() not in ("yes", "y"):
|
||||
print("Aborted.")
|
||||
return
|
||||
print("Deleting grid ...")
|
||||
grid.delete()
|
||||
print("... done.\nPlease reload the server now; otherwise "
|
||||
"removed rooms may linger in cache.")
|
||||
print(
|
||||
"... done.\nPlease reload the server now; otherwise "
|
||||
"removed rooms may linger in cache."
|
||||
)
|
||||
return
|
||||
|
||||
zcoords = (part.strip() for part in suboptions)
|
||||
|
|
@ -376,21 +401,24 @@ def _option_delete(*suboptions):
|
|||
print(f"Mapname/zcoord {zcoord} is not a part of the grid.")
|
||||
err = True
|
||||
if err:
|
||||
print("Valid mapnames/zcoords are\n:", "\n ".join(
|
||||
xymap.Z for xymap in grid.all_rooms()))
|
||||
print("Valid mapnames/zcoords are\n:", "\n ".join(xymap.Z for xymap in grid.all_rooms()))
|
||||
return
|
||||
repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
|
||||
"rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
|
||||
if repl.lower() not in ('yes', 'y'):
|
||||
repl = input(
|
||||
"This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
|
||||
"rooms/exits!"
|
||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? "
|
||||
)
|
||||
if repl.lower() not in ("yes", "y"):
|
||||
print("Aborted.")
|
||||
return
|
||||
|
||||
print("Deleting selected xymaps ...")
|
||||
grid.remove_map(*zcoords, remove_objects=True)
|
||||
print("... done.\nPlease reload the server to refresh room caches."
|
||||
"\nAlso remember to remove any links from remaining maps pointing to deleted maps.")
|
||||
print(
|
||||
"... done.\nPlease reload the server to refresh room caches."
|
||||
"\nAlso remember to remove any links from remaining maps pointing to deleted maps."
|
||||
)
|
||||
|
||||
|
||||
def xyzcommand(*args):
|
||||
|
|
@ -405,20 +433,19 @@ def xyzcommand(*args):
|
|||
|
||||
option, *suboptions = args
|
||||
|
||||
if option in ('help', 'h'):
|
||||
if option in ("help", "h"):
|
||||
_option_help(*suboptions)
|
||||
if option in ('list', 'show'):
|
||||
if option in ("list", "show"):
|
||||
_option_list(*suboptions)
|
||||
elif option == 'init':
|
||||
elif option == "init":
|
||||
_option_init(*suboptions)
|
||||
elif option == 'add':
|
||||
elif option == "add":
|
||||
_option_add(*suboptions)
|
||||
elif option == 'spawn':
|
||||
elif option == "spawn":
|
||||
_option_spawn(*suboptions)
|
||||
elif option == 'initpath':
|
||||
elif option == "initpath":
|
||||
_option_initpath(*suboptions)
|
||||
elif option == 'delete':
|
||||
elif option == "delete":
|
||||
_option_delete(*suboptions)
|
||||
else:
|
||||
print(f"Unknown option '{option}'. Use 'evennia xyzgrid help' for valid arguments.")
|
||||
|
||||
|
|
|
|||
|
|
@ -27,19 +27,19 @@ except AttributeError:
|
|||
exit_override = {}
|
||||
|
||||
room_prototype = {
|
||||
'prototype_key': 'xyz_room',
|
||||
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom',
|
||||
'prototype_tags': ("xyzroom", ),
|
||||
'key': "A room",
|
||||
'desc': "An empty room."
|
||||
"prototype_key": "xyz_room",
|
||||
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom",
|
||||
"prototype_tags": ("xyzroom",),
|
||||
"key": "A room",
|
||||
"desc": "An empty room.",
|
||||
}
|
||||
room_prototype.update(room_override)
|
||||
|
||||
exit_prototype = {
|
||||
'prototype_key': 'xyz_exit',
|
||||
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit',
|
||||
'prototype_tags': ("xyzexit", ),
|
||||
'desc': "An exit."
|
||||
"prototype_key": "xyz_exit",
|
||||
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit",
|
||||
"prototype_tags": ("xyzexit",),
|
||||
"desc": "An exit.",
|
||||
}
|
||||
exit_prototype.update(exit_override)
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -30,13 +30,15 @@ MAPSCAN = {
|
|||
|
||||
# errors for Map system
|
||||
|
||||
class MapError(RuntimeError):
|
||||
|
||||
class MapError(RuntimeError):
|
||||
def __init__(self, error="", node_or_link=None):
|
||||
prefix = ""
|
||||
if node_or_link:
|
||||
prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
||||
f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) ")
|
||||
prefix = (
|
||||
f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
||||
f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) "
|
||||
)
|
||||
self.node_or_link = node_or_link
|
||||
self.message = f"{prefix}{error}"
|
||||
super().__init__(self.message)
|
||||
|
|
@ -52,4 +54,5 @@ class MapTransition(RuntimeWarning):
|
|||
leads to another map.
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -104,7 +104,8 @@ try:
|
|||
except ImportError as err:
|
||||
raise ImportError(
|
||||
f"{err}\nThe XYZgrid contrib requires "
|
||||
"the SciPy package. Install with `pip install scipy'.")
|
||||
"the SciPy package. Install with `pip install scipy'."
|
||||
)
|
||||
from django.conf import settings
|
||||
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
||||
from evennia.utils import logger
|
||||
|
|
@ -122,9 +123,7 @@ _CACHE_DIR = settings.CACHE_DIR
|
|||
_LOADED_PROTOTYPES = None
|
||||
_XYZROOMCLASS = None
|
||||
|
||||
MAP_DATA_KEYS = [
|
||||
"zcoord", "map", "legend", "prototypes", "options", "module_path"
|
||||
]
|
||||
MAP_DATA_KEYS = ["zcoord", "map", "legend", "prototypes", "options", "module_path"]
|
||||
|
||||
DEFAULT_LEGEND = xymap_legend.LEGEND
|
||||
|
||||
|
|
@ -172,11 +171,11 @@ class XYMap:
|
|||
but recommended for readability!
|
||||
|
||||
"""
|
||||
mapcorner_symbol = '+'
|
||||
mapcorner_symbol = "+"
|
||||
max_pathfinding_length = 500
|
||||
empty_symbol = ' '
|
||||
empty_symbol = " "
|
||||
# we normally only accept one single character for the legend key
|
||||
legend_key_exceptions = ("\\")
|
||||
legend_key_exceptions = "\\"
|
||||
|
||||
def __init__(self, map_module_or_dict, Z="map", xyzgrid=None):
|
||||
"""
|
||||
|
|
@ -210,7 +209,9 @@ class XYMap:
|
|||
if not _LOADED_PROTOTYPES:
|
||||
# inject default prototypes, but don't override prototype-keys loaded from
|
||||
# settings, if they exist (that means the user wants to replace the defaults)
|
||||
protlib.load_module_prototypes("evennia.contrib.grid.xyzgrid.prototypes", override=False)
|
||||
protlib.load_module_prototypes(
|
||||
"evennia.contrib.grid.xyzgrid.prototypes", override=False
|
||||
)
|
||||
_LOADED_PROTOTYPES = True
|
||||
|
||||
self.Z = Z
|
||||
|
|
@ -264,7 +265,7 @@ class XYMap:
|
|||
nnodes = 0
|
||||
if self.node_index_map:
|
||||
nnodes = len(self.node_index_map)
|
||||
return (f"<XYMap(Z={self.Z}), {self.max_X + 1}x{self.max_Y + 1}, {nnodes} nodes>")
|
||||
return f"<XYMap(Z={self.Z}), {self.max_X + 1}x{self.max_Y + 1}, {nnodes} nodes>"
|
||||
|
||||
def log(self, msg):
|
||||
if self.xyzgrid:
|
||||
|
|
@ -317,34 +318,41 @@ class XYMap:
|
|||
mapdata = variable_from_module(mod, "XYMAP_DATA")
|
||||
|
||||
if not mapdata:
|
||||
raise MapError("No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
|
||||
f"{map_module_or_dict}.")
|
||||
raise MapError(
|
||||
"No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
|
||||
f"{map_module_or_dict}."
|
||||
)
|
||||
|
||||
# validate
|
||||
if any(key for key in mapdata if key not in MAP_DATA_KEYS):
|
||||
raise MapError(f"Mapdata has keys {list(mapdata)}, but only "
|
||||
f"keys {MAP_DATA_KEYS} are allowed.")
|
||||
raise MapError(
|
||||
f"Mapdata has keys {list(mapdata)}, but only " f"keys {MAP_DATA_KEYS} are allowed."
|
||||
)
|
||||
|
||||
for key in mapdata.get('legend', DEFAULT_LEGEND):
|
||||
for key in mapdata.get("legend", DEFAULT_LEGEND):
|
||||
if not key or len(key) > 1:
|
||||
if key not in self.legend_key_exceptions:
|
||||
raise MapError(f"Map-legend key '{key}' is invalid: All keys must "
|
||||
"be exactly one character long. Use the node/link's "
|
||||
"`.display_symbol` property to change how it is "
|
||||
"displayed.")
|
||||
if 'map' not in mapdata or not mapdata['map']:
|
||||
raise MapError(
|
||||
f"Map-legend key '{key}' is invalid: All keys must "
|
||||
"be exactly one character long. Use the node/link's "
|
||||
"`.display_symbol` property to change how it is "
|
||||
"displayed."
|
||||
)
|
||||
if "map" not in mapdata or not mapdata["map"]:
|
||||
raise MapError("No map found. Add 'map' key to map-data dict.")
|
||||
for key, prototype in mapdata.get('prototypes', {}).items():
|
||||
for key, prototype in mapdata.get("prototypes", {}).items():
|
||||
if not (is_iter(key) and (2 <= len(key) <= 3)):
|
||||
raise MapError(f"Prototype override key {key} is malformed: It must be a "
|
||||
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
||||
"where direction is a supported direction string ('n', 'ne', etc).")
|
||||
raise MapError(
|
||||
f"Prototype override key {key} is malformed: It must be a "
|
||||
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
||||
"where direction is a supported direction string ('n', 'ne', etc)."
|
||||
)
|
||||
|
||||
# store/update result
|
||||
self.Z = mapdata.get('zcoord', self.Z)
|
||||
self.mapstring = mapdata['map']
|
||||
self.prototypes = mapdata.get('prototypes', {})
|
||||
self.options = mapdata.get('options', {})
|
||||
self.Z = mapdata.get("zcoord", self.Z)
|
||||
self.mapstring = mapdata["map"]
|
||||
self.prototypes = mapdata.get("prototypes", {})
|
||||
self.options = mapdata.get("options", {})
|
||||
|
||||
# merge the custom legend onto the default legend to allow easily
|
||||
# overriding only parts of it
|
||||
|
|
@ -357,8 +365,9 @@ class XYMap:
|
|||
# nothing more to do
|
||||
continue
|
||||
# we need to load the prototype dict onto each for ease of access. Note that
|
||||
proto = protlib.search_prototype(prototype, require_single=True,
|
||||
no_db=_NO_DB_PROTOTYPES)[0]
|
||||
proto = protlib.search_prototype(
|
||||
prototype, require_single=True, no_db=_NO_DB_PROTOTYPES
|
||||
)[0]
|
||||
node_or_link_class.prototype = proto
|
||||
|
||||
def parse(self):
|
||||
|
|
@ -391,7 +400,8 @@ class XYMap:
|
|||
raise MapParserError(
|
||||
f"The mapstring must have at least two '{mapcorner_symbol}' "
|
||||
"symbols marking the upper- and bottom-left corners of the "
|
||||
"grid area.")
|
||||
"grid area."
|
||||
)
|
||||
|
||||
# find the the position (in the string as a whole) of the top-left corner-marker
|
||||
maplines = mapstring.split("\n")
|
||||
|
|
@ -406,13 +416,15 @@ class XYMap:
|
|||
# find the position (in the string as a whole) of the bottom-left corner-marker
|
||||
# this is always in a stright line down from the first marker
|
||||
botleft_marker_x, botleft_marker_y = topleft_marker_x, -1
|
||||
for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1:]):
|
||||
for botleft_marker_y, line in enumerate(maplines[topleft_marker_y + 1 :]):
|
||||
if line.find(mapcorner_symbol) == topleft_marker_x:
|
||||
break
|
||||
if botleft_marker_y == -1:
|
||||
raise MapParserError(f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
||||
"Make sure it lines up with the top-left corner-marker "
|
||||
f"(found at column {topleft_marker_x} of the string).")
|
||||
raise MapParserError(
|
||||
f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
||||
"Make sure it lines up with the top-left corner-marker "
|
||||
f"(found at column {topleft_marker_x} of the string)."
|
||||
)
|
||||
# the actual coordinate is dy below the topleft marker so we need to shift
|
||||
botleft_marker_y += topleft_marker_y + 1
|
||||
|
||||
|
|
@ -443,8 +455,7 @@ class XYMap:
|
|||
mapnode_or_link_class = self.legend.get(char)
|
||||
if not mapnode_or_link_class:
|
||||
raise MapParserError(
|
||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) "
|
||||
"is not found in LEGEND."
|
||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) " "is not found in LEGEND."
|
||||
)
|
||||
if hasattr(mapnode_or_link_class, "node_index"):
|
||||
# A mapnode. Mapnodes can only be placed on even grid positions, where
|
||||
|
|
@ -454,7 +465,8 @@ class XYMap:
|
|||
raise MapParserError(
|
||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) marks a "
|
||||
"MapNode but is located between integer (X,Y) positions (only "
|
||||
"Links can be placed between coordinates)!")
|
||||
"Links can be placed between coordinates)!"
|
||||
)
|
||||
|
||||
# save the node to several different maps for different uses
|
||||
# in both coordinate systems
|
||||
|
|
@ -462,14 +474,17 @@ class XYMap:
|
|||
max_X, max_Y = max(max_X, iX), max(max_Y, iY)
|
||||
node_index += 1
|
||||
|
||||
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[node_index] = \
|
||||
mapnode_or_link_class(x=ix, y=iy, Z=self.Z,
|
||||
node_index=node_index, symbol=char, xymap=self)
|
||||
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[
|
||||
node_index
|
||||
] = mapnode_or_link_class(
|
||||
x=ix, y=iy, Z=self.Z, node_index=node_index, symbol=char, xymap=self
|
||||
)
|
||||
|
||||
else:
|
||||
# we have a link at this xygrid position (this is ok everywhere)
|
||||
xygrid[ix][iy] = mapnode_or_link_class(x=ix, y=iy, Z=self.Z, symbol=char,
|
||||
xymap=self)
|
||||
xygrid[ix][iy] = mapnode_or_link_class(
|
||||
x=ix, y=iy, Z=self.Z, symbol=char, xymap=self
|
||||
)
|
||||
|
||||
# store the symbol mapping for transition lookups
|
||||
symbol_map[char].append(xygrid[ix][iy])
|
||||
|
|
@ -499,20 +514,23 @@ class XYMap:
|
|||
node_coord = (node.X, node.Y)
|
||||
# load prototype from override, or use default
|
||||
try:
|
||||
node.prototype = flatten_prototype(self.prototypes.get(
|
||||
node_coord,
|
||||
self.prototypes.get(('*', '*'), node.prototype)),
|
||||
no_db=_NO_DB_PROTOTYPES
|
||||
node.prototype = flatten_prototype(
|
||||
self.prototypes.get(
|
||||
node_coord, self.prototypes.get(("*", "*"), node.prototype)
|
||||
),
|
||||
no_db=_NO_DB_PROTOTYPES,
|
||||
)
|
||||
except Exception as err:
|
||||
raise MapParserError(f"Room prototype malformed: {err}", node)
|
||||
# do the same for links (x, y, direction) coords
|
||||
for direction, maplink in node.first_links.items():
|
||||
try:
|
||||
maplink.prototype = flatten_prototype(self.prototypes.get(
|
||||
node_coord + (direction,),
|
||||
self.prototypes.get(('*', '*', '*'), maplink.prototype)),
|
||||
no_db=_NO_DB_PROTOTYPES
|
||||
maplink.prototype = flatten_prototype(
|
||||
self.prototypes.get(
|
||||
node_coord + (direction,),
|
||||
self.prototypes.get(("*", "*", "*"), maplink.prototype),
|
||||
),
|
||||
no_db=_NO_DB_PROTOTYPES,
|
||||
)
|
||||
except Exception as err:
|
||||
raise MapParserError(f"Exit prototype malformed: {err}", maplink)
|
||||
|
|
@ -539,8 +557,10 @@ class XYMap:
|
|||
This performs a depth-first pass down the the given dist.
|
||||
|
||||
"""
|
||||
def _scan_neighbors(start_node, points, dist=2,
|
||||
xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0):
|
||||
|
||||
def _scan_neighbors(
|
||||
start_node, points, dist=2, xmin=BIGVAL, ymin=BIGVAL, xmax=0, ymax=0, depth=0
|
||||
):
|
||||
|
||||
x0, y0 = start_node.x, start_node.y
|
||||
points.append((x0, y0))
|
||||
|
|
@ -558,9 +578,15 @@ class XYMap:
|
|||
ymin, ymax = min(ymin, y), max(ymax, y)
|
||||
|
||||
points, xmin, xmax, ymin, ymax = _scan_neighbors(
|
||||
end_node, points, dist=dist,
|
||||
xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
|
||||
depth=depth + 1)
|
||||
end_node,
|
||||
points,
|
||||
dist=dist,
|
||||
xmin=xmin,
|
||||
ymin=ymin,
|
||||
xmax=xmax,
|
||||
ymax=ymax,
|
||||
depth=depth + 1,
|
||||
)
|
||||
|
||||
return points, xmin, xmax, ymin, ymax
|
||||
|
||||
|
|
@ -581,14 +607,16 @@ class XYMap:
|
|||
# check if the solution for this grid was already solved previously.
|
||||
|
||||
mapstr, dist_matrix, pathfinding_routes = "", None, None
|
||||
with open(self.pathfinder_baked_filename, 'rb') as fil:
|
||||
with open(self.pathfinder_baked_filename, "rb") as fil:
|
||||
try:
|
||||
mapstr, dist_matrix, pathfinding_routes = pickle.load(fil)
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
if (mapstr == self.mapstring
|
||||
and dist_matrix is not None
|
||||
and pathfinding_routes is not None):
|
||||
if (
|
||||
mapstr == self.mapstring
|
||||
and dist_matrix is not None
|
||||
and pathfinding_routes is not None
|
||||
):
|
||||
# this is important - it means the map hasn't changed so
|
||||
# we can re-use the stored data!
|
||||
self.dist_matrix = dist_matrix
|
||||
|
|
@ -606,16 +634,20 @@ class XYMap:
|
|||
|
||||
# solve using Dijkstra's algorithm
|
||||
self.dist_matrix, self.pathfinding_routes = dijkstra(
|
||||
pathfinding_matrix, directed=True,
|
||||
return_predecessors=True, limit=self.max_pathfinding_length)
|
||||
pathfinding_matrix,
|
||||
directed=True,
|
||||
return_predecessors=True,
|
||||
limit=self.max_pathfinding_length,
|
||||
)
|
||||
|
||||
if self.pathfinder_baked_filename:
|
||||
# try to cache the results
|
||||
with open(self.pathfinder_baked_filename, 'wb') as fil:
|
||||
pickle.dump((self.mapstring, self.dist_matrix, self.pathfinding_routes),
|
||||
fil, protocol=4)
|
||||
with open(self.pathfinder_baked_filename, "wb") as fil:
|
||||
pickle.dump(
|
||||
(self.mapstring, self.dist_matrix, self.pathfinding_routes), fil, protocol=4
|
||||
)
|
||||
|
||||
def spawn_nodes(self, xy=('*', '*')):
|
||||
def spawn_nodes(self, xy=("*", "*")):
|
||||
"""
|
||||
Convert the nodes of this XYMap into actual in-world rooms by spawning their
|
||||
related prototypes in the correct coordinate positions. This must be done *first*
|
||||
|
|
@ -638,12 +670,14 @@ class XYMap:
|
|||
if not _XYZROOMCLASS:
|
||||
from evennia.contrib.grid.xyzgrid.xyzroom import XYZRoom as _XYZROOMCLASS
|
||||
x, y = xy
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
spawned = []
|
||||
|
||||
# find existing nodes, in case some rooms need to be removed
|
||||
map_coords = [(node.X, node.Y) for node in
|
||||
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))]
|
||||
map_coords = [
|
||||
(node.X, node.Y)
|
||||
for node in sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))
|
||||
]
|
||||
for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)):
|
||||
roomX, roomY, _ = existing_room.xyz
|
||||
if (roomX, roomY) not in map_coords:
|
||||
|
|
@ -657,7 +691,7 @@ class XYMap:
|
|||
spawned.append(node)
|
||||
return spawned
|
||||
|
||||
def spawn_links(self, xy=('*', '*'), nodes=None, directions=None):
|
||||
def spawn_links(self, xy=("*", "*"), nodes=None, directions=None):
|
||||
"""
|
||||
Convert links of this XYMap into actual in-game exits by spawning their related
|
||||
prototypes. It's possible to only spawn a specic exit by specifying the node and
|
||||
|
|
@ -676,7 +710,7 @@ class XYMap:
|
|||
|
||||
"""
|
||||
x, y = xy
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
if not nodes:
|
||||
nodes = sorted(self.node_index_map.values(), key=lambda n: (n.Z, n.Y, n.X))
|
||||
|
|
@ -706,8 +740,10 @@ class XYMap:
|
|||
|
||||
iX, iY = xy
|
||||
if not ((0 <= iX <= self.max_X) and (0 <= iY <= self.max_Y)):
|
||||
raise MapError(f"get_node_from_coord got coordinate {xy} which is "
|
||||
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
|
||||
raise MapError(
|
||||
f"get_node_from_coord got coordinate {xy} which is "
|
||||
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y})."
|
||||
)
|
||||
try:
|
||||
return self.XYgrid[iX][iY]
|
||||
except KeyError:
|
||||
|
|
@ -753,8 +789,10 @@ class XYMap:
|
|||
istartnode = startnode.node_index
|
||||
inextnode = endnode.node_index
|
||||
except AttributeError:
|
||||
raise MapError(f"Map.get_shortest_path received start/end nodes {startnode} and "
|
||||
f"{endnode}. They must both be MapNodes (not Links)")
|
||||
raise MapError(
|
||||
f"Map.get_shortest_path received start/end nodes {startnode} and "
|
||||
f"{endnode}. They must both be MapNodes (not Links)"
|
||||
)
|
||||
|
||||
if self.pathfinding_routes is None:
|
||||
self.calculate_path_matrix()
|
||||
|
|
@ -782,13 +820,18 @@ class XYMap:
|
|||
|
||||
return directions, path
|
||||
|
||||
def get_visual_range(self, xy, dist=2, mode='nodes',
|
||||
character='@',
|
||||
target=None,
|
||||
target_path_style="|y{display_symbol}|n",
|
||||
max_size=None,
|
||||
indent=0,
|
||||
return_str=True):
|
||||
def get_visual_range(
|
||||
self,
|
||||
xy,
|
||||
dist=2,
|
||||
mode="nodes",
|
||||
character="@",
|
||||
target=None,
|
||||
target_path_style="|y{display_symbol}|n",
|
||||
max_size=None,
|
||||
indent=0,
|
||||
return_str=True,
|
||||
):
|
||||
"""
|
||||
Get a part of the grid centered on a specific point and extended a certain number
|
||||
of nodes or grid points in every direction.
|
||||
|
|
@ -876,7 +919,7 @@ class XYMap:
|
|||
# nothing but ourselves or emptiness
|
||||
return character if character else self.empty_symbol
|
||||
|
||||
elif mode == 'nodes':
|
||||
elif mode == "nodes":
|
||||
# dist measures only full, reachable nodes.
|
||||
points, xmin, xmax, ymin, ymax = self._get_topology_around_coord(xy, dist=dist)
|
||||
|
||||
|
|
@ -888,7 +931,7 @@ class XYMap:
|
|||
for (ix0, iy0) in points:
|
||||
gridmap[iy0 - ymin][ix0 - xmin] = display_map[iy0][ix0]
|
||||
|
||||
elif mode == 'scan':
|
||||
elif mode == "scan":
|
||||
# scan-mode - dist measures individual grid points
|
||||
|
||||
xmin, xmax = max(0, ix - dist), min(width, ix + dist + 1)
|
||||
|
|
@ -897,8 +940,10 @@ class XYMap:
|
|||
gridmap = [line[xmin:xmax] for line in display_map[ymin:ymax]]
|
||||
|
||||
else:
|
||||
raise MapError(f"Map.get_visual_range 'mode' was '{mode}' "
|
||||
"- it must be either 'scan' or 'nodes'.")
|
||||
raise MapError(
|
||||
f"Map.get_visual_range 'mode' was '{mode}' "
|
||||
"- it must be either 'scan' or 'nodes'."
|
||||
)
|
||||
if character:
|
||||
gridmap[iyc][ixc] = character # correct indexing; it's a list of lines
|
||||
|
||||
|
|
@ -906,8 +951,7 @@ class XYMap:
|
|||
# stylize path to target
|
||||
|
||||
def _default_callable(node):
|
||||
return target_path_style.format(
|
||||
display_symbol=node.get_display_symbol())
|
||||
return target_path_style.format(display_symbol=node.get_display_symbol())
|
||||
|
||||
if callable(target_path_style):
|
||||
_target_path_style = target_path_style
|
||||
|
|
@ -916,7 +960,7 @@ class XYMap:
|
|||
|
||||
_, path = self.get_shortest_path(xy, target)
|
||||
|
||||
maxstep = dist if mode == 'nodes' else dist / 2
|
||||
maxstep = dist if mode == "nodes" else dist / 2
|
||||
nsteps = 0
|
||||
for node_or_link in path[1:]:
|
||||
if hasattr(node_or_link, "node_index"):
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ try:
|
|||
except ImportError as err:
|
||||
raise ImportError(
|
||||
f"{err}\nThe XYZgrid contrib requires "
|
||||
"the SciPy package. Install with `pip install scipy'.")
|
||||
"the SciPy package. Install with `pip install scipy'."
|
||||
)
|
||||
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
|
|
@ -33,6 +34,7 @@ UUID_XYZ_NAMESPACE = uuid.uuid5(uuid.UUID(int=0), "xyzgrid")
|
|||
|
||||
# Nodes/Links
|
||||
|
||||
|
||||
class MapNode:
|
||||
"""
|
||||
This represents a 'room' node on the map. Note that the map system deals with two grids, the
|
||||
|
|
@ -62,8 +64,9 @@ class MapNode:
|
|||
for various reasons, mostly map-transitions).
|
||||
|
||||
"""
|
||||
|
||||
# symbol used to identify this link on the map
|
||||
symbol = '#'
|
||||
symbol = "#"
|
||||
# if printing this node should show another symbol. If set
|
||||
# to the empty string, use `symbol`.
|
||||
display_symbol = None
|
||||
|
|
@ -79,16 +82,16 @@ class MapNode:
|
|||
multilink = True
|
||||
# default values to use if the exit doesn't have a 'spawn_aliases' iterable
|
||||
direction_spawn_defaults = {
|
||||
'n': ('north', 'n'),
|
||||
'ne': ('northeast', 'ne', 'north-east'),
|
||||
'e': ('east', 'e'),
|
||||
'se': ('southeast', 'se', 'south-east'),
|
||||
's': ('south', 's'),
|
||||
'sw': ('southwest', 'sw', 'south-west'),
|
||||
'w': ('west', 'w'),
|
||||
'nw': ('northwest', 'nw', 'north-west'),
|
||||
'd': ('down', 'd', 'do'),
|
||||
'u': ('up', 'u'),
|
||||
"n": ("north", "n"),
|
||||
"ne": ("northeast", "ne", "north-east"),
|
||||
"e": ("east", "e"),
|
||||
"se": ("southeast", "se", "south-east"),
|
||||
"s": ("south", "s"),
|
||||
"sw": ("southwest", "sw", "south-west"),
|
||||
"w": ("west", "w"),
|
||||
"nw": ("northwest", "nw", "north-west"),
|
||||
"d": ("down", "d", "do"),
|
||||
"u": ("up", "u"),
|
||||
}
|
||||
|
||||
def __init__(self, x, y, Z, node_index=0, symbol=None, xymap=None):
|
||||
|
|
@ -202,7 +205,9 @@ class MapNode:
|
|||
if first_step_name in self.closest_neighbor_names:
|
||||
raise MapParserError(
|
||||
f"has more than one outgoing direction '{first_step_name}'. "
|
||||
"All directions out of a node must be unique.", self)
|
||||
"All directions out of a node must be unique.",
|
||||
self,
|
||||
)
|
||||
self.closest_neighbor_names[first_step_name] = direction
|
||||
|
||||
node_index = end_node.node_index
|
||||
|
|
@ -215,8 +220,9 @@ class MapNode:
|
|||
# used for building the shortest path. Note that we store the
|
||||
# aliased link directions here, for quick display by the
|
||||
# shortest-route solver
|
||||
shortest_route = self.shortest_route_to_node.get(
|
||||
node_index, ("", [], BIGVAL))[2]
|
||||
shortest_route = self.shortest_route_to_node.get(node_index, ("", [], BIGVAL))[
|
||||
2
|
||||
]
|
||||
if weight < shortest_route:
|
||||
self.shortest_route_to_node[node_index] = (first_step_name, steps, weight)
|
||||
|
||||
|
|
@ -280,11 +286,9 @@ class MapNode:
|
|||
str or tuple: The key of the spawned exit, or a tuple (key, alias, alias, ...)
|
||||
|
||||
"""
|
||||
key, *aliases = (
|
||||
self.first_links[direction]
|
||||
.spawn_aliases.get(
|
||||
direction, self.direction_spawn_defaults.get(
|
||||
direction, ('unknown', ))))
|
||||
key, *aliases = self.first_links[direction].spawn_aliases.get(
|
||||
direction, self.direction_spawn_defaults.get(direction, ("unknown",))
|
||||
)
|
||||
if return_aliases:
|
||||
return (key, *aliases)
|
||||
return key
|
||||
|
|
@ -313,28 +317,24 @@ class MapNode:
|
|||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||
except django_exceptions.ObjectDoesNotExist:
|
||||
# create a new entity with proper coordinates etc
|
||||
tclass = self.prototype['typeclass']
|
||||
tclass = (f' ({tclass})'
|
||||
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom'
|
||||
else '')
|
||||
self.log(f" spawning room at xyz={xyz}{tclass}")
|
||||
nodeobj, err = NodeTypeclass.create(
|
||||
self.prototype.get('key', 'An empty room'),
|
||||
xyz=xyz
|
||||
tclass = self.prototype["typeclass"]
|
||||
tclass = (
|
||||
f" ({tclass})" if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom" else ""
|
||||
)
|
||||
self.log(f" spawning room at xyz={xyz}{tclass}")
|
||||
nodeobj, err = NodeTypeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz)
|
||||
if err:
|
||||
raise RuntimeError(err)
|
||||
else:
|
||||
self.log(f" updating existing room (if changed) at xyz={xyz}")
|
||||
|
||||
if not self.prototype.get('prototype_key'):
|
||||
if not self.prototype.get("prototype_key"):
|
||||
# make sure there is a prototype_key in prototype
|
||||
self.prototype['prototype_key'] = self.generate_prototype_key()
|
||||
self.prototype["prototype_key"] = self.generate_prototype_key()
|
||||
|
||||
# apply prototype to node. This will not override the XYZ tags since
|
||||
# these are not in the prototype and exact=False
|
||||
spawner.batch_update_objects_with_prototype(
|
||||
self.prototype, objects=[nodeobj], exact=False)
|
||||
spawner.batch_update_objects_with_prototype(self.prototype, objects=[nodeobj], exact=False)
|
||||
|
||||
def spawn_links(self, directions=None):
|
||||
"""
|
||||
|
|
@ -364,9 +364,9 @@ class MapNode:
|
|||
for direction, link in self.first_links.items():
|
||||
|
||||
key, *aliases = self.get_exit_spawn_name(direction)
|
||||
if not link.prototype.get('prototype_key'):
|
||||
if not link.prototype.get("prototype_key"):
|
||||
# generate a deterministic prototype_key if it doesn't exist
|
||||
link.prototype['prototype_key'] = self.generate_prototype_key()
|
||||
link.prototype["prototype_key"] = self.generate_prototype_key()
|
||||
maplinks[key.lower()] = (key, aliases, direction, link)
|
||||
|
||||
# remove duplicates
|
||||
|
|
@ -380,8 +380,7 @@ class MapNode:
|
|||
|
||||
# we need to search for exits in all directions since some
|
||||
# may have been removed since last sync
|
||||
linkobjs = {exi.db_key.lower(): exi
|
||||
for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
|
||||
linkobjs = {exi.db_key.lower(): exi for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
|
||||
|
||||
# figure out if the topology changed between grid and map (will always
|
||||
# build all exits first run)
|
||||
|
|
@ -411,16 +410,19 @@ class MapNode:
|
|||
raise RuntimeError(err)
|
||||
linkobjs[key.lower()] = exi
|
||||
prot = maplinks[key.lower()][3].prototype
|
||||
tclass = prot['typeclass']
|
||||
tclass = (f' ({tclass})'
|
||||
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit'
|
||||
else '')
|
||||
tclass = prot["typeclass"]
|
||||
tclass = (
|
||||
f" ({tclass})"
|
||||
if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit"
|
||||
else ""
|
||||
)
|
||||
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
|
||||
|
||||
# apply prototypes to catch any changes
|
||||
for key, linkobj in linkobjs.items():
|
||||
spawner.batch_update_objects_with_prototype(
|
||||
maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False)
|
||||
maplinks[key.lower()][3].prototype, objects=[linkobj], exact=False
|
||||
)
|
||||
|
||||
def unspawn(self):
|
||||
"""
|
||||
|
|
@ -466,8 +468,9 @@ class TransitionMapNode(MapNode):
|
|||
actual rooms (`#`) on the other map (NOT to the `T`s)!
|
||||
|
||||
"""
|
||||
symbol = 'T'
|
||||
display_symbol = ' '
|
||||
|
||||
symbol = "T"
|
||||
display_symbol = " "
|
||||
# X,Y,Z coordinates of target node
|
||||
taget_map_xyz = (None, None, None)
|
||||
|
||||
|
|
@ -477,10 +480,13 @@ class TransitionMapNode(MapNode):
|
|||
the exit to this node (since the prototype is None, this node itself will not be built).
|
||||
|
||||
"""
|
||||
if any(True for coord in self.target_map_xyz if coord in (None, 'unset')):
|
||||
raise MapParserError(f"(Z={self.xymap.Z}) has not defined its "
|
||||
"`.target_map_xyz` property. It must point "
|
||||
"to another valid xymap (Z coordinate).", self)
|
||||
if any(True for coord in self.target_map_xyz if coord in (None, "unset")):
|
||||
raise MapParserError(
|
||||
f"(Z={self.xymap.Z}) has not defined its "
|
||||
"`.target_map_xyz` property. It must point "
|
||||
"to another valid xymap (Z coordinate).",
|
||||
self,
|
||||
)
|
||||
|
||||
return self.target_map_xyz
|
||||
|
||||
|
|
@ -548,6 +554,7 @@ class MapLink:
|
|||
`node.get_exit_spawn_name(direction)`
|
||||
|
||||
"""
|
||||
|
||||
# symbol for identifying this link on the map
|
||||
symbol = ""
|
||||
# if `None`, use .symbol
|
||||
|
|
@ -661,7 +668,9 @@ class MapLink:
|
|||
return None, 0, None
|
||||
raise MapParserError(
|
||||
f"was connected to from the direction {start_direction}, but "
|
||||
"is not set up to link in that direction.", self)
|
||||
"is not set up to link in that direction.",
|
||||
self,
|
||||
)
|
||||
|
||||
# note that if `get_direction` returns an unknown direction, this will be equivalent
|
||||
# to pointing to an empty location, which makes sense
|
||||
|
|
@ -674,8 +683,7 @@ class MapLink:
|
|||
next_target = self.at_empty_target(start_direction, end_direction)
|
||||
|
||||
if not next_target:
|
||||
raise MapParserError(
|
||||
f"points to empty space in the direction {end_direction}!", self)
|
||||
raise MapParserError(f"points to empty space in the direction {end_direction}!", self)
|
||||
|
||||
_weight += self.get_weight(start_direction, _weight)
|
||||
if _steps is None:
|
||||
|
|
@ -688,13 +696,16 @@ class MapLink:
|
|||
return (
|
||||
next_target,
|
||||
_weight / max(1, _linklen) if self.average_long_link_weights else _weight,
|
||||
_steps
|
||||
_steps,
|
||||
)
|
||||
else:
|
||||
# we hit another link. Progress recursively.
|
||||
return next_target.traverse(
|
||||
REVERSE_DIRECTIONS.get(end_direction, end_direction),
|
||||
_weight=_weight, _linklen=_linklen + 1, _steps=_steps)
|
||||
_weight=_weight,
|
||||
_linklen=_linklen + 1,
|
||||
_steps=_steps,
|
||||
)
|
||||
|
||||
def get_linked_neighbors(self, directions=None):
|
||||
"""
|
||||
|
|
@ -720,8 +731,7 @@ class MapLink:
|
|||
# there is is something there, we need to check if it is either
|
||||
# a map node or a link connecting in our direction
|
||||
node_or_link = xygrid[end_x][end_y]
|
||||
if (node_or_link.multilink
|
||||
or node_or_link.get_direction(direction)):
|
||||
if node_or_link.multilink or node_or_link.get_direction(direction):
|
||||
links[direction] = node_or_link
|
||||
return links
|
||||
|
||||
|
|
@ -845,7 +855,8 @@ class SmartRerouterMapLink(MapLink):
|
|||
for direction in unhandled_links_copy:
|
||||
if REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
|
||||
directions[direction] = REVERSE_DIRECTIONS[
|
||||
unhandled_links.pop(unhandled_links.index(direction))]
|
||||
unhandled_links.pop(unhandled_links.index(direction))
|
||||
]
|
||||
|
||||
# check if we have any non-cross-through paths left to handle
|
||||
n_unhandled = len(unhandled_links)
|
||||
|
|
@ -856,7 +867,8 @@ class SmartRerouterMapLink(MapLink):
|
|||
if n_unhandled != 2:
|
||||
links = ", ".join(unhandled_links)
|
||||
raise MapParserError(
|
||||
f"cannot determine how to connect in/out directions {links}.", self)
|
||||
f"cannot determine how to connect in/out directions {links}.", self
|
||||
)
|
||||
|
||||
directions[unhandled_links[0]] = unhandled_links[1]
|
||||
directions[unhandled_links[1]] = unhandled_links[0]
|
||||
|
|
@ -865,6 +877,7 @@ class SmartRerouterMapLink(MapLink):
|
|||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
|
||||
class SmartTeleporterMapLink(MapLink):
|
||||
"""
|
||||
The teleport link works by connecting to nowhere - and will then continue
|
||||
|
|
@ -889,10 +902,11 @@ class SmartTeleporterMapLink(MapLink):
|
|||
-#-t-# - invalid, only one connected link is allowed.
|
||||
|
||||
"""
|
||||
symbol = 't'
|
||||
|
||||
symbol = "t"
|
||||
# usually invisible
|
||||
display_symbol = ' '
|
||||
direction_name = 'teleport'
|
||||
display_symbol = " "
|
||||
direction_name = "teleport"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
|
@ -932,7 +946,9 @@ class SmartTeleporterMapLink(MapLink):
|
|||
if len(found_teleporters) > 1:
|
||||
raise MapParserError(
|
||||
"found too many matching teleporters (must be exactly one more): "
|
||||
f"{found_teleporters}", self)
|
||||
f"{found_teleporters}",
|
||||
self,
|
||||
)
|
||||
|
||||
other_teleporter = found_teleporters[0]
|
||||
# link the two so we don't need to scan again for the other one
|
||||
|
|
@ -952,9 +968,10 @@ class SmartTeleporterMapLink(MapLink):
|
|||
if len(neighbors) != 1:
|
||||
raise MapParserError("must have exactly one link connected to it.", self)
|
||||
direction, link = next(iter(neighbors.items()))
|
||||
if hasattr(link, 'node_index'):
|
||||
raise MapParserError("can only connect to a Link. Found {link} in "
|
||||
"direction {direction}.", self)
|
||||
if hasattr(link, "node_index"):
|
||||
raise MapParserError(
|
||||
"can only connect to a Link. Found {link} in " "direction {direction}.", self
|
||||
)
|
||||
# the string 'teleport' will not be understood by the traverser, leading to
|
||||
# this being interpreted as an empty target and the `at_empty_target`
|
||||
# hook firing when trying to traverse this link.
|
||||
|
|
@ -962,12 +979,10 @@ class SmartTeleporterMapLink(MapLink):
|
|||
if start_direction == direction_name:
|
||||
# called while traversing another teleport
|
||||
# - we must make sure we can always access/leave the teleport.
|
||||
self.directions = {direction_name: direction,
|
||||
direction: direction_name}
|
||||
self.directions = {direction_name: direction, direction: direction_name}
|
||||
else:
|
||||
# called while traversing a normal link
|
||||
self.directions = {start_direction: direction_name,
|
||||
direction_name: direction}
|
||||
self.directions = {start_direction: direction_name, direction_name: direction}
|
||||
|
||||
return self.directions.get(start_direction)
|
||||
|
||||
|
|
@ -1016,6 +1031,7 @@ class SmartMapLink(MapLink):
|
|||
# |
|
||||
|
||||
"""
|
||||
|
||||
multilink = True
|
||||
|
||||
def get_direction(self, start_direction):
|
||||
|
|
@ -1027,8 +1043,11 @@ class SmartMapLink(MapLink):
|
|||
if not self.directions:
|
||||
directions = {}
|
||||
neighbors = self.get_linked_neighbors()
|
||||
nodes = [direction for direction, neighbor in neighbors.items()
|
||||
if hasattr(neighbor, 'node_index')]
|
||||
nodes = [
|
||||
direction
|
||||
for direction, neighbor in neighbors.items()
|
||||
if hasattr(neighbor, "node_index")
|
||||
]
|
||||
|
||||
if len(nodes) == 2:
|
||||
# prefer link to these two nodes
|
||||
|
|
@ -1042,7 +1061,9 @@ class SmartMapLink(MapLink):
|
|||
"must have exactly two connections - either directly to "
|
||||
"two nodes or connecting directly to one node and with exactly one other "
|
||||
f"link direction. The neighbor(s) in directions {list(neighbors.keys())} do "
|
||||
"not fulfill these criteria.", self)
|
||||
"not fulfill these criteria.",
|
||||
self,
|
||||
)
|
||||
|
||||
self.directions = directions
|
||||
return self.directions.get(start_direction)
|
||||
|
|
@ -1071,20 +1092,26 @@ class InvisibleSmartMapLink(SmartMapLink):
|
|||
# this allows for normal movement directions even if the invisible-node
|
||||
# is marked with a different symbol.
|
||||
direction_aliases = {
|
||||
'n': 'n', 'ne': 'ne', 'e': 'e', 'se': 'se',
|
||||
's': 's', 'sw': 'sw', 'w': 'w', 'nw': 'nw'
|
||||
"n": "n",
|
||||
"ne": "ne",
|
||||
"e": "e",
|
||||
"se": "se",
|
||||
"s": "s",
|
||||
"sw": "sw",
|
||||
"w": "w",
|
||||
"nw": "nw",
|
||||
}
|
||||
|
||||
# replace current link position with what the smart links "should" look like
|
||||
display_symbol_aliases = {
|
||||
(('n', 's'), ('s', 'n')): '|',
|
||||
(('n', 's'),): 'v',
|
||||
(('s', 'n')): '^',
|
||||
(('e', 'w'), ('w', 'e')): '-',
|
||||
(('e', 'w'),): '>',
|
||||
(('w', 'e'),): '<',
|
||||
(('nw', 'se'), ('sw', 'ne')): '\\',
|
||||
(('ne', 'sw'), ('sw', 'ne')): '/',
|
||||
(("n", "s"), ("s", "n")): "|",
|
||||
(("n", "s"),): "v",
|
||||
(("s", "n")): "^",
|
||||
(("e", "w"), ("w", "e")): "-",
|
||||
(("e", "w"),): ">",
|
||||
(("w", "e"),): "<",
|
||||
(("nw", "se"), ("sw", "ne")): "\\",
|
||||
(("ne", "sw"), ("sw", "ne")): "/",
|
||||
}
|
||||
|
||||
def get_display_symbol(self):
|
||||
|
|
@ -1098,12 +1125,10 @@ class InvisibleSmartMapLink(SmartMapLink):
|
|||
"""
|
||||
if not hasattr(self, "_cached_display_symbol"):
|
||||
legend = self.xymap.legend
|
||||
default_symbol = (
|
||||
self.symbol if self.display_symbol is None else self.display_symbol)
|
||||
default_symbol = self.symbol if self.display_symbol is None else self.display_symbol
|
||||
self._cached_display_symbol = default_symbol
|
||||
|
||||
dirtuple = tuple((key, self.directions[key])
|
||||
for key in sorted(self.directions.keys()))
|
||||
dirtuple = tuple((key, self.directions[key]) for key in sorted(self.directions.keys()))
|
||||
|
||||
replacement_symbol = self.display_symbol_aliases.get(dirtuple, default_symbol)
|
||||
|
||||
|
|
@ -1112,16 +1137,19 @@ class InvisibleSmartMapLink(SmartMapLink):
|
|||
if node_or_link_class:
|
||||
# initiate class in the current location and run get_display_symbol
|
||||
# to get what it would show.
|
||||
self._cached_display_symbol = (
|
||||
node_or_link_class(self.x, self.y, self.Z).get_display_symbol())
|
||||
self._cached_display_symbol = node_or_link_class(
|
||||
self.x, self.y, self.Z
|
||||
).get_display_symbol()
|
||||
return self._cached_display_symbol
|
||||
|
||||
|
||||
# ----------------------------------
|
||||
# Default nodes and link classes
|
||||
|
||||
|
||||
class BasicMapNode(MapNode):
|
||||
"""A map node/room"""
|
||||
|
||||
symbol = "#"
|
||||
prototype = "xyz_room"
|
||||
|
||||
|
|
@ -1129,20 +1157,25 @@ class BasicMapNode(MapNode):
|
|||
class InterruptMapNode(MapNode):
|
||||
"""A point of interest node/room. Pathfinder will ignore but auto-stepper will
|
||||
stop here if passing through. Beginner-Tutorial from here is fine."""
|
||||
|
||||
symbol = "I"
|
||||
display_symbol = "#"
|
||||
interrupt_path = True
|
||||
prototype = "xyz_room"
|
||||
|
||||
|
||||
class MapTransitionNode(TransitionMapNode):
|
||||
"""Transition-target node to other map. This is not actually spawned in-game."""
|
||||
|
||||
symbol = "T"
|
||||
display_symbol = " "
|
||||
prototype = None # important to leave None!
|
||||
target_map_xyz = (None, None, None) # must be set manually
|
||||
|
||||
|
||||
class NSMapLink(MapLink):
|
||||
"""Two-way, North-South link"""
|
||||
|
||||
symbol = "|"
|
||||
display_symbol = "||"
|
||||
directions = {"n": "s", "s": "n"}
|
||||
|
|
@ -1151,6 +1184,7 @@ class NSMapLink(MapLink):
|
|||
|
||||
class EWMapLink(MapLink):
|
||||
"""Two-way, East-West link"""
|
||||
|
||||
symbol = "-"
|
||||
directions = {"e": "w", "w": "e"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1158,6 +1192,7 @@ class EWMapLink(MapLink):
|
|||
|
||||
class NESWMapLink(MapLink):
|
||||
"""Two-way, NorthWest-SouthWest link"""
|
||||
|
||||
symbol = "/"
|
||||
directions = {"ne": "sw", "sw": "ne"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1165,6 +1200,7 @@ class NESWMapLink(MapLink):
|
|||
|
||||
class SENWMapLink(MapLink):
|
||||
"""Two-way, SouthEast-NorthWest link"""
|
||||
|
||||
symbol = "\\"
|
||||
directions = {"se": "nw", "nw": "se"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1172,22 +1208,23 @@ class SENWMapLink(MapLink):
|
|||
|
||||
class PlusMapLink(MapLink):
|
||||
"""Two-way, crossing North-South and East-West links"""
|
||||
|
||||
symbol = "+"
|
||||
directions = {"s": "n", "n": "s",
|
||||
"e": "w", "w": "e"}
|
||||
directions = {"s": "n", "n": "s", "e": "w", "w": "e"}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class CrossMapLink(MapLink):
|
||||
"""Two-way, crossing NorthEast-SouthWest and SouthEast-NorthWest links"""
|
||||
|
||||
symbol = "x"
|
||||
directions = {"ne": "sw", "sw": "ne",
|
||||
"se": "nw", "nw": "se"}
|
||||
directions = {"ne": "sw", "sw": "ne", "se": "nw", "nw": "se"}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class NSOneWayMapLink(MapLink):
|
||||
"""One-way North-South link"""
|
||||
|
||||
symbol = "v"
|
||||
directions = {"n": "s"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1195,6 +1232,7 @@ class NSOneWayMapLink(MapLink):
|
|||
|
||||
class SNOneWayMapLink(MapLink):
|
||||
"""One-way South-North link"""
|
||||
|
||||
symbol = "^"
|
||||
directions = {"s": "n"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1202,6 +1240,7 @@ class SNOneWayMapLink(MapLink):
|
|||
|
||||
class EWOneWayMapLink(MapLink):
|
||||
"""One-way East-West link"""
|
||||
|
||||
symbol = "<"
|
||||
directions = {"e": "w"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1209,6 +1248,7 @@ class EWOneWayMapLink(MapLink):
|
|||
|
||||
class WEOneWayMapLink(MapLink):
|
||||
"""One-way West-East link"""
|
||||
|
||||
symbol = ">"
|
||||
directions = {"w": "e"}
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1216,21 +1256,39 @@ class WEOneWayMapLink(MapLink):
|
|||
|
||||
class UpMapLink(SmartMapLink):
|
||||
"""Up direction. Note that this stays on the same z-coord so it's a 'fake' up."""
|
||||
symbol = 'u'
|
||||
|
||||
symbol = "u"
|
||||
|
||||
# all movement over this link is 'up', regardless of where on the xygrid we move.
|
||||
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||
direction_aliases = {
|
||||
"n": symbol,
|
||||
"ne": symbol,
|
||||
"e": symbol,
|
||||
"se": symbol,
|
||||
"s": symbol,
|
||||
"sw": symbol,
|
||||
"w": symbol,
|
||||
"nw": symbol,
|
||||
}
|
||||
spawn_aliases = {direction: ("up", "u") for direction in direction_aliases}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class DownMapLink(UpMapLink):
|
||||
"""Down direction. Note that this stays on the same z-coord, so it's a 'fake' down."""
|
||||
symbol = 'd'
|
||||
|
||||
symbol = "d"
|
||||
# all movement over this link is 'down', regardless of where on the xygrid we move.
|
||||
direction_aliases = {'n': symbol, 'ne': symbol, 'e': symbol, 'se': symbol,
|
||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
||||
direction_aliases = {
|
||||
"n": symbol,
|
||||
"ne": symbol,
|
||||
"e": symbol,
|
||||
"se": symbol,
|
||||
"s": symbol,
|
||||
"sw": symbol,
|
||||
"w": symbol,
|
||||
"nw": symbol,
|
||||
}
|
||||
spawn_aliases = {direction: ("down", "d") for direction in direction_aliases}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
|
@ -1238,6 +1296,7 @@ class DownMapLink(UpMapLink):
|
|||
class InterruptMapLink(InvisibleSmartMapLink):
|
||||
"""A (still passable) link. Pathfinder will treat this as any link, but auto-stepper
|
||||
will always abort before crossing this link - so this must be crossed manually."""
|
||||
|
||||
symbol = "i"
|
||||
interrupt_path = True
|
||||
prototype = "xyz_exit"
|
||||
|
|
@ -1250,14 +1309,24 @@ class BlockedMapLink(InvisibleSmartMapLink):
|
|||
link in any paths.
|
||||
|
||||
"""
|
||||
symbol = 'b'
|
||||
weights = {'n': BIGVAL, 'ne': BIGVAL, 'e': BIGVAL, 'se': BIGVAL,
|
||||
's': BIGVAL, 'sw': BIGVAL, 'w': BIGVAL, 'nw': BIGVAL}
|
||||
|
||||
symbol = "b"
|
||||
weights = {
|
||||
"n": BIGVAL,
|
||||
"ne": BIGVAL,
|
||||
"e": BIGVAL,
|
||||
"se": BIGVAL,
|
||||
"s": BIGVAL,
|
||||
"sw": BIGVAL,
|
||||
"w": BIGVAL,
|
||||
"nw": BIGVAL,
|
||||
}
|
||||
prototype = "xyz_exit"
|
||||
|
||||
|
||||
class RouterMapLink(SmartRerouterMapLink):
|
||||
"""A link that connects other links to build 'knees', pass-throughs etc."""
|
||||
|
||||
symbol = "o"
|
||||
|
||||
|
||||
|
|
@ -1266,7 +1335,8 @@ class TeleporterMapLink(SmartTeleporterMapLink):
|
|||
Teleporter links. Must appear in pairs on the same xy map. To make it one-way, add additional
|
||||
one-way link out of the teleporter on one side.
|
||||
"""
|
||||
symbol = 't'
|
||||
|
||||
symbol = "t"
|
||||
|
||||
|
||||
# all map components; used as base if not overridden
|
||||
|
|
@ -1291,5 +1361,5 @@ LEGEND = {
|
|||
"d": DownMapLink,
|
||||
"b": BlockedMapLink,
|
||||
"i": InterruptMapLink,
|
||||
't': TeleporterMapLink,
|
||||
"t": TeleporterMapLink,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class XYZGrid(DefaultScript):
|
|||
Main grid class. This organizes the Maps based on their name/Z-coordinate.
|
||||
|
||||
"""
|
||||
|
||||
def at_script_creation(self):
|
||||
"""
|
||||
What we store persistently is data used to create each map (the legends, names etc)
|
||||
|
|
@ -88,7 +89,7 @@ class XYZGrid(DefaultScript):
|
|||
"""
|
||||
return XYZRoom.objects.filter_xyz(xyz=xyz, **kwargs)
|
||||
|
||||
def get_exit(self, xyz, name='north', **kwargs):
|
||||
def get_exit(self, xyz, name="north", **kwargs):
|
||||
"""
|
||||
Get one or more exit object at coordinate.
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ class XYZGrid(DefaultScript):
|
|||
Queryset: A queryset of XYZExit(s) found.
|
||||
|
||||
"""
|
||||
kwargs['db_key'] = name
|
||||
kwargs["db_key"] = name
|
||||
return XYZExit.objects.filter_xyz_exit(xyz=xyz, **kwargs)
|
||||
|
||||
def maps_from_module(self, module_path):
|
||||
|
|
@ -127,7 +128,7 @@ class XYZGrid(DefaultScript):
|
|||
if not mapdata:
|
||||
self.log(f"Could not find or load map from {module_path}.")
|
||||
return
|
||||
mapdata['module_path'] = module_path
|
||||
mapdata["module_path"] = module_path
|
||||
return map_data_list
|
||||
|
||||
def reload(self):
|
||||
|
|
@ -154,9 +155,9 @@ class XYZGrid(DefaultScript):
|
|||
# we reload the map from module
|
||||
new_mapdata = loaded_mapdata.get(zcoord)
|
||||
if not new_mapdata:
|
||||
if 'module_path' in old_mapdata:
|
||||
for mapdata in self.maps_from_module(old_mapdata['module_path']):
|
||||
loaded_mapdata[mapdata['zcoord']] = mapdata
|
||||
if "module_path" in old_mapdata:
|
||||
for mapdata in self.maps_from_module(old_mapdata["module_path"]):
|
||||
loaded_mapdata[mapdata["zcoord"]] = mapdata
|
||||
else:
|
||||
# nowhere to reload from - use what we have
|
||||
loaded_mapdata[zcoord] = old_mapdata
|
||||
|
|
@ -198,7 +199,7 @@ class XYZGrid(DefaultScript):
|
|||
|
||||
"""
|
||||
for mapdata in mapdatas:
|
||||
zcoord = mapdata.get('zcoord')
|
||||
zcoord = mapdata.get("zcoord")
|
||||
if not zcoord:
|
||||
raise RuntimeError("XYZGrid.add_map data must contain 'zcoord'.")
|
||||
|
||||
|
|
@ -220,7 +221,7 @@ class XYZGrid(DefaultScript):
|
|||
if remove_objects:
|
||||
# we can't batch-delete because we want to run the .delete
|
||||
# method that also wipes exits and moves content to save locations
|
||||
for xyzroom in XYZRoom.objects.filter_xyz(xyz=('*', '*', zcoord)):
|
||||
for xyzroom in XYZRoom.objects.filter_xyz(xyz=("*", "*", zcoord)):
|
||||
xyzroom.delete()
|
||||
self.reload()
|
||||
|
||||
|
|
@ -234,7 +235,7 @@ class XYZGrid(DefaultScript):
|
|||
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
||||
super().delete()
|
||||
|
||||
def spawn(self, xyz=('*', '*', '*'), directions=None):
|
||||
def spawn(self, xyz=("*", "*", "*"), directions=None):
|
||||
"""
|
||||
Create/recreate/update the in-game grid based on the stored Maps or for a specific Map
|
||||
or coordinate.
|
||||
|
|
@ -255,7 +256,7 @@ class XYZGrid(DefaultScript):
|
|||
|
||||
"""
|
||||
x, y, z = xyz
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
if z == wildcard:
|
||||
xymaps = self.grid
|
||||
|
|
@ -293,8 +294,10 @@ def get_xyzgrid(print_errors=True):
|
|||
xyzgrid.reload()
|
||||
return xyzgrid
|
||||
elif len(xyzgrid) > 1:
|
||||
("Warning: More than one XYZGrid instances were found. This is an error and "
|
||||
"only the first one will be used. Delete the other one(s) manually.")
|
||||
(
|
||||
"Warning: More than one XYZGrid instances were found. This is an error and "
|
||||
"only the first one will be used. Delete the other one(s) manually."
|
||||
)
|
||||
xyzgrid = xyzgrid[0]
|
||||
try:
|
||||
if not xyzgrid.ndb.loaded:
|
||||
|
|
|
|||
|
|
@ -31,7 +31,8 @@ class XYZManager(ObjectManager):
|
|||
efficiently querying the room in the database based on XY coordinates.
|
||||
|
||||
"""
|
||||
def filter_xyz(self, xyz=('*', '*', '*'), **kwargs):
|
||||
|
||||
def filter_xyz(self, xyz=("*", "*", "*"), **kwargs):
|
||||
"""
|
||||
Filter queryset based on XYZ position on the grid. The Z-position is the name of the XYMap
|
||||
Set a coordinate to `'*'` to act as a wildcard (setting all coords to `*` will thus find
|
||||
|
|
@ -49,23 +50,28 @@ class XYZManager(ObjectManager):
|
|||
|
||||
"""
|
||||
x, y, z = xyz
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
return (
|
||||
self
|
||||
.filter_family(**kwargs)
|
||||
self.filter_family(**kwargs)
|
||||
.filter(
|
||||
Q() if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
|
||||
Q()
|
||||
if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
|
||||
Q()
|
||||
if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
|
||||
Q()
|
||||
if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
)
|
||||
)
|
||||
|
||||
def get_xyz(self, xyz=(0, 0, 'map'), **kwargs):
|
||||
def get_xyz(self, xyz=(0, 0, "map"), **kwargs):
|
||||
"""
|
||||
Always return a single matched entity directly. This accepts no `*`-wildcards.
|
||||
This will also find children of XYZRooms on the given coordinates.
|
||||
|
|
@ -93,8 +99,9 @@ class XYZManager(ObjectManager):
|
|||
|
||||
# error - mimic default get() behavior but with a little more info
|
||||
x, y, z = xyz
|
||||
inp = (f"Query: xyz=({x},{y},{z}), " +
|
||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||
inp = f"Query: xyz=({x},{y},{z}), " + ",".join(
|
||||
f"{key}={val}" for key, val in kwargs.items()
|
||||
)
|
||||
if ncount > 1:
|
||||
raise self.model.MultipleObjectsReturned(inp)
|
||||
else:
|
||||
|
|
@ -108,8 +115,7 @@ class XYZExitManager(XYZManager):
|
|||
|
||||
"""
|
||||
|
||||
def filter_xyz_exit(self, xyz=('*', '*', '*'),
|
||||
xyz_destination=('*', '*', '*'), **kwargs):
|
||||
def filter_xyz_exit(self, xyz=("*", "*", "*"), xyz_destination=("*", "*", "*"), **kwargs):
|
||||
"""
|
||||
Used by exits (objects with a source and -destination property).
|
||||
Find all exits out of a source or to a particular destination. This will also find
|
||||
|
|
@ -138,32 +144,43 @@ class XYZExitManager(XYZManager):
|
|||
"""
|
||||
x, y, z = xyz
|
||||
xdest, ydest, zdest = xyz_destination
|
||||
wildcard = '*'
|
||||
wildcard = "*"
|
||||
|
||||
return (
|
||||
self
|
||||
.filter_family(**kwargs)
|
||||
self.filter_family(**kwargs)
|
||||
.filter(
|
||||
Q() if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
|
||||
Q()
|
||||
if x == wildcard
|
||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
|
||||
Q()
|
||||
if y == wildcard
|
||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
|
||||
Q()
|
||||
if z == wildcard
|
||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if xdest == wildcard
|
||||
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY))
|
||||
Q()
|
||||
if xdest == wildcard
|
||||
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if ydest == wildcard
|
||||
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY))
|
||||
Q()
|
||||
if ydest == wildcard
|
||||
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY)
|
||||
)
|
||||
.filter(
|
||||
Q() if zdest == wildcard
|
||||
else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY))
|
||||
Q()
|
||||
if zdest == wildcard
|
||||
else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY)
|
||||
)
|
||||
)
|
||||
|
||||
def get_xyz_exit(self, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'), **kwargs):
|
||||
def get_xyz_exit(self, xyz=(0, 0, "map"), xyz_destination=(0, 0, "map"), **kwargs):
|
||||
"""
|
||||
Used by exits (objects with a source and -destination property). Get a single
|
||||
exit. All source/destination coordinates (as well as the map's name) are required.
|
||||
|
|
@ -199,8 +216,7 @@ class XYZExitManager(XYZManager):
|
|||
|
||||
try:
|
||||
return (
|
||||
self
|
||||
.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
self.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||
.filter(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)
|
||||
|
|
@ -209,10 +225,12 @@ class XYZExitManager(XYZManager):
|
|||
.get(**kwargs)
|
||||
)
|
||||
except self.model.DoesNotExist:
|
||||
inp = (f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," +
|
||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
||||
raise self.model.DoesNotExist(f"{self.model.__name__} "
|
||||
f"matching query {inp} does not exist.")
|
||||
inp = f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," + ",".join(
|
||||
f"{key}={val}" for key, val in kwargs.items()
|
||||
)
|
||||
raise self.model.DoesNotExist(
|
||||
f"{self.model.__name__} " f"matching query {inp} does not exist."
|
||||
)
|
||||
|
||||
|
||||
class XYZRoom(DefaultRoom):
|
||||
|
|
@ -244,10 +262,10 @@ class XYZRoom(DefaultRoom):
|
|||
|
||||
# default settings for map visualization
|
||||
map_display = True
|
||||
map_mode = 'nodes' # or 'scan'
|
||||
map_mode = "nodes" # or 'scan'
|
||||
map_visual_range = 2
|
||||
map_character_symbol = "|g@|n"
|
||||
map_align = 'c'
|
||||
map_align = "c"
|
||||
map_target_path_style = "|y{display_symbol}|n"
|
||||
map_fill_all = True
|
||||
map_separator_char = "|x~|n"
|
||||
|
|
@ -267,8 +285,10 @@ class XYZRoom(DefaultRoom):
|
|||
z = self.tags.get(category=MAP_Z_TAG_CATEGORY, return_list=False)
|
||||
if x is None or y is None or z is None:
|
||||
# don't cache unfinished coordinate (probably tags have not finished saving)
|
||||
return tuple(int(coord) if coord is not None and coord.isdigit() else coord
|
||||
for coord in (x, y, z))
|
||||
return tuple(
|
||||
int(coord) if coord is not None and coord.isdigit() else coord
|
||||
for coord in (x, y, z)
|
||||
)
|
||||
# cache result, convert to correct types (tags are strings)
|
||||
self._xyz = tuple(int(coord) if coord.isdigit() else coord for coord in (x, y, z))
|
||||
|
||||
|
|
@ -290,7 +310,7 @@ class XYZRoom(DefaultRoom):
|
|||
return self._xymap
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, account=None, xyz=(0, 0, 'map'), **kwargs):
|
||||
def create(cls, key, account=None, xyz=(0, 0, "map"), **kwargs):
|
||||
"""
|
||||
Creation method aware of XYZ coordinates.
|
||||
|
||||
|
|
@ -316,14 +336,18 @@ class XYZRoom(DefaultRoom):
|
|||
try:
|
||||
x, y, z = xyz
|
||||
except ValueError:
|
||||
return None, [f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) "
|
||||
"coordinate of ints/strings."]
|
||||
return None, [
|
||||
f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) "
|
||||
"coordinate of ints/strings."
|
||||
]
|
||||
|
||||
existing_query = cls.objects.filter_xyz(xyz=(x, y, z))
|
||||
if existing_query.exists():
|
||||
existing_room = existing_query.first()
|
||||
return None, [f"XYRoom XYZ=({x},{y},{z}) already exists "
|
||||
f"(existing room is named '{existing_room.db_key}')!"]
|
||||
return None, [
|
||||
f"XYRoom XYZ=({x},{y},{z}) already exists "
|
||||
f"(existing room is named '{existing_room.db_key}')!"
|
||||
]
|
||||
|
||||
tags = (
|
||||
(str(x), MAP_X_TAG_CATEGORY),
|
||||
|
|
@ -410,26 +434,29 @@ class XYZRoom(DefaultRoom):
|
|||
xyz = self.xyz
|
||||
xymap = self.xyzgrid.get_map(xyz[2])
|
||||
|
||||
if xymap and kwargs.get('map_display', xymap.options.get("map_display", self.map_display)):
|
||||
if xymap and kwargs.get("map_display", xymap.options.get("map_display", self.map_display)):
|
||||
|
||||
# show the near-area map.
|
||||
map_character_symbol = kwargs.get(
|
||||
'map_character_symbol',
|
||||
xymap.options.get("map_character_symbol", self.map_character_symbol))
|
||||
"map_character_symbol",
|
||||
xymap.options.get("map_character_symbol", self.map_character_symbol),
|
||||
)
|
||||
map_visual_range = kwargs.get(
|
||||
"map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range))
|
||||
map_mode = kwargs.get(
|
||||
"map_mode", xymap.options.get("map_mode", self.map_mode))
|
||||
map_align = kwargs.get(
|
||||
"map_align", xymap.options.get("map_align", self.map_align))
|
||||
"map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range)
|
||||
)
|
||||
map_mode = kwargs.get("map_mode", xymap.options.get("map_mode", self.map_mode))
|
||||
map_align = kwargs.get("map_align", xymap.options.get("map_align", self.map_align))
|
||||
map_target_path_style = kwargs.get(
|
||||
"map_target_path_style",
|
||||
xymap.options.get("map_target_path_style", self.map_target_path_style))
|
||||
xymap.options.get("map_target_path_style", self.map_target_path_style),
|
||||
)
|
||||
map_area_client = kwargs.get(
|
||||
"map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all))
|
||||
"map_fill_all", xymap.options.get("map_fill_all", self.map_fill_all)
|
||||
)
|
||||
map_separator_char = kwargs.get(
|
||||
"map_separator_char",
|
||||
xymap.options.get("map_separator_char", self.map_separator_char))
|
||||
xymap.options.get("map_separator_char", self.map_separator_char),
|
||||
)
|
||||
|
||||
client_width, _ = looker.sessions.get()[0].get_client_size()
|
||||
|
||||
|
|
@ -438,15 +465,14 @@ class XYZRoom(DefaultRoom):
|
|||
if map_area_client:
|
||||
display_width = client_width
|
||||
else:
|
||||
display_width = max(map_width,
|
||||
max(len(line) for line in room_desc.split("\n")))
|
||||
display_width = max(map_width, max(len(line) for line in room_desc.split("\n")))
|
||||
|
||||
# align map
|
||||
map_indent = 0
|
||||
sep_width = display_width
|
||||
if map_align == 'r':
|
||||
if map_align == "r":
|
||||
map_indent = max(0, display_width - map_width)
|
||||
elif map_align == 'c':
|
||||
elif map_align == "c":
|
||||
map_indent = max(0, (display_width - map_width) // 2)
|
||||
|
||||
# data set by the goto/path-command, for displaying the shortest path
|
||||
|
|
@ -462,7 +488,7 @@ class XYZRoom(DefaultRoom):
|
|||
target_path_style=map_target_path_style,
|
||||
character=map_character_symbol,
|
||||
max_size=(display_width, None),
|
||||
indent=map_indent
|
||||
indent=map_indent,
|
||||
)
|
||||
sep = map_separator_char * sep_width
|
||||
map_display = f"{sep}|n\n{map_display}\n{sep}"
|
||||
|
|
@ -523,8 +549,16 @@ class XYZExit(DefaultExit):
|
|||
return self._xyz_destination
|
||||
|
||||
@classmethod
|
||||
def create(cls, key, account=None, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'),
|
||||
location=None, destination=None, **kwargs):
|
||||
def create(
|
||||
cls,
|
||||
key,
|
||||
account=None,
|
||||
xyz=(0, 0, "map"),
|
||||
xyz_destination=(0, 0, "map"),
|
||||
location=None,
|
||||
destination=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Creation method aware of coordinates.
|
||||
|
||||
|
|
@ -559,23 +593,33 @@ class XYZExit(DefaultExit):
|
|||
return None, ["XYExit.create need either `xyz=(X,Y,Z)` coordinate or a `location`."]
|
||||
else:
|
||||
source = XYZRoom.objects.get_xyz(xyz=(x, y, z))
|
||||
tags.extend(((str(x), MAP_X_TAG_CATEGORY),
|
||||
(str(y), MAP_Y_TAG_CATEGORY),
|
||||
(str(z), MAP_Z_TAG_CATEGORY)))
|
||||
tags.extend(
|
||||
(
|
||||
(str(x), MAP_X_TAG_CATEGORY),
|
||||
(str(y), MAP_Y_TAG_CATEGORY),
|
||||
(str(z), MAP_Z_TAG_CATEGORY),
|
||||
)
|
||||
)
|
||||
if destination:
|
||||
dest = destination
|
||||
else:
|
||||
try:
|
||||
xdest, ydest, zdest = xyz_destination
|
||||
except ValueError:
|
||||
return None, ["XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
|
||||
"or a `destination`."]
|
||||
return None, [
|
||||
"XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
|
||||
"or a `destination`."
|
||||
]
|
||||
else:
|
||||
dest = XYZRoom.objects.get_xyz(xyz=(xdest, ydest, zdest))
|
||||
tags.extend(((str(xdest), MAP_XDEST_TAG_CATEGORY),
|
||||
(str(ydest), MAP_YDEST_TAG_CATEGORY),
|
||||
(str(zdest), MAP_ZDEST_TAG_CATEGORY)))
|
||||
tags.extend(
|
||||
(
|
||||
(str(xdest), MAP_XDEST_TAG_CATEGORY),
|
||||
(str(ydest), MAP_YDEST_TAG_CATEGORY),
|
||||
(str(zdest), MAP_ZDEST_TAG_CATEGORY),
|
||||
)
|
||||
)
|
||||
|
||||
return DefaultExit.create(
|
||||
key, source, dest,
|
||||
account=account, tags=tags, typeclass=cls, **kwargs)
|
||||
key, source, dest, account=account, tags=tags, typeclass=cls, **kwargs
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@ Rolling dice - Griatch, 2012
|
|||
from .dice import roll # noqa
|
||||
from .dice import roll_dice # noqa
|
||||
from .dice import CmdDice # noqa
|
||||
from .dice import DiceCmdSet # noqa
|
||||
from .dice import DiceCmdSet # noqa
|
||||
|
|
|
|||
|
|
@ -58,8 +58,7 @@ from random import randint
|
|||
from evennia import default_cmds, CmdSet
|
||||
|
||||
|
||||
def roll(dicenum, dicetype, modifier=None,
|
||||
conditional=None, return_tuple=False):
|
||||
def roll(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False):
|
||||
"""
|
||||
This is a standard dice roller.
|
||||
|
||||
|
|
@ -141,6 +140,7 @@ def roll(dicenum, dicetype, modifier=None,
|
|||
else:
|
||||
return result
|
||||
|
||||
|
||||
# legacy alias
|
||||
roll_dice = roll
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa
|
|||
from .rpsystem import ordered_permutation_regex, regex_tuple_from_key_alias # noqa
|
||||
from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa
|
||||
from .rpsystem import SdescHandler, RecogHandler # noqa
|
||||
from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa
|
||||
from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa
|
||||
from .rpsystem import RPSystemCmdSet # noqa
|
||||
from .rpsystem import ContribRPObject # noqa
|
||||
from .rpsystem import ContribRPRoom # noqa
|
||||
|
|
|
|||
|
|
@ -315,7 +315,8 @@ class LanguageHandler(DefaultScript):
|
|||
raise IndexError(
|
||||
"Could not find a matching phoneme for the grammar "
|
||||
f"'{match.group()}'. Make there is at least one phoneme matching this "
|
||||
"combination of consonants and vowels.")
|
||||
"combination of consonants and vowels."
|
||||
)
|
||||
translation[word.lower()] = new_word.lower()
|
||||
|
||||
if manual_translations:
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
|
|||
errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group()))
|
||||
elif nmatches == 1:
|
||||
# a unique match - parse into intermediary representation
|
||||
case = '~' # retain original case of sdesc
|
||||
case = "~" # retain original case of sdesc
|
||||
if case_sensitive:
|
||||
# case sensitive mode
|
||||
# internal flags for the case used for the original /query
|
||||
|
|
@ -526,14 +526,14 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_
|
|||
# self-refs are kept as-is, others are parsed by case
|
||||
matchtext = marker_match.group().lstrip(_PREFIX)
|
||||
if matchtext.istitle():
|
||||
case = 't'
|
||||
case = "t"
|
||||
elif matchtext.isupper():
|
||||
case = '^'
|
||||
case = "^"
|
||||
elif matchtext.islower():
|
||||
case = 'v'
|
||||
case = "v"
|
||||
|
||||
key = "#%i%s" % (obj.id, case)
|
||||
string = string[:istart0] + "{%s}" % key + string[istart + maxscore:]
|
||||
string = string[:istart0] + "{%s}" % key + string[istart + maxscore :]
|
||||
mapping[key] = obj
|
||||
|
||||
else:
|
||||
|
|
@ -601,8 +601,9 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
|||
"""
|
||||
case_sensitive = kwargs.pop("case_sensitive", True)
|
||||
try:
|
||||
emote, obj_mapping = parse_sdescs_and_recogs(sender, receivers, emote,
|
||||
case_sensitive=case_sensitive)
|
||||
emote, obj_mapping = parse_sdescs_and_recogs(
|
||||
sender, receivers, emote, case_sensitive=case_sensitive
|
||||
)
|
||||
emote, language_mapping = parse_language(sender, emote)
|
||||
except (EmoteError, LanguageError) as err:
|
||||
# handle all error messages, don't hide actual coding errors
|
||||
|
|
@ -615,8 +616,8 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
|||
# (the text could have nested object mappings).
|
||||
emote = _RE_REF.sub(r"{{#\1}}", emote)
|
||||
# if anonymous_add is passed as a kwarg, collect and remove it from kwargs
|
||||
if 'anonymous_add' in kwargs:
|
||||
anonymous_add = kwargs.pop('anonymous_add')
|
||||
if "anonymous_add" in kwargs:
|
||||
anonymous_add = kwargs.pop("anonymous_add")
|
||||
if anonymous_add and not any(1 for tag in obj_mapping if tag.startswith(skey)):
|
||||
# no self-reference in the emote - add to the end
|
||||
obj_mapping[skey] = sender
|
||||
|
|
@ -670,12 +671,13 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
|||
)
|
||||
# make sure receiver always sees their real name
|
||||
rkey_start = "#%i" % receiver.id
|
||||
rkey_keep_case = rkey_start + '~' # signifies keeping the case
|
||||
rkey_keep_case = rkey_start + "~" # signifies keeping the case
|
||||
for rkey in (key for key in receiver_sdesc_mapping if key.startswith(rkey_start)):
|
||||
# we could have #%i^, #%it etc depending on input case - we want the
|
||||
# self-reference to retain case.
|
||||
receiver_sdesc_mapping[rkey] = process_sdesc(
|
||||
receiver.key, receiver, ref=rkey_keep_case, **kwargs)
|
||||
receiver.key, receiver, ref=rkey_keep_case, **kwargs
|
||||
)
|
||||
|
||||
# do the template replacement of the sdesc/recog {#num} markers
|
||||
receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs)
|
||||
|
|
@ -1709,14 +1711,14 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
|||
if not sdesc:
|
||||
return ""
|
||||
|
||||
ref = kwargs.get('ref', '~') # ~ to keep sdesc unchanged
|
||||
if 't' in ref:
|
||||
ref = kwargs.get("ref", "~") # ~ to keep sdesc unchanged
|
||||
if "t" in ref:
|
||||
# we only want to capitalize the first letter if there are many words
|
||||
sdesc = sdesc.lower()
|
||||
sdesc = sdesc[0].upper() + sdesc[1:] if len(sdesc) > 1 else sdesc.upper()
|
||||
elif '^' in ref:
|
||||
elif "^" in ref:
|
||||
sdesc = sdesc.upper()
|
||||
elif 'v' in ref:
|
||||
elif "v" in ref:
|
||||
sdesc = sdesc.lower()
|
||||
return "|b%s|n" % sdesc
|
||||
|
||||
|
|
|
|||
|
|
@ -274,7 +274,7 @@ class TestRPSystem(BaseEvenniaTest):
|
|||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
||||
t2 = time.time()
|
||||
# print(f"t1: {t1 - t0}, t2: {t2 - t1}")
|
||||
self.assertLess(t2 - t1, 10 ** -4)
|
||||
self.assertLess(t2 - t1, 10**-4)
|
||||
self.assertEqual(result, (Anything, self.speaker, self.speaker.key))
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -59,16 +59,28 @@ class TraitHandlerTest(_TraitHandlerBase):
|
|||
super().setUp()
|
||||
self.traithandler.add("test1", name="Test1", trait_type="trait")
|
||||
self.traithandler.add(
|
||||
"test2", name="Test2", trait_type="trait", value=["foo", {"1": [1, 2, 3]}, 4],
|
||||
"test2",
|
||||
name="Test2",
|
||||
trait_type="trait",
|
||||
value=["foo", {"1": [1, 2, 3]}, 4],
|
||||
)
|
||||
|
||||
def test_add_trait(self):
|
||||
self.assertEqual(
|
||||
self._get_dbstore("test1"), {"name": "Test1", "trait_type": "trait", "value": None,}
|
||||
self._get_dbstore("test1"),
|
||||
{
|
||||
"name": "Test1",
|
||||
"trait_type": "trait",
|
||||
"value": None,
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
self._get_dbstore("test2"),
|
||||
{"name": "Test2", "trait_type": "trait", "value": ["foo", {"1": [1, 2, 3]}, 4],},
|
||||
{
|
||||
"name": "Test2",
|
||||
"trait_type": "trait",
|
||||
"value": ["foo", {"1": [1, 2, 3]}, 4],
|
||||
},
|
||||
)
|
||||
self.assertEqual(len(self.traithandler), 2)
|
||||
|
||||
|
|
@ -328,7 +340,12 @@ class TestTraitCounter(_TraitHandlerBase):
|
|||
max=10,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
)
|
||||
self.trait = self.traithandler.get("test1")
|
||||
|
||||
|
|
@ -348,7 +365,12 @@ class TestTraitCounter(_TraitHandlerBase):
|
|||
"max": 10,
|
||||
"extra_val1": "xvalue1",
|
||||
"extra_val2": "xvalue2",
|
||||
"descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
"descs": {
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
"rate": 0,
|
||||
"ratetarget": None,
|
||||
"last_update": None,
|
||||
|
|
@ -507,7 +529,12 @@ class TestTraitCounterTimed(_TraitHandlerBase):
|
|||
max=100,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
rate=1,
|
||||
ratetarget=None,
|
||||
)
|
||||
|
|
@ -579,7 +606,12 @@ class TestTraitGauge(_TraitHandlerBase):
|
|||
mod=2,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
)
|
||||
self.trait = self.traithandler.get("test1")
|
||||
|
||||
|
|
@ -598,7 +630,12 @@ class TestTraitGauge(_TraitHandlerBase):
|
|||
"min": 0,
|
||||
"extra_val1": "xvalue1",
|
||||
"extra_val2": "xvalue2",
|
||||
"descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
"descs": {
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
"rate": 0,
|
||||
"ratetarget": None,
|
||||
"last_update": None,
|
||||
|
|
@ -763,7 +800,12 @@ class TestTraitGaugeTimed(_TraitHandlerBase):
|
|||
min=0,
|
||||
extra_val1="xvalue1",
|
||||
extra_val2="xvalue2",
|
||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
||||
descs={
|
||||
0: "range0",
|
||||
2: "range1",
|
||||
5: "range2",
|
||||
7: "range3",
|
||||
},
|
||||
rate=1,
|
||||
ratetarget=None,
|
||||
)
|
||||
|
|
@ -831,8 +873,20 @@ class TestNumericTraitOperators(BaseEvenniaTestCase):
|
|||
|
||||
def setUp(self):
|
||||
# direct instantiation for testing only; use TraitHandler in production
|
||||
self.st = traits.Trait({"name": "Strength", "trait_type": "trait", "value": 8,})
|
||||
self.at = traits.Trait({"name": "Attack", "trait_type": "trait", "value": 4,})
|
||||
self.st = traits.Trait(
|
||||
{
|
||||
"name": "Strength",
|
||||
"trait_type": "trait",
|
||||
"value": 8,
|
||||
}
|
||||
)
|
||||
self.at = traits.Trait(
|
||||
{
|
||||
"name": "Attack",
|
||||
"trait_type": "trait",
|
||||
"value": 4,
|
||||
}
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.st, self.at = None, None
|
||||
|
|
|
|||
|
|
@ -526,6 +526,7 @@ class MandatoryTraitKey:
|
|||
|
||||
"""
|
||||
|
||||
|
||||
class TraitHandler:
|
||||
"""
|
||||
Factory class that instantiates Trait objects. Must be assigned as a property
|
||||
|
|
@ -794,10 +795,7 @@ class TraitProperty:
|
|||
trait = traithandler.get(self._trait_key)
|
||||
if trait is None:
|
||||
# initialize the trait
|
||||
traithandler.add(
|
||||
self._trait_key,
|
||||
**self._trait_properties
|
||||
)
|
||||
traithandler.add(self._trait_key, **self._trait_properties)
|
||||
trait = traithandler.get(self._trait_key) # caches it in the traithandler
|
||||
self._cache[instance] = trait
|
||||
return self._cache[instance]
|
||||
|
|
@ -809,6 +807,7 @@ class TraitProperty:
|
|||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Parent Trait class
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ from evennia.utils.utils import delay, repeat, interactive
|
|||
|
||||
# Commands for the state when the lid covering the button is closed.
|
||||
|
||||
|
||||
class CmdPushLidClosed(Command):
|
||||
"""
|
||||
Push the red button (lid closed)
|
||||
|
|
@ -121,23 +122,27 @@ class CmdSmashGlass(Command):
|
|||
"""
|
||||
rand = random.random()
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} tries to smash the glass of the button.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} tries to smash the glass of the button.", exclude=self.caller
|
||||
)
|
||||
|
||||
if rand < 0.2:
|
||||
string = ("You smash your hand against the glass"
|
||||
" with all your might. The lid won't budge"
|
||||
" but you cause quite the tremor through the button's mount."
|
||||
"\nIt looks like the button's lamp stopped working for the time being, "
|
||||
"but the lid is still as closed as ever.")
|
||||
string = (
|
||||
"You smash your hand against the glass"
|
||||
" with all your might. The lid won't budge"
|
||||
" but you cause quite the tremor through the button's mount."
|
||||
"\nIt looks like the button's lamp stopped working for the time being, "
|
||||
"but the lid is still as closed as ever."
|
||||
)
|
||||
# self.obj is the button itself
|
||||
self.obj.break_lamp()
|
||||
elif rand < 0.6:
|
||||
string = "You hit the lid hard. It doesn't move an inch."
|
||||
else:
|
||||
string = ("You place a well-aimed fist against the glass of the lid."
|
||||
" Unfortunately all you get is a pain in your hand. Maybe"
|
||||
" you should just try to just ... open the lid instead?")
|
||||
string = (
|
||||
"You place a well-aimed fist against the glass of the lid."
|
||||
" Unfortunately all you get is a pain in your hand. Maybe"
|
||||
" you should just try to just ... open the lid instead?"
|
||||
)
|
||||
self.caller.msg(string)
|
||||
|
||||
|
||||
|
|
@ -165,8 +170,8 @@ class CmdOpenLid(Command):
|
|||
string += "the lid will soon close again."
|
||||
self.caller.msg(string)
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} opens the lid of the button.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} opens the lid of the button.", exclude=self.caller
|
||||
)
|
||||
self.obj.to_open_state()
|
||||
|
||||
|
||||
|
|
@ -200,6 +205,7 @@ class LidClosedCmdSet(CmdSet):
|
|||
# Commands for the state when the button's protective cover is open - now the
|
||||
# push command will work. You can also close the lid again.
|
||||
|
||||
|
||||
class CmdPushLidOpen(Command):
|
||||
"""
|
||||
Push the red button
|
||||
|
|
@ -225,15 +231,15 @@ class CmdPushLidOpen(Command):
|
|||
"""
|
||||
# pause a little between each message.
|
||||
self.caller.msg("You reach out to press the big red button ...")
|
||||
yield(2) # pause 2s before next message
|
||||
yield (2) # pause 2s before next message
|
||||
self.caller.msg("\n\n|wBOOOOM! A bright light blinds you!|n")
|
||||
yield(1) # pause 1s before next message
|
||||
yield (1) # pause 1s before next message
|
||||
self.caller.msg("\n\n|xThe world goes dark ...|n")
|
||||
|
||||
name = self.caller.name
|
||||
self.caller.location.msg_contents(
|
||||
f"{name} presses the button. BOOM! {name} is blinded by a flash!",
|
||||
exclude=self.caller)
|
||||
f"{name} presses the button. BOOM! {name} is blinded by a flash!", exclude=self.caller
|
||||
)
|
||||
self.obj.blind_target(self.caller)
|
||||
|
||||
|
||||
|
|
@ -259,8 +265,8 @@ class CmdCloseLid(Command):
|
|||
# this will clean out scripts dependent on lid being open.
|
||||
self.caller.msg("You close the button's lid. It clicks back into place.")
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} closes the button's lid.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} closes the button's lid.", exclude=self.caller
|
||||
)
|
||||
|
||||
|
||||
class LidOpenCmdSet(CmdSet):
|
||||
|
|
@ -286,6 +292,7 @@ class LidOpenCmdSet(CmdSet):
|
|||
# Commands for when the button has been pushed and the player is blinded. This
|
||||
# replaces commands on the player making them 'blind' for a while.
|
||||
|
||||
|
||||
class CmdBlindLook(Command):
|
||||
"""
|
||||
Looking around in darkness
|
||||
|
|
@ -317,12 +324,14 @@ class CmdBlindLook(Command):
|
|||
string = "You fumble around, hands outstretched. You bump your knee."
|
||||
else:
|
||||
# trying to look
|
||||
string = ("You are temporarily blinded by the flash. "
|
||||
"Until it wears off, all you can do is feel around blindly.")
|
||||
string = (
|
||||
"You are temporarily blinded by the flash. "
|
||||
"Until it wears off, all you can do is feel around blindly."
|
||||
)
|
||||
self.caller.msg(string)
|
||||
self.caller.location.msg_contents(
|
||||
f"{self.caller.name} stumbles around, blinded.",
|
||||
exclude=self.caller)
|
||||
f"{self.caller.name} stumbles around, blinded.", exclude=self.caller
|
||||
)
|
||||
|
||||
|
||||
class CmdBlindHelp(Command):
|
||||
|
|
@ -420,20 +429,26 @@ class RedButton(DefaultObject):
|
|||
`button = create_object(RedButton, ..., attributes=[('desc', 'my desc')])`.
|
||||
|
||||
"""
|
||||
|
||||
# these are the pre-set descriptions. Setting attributes will override
|
||||
# these on the fly.
|
||||
|
||||
desc_closed_lid = ("This is a large red button, inviting yet evil-looking. "
|
||||
"A closed glass lid protects it.")
|
||||
desc_open_lid = ("This is a large red button, inviting yet evil-looking. "
|
||||
"Its glass cover is open and the button exposed.")
|
||||
desc_closed_lid = (
|
||||
"This is a large red button, inviting yet evil-looking. " "A closed glass lid protects it."
|
||||
)
|
||||
desc_open_lid = (
|
||||
"This is a large red button, inviting yet evil-looking. "
|
||||
"Its glass cover is open and the button exposed."
|
||||
)
|
||||
auto_close_msg = "The button's glass lid silently slides back in place."
|
||||
lamp_breaks_msg = "The lamp flickers, the button going dark."
|
||||
desc_add_lamp_broken = "\nThe big red button has stopped blinking for the time being."
|
||||
# note that this is a list. A random message will display each time
|
||||
blink_msgs = ["The red button flashes briefly.",
|
||||
"The red button blinks invitingly.",
|
||||
"The red button flashes. You know you wanna push it!"]
|
||||
blink_msgs = [
|
||||
"The red button flashes briefly.",
|
||||
"The red button blinks invitingly.",
|
||||
"The red button flashes. You know you wanna push it!",
|
||||
]
|
||||
|
||||
def at_object_creation(self):
|
||||
"""
|
||||
|
|
@ -523,9 +538,9 @@ class RedButton(DefaultObject):
|
|||
self.cmdset.add(LidOpenCmdSet)
|
||||
|
||||
# wait 20s then call self.to_closed_state with a message as argument
|
||||
delay(35, self.to_closed_state,
|
||||
self.db.auto_close_msg or self.auto_close_msg,
|
||||
persistent=True)
|
||||
delay(
|
||||
35, self.to_closed_state, self.db.auto_close_msg or self.auto_close_msg, persistent=True
|
||||
)
|
||||
|
||||
def _unblind_target(self, caller):
|
||||
"""
|
||||
|
|
@ -536,7 +551,8 @@ class RedButton(DefaultObject):
|
|||
caller.msg("You blink feverishly as your eyesight slowly returns.")
|
||||
self.location.msg_contents(
|
||||
f"{caller.name} seems to be recovering their eyesight, blinking feverishly.",
|
||||
exclude=caller)
|
||||
exclude=caller,
|
||||
)
|
||||
|
||||
def blind_target(self, caller):
|
||||
"""
|
||||
|
|
@ -554,8 +570,7 @@ class RedButton(DefaultObject):
|
|||
|
||||
# wait 20s then call self._unblind to remove blindness effect. The
|
||||
# persistent=True means the delay should survive a server reload.
|
||||
delay(20, self._unblind_target, caller,
|
||||
persistent=True)
|
||||
delay(20, self._unblind_target, caller, persistent=True)
|
||||
|
||||
def _unbreak_lamp(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -65,8 +65,10 @@ def info2(caller):
|
|||
|
||||
|
||||
def info3(caller):
|
||||
text = ("'Well ... I'm sort of busy so, have to go. NPC business. "
|
||||
"Important stuff. You wouldn't understand.'")
|
||||
text = (
|
||||
"'Well ... I'm sort of busy so, have to go. NPC business. "
|
||||
"Important stuff. You wouldn't understand.'"
|
||||
)
|
||||
|
||||
options = (
|
||||
{"desc": "Oookay ... I won't keep you. Bye.", "goto": "END"},
|
||||
|
|
@ -91,15 +93,15 @@ def END(caller):
|
|||
|
||||
class CmdTalk(default_cmds.MuxCommand):
|
||||
"""
|
||||
Talks to an npc
|
||||
Talks to an npc
|
||||
|
||||
Usage:
|
||||
talk
|
||||
Usage:
|
||||
talk
|
||||
|
||||
This command is only available if a talkative non-player-character
|
||||
(NPC) is actually present. It will strike up a conversation with
|
||||
that NPC and give you options on what to talk about.
|
||||
"""
|
||||
This command is only available if a talkative non-player-character
|
||||
(NPC) is actually present. It will strike up a conversation with
|
||||
that NPC and give you options on what to talk about.
|
||||
"""
|
||||
|
||||
key = "talk"
|
||||
locks = "cmd:all()"
|
||||
|
|
@ -113,8 +115,11 @@ class CmdTalk(default_cmds.MuxCommand):
|
|||
|
||||
# Initiate the menu. Change this if you are putting this on
|
||||
# some other custom NPC class.
|
||||
EvMenu(self.caller, "evennia.contrib.tutorials.talking_npc.talking_npc",
|
||||
startnode="menu_start_node")
|
||||
EvMenu(
|
||||
self.caller,
|
||||
"evennia.contrib.tutorials.talking_npc.talking_npc",
|
||||
startnode="menu_start_node",
|
||||
)
|
||||
|
||||
|
||||
class TalkingCmdSet(CmdSet):
|
||||
|
|
|
|||
|
|
@ -1158,7 +1158,8 @@ class TutorialWeaponRack(TutorialObject):
|
|||
|wstab/thrust/pierce <target>|n - poke at the enemy. More damage but harder to hit.
|
||||
|wslash/chop/bash <target>|n - swipe at the enemy. Less damage but easier to hit.
|
||||
|wdefend/parry|n - protect yourself and make yourself harder to hit.)
|
||||
""").strip()
|
||||
"""
|
||||
).strip()
|
||||
|
||||
self.db.no_more_weapons_msg = "you find nothing else of use."
|
||||
self.db.available_weapons = ["knife", "dagger", "sword", "club"]
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class CmdTutorial(Command):
|
|||
helptext += "\n\n (Write 'give up' if you want to abandon your quest.)"
|
||||
caller.msg(helptext)
|
||||
|
||||
|
||||
# for the @detail command we inherit from MuxCommand, since
|
||||
# we want to make use of MuxCommand's pre-parsing of '=' in the
|
||||
# argument.
|
||||
|
|
@ -202,22 +203,26 @@ class CmdTutorialLook(default_cmds.CmdLook):
|
|||
looking_at_obj.at_desc(looker=caller)
|
||||
return
|
||||
|
||||
|
||||
class CmdTutorialGiveUp(default_cmds.MuxCommand):
|
||||
"""
|
||||
Give up the tutorial-world quest and return to Limbo, the start room of the
|
||||
server.
|
||||
|
||||
"""
|
||||
|
||||
key = "give up"
|
||||
aliases = ['abort']
|
||||
aliases = ["abort"]
|
||||
|
||||
def func(self):
|
||||
outro_room = OutroRoom.objects.all()
|
||||
if outro_room:
|
||||
outro_room = outro_room[0]
|
||||
else:
|
||||
self.caller.msg("That didn't work (seems like a bug). "
|
||||
"Try to use the |wteleport|n command instead.")
|
||||
self.caller.msg(
|
||||
"That didn't work (seems like a bug). "
|
||||
"Try to use the |wteleport|n command instead."
|
||||
)
|
||||
return
|
||||
|
||||
self.caller.move_to(outro_room)
|
||||
|
|
@ -312,6 +317,7 @@ class TutorialStartExit(DefaultExit):
|
|||
will also clean up the intro command.
|
||||
|
||||
"""
|
||||
|
||||
def at_object_creation(self):
|
||||
self.cmdset.add(CmdSetEvenniaIntro, persistent=True)
|
||||
|
||||
|
|
@ -397,6 +403,7 @@ SUPERUSER_WARNING = (
|
|||
#
|
||||
# -------------------------------------------------------------
|
||||
|
||||
|
||||
class CmdEvenniaIntro(Command):
|
||||
"""
|
||||
Start the Evennia intro wizard.
|
||||
|
|
@ -405,10 +412,12 @@ class CmdEvenniaIntro(Command):
|
|||
intro
|
||||
|
||||
"""
|
||||
|
||||
key = "intro"
|
||||
|
||||
def func(self):
|
||||
from .intro_menu import init_menu
|
||||
|
||||
# quell also superusers
|
||||
if self.caller.account:
|
||||
self.caller.msg("Auto-quelling permissions while in intro ...")
|
||||
|
|
@ -463,6 +472,7 @@ class IntroRoom(TutorialRoom):
|
|||
character.account.execute_cmd("quell")
|
||||
character.msg("(Auto-quelling while in tutorial-world)")
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
# Bridge - unique room
|
||||
|
|
@ -1176,4 +1186,3 @@ class OutroRoom(TutorialRoom):
|
|||
def at_object_leave(self, character, destination):
|
||||
if character.account:
|
||||
character.account.execute_cmd("unquell")
|
||||
|
||||
|
|
|
|||
|
|
@ -12,10 +12,8 @@ from .server import AuditedServerSession
|
|||
from evennia.server.sessionhandler import SESSIONS
|
||||
|
||||
|
||||
@override_settings(
|
||||
AUDIT_MASKS=[])
|
||||
@override_settings(AUDIT_MASKS=[])
|
||||
class AuditingTest(BaseEvenniaTest):
|
||||
|
||||
@patch("evennia.server.sessionhandler._ServerSession", AuditedServerSession)
|
||||
def setup_session(self):
|
||||
"""Overrides default one in EvenniaTest"""
|
||||
|
|
@ -29,8 +27,10 @@ class AuditingTest(BaseEvenniaTest):
|
|||
SESSIONS.login(session, self.account, testmode=True)
|
||||
self.session = session
|
||||
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog")
|
||||
@patch(
|
||||
"evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog",
|
||||
)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_IN", True)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
||||
def test_mask(self):
|
||||
|
|
@ -100,8 +100,10 @@ class AuditingTest(BaseEvenniaTest):
|
|||
for secret in secrets:
|
||||
self.assertEqual(self.session.mask(secret), secret)
|
||||
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog")
|
||||
@patch(
|
||||
"evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
||||
"evennia.contrib.utils.auditing.outputs.to_syslog",
|
||||
)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_IN", True)
|
||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
||||
def test_audit(self):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ FieldFill contrib - Tim Ashley Jenkins 2018
|
|||
|
||||
from .fieldfill import FieldEvMenu # noqa
|
||||
from .fieldfill import CmdTestMenu # noqa
|
||||
from .fieldfill import init_fill_field # noqa
|
||||
from .fieldfill import form_template_to_dict # noqa
|
||||
from .fieldfill import display_formdata # noqa
|
||||
from .fieldfill import init_fill_field # noqa
|
||||
from .fieldfill import form_template_to_dict # noqa
|
||||
from .fieldfill import display_formdata # noqa
|
||||
|
|
|
|||
|
|
@ -5,4 +5,4 @@ Pseudo-random generator - vlgeoff 2017
|
|||
|
||||
from .random_string_generator import RandomStringGenerator # noqa
|
||||
from .random_string_generator import RandomStringGeneratorScript # noqa
|
||||
from .random_string_generator import RejectedRegex, ExhaustedGenerator # noqa
|
||||
from .random_string_generator import RejectedRegex, ExhaustedGenerator # noqa
|
||||
|
|
|
|||
|
|
@ -156,8 +156,10 @@ class RandomStringGenerator:
|
|||
self._find_elements(regex)
|
||||
|
||||
def __repr__(self):
|
||||
return "<evennia.contrib.utils.random_string_generator.RandomStringGenerator for {}>".format(
|
||||
self.name
|
||||
return (
|
||||
"<evennia.contrib.utils.random_string_generator.RandomStringGenerator for {}>".format(
|
||||
self.name
|
||||
)
|
||||
)
|
||||
|
||||
def _get_script(self):
|
||||
|
|
@ -169,7 +171,8 @@ class RandomStringGenerator:
|
|||
script = ScriptDB.objects.get(db_key="generator_script")
|
||||
except ScriptDB.DoesNotExist:
|
||||
script = create_script(
|
||||
"evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript")
|
||||
"evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript"
|
||||
)
|
||||
|
||||
type(self).script = script
|
||||
return script
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class Command(BaseCommand):
|
|||
here. Without setting one, the parent's docstring will show (like now).
|
||||
|
||||
"""
|
||||
|
||||
# Each Command class implements the following methods, called in this order
|
||||
# (only func() is actually required):
|
||||
#
|
||||
|
|
|
|||
|
|
@ -157,6 +157,6 @@ class Object(DefaultObject):
|
|||
at_say(speaker, message) - by default, called if an object inside this
|
||||
object speaks
|
||||
|
||||
"""
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ Search the Django documentation for "URL dispatcher" for more help.
|
|||
|
||||
"""
|
||||
from django.urls import path, include
|
||||
|
||||
# default evennia patterns
|
||||
from evennia.web.urls import urlpatterns as evennia_default_urlpatterns
|
||||
|
||||
|
|
@ -24,7 +25,6 @@ urlpatterns = [
|
|||
path("webclient/", include("web.webclient.urls")),
|
||||
# web admin
|
||||
path("admin/", include("web.admin.urls")),
|
||||
|
||||
# add any extra urls here:
|
||||
# path("mypath/", include("path.to.my.urls.file")),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Each dict is on the form
|
|||
HELP_ENTRY_DICTS = [
|
||||
{
|
||||
"key": "evennia",
|
||||
"aliases": ['ev'],
|
||||
"aliases": ["ev"],
|
||||
"category": "General",
|
||||
"locks": "read:perm(Developer)",
|
||||
"text": """
|
||||
|
|
@ -51,7 +51,7 @@ HELP_ENTRY_DICTS = [
|
|||
|
||||
There is also a discord channel you can find from the sidebard on evennia.com.
|
||||
|
||||
"""
|
||||
""",
|
||||
},
|
||||
{
|
||||
"key": "building",
|
||||
|
|
@ -60,6 +60,6 @@ HELP_ENTRY_DICTS = [
|
|||
Evennia comes with a bunch of default building commands. You can
|
||||
find a building tutorial in the evennia documentation.
|
||||
|
||||
"""
|
||||
}
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -69,8 +69,7 @@ from dataclasses import dataclass
|
|||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
from evennia.utils.utils import (
|
||||
variable_from_module, make_iter, all_from_module)
|
||||
from evennia.utils.utils import variable_from_module, make_iter, all_from_module
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import lazy_property
|
||||
from evennia.locks.lockhandler import LockHandler
|
||||
|
|
@ -86,6 +85,7 @@ class FileHelpEntry:
|
|||
help command.
|
||||
|
||||
"""
|
||||
|
||||
key: str
|
||||
aliases: list
|
||||
help_category: str
|
||||
|
|
@ -147,7 +147,7 @@ class FileHelpEntry:
|
|||
"""
|
||||
try:
|
||||
return reverse(
|
||||
'help-entry-detail',
|
||||
"help-entry-detail",
|
||||
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
|
||||
)
|
||||
except Exception:
|
||||
|
|
@ -192,8 +192,7 @@ class FileHelpStorageHandler:
|
|||
"""
|
||||
Initialize the storage.
|
||||
"""
|
||||
self.help_file_modules = [str(part).strip()
|
||||
for part in make_iter(help_file_modules)]
|
||||
self.help_file_modules = [str(part).strip() for part in make_iter(help_file_modules)]
|
||||
self.help_entries = []
|
||||
self.help_entries_dict = {}
|
||||
self.load()
|
||||
|
|
@ -206,13 +205,11 @@ class FileHelpStorageHandler:
|
|||
loaded_help_dicts = []
|
||||
|
||||
for module_or_path in self.help_file_modules:
|
||||
help_dict_list = variable_from_module(
|
||||
module_or_path, variable="HELP_ENTRY_DICTS"
|
||||
)
|
||||
help_dict_list = variable_from_module(module_or_path, variable="HELP_ENTRY_DICTS")
|
||||
if not help_dict_list:
|
||||
help_dict_list = [
|
||||
dct for dct in all_from_module(module_or_path).values()
|
||||
if isinstance(dct, dict)]
|
||||
dct for dct in all_from_module(module_or_path).values() if isinstance(dct, dict)
|
||||
]
|
||||
if help_dict_list:
|
||||
loaded_help_dicts.extend(help_dict_list)
|
||||
else:
|
||||
|
|
@ -223,19 +220,23 @@ class FileHelpStorageHandler:
|
|||
unique_help_entries = {}
|
||||
|
||||
for dct in loaded_help_dicts:
|
||||
key = dct.get('key').lower().strip()
|
||||
category = dct.get('category', _DEFAULT_HELP_CATEGORY).strip()
|
||||
aliases = list(dct.get('aliases', []))
|
||||
entrytext = dct.get('text', '')
|
||||
locks = dct.get('locks', '')
|
||||
key = dct.get("key").lower().strip()
|
||||
category = dct.get("category", _DEFAULT_HELP_CATEGORY).strip()
|
||||
aliases = list(dct.get("aliases", []))
|
||||
entrytext = dct.get("text", "")
|
||||
locks = dct.get("locks", "")
|
||||
|
||||
if not key and entrytext:
|
||||
logger.error(f"Cannot load file-help-entry (missing key or text): {dct}")
|
||||
continue
|
||||
|
||||
unique_help_entries[key] = FileHelpEntry(
|
||||
key=key, help_category=category, aliases=aliases, lock_storage=locks,
|
||||
entrytext=entrytext)
|
||||
key=key,
|
||||
help_category=category,
|
||||
aliases=aliases,
|
||||
lock_storage=locks,
|
||||
entrytext=entrytext,
|
||||
)
|
||||
|
||||
self.help_entries_dict = unique_help_entries
|
||||
self.help_entries = list(unique_help_entries.values())
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class TestParseSubtopics(TestCase):
|
|||
"""
|
||||
self.maxDiff = None
|
||||
|
||||
entry = dedent("""
|
||||
entry = dedent(
|
||||
"""
|
||||
Main topic text
|
||||
# subtopics
|
||||
## foo
|
||||
|
|
@ -36,7 +37,9 @@ class TestParseSubtopics(TestCase):
|
|||
Bar subcategory
|
||||
### moo
|
||||
Bar/Moo subcategory
|
||||
""", indent=0)
|
||||
""",
|
||||
indent=0,
|
||||
)
|
||||
expected = {
|
||||
None: "Main topic text",
|
||||
"foo": {
|
||||
|
|
@ -45,15 +48,10 @@ class TestParseSubtopics(TestCase):
|
|||
None: "\nFoo/Moo subsub-category\n",
|
||||
"dum": {
|
||||
None: "\nFoo/Moo/Dum subsubsub-category\n",
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"bar": {
|
||||
None: "\nBar subcategory\n",
|
||||
"moo": {
|
||||
None: "\nBar/Moo subcategory"
|
||||
}
|
||||
}
|
||||
"bar": {None: "\nBar subcategory\n", "moo": {None: "\nBar/Moo subcategory"}},
|
||||
}
|
||||
|
||||
actual_result = help_utils.parse_entry_for_subcategories(entry)
|
||||
|
|
@ -65,28 +63,30 @@ class TestParseSubtopics(TestCase):
|
|||
|
||||
"""
|
||||
|
||||
entry = dedent("""
|
||||
entry = dedent(
|
||||
"""
|
||||
Main topic text
|
||||
# SUBTOPICS
|
||||
## creating extra stuff
|
||||
Help on creating extra stuff.
|
||||
""", indent=0)
|
||||
""",
|
||||
indent=0,
|
||||
)
|
||||
expected = {
|
||||
None: "Main topic text",
|
||||
"creating extra stuff": {
|
||||
None: "\nHelp on creating extra stuff."
|
||||
}
|
||||
"creating extra stuff": {None: "\nHelp on creating extra stuff."},
|
||||
}
|
||||
|
||||
actual_result = help_utils.parse_entry_for_subcategories(entry)
|
||||
self.assertEqual(expected, actual_result)
|
||||
|
||||
|
||||
# test filehelp system
|
||||
|
||||
HELP_ENTRY_DICTS = [
|
||||
{
|
||||
"key": "evennia",
|
||||
"aliases": ['ev'],
|
||||
"aliases": ["ev"],
|
||||
"category": "General",
|
||||
"text": """
|
||||
Evennia is a MUD game server in Python.
|
||||
|
|
@ -105,7 +105,7 @@ HELP_ENTRY_DICTS = [
|
|||
|
||||
There is also a discord channel you can find from the sidebard on evennia.com.
|
||||
|
||||
"""
|
||||
""",
|
||||
},
|
||||
{
|
||||
"key": "building",
|
||||
|
|
@ -114,12 +114,11 @@ HELP_ENTRY_DICTS = [
|
|||
Evennia comes with a bunch of default building commands. You can
|
||||
find a building tutorial in the evennia documentation.
|
||||
|
||||
"""
|
||||
}
|
||||
""",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
||||
class TestFileHelp(TestCase):
|
||||
"""
|
||||
Test the File-help system
|
||||
|
|
@ -135,7 +134,7 @@ class TestFileHelp(TestCase):
|
|||
result = storage.all()
|
||||
|
||||
for inum, helpentry in enumerate(result):
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]['key'], helpentry.key)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum].get('aliases', []), helpentry.aliases)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]['category'], helpentry.help_category)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]['text'], helpentry.entrytext)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]["key"], helpentry.key)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum].get("aliases", []), helpentry.aliases)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]["category"], helpentry.help_category)
|
||||
self.assertEqual(HELP_ENTRY_DICTS[inum]["text"], helpentry.entrytext)
|
||||
|
|
|
|||
|
|
@ -19,11 +19,9 @@ _LUNR_EXCEPTION = None
|
|||
_LUNR_GET_BUILDER = None
|
||||
_LUNR_BUILDER_PIPELINE = None
|
||||
|
||||
_RE_HELP_SUBTOPICS_START = re.compile(
|
||||
r"^\s*?#\s*?subtopics\s*?$", re.I + re.M)
|
||||
_RE_HELP_SUBTOPICS_START = re.compile(r"^\s*?#\s*?subtopics\s*?$", re.I + re.M)
|
||||
_RE_HELP_SUBTOPIC_SPLIT = re.compile(r"^\s*?(\#{2,6}\s*?\w+?[a-z0-9 \-\?!,\.]*?)$", re.M + re.I)
|
||||
_RE_HELP_SUBTOPIC_PARSE = re.compile(
|
||||
r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
|
||||
_RE_HELP_SUBTOPIC_PARSE = re.compile(r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
|
||||
|
||||
MAX_SUBTOPIC_NESTING = 5
|
||||
|
||||
|
|
@ -57,6 +55,7 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5, fields
|
|||
from lunr import get_default_builder as _LUNR_GET_BUILDER
|
||||
from lunr import stop_word_filter
|
||||
from lunr.stemmer import stemmer
|
||||
|
||||
# from lunr.trimmer import trimmer
|
||||
|
||||
# pre-create a lunr index-builder pipeline where we've removed some of
|
||||
|
|
@ -90,12 +89,7 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5, fields
|
|||
builder.pipeline.reset()
|
||||
builder.pipeline.add(*_LUNR_BUILDER_PIPELINE)
|
||||
|
||||
search_index = _LUNR(
|
||||
ref="key",
|
||||
fields=fields,
|
||||
documents=indx,
|
||||
builder=builder
|
||||
)
|
||||
search_index = _LUNR(ref="key", fields=fields, documents=indx, builder=builder)
|
||||
|
||||
try:
|
||||
matches = search_index.search(query)[:suggestion_maxnum]
|
||||
|
|
@ -175,7 +169,7 @@ def parse_entry_for_subcategories(entry):
|
|||
|
||||
"""
|
||||
topic, *subtopics = _RE_HELP_SUBTOPICS_START.split(entry, maxsplit=1)
|
||||
structure = {None: topic.strip('\n')}
|
||||
structure = {None: topic.strip("\n")}
|
||||
|
||||
if subtopics:
|
||||
subtopics = subtopics[0]
|
||||
|
|
@ -193,12 +187,13 @@ def parse_entry_for_subcategories(entry):
|
|||
if subtopic_match:
|
||||
# a new sub(-sub..) category starts.
|
||||
mdict = subtopic_match.groupdict()
|
||||
subtopic = mdict['name'].lower().strip()
|
||||
new_nesting = len(mdict['nesting']) - 1
|
||||
subtopic = mdict["name"].lower().strip()
|
||||
new_nesting = len(mdict["nesting"]) - 1
|
||||
|
||||
if new_nesting > MAX_SUBTOPIC_NESTING:
|
||||
raise RuntimeError(
|
||||
f"Can have max {MAX_SUBTOPIC_NESTING} levels of nested help subtopics.")
|
||||
f"Can have max {MAX_SUBTOPIC_NESTING} levels of nested help subtopics."
|
||||
)
|
||||
|
||||
nestdiff = new_nesting - current_nesting
|
||||
if nestdiff < 0:
|
||||
|
|
@ -226,7 +221,5 @@ def parse_entry_for_subcategories(entry):
|
|||
if key in dct:
|
||||
dct = dct[key]
|
||||
else:
|
||||
dct[key] = {
|
||||
None: part
|
||||
}
|
||||
dct[key] = {None: part}
|
||||
return structure
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ def true(*args, **kwargs):
|
|||
"""
|
||||
return True
|
||||
|
||||
|
||||
def all(*args, **kwargs):
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -235,8 +235,11 @@ class LockHandler:
|
|||
funcname, rest = (part.strip().strip(")") for part in funcstring.split("(", 1))
|
||||
func = _LOCKFUNCS.get(funcname, None)
|
||||
if not callable(func):
|
||||
elist.append(_("Lock: lock-function '{lockfunc}' is not available.").format(
|
||||
lockfunc=funcstring))
|
||||
elist.append(
|
||||
_("Lock: lock-function '{lockfunc}' is not available.").format(
|
||||
lockfunc=funcstring
|
||||
)
|
||||
)
|
||||
continue
|
||||
args = list(arg.strip() for arg in rest.split(",") if arg and "=" not in arg)
|
||||
kwargs = dict(
|
||||
|
|
@ -263,13 +266,16 @@ class LockHandler:
|
|||
continue
|
||||
if access_type in locks:
|
||||
duplicates += 1
|
||||
wlist.append(_(
|
||||
"LockHandler on {obj}: access type '{access_type}' "
|
||||
"changed from '{source}' to '{goal}' ".format(
|
||||
obj=self.obj,
|
||||
access_type=access_type,
|
||||
source=locks[access_type][2],
|
||||
goal=raw_lockstring))
|
||||
wlist.append(
|
||||
_(
|
||||
"LockHandler on {obj}: access type '{access_type}' "
|
||||
"changed from '{source}' to '{goal}' ".format(
|
||||
obj=self.obj,
|
||||
access_type=access_type,
|
||||
source=locks[access_type][2],
|
||||
goal=raw_lockstring,
|
||||
)
|
||||
)
|
||||
)
|
||||
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
||||
if wlist and WARNING_LOG:
|
||||
|
|
@ -695,6 +701,7 @@ def check_lockstring(
|
|||
access_type=access_type,
|
||||
)
|
||||
|
||||
|
||||
def check_perm(obj, permission, no_superuser_bypass=False):
|
||||
"""
|
||||
Shortcut for checking if an object has the given `permission`. If the
|
||||
|
|
@ -713,6 +720,7 @@ def check_perm(obj, permission, no_superuser_bypass=False):
|
|||
|
||||
"""
|
||||
from evennia.locks.lockfuncs import perm
|
||||
|
||||
if not no_superuser_bypass and obj.is_superuser:
|
||||
return True
|
||||
return perm(obj, None, permission)
|
||||
|
|
|
|||
|
|
@ -215,12 +215,11 @@ class TestPermissionCheck(BaseEvenniaTest):
|
|||
Test the PermissionHandler.check method
|
||||
|
||||
"""
|
||||
|
||||
def test_check__success(self):
|
||||
"""Test combinations that should pass the check"""
|
||||
self.assertEqual(
|
||||
[perm for perm in self.char1.account.permissions.all()],
|
||||
['developer', 'player']
|
||||
|
||||
[perm for perm in self.char1.account.permissions.all()], ["developer", "player"]
|
||||
)
|
||||
self.assertTrue(self.char1.permissions.check("Builder"))
|
||||
self.assertTrue(self.char1.permissions.check("Builder", "Player"))
|
||||
|
|
@ -234,12 +233,11 @@ class TestPermissionCheck(BaseEvenniaTest):
|
|||
self.assertFalse(self.char1.permissions.check("Builder", "dummy", require_all=True))
|
||||
self.assertFalse(self.char1.permissions.check("Developer", "foobar", require_all=True))
|
||||
|
||||
self.char1.account.permissions.remove('developer')
|
||||
self.char1.account.permissions.remove("developer")
|
||||
self.char1.account.permissions.add("Builder")
|
||||
|
||||
self.assertEqual(
|
||||
[perm for perm in self.char1.account.permissions.all()],
|
||||
['builder', 'player']
|
||||
[perm for perm in self.char1.account.permissions.all()], ["builder", "player"]
|
||||
)
|
||||
self.assertFalse(self.char1.permissions.check("Developer"))
|
||||
self.assertFalse(self.char1.permissions.check("Developer", "Player", require_all=True))
|
||||
|
|
|
|||
|
|
@ -237,8 +237,9 @@ class ObjectDBManager(TypedObjectManager):
|
|||
)
|
||||
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
|
||||
try:
|
||||
return self.filter(
|
||||
cand_restriction & type_restriction & Q(**querykwargs)).order_by("id")
|
||||
return self.filter(cand_restriction & type_restriction & Q(**querykwargs)).order_by(
|
||||
"id"
|
||||
)
|
||||
except exceptions.FieldError:
|
||||
return self.none()
|
||||
except ValueError:
|
||||
|
|
@ -335,8 +336,9 @@ class ObjectDBManager(TypedObjectManager):
|
|||
index_matches = string_partial_matching(key_strings, ostring, ret_index=True)
|
||||
if index_matches:
|
||||
# a match by key
|
||||
match_ids = [obj.id for ind, obj in enumerate(search_candidates)
|
||||
if ind in index_matches]
|
||||
match_ids = [
|
||||
obj.id for ind, obj in enumerate(search_candidates) if ind in index_matches
|
||||
]
|
||||
else:
|
||||
# match by alias rather than by key
|
||||
search_candidates = search_candidates.filter(
|
||||
|
|
|
|||
|
|
@ -22,9 +22,15 @@ from evennia.scripts.scripthandler import ScriptHandler
|
|||
from evennia.typeclasses.attributes import ModelAttributeBackend, NickHandler
|
||||
from evennia.typeclasses.models import TypeclassBase
|
||||
from evennia.utils import ansi, create, funcparser, logger, search
|
||||
from evennia.utils.utils import (class_from_module, is_iter, lazy_property,
|
||||
list_to_string, make_iter, to_str,
|
||||
variable_from_module)
|
||||
from evennia.utils.utils import (
|
||||
class_from_module,
|
||||
is_iter,
|
||||
lazy_property,
|
||||
list_to_string,
|
||||
make_iter,
|
||||
to_str,
|
||||
variable_from_module,
|
||||
)
|
||||
|
||||
_INFLECT = inflect.engine()
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
|
|
@ -212,13 +218,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
objects = ObjectManager()
|
||||
|
||||
# populated by `return_apperance`
|
||||
appearance_template = '''
|
||||
appearance_template = """
|
||||
{header}
|
||||
|c{name}|n
|
||||
{desc}
|
||||
{exits}{characters}{things}
|
||||
{footer}
|
||||
'''
|
||||
"""
|
||||
|
||||
# on-object properties
|
||||
|
||||
|
|
@ -532,12 +538,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# we re-run exact match agains one of the matches to
|
||||
# make sure we were not catching partial matches not belonging
|
||||
# to the stack
|
||||
nstack = len(ObjectDB.objects.get_objs_with_key_or_alias(
|
||||
results[0].key,
|
||||
exact=True,
|
||||
candidates=list(results),
|
||||
typeclasses=[typeclass] if typeclass else None
|
||||
))
|
||||
nstack = len(
|
||||
ObjectDB.objects.get_objs_with_key_or_alias(
|
||||
results[0].key,
|
||||
exact=True,
|
||||
candidates=list(results),
|
||||
typeclasses=[typeclass] if typeclass else None,
|
||||
)
|
||||
)
|
||||
if nstack == nresults:
|
||||
# a valid stack, return multiple results
|
||||
return list(results)[:stacked]
|
||||
|
|
@ -630,9 +638,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
raw_string = self.nicks.nickreplace(
|
||||
raw_string, categories=("inputline", "channel"), include_account=True
|
||||
)
|
||||
return _CMDHANDLER(
|
||||
self, raw_string, callertype="object", session=session, **kwargs
|
||||
)
|
||||
return _CMDHANDLER(self, raw_string, callertype="object", session=session, **kwargs)
|
||||
|
||||
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
|
||||
"""
|
||||
|
|
@ -790,7 +796,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
mapping = mapping or {}
|
||||
you = from_obj or self
|
||||
|
||||
if 'you' not in mapping:
|
||||
if "you" not in mapping:
|
||||
mapping[you] = you
|
||||
|
||||
contents = self.contents
|
||||
|
|
@ -802,14 +808,23 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
# actor-stance replacements
|
||||
inmessage = _MSG_CONTENTS_PARSER.parse(
|
||||
inmessage, raise_errors=True, return_string=True,
|
||||
caller=you, receiver=receiver, mapping=mapping)
|
||||
inmessage,
|
||||
raise_errors=True,
|
||||
return_string=True,
|
||||
caller=you,
|
||||
receiver=receiver,
|
||||
mapping=mapping,
|
||||
)
|
||||
|
||||
# director-stance replacements
|
||||
outmessage = inmessage.format(
|
||||
**{key: obj.get_display_name(looker=receiver)
|
||||
if hasattr(obj, "get_display_name") else str(obj)
|
||||
for key, obj in mapping.items()})
|
||||
**{
|
||||
key: obj.get_display_name(looker=receiver)
|
||||
if hasattr(obj, "get_display_name")
|
||||
else str(obj)
|
||||
for key, obj in mapping.items()
|
||||
}
|
||||
)
|
||||
|
||||
receiver.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
|
||||
|
||||
|
|
@ -866,6 +881,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
7. `self.at_post_move(source_location)`
|
||||
|
||||
"""
|
||||
|
||||
def logerr(string="", err=None):
|
||||
"""Simple log helper method"""
|
||||
logger.log_trace()
|
||||
|
|
@ -989,8 +1005,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
if not home:
|
||||
obj.location = None
|
||||
obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin."))
|
||||
logger.log_err("Missing default home - '{name}(#{dbid})' now "
|
||||
"has a null location.".format(name=obj.name, dbid=obj.dbid))
|
||||
logger.log_err(
|
||||
"Missing default home - '{name}(#{dbid})' now "
|
||||
"has a null location.".format(name=obj.name, dbid=obj.dbid)
|
||||
)
|
||||
return
|
||||
|
||||
if obj.has_account:
|
||||
|
|
@ -1550,7 +1568,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# This was created from nowhere and added to an account's
|
||||
# inventory; it's probably the result of a create command.
|
||||
string = _("You now have {name} in your possession.").format(
|
||||
name=self.get_display_name(self.location))
|
||||
name=self.get_display_name(self.location)
|
||||
)
|
||||
self.location.msg(string)
|
||||
return
|
||||
|
||||
|
|
@ -1754,13 +1773,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
lists are the actual objects.
|
||||
|
||||
"""
|
||||
|
||||
def filter_visible(obj_list):
|
||||
return [obj for obj in obj_list if obj != looker and obj.access(looker, "view")]
|
||||
|
||||
return {
|
||||
"exits": filter_visible(self.contents_get(content_type="exit")),
|
||||
"characters": filter_visible(self.contents_get(content_type="character")),
|
||||
"things": filter_visible(self.contents_get(content_type="object"))
|
||||
"things": filter_visible(self.contents_get(content_type="object")),
|
||||
}
|
||||
|
||||
def get_content_names(self, looker, **kwargs):
|
||||
|
|
@ -1789,13 +1809,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
# a mapping {'exits': [...], 'characters': [...], 'things': [...]}
|
||||
contents_map = self.get_visible_contents(looker, **kwargs)
|
||||
|
||||
character_names = [char.get_display_name(looker, **kwargs)
|
||||
for char in contents_map['characters']]
|
||||
exit_names = [exi.get_display_name(looker, **kwargs) for exi in contents_map['exits']]
|
||||
character_names = [
|
||||
char.get_display_name(looker, **kwargs) for char in contents_map["characters"]
|
||||
]
|
||||
exit_names = [exi.get_display_name(looker, **kwargs) for exi in contents_map["exits"]]
|
||||
|
||||
# group all same-named things under one name
|
||||
things = defaultdict(list)
|
||||
for thing in contents_map['things']:
|
||||
for thing in contents_map["things"]:
|
||||
things[thing.get_display_name(looker, **kwargs)].append(thing)
|
||||
|
||||
# pluralize same-named things
|
||||
|
|
@ -1806,11 +1827,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
singular, plural = thing.get_numbered_name(nthings, looker, key=thingname)
|
||||
thing_names.append(singular if nthings == 1 else plural)
|
||||
|
||||
return {
|
||||
"exits": exit_names,
|
||||
"characters": character_names,
|
||||
"things": thing_names
|
||||
}
|
||||
return {"exits": exit_names, "characters": character_names, "things": thing_names}
|
||||
|
||||
def return_appearance(self, looker, **kwargs):
|
||||
"""
|
||||
|
|
@ -1840,7 +1857,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
"""
|
||||
|
||||
if not looker:
|
||||
return ''
|
||||
return ""
|
||||
|
||||
# ourselves
|
||||
name = self.get_display_name(looker, **kwargs)
|
||||
|
|
@ -1848,20 +1865,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
# contents
|
||||
content_names_map = self.get_content_names(looker, **kwargs)
|
||||
exits = list_to_string(content_names_map['exits'])
|
||||
characters = list_to_string(content_names_map['characters'])
|
||||
things = list_to_string(content_names_map['things'])
|
||||
exits = list_to_string(content_names_map["exits"])
|
||||
characters = list_to_string(content_names_map["characters"])
|
||||
things = list_to_string(content_names_map["things"])
|
||||
|
||||
# populate the appearance_template string. It's a good idea to strip it and
|
||||
# let the client add any extra spaces instead.
|
||||
return self.appearance_template.format(
|
||||
header='',
|
||||
header="",
|
||||
name=name,
|
||||
desc=desc,
|
||||
exits=f"|wExits:|n {exits}" if exits else '',
|
||||
characters=f"\n|wCharacters:|n {characters}" if characters else '',
|
||||
things=f"\n|wYou see:|n {things}" if things else '',
|
||||
footer=''
|
||||
exits=f"|wExits:|n {exits}" if exits else "",
|
||||
characters=f"\n|wCharacters:|n {characters}" if characters else "",
|
||||
things=f"\n|wYou see:|n {things}" if things else "",
|
||||
footer="",
|
||||
).strip()
|
||||
|
||||
def at_look(self, target, **kwargs):
|
||||
|
|
@ -2124,7 +2141,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
msg_type = "whisper"
|
||||
msg_self = (
|
||||
'{self} whisper to {all_receivers}, "|n{speech}|n"'
|
||||
if msg_self is True else msg_self
|
||||
if msg_self is True
|
||||
else msg_self
|
||||
)
|
||||
msg_receivers = msg_receivers or '{object} whispers: "|n{speech}|n"'
|
||||
msg_location = None
|
||||
|
|
@ -2332,7 +2350,7 @@ class DefaultCharacter(DefaultObject):
|
|||
|
||||
@classmethod
|
||||
def validate_name(cls, name):
|
||||
""" Validate the character name prior to creating. Overload this function to add custom validators
|
||||
"""Validate the character name prior to creating. Overload this function to add custom validators
|
||||
|
||||
Args:
|
||||
name (str) : The name of the character
|
||||
|
|
@ -2391,8 +2409,7 @@ class DefaultCharacter(DefaultObject):
|
|||
self.db.prelogout_location = self.location # save location again to be sure.
|
||||
else:
|
||||
account.msg(
|
||||
_("|r{obj} has no location and no home is set.|n").format(obj=self),
|
||||
session=session
|
||||
_("|r{obj} has no location and no home is set.|n").format(obj=self), session=session
|
||||
) # Note to set home.
|
||||
|
||||
def at_post_puppet(self, **kwargs):
|
||||
|
|
@ -2414,8 +2431,10 @@ class DefaultCharacter(DefaultObject):
|
|||
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
||||
|
||||
def message(obj, from_obj):
|
||||
obj.msg(_("{name} has entered the game.").format(name=self.get_display_name(obj)),
|
||||
from_obj=from_obj)
|
||||
obj.msg(
|
||||
_("{name} has entered the game.").format(name=self.get_display_name(obj)),
|
||||
from_obj=from_obj,
|
||||
)
|
||||
|
||||
self.location.for_contents(message, exclude=[self], from_obj=self)
|
||||
|
||||
|
|
@ -2438,8 +2457,10 @@ class DefaultCharacter(DefaultObject):
|
|||
if self.location:
|
||||
|
||||
def message(obj, from_obj):
|
||||
obj.msg(_("{name} has left the game.").format(name=self.get_display_name(obj)),
|
||||
from_obj=from_obj)
|
||||
obj.msg(
|
||||
_("{name} has left the game.").format(name=self.get_display_name(obj)),
|
||||
from_obj=from_obj,
|
||||
)
|
||||
|
||||
self.location.for_contents(message, exclude=[self], from_obj=self)
|
||||
self.db.prelogout_location = self.location
|
||||
|
|
@ -2582,6 +2603,7 @@ class DefaultRoom(DefaultObject):
|
|||
# Default Exit command, used by the base exit object
|
||||
#
|
||||
|
||||
|
||||
class ExitCommand(_COMMAND_DEFAULT_CLASS):
|
||||
"""
|
||||
This is a command that simply cause the caller to traverse
|
||||
|
|
|
|||
|
|
@ -202,18 +202,19 @@ class TestContentHandler(BaseEvenniaTest):
|
|||
|
||||
def test_contents_order(self):
|
||||
"""Move object from room to room in various ways"""
|
||||
self.assertEqual(self.room1.contents, [
|
||||
self.exit, self.obj1, self.obj2, self.char1, self.char2])
|
||||
self.assertEqual(
|
||||
self.room1.contents, [self.exit, self.obj1, self.obj2, self.char1, self.char2]
|
||||
)
|
||||
self.assertEqual(self.room2.contents, [])
|
||||
|
||||
# use move_to hook to move obj1
|
||||
self.obj1.move_to(self.room2)
|
||||
self.assertEqual(self.room1.contents, [self.exit, self.obj2, self.char1, self.char2 ])
|
||||
self.assertEqual(self.room1.contents, [self.exit, self.obj2, self.char1, self.char2])
|
||||
self.assertEqual(self.room2.contents, [self.obj1])
|
||||
|
||||
# move obj2
|
||||
self.obj2.move_to(self.room2)
|
||||
self.assertEqual(self.room1.contents, [self.exit, self.char1, self.char2 ])
|
||||
self.assertEqual(self.room1.contents, [self.exit, self.char1, self.char2])
|
||||
self.assertEqual(self.room2.contents, [self.obj1, self.obj2])
|
||||
|
||||
# move back and forth - it should
|
||||
|
|
|
|||
|
|
@ -262,7 +262,8 @@ def _validate_prototype(prototype):
|
|||
def _format_protfuncs():
|
||||
out = []
|
||||
sorted_funcs = [
|
||||
(key, func) for key, func in sorted(protlib.FUNC_PARSER.callables.items(), key=lambda tup: tup[0])
|
||||
(key, func)
|
||||
for key, func in sorted(protlib.FUNC_PARSER.callables.items(), key=lambda tup: tup[0])
|
||||
]
|
||||
for protfunc_name, protfunc in sorted_funcs:
|
||||
out.append(
|
||||
|
|
@ -2113,8 +2114,9 @@ def _apply_diff(caller, **kwargs):
|
|||
objects = kwargs["objects"]
|
||||
back_node = kwargs["back_node"]
|
||||
diff = kwargs.get("diff", None)
|
||||
num_changed = spawner.batch_update_objects_with_prototype(prototype, diff=diff, objects=objects,
|
||||
caller=caller)
|
||||
num_changed = spawner.batch_update_objects_with_prototype(
|
||||
prototype, diff=diff, objects=objects, caller=caller
|
||||
)
|
||||
caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed))
|
||||
return back_node
|
||||
|
||||
|
|
@ -2354,7 +2356,7 @@ def node_apply_diff(caller, **kwargs):
|
|||
|
||||
|
||||
def node_prototype_save(caller, **kwargs):
|
||||
"""Save prototype to disk """
|
||||
"""Save prototype to disk"""
|
||||
# these are only set if we selected 'yes' to save on a previous pass
|
||||
prototype = kwargs.get("prototype", None)
|
||||
# set to True/False if answered, None if first pass
|
||||
|
|
|
|||
|
|
@ -50,16 +50,20 @@ def protfunc_callable_protkey(*args, **kwargs):
|
|||
prot_value = prototype[fieldname]
|
||||
else:
|
||||
# check if it's an attribute
|
||||
for attrtuple in prototype.get('attrs', []):
|
||||
for attrtuple in prototype.get("attrs", []):
|
||||
if attrtuple[0] == fieldname:
|
||||
prot_value = attrtuple[1]
|
||||
break
|
||||
else:
|
||||
raise AttributeError(f"{fieldname} not found in prototype\n{prototype}\n"
|
||||
"(neither as prototype-field or as an Attribute")
|
||||
raise AttributeError(
|
||||
f"{fieldname} not found in prototype\n{prototype}\n"
|
||||
"(neither as prototype-field or as an Attribute"
|
||||
)
|
||||
if callable(prot_value):
|
||||
raise RuntimeError(f"Error in prototype\n{prototype}\n$protkey can only reference static "
|
||||
f"values/attributes (found {prot_value})")
|
||||
raise RuntimeError(
|
||||
f"Error in prototype\n{prototype}\n$protkey can only reference static "
|
||||
f"values/attributes (found {prot_value})"
|
||||
)
|
||||
try:
|
||||
return funcparser.funcparser_callable_eval(prot_value, **kwargs)
|
||||
except funcparser.ParsingError:
|
||||
|
|
|
|||
|
|
@ -131,8 +131,9 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
for attr in attrs:
|
||||
# attrs must be on form [(key, value, category, lockstr)]
|
||||
if not is_iter(attr):
|
||||
logger.log_error("Prototype's 'attr' field must "
|
||||
f"be a list of tuples: {prototype}")
|
||||
logger.log_error(
|
||||
"Prototype's 'attr' field must " f"be a list of tuples: {prototype}"
|
||||
)
|
||||
elif attr:
|
||||
nattr = len(attr)
|
||||
if nattr == 1:
|
||||
|
|
@ -147,14 +148,15 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
|
||||
elif key == "prototype_parent":
|
||||
# homogenize any prototype-parents embedded directly as dicts
|
||||
protparents = prototype.get('prototype_parent', [])
|
||||
protparents = prototype.get("prototype_parent", [])
|
||||
if isinstance(protparents, dict):
|
||||
protparents = [protparents]
|
||||
for parent in make_iter(protparents):
|
||||
if isinstance(parent, dict):
|
||||
# recursively homogenize directly embedded prototype parents
|
||||
homogenized_parents.append(
|
||||
homogenize_prototype(parent, custom_keys=custom_keys))
|
||||
homogenize_prototype(parent, custom_keys=custom_keys)
|
||||
)
|
||||
else:
|
||||
# normal prototype-parent names are added as-is
|
||||
homogenized_parents.append(parent)
|
||||
|
|
@ -170,7 +172,7 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
if homogenized_tags:
|
||||
homogenized["tags"] = homogenized_tags
|
||||
if homogenized_parents:
|
||||
homogenized['prototype_parent'] = homogenized_parents
|
||||
homogenized["prototype_parent"] = homogenized_parents
|
||||
|
||||
# add required missing parts that had defaults before
|
||||
|
||||
|
|
@ -190,6 +192,7 @@ def homogenize_prototype(prototype, custom_keys=None):
|
|||
|
||||
# module/dict-based prototypes
|
||||
|
||||
|
||||
def load_module_prototypes(*mod_or_prototypes, override=True):
|
||||
"""
|
||||
Load module prototypes. Also prototype-dicts passed directly to this function are considered
|
||||
|
|
@ -233,12 +236,16 @@ def load_module_prototypes(*mod_or_prototypes, override=True):
|
|||
# prototype dicts that must have 'prototype_key' set.
|
||||
for prot in prototype_list:
|
||||
if not isinstance(prot, dict):
|
||||
logger.log_err(f"Prototype read from {mod}.PROTOTYPE_LIST "
|
||||
f"is not a dict (skipping): {prot}")
|
||||
logger.log_err(
|
||||
f"Prototype read from {mod}.PROTOTYPE_LIST "
|
||||
f"is not a dict (skipping): {prot}"
|
||||
)
|
||||
continue
|
||||
elif "prototype_key" not in prot:
|
||||
logger.log_err(f"Prototype read from {mod}.PROTOTYPE_LIST "
|
||||
f"is missing the 'prototype_key' (skipping): {prot}")
|
||||
logger.log_err(
|
||||
f"Prototype read from {mod}.PROTOTYPE_LIST "
|
||||
f"is missing the 'prototype_key' (skipping): {prot}"
|
||||
)
|
||||
continue
|
||||
prots.append((prot["prototype_key"], homogenize_prototype(prot)))
|
||||
else:
|
||||
|
|
@ -270,7 +277,8 @@ def load_module_prototypes(*mod_or_prototypes, override=True):
|
|||
{
|
||||
"prototype_key": actual_prot_key,
|
||||
"prototype_desc": (
|
||||
prototype["prototype_desc"] if "prototype_desc" in prototype else (mod or "N/A")),
|
||||
prototype["prototype_desc"] if "prototype_desc" in prototype else (mod or "N/A")
|
||||
),
|
||||
"prototype_locks": (
|
||||
prototype["prototype_locks"]
|
||||
if "prototype_locks" in prototype
|
||||
|
|
@ -292,9 +300,11 @@ def load_module_prototypes(*mod_or_prototypes, override=True):
|
|||
|
||||
if isinstance(mod_or_dict, dict):
|
||||
# a single prototype; we must make sure it has its key
|
||||
prototype_key = mod_or_dict.get('prototype_key')
|
||||
prototype_key = mod_or_dict.get("prototype_key")
|
||||
if not prototype_key:
|
||||
raise ValidationError(f"The prototype {mod_or_prototype} does not contain a 'prototype_key'")
|
||||
raise ValidationError(
|
||||
f"The prototype {mod_or_prototype} does not contain a 'prototype_key'"
|
||||
)
|
||||
prots = [(prototype_key, mod_or_dict)]
|
||||
mod = None
|
||||
else:
|
||||
|
|
@ -307,7 +317,7 @@ def load_module_prototypes(*mod_or_prototypes, override=True):
|
|||
for prototype_key, prot in prots:
|
||||
prototype = _cleanup_prototype(prototype_key, prot, mod=mod)
|
||||
# the key can change since in-proto key is given prio over variable-name-based keys
|
||||
actual_prototype_key = prototype['prototype_key']
|
||||
actual_prototype_key = prototype["prototype_key"]
|
||||
|
||||
if actual_prototype_key in _MODULE_PROTOTYPES and not override:
|
||||
# don't override - useful to still let settings replace dynamic inserts
|
||||
|
|
@ -462,23 +472,26 @@ def delete_prototype(prototype_key, caller=None):
|
|||
stored_prototype = DbPrototype.objects.filter(db_key__iexact=prototype_key)
|
||||
|
||||
if not stored_prototype:
|
||||
raise PermissionError(_("Prototype {prototype_key} was not found.").format(
|
||||
prototype_key=prototype_key))
|
||||
raise PermissionError(
|
||||
_("Prototype {prototype_key} was not found.").format(prototype_key=prototype_key)
|
||||
)
|
||||
|
||||
stored_prototype = stored_prototype[0]
|
||||
if caller:
|
||||
if not stored_prototype.access(caller, "edit"):
|
||||
raise PermissionError(
|
||||
_("{caller} needs explicit 'edit' permissions to "
|
||||
"delete prototype {prototype_key}.").format(
|
||||
caller=caller, prototype_key=prototype_key)
|
||||
_(
|
||||
"{caller} needs explicit 'edit' permissions to "
|
||||
"delete prototype {prototype_key}."
|
||||
).format(caller=caller, prototype_key=prototype_key)
|
||||
)
|
||||
stored_prototype.delete()
|
||||
return True
|
||||
|
||||
|
||||
def search_prototype(key=None, tags=None, require_single=False, return_iterators=False,
|
||||
no_db=False):
|
||||
def search_prototype(
|
||||
key=None, tags=None, require_single=False, return_iterators=False, no_db=False
|
||||
):
|
||||
"""
|
||||
Find prototypes based on key and/or tags, or all prototypes.
|
||||
|
||||
|
|
@ -520,7 +533,7 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
|||
|
||||
# prototype keys are always in lowecase
|
||||
if key:
|
||||
key = key.lower()
|
||||
key = key.lower()
|
||||
|
||||
# search module prototypes
|
||||
|
||||
|
|
@ -587,10 +600,10 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
|||
nmodules = len(module_prototypes)
|
||||
ndbprots = db_matches.count() if db_matches else 0
|
||||
if nmodules + ndbprots != 1:
|
||||
raise KeyError(_(
|
||||
"Found {num} matching prototypes among {module_prototypes}.").format(
|
||||
num=nmodules + ndbprots,
|
||||
module_prototypes=module_prototypes)
|
||||
raise KeyError(
|
||||
_("Found {num} matching prototypes among {module_prototypes}.").format(
|
||||
num=nmodules + ndbprots, module_prototypes=module_prototypes
|
||||
)
|
||||
)
|
||||
|
||||
if return_iterators:
|
||||
|
|
@ -670,7 +683,7 @@ class PrototypeEvMore(EvMore):
|
|||
else:
|
||||
# get the correct slice, adjusted for the db-prototypes
|
||||
pageno = max(0, pageno - self._npages_db)
|
||||
return modprot_list[pageno * self.height: pageno * self.height + self.height]
|
||||
return modprot_list[pageno * self.height : pageno * self.height + self.height]
|
||||
|
||||
def page_formatter(self, page):
|
||||
"""
|
||||
|
|
@ -809,12 +822,15 @@ def validate_prototype(
|
|||
if is_prototype_base:
|
||||
_flags["errors"].append(
|
||||
_("Prototype {protkey} requires `typeclass` " "or 'prototype_parent'.").format(
|
||||
protkey=protkey)
|
||||
protkey=protkey
|
||||
)
|
||||
)
|
||||
else:
|
||||
_flags["warnings"].append(
|
||||
_("Prototype {protkey} can only be used as a mixin since it lacks "
|
||||
"'typeclass' or 'prototype_parent' keys.").format(protkey=protkey)
|
||||
_(
|
||||
"Prototype {protkey} can only be used as a mixin since it lacks "
|
||||
"'typeclass' or 'prototype_parent' keys."
|
||||
).format(protkey=protkey)
|
||||
)
|
||||
|
||||
if strict and typeclass:
|
||||
|
|
@ -822,9 +838,10 @@ def validate_prototype(
|
|||
class_from_module(typeclass)
|
||||
except ImportError as err:
|
||||
_flags["errors"].append(
|
||||
_("{err}: Prototype {protkey} is based on typeclass {typeclass}, "
|
||||
"which could not be imported!").format(
|
||||
err=err, protkey=protkey, typeclass=typeclass)
|
||||
_(
|
||||
"{err}: Prototype {protkey} is based on typeclass {typeclass}, "
|
||||
"which could not be imported!"
|
||||
).format(err=err, protkey=protkey, typeclass=typeclass)
|
||||
)
|
||||
|
||||
if prototype_parent and isinstance(prototype_parent, dict):
|
||||
|
|
@ -840,20 +857,24 @@ def validate_prototype(
|
|||
else:
|
||||
protstring = protstring.lower()
|
||||
if protkey is not None and protstring == protkey:
|
||||
_flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format(
|
||||
protkey=protkey))
|
||||
_flags["errors"].append(
|
||||
_("Prototype {protkey} tries to parent itself.").format(protkey=protkey)
|
||||
)
|
||||
protparent = protparents.get(protstring)
|
||||
if not protparent:
|
||||
_flags["errors"].append(
|
||||
_("Prototype {protkey}'s `prototype_parent` (named '{parent}') "
|
||||
"was not found.").format(protkey=protkey, parent=protstring)
|
||||
_(
|
||||
"Prototype {protkey}'s `prototype_parent` (named '{parent}') "
|
||||
"was not found."
|
||||
).format(protkey=protkey, parent=protstring)
|
||||
)
|
||||
|
||||
# check for infinite recursion
|
||||
if id(prototype) in _flags["visited"]:
|
||||
_flags["errors"].append(
|
||||
_("{protkey} has infinite nesting of prototypes.").format(
|
||||
protkey=protkey or prototype)
|
||||
protkey=protkey or prototype
|
||||
)
|
||||
)
|
||||
|
||||
if _flags["errors"]:
|
||||
|
|
@ -875,9 +896,11 @@ def validate_prototype(
|
|||
# if we get back to the current level without a typeclass it's an error.
|
||||
if strict and is_prototype_base and _flags["depth"] <= 0 and not _flags["typeclass"]:
|
||||
_flags["errors"].append(
|
||||
_("Prototype {protkey} has no `typeclass` defined anywhere in its parent\n "
|
||||
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||
"prototype with a typeclass.").format(protkey=protkey)
|
||||
_(
|
||||
"Prototype {protkey} has no `typeclass` defined anywhere in its parent\n "
|
||||
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||
"prototype with a typeclass."
|
||||
).format(protkey=protkey)
|
||||
)
|
||||
|
||||
if _flags["depth"] <= 0:
|
||||
|
|
@ -901,8 +924,9 @@ def validate_prototype(
|
|||
prototype["prototype_locks"] = prototype_locks
|
||||
|
||||
|
||||
def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False,
|
||||
caller=None, **kwargs):
|
||||
def protfunc_parser(
|
||||
value, available_functions=None, testing=False, stacktrace=False, caller=None, **kwargs
|
||||
):
|
||||
"""
|
||||
Parse a prototype value string for a protfunc and process it.
|
||||
|
||||
|
|
@ -1129,8 +1153,9 @@ def value_to_obj(value, force=True):
|
|||
stype = type(value)
|
||||
if is_iter(value):
|
||||
if stype == dict:
|
||||
return {value_to_obj_or_any(key): value_to_obj_or_any(val)
|
||||
for key, val in value.items()}
|
||||
return {
|
||||
value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.items()
|
||||
}
|
||||
else:
|
||||
return stype([value_to_obj_or_any(val) for val in value])
|
||||
return dbid_to_obj(value, ObjectDB)
|
||||
|
|
|
|||
|
|
@ -154,8 +154,13 @@ from evennia.prototypes.prototypes import (
|
|||
|
||||
|
||||
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
|
||||
_PROTOTYPE_META_NAMES = ("prototype_key", "prototype_desc", "prototype_tags",
|
||||
"prototype_locks", "prototype_parent")
|
||||
_PROTOTYPE_META_NAMES = (
|
||||
"prototype_key",
|
||||
"prototype_desc",
|
||||
"prototype_tags",
|
||||
"prototype_locks",
|
||||
"prototype_parent",
|
||||
)
|
||||
_PROTOTYPE_ROOT_NAMES = (
|
||||
"typeclass",
|
||||
"key",
|
||||
|
|
@ -235,9 +240,7 @@ def _get_prototype(inprot, protparents, uninherited=None, _workprot=None):
|
|||
parent_prototype = protparents.get(prototype.lower(), {})
|
||||
|
||||
# Build the prot dictionary in reverse order, overloading
|
||||
new_prot = _get_prototype(
|
||||
parent_prototype, protparents, _workprot=_workprot
|
||||
)
|
||||
new_prot = _get_prototype(parent_prototype, protparents, _workprot=_workprot)
|
||||
|
||||
# attrs, tags have internal structure that should be inherited separately
|
||||
new_prot["attrs"] = _inherit_attrs(
|
||||
|
|
@ -276,8 +279,9 @@ def flatten_prototype(prototype, validate=False, no_db=False):
|
|||
|
||||
if prototype:
|
||||
prototype = protlib.homogenize_prototype(prototype)
|
||||
protparents = {prot["prototype_key"].lower(): prot
|
||||
for prot in protlib.search_prototype(no_db=no_db)}
|
||||
protparents = {
|
||||
prot["prototype_key"].lower(): prot for prot in protlib.search_prototype(no_db=no_db)
|
||||
}
|
||||
protlib.validate_prototype(
|
||||
prototype, None, protparents, is_prototype_base=validate, strict=validate
|
||||
)
|
||||
|
|
@ -342,7 +346,7 @@ def prototype_from_object(obj):
|
|||
prot["aliases"] = aliases
|
||||
tags = sorted(
|
||||
[(tag.db_key, tag.db_category, tag.db_data) for tag in obj.tags.all(return_objs=True)],
|
||||
key=lambda tup: (str(tup[0]), tup[1] or '', tup[2] or '')
|
||||
key=lambda tup: (str(tup[0]), tup[1] or "", tup[2] or ""),
|
||||
)
|
||||
if tags:
|
||||
prot["tags"] = tags
|
||||
|
|
@ -351,7 +355,7 @@ def prototype_from_object(obj):
|
|||
(attr.key, attr.value, attr.category, ";".join(attr.locks.all()))
|
||||
for attr in obj.attributes.all()
|
||||
],
|
||||
key=lambda tup: (str(tup[0]), tup[1] or '', tup[2] or '', tup[3])
|
||||
key=lambda tup: (str(tup[0]), tup[1] or "", tup[2] or "", tup[3]),
|
||||
)
|
||||
if attrs:
|
||||
prot["attrs"] = attrs
|
||||
|
|
@ -489,8 +493,10 @@ def flatten_diff(diff):
|
|||
out.extend(_get_all_nested_diff_instructions(val))
|
||||
else:
|
||||
raise RuntimeError(
|
||||
_("Diff contains non-dicts that are not on the "
|
||||
"form (old, new, action_to_take): {diffpart}").format(diffpart)
|
||||
_(
|
||||
"Diff contains non-dicts that are not on the "
|
||||
"form (old, new, action_to_take): {diffpart}"
|
||||
).format(diffpart)
|
||||
)
|
||||
return out
|
||||
|
||||
|
|
@ -627,8 +633,9 @@ def format_diff(diff, minimal=True):
|
|||
return "\n ".join(line for line in texts if line)
|
||||
|
||||
|
||||
def batch_update_objects_with_prototype(prototype, diff=None, objects=None,
|
||||
exact=False, caller=None):
|
||||
def batch_update_objects_with_prototype(
|
||||
prototype, diff=None, objects=None, exact=False, caller=None
|
||||
):
|
||||
"""
|
||||
Update existing objects with the latest version of the prototype.
|
||||
|
||||
|
|
@ -941,27 +948,32 @@ def spawn(*prototypes, caller=None, **kwargs):
|
|||
|
||||
val = prot.pop("location", None)
|
||||
create_kwargs["db_location"] = init_spawn_value(
|
||||
val, value_to_obj, caller=caller, prototype=prototype)
|
||||
val, value_to_obj, caller=caller, prototype=prototype
|
||||
)
|
||||
|
||||
val = prot.pop("home", None)
|
||||
if val:
|
||||
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, caller=caller,
|
||||
prototype=prototype)
|
||||
create_kwargs["db_home"] = init_spawn_value(
|
||||
val, value_to_obj, caller=caller, prototype=prototype
|
||||
)
|
||||
else:
|
||||
try:
|
||||
create_kwargs["db_home"] = init_spawn_value(
|
||||
settings.DEFAULT_HOME, value_to_obj, caller=caller, prototype=prototype)
|
||||
settings.DEFAULT_HOME, value_to_obj, caller=caller, prototype=prototype
|
||||
)
|
||||
except ObjectDB.DoesNotExist:
|
||||
# settings.DEFAULT_HOME not existing is common for unittests
|
||||
pass
|
||||
|
||||
val = prot.pop("destination", None)
|
||||
create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj, caller=caller,
|
||||
prototype=prototype)
|
||||
create_kwargs["db_destination"] = init_spawn_value(
|
||||
val, value_to_obj, caller=caller, prototype=prototype
|
||||
)
|
||||
|
||||
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
||||
create_kwargs["db_typeclass_path"] = init_spawn_value(val, str, caller=caller,
|
||||
prototype=prototype)
|
||||
create_kwargs["db_typeclass_path"] = init_spawn_value(
|
||||
val, str, caller=caller, prototype=prototype
|
||||
)
|
||||
|
||||
# extract calls to handlers
|
||||
val = prot.pop("permissions", [])
|
||||
|
|
@ -974,8 +986,13 @@ def spawn(*prototypes, caller=None, **kwargs):
|
|||
val = prot.pop("tags", [])
|
||||
tags = []
|
||||
for (tag, category, *data) in val:
|
||||
tags.append((init_spawn_value(tag, str, caller=caller, prototype=prototype),
|
||||
category, data[0] if data else None))
|
||||
tags.append(
|
||||
(
|
||||
init_spawn_value(tag, str, caller=caller, prototype=prototype),
|
||||
category,
|
||||
data[0] if data else None,
|
||||
)
|
||||
)
|
||||
|
||||
prototype_key = prototype.get("prototype_key", None)
|
||||
if prototype_key:
|
||||
|
|
@ -987,8 +1004,10 @@ def spawn(*prototypes, caller=None, **kwargs):
|
|||
|
||||
# extract ndb assignments
|
||||
nattributes = dict(
|
||||
(key.split("_", 1)[1], init_spawn_value(val, value_to_obj, caller=caller,
|
||||
prototype=prototype))
|
||||
(
|
||||
key.split("_", 1)[1],
|
||||
init_spawn_value(val, value_to_obj, caller=caller, prototype=prototype),
|
||||
)
|
||||
for key, val in prot.items()
|
||||
if key.startswith("ndb_")
|
||||
)
|
||||
|
|
@ -998,8 +1017,13 @@ def spawn(*prototypes, caller=None, **kwargs):
|
|||
attributes = []
|
||||
for (attrname, value, *rest) in val:
|
||||
attributes.append(
|
||||
(attrname, init_spawn_value(value, caller=caller, prototype=prototype),
|
||||
rest[0] if rest else None, rest[1] if len(rest) > 1 else None))
|
||||
(
|
||||
attrname,
|
||||
init_spawn_value(value, caller=caller, prototype=prototype),
|
||||
rest[0] if rest else None,
|
||||
rest[1] if len(rest) > 1 else None,
|
||||
)
|
||||
)
|
||||
|
||||
simple_attributes = []
|
||||
for key, value in (
|
||||
|
|
@ -1010,8 +1034,14 @@ def spawn(*prototypes, caller=None, **kwargs):
|
|||
continue
|
||||
else:
|
||||
simple_attributes.append(
|
||||
(key, init_spawn_value(value, value_to_obj_or_any, caller=caller,
|
||||
prototype=prototype), None, None)
|
||||
(
|
||||
key,
|
||||
init_spawn_value(
|
||||
value, value_to_obj_or_any, caller=caller, prototype=prototype
|
||||
),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
||||
attributes = attributes + simple_attributes
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ _PROTPARENTS = {
|
|||
},
|
||||
}
|
||||
|
||||
|
||||
class TestSpawner(BaseEvenniaTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
|
@ -340,7 +341,6 @@ class TestProtLib(BaseEvenniaTest):
|
|||
|
||||
|
||||
class TestProtFuncs(BaseEvenniaTest):
|
||||
|
||||
@override_settings(PROT_FUNC_MODULES=["evennia.prototypes.protfuncs"])
|
||||
def test_protkey_protfunc(self):
|
||||
test_prot = {"key1": "value1", "key2": 2}
|
||||
|
|
@ -350,8 +350,7 @@ class TestProtFuncs(BaseEvenniaTest):
|
|||
"value1",
|
||||
)
|
||||
self.assertEqual(
|
||||
protlib.protfunc_parser("$protkey(key2)", testing=True, prototype=test_prot),
|
||||
2
|
||||
protlib.protfunc_parser("$protkey(key2)", testing=True, prototype=test_prot), 2
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -908,6 +907,7 @@ class Test2474(BaseEvenniaTest):
|
|||
that of its prototype_parent.
|
||||
|
||||
"""
|
||||
|
||||
prototypes = {
|
||||
"WEAPON": {
|
||||
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||
|
|
@ -951,14 +951,14 @@ class TestPartialTagAttributes(BaseEvenniaTest):
|
|||
def setUp(self):
|
||||
super().setUp()
|
||||
self.prot = {
|
||||
'prototype_key': 'rock',
|
||||
'typeclass': 'evennia.objects.objects.DefaultObject',
|
||||
'key': 'a rock',
|
||||
'tags': [('quantity', 'groupable')], # missing data field
|
||||
'attrs': [('quantity', 1)], # missing category and lock fields
|
||||
'desc': 'A good way to get stoned.'
|
||||
"prototype_key": "rock",
|
||||
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||
"key": "a rock",
|
||||
"tags": [("quantity", "groupable")], # missing data field
|
||||
"attrs": [("quantity", 1)], # missing category and lock fields
|
||||
"desc": "A good way to get stoned.",
|
||||
}
|
||||
|
||||
def test_partial_spawn(self):
|
||||
obj = spawner.spawn(self.prot)
|
||||
self.assertEqual(obj[0].key, self.prot['key'])
|
||||
self.assertEqual(obj[0].key, self.prot["key"])
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ class MonitorHandler(object):
|
|||
"""
|
||||
# if this an Attribute with a category we should differentiate
|
||||
fieldname = self._attr_category_fieldname(
|
||||
fieldname, obj.db_category
|
||||
if fieldname == "db_value" and hasattr(obj, "db_category") else None
|
||||
fieldname,
|
||||
obj.db_category if fieldname == "db_value" and hasattr(obj, "db_category") else None,
|
||||
)
|
||||
|
||||
to_delete = []
|
||||
|
|
@ -124,8 +124,7 @@ class MonitorHandler(object):
|
|||
for (obj, fieldname, idstring) in to_delete:
|
||||
del self.monitors[obj][fieldname][idstring]
|
||||
|
||||
def add(self, obj, fieldname, callback, idstring="", persistent=False,
|
||||
category=None, **kwargs):
|
||||
def add(self, obj, fieldname, callback, idstring="", persistent=False, category=None, **kwargs):
|
||||
"""
|
||||
Add monitoring to a given field or Attribute. A field must
|
||||
be specified with the full db_* name or it will be assumed
|
||||
|
|
|
|||
|
|
@ -49,10 +49,12 @@ class ScriptHandler(object):
|
|||
except Exception:
|
||||
next_repeat = "?"
|
||||
string += _("\n '{key}' ({next_repeat}/{interval}, {repeats} repeats): {desc}").format(
|
||||
key=script.key, next_repeat=next_repeat,
|
||||
key=script.key,
|
||||
next_repeat=next_repeat,
|
||||
interval=interval,
|
||||
repeats=repeats,
|
||||
desc=script.desc)
|
||||
desc=script.desc,
|
||||
)
|
||||
return string.strip()
|
||||
|
||||
def add(self, scriptclass, key=None, autostart=True):
|
||||
|
|
|
|||
|
|
@ -85,5 +85,5 @@ class TestExtendedLoopingCall(TestCase):
|
|||
loopcall.start(20, now=False, start_delay=10, count_start=1)
|
||||
|
||||
loopcall.__call__.assert_not_called()
|
||||
self.assertEqual(loopcall.interval , 20)
|
||||
self.assertEqual(loopcall.interval, 20)
|
||||
loopcall._scheduleFrom.assert_called_with(121)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ a text-game, and if you want to update some property, consider doing so
|
|||
on-demand rather than using a ticker.
|
||||
"""
|
||||
|
||||
|
||||
class Ticker(object):
|
||||
"""
|
||||
Represents a repeatedly running task that calls
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue