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):
|
def _rst2md(filename_rst):
|
||||||
|
|
||||||
with open(filename_rst, 'r') as fil:
|
with open(filename_rst, "r") as fil:
|
||||||
# read rst file, reformat and save
|
# read rst file, reformat and save
|
||||||
txt = fil.read()
|
txt = fil.read()
|
||||||
with open(filename_rst, 'w') as fil:
|
with open(filename_rst, "w") as fil:
|
||||||
txt = "```{eval-rst}\n" + txt + "\n```"
|
txt = "```{eval-rst}\n" + txt + "\n```"
|
||||||
fil.write(txt)
|
fil.write(txt)
|
||||||
|
|
||||||
# rename .rst file to .md file
|
# rename .rst file to .md file
|
||||||
filename, _ = filename_rst.rsplit('.', 1)
|
filename, _ = filename_rst.rsplit(".", 1)
|
||||||
filename_md = filename + ".md"
|
filename_md = filename + ".md"
|
||||||
rename(filename_rst, filename_md)
|
rename(filename_rst, filename_md)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ def auto_link_remapper(no_autodoc=False):
|
||||||
|
|
||||||
for strip_prefix in _STRIP_PREFIX:
|
for strip_prefix in _STRIP_PREFIX:
|
||||||
if url.startswith(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):
|
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
|
||||||
# skip regular http/s urls etc
|
# skip regular http/s urls etc
|
||||||
|
|
@ -157,10 +157,10 @@ def auto_link_remapper(no_autodoc=False):
|
||||||
|
|
||||||
if url.startswith("evennia."):
|
if url.startswith("evennia."):
|
||||||
# api link - we want to remove legacy #reference and remove .md
|
# api link - we want to remove legacy #reference and remove .md
|
||||||
if '#' in url:
|
if "#" in url:
|
||||||
_, url = url.rsplit('#', 1)
|
_, url = url.rsplit("#", 1)
|
||||||
if url.endswith(".md"):
|
if url.endswith(".md"):
|
||||||
url, _ = url.rsplit('.', 1)
|
url, _ = url.rsplit(".", 1)
|
||||||
return f"[{txt}]({url})"
|
return f"[{txt}]({url})"
|
||||||
|
|
||||||
fname, *part = url.rsplit("/", 1)
|
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]:
|
if _CURRFILE in docref_map and fname in docref_map[_CURRFILE]:
|
||||||
cfilename = _CURRFILE.rsplit("/", 1)[-1]
|
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:
|
if urlout != url:
|
||||||
print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})")
|
print(f" {cfilename}: [{txt}]({url}) -> [{txt}]({urlout})")
|
||||||
else:
|
else:
|
||||||
|
|
@ -193,7 +195,7 @@ def auto_link_remapper(no_autodoc=False):
|
||||||
|
|
||||||
for strip_prefix in _STRIP_PREFIX:
|
for strip_prefix in _STRIP_PREFIX:
|
||||||
if url.startswith(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):
|
if any(url.startswith(noremap) for noremap in _NO_REMAP_STARTSWITH):
|
||||||
return f"[{txt}]: {url}"
|
return f"[{txt}]: {url}"
|
||||||
|
|
@ -202,8 +204,8 @@ def auto_link_remapper(no_autodoc=False):
|
||||||
urlout = url
|
urlout = url
|
||||||
elif url.startswith("evennia."):
|
elif url.startswith("evennia."):
|
||||||
# api link - we want to remove legacy #reference
|
# api link - we want to remove legacy #reference
|
||||||
if '#' in url:
|
if "#" in url:
|
||||||
_, urlout = url.rsplit('#', 1)
|
_, urlout = url.rsplit("#", 1)
|
||||||
else:
|
else:
|
||||||
fname, *part = url.rsplit("/", 1)
|
fname, *part = url.rsplit("/", 1)
|
||||||
fname = part[0] if part else fname
|
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": """
|
"utils": """
|
||||||
Miscellaneous, optional tools for manipulating text, auditing connections
|
Miscellaneous, optional tools for manipulating text, auditing connections
|
||||||
and more.
|
and more.
|
||||||
"""
|
""",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_FILENAME_MAP = {
|
_FILENAME_MAP = {"rpsystem": "RPSystem", "xyzgrid": "XYZGrid", "awsstorage": "AWSStorage"}
|
||||||
"rpsystem": "RPSystem",
|
|
||||||
"xyzgrid": "XYZGrid",
|
|
||||||
"awsstorage": "AWSStorage"
|
|
||||||
}
|
|
||||||
|
|
||||||
HEADER = """# Contribs
|
HEADER = """# Contribs
|
||||||
|
|
||||||
|
|
@ -145,10 +141,14 @@ def readmes2docs(directory=_SOURCE_DIR):
|
||||||
|
|
||||||
pypath = f"evennia.contrib.{category}.{name}"
|
pypath = f"evennia.contrib.{category}.{name}"
|
||||||
|
|
||||||
filename = "Contrib-" + "-".join(
|
filename = (
|
||||||
_FILENAME_MAP.get(
|
"Contrib-"
|
||||||
part, part.capitalize() if part[0].islower() else part)
|
+ "-".join(
|
||||||
for part in name.split("_")) + ".md"
|
_FILENAME_MAP.get(part, part.capitalize() if part[0].islower() else part)
|
||||||
|
for part in name.split("_")
|
||||||
|
)
|
||||||
|
+ ".md"
|
||||||
|
)
|
||||||
outfile = pathjoin(_OUT_DIR, filename)
|
outfile = pathjoin(_OUT_DIR, filename)
|
||||||
|
|
||||||
with open(file_path) as fil:
|
with open(file_path) as fil:
|
||||||
|
|
@ -163,7 +163,7 @@ def readmes2docs(directory=_SOURCE_DIR):
|
||||||
except IndexError:
|
except IndexError:
|
||||||
blurb = name
|
blurb = name
|
||||||
|
|
||||||
with open(outfile, 'w') as fil:
|
with open(outfile, "w") as fil:
|
||||||
fil.write(data)
|
fil.write(data)
|
||||||
|
|
||||||
categories[category].append((name, credits, blurb, filename, pypath))
|
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()):
|
for tup in sorted(contrib_tups, key=lambda tup: tup[0].lower()):
|
||||||
catlines.append(
|
catlines.append(
|
||||||
BLURB.format(
|
BLURB.format(
|
||||||
name=tup[0],
|
name=tup[0], credits=tup[1], blurb=tup[2], filename=tup[3], code_location=tup[4]
|
||||||
credits=tup[1],
|
|
||||||
blurb=tup[2],
|
|
||||||
filename=tup[3],
|
|
||||||
code_location=tup[4]
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
filenames.append(f"{tup[3]}")
|
filenames.append(f"{tup[3]}")
|
||||||
|
|
@ -193,17 +189,15 @@ def readmes2docs(directory=_SOURCE_DIR):
|
||||||
category=category,
|
category=category,
|
||||||
category_desc=_CATEGORY_DESCS[category].strip(),
|
category_desc=_CATEGORY_DESCS[category].strip(),
|
||||||
blurbs="\n".join(catlines),
|
blurbs="\n".join(catlines),
|
||||||
toctree=toctree
|
toctree=toctree,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
text = _FILE_STRUCTURE.format(
|
text = _FILE_STRUCTURE.format(
|
||||||
header=HEADER,
|
header=HEADER, categories="\n".join(category_sections), footer=INDEX_FOOTER
|
||||||
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)
|
fil.write(text)
|
||||||
|
|
||||||
print(f" -- Converted Contrib READMEs to {ncount} doc pages + index.")
|
print(f" -- Converted Contrib READMEs to {ncount} doc pages + index.")
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,11 @@ if __name__ == "__main__":
|
||||||
filepaths = glob.glob(args.files, recursive=True)
|
filepaths = glob.glob(args.files, recursive=True)
|
||||||
width = args.width
|
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
|
count = 0
|
||||||
for filepath in filepaths:
|
for filepath in filepaths:
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,9 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
from os.path import dirname, abspath, join as pathjoin
|
from os.path import dirname, abspath, join as pathjoin
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import mod_import, variable_from_module, callables_from_module
|
||||||
mod_import, variable_from_module, callables_from_module
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = ("run_update")
|
__all__ = "run_update"
|
||||||
|
|
||||||
|
|
||||||
PAGE = """
|
PAGE = """
|
||||||
|
|
@ -33,6 +31,7 @@ with [EvEditor](EvEditor), flipping pages in [EvMore](EvMore) or using the
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def run_update(no_autodoc=False):
|
def run_update(no_autodoc=False):
|
||||||
|
|
||||||
if no_autodoc:
|
if no_autodoc:
|
||||||
|
|
@ -71,7 +70,8 @@ def run_update(no_autodoc=False):
|
||||||
for modname in cmd_modules:
|
for modname in cmd_modules:
|
||||||
module = mod_import(modname)
|
module = mod_import(modname)
|
||||||
cmds_per_module[module] = [
|
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]:
|
for cmd in cmds_per_module[module]:
|
||||||
cmd_to_module_map[cmd] = module
|
cmd_to_module_map[cmd] = module
|
||||||
cmds_alphabetically.append(cmd)
|
cmds_alphabetically.append(cmd)
|
||||||
|
|
@ -79,8 +79,9 @@ def run_update(no_autodoc=False):
|
||||||
|
|
||||||
cmd_infos = []
|
cmd_infos = []
|
||||||
for cmd in cmds_alphabetically:
|
for cmd in cmds_alphabetically:
|
||||||
aliases = [alias[1:] if alias and alias[0] == "@" else alias
|
aliases = [
|
||||||
for alias in sorted(cmd.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 ""
|
aliases = f" [{', '.join(sorted(cmd.aliases))}]" if aliases else ""
|
||||||
cmdlink = f"[**{cmd.key}**{aliases}]({cmd.__module__}.{cmd.__name__})"
|
cmdlink = f"[**{cmd.key}**{aliases}]({cmd.__module__}.{cmd.__name__})"
|
||||||
category = f"help-category: _{cmd.help_category.capitalize()}_"
|
category = f"help-category: _{cmd.help_category.capitalize()}_"
|
||||||
|
|
@ -98,12 +99,13 @@ def run_update(no_autodoc=False):
|
||||||
txt = PAGE.format(
|
txt = PAGE.format(
|
||||||
ncommands=len(cmd_to_cmdset_map),
|
ncommands=len(cmd_to_cmdset_map),
|
||||||
nfiles=len(cmds_per_module),
|
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")
|
outdir = pathjoin(dirname(dirname(abspath(__file__))), "source", "Components")
|
||||||
fname = pathjoin(outdir, "Default-Commands.md")
|
fname = pathjoin(outdir, "Default-Commands.md")
|
||||||
|
|
||||||
with open(fname, 'w') as fil:
|
with open(fname, "w") as fil:
|
||||||
fil.write(txt)
|
fil.write(txt)
|
||||||
|
|
||||||
print(" -- Updated Default Command index.")
|
print(" -- Updated Default Command index.")
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ DOCDIR = pathjoin(ROOTDIR, "docs")
|
||||||
DOCSRCDIR = pathjoin(DOCDIR, "source")
|
DOCSRCDIR = pathjoin(DOCDIR, "source")
|
||||||
EVENNIADIR = pathjoin(ROOTDIR, "evennia")
|
EVENNIADIR = pathjoin(ROOTDIR, "evennia")
|
||||||
|
|
||||||
|
|
||||||
def update_changelog():
|
def update_changelog():
|
||||||
"""
|
"""
|
||||||
Plain CHANGELOG copy
|
Plain CHANGELOG copy
|
||||||
|
|
@ -22,7 +23,7 @@ def update_changelog():
|
||||||
with open(sourcefile) as fil:
|
with open(sourcefile) as fil:
|
||||||
txt = fil.read()
|
txt = fil.read()
|
||||||
|
|
||||||
with open(targetfile, 'w') as fil:
|
with open(targetfile, "w") as fil:
|
||||||
fil.write(txt)
|
fil.write(txt)
|
||||||
|
|
||||||
print(" -- Updated Changelog.md")
|
print(" -- Updated Changelog.md")
|
||||||
|
|
@ -62,7 +63,7 @@ if settings.SERVERNAME == "Evennia":
|
||||||
{txt}
|
{txt}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
with open(targetfile, 'w') as fil:
|
with open(targetfile, "w") as fil:
|
||||||
fil.write(txt)
|
fil.write(txt)
|
||||||
|
|
||||||
print(" -- Updated Settings-Default.md")
|
print(" -- Updated Settings-Default.md")
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# from recommonmark.transform import AutoStructify
|
# from recommonmark.transform import AutoStructify
|
||||||
from sphinx.util.osutil import cd
|
from sphinx.util.osutil import cd
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ extensions = [
|
||||||
"sphinx.ext.viewcode",
|
"sphinx.ext.viewcode",
|
||||||
"sphinx.ext.todo",
|
"sphinx.ext.todo",
|
||||||
"sphinx.ext.githubpages",
|
"sphinx.ext.githubpages",
|
||||||
"myst_parser"
|
"myst_parser",
|
||||||
]
|
]
|
||||||
|
|
||||||
source_suffix = [".md", ".rst"]
|
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_doc_root = "https://github.com/evennia/tree/master/docs/sources/"
|
||||||
_github_issue_choose = "https://github.com/evennia/evennia/issues/new/choose"
|
_github_issue_choose = "https://github.com/evennia/evennia/issues/new/choose"
|
||||||
_ref_regex = re.compile( # normal reference-links [txt](url)
|
_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
|
_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):
|
def url_resolver(app, docname, source):
|
||||||
|
|
@ -165,10 +168,11 @@ def url_resolver(app, docname, source):
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _url_remap(url):
|
def _url_remap(url):
|
||||||
|
|
||||||
# determine depth in tree of current document
|
# determine depth in tree of current document
|
||||||
docdepth = docname.count('/') + 1
|
docdepth = docname.count("/") + 1
|
||||||
relative_path = "../".join("" for _ in range(docdepth))
|
relative_path = "../".join("" for _ in range(docdepth))
|
||||||
|
|
||||||
if url.endswith(_choose_issue):
|
if url.endswith(_choose_issue):
|
||||||
|
|
@ -176,14 +180,14 @@ def url_resolver(app, docname, source):
|
||||||
return _github_issue_choose
|
return _github_issue_choose
|
||||||
elif _githubstart in url:
|
elif _githubstart in url:
|
||||||
# github:develop/... shortcut
|
# 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")):
|
if not (urlpath.startswith("develop/") or urlpath.startswith("master")):
|
||||||
urlpath = "master/" + urlpath
|
urlpath = "master/" + urlpath
|
||||||
return _github_code_root + urlpath
|
return _github_code_root + urlpath
|
||||||
elif _sourcestart in url:
|
elif _sourcestart in url:
|
||||||
ind = url.index(_sourcestart)
|
ind = url.index(_sourcestart)
|
||||||
|
|
||||||
modpath, *inmodule = url[ind + len(_sourcestart):].rsplit("#", 1)
|
modpath, *inmodule = url[ind + len(_sourcestart) :].rsplit("#", 1)
|
||||||
modpath = "/".join(modpath.split("."))
|
modpath = "/".join(modpath.split("."))
|
||||||
inmodule = "#" + inmodule[0] if inmodule else ""
|
inmodule = "#" + inmodule[0] if inmodule else ""
|
||||||
modpath = modpath + ".html" + inmodule
|
modpath = modpath + ".html" + inmodule
|
||||||
|
|
@ -194,13 +198,13 @@ def url_resolver(app, docname, source):
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def _re_ref_sub(match):
|
def _re_ref_sub(match):
|
||||||
txt = match.group('txt')
|
txt = match.group("txt")
|
||||||
url = _url_remap(match.group('url'))
|
url = _url_remap(match.group("url"))
|
||||||
return f"[{txt}]({url})"
|
return f"[{txt}]({url})"
|
||||||
|
|
||||||
def _re_docref_sub(match):
|
def _re_docref_sub(match):
|
||||||
txt = match.group('txt')
|
txt = match.group("txt")
|
||||||
url = _url_remap(match.group('url'))
|
url = _url_remap(match.group("url"))
|
||||||
return f"[{txt}]: {url}"
|
return f"[{txt}]: {url}"
|
||||||
|
|
||||||
src = source[0]
|
src = source[0]
|
||||||
|
|
@ -248,7 +252,7 @@ autodoc_default_options = {
|
||||||
"show-inheritance": True,
|
"show-inheritance": True,
|
||||||
"special-members": "__init__",
|
"special-members": "__init__",
|
||||||
"enable_eval_rst": True,
|
"enable_eval_rst": True,
|
||||||
"inherited_members": True
|
"inherited_members": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
autodoc_member_order = "bysource"
|
autodoc_member_order = "bysource"
|
||||||
|
|
@ -345,8 +349,12 @@ def setup(app):
|
||||||
|
|
||||||
# build toctree file
|
# build toctree file
|
||||||
sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.dirname(__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,
|
from docs.pylib import (
|
||||||
contrib_readmes2docs, update_dynamic_pages)
|
auto_link_remapper,
|
||||||
|
update_default_cmd_index,
|
||||||
|
contrib_readmes2docs,
|
||||||
|
update_dynamic_pages,
|
||||||
|
)
|
||||||
|
|
||||||
_no_autodoc = os.environ.get("NOAUTODOC")
|
_no_autodoc = os.environ.get("NOAUTODOC")
|
||||||
update_default_cmd_index.run_update(no_autodoc=_no_autodoc)
|
update_default_cmd_index.run_update(no_autodoc=_no_autodoc)
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ MONITOR_HANDLER = None
|
||||||
GLOBAL_SCRIPTS = None
|
GLOBAL_SCRIPTS = None
|
||||||
OPTION_CLASSES = None
|
OPTION_CLASSES = None
|
||||||
|
|
||||||
|
|
||||||
def _create_version():
|
def _create_version():
|
||||||
"""
|
"""
|
||||||
Helper function for building the version string
|
Helper function for building the version string
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,12 @@ _CMDHANDLER = None
|
||||||
|
|
||||||
# Create throttles for too many account-creations and login attempts
|
# Create throttles for too many account-creations and login attempts
|
||||||
CREATION_THROTTLE = Throttle(
|
CREATION_THROTTLE = Throttle(
|
||||||
name='creation', limit=settings.CREATION_THROTTLE_LIMIT,
|
name="creation",
|
||||||
timeout=settings.CREATION_THROTTLE_TIMEOUT
|
limit=settings.CREATION_THROTTLE_LIMIT,
|
||||||
|
timeout=settings.CREATION_THROTTLE_TIMEOUT,
|
||||||
)
|
)
|
||||||
LOGIN_THROTTLE = Throttle(
|
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:
|
except Exception:
|
||||||
errors.append(
|
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()
|
logger.log_trace()
|
||||||
return None, errors
|
return None, errors
|
||||||
|
|
||||||
|
|
@ -879,7 +883,6 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
super().delete(*args, **kwargs)
|
super().delete(*args, **kwargs)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
# methods inherited from database model
|
# methods inherited from database model
|
||||||
|
|
||||||
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
|
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()
|
sessions = self.sessions.get()
|
||||||
session = sessions[0] if sessions else None
|
session = sessions[0] if sessions else None
|
||||||
|
|
||||||
return _CMDHANDLER(
|
return _CMDHANDLER(self, raw_string, callertype="account", session=session, **kwargs)
|
||||||
self, raw_string, callertype="account", session=session, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
# channel receive hooks
|
# channel receive hooks
|
||||||
|
|
||||||
|
|
@ -1000,11 +1001,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if senders:
|
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()
|
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"
|
# 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:]}"
|
message = f"{sender_string}{spacing}{message_lstrip[1:]}"
|
||||||
else:
|
else:
|
||||||
# normal message
|
# normal message
|
||||||
|
|
@ -1035,8 +1036,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
to customize the message for the receiver on the channel-level.
|
to customize the message for the receiver on the channel-level.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.msg(text=(message, {"from_channel": channel.id}),
|
self.msg(
|
||||||
from_obj=senders, options={"from_channel": channel.id})
|
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):
|
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 _MUDINFO_CHANNEL is None:
|
||||||
if settings.CHANNEL_MUDINFO:
|
if settings.CHANNEL_MUDINFO:
|
||||||
try:
|
try:
|
||||||
_MUDINFO_CHANNEL = ChannelDB.objects.get(
|
_MUDINFO_CHANNEL = ChannelDB.objects.get(db_key=settings.CHANNEL_MUDINFO["key"])
|
||||||
db_key=settings.CHANNEL_MUDINFO["key"])
|
|
||||||
except ChannelDB.DoesNotExist:
|
except ChannelDB.DoesNotExist:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
else:
|
else:
|
||||||
|
|
@ -1383,7 +1386,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
if settings.CHANNEL_CONNECTINFO:
|
if settings.CHANNEL_CONNECTINFO:
|
||||||
try:
|
try:
|
||||||
_CONNECT_CHANNEL = ChannelDB.objects.get(
|
_CONNECT_CHANNEL = ChannelDB.objects.get(
|
||||||
db_key=settings.CHANNEL_CONNECTINFO["key"])
|
db_key=settings.CHANNEL_CONNECTINFO["key"]
|
||||||
|
)
|
||||||
except ChannelDB.DoesNotExist:
|
except ChannelDB.DoesNotExist:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
else:
|
else:
|
||||||
|
|
@ -1661,7 +1665,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
if sess and sid:
|
if sess and sid:
|
||||||
result.append(
|
result.append(
|
||||||
f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] "
|
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:
|
else:
|
||||||
result.append(
|
result.append(
|
||||||
f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] "
|
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})"
|
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
|
||||||
nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower()))
|
nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower()))
|
||||||
for obj in self._nicklist_callers:
|
for obj in self._nicklist_callers:
|
||||||
obj.msg(
|
obj.msg("Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist))
|
||||||
"Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist)
|
|
||||||
)
|
|
||||||
self._nicklist_callers = []
|
self._nicklist_callers = []
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -276,8 +276,11 @@ class AccountDBManager(TypedObjectManager, UserManager):
|
||||||
new_account.set_password(password)
|
new_account.set_password(password)
|
||||||
|
|
||||||
new_account._createdict = dict(
|
new_account._createdict = dict(
|
||||||
locks=locks, permissions=permissions,
|
locks=locks,
|
||||||
report_to=report_to, tags=tags, attributes=attributes
|
permissions=permissions,
|
||||||
|
report_to=report_to,
|
||||||
|
tags=tags,
|
||||||
|
attributes=attributes,
|
||||||
)
|
)
|
||||||
# saving will trigger the signal that calls the
|
# saving will trigger the signal that calls the
|
||||||
# at_first_save hook on the typeclass, where the _createdict
|
# 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.
|
# is the normal "production message to echo to the account.
|
||||||
|
|
||||||
_ERROR_UNTRAPPED = (
|
_ERROR_UNTRAPPED = (
|
||||||
_("""
|
_(
|
||||||
|
"""
|
||||||
An untrapped error occurred.
|
An untrapped error occurred.
|
||||||
"""),
|
"""
|
||||||
_("""
|
),
|
||||||
|
_(
|
||||||
|
"""
|
||||||
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
||||||
"""),
|
"""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_CMDSETS = (
|
_ERROR_CMDSETS = (
|
||||||
_("""
|
_(
|
||||||
|
"""
|
||||||
A cmdset merger-error occurred. This is often due to a syntax
|
A cmdset merger-error occurred. This is often due to a syntax
|
||||||
error in one of the cmdsets to merge.
|
error in one of the cmdsets to merge.
|
||||||
"""),
|
"""
|
||||||
_("""
|
),
|
||||||
|
_(
|
||||||
|
"""
|
||||||
A cmdset merger-error occurred. Please file a bug report detailing the
|
A cmdset merger-error occurred. Please file a bug report detailing the
|
||||||
steps to reproduce.
|
steps to reproduce.
|
||||||
"""),
|
"""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_NOCMDSETS = (
|
_ERROR_NOCMDSETS = (
|
||||||
_("""
|
_(
|
||||||
|
"""
|
||||||
No command sets found! This is a critical bug that can have
|
No command sets found! This is a critical bug that can have
|
||||||
multiple causes.
|
multiple causes.
|
||||||
"""),
|
"""
|
||||||
_("""
|
),
|
||||||
|
_(
|
||||||
|
"""
|
||||||
No command sets found! This is a sign of a critical bug. If
|
No command sets found! This is a sign of a critical bug. If
|
||||||
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
||||||
the server admin through" some other means for assistance.
|
the server admin through" some other means for assistance.
|
||||||
"""),
|
"""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_CMDHANDLER = (
|
_ERROR_CMDHANDLER = (
|
||||||
_("""
|
_(
|
||||||
|
"""
|
||||||
A command handler bug occurred. If this is not due to a local change,
|
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
|
please file a bug report with the Evennia project, including the
|
||||||
traceback and steps to reproduce.
|
traceback and steps to reproduce.
|
||||||
"""),
|
"""
|
||||||
_("""
|
),
|
||||||
|
_(
|
||||||
|
"""
|
||||||
A command handler bug occurred. Please notify staff - they should
|
A command handler bug occurred. Please notify staff - they should
|
||||||
likely file a bug report with the Evennia project.
|
likely file a bug report with the Evennia project.
|
||||||
"""),
|
"""
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_RECURSION_LIMIT = _(
|
_ERROR_RECURSION_LIMIT = _(
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ def build_matches(raw_string, cmdset, include_prefixes=False):
|
||||||
for cmdname in [cmd.key] + cmd.aliases
|
for cmdname in [cmd.key] + cmd.aliases
|
||||||
if cmdname
|
if cmdname
|
||||||
and l_raw_string.startswith(cmdname.lower())
|
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:
|
else:
|
||||||
|
|
@ -90,7 +90,7 @@ def build_matches(raw_string, cmdset, include_prefixes=False):
|
||||||
if (
|
if (
|
||||||
cmdname
|
cmdname
|
||||||
and l_raw_string.startswith(cmdname.lower())
|
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))
|
matches.append(create_match(cmdname, raw_string, cmd, raw_cmdname))
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -125,7 +125,10 @@ def try_num_differentiators(raw_string):
|
||||||
# the user might be trying to identify the command
|
# the user might be trying to identify the command
|
||||||
# with a #num-command style syntax. We expect the regex to
|
# with a #num-command style syntax. We expect the regex to
|
||||||
# contain the groups "number" and "name".
|
# 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
|
return int(mindex), new_raw_string
|
||||||
else:
|
else:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
@ -182,9 +185,7 @@ def cmdparser(raw_string, cmdset, caller, match_index=None):
|
||||||
|
|
||||||
if not matches and _CMD_IGNORE_PREFIXES:
|
if not matches and _CMD_IGNORE_PREFIXES:
|
||||||
# still no match. Try to strip prefixes
|
# still no match. Try to strip prefixes
|
||||||
raw_string = (
|
raw_string = raw_string.lstrip(_CMD_IGNORE_PREFIXES) if len(raw_string) > 1 else 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)
|
matches = build_matches(raw_string, cmdset, include_prefixes=False)
|
||||||
|
|
||||||
# only select command matches we are actually allowed to call.
|
# 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"
|
perm = "perm" if self.persistent else "non-perm"
|
||||||
options = ", ".join([
|
options = ", ".join(
|
||||||
"{}:{}".format(opt, "T" if getattr(self, opt) else "F")
|
[
|
||||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
"{}:{}".format(opt, "T" if getattr(self, opt) else "F")
|
||||||
if getattr(self, opt) is not None
|
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
||||||
])
|
if getattr(self, opt) is not None
|
||||||
|
]
|
||||||
|
)
|
||||||
options = (", " + options) if options else ""
|
options = (", " + options) if options else ""
|
||||||
return (f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: "
|
return (
|
||||||
+ ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)]))
|
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):
|
def __iter__(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -519,10 +523,12 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
||||||
try:
|
try:
|
||||||
cmdset = self._instantiate(cmdset)
|
cmdset = self._instantiate(cmdset)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
err = ("Adding cmdset {cmdset} to {cls} lead to an "
|
err = (
|
||||||
"infinite loop. When adding a cmdset to another, "
|
"Adding cmdset {cmdset} to {cls} lead to an "
|
||||||
"make sure they are not themself cyclically added to "
|
"infinite loop. When adding a cmdset to another, "
|
||||||
"the new cmdset somewhere in the chain.")
|
"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__)))
|
raise RuntimeError(_(err.format(cmdset=cmdset, cls=self.__class__)))
|
||||||
cmds = cmdset.commands
|
cmds = cmdset.commands
|
||||||
elif is_iter(cmd):
|
elif is_iter(cmd):
|
||||||
|
|
|
||||||
|
|
@ -423,8 +423,7 @@ class CmdSetHandler(object):
|
||||||
self.mergetype_stack.append(new_current.actual_mergetype)
|
self.mergetype_stack.append(new_current.actual_mergetype)
|
||||||
self.current = new_current
|
self.current = new_current
|
||||||
|
|
||||||
def add(self, cmdset, emit_to_obj=None, persistent=False, default_cmdset=False,
|
def add(self, cmdset, emit_to_obj=None, persistent=False, default_cmdset=False, **kwargs):
|
||||||
**kwargs):
|
|
||||||
"""
|
"""
|
||||||
Add a cmdset to the handler, on top of the old ones, unless it
|
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)
|
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:
|
if "permanent" in kwargs:
|
||||||
logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to "
|
logger.log_dep(
|
||||||
"'persistent' and now defaults to True.")
|
"obj.cmdset.add() kwarg 'permanent' has changed name to "
|
||||||
persistent = kwargs['permanent'] if persistent is None else persistent
|
"'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)):
|
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
||||||
string = _("Only CmdSets can be added to the cmdsethandler!")
|
string = _("Only CmdSets can be added to the cmdsethandler!")
|
||||||
|
|
@ -491,9 +492,10 @@ class CmdSetHandler(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if "permanent" in kwargs:
|
if "permanent" in kwargs:
|
||||||
logger.log_dep("obj.cmdset.add_default() kwarg 'permanent' has changed name to "
|
logger.log_dep(
|
||||||
"'persistent'.")
|
"obj.cmdset.add_default() kwarg 'permanent' has changed name to 'persistent'."
|
||||||
persistent = kwargs['permanent'] if persistent is None else persistent
|
)
|
||||||
|
persistent = kwargs["permanent"] if persistent is None else persistent
|
||||||
self.add(cmdset, emit_to_obj=emit_to_obj, persistent=persistent, default_cmdset=True)
|
self.add(cmdset, emit_to_obj=emit_to_obj, persistent=persistent, default_cmdset=True)
|
||||||
|
|
||||||
def remove(self, cmdset=None, default_cmdset=False):
|
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
|
# pre-prepare a help index entry for quicker lookup
|
||||||
# strip the @- etc to allow help to be agnostic
|
# 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_key = cls.key[1:] if cls.key and cls.key[0] in CMD_IGNORE_PREFIXES else ""
|
||||||
stripped_aliases = (
|
stripped_aliases = " ".join(
|
||||||
" ".join(al[1:] if al and al[0] in CMD_IGNORE_PREFIXES else al
|
al[1:] if al and al[0] in CMD_IGNORE_PREFIXES else al for al in cls.aliases
|
||||||
for al in cls.aliases))
|
)
|
||||||
cls.search_index_entry = {
|
cls.search_index_entry = {
|
||||||
"key": cls.key,
|
"key": cls.key,
|
||||||
"aliases": " ".join(cls.aliases),
|
"aliases": " ".join(cls.aliases),
|
||||||
"no_prefix": f"{stripped_key} {stripped_aliases}",
|
"no_prefix": f"{stripped_key} {stripped_aliases}",
|
||||||
"category": cls.help_category,
|
"category": cls.help_category,
|
||||||
"text": cls.__doc__,
|
"text": cls.__doc__,
|
||||||
"tags": ""
|
"tags": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -562,7 +562,7 @@ Command {self} has no defined `func()` - showing on-command variables:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return reverse(
|
return reverse(
|
||||||
'help-entry-detail',
|
"help-entry-detail",
|
||||||
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
|
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
ipregex = re.compile(r"%s" % ipregex)
|
ipregex = re.compile(r"%s" % ipregex)
|
||||||
bantup = ("", ban, ipregex, now, reason)
|
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"):
|
if str(ret).lower() in ("no", "n"):
|
||||||
self.caller.msg("Aborted.")
|
self.caller.msg("Aborted.")
|
||||||
return
|
return
|
||||||
|
|
@ -273,7 +273,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||||
ban = banlist[num - 1]
|
ban = banlist[num - 1]
|
||||||
value = (" ".join([s for s in ban[:2]])).strip()
|
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"):
|
if str(ret).lower() in ("n", "no"):
|
||||||
self.caller.msg("Aborted.")
|
self.caller.msg("Aborted.")
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@ from evennia.utils.utils import (
|
||||||
class_from_module,
|
class_from_module,
|
||||||
get_all_typeclasses,
|
get_all_typeclasses,
|
||||||
variable_from_module,
|
variable_from_module,
|
||||||
dbref, crop,
|
dbref,
|
||||||
|
crop,
|
||||||
interactive,
|
interactive,
|
||||||
list_to_string,
|
list_to_string,
|
||||||
display_len,
|
display_len,
|
||||||
|
|
@ -1498,9 +1499,11 @@ class CmdOpen(ObjManipCommand):
|
||||||
super().parse()
|
super().parse()
|
||||||
self.location = self.caller.location
|
self.location = self.caller.location
|
||||||
if not self.args or not self.rhs:
|
if not self.args or not self.rhs:
|
||||||
self.caller.msg("Usage: open <new exit>[;alias...][:typeclass]"
|
self.caller.msg(
|
||||||
"[,<return exit>[;alias..][:typeclass]]] "
|
"Usage: open <new exit>[;alias...][:typeclass]"
|
||||||
"= <destination>")
|
"[,<return exit>[;alias..][:typeclass]]] "
|
||||||
|
"= <destination>"
|
||||||
|
)
|
||||||
raise InterruptCommand
|
raise InterruptCommand
|
||||||
if not self.location:
|
if not self.location:
|
||||||
self.caller.msg("You cannot create an exit from a None-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.
|
as well as the self.create_exit() method.
|
||||||
"""
|
"""
|
||||||
# Create exit
|
# Create exit
|
||||||
ok = self.create_exit(self.exit_name, self.location, self.destination,
|
ok = self.create_exit(
|
||||||
self.exit_aliases, self.exit_typeclass)
|
self.exit_name, self.location, self.destination, self.exit_aliases, self.exit_typeclass
|
||||||
|
)
|
||||||
if not ok:
|
if not ok:
|
||||||
# an error; the exit was not created, so we quit.
|
# an error; the exit was not created, so we quit.
|
||||||
return
|
return
|
||||||
|
|
@ -1529,8 +1533,13 @@ class CmdOpen(ObjManipCommand):
|
||||||
back_exit_name = self.lhs_objs[1]["name"]
|
back_exit_name = self.lhs_objs[1]["name"]
|
||||||
back_exit_aliases = self.lhs_objs[1]["aliases"]
|
back_exit_aliases = self.lhs_objs[1]["aliases"]
|
||||||
back_exit_typeclass = self.lhs_objs[1]["option"]
|
back_exit_typeclass = self.lhs_objs[1]["option"]
|
||||||
self.create_exit(back_exit_name, self.destination, self.location, back_exit_aliases,
|
self.create_exit(
|
||||||
back_exit_typeclass)
|
back_exit_name,
|
||||||
|
self.destination,
|
||||||
|
self.location,
|
||||||
|
back_exit_aliases,
|
||||||
|
back_exit_typeclass,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _convert_from_string(cmd, strobj):
|
def _convert_from_string(cmd, strobj):
|
||||||
|
|
@ -1740,8 +1749,10 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
obj.attributes.remove(attr, category=category)
|
obj.attributes.remove(attr, category=category)
|
||||||
return f"\nDeleted attribute {obj.name}/|w{attr}|n [category:{category}]."
|
return f"\nDeleted attribute {obj.name}/|w{attr}|n [category:{category}]."
|
||||||
else:
|
else:
|
||||||
return (f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] "
|
return (
|
||||||
"was found to delete.")
|
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."
|
error = f"\nNo attribute {obj.name}/|w{attr}|n [category: {category}] was found to delete."
|
||||||
if nested:
|
if nested:
|
||||||
error += " (Nested lookups attempted)"
|
error += " (Nested lookups attempted)"
|
||||||
|
|
@ -1813,7 +1824,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# we set empty buffer on nonexisting Attribute because otherwise
|
# we set empty buffer on nonexisting Attribute because otherwise
|
||||||
# we'd always have the string "None" in the buffer to start with
|
# 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
|
return str(old_value) # we already confirmed we are ok with this
|
||||||
|
|
||||||
def save(caller, buf):
|
def save(caller, buf):
|
||||||
|
|
@ -1825,11 +1836,12 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
try:
|
try:
|
||||||
old_value = obj.attributes.get(attr, raise_exception=True)
|
old_value = obj.attributes.get(attr, raise_exception=True)
|
||||||
if not isinstance(old_value, str):
|
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. "
|
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. "
|
"\nTo continue editing, it must be converted to (and saved as) a string. "
|
||||||
"Continue? [Y]/N?")
|
"Continue? [Y]/N?"
|
||||||
if answer.lower() in ('n', 'no'):
|
)
|
||||||
|
if answer.lower() in ("n", "no"):
|
||||||
self.caller.msg("Aborted edit.")
|
self.caller.msg("Aborted edit.")
|
||||||
return
|
return
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
@ -1903,9 +1915,11 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
caller.msg("The Line editor can only be applied " "to one attribute at a time.")
|
caller.msg("The Line editor can only be applied " "to one attribute at a time.")
|
||||||
return
|
return
|
||||||
if not attrs:
|
if not attrs:
|
||||||
caller.msg("Use `set/edit <objname>/<attr>` to define the Attribute to edit.\nTo "
|
caller.msg(
|
||||||
"edit the current room description, use `set/edit here/desc` (or "
|
"Use `set/edit <objname>/<attr>` to define the Attribute to edit.\nTo "
|
||||||
"use the `desc` command).")
|
"edit the current room description, use `set/edit here/desc` (or "
|
||||||
|
"use the `desc` command)."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
self.edit_handler(obj, attrs[0], caller)
|
self.edit_handler(obj, attrs[0], caller)
|
||||||
return
|
return
|
||||||
|
|
@ -1936,8 +1950,10 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
global _ATTRFUNCPARSER
|
global _ATTRFUNCPARSER
|
||||||
if not _ATTRFUNCPARSER:
|
if not _ATTRFUNCPARSER:
|
||||||
_ATTRFUNCPARSER = funcparser.FuncParser(
|
_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")):
|
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 hasattr(parsed_value, "access"):
|
||||||
# if this is an object we must have the right to read it, if so,
|
# if this is an object we must have the right to read it, if so,
|
||||||
# we will not convert it to a string
|
# we will not convert it to a string
|
||||||
if not (parsed_value.access(caller, "control")
|
if not (
|
||||||
or parsed_value.access(self.caller, "edit")):
|
parsed_value.access(caller, "control")
|
||||||
caller.msg("You don't have permission to set "
|
or parsed_value.access(self.caller, "edit")
|
||||||
f"object with identifier '{value}'.")
|
):
|
||||||
|
caller.msg(
|
||||||
|
"You don't have permission to set " f"object with identifier '{value}'."
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
value = parsed_value
|
value = parsed_value
|
||||||
else:
|
else:
|
||||||
|
|
@ -2038,7 +2057,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
obj = caller.search(query)
|
obj = caller.search(query)
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
elif (self.account and self.account.__dbclass__ == dbclass):
|
elif self.account and self.account.__dbclass__ == dbclass:
|
||||||
# applying account while caller is object
|
# applying account while caller is object
|
||||||
caller.msg(f"Trying to search {new_typeclass} with query '{self.lhs}'.")
|
caller.msg(f"Trying to search {new_typeclass} with query '{self.lhs}'.")
|
||||||
obj = self.account.search(query)
|
obj = self.account.search(query)
|
||||||
|
|
@ -2071,7 +2090,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
caller = self.caller
|
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()
|
tclasses = get_all_typeclasses()
|
||||||
contribs = [key for key in sorted(tclasses) if key.startswith("evennia.contrib")] or [
|
contribs = [key for key in sorted(tclasses) if key.startswith("evennia.contrib")] or [
|
||||||
"<None loaded>"
|
"<None loaded>"
|
||||||
|
|
@ -2188,8 +2207,10 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
is_same = obj.is_typeclass(new_typeclass, exact=True)
|
is_same = obj.is_typeclass(new_typeclass, exact=True)
|
||||||
if is_same and "force" not in self.switches:
|
if is_same and "force" not in self.switches:
|
||||||
string = (f"{obj.name} already has the typeclass '{new_typeclass}'. "
|
string = (
|
||||||
"Use /force to override.")
|
f"{obj.name} already has the typeclass '{new_typeclass}'. "
|
||||||
|
"Use /force to override."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
update = "update" in self.switches
|
update = "update" in self.switches
|
||||||
reset = "reset" in self.switches
|
reset = "reset" in self.switches
|
||||||
|
|
@ -2220,7 +2241,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if "prototype" in self.switches:
|
if "prototype" in self.switches:
|
||||||
modified = spawner.batch_update_objects_with_prototype(
|
modified = spawner.batch_update_objects_with_prototype(
|
||||||
prototype, objects=[obj], caller=self.caller)
|
prototype, objects=[obj], caller=self.caller
|
||||||
|
)
|
||||||
prototype_success = modified > 0
|
prototype_success = modified > 0
|
||||||
if not prototype_success:
|
if not prototype_success:
|
||||||
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
||||||
|
|
@ -2543,9 +2565,7 @@ class CmdExamine(ObjManipCommand):
|
||||||
def format_locks(self, obj):
|
def format_locks(self, obj):
|
||||||
locks = str(obj.locks)
|
locks = str(obj.locks)
|
||||||
if locks:
|
if locks:
|
||||||
return utils.fill(
|
return utils.fill("; ".join([lock for lock in locks.split(";")]), indent=2)
|
||||||
"; ".join([lock for lock in locks.split(";")]), indent=2
|
|
||||||
)
|
|
||||||
return "Default"
|
return "Default"
|
||||||
|
|
||||||
def format_scripts(self, obj):
|
def format_scripts(self, obj):
|
||||||
|
|
@ -2572,6 +2592,7 @@ class CmdExamine(ObjManipCommand):
|
||||||
if value:
|
if value:
|
||||||
return f"{string}: T"
|
return f"{string}: T"
|
||||||
return f"{string}: F"
|
return f"{string}: F"
|
||||||
|
|
||||||
txt = ", ".join(
|
txt = ", ".join(
|
||||||
_truefalse(opt, getattr(cmdset, opt))
|
_truefalse(opt, getattr(cmdset, opt))
|
||||||
for opt in ("no_exits", "no_objs", "no_channels", "duplicates")
|
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
|
# 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
|
# 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).
|
# madness it is better that they overload with their own CmdExamine to handle it).
|
||||||
all_cmdsets.extend([(cmdset.key, cmdset)
|
all_cmdsets.extend(
|
||||||
for cmdset in obj.account.sessions.all()[0].cmdset.all()])
|
[(cmdset.key, cmdset) for cmdset in obj.account.sessions.all()[0].cmdset.all()]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
# we have to protect this since many objects don't have sessions.
|
# we have to protect this since many objects don't have sessions.
|
||||||
all_cmdsets.extend([(cmdset.key, cmdset)
|
all_cmdsets.extend(
|
||||||
for cmdset in obj.get_session(obj.sessions.get()).cmdset.all()])
|
[
|
||||||
|
(cmdset.key, cmdset)
|
||||||
|
for cmdset in obj.get_session(obj.sessions.get()).cmdset.all()
|
||||||
|
]
|
||||||
|
)
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
# an error means we are merging an object without a session
|
# an error means we are merging an object without a session
|
||||||
pass
|
pass
|
||||||
|
|
@ -2659,8 +2685,10 @@ class CmdExamine(ObjManipCommand):
|
||||||
typ = f" |B[type: {typ}]|n" if typ else ""
|
typ = f" |B[type: {typ}]|n" if typ else ""
|
||||||
value = utils.to_str(value)
|
value = utils.to_str(value)
|
||||||
value = _FUNCPARSER.parse(ansi_raw(value), escape=True)
|
value = _FUNCPARSER.parse(ansi_raw(value), escape=True)
|
||||||
return (f"Attribute {obj.name}/{self.header_color}{key}|n "
|
return (
|
||||||
f"[category={category}]{typ}:\n\n{value}")
|
f"Attribute {obj.name}/{self.header_color}{key}|n "
|
||||||
|
f"[category={category}]{typ}:\n\n{value}"
|
||||||
|
)
|
||||||
|
|
||||||
def format_single_attribute(self, attr):
|
def format_single_attribute(self, attr):
|
||||||
global _FUNCPARSER
|
global _FUNCPARSER
|
||||||
|
|
@ -2680,8 +2708,7 @@ class CmdExamine(ObjManipCommand):
|
||||||
|
|
||||||
def format_attributes(self, obj):
|
def format_attributes(self, obj):
|
||||||
output = "\n " + "\n ".join(
|
output = "\n " + "\n ".join(
|
||||||
sorted(self.format_single_attribute(attr)
|
sorted(self.format_single_attribute(attr) for attr in obj.db_attributes.all())
|
||||||
for attr in obj.db_attributes.all())
|
|
||||||
)
|
)
|
||||||
if output.strip():
|
if output.strip():
|
||||||
# we don't want just an empty line
|
# we don't want just an empty line
|
||||||
|
|
@ -2695,8 +2722,7 @@ class CmdExamine(ObjManipCommand):
|
||||||
|
|
||||||
if ndb_attr and ndb_attr[0]:
|
if ndb_attr and ndb_attr[0]:
|
||||||
return "\n " + " \n".join(
|
return "\n " + " \n".join(
|
||||||
sorted(self.format_single_attribute(attr)
|
sorted(self.format_single_attribute(attr) for attr, value in ndb_attr)
|
||||||
for attr, value in ndb_attr)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def format_exits(self, obj):
|
def format_exits(self, obj):
|
||||||
|
|
@ -2706,14 +2732,16 @@ class CmdExamine(ObjManipCommand):
|
||||||
|
|
||||||
def format_chars(self, obj):
|
def format_chars(self, obj):
|
||||||
if hasattr(obj, "contents"):
|
if hasattr(obj, "contents"):
|
||||||
chars = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents
|
chars = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents if obj.account)
|
||||||
if obj.account)
|
|
||||||
return chars if chars else None
|
return chars if chars else None
|
||||||
|
|
||||||
def format_things(self, obj):
|
def format_things(self, obj):
|
||||||
if hasattr(obj, "contents"):
|
if hasattr(obj, "contents"):
|
||||||
things = ", ".join(f"{obj.name}({obj.dbref})" for obj in obj.contents
|
things = ", ".join(
|
||||||
if not obj.account and not obj.destination)
|
f"{obj.name}({obj.dbref})"
|
||||||
|
for obj in obj.contents
|
||||||
|
if not obj.account and not obj.destination
|
||||||
|
)
|
||||||
return things if things else None
|
return things if things else None
|
||||||
|
|
||||||
def format_script_desc(self, obj):
|
def format_script_desc(self, obj):
|
||||||
|
|
@ -2736,8 +2764,10 @@ class CmdExamine(ObjManipCommand):
|
||||||
remaining_repeats = obj.remaining_repeats()
|
remaining_repeats = obj.remaining_repeats()
|
||||||
remaining_repeats = 0 if remaining_repeats is None else remaining_repeats
|
remaining_repeats = 0 if remaining_repeats is None else remaining_repeats
|
||||||
repeats = f" - {remaining_repeats}/{obj.db_repeats} remain"
|
repeats = f" - {remaining_repeats}/{obj.db_repeats} remain"
|
||||||
return (f"{active} - interval: {interval}s "
|
return (
|
||||||
f"(next: {next_repeat}{repeats}, start_delay: {start_delay})")
|
f"{active} - interval: {interval}s "
|
||||||
|
f"(next: {next_repeat}{repeats}, start_delay: {start_delay})"
|
||||||
|
)
|
||||||
|
|
||||||
def format_channel_sub_totals(self, obj):
|
def format_channel_sub_totals(self, obj):
|
||||||
if hasattr(obj, "db_account_subscriptions"):
|
if hasattr(obj, "db_account_subscriptions"):
|
||||||
|
|
@ -2752,14 +2782,16 @@ class CmdExamine(ObjManipCommand):
|
||||||
account_subs = obj.db_account_subscriptions.all()
|
account_subs = obj.db_account_subscriptions.all()
|
||||||
if account_subs:
|
if account_subs:
|
||||||
return "\n " + "\n ".join(
|
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):
|
def format_channel_object_subs(self, obj):
|
||||||
if hasattr(obj, "db_object_subscriptions"):
|
if hasattr(obj, "db_object_subscriptions"):
|
||||||
object_subs = obj.db_object_subscriptions.all()
|
object_subs = obj.db_object_subscriptions.all()
|
||||||
if object_subs:
|
if object_subs:
|
||||||
return "\n " + "\n ".join(
|
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):
|
def get_formatted_obj_data(self, obj, current_cmdset):
|
||||||
"""
|
"""
|
||||||
|
|
@ -2781,13 +2813,14 @@ class CmdExamine(ObjManipCommand):
|
||||||
objdata["Destination"] = self.format_destination(obj)
|
objdata["Destination"] = self.format_destination(obj)
|
||||||
objdata["Permissions"] = self.format_permissions(obj)
|
objdata["Permissions"] = self.format_permissions(obj)
|
||||||
objdata["Locks"] = self.format_locks(obj)
|
objdata["Locks"] = self.format_locks(obj)
|
||||||
if (current_cmdset
|
if current_cmdset and not (
|
||||||
and not (len(obj.cmdset.all()) == 1
|
len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"
|
||||||
and obj.cmdset.current.key == "_EMPTY_CMDSET")):
|
):
|
||||||
objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj)
|
objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj)
|
||||||
objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset)
|
objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset)
|
||||||
objdata[f"Commands vailable to {obj.key} (result of Merged Cmdset(s))"] = (
|
objdata[
|
||||||
self.format_current_cmds(obj, current_cmdset))
|
f"Commands vailable to {obj.key} (result of Merged Cmdset(s))"
|
||||||
|
] = self.format_current_cmds(obj, current_cmdset)
|
||||||
if self.object_type == "script":
|
if self.object_type == "script":
|
||||||
objdata["Description"] = self.format_script_desc(obj)
|
objdata["Description"] = self.format_script_desc(obj)
|
||||||
objdata["Persistent"] = self.format_script_is_persistent(obj)
|
objdata["Persistent"] = self.format_script_is_persistent(obj)
|
||||||
|
|
@ -2859,10 +2892,11 @@ class CmdExamine(ObjManipCommand):
|
||||||
obj = None
|
obj = None
|
||||||
elif len(obj) > 1:
|
elif len(obj) > 1:
|
||||||
err = "Multiple {objtype} found with key {obj_name}:\n{matches}"
|
err = "Multiple {objtype} found with key {obj_name}:\n{matches}"
|
||||||
self.caller.msg(err.format(
|
self.caller.msg(
|
||||||
obj_name=obj_name,
|
err.format(
|
||||||
matches=", ".join(f"{ob.key}(#{ob.id})" for ob in obj)
|
obj_name=obj_name, matches=", ".join(f"{ob.key}(#{ob.id})" for ob in obj)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
obj = None
|
obj = None
|
||||||
else:
|
else:
|
||||||
obj = obj[0]
|
obj = obj[0]
|
||||||
|
|
@ -2887,13 +2921,16 @@ class CmdExamine(ObjManipCommand):
|
||||||
# is not so common anyway.
|
# is not so common anyway.
|
||||||
|
|
||||||
obj = None
|
obj = None
|
||||||
obj_name = objdef["name"] # name
|
obj_name = objdef["name"] # name
|
||||||
obj_attrs = objdef["attrs"] # /attrs
|
obj_attrs = objdef["attrs"] # /attrs
|
||||||
|
|
||||||
# identify object type, in prio account - script - channel
|
# identify object type, in prio account - script - channel
|
||||||
object_type = "object"
|
object_type = "object"
|
||||||
if (utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount")
|
if (
|
||||||
or "account" in self.switches or obj_name.startswith("*")):
|
utils.inherits_from(self.caller, "evennia.accounts.accounts.DefaultAccount")
|
||||||
|
or "account" in self.switches
|
||||||
|
or obj_name.startswith("*")
|
||||||
|
):
|
||||||
object_type = "account"
|
object_type = "account"
|
||||||
elif "script" in self.switches:
|
elif "script" in self.switches:
|
||||||
object_type = "script"
|
object_type = "script"
|
||||||
|
|
@ -3293,7 +3330,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
"start": "|gStarted|n",
|
"start": "|gStarted|n",
|
||||||
"stop": "|RStopped|n",
|
"stop": "|RStopped|n",
|
||||||
"pause": "|Paused|n",
|
"pause": "|Paused|n",
|
||||||
"delete": "|rDeleted|n"
|
"delete": "|rDeleted|n",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _search_script(self, args):
|
def _search_script(self, args):
|
||||||
|
|
@ -3307,7 +3344,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
return scripts
|
return scripts
|
||||||
if "-" in args:
|
if "-" in args:
|
||||||
# may be a dbref-range
|
# 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:
|
if val1 and val2:
|
||||||
scripts = ScriptDB.objects.filter(id__in=(range(val1, val2 + 1)))
|
scripts = ScriptDB.objects.filter(id__in=(range(val1, val2 + 1)))
|
||||||
if scripts:
|
if scripts:
|
||||||
|
|
@ -3348,11 +3385,14 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
if obj.scripts.add(self.rhs, autostart=True):
|
if obj.scripts.add(self.rhs, autostart=True):
|
||||||
caller.msg(
|
caller.msg(
|
||||||
f"Script |w{self.rhs}|n successfully added and "
|
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:
|
else:
|
||||||
caller.msg(f"Script {self.rhs} could not be added and/or started "
|
caller.msg(
|
||||||
f"on {obj.get_display_name(caller)} (or it started and "
|
f"Script {self.rhs} could not be added and/or started "
|
||||||
"immediately shut down).")
|
f"on {obj.get_display_name(caller)} (or it started and "
|
||||||
|
"immediately shut down)."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# just show all scripts on object
|
# just show all scripts on object
|
||||||
scripts = ScriptDB.objects.filter(db_obj=obj)
|
scripts = ScriptDB.objects.filter(db_obj=obj)
|
||||||
|
|
@ -3374,12 +3414,15 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
new_script = None
|
new_script = None
|
||||||
|
|
||||||
if new_script:
|
if new_script:
|
||||||
caller.msg(f"Global Script Created - "
|
caller.msg(
|
||||||
f"{new_script.key} ({new_script.typeclass_path})")
|
f"Global Script Created - "
|
||||||
|
f"{new_script.key} ({new_script.typeclass_path})"
|
||||||
|
)
|
||||||
ScriptEvMore(caller, [new_script], session=self.session)
|
ScriptEvMore(caller, [new_script], session=self.session)
|
||||||
else:
|
else:
|
||||||
caller.msg(f"Global Script |rNOT|n Created |r(see log)|n - "
|
caller.msg(
|
||||||
f"arguments: {self.args}")
|
f"Global Script |rNOT|n Created |r(see log)|n - " f"arguments: {self.args}"
|
||||||
|
)
|
||||||
|
|
||||||
elif scripts or obj:
|
elif scripts or obj:
|
||||||
# modification switches - must operate on existing scripts
|
# modification switches - must operate on existing scripts
|
||||||
|
|
@ -3388,9 +3431,11 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
scripts = ScriptDB.objects.filter(db_obj=obj)
|
scripts = ScriptDB.objects.filter(db_obj=obj)
|
||||||
|
|
||||||
if scripts.count() > 1:
|
if scripts.count() > 1:
|
||||||
ret = yield(f"Multiple scripts found: {scripts}. Are you sure you want to "
|
ret = yield (
|
||||||
"operate on all of them? [Y]/N? ")
|
f"Multiple scripts found: {scripts}. Are you sure you want to "
|
||||||
if ret.lower() in ('n', 'no'):
|
"operate on all of them? [Y]/N? "
|
||||||
|
)
|
||||||
|
if ret.lower() in ("n", "no"):
|
||||||
caller.msg("Aborted.")
|
caller.msg("Aborted.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -3406,11 +3451,14 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
getattr(script, switch)()
|
getattr(script, switch)()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
msgs.append(f"{scripttype} |rNOT|n {verb} |r(see log)|n - "
|
msgs.append(
|
||||||
f"{script_key} ({script_typeclass_path})|n")
|
f"{scripttype} |rNOT|n {verb} |r(see log)|n - "
|
||||||
|
f"{script_key} ({script_typeclass_path})|n"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
msgs.append(f"{scripttype} {verb} - "
|
msgs.append(
|
||||||
f"{script_key} ({script_typeclass_path})")
|
f"{scripttype} {verb} - " f"{script_key} ({script_typeclass_path})"
|
||||||
|
)
|
||||||
caller.msg("\n".join(msgs))
|
caller.msg("\n".join(msgs))
|
||||||
if "delete" not in self.switches:
|
if "delete" not in self.switches:
|
||||||
ScriptEvMore(caller, [script], session=self.session)
|
ScriptEvMore(caller, [script], session=self.session)
|
||||||
|
|
@ -3488,7 +3536,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
|
|
||||||
# last N table
|
# 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(
|
latesttable = self.styled_table(
|
||||||
"|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="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
|
# check any locks
|
||||||
if not (caller.permissions.check("Admin") or obj_to_teleport.access(caller, "teleport")):
|
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 "
|
caller.msg(
|
||||||
"it anywhere.")
|
f"{obj_to_teleport} 'teleport'-lock blocks you from teleporting " "it anywhere."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not (caller.permissions.check("Admin")
|
if not (
|
||||||
or destination.access(obj_to_teleport, "teleport_here")):
|
caller.permissions.check("Admin")
|
||||||
caller.msg(f"{destination} 'teleport_here'-lock blocks {obj_to_teleport} from "
|
or destination.access(obj_to_teleport, "teleport_here")
|
||||||
"moving there.")
|
):
|
||||||
|
caller.msg(
|
||||||
|
f"{destination} 'teleport_here'-lock blocks {obj_to_teleport} from " "moving there."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# try the teleport
|
# try the teleport
|
||||||
|
|
@ -3636,8 +3688,11 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
obj_to_teleport.location = destination
|
obj_to_teleport.location = destination
|
||||||
caller.msg(f"Teleported {obj_to_teleport} None -> {destination}")
|
caller.msg(f"Teleported {obj_to_teleport} None -> {destination}")
|
||||||
elif obj_to_teleport.move_to(
|
elif obj_to_teleport.move_to(
|
||||||
destination, quiet="quiet" in self.switches,
|
destination,
|
||||||
emit_to_obj=caller, use_destination="intoexit" not in self.switches):
|
quiet="quiet" in self.switches,
|
||||||
|
emit_to_obj=caller,
|
||||||
|
use_destination="intoexit" not in self.switches,
|
||||||
|
):
|
||||||
|
|
||||||
if obj_to_teleport == caller:
|
if obj_to_teleport == caller:
|
||||||
caller.msg(f"Teleported to {destination}.")
|
caller.msg(f"Teleported to {destination}.")
|
||||||
|
|
@ -3995,7 +4050,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
self.caller.msg("No prototypes found.")
|
self.caller.msg("No prototypes found.")
|
||||||
|
|
||||||
def _list_prototypes(self, key=None, tags=None):
|
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)
|
protlib.list_prototypes(self.caller, key=key, tags=tags, session=self.session)
|
||||||
|
|
||||||
@interactive
|
@interactive
|
||||||
|
|
@ -4039,7 +4094,9 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
n_updated = spawner.batch_update_objects_with_prototype(
|
n_updated = spawner.batch_update_objects_with_prototype(
|
||||||
prototype, objects=existing_objects, caller=caller,
|
prototype,
|
||||||
|
objects=existing_objects,
|
||||||
|
caller=caller,
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
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)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
CHANNEL_DEFAULT_TYPECLASS = class_from_module(
|
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
|
# limit symbol import for API
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"CmdChannel",
|
"CmdChannel",
|
||||||
"CmdObjectChannel",
|
"CmdObjectChannel",
|
||||||
|
|
||||||
"CmdPage",
|
"CmdPage",
|
||||||
|
|
||||||
"CmdIRC2Chan",
|
"CmdIRC2Chan",
|
||||||
"CmdIRCStatus",
|
"CmdIRCStatus",
|
||||||
"CmdRSS2Chan",
|
"CmdRSS2Chan",
|
||||||
|
|
@ -207,6 +206,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
|
ban mychannel1,mychannel2= EvilUser : Was banned for spamming.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "@channel"
|
key = "@channel"
|
||||||
aliases = ["@chan", "@channels"]
|
aliases = ["@chan", "@channels"]
|
||||||
help_category = "Comms"
|
help_category = "Comms"
|
||||||
|
|
@ -215,8 +215,25 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
# the manage: lock controls access to /create/destroy/desc/lock/unlock switches
|
# 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)"
|
locks = "cmd:not pperm(channel_banned);admin:all();manage:all();changelocks:perm(Admin)"
|
||||||
switch_options = (
|
switch_options = (
|
||||||
"list", "all", "history", "sub", "unsub", "mute", "unmute", "alias", "unalias",
|
"list",
|
||||||
"create", "destroy", "desc", "lock", "unlock", "boot", "ban", "unban", "who",)
|
"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
|
# disable this in child command classes if wanting on-character channels
|
||||||
account_caller = True
|
account_caller = True
|
||||||
|
|
||||||
|
|
@ -253,17 +270,24 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname, exact=exact)
|
channels = CHANNEL_DEFAULT_TYPECLASS.objects.channel_search(channelname, exact=exact)
|
||||||
|
|
||||||
# check permissions
|
# check permissions
|
||||||
channels = [channel for channel in channels
|
channels = [
|
||||||
if channel.access(caller, 'listen') or channel.access(caller, 'control')]
|
channel
|
||||||
|
for channel in channels
|
||||||
|
if channel.access(caller, "listen") or channel.access(caller, "control")
|
||||||
|
]
|
||||||
|
|
||||||
if handle_errors:
|
if handle_errors:
|
||||||
if not channels:
|
if not channels:
|
||||||
self.msg(f"No channel found matching '{channelname}' "
|
self.msg(
|
||||||
"(could also be due to missing access).")
|
f"No channel found matching '{channelname}' "
|
||||||
|
"(could also be due to missing access)."
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
elif len(channels) > 1:
|
elif len(channels) > 1:
|
||||||
self.msg("Multiple possible channel matches/alias for "
|
self.msg(
|
||||||
"'{channelname}':\n" + ", ".join(chan.key for chan in channels))
|
"Multiple possible channel matches/alias for "
|
||||||
|
"'{channelname}':\n" + ", ".join(chan.key for chan in channels)
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
return channels[0]
|
return channels[0]
|
||||||
else:
|
else:
|
||||||
|
|
@ -312,6 +336,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
return self.msg(
|
return self.msg(
|
||||||
"".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines)
|
"".join(line.split("[-]", 1)[1] if "[-]" in line else line for line in lines)
|
||||||
)
|
)
|
||||||
|
|
||||||
# asynchronously tail the log file
|
# asynchronously tail the log file
|
||||||
tail_log_file(log_file, start_index, 20, callback=send_msg)
|
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
|
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
|
||||||
|
|
||||||
new_chan = create.create_channel(
|
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)
|
self.sub_to_channel(new_chan)
|
||||||
return new_chan, ""
|
return new_chan, ""
|
||||||
|
|
||||||
|
|
@ -514,14 +540,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
channel_key = channel.key
|
channel_key = channel.key
|
||||||
if message is None:
|
if message is None:
|
||||||
message = (f"|rChannel {channel_key} is being destroyed. "
|
message = (
|
||||||
"Make sure to clean any channel aliases.|n")
|
f"|rChannel {channel_key} is being destroyed. "
|
||||||
|
"Make sure to clean any channel aliases.|n"
|
||||||
|
)
|
||||||
if message:
|
if message:
|
||||||
channel.msg(message, senders=caller, bypass_mute=True)
|
channel.msg(message, senders=caller, bypass_mute=True)
|
||||||
channel.delete()
|
channel.delete()
|
||||||
logger.log_sec(
|
logger.log_sec("Channel {} was deleted by {}".format(channel_key, caller))
|
||||||
"Channel {} was deleted by {}".format(channel_key, caller)
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_lock(self, channel, lockstring):
|
def set_lock(self, channel, lockstring):
|
||||||
"""
|
"""
|
||||||
|
|
@ -610,8 +636,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
if not quiet:
|
if not quiet:
|
||||||
channel.msg(f"{target.key} was booted from channel by {self.caller.key}.{reason}")
|
channel.msg(f"{target.key} was booted from channel by {self.caller.key}.{reason}")
|
||||||
|
|
||||||
logger.log_sec(f"Channel Boot: {target} (Channel: {channel}, "
|
logger.log_sec(
|
||||||
f"Reason: {reason.strip()}, Caller: {self.caller}")
|
f"Channel Boot: {target} (Channel: {channel}, "
|
||||||
|
f"Reason: {reason.strip()}, Caller: {self.caller}"
|
||||||
|
)
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
def ban_user(self, channel, target, quiet=False, reason=""):
|
def ban_user(self, channel, target, quiet=False, reason=""):
|
||||||
|
|
@ -684,7 +712,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
mute_list = list(channel.mutelist)
|
mute_list = list(channel.mutelist)
|
||||||
online_list = channel.subscriptions.online()
|
online_list = channel.subscriptions.online()
|
||||||
if channel.access(caller, 'control'):
|
if channel.access(caller, "control"):
|
||||||
# for those with channel control, show also offline users
|
# for those with channel control, show also offline users
|
||||||
all_subs = list(channel.subscriptions.all())
|
all_subs = list(channel.subscriptions.all())
|
||||||
else:
|
else:
|
||||||
|
|
@ -694,8 +722,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
who_list = []
|
who_list = []
|
||||||
for subscriber in all_subs:
|
for subscriber in all_subs:
|
||||||
name = subscriber.get_display_name(caller)
|
name = subscriber.get_display_name(caller)
|
||||||
conditions = ("muting" if subscriber in mute_list else "",
|
conditions = (
|
||||||
"offline" if subscriber not in online_list else "")
|
"muting" if subscriber in mute_list else "",
|
||||||
|
"offline" if subscriber not in online_list else "",
|
||||||
|
)
|
||||||
conditions = [cond for cond in conditions if cond]
|
conditions = [cond for cond in conditions if cond]
|
||||||
cond_text = "(" + ", ".join(conditions) + ")" if conditions else ""
|
cond_text = "(" + ", ".join(conditions) + ")" if conditions else ""
|
||||||
who_list.append(f"{name}{cond_text}")
|
who_list.append(f"{name}{cond_text}")
|
||||||
|
|
@ -743,7 +773,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
"locks",
|
"locks",
|
||||||
"description",
|
"description",
|
||||||
align="l",
|
align="l",
|
||||||
maxwidth=_DEFAULT_WIDTH
|
maxwidth=_DEFAULT_WIDTH,
|
||||||
)
|
)
|
||||||
for chan in subscribed:
|
for chan in subscribed:
|
||||||
|
|
||||||
|
|
@ -756,14 +786,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
||||||
comtable.add_row(
|
comtable.add_row(
|
||||||
*(
|
*(
|
||||||
chanid,
|
chanid,
|
||||||
"{key}{aliases}".format(
|
"{key}{aliases}".format(
|
||||||
key=chan.key,
|
key=chan.key,
|
||||||
aliases=";"+ ";".join(chan.aliases.all()) if chan.aliases.all() else ""
|
aliases=";" + ";".join(chan.aliases.all()) if chan.aliases.all() else "",
|
||||||
),
|
),
|
||||||
my_aliases,
|
my_aliases,
|
||||||
locks,
|
locks,
|
||||||
chan.db.desc
|
chan.db.desc,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return comtable
|
return comtable
|
||||||
|
|
@ -799,11 +829,14 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
substatus = "|gYes|n"
|
substatus = "|gYes|n"
|
||||||
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
my_aliases = ", ".join(self.get_channel_aliases(chan))
|
||||||
comtable.add_row(
|
comtable.add_row(
|
||||||
*(substatus,
|
*(
|
||||||
chan.key,
|
substatus,
|
||||||
",".join(chan.aliases.all()) if chan.aliases.all() else "",
|
chan.key,
|
||||||
my_aliases,
|
",".join(chan.aliases.all()) if chan.aliases.all() else "",
|
||||||
chan.db.desc))
|
my_aliases,
|
||||||
|
chan.db.desc,
|
||||||
|
)
|
||||||
|
)
|
||||||
comtable.reformat_column(0, width=8)
|
comtable.reformat_column(0, width=8)
|
||||||
|
|
||||||
return comtable
|
return comtable
|
||||||
|
|
@ -818,16 +851,17 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
switches = self.switches
|
switches = self.switches
|
||||||
channel_names = [name for name in self.lhslist if name]
|
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
|
# show all available channels
|
||||||
subscribed, available = self.list_channels()
|
subscribed, available = self.list_channels()
|
||||||
table = self.display_all_channels(subscribed, available)
|
table = self.display_all_channels(subscribed, available)
|
||||||
|
|
||||||
self.msg(
|
self.msg(
|
||||||
"\n|wAvailable channels|n (use no argument to "
|
"\n|wAvailable channels|n (use no argument to "
|
||||||
f"only show your subscriptions)\n{table}")
|
f"only show your subscriptions)\n{table}"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not channel_names:
|
if not channel_names:
|
||||||
|
|
@ -835,15 +869,16 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
subscribed, _ = self.list_channels()
|
subscribed, _ = self.list_channels()
|
||||||
table = self.display_subbed_channels(subscribed)
|
table = self.display_subbed_channels(subscribed)
|
||||||
|
|
||||||
self.msg("\n|wChannel subscriptions|n "
|
self.msg(
|
||||||
f"(use |w/all|n to see all available):\n{table}")
|
"\n|wChannel subscriptions|n " f"(use |w/all|n to see all available):\n{table}"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.switches and not self.args:
|
if not self.switches and not self.args:
|
||||||
self.msg("Usage[/switches]: channel [= message]")
|
self.msg("Usage[/switches]: channel [= message]")
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'create' in switches:
|
if "create" in switches:
|
||||||
# create a new channel
|
# create a new channel
|
||||||
|
|
||||||
if not self.access(caller, "manage"):
|
if not self.access(caller, "manage"):
|
||||||
|
|
@ -865,7 +900,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'unalias' in switches:
|
if "unalias" in switches:
|
||||||
# remove a personal alias (no channel needed)
|
# remove a personal alias (no channel needed)
|
||||||
alias = self.args.strip()
|
alias = self.args.strip()
|
||||||
if not alias:
|
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
|
# channels without a space in their name), we need to check if the
|
||||||
# first 'channel name' is in fact 'channelname text'
|
# first 'channel name' is in fact 'channelname text'
|
||||||
no_rhs_channel_name = self.args.split(" ", 1)[0]
|
no_rhs_channel_name = self.args.split(" ", 1)[0]
|
||||||
possible_lhs_message = self.args[len(no_rhs_channel_name):]
|
possible_lhs_message = self.args[len(no_rhs_channel_name) :]
|
||||||
if possible_lhs_message.strip() == '=':
|
if possible_lhs_message.strip() == "=":
|
||||||
possible_lhs_message = ""
|
possible_lhs_message = ""
|
||||||
channel_names.append(no_rhs_channel_name)
|
channel_names.append(no_rhs_channel_name)
|
||||||
|
|
||||||
|
|
||||||
channels = []
|
channels = []
|
||||||
errors = []
|
errors = []
|
||||||
for channel_name in channel_names:
|
for channel_name in channel_names:
|
||||||
|
|
@ -897,16 +931,20 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
# 'listen/control' perms.
|
# 'listen/control' perms.
|
||||||
found_channels = self.search_channel(channel_name, exact=False, handle_errors=False)
|
found_channels = self.search_channel(channel_name, exact=False, handle_errors=False)
|
||||||
if not found_channels:
|
if not found_channels:
|
||||||
errors.append(f"No channel found matching '{channel_name}' "
|
errors.append(
|
||||||
"(could also be due to missing access).")
|
f"No channel found matching '{channel_name}' "
|
||||||
|
"(could also be due to missing access)."
|
||||||
|
)
|
||||||
elif len(found_channels) > 1:
|
elif len(found_channels) > 1:
|
||||||
errors.append("Multiple possible channel matches/alias for "
|
errors.append(
|
||||||
"'{channel_name}':\n" + ", ".join(chan.key for chan in found_channels))
|
"Multiple possible channel matches/alias for "
|
||||||
|
"'{channel_name}':\n" + ", ".join(chan.key for chan in found_channels)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
channels.append(found_channels[0])
|
channels.append(found_channels[0])
|
||||||
|
|
||||||
if not channels:
|
if not channels:
|
||||||
self.msg('\n'.join(errors))
|
self.msg("\n".join(errors))
|
||||||
return
|
return
|
||||||
|
|
||||||
# we have at least one channel at this point
|
# we have at least one channel at this point
|
||||||
|
|
@ -925,30 +963,35 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
if channel in subscribed:
|
if channel in subscribed:
|
||||||
table = self.display_subbed_channels([channel])
|
table = self.display_subbed_channels([channel])
|
||||||
header = f"Channel |w{channel.key}|n"
|
header = f"Channel |w{channel.key}|n"
|
||||||
self.msg(f"{header}\n(use |w{channel.key} <msg>|n (or a channel-alias) "
|
self.msg(
|
||||||
f"to chat and the 'channel' command "
|
f"{header}\n(use |w{channel.key} <msg>|n (or a channel-alias) "
|
||||||
f"to customize)\n{table}")
|
f"to chat and the 'channel' command "
|
||||||
|
f"to customize)\n{table}"
|
||||||
|
)
|
||||||
elif channel in available:
|
elif channel in available:
|
||||||
table = self.display_all_channels([], [channel])
|
table = self.display_all_channels([], [channel])
|
||||||
self.msg(
|
self.msg(
|
||||||
"\n|wNot subscribed to this channel|n (use /list to "
|
"\n|wNot subscribed to this channel|n (use /list to "
|
||||||
f"show all subscriptions)\n{table}")
|
f"show all subscriptions)\n{table}"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'history' in switches or 'hist' in switches:
|
if "history" in switches or "hist" in switches:
|
||||||
# view channel history
|
# view channel history
|
||||||
|
|
||||||
index = self.rhs or 0
|
index = self.rhs or 0
|
||||||
try:
|
try:
|
||||||
index = max(0, int(index))
|
index = max(0, int(index))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.msg("The history index (describing how many lines to go back) "
|
self.msg(
|
||||||
"must be an integer >= 0.")
|
"The history index (describing how many lines to go back) "
|
||||||
|
"must be an integer >= 0."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
self.get_channel_history(channel, start_index=index)
|
self.get_channel_history(channel, start_index=index)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'sub' in switches:
|
if "sub" in switches:
|
||||||
# subscribe to a channel
|
# subscribe to a channel
|
||||||
aliases = []
|
aliases = []
|
||||||
if self.rhs:
|
if self.rhs:
|
||||||
|
|
@ -957,26 +1000,29 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
if success:
|
if success:
|
||||||
for alias in aliases:
|
for alias in aliases:
|
||||||
self.add_alias(channel, alias)
|
self.add_alias(channel, alias)
|
||||||
alias_txt = ', '.join(aliases)
|
alias_txt = ", ".join(aliases)
|
||||||
alias_txt = f" using alias(es) {alias_txt}" if aliases else ''
|
alias_txt = f" using alias(es) {alias_txt}" if aliases else ""
|
||||||
self.msg("You are now subscribed "
|
self.msg(
|
||||||
f"to the channel {channel.key}{alias_txt}. Use /alias to "
|
"You are now subscribed "
|
||||||
"add additional aliases for referring to the channel.")
|
f"to the channel {channel.key}{alias_txt}. Use /alias to "
|
||||||
|
"add additional aliases for referring to the channel."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'unsub' in switches:
|
if "unsub" in switches:
|
||||||
# un-subscribe from a channel
|
# un-subscribe from a channel
|
||||||
success, err = self.unsub_from_channel(channel)
|
success, err = self.unsub_from_channel(channel)
|
||||||
if success:
|
if success:
|
||||||
self.msg(f"You un-subscribed from channel {channel.key}. "
|
self.msg(
|
||||||
"All aliases were cleared.")
|
f"You un-subscribed from channel {channel.key}. " "All aliases were cleared."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'alias' in switches:
|
if "alias" in switches:
|
||||||
# create a new personal alias for a channel
|
# create a new personal alias for a channel
|
||||||
alias = self.rhs
|
alias = self.rhs
|
||||||
if not alias:
|
if not alias:
|
||||||
|
|
@ -986,7 +1032,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(f"Added/updated your alias '{alias}' for channel {channel.key}.")
|
self.msg(f"Added/updated your alias '{alias}' for channel {channel.key}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'mute' in switches:
|
if "mute" in switches:
|
||||||
# mute a given channel
|
# mute a given channel
|
||||||
success, err = self.mute_channel(channel)
|
success, err = self.mute_channel(channel)
|
||||||
if success:
|
if success:
|
||||||
|
|
@ -995,7 +1041,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'unmute' in switches:
|
if "unmute" in switches:
|
||||||
# unmute a given channel
|
# unmute a given channel
|
||||||
success, err = self.unmute_channel(channel)
|
success, err = self.unmute_channel(channel)
|
||||||
if success:
|
if success:
|
||||||
|
|
@ -1004,7 +1050,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(err)
|
self.msg(err)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'destroy' in switches or 'delete' in switches:
|
if "destroy" in switches or "delete" in switches:
|
||||||
# destroy a channel we control
|
# destroy a channel we control
|
||||||
|
|
||||||
if not self.access(caller, "manage"):
|
if not self.access(caller, "manage"):
|
||||||
|
|
@ -1028,10 +1074,10 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
"remove all users' aliases. {options}?",
|
"remove all users' aliases. {options}?",
|
||||||
yes_action=_perform_delete,
|
yes_action=_perform_delete,
|
||||||
no_action="Aborted.",
|
no_action="Aborted.",
|
||||||
default="N"
|
default="N",
|
||||||
)
|
)
|
||||||
|
|
||||||
if 'desc' in switches:
|
if "desc" in switches:
|
||||||
# set channel description
|
# set channel description
|
||||||
|
|
||||||
if not self.access(caller, "manage"):
|
if not self.access(caller, "manage"):
|
||||||
|
|
@ -1051,7 +1097,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.set_desc(channel, desc)
|
self.set_desc(channel, desc)
|
||||||
self.msg("Updated channel description.")
|
self.msg("Updated channel description.")
|
||||||
|
|
||||||
if 'lock' in switches:
|
if "lock" in switches:
|
||||||
# add a lockstring to channel
|
# add a lockstring to channel
|
||||||
|
|
||||||
if not self.access(caller, "changelocks"):
|
if not self.access(caller, "changelocks"):
|
||||||
|
|
@ -1075,7 +1121,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(f"Could not add/update lock: {err}")
|
self.msg(f"Could not add/update lock: {err}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'unlock' in switches:
|
if "unlock" in switches:
|
||||||
# remove/update lockstring from channel
|
# remove/update lockstring from channel
|
||||||
|
|
||||||
if not self.access(caller, "changelocks"):
|
if not self.access(caller, "changelocks"):
|
||||||
|
|
@ -1099,7 +1145,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(f"Could not remove lock: {err}")
|
self.msg(f"Could not remove lock: {err}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'boot' in switches:
|
if "boot" in switches:
|
||||||
# boot a user from channel(s)
|
# boot a user from channel(s)
|
||||||
|
|
||||||
if not self.access(caller, "admin"):
|
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}")
|
self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}")
|
||||||
|
|
||||||
channames = ", ".join(chan.key for chan in channels)
|
channames = ", ".join(chan.key for chan in channels)
|
||||||
reasonwarn = (". Also note that your reason will be echoed to the channel"
|
reasonwarn = (
|
||||||
if reason else '')
|
". Also note that your reason will be echoed to the channel" if reason else ""
|
||||||
|
)
|
||||||
ask_yes_no(
|
ask_yes_no(
|
||||||
caller,
|
caller,
|
||||||
prompt=f"Are you sure you want to boot user {target.key} from "
|
prompt=f"Are you sure you want to boot user {target.key} from "
|
||||||
|
|
@ -1143,11 +1190,11 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
"{options}?",
|
"{options}?",
|
||||||
yes_action=_boot_user,
|
yes_action=_boot_user,
|
||||||
no_action="Aborted.",
|
no_action="Aborted.",
|
||||||
default="Y"
|
default="Y",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'ban' in switches:
|
if "ban" in switches:
|
||||||
# ban a user from channel(s)
|
# ban a user from channel(s)
|
||||||
|
|
||||||
if not self.access(caller, "admin"):
|
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}")
|
self.msg(f"You need 'control'-access to view bans on channel {channel.key}")
|
||||||
return
|
return
|
||||||
|
|
||||||
bans = ["Channel bans "
|
bans = [
|
||||||
"(to ban, use channel/ban channel[,channel,...] = username [:reason]"]
|
"Channel bans "
|
||||||
|
"(to ban, use channel/ban channel[,channel,...] = username [:reason]"
|
||||||
|
]
|
||||||
bans.extend(self.channel_list_bans(channel))
|
bans.extend(self.channel_list_bans(channel))
|
||||||
self.msg("\n".join(bans))
|
self.msg("\n".join(bans))
|
||||||
return
|
return
|
||||||
|
|
@ -1191,8 +1240,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}")
|
self.msg(f"Cannot boot {target.key} from channel {chan.key}: {err}")
|
||||||
|
|
||||||
channames = ", ".join(chan.key for chan in channels)
|
channames = ", ".join(chan.key for chan in channels)
|
||||||
reasonwarn = (". Also note that your reason will be echoed to the channel"
|
reasonwarn = (
|
||||||
if reason else '')
|
". Also note that your reason will be echoed to the channel" if reason else ""
|
||||||
|
)
|
||||||
ask_yes_no(
|
ask_yes_no(
|
||||||
caller,
|
caller,
|
||||||
f"Are you sure you want to ban user {target.key} from "
|
f"Are you sure you want to ban user {target.key} from "
|
||||||
|
|
@ -1203,7 +1253,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'unban' in switches:
|
if "unban" in switches:
|
||||||
# unban a previously banned user from channel
|
# unban a previously banned user from channel
|
||||||
|
|
||||||
if not self.access(caller, "admin"):
|
if not self.access(caller, "admin"):
|
||||||
|
|
@ -1414,7 +1464,6 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
receiver=receiver,
|
receiver=receiver,
|
||||||
message=page.message,
|
message=page.message,
|
||||||
)
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
lastpages = "\n ".join(listing)
|
lastpages = "\n ".join(listing)
|
||||||
|
|
||||||
|
|
@ -1465,6 +1514,7 @@ def _list_bots(cmd):
|
||||||
else:
|
else:
|
||||||
return "No irc bots found."
|
return "No irc bots found."
|
||||||
|
|
||||||
|
|
||||||
class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
Link an evennia channel to an external IRC channel
|
Link an evennia channel to an external IRC channel
|
||||||
|
|
|
||||||
|
|
@ -382,10 +382,13 @@ class CmdInventory(COMMAND_DEFAULT_CLASS):
|
||||||
string = "You are not carrying anything."
|
string = "You are not carrying anything."
|
||||||
else:
|
else:
|
||||||
from evennia.utils.ansi import raw as raw_ansi
|
from evennia.utils.ansi import raw as raw_ansi
|
||||||
|
|
||||||
table = self.styled_table(border="header")
|
table = self.styled_table(border="header")
|
||||||
for item in items:
|
for item in items:
|
||||||
table.add_row(f"|C{item.name}|n",
|
table.add_row(
|
||||||
"{}|n".format(utils.crop(raw_ansi(item.db.desc or ""), width=50) or ""))
|
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}"
|
string = f"|wYou are carrying:\n{table}"
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,7 @@ from evennia.utils import create, evmore
|
||||||
from evennia.utils.ansi import ANSIString
|
from evennia.utils.ansi import ANSIString
|
||||||
from evennia.help.filehelp import FILE_HELP_ENTRIES
|
from evennia.help.filehelp import FILE_HELP_ENTRIES
|
||||||
from evennia.utils.eveditor import EvEditor
|
from evennia.utils.eveditor import EvEditor
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import class_from_module, inherits_from, format_grid, pad
|
||||||
class_from_module,
|
|
||||||
inherits_from,
|
|
||||||
format_grid, pad
|
|
||||||
)
|
|
||||||
from evennia.help.utils import help_search_with_index, parse_entry_for_subcategories
|
from evennia.help.utils import help_search_with_index, parse_entry_for_subcategories
|
||||||
|
|
||||||
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
|
CMD_IGNORE_PREFIXES = settings.CMD_IGNORE_PREFIXES
|
||||||
|
|
@ -35,12 +31,14 @@ HELP_CLICKABLE_TOPICS = settings.HELP_CLICKABLE_TOPICS
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class HelpCategory:
|
class HelpCategory:
|
||||||
"""
|
"""
|
||||||
Mock 'help entry' to search categories with the same code.
|
Mock 'help entry' to search categories with the same code.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -113,7 +111,10 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
if type(self).help_more:
|
if type(self).help_more:
|
||||||
usemore = True
|
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:
|
try:
|
||||||
options = self.account.db._saved_webclient_options
|
options = self.account.db._saved_webclient_options
|
||||||
if options and options["helppopup"]:
|
if options and options["helppopup"]:
|
||||||
|
|
@ -127,8 +128,15 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
self.msg(text=(text, {"type": "help"}))
|
self.msg(text=(text, {"type": "help"}))
|
||||||
|
|
||||||
def format_help_entry(self, topic="", help_text="", aliases=None, suggested=None,
|
def format_help_entry(
|
||||||
subtopics=None, click_topics=True):
|
self,
|
||||||
|
topic="",
|
||||||
|
help_text="",
|
||||||
|
aliases=None,
|
||||||
|
suggested=None,
|
||||||
|
subtopics=None,
|
||||||
|
click_topics=True,
|
||||||
|
):
|
||||||
"""This visually formats the help entry.
|
"""This visually formats the help entry.
|
||||||
This method can be overriden to customize the way a help
|
This method can be overriden to customize the way a help
|
||||||
entry is displayed.
|
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"
|
title = f"|CHelp for |w{topic}|n" if topic else "|rNo help found|n"
|
||||||
|
|
||||||
if aliases:
|
if aliases:
|
||||||
aliases = (
|
aliases = " |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases))
|
||||||
" |C(aliases: {}|C)|n".format("|C,|n ".join(f"|w{ali}|n" for ali in aliases))
|
|
||||||
)
|
|
||||||
else:
|
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 subtopics:
|
||||||
if click_topics:
|
if click_topics:
|
||||||
subtopics = [
|
subtopics = [
|
||||||
f"|lchelp {topic}/{subtop}|lt|w{topic}/{subtop}|n|le"
|
f"|lchelp {topic}/{subtop}|lt|w{topic}/{subtop}|n|le" for subtop in subtopics
|
||||||
for subtop in subtopics
|
]
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics]
|
subtopics = [f"|w{topic}/{subtop}|n" for subtop in subtopics]
|
||||||
subtopics = (
|
subtopics = "\n|CSubtopics:|n\n {}".format(
|
||||||
"\n|CSubtopics:|n\n {}".format(
|
"\n ".join(format_grid(subtopics, width=self.client_width()))
|
||||||
"\n ".join(format_grid(subtopics, width=self.client_width())))
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
subtopics = ''
|
subtopics = ""
|
||||||
|
|
||||||
if suggested:
|
if suggested:
|
||||||
suggested = sorted(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]
|
suggested = [f"|lchelp {sug}|lt|w{sug}|n|le" for sug in suggested]
|
||||||
else:
|
else:
|
||||||
suggested = [f"|w{sug}|n" for sug in suggested]
|
suggested = [f"|w{sug}|n" for sug in suggested]
|
||||||
suggested = (
|
suggested = "\n|COther topic suggestions:|n\n{}".format(
|
||||||
"\n|COther topic suggestions:|n\n{}".format(
|
"\n ".join(format_grid(suggested, width=self.client_width()))
|
||||||
"\n ".join(format_grid(suggested, width=self.client_width())))
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
suggested = ''
|
suggested = ""
|
||||||
|
|
||||||
end = start
|
end = start
|
||||||
|
|
||||||
|
|
@ -194,8 +197,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
return "\n".join(part.rstrip() for part in partorder if part)
|
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,
|
def format_help_index(
|
||||||
click_topics=True):
|
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
|
"""Output a category-ordered g for displaying the main help, grouped by
|
||||||
category.
|
category.
|
||||||
|
|
||||||
|
|
@ -219,6 +223,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
commands and topics.
|
commands and topics.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _group_by_category(help_dict):
|
def _group_by_category(help_dict):
|
||||||
grid = []
|
grid = []
|
||||||
verbatim_elements = []
|
verbatim_elements = []
|
||||||
|
|
@ -231,9 +236,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# make the help topics clickable
|
# make the help topics clickable
|
||||||
if click_topics:
|
if click_topics:
|
||||||
entries = [
|
entries = [f"|lchelp {entry}|lt{entry}|le" for entry in entries]
|
||||||
f'|lchelp {entry}|lt{entry}|le' for entry in entries
|
|
||||||
]
|
|
||||||
|
|
||||||
# add the entries to the grid
|
# add the entries to the grid
|
||||||
grid.extend(entries)
|
grid.extend(entries)
|
||||||
|
|
@ -243,7 +246,8 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
category_str = f"-- {category.title()} "
|
category_str = f"-- {category.title()} "
|
||||||
grid.append(
|
grid.append(
|
||||||
ANSIString(
|
ANSIString(
|
||||||
self.index_category_clr + category_str
|
self.index_category_clr
|
||||||
|
+ category_str
|
||||||
+ "-" * (width - len(category_str))
|
+ "-" * (width - len(category_str))
|
||||||
+ self.index_topic_clr
|
+ self.index_topic_clr
|
||||||
)
|
)
|
||||||
|
|
@ -255,9 +259,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# make the help topics clickable
|
# make the help topics clickable
|
||||||
if click_topics:
|
if click_topics:
|
||||||
entries = [
|
entries = [f"|lchelp {entry}|lt{entry}|le" for entry in entries]
|
||||||
f'|lchelp {entry}|lt{entry}|le' for entry in entries
|
|
||||||
]
|
|
||||||
|
|
||||||
# add the entries to the grid
|
# add the entries to the grid
|
||||||
grid.extend(entries)
|
grid.extend(entries)
|
||||||
|
|
@ -272,18 +274,22 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if any(cmd_help_dict.values()):
|
if any(cmd_help_dict.values()):
|
||||||
# get the command-help entries by-category
|
# get the command-help entries by-category
|
||||||
sep1 = (self.index_type_separator_clr
|
sep1 = (
|
||||||
+ pad("Commands", width=width, fillchar='-')
|
self.index_type_separator_clr
|
||||||
+ self.index_topic_clr)
|
+ pad("Commands", width=width, fillchar="-")
|
||||||
|
+ self.index_topic_clr
|
||||||
|
)
|
||||||
grid, verbatim_elements = _group_by_category(cmd_help_dict)
|
grid, verbatim_elements = _group_by_category(cmd_help_dict)
|
||||||
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
||||||
cmd_grid = ANSIString("\n").join(gridrows) if gridrows else ""
|
cmd_grid = ANSIString("\n").join(gridrows) if gridrows else ""
|
||||||
|
|
||||||
if any(db_help_dict.values()):
|
if any(db_help_dict.values()):
|
||||||
# get db-based help entries by-category
|
# get db-based help entries by-category
|
||||||
sep2 = (self.index_type_separator_clr
|
sep2 = (
|
||||||
+ pad("Game & World", width=width, fillchar='-')
|
self.index_type_separator_clr
|
||||||
+ self.index_topic_clr)
|
+ pad("Game & World", width=width, fillchar="-")
|
||||||
|
+ self.index_topic_clr
|
||||||
|
)
|
||||||
grid, verbatim_elements = _group_by_category(db_help_dict)
|
grid, verbatim_elements = _group_by_category(db_help_dict)
|
||||||
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
gridrows = format_grid(grid, width, sep=" ", verbatim_elements=verbatim_elements)
|
||||||
db_grid = ANSIString("\n").join(gridrows) if gridrows else ""
|
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"):
|
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:
|
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):
|
def can_list_topic(self, cmd_or_topic, caller):
|
||||||
"""
|
"""
|
||||||
|
|
@ -355,12 +361,12 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_view:
|
if has_view:
|
||||||
return cmd_or_topic.access(caller, 'view', default=True)
|
return cmd_or_topic.access(caller, "view", default=True)
|
||||||
else:
|
else:
|
||||||
# no explicit 'view' lock - use the 'read' lock
|
# 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).
|
Collect help topics from all sources (cmd/db/file).
|
||||||
|
|
||||||
|
|
@ -383,43 +389,45 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
cmdset.make_unique(caller)
|
cmdset.make_unique(caller)
|
||||||
# retrieve all available commands and database / file-help topics.
|
# retrieve all available commands and database / file-help topics.
|
||||||
# also check the 'cmd:' lock here
|
# 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
|
# get all file-based help entries, checking perms
|
||||||
file_help_topics = {
|
file_help_topics = {topic.key.lower().strip(): topic for topic in FILE_HELP_ENTRIES.all()}
|
||||||
topic.key.lower().strip(): topic
|
|
||||||
for topic in FILE_HELP_ENTRIES.all()
|
|
||||||
}
|
|
||||||
# get db-based help entries, checking perms
|
# get db-based help entries, checking perms
|
||||||
db_help_topics = {
|
db_help_topics = {topic.key.lower().strip(): topic for topic in HelpEntry.objects.all()}
|
||||||
topic.key.lower().strip(): topic
|
if mode == "list":
|
||||||
for topic in HelpEntry.objects.all()
|
|
||||||
}
|
|
||||||
if mode == 'list':
|
|
||||||
# check the view lock for all help entries/commands and determine key
|
# check the view lock for all help entries/commands and determine key
|
||||||
cmd_help_topics = {
|
cmd_help_topics = {
|
||||||
cmd.auto_help_display_key
|
cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||||
if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
for cmd in cmd_help_topics
|
||||||
for cmd in cmd_help_topics if self.can_list_topic(cmd, caller)}
|
if self.can_list_topic(cmd, caller)
|
||||||
|
}
|
||||||
db_help_topics = {
|
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)
|
if self.can_list_topic(entry, caller)
|
||||||
}
|
}
|
||||||
file_help_topics = {
|
file_help_topics = {
|
||||||
key: entry for key, entry in file_help_topics.items()
|
key: entry
|
||||||
if self.can_list_topic(entry, caller)}
|
for key, entry in file_help_topics.items()
|
||||||
|
if self.can_list_topic(entry, caller)
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
# query - check the read lock on entries
|
# query - check the read lock on entries
|
||||||
cmd_help_topics = {
|
cmd_help_topics = {
|
||||||
cmd.auto_help_display_key
|
cmd.auto_help_display_key if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
||||||
if hasattr(cmd, "auto_help_display_key") else cmd.key: cmd
|
for cmd in cmd_help_topics
|
||||||
for cmd in cmd_help_topics if self.can_read_topic(cmd, caller)}
|
if self.can_read_topic(cmd, caller)
|
||||||
|
}
|
||||||
db_help_topics = {
|
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)
|
if self.can_read_topic(entry, caller)
|
||||||
}
|
}
|
||||||
file_help_topics = {
|
file_help_topics = {
|
||||||
key: entry for key, entry in file_help_topics.items()
|
key: entry
|
||||||
if self.can_read_topic(entry, caller)}
|
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
|
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
|
# return of this will either be a HelpCategory, a Command or a
|
||||||
# HelpEntry/FileHelpEntry.
|
# HelpEntry/FileHelpEntry.
|
||||||
matches, suggestions = help_search_with_index(
|
matches, suggestions = help_search_with_index(
|
||||||
match_query, entries,
|
match_query, entries, suggestion_maxnum=self.suggestion_maxnum, fields=search_fields
|
||||||
suggestion_maxnum=self.suggestion_maxnum,
|
|
||||||
fields=search_fields
|
|
||||||
)
|
)
|
||||||
if matches:
|
if matches:
|
||||||
match = matches[0]
|
match = matches[0]
|
||||||
|
|
@ -478,8 +484,9 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
# parse the query
|
# parse the query
|
||||||
|
|
||||||
if self.args:
|
if self.args:
|
||||||
self.subtopics = [part.strip().lower()
|
self.subtopics = [
|
||||||
for part in self.args.split(self.subtopic_separator_char)]
|
part.strip().lower() for part in self.args.split(self.subtopic_separator_char)
|
||||||
|
]
|
||||||
self.topic = self.subtopics.pop(0)
|
self.topic = self.subtopics.pop(0)
|
||||||
else:
|
else:
|
||||||
self.topic = ""
|
self.topic = ""
|
||||||
|
|
@ -505,7 +512,6 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
return key[1:]
|
return key[1:]
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
Run the dynamic help entry creator.
|
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
|
# list all available help entries, grouped by category. We want to
|
||||||
# build dictionaries {category: [topic, topic, ...], ...}
|
# build dictionaries {category: [topic, topic, ...], ...}
|
||||||
|
|
||||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics(
|
||||||
self.collect_topics(caller, mode='list')
|
caller, mode="list"
|
||||||
|
)
|
||||||
|
|
||||||
# db-topics override file-based ones
|
# db-topics override file-based ones
|
||||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
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)
|
file_db_help_by_category[entry.help_category].append(key)
|
||||||
|
|
||||||
# generate the index and display
|
# generate the index and display
|
||||||
output = self.format_help_index(cmd_help_by_category,
|
output = self.format_help_index(
|
||||||
file_db_help_by_category,
|
cmd_help_by_category, file_db_help_by_category, click_topics=clickable_topics
|
||||||
click_topics=clickable_topics)
|
)
|
||||||
self.msg_help(output)
|
self.msg_help(output)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# search for a specific entry. We need to check for 'read' access here before
|
# search for a specific entry. We need to check for 'read' access here before
|
||||||
# building the set of possibilities.
|
# building the set of possibilities.
|
||||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics(
|
||||||
self.collect_topics(caller, mode='query')
|
caller, mode="query"
|
||||||
|
)
|
||||||
|
|
||||||
# get a collection of all keys + aliases to be able to strip prefixes like @
|
# get a collection of all keys + aliases to be able to strip prefixes like @
|
||||||
key_and_aliases = set(
|
key_and_aliases = set(chain(*(cmd._keyaliases for cmd in cmd_help_topics.values())))
|
||||||
chain(*(cmd._keyaliases for cmd in cmd_help_topics.values())))
|
|
||||||
|
|
||||||
# db-help topics takes priority over file-help
|
# db-help topics takes priority over file-help
|
||||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
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}
|
all_topics = {**file_db_help_topics, **cmd_help_topics}
|
||||||
|
|
||||||
# get all categories
|
# get all categories
|
||||||
all_categories = list(set(
|
all_categories = list(
|
||||||
HelpCategory(topic.help_category) for topic in all_topics.values()))
|
set(HelpCategory(topic.help_category) for topic in all_topics.values())
|
||||||
|
)
|
||||||
|
|
||||||
# all available help options - will be searched in order. We also check # the
|
# all available help options - will be searched in order. We also check # the
|
||||||
# read-permission here.
|
# read-permission here.
|
||||||
|
|
@ -586,23 +594,26 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
for match_query in [query, f"{query}*", f"*{query}"]:
|
for match_query in [query, f"{query}*", f"*{query}"]:
|
||||||
_, suggestions = help_search_with_index(
|
_, suggestions = help_search_with_index(
|
||||||
match_query, entries,
|
match_query,
|
||||||
|
entries,
|
||||||
suggestion_maxnum=self.suggestion_maxnum,
|
suggestion_maxnum=self.suggestion_maxnum,
|
||||||
fields=search_fields
|
fields=search_fields,
|
||||||
)
|
)
|
||||||
if suggestions:
|
if suggestions:
|
||||||
help_text += (
|
help_text += (
|
||||||
"\n... But matches where found within the help "
|
"\n... But matches where found within the help "
|
||||||
"texts of the suggestions below.")
|
"texts of the suggestions below."
|
||||||
suggestions = [self.strip_cmd_prefix(sugg, key_and_aliases)
|
)
|
||||||
for sugg in suggestions]
|
suggestions = [
|
||||||
|
self.strip_cmd_prefix(sugg, key_and_aliases) for sugg in suggestions
|
||||||
|
]
|
||||||
break
|
break
|
||||||
|
|
||||||
output = self.format_help_entry(
|
output = self.format_help_entry(
|
||||||
topic=None, # this will give a no-match style title
|
topic=None, # this will give a no-match style title
|
||||||
help_text=help_text,
|
help_text=help_text,
|
||||||
suggested=suggestions,
|
suggested=suggestions,
|
||||||
click_topics=clickable_topics
|
click_topics=clickable_topics,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.msg_help(output)
|
self.msg_help(output)
|
||||||
|
|
@ -612,14 +623,20 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
# no subtopics for categories - these are just lists of topics
|
# no subtopics for categories - these are just lists of topics
|
||||||
category = match.key
|
category = match.key
|
||||||
category_lower = category.lower()
|
category_lower = category.lower()
|
||||||
cmds_in_category = [key for key, cmd in cmd_help_topics.items()
|
cmds_in_category = [
|
||||||
if category_lower == cmd.help_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]
|
topics_in_category = [
|
||||||
output = self.format_help_index({category: cmds_in_category},
|
key
|
||||||
{category: topics_in_category},
|
for key, topic in file_db_help_topics.items()
|
||||||
title_lone_category=True,
|
if category_lower == topic.help_category
|
||||||
click_topics=clickable_topics)
|
]
|
||||||
|
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)
|
self.msg_help(output)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -674,7 +691,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
topic=topic,
|
topic=topic,
|
||||||
help_text=f"No help entry found for '{checked_topic}'",
|
help_text=f"No help entry found for '{checked_topic}'",
|
||||||
subtopics=subtopic_index,
|
subtopics=subtopic_index,
|
||||||
click_topics=clickable_topics
|
click_topics=clickable_topics,
|
||||||
)
|
)
|
||||||
self.msg_help(output)
|
self.msg_help(output)
|
||||||
return
|
return
|
||||||
|
|
@ -702,7 +719,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
aliases=aliases,
|
aliases=aliases,
|
||||||
subtopics=subtopic_index,
|
subtopics=subtopic_index,
|
||||||
suggested=suggested,
|
suggested=suggested,
|
||||||
click_topics=clickable_topics
|
click_topics=clickable_topics,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.msg_help(output)
|
self.msg_help(output)
|
||||||
|
|
@ -829,15 +846,17 @@ class CmdSetHelp(CmdHelp):
|
||||||
|
|
||||||
# check if we have an old entry with the same name
|
# check if we have an old entry with the same name
|
||||||
|
|
||||||
cmd_help_topics, db_help_topics, file_help_topics = \
|
cmd_help_topics, db_help_topics, file_help_topics = self.collect_topics(
|
||||||
self.collect_topics(self.caller, mode='query')
|
self.caller, mode="query"
|
||||||
|
)
|
||||||
# db-help topics takes priority over file-help
|
# db-help topics takes priority over file-help
|
||||||
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
file_db_help_topics = {**file_help_topics, **db_help_topics}
|
||||||
# commands take priority over the other types
|
# commands take priority over the other types
|
||||||
all_topics = {**file_db_help_topics, **cmd_help_topics}
|
all_topics = {**file_db_help_topics, **cmd_help_topics}
|
||||||
# get all categories
|
# get all categories
|
||||||
all_categories = list(set(
|
all_categories = list(
|
||||||
HelpCategory(topic.help_category) for topic in all_topics.values()))
|
set(HelpCategory(topic.help_category) for topic in all_topics.values())
|
||||||
|
)
|
||||||
# all available help options - will be searched in order. We also check # the
|
# all available help options - will be searched in order. We also check # the
|
||||||
# read-permission here.
|
# read-permission here.
|
||||||
entries = list(all_topics.values()) + all_categories
|
entries = list(all_topics.values()) + all_categories
|
||||||
|
|
@ -853,29 +872,35 @@ class CmdSetHelp(CmdHelp):
|
||||||
if match:
|
if match:
|
||||||
warning = None
|
warning = None
|
||||||
if isinstance(match, HelpCategory):
|
if isinstance(match, HelpCategory):
|
||||||
warning = (f"'{querystr}' matches (or partially matches) the name of "
|
warning = (
|
||||||
"help-category '{match.key}'. If you continue, your help entry will "
|
f"'{querystr}' matches (or partially matches) the name of "
|
||||||
"take precedence and the category (or part of its name) *may* not "
|
"help-category '{match.key}'. If you continue, your help entry will "
|
||||||
"be usable for grouping help entries anymore.")
|
"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"):
|
elif inherits_from(match, "evennia.commands.command.Command"):
|
||||||
warning = (f"'{querystr}' matches (or partially matches) the key/alias of "
|
warning = (
|
||||||
"Command '{match.key}'. Command-help take precedence over other "
|
f"'{querystr}' matches (or partially matches) the key/alias of "
|
||||||
"help entries so your help *may* be impossible to reach for those "
|
"Command '{match.key}'. Command-help take precedence over other "
|
||||||
"with access to that command.")
|
"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"):
|
elif inherits_from(match, "evennia.help.filehelp.FileHelpEntry"):
|
||||||
warning = (f"'{querystr}' matches (or partially matches) the name/alias of the "
|
warning = (
|
||||||
f"file-based help topic '{match.key}'. File-help entries cannot be "
|
f"'{querystr}' matches (or partially matches) the name/alias of the "
|
||||||
"modified from in-game (they are files on-disk). If you continue, "
|
f"file-based help topic '{match.key}'. File-help entries cannot be "
|
||||||
"your help entry may shadow the file-based one's name partly or "
|
"modified from in-game (they are files on-disk). If you continue, "
|
||||||
"completely.")
|
"your help entry may shadow the file-based one's name partly or "
|
||||||
|
"completely."
|
||||||
|
)
|
||||||
if warning:
|
if warning:
|
||||||
# show a warning for a clashing help-entry type. Even if user accepts this
|
# 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 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
|
# We don't count this as an old-entry hit because we can't edit these
|
||||||
# types of entries.
|
# types of entries.
|
||||||
self.msg(f"|rWarning:\n|r{warning}|n")
|
self.msg(f"|rWarning:\n|r{warning}|n")
|
||||||
repl = yield("|wDo you still want to continue? Y/[N]?|n")
|
repl = yield ("|wDo you still want to continue? Y/[N]?|n")
|
||||||
if repl.lower() not in ('y', 'yes'):
|
if repl.lower() not in ("y", "yes"):
|
||||||
self.msg("Aborted.")
|
self.msg("Aborted.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
|
@ -897,7 +922,11 @@ class CmdSetHelp(CmdHelp):
|
||||||
helpentry = old_entry
|
helpentry = old_entry
|
||||||
else:
|
else:
|
||||||
helpentry = create.create_help_entry(
|
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
|
self.caller.db._editing_help = helpentry
|
||||||
|
|
||||||
|
|
@ -976,6 +1005,4 @@ class CmdSetHelp(CmdHelp):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.msg(
|
self.msg(f"Error when creating topic '{topicstr}'{aliastxt}! Contact an admin.")
|
||||||
f"Error when creating topic '{topicstr}'{aliastxt}! Contact an admin."
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -593,13 +593,15 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
||||||
if delmode:
|
if delmode:
|
||||||
caller.msg("You cannot remove a core Evennia service (named 'Evennia*').")
|
caller.msg("You cannot remove a core Evennia service (named 'Evennia*').")
|
||||||
return
|
return
|
||||||
string = ("|RYou seem to be shutting down a core Evennia "
|
string = (
|
||||||
"service (named 'Evennia*').\nNote that stopping "
|
"|RYou seem to be shutting down a core Evennia "
|
||||||
"some TCP port services will *not* disconnect users "
|
"service (named 'Evennia*').\nNote that stopping "
|
||||||
"*already* connected on those ports, but *may* "
|
"some TCP port services will *not* disconnect users "
|
||||||
"instead cause spurious errors for them.\nTo safely "
|
"*already* connected on those ports, but *may* "
|
||||||
"and permanently remove ports, change settings file "
|
"instead cause spurious errors for them.\nTo safely "
|
||||||
"and restart the server.|n\n")
|
"and permanently remove ports, change settings file "
|
||||||
|
"and restart the server.|n\n"
|
||||||
|
)
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
if delmode:
|
if delmode:
|
||||||
|
|
@ -611,9 +613,11 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
||||||
try:
|
try:
|
||||||
service.stopService()
|
service.stopService()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
caller.msg(f"|rErrors were reported when stopping this service{err}.\n"
|
caller.msg(
|
||||||
"If there are remaining problems, try reloading "
|
f"|rErrors were reported when stopping this service{err}.\n"
|
||||||
"or rebooting the server.")
|
"If there are remaining problems, try reloading "
|
||||||
|
"or rebooting the server."
|
||||||
|
)
|
||||||
caller.msg("|g... Stopped service '%s'.|n" % self.args)
|
caller.msg("|g... Stopped service '%s'.|n" % self.args)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -626,9 +630,11 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
||||||
try:
|
try:
|
||||||
service.startService()
|
service.startService()
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
caller.msg(f"|rErrors were reported when starting this service{err}.\n"
|
caller.msg(
|
||||||
"If there are remaining problems, try reloading the server, changing the "
|
f"|rErrors were reported when starting this service{err}.\n"
|
||||||
"settings if it's a non-standard service.|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")
|
caller.msg("|gService started.|n")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -973,8 +979,8 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def coll_date_func(task):
|
def coll_date_func(task):
|
||||||
"""Replace regex characters in date string and collect deferred function name."""
|
"""Replace regex characters in date string and collect deferred function name."""
|
||||||
t_comp_date = str(task[0]).replace('-', '/')
|
t_comp_date = str(task[0]).replace("-", "/")
|
||||||
t_func_name = str(task[1]).split(' ')
|
t_func_name = str(task[1]).split(" ")
|
||||||
t_func_mem_ref = t_func_name[3] if len(t_func_name) >= 4 else None
|
t_func_mem_ref = t_func_name[3] if len(t_func_name) >= 4 else None
|
||||||
return t_comp_date, t_func_mem_ref
|
return t_comp_date, t_func_mem_ref
|
||||||
|
|
||||||
|
|
@ -994,19 +1000,19 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
||||||
# verify manipulating the correct task
|
# verify manipulating the correct task
|
||||||
task_args = _TASK_HANDLER.tasks.get(task_id, False)
|
task_args = _TASK_HANDLER.tasks.get(task_id, False)
|
||||||
if not task_args: # check if the task is still active
|
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
|
return
|
||||||
else:
|
else:
|
||||||
# make certain a task with matching IDs has not been created
|
# make certain a task with matching IDs has not been created
|
||||||
t_comp_date, t_func_mem_ref = self.coll_date_func(task_args)
|
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:
|
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
|
return
|
||||||
|
|
||||||
# Do the action requested by command caller
|
# Do the action requested by command caller
|
||||||
action_return = self.task_action()
|
action_return = self.task_action()
|
||||||
self.msg(f'{self.action_request} request completed.')
|
self.msg(f"{self.action_request} request completed.")
|
||||||
self.msg(f'The task function {self.action_request} returned: {action_return}')
|
self.msg(f"The task function {self.action_request} returned: {action_return}")
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
# get a reference of the global task handler
|
# 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
|
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
|
||||||
# handle no tasks active.
|
# handle no tasks active.
|
||||||
if not _TASK_HANDLER.tasks:
|
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:
|
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
|
return
|
||||||
|
|
||||||
# handle caller's request to manipulate a task(s)
|
# 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 the argument is a task id, proccess the action on a single task
|
||||||
if arg_is_id:
|
if arg_is_id:
|
||||||
|
|
||||||
err_arg_msg = 'Switch and task ID are required when manipulating a task.'
|
err_arg_msg = "Switch and task ID are required when manipulating a task."
|
||||||
task_comp_msg = 'Task completed while processing request.'
|
task_comp_msg = "Task completed while processing request."
|
||||||
|
|
||||||
# handle missing arguments or switches
|
# handle missing arguments or switches
|
||||||
if not self.switches and self.lhs:
|
if not self.switches and self.lhs:
|
||||||
|
|
@ -1047,14 +1053,16 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# handle task no longer existing
|
# handle task no longer existing
|
||||||
if not task.exists():
|
if not task.exists():
|
||||||
self.msg(f'Task {task_id} does not exist.')
|
self.msg(f"Task {task_id} does not exist.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# get a reference of the function caller requested
|
# get a reference of the function caller requested
|
||||||
switch_action = getattr(task, action_request, False)
|
switch_action = getattr(task, action_request, False)
|
||||||
if not switch_action:
|
if not switch_action:
|
||||||
self.msg(f'{self.switches[0]}, is not an acceptable task action or ' \
|
self.msg(
|
||||||
f'{task_comp_msg.lower()}')
|
f"{self.switches[0]}, is not an acceptable task action or "
|
||||||
|
f"{task_comp_msg.lower()}"
|
||||||
|
)
|
||||||
|
|
||||||
# verify manipulating the correct task
|
# verify manipulating the correct task
|
||||||
if task_id in _TASK_HANDLER.tasks:
|
if task_id in _TASK_HANDLER.tasks:
|
||||||
|
|
@ -1064,25 +1072,29 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
t_comp_date, t_func_mem_ref = self.coll_date_func(task_args)
|
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
|
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.
|
if task.exists(): # make certain the task has not been called yet.
|
||||||
prompt = (f'{action_request.capitalize()} task {task_id} with completion date '
|
prompt = (
|
||||||
f'{t_comp_date} ({t_func_name}) {{options}}?')
|
f"{action_request.capitalize()} task {task_id} with completion date "
|
||||||
no_msg = f'No {action_request} processed.'
|
f"{t_comp_date} ({t_func_name}) {{options}}?"
|
||||||
|
)
|
||||||
|
no_msg = f"No {action_request} processed."
|
||||||
# record variables for use in do_task_action method
|
# record variables for use in do_task_action method
|
||||||
self.task_id = task_id
|
self.task_id = task_id
|
||||||
self.t_comp_date = t_comp_date
|
self.t_comp_date = t_comp_date
|
||||||
self.t_func_mem_ref = t_func_mem_ref
|
self.t_func_mem_ref = t_func_mem_ref
|
||||||
self.task_action = switch_action
|
self.task_action = switch_action
|
||||||
self.action_request = action_request
|
self.action_request = action_request
|
||||||
ask_yes_no(self.caller,
|
ask_yes_no(
|
||||||
prompt=prompt,
|
self.caller,
|
||||||
yes_action=self.do_task_action,
|
prompt=prompt,
|
||||||
no_action=no_msg,
|
yes_action=self.do_task_action,
|
||||||
default="Y",
|
no_action=no_msg,
|
||||||
allow_abort=True)
|
default="Y",
|
||||||
|
allow_abort=True,
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.msg(task_comp_msg)
|
self.msg(task_comp_msg)
|
||||||
|
|
@ -1102,7 +1114,7 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# call requested action on all tasks with the function name
|
# call requested action on all tasks with the function name
|
||||||
for task_id, task_args in current_tasks.items():
|
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
|
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
|
# skip this task if it is not for the function desired
|
||||||
if arg_func_name != t_func_name:
|
if arg_func_name != t_func_name:
|
||||||
|
|
@ -1112,33 +1124,39 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
||||||
switch_action = getattr(task, action_request, False)
|
switch_action = getattr(task, action_request, False)
|
||||||
if switch_action:
|
if switch_action:
|
||||||
action_return = switch_action()
|
action_return = switch_action()
|
||||||
self.msg(f'Task action {action_request} completed on task ID {task_id}.')
|
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"The task function {action_request} returned: {action_return}")
|
||||||
|
|
||||||
# provide a message if not tasks of the function name was found
|
# provide a message if not tasks of the function name was found
|
||||||
if not name_match_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
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# check if an maleformed request was created
|
# check if an maleformed request was created
|
||||||
elif self.switches or self.lhs:
|
elif self.switches or self.lhs:
|
||||||
self.msg('Task command misformed.')
|
self.msg("Task command misformed.")
|
||||||
self.msg('Proper format tasks[/switch] [function name or task id]')
|
self.msg("Proper format tasks[/switch] [function name or task id]")
|
||||||
return
|
return
|
||||||
|
|
||||||
# No task manupilation requested, build a table of tasks and display it
|
# No task manupilation requested, build a table of tasks and display it
|
||||||
# get the width of screen in characters
|
# get the width of screen in characters
|
||||||
width = self.client_width()
|
width = self.client_width()
|
||||||
# create table header and list to hold tasks data and actions
|
# create table header and list to hold tasks data and actions
|
||||||
tasks_header = ('Task ID', 'Completion Date', 'Function', 'Arguments', 'KWARGS',
|
tasks_header = (
|
||||||
'persistent')
|
"Task ID",
|
||||||
|
"Completion Date",
|
||||||
|
"Function",
|
||||||
|
"Arguments",
|
||||||
|
"KWARGS",
|
||||||
|
"persistent",
|
||||||
|
)
|
||||||
# empty list of lists, the size of the header
|
# empty list of lists, the size of the header
|
||||||
tasks_list = [list() for i in range(len(tasks_header))]
|
tasks_list = [list() for i in range(len(tasks_header))]
|
||||||
for task_id, task in _TASK_HANDLER.tasks.items():
|
for task_id, task in _TASK_HANDLER.tasks.items():
|
||||||
# collect data from the task
|
# collect data from the task
|
||||||
t_comp_date, t_func_mem_ref = self.coll_date_func(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_func_name = t_func_name[1] if len(t_func_name) >= 2 else None
|
||||||
t_args = str(task[2])
|
t_args = str(task[2])
|
||||||
t_kwargs = str(task[3])
|
t_kwargs = str(task[3])
|
||||||
|
|
@ -1148,8 +1166,9 @@ class CmdTasks(COMMAND_DEFAULT_CLASS):
|
||||||
for i in range(len(tasks_header)):
|
for i in range(len(tasks_header)):
|
||||||
tasks_list[i].append(task_data[i])
|
tasks_list[i].append(task_data[i])
|
||||||
# create and display the table
|
# create and display the table
|
||||||
tasks_table = EvTable(*tasks_header, table=tasks_list, maxwidth=width, border='cells',
|
tasks_table = EvTable(
|
||||||
align='center')
|
*tasks_header, table=tasks_list, maxwidth=width, border="cells", align="center"
|
||||||
actions = (f'/{switch}' for switch in self.switch_options)
|
)
|
||||||
|
actions = (f"/{switch}" for switch in self.switch_options)
|
||||||
helptxt = f"\nActions: {iter_to_str(actions)}"
|
helptxt = f"\nActions: {iter_to_str(actions)}"
|
||||||
self.msg(str(tasks_table) + helptxt)
|
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
|
non_normalized_username = username
|
||||||
username = Account.normalize_username(username)
|
username = Account.normalize_username(username)
|
||||||
if non_normalized_username != username:
|
if non_normalized_username != username:
|
||||||
session.msg("Note: your username was normalized to strip spaces and remove characters "
|
session.msg(
|
||||||
"that could be visually confusing.")
|
"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
|
# have the user verify their new account was what they intended
|
||||||
answer = yield(f"You want to create an account '{username}' with password '{password}'."
|
answer = yield (
|
||||||
"\nIs this what you intended? [Y]/N?")
|
f"You want to create an account '{username}' with password '{password}'."
|
||||||
if answer.lower() in ('n', 'no'):
|
"\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.")
|
session.msg("Aborted. If your user name contains spaces, surround it by quotes.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -344,7 +348,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
If you don't submit an encoding, the current encoding will be displayed
|
If you don't submit an encoding, the current encoding will be displayed
|
||||||
instead.
|
instead.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "encoding"
|
key = "encoding"
|
||||||
aliases = "encode"
|
aliases = "encode"
|
||||||
|
|
|
||||||
|
|
@ -339,7 +339,7 @@ class TestOptionTransferTrue(TestCase):
|
||||||
b.no_objs = False
|
b.no_objs = False
|
||||||
d.duplicates = False
|
d.duplicates = False
|
||||||
# higher-prio sets will change the option up the chain
|
# 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_exits)
|
||||||
self.assertTrue(cmdset_f.no_objs)
|
self.assertTrue(cmdset_f.no_objs)
|
||||||
self.assertTrue(cmdset_f.no_channels)
|
self.assertTrue(cmdset_f.no_channels)
|
||||||
|
|
@ -407,7 +407,7 @@ class TestOptionTransferTrue(TestCase):
|
||||||
c.priority = 1
|
c.priority = 1
|
||||||
d.priority = 2
|
d.priority = 2
|
||||||
c.no_exits = False
|
c.no_exits = False
|
||||||
c.no_channels = None # passthrough
|
c.no_channels = None # passthrough
|
||||||
b.no_objs = False
|
b.no_objs = False
|
||||||
d.duplicates = False
|
d.duplicates = False
|
||||||
# higher-prio sets will change the option up the chain
|
# higher-prio sets will change the option up the chain
|
||||||
|
|
@ -639,7 +639,7 @@ class TestOptionTransferFalse(TestCase):
|
||||||
b.no_objs = True
|
b.no_objs = True
|
||||||
d.duplicates = True
|
d.duplicates = True
|
||||||
# higher-prio sets will change the option up the chain
|
# 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_exits)
|
||||||
self.assertFalse(cmdset_f.no_objs)
|
self.assertFalse(cmdset_f.no_objs)
|
||||||
self.assertFalse(cmdset_f.no_channels)
|
self.assertFalse(cmdset_f.no_channels)
|
||||||
|
|
@ -663,7 +663,7 @@ class TestOptionTransferFalse(TestCase):
|
||||||
b.no_objs = True
|
b.no_objs = True
|
||||||
d.duplicates = True
|
d.duplicates = True
|
||||||
# higher-prio sets will change the option up the chain
|
# 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_exits)
|
||||||
self.assertFalse(cmdset_f.no_objs)
|
self.assertFalse(cmdset_f.no_objs)
|
||||||
self.assertFalse(cmdset_f.no_channels)
|
self.assertFalse(cmdset_f.no_channels)
|
||||||
|
|
@ -707,7 +707,7 @@ class TestOptionTransferFalse(TestCase):
|
||||||
c.priority = 1
|
c.priority = 1
|
||||||
d.priority = 2
|
d.priority = 2
|
||||||
c.no_exits = True
|
c.no_exits = True
|
||||||
c.no_channels = None # passthrough
|
c.no_channels = None # passthrough
|
||||||
b.no_objs = True
|
b.no_objs = True
|
||||||
d.duplicates = True
|
d.duplicates = True
|
||||||
# higher-prio sets will change the option up the chain
|
# 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.
|
Test option transfer through more complex merge types.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.cmdset_a = _CmdSetA()
|
self.cmdset_a = _CmdSetA()
|
||||||
|
|
@ -1182,7 +1183,6 @@ class TestCmdSetNesting(BaseEvenniaTest):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_nest(self):
|
def test_nest(self):
|
||||||
|
|
||||||
class CmdA(Command):
|
class CmdA(Command):
|
||||||
key = "a"
|
key = "a"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
if not self._log_file:
|
if not self._log_file:
|
||||||
self._log_file = self.attributes.get(
|
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
|
return self._log_file
|
||||||
|
|
||||||
def set_log_filename(self, filename):
|
def set_log_filename(self, filename):
|
||||||
|
|
@ -455,8 +456,13 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
# needing to use the `channel` command explicitly.
|
# needing to use the `channel` command explicitly.
|
||||||
msg_nick_pattern = self.channel_msg_nick_pattern.format(alias=alias)
|
msg_nick_pattern = self.channel_msg_nick_pattern.format(alias=alias)
|
||||||
msg_nick_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key)
|
msg_nick_replacement = self.channel_msg_nick_replacement.format(channelname=chan_key)
|
||||||
user.nicks.add(msg_nick_pattern, msg_nick_replacement, category="inputline",
|
user.nicks.add(
|
||||||
pattern_is_regex=True, **kwargs)
|
msg_nick_pattern,
|
||||||
|
msg_nick_replacement,
|
||||||
|
category="inputline",
|
||||||
|
pattern_is_regex=True,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
if chan_key != alias:
|
if chan_key != alias:
|
||||||
# this allows for using the alias for general channel lookups
|
# this allows for using the alias for general channel lookups
|
||||||
|
|
@ -546,7 +552,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
if not bypass_mute:
|
if not bypass_mute:
|
||||||
receivers = [receiver for receiver in receivers if receiver not in self.mutelist]
|
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
|
# pre-send hook
|
||||||
message = self.at_pre_msg(message, **send_kwargs)
|
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.
|
# TODO Evennia 1.0+ removed hooks. Remove in 1.1.
|
||||||
def message_transform(self, *args, **kwargs):
|
def message_transform(self, *args, **kwargs):
|
||||||
raise RuntimeError("Channel.message_transform is no longer used in 1.0+. "
|
raise RuntimeError(
|
||||||
"Use Account/Object.at_pre_channel_msg instead.")
|
"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):
|
def distribute_message(self, msgobj, online=False, **kwargs):
|
||||||
raise RuntimeError("Channel.distribute_message is no longer used in 1.0+.")
|
raise RuntimeError("Channel.distribute_message is no longer used in 1.0+.")
|
||||||
|
|
||||||
def format_senders(self, senders=None, **kwargs):
|
def format_senders(self, senders=None, **kwargs):
|
||||||
raise RuntimeError("Channel.format_senders is no longer used in 1.0+. "
|
raise RuntimeError(
|
||||||
"Use Account/Object.at_pre_channel_msg instead.")
|
"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):
|
def pose_transform(self, msgobj, sender_string, **kwargs):
|
||||||
raise RuntimeError("Channel.pose_transform is no longer used in 1.0+. "
|
raise RuntimeError(
|
||||||
"Use Account/Object.at_pre_channel_msg instead.")
|
"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):
|
def format_external(self, msgobj, senders, emit=False, **kwargs):
|
||||||
raise RuntimeError("Channel.format_external is no longer used in 1.0+. "
|
raise RuntimeError(
|
||||||
"Use Account/Object.at_pre_channel_msg instead.")
|
"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):
|
def format_message(self, msgobj, emit=False, **kwargs):
|
||||||
raise RuntimeError("Channel.format_message is no longer used in 1.0+. "
|
raise RuntimeError(
|
||||||
"Use Account/Object.at_pre_channel_msg instead.")
|
"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):
|
def pre_send_message(self, msg, **kwargs):
|
||||||
raise RuntimeError("Channel.pre_send_message was renamed to Channel.at_pre_msg.")
|
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)
|
return self.filter(db_receivers_accounts=obj).exclude(db_hide_from_accounts=obj)
|
||||||
elif typ == "object":
|
elif typ == "object":
|
||||||
return self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj)
|
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)
|
return self.filter(db_receivers_scripts=obj)
|
||||||
else:
|
else:
|
||||||
raise CommError
|
raise CommError
|
||||||
|
|
@ -257,7 +257,7 @@ class MsgManager(TypedObjectManager):
|
||||||
sender_restrict = Q(db_sender_accounts__pk=spk) & ~Q(db_hide_from_accounts__pk=spk)
|
sender_restrict = Q(db_sender_accounts__pk=spk) & ~Q(db_hide_from_accounts__pk=spk)
|
||||||
elif styp == "object":
|
elif styp == "object":
|
||||||
sender_restrict = Q(db_sender_objects__pk=spk) & ~Q(db_hide_from_objects__pk=spk)
|
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)
|
sender_restrict = Q(db_sender_scripts__pk=spk)
|
||||||
else:
|
else:
|
||||||
sender_restrict = Q()
|
sender_restrict = Q()
|
||||||
|
|
@ -266,16 +266,16 @@ class MsgManager(TypedObjectManager):
|
||||||
if receiver:
|
if receiver:
|
||||||
rpk = receiver.pk
|
rpk = receiver.pk
|
||||||
if rtyp == "account":
|
if rtyp == "account":
|
||||||
receiver_restrict = (
|
receiver_restrict = Q(db_receivers_accounts__pk=rpk) & ~Q(db_hide_from_accounts__pk=rpk)
|
||||||
Q(db_receivers_accounts__pk=rpk) & ~Q(db_hide_from_accounts__pk=rpk))
|
|
||||||
elif rtyp == "object":
|
elif rtyp == "object":
|
||||||
receiver_restrict = Q(db_receivers_objects__pk=rpk) & ~Q(db_hide_from_objects__pk=rpk)
|
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)
|
receiver_restrict = Q(db_receivers_scripts__pk=rpk)
|
||||||
elif rtyp == "channel":
|
elif rtyp == "channel":
|
||||||
raise DeprecationWarning(
|
raise DeprecationWarning(
|
||||||
"Msg.objects.search don't accept channel recipients since "
|
"Msg.objects.search don't accept channel recipients since "
|
||||||
"Channels no longer accepts Msg objects.")
|
"Channels no longer accepts Msg objects."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
receiver_restrict = Q()
|
receiver_restrict = Q()
|
||||||
# filter by full text
|
# filter by full text
|
||||||
|
|
@ -289,8 +289,9 @@ class MsgManager(TypedObjectManager):
|
||||||
# back-compatibility alias
|
# back-compatibility alias
|
||||||
message_search = search_message
|
message_search = search_message
|
||||||
|
|
||||||
def create_message(self, senderobj, message, receivers=None, locks=None, tags=None,
|
def create_message(
|
||||||
header=None, **kwargs):
|
self, senderobj, message, receivers=None, locks=None, tags=None, header=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create a new communication Msg. Msgs represent a unit of
|
Create a new communication Msg. Msgs represent a unit of
|
||||||
database-persistent communication between entites.
|
database-persistent communication between entites.
|
||||||
|
|
@ -315,7 +316,7 @@ class MsgManager(TypedObjectManager):
|
||||||
it's up to the command definitions to limit this as desired.
|
it's up to the command definitions to limit this as desired.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if 'channels' in kwargs:
|
if "channels" in kwargs:
|
||||||
raise DeprecationWarning(
|
raise DeprecationWarning(
|
||||||
"create_message() does not accept 'channel' kwarg anymore "
|
"create_message() does not accept 'channel' kwarg anymore "
|
||||||
"- channels no longer accept Msg objects."
|
"- channels no longer accept Msg objects."
|
||||||
|
|
@ -339,6 +340,7 @@ class MsgManager(TypedObjectManager):
|
||||||
new_message.save()
|
new_message.save()
|
||||||
return new_message
|
return new_message
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Channel manager
|
# Channel manager
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ class Msg(SharedMemoryModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
help_text="Identifier for single external sender, for use with senders "
|
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(
|
db_receivers_accounts = models.ManyToManyField(
|
||||||
|
|
@ -137,7 +137,7 @@ class Msg(SharedMemoryModel):
|
||||||
blank=True,
|
blank=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
help_text="Identifier for single external receiver, for use with recievers "
|
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
|
# 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):
|
if isinstance(receivers, str):
|
||||||
self.db_receiver_external = receivers
|
self.db_receiver_external = receivers
|
||||||
self.save(update_fields=['db_receiver_external'])
|
self.save(update_fields=["db_receiver_external"])
|
||||||
return
|
return
|
||||||
|
|
||||||
for receiver in make_iter(receivers):
|
for receiver in make_iter(receivers):
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ class ObjectCreationTest(BaseEvenniaTest):
|
||||||
class ChannelWholistTests(BaseEvenniaTest):
|
class ChannelWholistTests(BaseEvenniaTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
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)
|
self.default_channel.connect(self.obj1)
|
||||||
|
|
||||||
def test_wholist_shows_subscribed_objects(self):
|
def test_wholist_shows_subscribed_objects(self):
|
||||||
|
|
@ -31,7 +33,9 @@ class ChannelWholistTests(BaseEvenniaTest):
|
||||||
|
|
||||||
def test_wholist_shows_none_when_empty(self):
|
def test_wholist_shows_none_when_empty(self):
|
||||||
# No one hates dogs
|
# 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>"
|
expected = "<None>"
|
||||||
result = empty_channel.wholist
|
result = empty_channel.wholist
|
||||||
self.assertEqual(expected, result)
|
self.assertEqual(expected, result)
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,9 @@ def check_location(storage):
|
||||||
correct = storage.location.lstrip("/")
|
correct = storage.location.lstrip("/")
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"{}.location cannot begin with a leading slash. Found '{}'. Use '{}' instead.".format(
|
"{}.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 = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
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):
|
def test_storage_save_with_acl(self):
|
||||||
|
|
@ -136,7 +140,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
|
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
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):
|
def test_content_type(self):
|
||||||
|
|
@ -151,7 +159,11 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
|
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
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):
|
def test_storage_save_gzipped(self):
|
||||||
|
|
@ -379,7 +391,10 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
self.assertEqual(uploaded_content, written_content)
|
self.assertEqual(uploaded_content, written_content)
|
||||||
multipart.complete.assert_called_once_with(
|
multipart.complete.assert_called_once_with(
|
||||||
MultipartUpload={
|
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")
|
self.storage._get_or_create_bucket("testbucketname")
|
||||||
Bucket.create.assert_called_once_with(
|
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):
|
def test_auto_creating_bucket_with_acl(self):
|
||||||
|
|
@ -409,22 +427,28 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
)
|
)
|
||||||
self.storage._get_or_create_bucket("testbucketname")
|
self.storage._get_or_create_bucket("testbucketname")
|
||||||
Bucket.create.assert_called_once_with(
|
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):
|
def test_storage_exists(self):
|
||||||
self.assertTrue(self.storage.exists("file.txt"))
|
self.assertTrue(self.storage.exists("file.txt"))
|
||||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
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):
|
def test_storage_exists_false(self):
|
||||||
self.storage.connection.meta.client.head_object.side_effect = ClientError(
|
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.assertFalse(self.storage.exists("file.txt"))
|
||||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
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):
|
def test_storage_exists_doesnt_create_bucket(self):
|
||||||
|
|
@ -445,8 +469,14 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
# 4.txt
|
# 4.txt
|
||||||
pages = [
|
pages = [
|
||||||
{
|
{
|
||||||
"CommonPrefixes": [{"Prefix": "some"}, {"Prefix": "other"},],
|
"CommonPrefixes": [
|
||||||
"Contents": [{"Key": "2.txt"}, {"Key": "4.txt"},],
|
{"Prefix": "some"},
|
||||||
|
{"Prefix": "other"},
|
||||||
|
],
|
||||||
|
"Contents": [
|
||||||
|
{"Key": "2.txt"},
|
||||||
|
{"Key": "4.txt"},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -465,7 +495,14 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
# some/path/1.txt
|
# some/path/1.txt
|
||||||
# some/2.txt
|
# some/2.txt
|
||||||
pages = [
|
pages = [
|
||||||
{"CommonPrefixes": [{"Prefix": "some/path"},], "Contents": [{"Key": "some/2.txt"},],},
|
{
|
||||||
|
"CommonPrefixes": [
|
||||||
|
{"Prefix": "some/path"},
|
||||||
|
],
|
||||||
|
"Contents": [
|
||||||
|
{"Key": "some/2.txt"},
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
paginator = mock.MagicMock()
|
paginator = mock.MagicMock()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Building menu tests.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
from evennia.commands.default.tests import BaseEvenniaCommandTest
|
||||||
from . building_menu import BuildingMenu, CmdNoMatch
|
from .building_menu import BuildingMenu, CmdNoMatch
|
||||||
|
|
||||||
|
|
||||||
class Submenu(BuildingMenu):
|
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:
|
if days <= 0 or weeks <= 0 or months <= 0:
|
||||||
raise ValueError("realtime_to_gametime: days/weeks/months cannot be set <= 0, "
|
raise ValueError(
|
||||||
"they start from 1.")
|
"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 start from 1, we need to adjust them to work mathematically.
|
||||||
days, weeks, months = days - 1, weeks - 1, months - 1
|
days, weeks, months = days - 1, weeks - 1, months - 1
|
||||||
|
|
|
||||||
|
|
@ -31,17 +31,27 @@ class TestEventHandler(BaseEvenniaTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Create the event handler."""
|
"""Create the event handler."""
|
||||||
super().setUp()
|
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
|
# Copy old events if necessary
|
||||||
if OLD_EVENTS:
|
if OLD_EVENTS:
|
||||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||||
|
|
||||||
# Alter typeclasses
|
# Alter typeclasses
|
||||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
self.char1.swap_typeclass(
|
||||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
"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.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")
|
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
|
@ -253,17 +263,27 @@ class TestCmdCallback(BaseEvenniaCommandTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Create the callback handler."""
|
"""Create the callback handler."""
|
||||||
super().setUp()
|
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
|
# Copy old events if necessary
|
||||||
if OLD_EVENTS:
|
if OLD_EVENTS:
|
||||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||||
|
|
||||||
# Alter typeclasses
|
# Alter typeclasses
|
||||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
self.char1.swap_typeclass(
|
||||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
"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.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")
|
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
|
@ -432,17 +452,27 @@ class TestDefaultCallbacks(BaseEvenniaCommandTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""Create the callback handler."""
|
"""Create the callback handler."""
|
||||||
super().setUp()
|
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
|
# Copy old events if necessary
|
||||||
if OLD_EVENTS:
|
if OLD_EVENTS:
|
||||||
self.handler.ndb.events = dict(OLD_EVENTS)
|
self.handler.ndb.events = dict(OLD_EVENTS)
|
||||||
|
|
||||||
# Alter typeclasses
|
# Alter typeclasses
|
||||||
self.char1.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
self.char1.swap_typeclass(
|
||||||
self.char2.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventCharacter")
|
"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.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")
|
self.exit.swap_typeclass("evennia.contrib.base_systems.ingame_python.typeclasses.EventExit")
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,11 @@ from evennia import DefaultCharacter, DefaultExit, DefaultObject, DefaultRoom
|
||||||
from evennia import ScriptDB
|
from evennia import ScriptDB
|
||||||
from evennia.utils.utils import delay, inherits_from, lazy_property
|
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.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 help
|
||||||
CHARACTER_CAN_DELETE = """
|
CHARACTER_CAN_DELETE = """
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,9 @@ def time_event(obj, event_name, number, parameters):
|
||||||
"""
|
"""
|
||||||
seconds, usual, key = get_next_wait(parameters)
|
seconds, usual, key = get_next_wait(parameters)
|
||||||
script = create_script(
|
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.key = key
|
||||||
script.desc = "event on {}".format(key)
|
script.desc = "event on {}".format(key)
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@ _GUEST_ENABLED = settings.GUEST_ENABLED
|
||||||
_ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
_ACCOUNT = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||||
_GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS)
|
_GUEST = class_from_module(settings.BASE_GUEST_TYPECLASS)
|
||||||
|
|
||||||
_ACCOUNT_HELP = (
|
_ACCOUNT_HELP = "Enter a new or existing login name."
|
||||||
"Enter a new or existing login name.")
|
|
||||||
_PASSWORD_HELP = (
|
_PASSWORD_HELP = (
|
||||||
"Password should be a minimum of 8 characters (preferably longer) and "
|
"Password should be a minimum of 8 characters (preferably longer) and "
|
||||||
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
|
"can contain a mix of letters, spaces, digits and @/./+/-/_/'/, only."
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ class CmdDelCom(CmdChannel):
|
||||||
account_caller = True
|
account_caller = True
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implementing the command. """
|
"""Implementing the command."""
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
|
|
@ -207,8 +207,7 @@ class CmdAllCom(CmdChannel):
|
||||||
args = self.args
|
args = self.args
|
||||||
if not args:
|
if not args:
|
||||||
subscribed, available = self.list_channels()
|
subscribed, available = self.list_channels()
|
||||||
self.msg(
|
self.msg("\n|wAvailable channels:\n{table}")
|
||||||
"\n|wAvailable channels:\n{table}")
|
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -353,7 +352,7 @@ class CmdCBoot(CmdChannel):
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
return
|
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:
|
if success:
|
||||||
self.msg(f"Booted {target.key} from {channel.key}")
|
self.msg(f"Booted {target.key} from {channel.key}")
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,7 @@ class EvscaperoomObject(DefaultObject):
|
||||||
# Evennia hooks
|
# Evennia hooks
|
||||||
|
|
||||||
def return_appearance(self, looker, **kwargs):
|
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.
|
contents of the object by default.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -667,8 +667,8 @@ class CraftingRecipe(CraftingRecipeBase):
|
||||||
consumable_kwargs = {}
|
consumable_kwargs = {}
|
||||||
|
|
||||||
if location:
|
if location:
|
||||||
tool_kwargs['location'] = location
|
tool_kwargs["location"] = location
|
||||||
consumable_kwargs['location'] = location
|
consumable_kwargs["location"] = location
|
||||||
|
|
||||||
tool_key = tool_kwargs.pop("key", None)
|
tool_key = tool_kwargs.pop("key", None)
|
||||||
cons_key = consumable_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.
|
things in the current location, like a furnace, windmill or anvil.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "craft"
|
key = "craft"
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ from .crafting import craft, CraftingRecipe, CraftingValidationError
|
||||||
# Sword recipe
|
# Sword recipe
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class PigIronRecipe(CraftingRecipe):
|
class PigIronRecipe(CraftingRecipe):
|
||||||
"""
|
"""
|
||||||
Pig iron is a high-carbon result of melting iron in a blast furnace.
|
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
|
exact_consumable_order = True
|
||||||
|
|
||||||
|
|
||||||
#------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
# Recipes for spell casting
|
# Recipes for spell casting
|
||||||
#------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class _MagicRecipe(CraftingRecipe):
|
class _MagicRecipe(CraftingRecipe):
|
||||||
|
|
@ -348,6 +349,7 @@ class _MagicRecipe(CraftingRecipe):
|
||||||
We also assume that the crafter has skills set on itself as plain Attributes.
|
We also assume that the crafter has skills set on itself as plain Attributes.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = ""
|
name = ""
|
||||||
# all spells require a spellbook and a wand (so there!)
|
# all spells require a spellbook and a wand (so there!)
|
||||||
tool_tags = ["spellbook", "wand"]
|
tool_tags = ["spellbook", "wand"]
|
||||||
|
|
@ -390,15 +392,15 @@ class _MagicRecipe(CraftingRecipe):
|
||||||
skill_value = crafter.attributes.get(skill_name)
|
skill_value = crafter.attributes.get(skill_name)
|
||||||
|
|
||||||
if skill_value is None or skill_value < min_value:
|
if skill_value is None or skill_value < min_value:
|
||||||
self.msg(self.error_too_low_skill_level.format(skill_name=skill_name,
|
self.msg(
|
||||||
spell=self.name))
|
self.error_too_low_skill_level.format(skill_name=skill_name, spell=self.name)
|
||||||
|
)
|
||||||
raise CraftingValidationError
|
raise CraftingValidationError
|
||||||
|
|
||||||
# get the value of the skill to roll
|
# get the value of the skill to roll
|
||||||
self.skill_roll_value = self.crafter.attributes.get(self.skill_roll)
|
self.skill_roll_value = self.crafter.attributes.get(self.skill_roll)
|
||||||
if self.skill_roll_value is None:
|
if self.skill_roll_value is None:
|
||||||
self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll,
|
self.msg(self.error_no_skill_roll.format(skill_name=self.skill_roll, spell=self.name))
|
||||||
spell=self.name))
|
|
||||||
raise CraftingValidationError
|
raise CraftingValidationError
|
||||||
|
|
||||||
def do_craft(self, **kwargs):
|
def do_craft(self, **kwargs):
|
||||||
|
|
@ -446,12 +448,13 @@ class FireballRecipe(_MagicRecipe):
|
||||||
need to be created to understand what they mean when used.
|
need to be created to understand what they mean when used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "fireball"
|
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"
|
skill_roll = "firemagic"
|
||||||
success_message = "A ball of flame appears!"
|
success_message = "A ball of flame appears!"
|
||||||
desired_effects = [('target_fire_damage', 25), ('ranged_attack', -2), ('mana_cost', 12)]
|
desired_effects = [("target_fire_damage", 25), ("ranged_attack", -2), ("mana_cost", 12)]
|
||||||
failure_effects = [('self_fire_damage', 5), ('mana_cost', 5)]
|
failure_effects = [("self_fire_damage", 5), ("mana_cost", 5)]
|
||||||
|
|
||||||
|
|
||||||
class HealingRecipe(_MagicRecipe):
|
class HealingRecipe(_MagicRecipe):
|
||||||
|
|
@ -462,11 +465,12 @@ class HealingRecipe(_MagicRecipe):
|
||||||
need to be created to understand what they mean.
|
need to be created to understand what they mean.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = "heal"
|
name = "heal"
|
||||||
skill_requirements = [('bodymagic', 5), ("empathy", 10)]
|
skill_requirements = [("bodymagic", 5), ("empathy", 10)]
|
||||||
skill_roll = "bodymagic"
|
skill_roll = "bodymagic"
|
||||||
success_message = "You successfully extend your healing aura."
|
success_message = "You successfully extend your healing aura."
|
||||||
desired_effects = [('healing', 15), ('mana_cost', 5)]
|
desired_effects = [("healing", 15), ("mana_cost", 5)]
|
||||||
failure_effects = []
|
failure_effects = []
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -478,7 +482,8 @@ class CmdCast(Command):
|
||||||
cast <spell> <target>
|
cast <spell> <target>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = 'cast'
|
|
||||||
|
key = "cast"
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -488,8 +493,8 @@ class CmdCast(Command):
|
||||||
"""
|
"""
|
||||||
args = self.args.strip().lower()
|
args = self.args.strip().lower()
|
||||||
target = None
|
target = None
|
||||||
if ' ' in args:
|
if " " in args:
|
||||||
self.spellname, *target = args.split(' ', 1)
|
self.spellname, *target = args.split(" ", 1)
|
||||||
else:
|
else:
|
||||||
self.spellname = args
|
self.spellname = args
|
||||||
|
|
||||||
|
|
@ -512,8 +517,9 @@ class CmdCast(Command):
|
||||||
try:
|
try:
|
||||||
# if this completes without an exception, the caster will have
|
# 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.
|
# a new magic_effect set on themselves, ready to use or apply in some way.
|
||||||
success, effects = craft(self.caller, self.spellname, *possible_tools,
|
success, effects = craft(
|
||||||
raise_exception=True)
|
self.caller, self.spellname, *possible_tools, raise_exception=True
|
||||||
|
)
|
||||||
except CraftingValidationError:
|
except CraftingValidationError:
|
||||||
return
|
return
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -527,5 +533,7 @@ class CmdCast(Command):
|
||||||
# (which could be yourself) by a number of health points given by the recipe.
|
# (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)
|
effect_txt = ", ".join(f"{eff[0]}({eff[1]})" for eff in effects)
|
||||||
success_txt = "|gsucceeded|n" if success else "|rfailed|n"
|
success_txt = "|gsucceeded|n" if success else "|rfailed|n"
|
||||||
self.caller.msg(f"Casting the spell {self.spellname} on {self.target} {success_txt}, "
|
self.caller.msg(
|
||||||
f"causing the following effects: {effect_txt}.")
|
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
|
pass
|
||||||
|
|
||||||
def test_error_format(self):
|
def test_error_format(self):
|
||||||
"""Test the automatic error formatter """
|
"""Test the automatic error formatter"""
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
self.crafter, self.tool1, self.tool2, self.cons1, self.cons2, self.cons3
|
||||||
)
|
)
|
||||||
|
|
@ -428,7 +428,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
||||||
self.assertIsNotNone(self.tool2.pk)
|
self.assertIsNotNone(self.tool2.pk)
|
||||||
|
|
||||||
def test_craft_tool_order__fail(self):
|
def test_craft_tool_order__fail(self):
|
||||||
"""Strict tool-order recipe fail """
|
"""Strict tool-order recipe fail"""
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter, self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
self.crafter, self.tool2, self.tool1, self.cons1, self.cons2, self.cons3
|
||||||
)
|
)
|
||||||
|
|
@ -451,7 +451,7 @@ class TestCraftingRecipe(BaseEvenniaTestCase):
|
||||||
self.assertIsNotNone(self.tool2.pk)
|
self.assertIsNotNone(self.tool2.pk)
|
||||||
|
|
||||||
def test_craft_cons_order__fail(self):
|
def test_craft_cons_order__fail(self):
|
||||||
"""Strict tool-order recipe fail """
|
"""Strict tool-order recipe fail"""
|
||||||
recipe = _MockRecipe(
|
recipe = _MockRecipe(
|
||||||
self.crafter, self.tool1, self.tool2, self.cons3, self.cons2, self.cons1
|
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._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=[])
|
@override_settings(CRAFT_RECIPE_MODULES=[])
|
||||||
class TestCraftCommand(BaseEvenniaCommandTest):
|
class TestCraftCommand(BaseEvenniaCommandTest):
|
||||||
"""Test the crafting command"""
|
"""Test the crafting command"""
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ class TestGenderSub(BaseEvenniaCommandTest):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender"
|
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.db.gender = "female"
|
||||||
char.msg("Test |p gender")
|
char.msg("Test |p gender")
|
||||||
mock_msg.assert_called_with("Test her gender", from_obj=None, session=None)
|
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
|
A character able to participate in turn-based combat. Has attributes for current
|
||||||
and maximum HP, and access to combat commands.
|
and maximum HP, and access to combat commands.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rules = COMBAT_RULES
|
rules = COMBAT_RULES
|
||||||
|
|
||||||
def at_object_creation(self):
|
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
|
Fights persist until only one participant is left with any HP or all
|
||||||
remaining participants choose to end the combat with the 'disengage' command.
|
remaining participants choose to end the combat with the 'disengage' command.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rules = COMBAT_RULES
|
rules = COMBAT_RULES
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -258,6 +259,7 @@ class TBEWeapon(DefaultObject):
|
||||||
A weapon which can be wielded in combat with the 'wield' command.
|
A weapon which can be wielded in combat with the 'wield' command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rules = COMBAT_RULES
|
rules = COMBAT_RULES
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
|
|
@ -513,8 +515,9 @@ class CmdWield(Command):
|
||||||
weapon = self.caller.search(self.args, candidates=self.caller.contents)
|
weapon = self.caller.search(self.args, candidates=self.caller.contents)
|
||||||
if not weapon:
|
if not weapon:
|
||||||
return
|
return
|
||||||
if not weapon.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon",
|
if not weapon.is_typeclass(
|
||||||
exact=True):
|
"evennia.contrib.game_systems.turnbattle.tb_equip.TBEWeapon", exact=True
|
||||||
|
):
|
||||||
self.caller.msg("That's not a weapon!")
|
self.caller.msg("That's not a weapon!")
|
||||||
# Remember to update the path to the weapon typeclass if you move this module!
|
# Remember to update the path to the weapon typeclass if you move this module!
|
||||||
return
|
return
|
||||||
|
|
@ -597,8 +600,9 @@ class CmdDon(Command):
|
||||||
armor = self.caller.search(self.args, candidates=self.caller.contents)
|
armor = self.caller.search(self.args, candidates=self.caller.contents)
|
||||||
if not armor:
|
if not armor:
|
||||||
return
|
return
|
||||||
if not armor.is_typeclass("evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor",
|
if not armor.is_typeclass(
|
||||||
exact=True):
|
"evennia.contrib.game_systems.turnbattle.tb_equip.TBEArmor", exact=True
|
||||||
|
):
|
||||||
self.caller.msg("That's not armor!")
|
self.caller.msg("That's not armor!")
|
||||||
# Remember to update the path to the armor typeclass if you move this module!
|
# Remember to update the path to the armor typeclass if you move this module!
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,6 @@ COMBAT FUNCTIONS START HERE
|
||||||
|
|
||||||
|
|
||||||
class ItemCombatRules(tb_basic.BasicCombatRules):
|
class ItemCombatRules(tb_basic.BasicCombatRules):
|
||||||
|
|
||||||
def get_attack(self, attacker, defender):
|
def get_attack(self, attacker, defender):
|
||||||
"""
|
"""
|
||||||
Returns a value for an attack roll.
|
Returns a value for an attack roll.
|
||||||
|
|
@ -699,7 +698,7 @@ AMULET_OF_MIGHT = {
|
||||||
AMULET_OF_WEAKNESS = {
|
AMULET_OF_WEAKNESS = {
|
||||||
"key": "The Amulet of Weakness",
|
"key": "The Amulet of Weakness",
|
||||||
"desc": "The one who holds this amulet can call upon its power to gain great 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_func": "add_condition",
|
||||||
"item_selfonly": True,
|
"item_selfonly": True,
|
||||||
"item_kwargs": {"conditions": [("Damage Down", 3), ("Accuracy Down", 3), ("Defense Down", 3)]},
|
"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.
|
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):
|
def spell_healing(self, caster, spell_name, targets, cost, **kwargs):
|
||||||
"""
|
"""
|
||||||
Spell that restores HP to a target or targets.
|
Spell that restores HP to a target or targets.
|
||||||
|
|
@ -325,11 +325,7 @@ SPELLS = {
|
||||||
"attack_name": ("A jet of flame", "jets of flame"),
|
"attack_name": ("A jet of flame", "jets of flame"),
|
||||||
"damage_range": (25, 35),
|
"damage_range": (25, 35),
|
||||||
},
|
},
|
||||||
"cure wounds": {
|
"cure wounds": {"spellfunc": COMBAT_RULES.spell_healing, "target": "anychar", "cost": 5},
|
||||||
"spellfunc": COMBAT_RULES.spell_healing,
|
|
||||||
"target": "anychar",
|
|
||||||
"cost": 5
|
|
||||||
},
|
|
||||||
"mass cure wounds": {
|
"mass cure wounds": {
|
||||||
"spellfunc": COMBAT_RULES.spell_healing,
|
"spellfunc": COMBAT_RULES.spell_healing,
|
||||||
"target": "anychar",
|
"target": "anychar",
|
||||||
|
|
@ -376,6 +372,7 @@ class TBMagicCharacter(tb_basic.TBBasicCharacter):
|
||||||
and maximum HP, access to combat commands and magic.
|
and maximum HP, access to combat commands and magic.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rules = COMBAT_RULES
|
rules = COMBAT_RULES
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,6 @@ COMBAT FUNCTIONS START HERE
|
||||||
|
|
||||||
|
|
||||||
class RangedCombatRules(tb_basic.BasicCombatRules):
|
class RangedCombatRules(tb_basic.BasicCombatRules):
|
||||||
|
|
||||||
def get_attack(self, attacker, defender, attack_type):
|
def get_attack(self, attacker, defender, attack_type):
|
||||||
"""
|
"""
|
||||||
Returns a value for an attack roll.
|
Returns a value for an attack roll.
|
||||||
|
|
@ -154,7 +153,7 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
||||||
attack_value -= 15
|
attack_value -= 15
|
||||||
return attack_value
|
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
|
Returns a value for defense, which an attack roll must equal or exceed in order
|
||||||
for an attack to hit.
|
for an attack to hit.
|
||||||
|
|
@ -284,8 +283,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
||||||
if thing != mover and thing != target:
|
if thing != mover and thing != target:
|
||||||
# Move away from each object closer to the target than you, if it's also closer to
|
# Move away from each object closer to the target than you, if it's also closer to
|
||||||
# you than you are to the target.
|
# you than you are to the target.
|
||||||
if (self.get_range(mover, thing) >= self.get_range(target, thing)
|
if self.get_range(mover, thing) >= self.get_range(target, thing) and self.get_range(
|
||||||
and self.get_range(mover, thing) < self.get_range(mover, target)):
|
mover, thing
|
||||||
|
) < self.get_range(mover, target):
|
||||||
self.distance_inc(mover, thing)
|
self.distance_inc(mover, thing)
|
||||||
# Move away from anything your target is engaged with
|
# Move away from anything your target is engaged with
|
||||||
if self.get_range(target, thing) == 0:
|
if self.get_range(target, thing) == 0:
|
||||||
|
|
@ -296,8 +296,9 @@ class RangedCombatRules(tb_basic.BasicCombatRules):
|
||||||
# Then, move away from your target.
|
# Then, move away from your target.
|
||||||
self.distance_inc(mover, target)
|
self.distance_inc(mover, target)
|
||||||
|
|
||||||
def resolve_attack(self, attacker, defender, attack_value=None, defense_value=None,
|
def resolve_attack(
|
||||||
attack_type='melee'):
|
self, attacker, defender, attack_value=None, defense_value=None, attack_type="melee"
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Resolves an attack and outputs the result.
|
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
|
A character able to participate in turn-based combat. Has attributes for current
|
||||||
and maximum HP, and access to combat commands.
|
and maximum HP, and access to combat commands.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
rules = COMBAT_RULES
|
rules = COMBAT_RULES
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -797,15 +799,17 @@ class CmdShoot(Command):
|
||||||
in_melee = []
|
in_melee = []
|
||||||
for target in attacker.db.combat_range:
|
for target in attacker.db.combat_range:
|
||||||
# Object is engaged and has HP
|
# Object is engaged and has HP
|
||||||
if (self.rules.get_range(attacker, defender) == 0
|
if (
|
||||||
and target.db.hp and target != self.caller):
|
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
|
in_melee.append(target) # Add to list of targets in melee
|
||||||
|
|
||||||
if len(in_melee) > 0:
|
if len(in_melee) > 0:
|
||||||
self.caller.msg(
|
self.caller.msg(
|
||||||
"You can't shoot because there are fighters engaged with you (%s) - you need "
|
"You can't shoot because there are fighters engaged with you (%s) - you need "
|
||||||
"to retreat! (see: help withdraw)"
|
"to retreat! (see: help withdraw)" % ", ".join(obj.key for obj in in_melee)
|
||||||
% ", ".join(obj.key for obj in in_melee)
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,8 +133,9 @@ class TestTurnBattleBasicFunc(BaseEvenniaTest):
|
||||||
self.assertTrue(self.defender.db.hp == 7)
|
self.assertTrue(self.defender.db.hp == 7)
|
||||||
# Resolve attack
|
# Resolve attack
|
||||||
self.defender.db.hp = 40
|
self.defender.db.hp = 40
|
||||||
tb_basic.COMBAT_RULES.resolve_attack(self.attacker, self.defender,
|
tb_basic.COMBAT_RULES.resolve_attack(
|
||||||
attack_value=20, defense_value=10)
|
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||||
|
)
|
||||||
self.assertTrue(self.defender.db.hp < 40)
|
self.assertTrue(self.defender.db.hp < 40)
|
||||||
# Combat cleanup
|
# Combat cleanup
|
||||||
self.attacker.db.Combat_attribute = True
|
self.attacker.db.Combat_attribute = True
|
||||||
|
|
@ -227,7 +228,9 @@ class TestTurnBattleEquipFunc(BaseEvenniaTest):
|
||||||
self.assertTrue(self.defender.db.hp == 7)
|
self.assertTrue(self.defender.db.hp == 7)
|
||||||
# Resolve attack
|
# Resolve attack
|
||||||
self.defender.db.hp = 40
|
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)
|
self.assertTrue(self.defender.db.hp < 40)
|
||||||
# Combat cleanup
|
# Combat cleanup
|
||||||
self.attacker.db.Combat_attribute = True
|
self.attacker.db.Combat_attribute = True
|
||||||
|
|
@ -305,12 +308,14 @@ class TestTurnBattleRangeFunc(BaseEvenniaTest):
|
||||||
initiative = tb_range.COMBAT_RULES.roll_init(self.attacker)
|
initiative = tb_range.COMBAT_RULES.roll_init(self.attacker)
|
||||||
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
self.assertTrue(initiative >= 0 and initiative <= 1000)
|
||||||
# Attack roll
|
# Attack roll
|
||||||
attack_roll = tb_range.COMBAT_RULES.get_attack(self.attacker, self.defender,
|
attack_roll = tb_range.COMBAT_RULES.get_attack(
|
||||||
attack_type="test")
|
self.attacker, self.defender, attack_type="test"
|
||||||
|
)
|
||||||
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
self.assertTrue(attack_roll >= 0 and attack_roll <= 100)
|
||||||
# Defense roll
|
# Defense roll
|
||||||
defense_roll = tb_range.COMBAT_RULES.get_defense(self.attacker, self.defender,
|
defense_roll = tb_range.COMBAT_RULES.get_defense(
|
||||||
attack_type="test")
|
self.attacker, self.defender, attack_type="test"
|
||||||
|
)
|
||||||
self.assertTrue(defense_roll == 50)
|
self.assertTrue(defense_roll == 50)
|
||||||
# Damage roll
|
# Damage roll
|
||||||
damage_roll = tb_range.COMBAT_RULES.get_damage(self.attacker, self.defender)
|
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)
|
self.assertTrue(self.defender.db.hp == 7)
|
||||||
# Resolve attack
|
# Resolve attack
|
||||||
self.defender.db.hp = 40
|
self.defender.db.hp = 40
|
||||||
tb_items.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20,
|
tb_items.COMBAT_RULES.resolve_attack(
|
||||||
defense_value=10)
|
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||||
|
)
|
||||||
self.assertTrue(self.defender.db.hp < 40)
|
self.assertTrue(self.defender.db.hp < 40)
|
||||||
# Combat cleanup
|
# Combat cleanup
|
||||||
self.attacker.db.Combat_attribute = True
|
self.attacker.db.Combat_attribute = True
|
||||||
|
|
@ -555,8 +561,9 @@ class TestTurnBattleMagicFunc(BaseEvenniaTest):
|
||||||
self.assertTrue(self.defender.db.hp == 7)
|
self.assertTrue(self.defender.db.hp == 7)
|
||||||
# Resolve attack
|
# Resolve attack
|
||||||
self.defender.db.hp = 40
|
self.defender.db.hp = 40
|
||||||
tb_magic.COMBAT_RULES.resolve_attack(self.attacker, self.defender, attack_value=20,
|
tb_magic.COMBAT_RULES.resolve_attack(
|
||||||
defense_value=10)
|
self.attacker, self.defender, attack_value=20, defense_value=10
|
||||||
|
)
|
||||||
self.assertTrue(self.defender.db.hp < 40)
|
self.assertTrue(self.defender.db.hp < 40)
|
||||||
# Combat cleanup
|
# Combat cleanup
|
||||||
self.attacker.db.Combat_attribute = True
|
self.attacker.db.Combat_attribute = True
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,3 @@
|
||||||
Contribs related to moving in and manipulating the game world and grid.
|
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
|
# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an
|
||||||
# island surrounded by water (≈). By giving no instructions for the water
|
# island surrounded by water (≈). By giving no instructions for the water
|
||||||
# characters we effectively skip it and create no rooms for those squares.
|
# characters we effectively skip it and create no rooms for those squares.
|
||||||
EXAMPLE1_MAP = '''\
|
EXAMPLE1_MAP = """\
|
||||||
≈≈≈≈≈
|
≈≈≈≈≈
|
||||||
≈♣n♣≈
|
≈♣n♣≈
|
||||||
≈∩▲∩≈
|
≈∩▲∩≈
|
||||||
≈♠n♠≈
|
≈♠n♠≈
|
||||||
≈≈≈≈≈
|
≈≈≈≈≈
|
||||||
'''
|
"""
|
||||||
|
|
||||||
|
|
||||||
def example1_build_forest(x, y, **kwargs):
|
def example1_build_forest(x, y, **kwargs):
|
||||||
'''A basic example of build instructions. Make sure to include **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.'''
|
in the arguments and return an instance of the room for exit generation."""
|
||||||
|
|
||||||
# Create a room and provide a basic description.
|
# Create a room and provide a basic description.
|
||||||
room = create_object(rooms.Room, key="forest" + str(x) + str(y))
|
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):
|
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.
|
# Create the room.
|
||||||
room = create_object(rooms.Room, key="mountains" + str(x) + str(y))
|
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):
|
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.
|
# Create the room.
|
||||||
room = create_object(rooms.Room, key="temple" + str(x) + str(y))
|
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.
|
# 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.
|
# 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):
|
def example2_build_forest(x, y, **kwargs):
|
||||||
'''A basic room'''
|
"""A basic room"""
|
||||||
# If on anything other than the first iteration - Do nothing.
|
# If on anything other than the first iteration - Do nothing.
|
||||||
if kwargs["iteration"] > 0:
|
if kwargs["iteration"] > 0:
|
||||||
return None
|
return None
|
||||||
|
|
@ -143,7 +144,7 @@ def example2_build_forest(x, y, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def example2_build_verticle_exit(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 on the first iteration - Do nothing.
|
||||||
if kwargs["iteration"] == 0:
|
if kwargs["iteration"] == 0:
|
||||||
return
|
return
|
||||||
|
|
@ -164,7 +165,7 @@ def example2_build_verticle_exit(x, y, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def example2_build_horizontal_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 on the first iteration - Do nothing.
|
||||||
if kwargs["iteration"] == 0:
|
if kwargs["iteration"] == 0:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Wilderness contrib - titeuf87, 2017
|
||||||
|
|
||||||
from .wilderness import create_wilderness # noqa
|
from .wilderness import create_wilderness # noqa
|
||||||
from .wilderness import enter_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 WildernessScript # noqa
|
||||||
from .wilderness import WildernessExit # noqa
|
from .wilderness import WildernessExit # noqa
|
||||||
from .wilderness import WildernessRoom # noqa
|
from .wilderness import WildernessRoom # noqa
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ class CmdXYZTeleport(building.CmdTeleport):
|
||||||
are given, the target is a location on the XYZGrid.
|
are given, the target is a location on the XYZGrid.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _search_by_xyz(self, inp):
|
def _search_by_xyz(self, inp):
|
||||||
inp = inp.strip("()")
|
inp = inp.strip("()")
|
||||||
X, Y, *Z = inp.split(",", 2)
|
X, Y, *Z = inp.split(",", 2)
|
||||||
|
|
@ -69,8 +70,10 @@ class CmdXYZTeleport(building.CmdTeleport):
|
||||||
try:
|
try:
|
||||||
xyz = self.caller.xyz
|
xyz = self.caller.xyz
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.caller.msg("Z-coordinate is also required since you are not currently "
|
self.caller.msg(
|
||||||
"in a room with a Z coordinate of its own.")
|
"Z-coordinate is also required since you are not currently "
|
||||||
|
"in a room with a Z coordinate of its own."
|
||||||
|
)
|
||||||
raise InterruptCommand
|
raise InterruptCommand
|
||||||
else:
|
else:
|
||||||
Z = xyz[2]
|
Z = xyz[2]
|
||||||
|
|
@ -134,9 +137,11 @@ class CmdXYZOpen(building.CmdOpen):
|
||||||
|
|
||||||
self.location = self.caller.location
|
self.location = self.caller.location
|
||||||
if not self.args or not self.rhs:
|
if not self.args or not self.rhs:
|
||||||
self.caller.msg("Usage: open <new exit>[;alias...][:typeclass]"
|
self.caller.msg(
|
||||||
"[,<return exit>[;alias..][:typeclass]]] "
|
"Usage: open <new exit>[;alias...][:typeclass]"
|
||||||
"= <destination or (X,Y,Z)>")
|
"[,<return exit>[;alias..][:typeclass]]] "
|
||||||
|
"= <destination or (X,Y,Z)>"
|
||||||
|
)
|
||||||
raise InterruptCommand
|
raise InterruptCommand
|
||||||
if not self.location:
|
if not self.location:
|
||||||
self.caller.msg("You cannot create an exit from a None-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.
|
Builders can optionally specify a specific grid coordinate (X,Y) to go to.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "goto"
|
key = "goto"
|
||||||
aliases = "path"
|
aliases = "path"
|
||||||
help_category = "General"
|
help_category = "General"
|
||||||
|
|
@ -207,11 +213,19 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
def _search_by_key_and_alias(self, inp, xyz_start):
|
def _search_by_key_and_alias(self, inp, xyz_start):
|
||||||
Z = xyz_start[2]
|
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)
|
return self.caller.search(inp, candidates=candidates)
|
||||||
|
|
||||||
def _auto_step(self, caller, session, target=None,
|
def _auto_step(
|
||||||
xymap=None, directions=None, step_sequence=None, step=True):
|
self,
|
||||||
|
caller,
|
||||||
|
session,
|
||||||
|
target=None,
|
||||||
|
xymap=None,
|
||||||
|
directions=None,
|
||||||
|
step_sequence=None,
|
||||||
|
step=True,
|
||||||
|
):
|
||||||
|
|
||||||
path_data = caller.ndb.xy_path_data
|
path_data = caller.ndb.xy_path_data
|
||||||
|
|
||||||
|
|
@ -221,8 +235,12 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
||||||
# stop any old task in its tracks
|
# stop any old task in its tracks
|
||||||
path_data.task.cancel()
|
path_data.task.cancel()
|
||||||
path_data = caller.ndb.xy_path_data = PathData(
|
path_data = caller.ndb.xy_path_data = PathData(
|
||||||
target=target, xymap=xymap, directions=directions,
|
target=target,
|
||||||
step_sequence=step_sequence, task=None)
|
xymap=xymap,
|
||||||
|
directions=directions,
|
||||||
|
step_sequence=step_sequence,
|
||||||
|
task=None,
|
||||||
|
)
|
||||||
|
|
||||||
if step and path_data:
|
if step and path_data:
|
||||||
|
|
||||||
|
|
@ -285,7 +303,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
||||||
xymap=path_data.xymap,
|
xymap=path_data.xymap,
|
||||||
directions=directions,
|
directions=directions,
|
||||||
step_sequence=step_sequence,
|
step_sequence=step_sequence,
|
||||||
task=None
|
task=None,
|
||||||
)
|
)
|
||||||
# the map can itself tell the stepper to stop the auto-step prematurely
|
# the map can itself tell the stepper to stop the auto-step prematurely
|
||||||
interrupt_node_or_link = None
|
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!
|
# the exit name does not need to be the same as the cardinal direction!
|
||||||
exit_name, *_ = first_link.spawn_aliases.get(
|
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)
|
exit_obj = caller.search(exit_name)
|
||||||
if not exit_obj:
|
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
|
# premature stop of pathfind-step because of map node/link of interrupt type
|
||||||
if hasattr(interrupt_node_or_link, "node_index"):
|
if hasattr(interrupt_node_or_link, "node_index"):
|
||||||
message = exit_obj.destination.attributes.get(
|
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
|
# we move into the node/room and then stop
|
||||||
caller.execute_cmd(exit_name, session=session)
|
caller.execute_cmd(exit_name, session=session)
|
||||||
else:
|
else:
|
||||||
# if the link is interrupted we don't cross it at all
|
# if the link is interrupted we don't cross it at all
|
||||||
message = exit_obj.attributes.get(
|
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)
|
caller.msg(message)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -335,7 +356,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
||||||
xymap=path_data.xymap,
|
xymap=path_data.xymap,
|
||||||
directions=path_data.directions,
|
directions=path_data.directions,
|
||||||
step_sequence=path_data.step_sequence,
|
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):
|
def func(self):
|
||||||
|
|
@ -344,7 +365,7 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
goto_mode = self.cmdname == 'goto'
|
goto_mode = self.cmdname == "goto"
|
||||||
|
|
||||||
# check if we have an existing path
|
# check if we have an existing path
|
||||||
path_data = caller.ndb.xy_path_data
|
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}.")
|
caller.msg(f"Aborted auto-walking to {target_name}.")
|
||||||
return
|
return
|
||||||
# goto/path-command will show current path
|
# goto/path-command will show current path
|
||||||
current_path = list_to_string(
|
current_path = list_to_string([f"|w{step}|n" for step in path_data.directions])
|
||||||
[f"|w{step}|n" for step in path_data.directions])
|
|
||||||
moving = "(moving)" if task and task.active() else ""
|
moving = "(moving)" if task and task.active() else ""
|
||||||
caller.msg(f"Path to {target_name}{moving}: {current_path}")
|
caller.msg(f"Path to {target_name}{moving}: {current_path}")
|
||||||
else:
|
else:
|
||||||
|
|
@ -405,12 +425,21 @@ class CmdGoto(COMMAND_DEFAULT_CLASS):
|
||||||
xy_end = xyz_end[:2]
|
xy_end = xyz_end[:2]
|
||||||
directions, step_sequence = xymap.get_shortest_path(xy_start, xy_end)
|
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)}: "
|
caller.msg(
|
||||||
f"|w{list_to_string(directions, endsep='|n, and finally|w')}|n")
|
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
|
# create data for display and start stepping if we used goto
|
||||||
self._auto_step(caller, self.session, target=target, xymap=xymap,
|
self._auto_step(
|
||||||
directions=directions, step_sequence=step_sequence, step=goto_mode)
|
caller,
|
||||||
|
self.session,
|
||||||
|
target=target,
|
||||||
|
xymap=xymap,
|
||||||
|
directions=directions,
|
||||||
|
step_sequence=step_sequence,
|
||||||
|
step=goto_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CmdMap(COMMAND_DEFAULT_CLASS):
|
class CmdMap(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -424,6 +453,7 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
|
||||||
This is a builder-command.
|
This is a builder-command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "map"
|
key = "map"
|
||||||
locks = "cmd:perm(Builders)"
|
locks = "cmd:perm(Builders)"
|
||||||
|
|
||||||
|
|
@ -453,8 +483,10 @@ class CmdMap(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
xymap = xyzgrid.get_map(Z)
|
xymap = xyzgrid.get_map(Z)
|
||||||
if not xymap:
|
if not xymap:
|
||||||
self.caller.msg(f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
|
self.caller.msg(
|
||||||
"available maps/Zcoords.")
|
f"XYMap '{Z}' is not found on the grid. Try 'map list' to see "
|
||||||
|
"available maps/Zcoords."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.caller.msg(ansi.raw(xymap.mapstring))
|
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.
|
Cmdset for easily adding the above cmds to the character cmdset.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "xyzgrid_cmdset"
|
key = "xyzgrid_cmdset"
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
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.
|
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
|
# extends the default legend
|
||||||
LEGEND_MAP1 = {
|
LEGEND_MAP1 = {"T": TransitionToCave}
|
||||||
'T': TransitionToCave
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# link coordinates to rooms
|
# link coordinates to rooms
|
||||||
|
|
@ -96,70 +95,62 @@ PROTOTYPES_MAP1 = {
|
||||||
# node/room prototypes
|
# node/room prototypes
|
||||||
(3, 0): {
|
(3, 0): {
|
||||||
"key": "Dungeon Entrance",
|
"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): {
|
(4, 1): {
|
||||||
"key": "Under the foilage of a giant tree",
|
"key": "Under the foilage of a giant tree",
|
||||||
"desc": "High above the branches of a giant tree blocks out the sunlight. A slide "
|
"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): {
|
(4, 4): {
|
||||||
"key": "The slide",
|
"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): {
|
(6, 1): {
|
||||||
"key": "Thorny path",
|
"key": "Thorny path",
|
||||||
"desc": "To the east is a pathway of thorns. If you get through, you don't think you'll be "
|
"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."
|
"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."
|
|
||||||
},
|
},
|
||||||
|
(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): {
|
(3, 7): {
|
||||||
"key": "Dense foilage",
|
"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): {
|
(5, 6): {
|
||||||
"key": "On a huge branch",
|
"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): {
|
(9, 7): {
|
||||||
"key": "On an enormous branch",
|
"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): {
|
(10, 8): {
|
||||||
"key": "A gorgeous view",
|
"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
|
# default rooms
|
||||||
('*', '*'): {
|
("*", "*"): {
|
||||||
"key": "Among the branches of a giant tree",
|
"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
|
# directional prototypes
|
||||||
(3, 0, 'e'): {
|
(3, 0, "e"): {"desc": "A dark passage into the underworld."},
|
||||||
"desc": "A dark passage into the underworld."
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, prot in PROTOTYPES_MAP1.items():
|
for key, prot in PROTOTYPES_MAP1.items():
|
||||||
if len(key) == 2:
|
if len(key) == 2:
|
||||||
# we don't want to give exits the room typeclass!
|
# we don't want to give exits the room typeclass!
|
||||||
prot['prototype_parent'] = ROOM_PARENT
|
prot["prototype_parent"] = ROOM_PARENT
|
||||||
else:
|
else:
|
||||||
prot['prototype_parent'] = EXIT_PARENT
|
prot["prototype_parent"] = EXIT_PARENT
|
||||||
|
|
||||||
|
|
||||||
XYMAP_DATA_MAP1 = {
|
XYMAP_DATA_MAP1 = {
|
||||||
"zcoord": "the large tree",
|
"zcoord": "the large tree",
|
||||||
"map": MAP1,
|
"map": MAP1,
|
||||||
"legend": LEGEND_MAP1,
|
"legend": LEGEND_MAP1,
|
||||||
"prototypes": PROTOTYPES_MAP1
|
"prototypes": PROTOTYPES_MAP1,
|
||||||
}
|
}
|
||||||
|
|
||||||
# -------------------------------------- map2
|
# -------------------------------------- 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.
|
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)
|
# this extends the default legend (that defines #,-+ etc)
|
||||||
LEGEND_MAP2 = {
|
LEGEND_MAP2 = {"T": TransitionToLargeTree}
|
||||||
"T": TransitionToLargeTree
|
|
||||||
}
|
|
||||||
|
|
||||||
# prototypes for specific locations
|
# prototypes for specific locations
|
||||||
PROTOTYPES_MAP2 = {
|
PROTOTYPES_MAP2 = {
|
||||||
|
|
@ -203,64 +193,54 @@ PROTOTYPES_MAP2 = {
|
||||||
(1, 0): {
|
(1, 0): {
|
||||||
"key": "The entrance",
|
"key": "The entrance",
|
||||||
"desc": "This is the entrance to a small cave leading into the ground. "
|
"desc": "This is the entrance to a small cave leading into the ground. "
|
||||||
"Light sifts in from the outside, while cavernous passages disappear "
|
"Light sifts in from the outside, while cavernous passages disappear "
|
||||||
"into darkness."
|
"into darkness.",
|
||||||
},
|
},
|
||||||
(2, 0): {
|
(2, 0): {
|
||||||
"key": "A gruesome sight.",
|
"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): {
|
(1, 1): {
|
||||||
"key": "A dark pathway",
|
"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): {
|
(3, 2): {
|
||||||
"key": "Stagnant water",
|
"key": "Stagnant water",
|
||||||
"desc": "A pool of stagnant, black water dominates this small chamber. To the nortwest "
|
"desc": "A pool of stagnant, black water dominates this small chamber. To the nortwest "
|
||||||
"a faint light can be seen."
|
"a faint light can be seen.",
|
||||||
},
|
|
||||||
(0, 2): {
|
|
||||||
"key": "A dark alcove",
|
|
||||||
"desc": "This alcove is empty."
|
|
||||||
},
|
},
|
||||||
|
(0, 2): {"key": "A dark alcove", "desc": "This alcove is empty."},
|
||||||
(1, 2): {
|
(1, 2): {
|
||||||
"key": "South-west corner of the atrium",
|
"key": "South-west corner of the atrium",
|
||||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||||
"between the stones."
|
"between the stones.",
|
||||||
},
|
},
|
||||||
(2, 2): {
|
(2, 2): {
|
||||||
"key": "South-east corner of the atrium",
|
"key": "South-east corner of the atrium",
|
||||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||||
"between the stones."
|
"between the stones.",
|
||||||
},
|
},
|
||||||
(1, 3): {
|
(1, 3): {
|
||||||
"key": "North-west corner of the atrium",
|
"key": "North-west corner of the atrium",
|
||||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
||||||
"between the stones."
|
"between the stones.",
|
||||||
},
|
},
|
||||||
(2, 3): {
|
(2, 3): {
|
||||||
"key": "North-east corner of the atrium",
|
"key": "North-east corner of the atrium",
|
||||||
"desc": "Sunlight sifts down into a large underground chamber. Weeds and grass sprout "
|
"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): {
|
(3, 3): {
|
||||||
"key": "Craggy crevice",
|
"key": "Craggy crevice",
|
||||||
"desc": "This is the deepest part of the dungeon. The path shrinks away and there "
|
"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
|
# 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
|
# directional prototypes
|
||||||
(1, 0, 'w'): {
|
(1, 0, "w"): {"desc": "A narrow path to the fresh air of the outside world."},
|
||||||
"desc": "A narrow path to the fresh air of the outside world."
|
|
||||||
},
|
|
||||||
# directional fallbacks for unset directions
|
# 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
|
# 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():
|
for key, prot in PROTOTYPES_MAP2.items():
|
||||||
if len(key) == 2:
|
if len(key) == 2:
|
||||||
# we don't want to give exits the room typeclass!
|
# we don't want to give exits the room typeclass!
|
||||||
prot['prototype_parent'] = ROOM_PARENT
|
prot["prototype_parent"] = ROOM_PARENT
|
||||||
else:
|
else:
|
||||||
prot['prototype_parent'] = EXIT_PARENT
|
prot["prototype_parent"] = EXIT_PARENT
|
||||||
|
|
||||||
|
|
||||||
XYMAP_DATA_MAP2 = {
|
XYMAP_DATA_MAP2 = {
|
||||||
|
|
@ -278,14 +258,8 @@ XYMAP_DATA_MAP2 = {
|
||||||
"zcoord": "the small cave",
|
"zcoord": "the small cave",
|
||||||
"legend": LEGEND_MAP2,
|
"legend": LEGEND_MAP2,
|
||||||
"prototypes": PROTOTYPES_MAP2,
|
"prototypes": PROTOTYPES_MAP2,
|
||||||
"options": {
|
"options": {"map_visual_range": 1, "map_mode": "scan"},
|
||||||
"map_visual_range": 1,
|
|
||||||
"map_mode": 'scan'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# This is read by the parser
|
# This is read by the parser
|
||||||
XYMAP_DATA_LIST = [
|
XYMAP_DATA_LIST = [XYMAP_DATA_MAP1, XYMAP_DATA_MAP2]
|
||||||
XYMAP_DATA_MAP1,
|
|
||||||
XYMAP_DATA_MAP2
|
|
||||||
]
|
|
||||||
|
|
|
||||||
|
|
@ -160,11 +160,12 @@ _TOPICS_MAP = {
|
||||||
"add": _HELP_ADD,
|
"add": _HELP_ADD,
|
||||||
"spawn": _HELP_SPAWN,
|
"spawn": _HELP_SPAWN,
|
||||||
"initpath": _HELP_INITPATH,
|
"initpath": _HELP_INITPATH,
|
||||||
"delete": _HELP_DELETE
|
"delete": _HELP_DELETE,
|
||||||
}
|
}
|
||||||
|
|
||||||
evennia._init()
|
evennia._init()
|
||||||
|
|
||||||
|
|
||||||
def _option_help(*suboptions):
|
def _option_help(*suboptions):
|
||||||
"""
|
"""
|
||||||
Show help <command> aid.
|
Show help <command> aid.
|
||||||
|
|
@ -188,6 +189,7 @@ def _option_list(*suboptions):
|
||||||
# override grid's logger to echo directly to console
|
# override grid's logger to echo directly to console
|
||||||
def _log(msg):
|
def _log(msg):
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
xyzgrid.log = _log
|
xyzgrid.log = _log
|
||||||
|
|
||||||
xymap_data = xyzgrid.grid
|
xymap_data = xyzgrid.grid
|
||||||
|
|
@ -210,7 +212,7 @@ def _option_list(*suboptions):
|
||||||
if not xymap:
|
if not xymap:
|
||||||
print(f"No XYMap with Z='{zcoord}' was found on grid.")
|
print(f"No XYMap with Z='{zcoord}' was found on grid.")
|
||||||
else:
|
else:
|
||||||
nrooms = xyzgrid.get_room(('*', '*', zcoord)).count()
|
nrooms = xyzgrid.get_room(("*", "*", zcoord)).count()
|
||||||
nnodes = len(xymap.node_index_map)
|
nnodes = len(xymap.node_index_map)
|
||||||
print("\n" + str(repr(xymap)) + ":\n")
|
print("\n" + str(repr(xymap)) + ":\n")
|
||||||
checkwarning = True
|
checkwarning = True
|
||||||
|
|
@ -218,22 +220,29 @@ def _option_list(*suboptions):
|
||||||
print(f"{nrooms} / {nnodes} rooms are spawned.")
|
print(f"{nrooms} / {nnodes} rooms are spawned.")
|
||||||
checkwarning = False
|
checkwarning = False
|
||||||
elif nrooms < nnodes:
|
elif nrooms < nnodes:
|
||||||
print(f"{nrooms} / {nnodes} rooms are spawned\n"
|
print(
|
||||||
"Note: Transitional nodes are *not* spawned (they just point \n"
|
f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||||
"to another map), so the 'missing room(s)' may just be from such nodes.")
|
"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:
|
elif nrooms > nnodes:
|
||||||
print(f"{nrooms} / {nnodes} rooms are spawned\n"
|
print(
|
||||||
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync.")
|
f"{nrooms} / {nnodes} rooms are spawned\n"
|
||||||
|
"Note: Maybe some rooms were removed from map. Run 'spawn' to re-sync."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f"{nrooms} / {nnodes} rooms are spawned\n")
|
print(f"{nrooms} / {nnodes} rooms are spawned\n")
|
||||||
|
|
||||||
if checkwarning:
|
if checkwarning:
|
||||||
print("Note: This check is not complete; it does not consider changed map "
|
print(
|
||||||
"topology\nlike relocated nodes/rooms and new/removed links/exits - this "
|
"Note: This check is not complete; it does not consider changed map "
|
||||||
"is calculated only during a spawn.")
|
"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("\nDisplayed map (as appearing in-game):\n\n" + ansi.parse_ansi(str(xymap)))
|
||||||
print("\nRaw map string (including axes and invisible nodes/links):\n"
|
print(
|
||||||
+ str(xymap.mapstring))
|
"\nRaw map string (including axes and invisible nodes/links):\n" + str(xymap.mapstring)
|
||||||
|
)
|
||||||
print(f"\nCustom map options: {xymap.options}\n")
|
print(f"\nCustom map options: {xymap.options}\n")
|
||||||
legend = []
|
legend = []
|
||||||
for key, node_or_link in xymap.legend.items():
|
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
|
# override grid's logger to echo directly to console
|
||||||
def _log(msg):
|
def _log(msg):
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
grid.log = _log
|
grid.log = _log
|
||||||
|
|
||||||
xymap_data_list = []
|
xymap_data_list = []
|
||||||
|
|
@ -290,35 +300,44 @@ def _option_spawn(*suboptions):
|
||||||
# override grid's logger to echo directly to console
|
# override grid's logger to echo directly to console
|
||||||
def _log(msg):
|
def _log(msg):
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
grid.log = _log
|
grid.log = _log
|
||||||
|
|
||||||
if suboptions:
|
if suboptions:
|
||||||
opts = ''.join(suboptions).strip('()')
|
opts = "".join(suboptions).strip("()")
|
||||||
# coordinate tuple
|
# coordinate tuple
|
||||||
try:
|
try:
|
||||||
x, y, z = (part.strip() for part in opts.split(","))
|
x, y, z = (part.strip() for part in opts.split(","))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
print("spawn coordinate must be given as (X, Y, Z) tuple, where '*' act "
|
print(
|
||||||
"wild cards and Z is the mapname/z-coord of the map to load.")
|
"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
|
return
|
||||||
else:
|
else:
|
||||||
x, y, z = '*', '*', '*'
|
x, y, z = "*", "*", "*"
|
||||||
|
|
||||||
if 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"
|
inp = input(
|
||||||
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
|
"This will (re)spawn the entire grid. If it was built before, it may spawn \n"
|
||||||
"continue? [Y]/N? ")
|
"new rooms or delete rooms that no longer matches the grid.\nDo you want to "
|
||||||
|
"continue? [Y]/N? "
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
inp = input("This will spawn/delete objects in the database matching grid coordinates \n"
|
inp = input(
|
||||||
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? ")
|
"This will spawn/delete objects in the database matching grid coordinates \n"
|
||||||
if inp.lower() in ('no', 'n'):
|
f"({x},{y},{z}) (where '*' is a wildcard).\nDo you want to continue? [Y]/N? "
|
||||||
|
)
|
||||||
|
if inp.lower() in ("no", "n"):
|
||||||
print("Aborted.")
|
print("Aborted.")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("Beginner-Tutorial spawn ...")
|
print("Beginner-Tutorial spawn ...")
|
||||||
grid.spawn(xyz=(x, y, z))
|
grid.spawn(xyz=(x, y, z))
|
||||||
print("... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
|
print(
|
||||||
"modified an existing grid.")
|
"... spawn complete!\nIt's recommended to reload the server to refresh caches if this "
|
||||||
|
"modified an existing grid."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _option_initpath(*suboptions):
|
def _option_initpath(*suboptions):
|
||||||
|
|
@ -331,6 +350,7 @@ def _option_initpath(*suboptions):
|
||||||
# override grid's logger to echo directly to console
|
# override grid's logger to echo directly to console
|
||||||
def _log(msg):
|
def _log(msg):
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
grid.log = _log
|
grid.log = _log
|
||||||
|
|
||||||
xymaps = grid.all_maps()
|
xymaps = grid.all_maps()
|
||||||
|
|
@ -354,19 +374,24 @@ def _option_delete(*suboptions):
|
||||||
# override grid's logger to echo directly to console
|
# override grid's logger to echo directly to console
|
||||||
def _log(msg):
|
def _log(msg):
|
||||||
print(msg)
|
print(msg)
|
||||||
|
|
||||||
grid.log = _log
|
grid.log = _log
|
||||||
|
|
||||||
if not suboptions:
|
if not suboptions:
|
||||||
repl = input("WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
repl = input(
|
||||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
"WARNING: This will delete the ENTIRE Grid and wipe all rooms/exits!"
|
||||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
|
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||||
if repl.lower() not in ('yes', 'y'):
|
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? "
|
||||||
|
)
|
||||||
|
if repl.lower() not in ("yes", "y"):
|
||||||
print("Aborted.")
|
print("Aborted.")
|
||||||
return
|
return
|
||||||
print("Deleting grid ...")
|
print("Deleting grid ...")
|
||||||
grid.delete()
|
grid.delete()
|
||||||
print("... done.\nPlease reload the server now; otherwise "
|
print(
|
||||||
"removed rooms may linger in cache.")
|
"... done.\nPlease reload the server now; otherwise "
|
||||||
|
"removed rooms may linger in cache."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
zcoords = (part.strip() for part in suboptions)
|
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.")
|
print(f"Mapname/zcoord {zcoord} is not a part of the grid.")
|
||||||
err = True
|
err = True
|
||||||
if err:
|
if err:
|
||||||
print("Valid mapnames/zcoords are\n:", "\n ".join(
|
print("Valid mapnames/zcoords are\n:", "\n ".join(xymap.Z for xymap in grid.all_rooms()))
|
||||||
xymap.Z for xymap in grid.all_rooms()))
|
|
||||||
return
|
return
|
||||||
repl = input("This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
|
repl = input(
|
||||||
"rooms/exits!"
|
"This will delete map(s) {', '.join(zcoords)} and wipe all corresponding\n"
|
||||||
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
"rooms/exits!"
|
||||||
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? ")
|
"\nObjects/Chars inside deleted rooms will be moved to their home locations."
|
||||||
if repl.lower() not in ('yes', 'y'):
|
"\nThis can't be undone. Are you sure you want to continue? Y/[N]? "
|
||||||
|
)
|
||||||
|
if repl.lower() not in ("yes", "y"):
|
||||||
print("Aborted.")
|
print("Aborted.")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("Deleting selected xymaps ...")
|
print("Deleting selected xymaps ...")
|
||||||
grid.remove_map(*zcoords, remove_objects=True)
|
grid.remove_map(*zcoords, remove_objects=True)
|
||||||
print("... done.\nPlease reload the server to refresh room caches."
|
print(
|
||||||
"\nAlso remember to remove any links from remaining maps pointing to deleted maps.")
|
"... 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):
|
def xyzcommand(*args):
|
||||||
|
|
@ -405,20 +433,19 @@ def xyzcommand(*args):
|
||||||
|
|
||||||
option, *suboptions = args
|
option, *suboptions = args
|
||||||
|
|
||||||
if option in ('help', 'h'):
|
if option in ("help", "h"):
|
||||||
_option_help(*suboptions)
|
_option_help(*suboptions)
|
||||||
if option in ('list', 'show'):
|
if option in ("list", "show"):
|
||||||
_option_list(*suboptions)
|
_option_list(*suboptions)
|
||||||
elif option == 'init':
|
elif option == "init":
|
||||||
_option_init(*suboptions)
|
_option_init(*suboptions)
|
||||||
elif option == 'add':
|
elif option == "add":
|
||||||
_option_add(*suboptions)
|
_option_add(*suboptions)
|
||||||
elif option == 'spawn':
|
elif option == "spawn":
|
||||||
_option_spawn(*suboptions)
|
_option_spawn(*suboptions)
|
||||||
elif option == 'initpath':
|
elif option == "initpath":
|
||||||
_option_initpath(*suboptions)
|
_option_initpath(*suboptions)
|
||||||
elif option == 'delete':
|
elif option == "delete":
|
||||||
_option_delete(*suboptions)
|
_option_delete(*suboptions)
|
||||||
else:
|
else:
|
||||||
print(f"Unknown option '{option}'. Use 'evennia xyzgrid help' for valid arguments.")
|
print(f"Unknown option '{option}'. Use 'evennia xyzgrid help' for valid arguments.")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,19 +27,19 @@ except AttributeError:
|
||||||
exit_override = {}
|
exit_override = {}
|
||||||
|
|
||||||
room_prototype = {
|
room_prototype = {
|
||||||
'prototype_key': 'xyz_room',
|
"prototype_key": "xyz_room",
|
||||||
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom',
|
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom",
|
||||||
'prototype_tags': ("xyzroom", ),
|
"prototype_tags": ("xyzroom",),
|
||||||
'key': "A room",
|
"key": "A room",
|
||||||
'desc': "An empty room."
|
"desc": "An empty room.",
|
||||||
}
|
}
|
||||||
room_prototype.update(room_override)
|
room_prototype.update(room_override)
|
||||||
|
|
||||||
exit_prototype = {
|
exit_prototype = {
|
||||||
'prototype_key': 'xyz_exit',
|
"prototype_key": "xyz_exit",
|
||||||
'typeclass': 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit',
|
"typeclass": "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit",
|
||||||
'prototype_tags': ("xyzexit", ),
|
"prototype_tags": ("xyzexit",),
|
||||||
'desc': "An exit."
|
"desc": "An exit.",
|
||||||
}
|
}
|
||||||
exit_prototype.update(exit_override)
|
exit_prototype.update(exit_override)
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -30,13 +30,15 @@ MAPSCAN = {
|
||||||
|
|
||||||
# errors for Map system
|
# errors for Map system
|
||||||
|
|
||||||
class MapError(RuntimeError):
|
|
||||||
|
|
||||||
|
class MapError(RuntimeError):
|
||||||
def __init__(self, error="", node_or_link=None):
|
def __init__(self, error="", node_or_link=None):
|
||||||
prefix = ""
|
prefix = ""
|
||||||
if node_or_link:
|
if node_or_link:
|
||||||
prefix = (f"{node_or_link.__class__.__name__} '{node_or_link.symbol}' "
|
prefix = (
|
||||||
f"at XYZ=({node_or_link.X:g},{node_or_link.Y:g},{node_or_link.Z}) ")
|
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.node_or_link = node_or_link
|
||||||
self.message = f"{prefix}{error}"
|
self.message = f"{prefix}{error}"
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
@ -52,4 +54,5 @@ class MapTransition(RuntimeWarning):
|
||||||
leads to another map.
|
leads to another map.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,8 @@ try:
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
f"{err}\nThe XYZgrid contrib requires "
|
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 django.conf import settings
|
||||||
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
from evennia.utils.utils import variable_from_module, mod_import, is_iter
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
@ -122,9 +123,7 @@ _CACHE_DIR = settings.CACHE_DIR
|
||||||
_LOADED_PROTOTYPES = None
|
_LOADED_PROTOTYPES = None
|
||||||
_XYZROOMCLASS = None
|
_XYZROOMCLASS = None
|
||||||
|
|
||||||
MAP_DATA_KEYS = [
|
MAP_DATA_KEYS = ["zcoord", "map", "legend", "prototypes", "options", "module_path"]
|
||||||
"zcoord", "map", "legend", "prototypes", "options", "module_path"
|
|
||||||
]
|
|
||||||
|
|
||||||
DEFAULT_LEGEND = xymap_legend.LEGEND
|
DEFAULT_LEGEND = xymap_legend.LEGEND
|
||||||
|
|
||||||
|
|
@ -172,11 +171,11 @@ class XYMap:
|
||||||
but recommended for readability!
|
but recommended for readability!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mapcorner_symbol = '+'
|
mapcorner_symbol = "+"
|
||||||
max_pathfinding_length = 500
|
max_pathfinding_length = 500
|
||||||
empty_symbol = ' '
|
empty_symbol = " "
|
||||||
# we normally only accept one single character for the legend key
|
# 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):
|
def __init__(self, map_module_or_dict, Z="map", xyzgrid=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -210,7 +209,9 @@ class XYMap:
|
||||||
if not _LOADED_PROTOTYPES:
|
if not _LOADED_PROTOTYPES:
|
||||||
# inject default prototypes, but don't override prototype-keys loaded from
|
# inject default prototypes, but don't override prototype-keys loaded from
|
||||||
# settings, if they exist (that means the user wants to replace the defaults)
|
# 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
|
_LOADED_PROTOTYPES = True
|
||||||
|
|
||||||
self.Z = Z
|
self.Z = Z
|
||||||
|
|
@ -264,7 +265,7 @@ class XYMap:
|
||||||
nnodes = 0
|
nnodes = 0
|
||||||
if self.node_index_map:
|
if self.node_index_map:
|
||||||
nnodes = len(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):
|
def log(self, msg):
|
||||||
if self.xyzgrid:
|
if self.xyzgrid:
|
||||||
|
|
@ -317,34 +318,41 @@ class XYMap:
|
||||||
mapdata = variable_from_module(mod, "XYMAP_DATA")
|
mapdata = variable_from_module(mod, "XYMAP_DATA")
|
||||||
|
|
||||||
if not mapdata:
|
if not mapdata:
|
||||||
raise MapError("No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
|
raise MapError(
|
||||||
f"{map_module_or_dict}.")
|
"No valid XYMAP_DATA or XYMAP_DATA_LIST could be found from "
|
||||||
|
f"{map_module_or_dict}."
|
||||||
|
)
|
||||||
|
|
||||||
# validate
|
# validate
|
||||||
if any(key for key in mapdata if key not in MAP_DATA_KEYS):
|
if any(key for key in mapdata if key not in MAP_DATA_KEYS):
|
||||||
raise MapError(f"Mapdata has keys {list(mapdata)}, but only "
|
raise MapError(
|
||||||
f"keys {MAP_DATA_KEYS} are allowed.")
|
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 not key or len(key) > 1:
|
||||||
if key not in self.legend_key_exceptions:
|
if key not in self.legend_key_exceptions:
|
||||||
raise MapError(f"Map-legend key '{key}' is invalid: All keys must "
|
raise MapError(
|
||||||
"be exactly one character long. Use the node/link's "
|
f"Map-legend key '{key}' is invalid: All keys must "
|
||||||
"`.display_symbol` property to change how it is "
|
"be exactly one character long. Use the node/link's "
|
||||||
"displayed.")
|
"`.display_symbol` property to change how it is "
|
||||||
if 'map' not in mapdata or not mapdata['map']:
|
"displayed."
|
||||||
|
)
|
||||||
|
if "map" not in mapdata or not mapdata["map"]:
|
||||||
raise MapError("No map found. Add 'map' key to map-data dict.")
|
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)):
|
if not (is_iter(key) and (2 <= len(key) <= 3)):
|
||||||
raise MapError(f"Prototype override key {key} is malformed: It must be a "
|
raise MapError(
|
||||||
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
f"Prototype override key {key} is malformed: It must be a "
|
||||||
"where direction is a supported direction string ('n', 'ne', etc).")
|
"coordinate (X, Y) for nodes or (X, Y, direction) for links; "
|
||||||
|
"where direction is a supported direction string ('n', 'ne', etc)."
|
||||||
|
)
|
||||||
|
|
||||||
# store/update result
|
# store/update result
|
||||||
self.Z = mapdata.get('zcoord', self.Z)
|
self.Z = mapdata.get("zcoord", self.Z)
|
||||||
self.mapstring = mapdata['map']
|
self.mapstring = mapdata["map"]
|
||||||
self.prototypes = mapdata.get('prototypes', {})
|
self.prototypes = mapdata.get("prototypes", {})
|
||||||
self.options = mapdata.get('options', {})
|
self.options = mapdata.get("options", {})
|
||||||
|
|
||||||
# merge the custom legend onto the default legend to allow easily
|
# merge the custom legend onto the default legend to allow easily
|
||||||
# overriding only parts of it
|
# overriding only parts of it
|
||||||
|
|
@ -357,8 +365,9 @@ class XYMap:
|
||||||
# nothing more to do
|
# nothing more to do
|
||||||
continue
|
continue
|
||||||
# we need to load the prototype dict onto each for ease of access. Note that
|
# we need to load the prototype dict onto each for ease of access. Note that
|
||||||
proto = protlib.search_prototype(prototype, require_single=True,
|
proto = protlib.search_prototype(
|
||||||
no_db=_NO_DB_PROTOTYPES)[0]
|
prototype, require_single=True, no_db=_NO_DB_PROTOTYPES
|
||||||
|
)[0]
|
||||||
node_or_link_class.prototype = proto
|
node_or_link_class.prototype = proto
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
|
|
@ -391,7 +400,8 @@ class XYMap:
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"The mapstring must have at least two '{mapcorner_symbol}' "
|
f"The mapstring must have at least two '{mapcorner_symbol}' "
|
||||||
"symbols marking the upper- and bottom-left corners of the "
|
"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
|
# find the the position (in the string as a whole) of the top-left corner-marker
|
||||||
maplines = mapstring.split("\n")
|
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
|
# 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
|
# this is always in a stright line down from the first marker
|
||||||
botleft_marker_x, botleft_marker_y = topleft_marker_x, -1
|
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:
|
if line.find(mapcorner_symbol) == topleft_marker_x:
|
||||||
break
|
break
|
||||||
if botleft_marker_y == -1:
|
if botleft_marker_y == -1:
|
||||||
raise MapParserError(f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
raise MapParserError(
|
||||||
"Make sure it lines up with the top-left corner-marker "
|
f"No bottom-left corner-marker ({mapcorner_symbol}) found! "
|
||||||
f"(found at column {topleft_marker_x} of the string).")
|
"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
|
# the actual coordinate is dy below the topleft marker so we need to shift
|
||||||
botleft_marker_y += topleft_marker_y + 1
|
botleft_marker_y += topleft_marker_y + 1
|
||||||
|
|
||||||
|
|
@ -443,8 +455,7 @@ class XYMap:
|
||||||
mapnode_or_link_class = self.legend.get(char)
|
mapnode_or_link_class = self.legend.get(char)
|
||||||
if not mapnode_or_link_class:
|
if not mapnode_or_link_class:
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) "
|
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) " "is not found in LEGEND."
|
||||||
"is not found in LEGEND."
|
|
||||||
)
|
)
|
||||||
if hasattr(mapnode_or_link_class, "node_index"):
|
if hasattr(mapnode_or_link_class, "node_index"):
|
||||||
# A mapnode. Mapnodes can only be placed on even grid positions, where
|
# A mapnode. Mapnodes can only be placed on even grid positions, where
|
||||||
|
|
@ -454,7 +465,8 @@ class XYMap:
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) marks a "
|
f"Symbol '{char}' on XY=({ix / 2:g},{iy / 2:g}) marks a "
|
||||||
"MapNode but is located between integer (X,Y) positions (only "
|
"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
|
# save the node to several different maps for different uses
|
||||||
# in both coordinate systems
|
# in both coordinate systems
|
||||||
|
|
@ -462,14 +474,17 @@ class XYMap:
|
||||||
max_X, max_Y = max(max_X, iX), max(max_Y, iY)
|
max_X, max_Y = max(max_X, iX), max(max_Y, iY)
|
||||||
node_index += 1
|
node_index += 1
|
||||||
|
|
||||||
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[node_index] = \
|
xygrid[ix][iy] = XYgrid[iX][iY] = node_index_map[
|
||||||
mapnode_or_link_class(x=ix, y=iy, Z=self.Z,
|
node_index
|
||||||
node_index=node_index, symbol=char, xymap=self)
|
] = mapnode_or_link_class(
|
||||||
|
x=ix, y=iy, Z=self.Z, node_index=node_index, symbol=char, xymap=self
|
||||||
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# we have a link at this xygrid position (this is ok everywhere)
|
# 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,
|
xygrid[ix][iy] = mapnode_or_link_class(
|
||||||
xymap=self)
|
x=ix, y=iy, Z=self.Z, symbol=char, xymap=self
|
||||||
|
)
|
||||||
|
|
||||||
# store the symbol mapping for transition lookups
|
# store the symbol mapping for transition lookups
|
||||||
symbol_map[char].append(xygrid[ix][iy])
|
symbol_map[char].append(xygrid[ix][iy])
|
||||||
|
|
@ -499,20 +514,23 @@ class XYMap:
|
||||||
node_coord = (node.X, node.Y)
|
node_coord = (node.X, node.Y)
|
||||||
# load prototype from override, or use default
|
# load prototype from override, or use default
|
||||||
try:
|
try:
|
||||||
node.prototype = flatten_prototype(self.prototypes.get(
|
node.prototype = flatten_prototype(
|
||||||
node_coord,
|
self.prototypes.get(
|
||||||
self.prototypes.get(('*', '*'), node.prototype)),
|
node_coord, self.prototypes.get(("*", "*"), node.prototype)
|
||||||
no_db=_NO_DB_PROTOTYPES
|
),
|
||||||
|
no_db=_NO_DB_PROTOTYPES,
|
||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise MapParserError(f"Room prototype malformed: {err}", node)
|
raise MapParserError(f"Room prototype malformed: {err}", node)
|
||||||
# do the same for links (x, y, direction) coords
|
# do the same for links (x, y, direction) coords
|
||||||
for direction, maplink in node.first_links.items():
|
for direction, maplink in node.first_links.items():
|
||||||
try:
|
try:
|
||||||
maplink.prototype = flatten_prototype(self.prototypes.get(
|
maplink.prototype = flatten_prototype(
|
||||||
node_coord + (direction,),
|
self.prototypes.get(
|
||||||
self.prototypes.get(('*', '*', '*'), maplink.prototype)),
|
node_coord + (direction,),
|
||||||
no_db=_NO_DB_PROTOTYPES
|
self.prototypes.get(("*", "*", "*"), maplink.prototype),
|
||||||
|
),
|
||||||
|
no_db=_NO_DB_PROTOTYPES,
|
||||||
)
|
)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise MapParserError(f"Exit prototype malformed: {err}", maplink)
|
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.
|
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
|
x0, y0 = start_node.x, start_node.y
|
||||||
points.append((x0, y0))
|
points.append((x0, y0))
|
||||||
|
|
@ -558,9 +578,15 @@ class XYMap:
|
||||||
ymin, ymax = min(ymin, y), max(ymax, y)
|
ymin, ymax = min(ymin, y), max(ymax, y)
|
||||||
|
|
||||||
points, xmin, xmax, ymin, ymax = _scan_neighbors(
|
points, xmin, xmax, ymin, ymax = _scan_neighbors(
|
||||||
end_node, points, dist=dist,
|
end_node,
|
||||||
xmin=xmin, ymin=ymin, xmax=xmax, ymax=ymax,
|
points,
|
||||||
depth=depth + 1)
|
dist=dist,
|
||||||
|
xmin=xmin,
|
||||||
|
ymin=ymin,
|
||||||
|
xmax=xmax,
|
||||||
|
ymax=ymax,
|
||||||
|
depth=depth + 1,
|
||||||
|
)
|
||||||
|
|
||||||
return points, xmin, xmax, ymin, ymax
|
return points, xmin, xmax, ymin, ymax
|
||||||
|
|
||||||
|
|
@ -581,14 +607,16 @@ class XYMap:
|
||||||
# check if the solution for this grid was already solved previously.
|
# check if the solution for this grid was already solved previously.
|
||||||
|
|
||||||
mapstr, dist_matrix, pathfinding_routes = "", None, None
|
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:
|
try:
|
||||||
mapstr, dist_matrix, pathfinding_routes = pickle.load(fil)
|
mapstr, dist_matrix, pathfinding_routes = pickle.load(fil)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
if (mapstr == self.mapstring
|
if (
|
||||||
and dist_matrix is not None
|
mapstr == self.mapstring
|
||||||
and pathfinding_routes is not None):
|
and dist_matrix is not None
|
||||||
|
and pathfinding_routes is not None
|
||||||
|
):
|
||||||
# this is important - it means the map hasn't changed so
|
# this is important - it means the map hasn't changed so
|
||||||
# we can re-use the stored data!
|
# we can re-use the stored data!
|
||||||
self.dist_matrix = dist_matrix
|
self.dist_matrix = dist_matrix
|
||||||
|
|
@ -606,16 +634,20 @@ class XYMap:
|
||||||
|
|
||||||
# solve using Dijkstra's algorithm
|
# solve using Dijkstra's algorithm
|
||||||
self.dist_matrix, self.pathfinding_routes = dijkstra(
|
self.dist_matrix, self.pathfinding_routes = dijkstra(
|
||||||
pathfinding_matrix, directed=True,
|
pathfinding_matrix,
|
||||||
return_predecessors=True, limit=self.max_pathfinding_length)
|
directed=True,
|
||||||
|
return_predecessors=True,
|
||||||
|
limit=self.max_pathfinding_length,
|
||||||
|
)
|
||||||
|
|
||||||
if self.pathfinder_baked_filename:
|
if self.pathfinder_baked_filename:
|
||||||
# try to cache the results
|
# try to cache the results
|
||||||
with open(self.pathfinder_baked_filename, 'wb') as fil:
|
with open(self.pathfinder_baked_filename, "wb") as fil:
|
||||||
pickle.dump((self.mapstring, self.dist_matrix, self.pathfinding_routes),
|
pickle.dump(
|
||||||
fil, protocol=4)
|
(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
|
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*
|
related prototypes in the correct coordinate positions. This must be done *first*
|
||||||
|
|
@ -638,12 +670,14 @@ class XYMap:
|
||||||
if not _XYZROOMCLASS:
|
if not _XYZROOMCLASS:
|
||||||
from evennia.contrib.grid.xyzgrid.xyzroom import XYZRoom as _XYZROOMCLASS
|
from evennia.contrib.grid.xyzgrid.xyzroom import XYZRoom as _XYZROOMCLASS
|
||||||
x, y = xy
|
x, y = xy
|
||||||
wildcard = '*'
|
wildcard = "*"
|
||||||
spawned = []
|
spawned = []
|
||||||
|
|
||||||
# find existing nodes, in case some rooms need to be removed
|
# find existing nodes, in case some rooms need to be removed
|
||||||
map_coords = [(node.X, node.Y) for node in
|
map_coords = [
|
||||||
sorted(self.node_index_map.values(), key=lambda n: (n.Y, n.X))]
|
(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)):
|
for existing_room in _XYZROOMCLASS.objects.filter_xyz(xyz=(x, y, self.Z)):
|
||||||
roomX, roomY, _ = existing_room.xyz
|
roomX, roomY, _ = existing_room.xyz
|
||||||
if (roomX, roomY) not in map_coords:
|
if (roomX, roomY) not in map_coords:
|
||||||
|
|
@ -657,7 +691,7 @@ class XYMap:
|
||||||
spawned.append(node)
|
spawned.append(node)
|
||||||
return spawned
|
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
|
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
|
prototypes. It's possible to only spawn a specic exit by specifying the node and
|
||||||
|
|
@ -676,7 +710,7 @@ class XYMap:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
x, y = xy
|
x, y = xy
|
||||||
wildcard = '*'
|
wildcard = "*"
|
||||||
|
|
||||||
if not nodes:
|
if not nodes:
|
||||||
nodes = sorted(self.node_index_map.values(), key=lambda n: (n.Z, n.Y, n.X))
|
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
|
iX, iY = xy
|
||||||
if not ((0 <= iX <= self.max_X) and (0 <= iY <= self.max_Y)):
|
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 "
|
raise MapError(
|
||||||
f"outside the grid size of (0,0) - ({self.max_X}, {self.max_Y}).")
|
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:
|
try:
|
||||||
return self.XYgrid[iX][iY]
|
return self.XYgrid[iX][iY]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -753,8 +789,10 @@ class XYMap:
|
||||||
istartnode = startnode.node_index
|
istartnode = startnode.node_index
|
||||||
inextnode = endnode.node_index
|
inextnode = endnode.node_index
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise MapError(f"Map.get_shortest_path received start/end nodes {startnode} and "
|
raise MapError(
|
||||||
f"{endnode}. They must both be MapNodes (not Links)")
|
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:
|
if self.pathfinding_routes is None:
|
||||||
self.calculate_path_matrix()
|
self.calculate_path_matrix()
|
||||||
|
|
@ -782,13 +820,18 @@ class XYMap:
|
||||||
|
|
||||||
return directions, path
|
return directions, path
|
||||||
|
|
||||||
def get_visual_range(self, xy, dist=2, mode='nodes',
|
def get_visual_range(
|
||||||
character='@',
|
self,
|
||||||
target=None,
|
xy,
|
||||||
target_path_style="|y{display_symbol}|n",
|
dist=2,
|
||||||
max_size=None,
|
mode="nodes",
|
||||||
indent=0,
|
character="@",
|
||||||
return_str=True):
|
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
|
Get a part of the grid centered on a specific point and extended a certain number
|
||||||
of nodes or grid points in every direction.
|
of nodes or grid points in every direction.
|
||||||
|
|
@ -876,7 +919,7 @@ class XYMap:
|
||||||
# nothing but ourselves or emptiness
|
# nothing but ourselves or emptiness
|
||||||
return character if character else self.empty_symbol
|
return character if character else self.empty_symbol
|
||||||
|
|
||||||
elif mode == 'nodes':
|
elif mode == "nodes":
|
||||||
# dist measures only full, reachable nodes.
|
# dist measures only full, reachable nodes.
|
||||||
points, xmin, xmax, ymin, ymax = self._get_topology_around_coord(xy, dist=dist)
|
points, xmin, xmax, ymin, ymax = self._get_topology_around_coord(xy, dist=dist)
|
||||||
|
|
||||||
|
|
@ -888,7 +931,7 @@ class XYMap:
|
||||||
for (ix0, iy0) in points:
|
for (ix0, iy0) in points:
|
||||||
gridmap[iy0 - ymin][ix0 - xmin] = display_map[iy0][ix0]
|
gridmap[iy0 - ymin][ix0 - xmin] = display_map[iy0][ix0]
|
||||||
|
|
||||||
elif mode == 'scan':
|
elif mode == "scan":
|
||||||
# scan-mode - dist measures individual grid points
|
# scan-mode - dist measures individual grid points
|
||||||
|
|
||||||
xmin, xmax = max(0, ix - dist), min(width, ix + dist + 1)
|
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]]
|
gridmap = [line[xmin:xmax] for line in display_map[ymin:ymax]]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise MapError(f"Map.get_visual_range 'mode' was '{mode}' "
|
raise MapError(
|
||||||
"- it must be either 'scan' or 'nodes'.")
|
f"Map.get_visual_range 'mode' was '{mode}' "
|
||||||
|
"- it must be either 'scan' or 'nodes'."
|
||||||
|
)
|
||||||
if character:
|
if character:
|
||||||
gridmap[iyc][ixc] = character # correct indexing; it's a list of lines
|
gridmap[iyc][ixc] = character # correct indexing; it's a list of lines
|
||||||
|
|
||||||
|
|
@ -906,8 +951,7 @@ class XYMap:
|
||||||
# stylize path to target
|
# stylize path to target
|
||||||
|
|
||||||
def _default_callable(node):
|
def _default_callable(node):
|
||||||
return target_path_style.format(
|
return target_path_style.format(display_symbol=node.get_display_symbol())
|
||||||
display_symbol=node.get_display_symbol())
|
|
||||||
|
|
||||||
if callable(target_path_style):
|
if callable(target_path_style):
|
||||||
_target_path_style = target_path_style
|
_target_path_style = target_path_style
|
||||||
|
|
@ -916,7 +960,7 @@ class XYMap:
|
||||||
|
|
||||||
_, path = self.get_shortest_path(xy, target)
|
_, 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
|
nsteps = 0
|
||||||
for node_or_link in path[1:]:
|
for node_or_link in path[1:]:
|
||||||
if hasattr(node_or_link, "node_index"):
|
if hasattr(node_or_link, "node_index"):
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,8 @@ try:
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
f"{err}\nThe XYZgrid contrib requires "
|
f"{err}\nThe XYZgrid contrib requires "
|
||||||
"the SciPy package. Install with `pip install scipy'.")
|
"the SciPy package. Install with `pip install scipy'."
|
||||||
|
)
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
@ -33,6 +34,7 @@ UUID_XYZ_NAMESPACE = uuid.uuid5(uuid.UUID(int=0), "xyzgrid")
|
||||||
|
|
||||||
# Nodes/Links
|
# Nodes/Links
|
||||||
|
|
||||||
|
|
||||||
class MapNode:
|
class MapNode:
|
||||||
"""
|
"""
|
||||||
This represents a 'room' node on the map. Note that the map system deals with two grids, the
|
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).
|
for various reasons, mostly map-transitions).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# symbol used to identify this link on the map
|
# symbol used to identify this link on the map
|
||||||
symbol = '#'
|
symbol = "#"
|
||||||
# if printing this node should show another symbol. If set
|
# if printing this node should show another symbol. If set
|
||||||
# to the empty string, use `symbol`.
|
# to the empty string, use `symbol`.
|
||||||
display_symbol = None
|
display_symbol = None
|
||||||
|
|
@ -79,16 +82,16 @@ class MapNode:
|
||||||
multilink = True
|
multilink = True
|
||||||
# default values to use if the exit doesn't have a 'spawn_aliases' iterable
|
# default values to use if the exit doesn't have a 'spawn_aliases' iterable
|
||||||
direction_spawn_defaults = {
|
direction_spawn_defaults = {
|
||||||
'n': ('north', 'n'),
|
"n": ("north", "n"),
|
||||||
'ne': ('northeast', 'ne', 'north-east'),
|
"ne": ("northeast", "ne", "north-east"),
|
||||||
'e': ('east', 'e'),
|
"e": ("east", "e"),
|
||||||
'se': ('southeast', 'se', 'south-east'),
|
"se": ("southeast", "se", "south-east"),
|
||||||
's': ('south', 's'),
|
"s": ("south", "s"),
|
||||||
'sw': ('southwest', 'sw', 'south-west'),
|
"sw": ("southwest", "sw", "south-west"),
|
||||||
'w': ('west', 'w'),
|
"w": ("west", "w"),
|
||||||
'nw': ('northwest', 'nw', 'north-west'),
|
"nw": ("northwest", "nw", "north-west"),
|
||||||
'd': ('down', 'd', 'do'),
|
"d": ("down", "d", "do"),
|
||||||
'u': ('up', 'u'),
|
"u": ("up", "u"),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, x, y, Z, node_index=0, symbol=None, xymap=None):
|
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:
|
if first_step_name in self.closest_neighbor_names:
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"has more than one outgoing direction '{first_step_name}'. "
|
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
|
self.closest_neighbor_names[first_step_name] = direction
|
||||||
|
|
||||||
node_index = end_node.node_index
|
node_index = end_node.node_index
|
||||||
|
|
@ -215,8 +220,9 @@ class MapNode:
|
||||||
# used for building the shortest path. Note that we store the
|
# used for building the shortest path. Note that we store the
|
||||||
# aliased link directions here, for quick display by the
|
# aliased link directions here, for quick display by the
|
||||||
# shortest-route solver
|
# shortest-route solver
|
||||||
shortest_route = self.shortest_route_to_node.get(
|
shortest_route = self.shortest_route_to_node.get(node_index, ("", [], BIGVAL))[
|
||||||
node_index, ("", [], BIGVAL))[2]
|
2
|
||||||
|
]
|
||||||
if weight < shortest_route:
|
if weight < shortest_route:
|
||||||
self.shortest_route_to_node[node_index] = (first_step_name, steps, weight)
|
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, ...)
|
str or tuple: The key of the spawned exit, or a tuple (key, alias, alias, ...)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key, *aliases = (
|
key, *aliases = self.first_links[direction].spawn_aliases.get(
|
||||||
self.first_links[direction]
|
direction, self.direction_spawn_defaults.get(direction, ("unknown",))
|
||||||
.spawn_aliases.get(
|
)
|
||||||
direction, self.direction_spawn_defaults.get(
|
|
||||||
direction, ('unknown', ))))
|
|
||||||
if return_aliases:
|
if return_aliases:
|
||||||
return (key, *aliases)
|
return (key, *aliases)
|
||||||
return key
|
return key
|
||||||
|
|
@ -313,28 +317,24 @@ class MapNode:
|
||||||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||||
except django_exceptions.ObjectDoesNotExist:
|
except django_exceptions.ObjectDoesNotExist:
|
||||||
# create a new entity with proper coordinates etc
|
# create a new entity with proper coordinates etc
|
||||||
tclass = self.prototype['typeclass']
|
tclass = self.prototype["typeclass"]
|
||||||
tclass = (f' ({tclass})'
|
tclass = (
|
||||||
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom'
|
f" ({tclass})" if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom" else ""
|
||||||
else '')
|
|
||||||
self.log(f" spawning room at xyz={xyz}{tclass}")
|
|
||||||
nodeobj, err = NodeTypeclass.create(
|
|
||||||
self.prototype.get('key', 'An empty room'),
|
|
||||||
xyz=xyz
|
|
||||||
)
|
)
|
||||||
|
self.log(f" spawning room at xyz={xyz}{tclass}")
|
||||||
|
nodeobj, err = NodeTypeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz)
|
||||||
if err:
|
if err:
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
else:
|
else:
|
||||||
self.log(f" updating existing room (if changed) at xyz={xyz}")
|
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
|
# 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
|
# apply prototype to node. This will not override the XYZ tags since
|
||||||
# these are not in the prototype and exact=False
|
# these are not in the prototype and exact=False
|
||||||
spawner.batch_update_objects_with_prototype(
|
spawner.batch_update_objects_with_prototype(self.prototype, objects=[nodeobj], exact=False)
|
||||||
self.prototype, objects=[nodeobj], exact=False)
|
|
||||||
|
|
||||||
def spawn_links(self, directions=None):
|
def spawn_links(self, directions=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -364,9 +364,9 @@ class MapNode:
|
||||||
for direction, link in self.first_links.items():
|
for direction, link in self.first_links.items():
|
||||||
|
|
||||||
key, *aliases = self.get_exit_spawn_name(direction)
|
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
|
# 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)
|
maplinks[key.lower()] = (key, aliases, direction, link)
|
||||||
|
|
||||||
# remove duplicates
|
# remove duplicates
|
||||||
|
|
@ -380,8 +380,7 @@ class MapNode:
|
||||||
|
|
||||||
# we need to search for exits in all directions since some
|
# we need to search for exits in all directions since some
|
||||||
# may have been removed since last sync
|
# may have been removed since last sync
|
||||||
linkobjs = {exi.db_key.lower(): exi
|
linkobjs = {exi.db_key.lower(): exi for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
|
||||||
for exi in ExitTypeclass.objects.filter_xyz(xyz=xyz)}
|
|
||||||
|
|
||||||
# figure out if the topology changed between grid and map (will always
|
# figure out if the topology changed between grid and map (will always
|
||||||
# build all exits first run)
|
# build all exits first run)
|
||||||
|
|
@ -411,16 +410,19 @@ class MapNode:
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
linkobjs[key.lower()] = exi
|
linkobjs[key.lower()] = exi
|
||||||
prot = maplinks[key.lower()][3].prototype
|
prot = maplinks[key.lower()][3].prototype
|
||||||
tclass = prot['typeclass']
|
tclass = prot["typeclass"]
|
||||||
tclass = (f' ({tclass})'
|
tclass = (
|
||||||
if tclass != 'evennia.contrib.grid.xyzgrid.xyzroom.XYZExit'
|
f" ({tclass})"
|
||||||
else '')
|
if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit"
|
||||||
|
else ""
|
||||||
|
)
|
||||||
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
|
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
|
||||||
|
|
||||||
# apply prototypes to catch any changes
|
# apply prototypes to catch any changes
|
||||||
for key, linkobj in linkobjs.items():
|
for key, linkobj in linkobjs.items():
|
||||||
spawner.batch_update_objects_with_prototype(
|
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):
|
def unspawn(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -466,8 +468,9 @@ class TransitionMapNode(MapNode):
|
||||||
actual rooms (`#`) on the other map (NOT to the `T`s)!
|
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
|
# X,Y,Z coordinates of target node
|
||||||
taget_map_xyz = (None, None, None)
|
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).
|
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')):
|
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 "
|
raise MapParserError(
|
||||||
"`.target_map_xyz` property. It must point "
|
f"(Z={self.xymap.Z}) has not defined its "
|
||||||
"to another valid xymap (Z coordinate).", self)
|
"`.target_map_xyz` property. It must point "
|
||||||
|
"to another valid xymap (Z coordinate).",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
|
||||||
return self.target_map_xyz
|
return self.target_map_xyz
|
||||||
|
|
||||||
|
|
@ -548,6 +554,7 @@ class MapLink:
|
||||||
`node.get_exit_spawn_name(direction)`
|
`node.get_exit_spawn_name(direction)`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# symbol for identifying this link on the map
|
# symbol for identifying this link on the map
|
||||||
symbol = ""
|
symbol = ""
|
||||||
# if `None`, use .symbol
|
# if `None`, use .symbol
|
||||||
|
|
@ -661,7 +668,9 @@ class MapLink:
|
||||||
return None, 0, None
|
return None, 0, None
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
f"was connected to from the direction {start_direction}, but "
|
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
|
# note that if `get_direction` returns an unknown direction, this will be equivalent
|
||||||
# to pointing to an empty location, which makes sense
|
# 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)
|
next_target = self.at_empty_target(start_direction, end_direction)
|
||||||
|
|
||||||
if not next_target:
|
if not next_target:
|
||||||
raise MapParserError(
|
raise MapParserError(f"points to empty space in the direction {end_direction}!", self)
|
||||||
f"points to empty space in the direction {end_direction}!", self)
|
|
||||||
|
|
||||||
_weight += self.get_weight(start_direction, _weight)
|
_weight += self.get_weight(start_direction, _weight)
|
||||||
if _steps is None:
|
if _steps is None:
|
||||||
|
|
@ -688,13 +696,16 @@ class MapLink:
|
||||||
return (
|
return (
|
||||||
next_target,
|
next_target,
|
||||||
_weight / max(1, _linklen) if self.average_long_link_weights else _weight,
|
_weight / max(1, _linklen) if self.average_long_link_weights else _weight,
|
||||||
_steps
|
_steps,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# we hit another link. Progress recursively.
|
# we hit another link. Progress recursively.
|
||||||
return next_target.traverse(
|
return next_target.traverse(
|
||||||
REVERSE_DIRECTIONS.get(end_direction, end_direction),
|
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):
|
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
|
# there is is something there, we need to check if it is either
|
||||||
# a map node or a link connecting in our direction
|
# a map node or a link connecting in our direction
|
||||||
node_or_link = xygrid[end_x][end_y]
|
node_or_link = xygrid[end_x][end_y]
|
||||||
if (node_or_link.multilink
|
if node_or_link.multilink or node_or_link.get_direction(direction):
|
||||||
or node_or_link.get_direction(direction)):
|
|
||||||
links[direction] = node_or_link
|
links[direction] = node_or_link
|
||||||
return links
|
return links
|
||||||
|
|
||||||
|
|
@ -845,7 +855,8 @@ class SmartRerouterMapLink(MapLink):
|
||||||
for direction in unhandled_links_copy:
|
for direction in unhandled_links_copy:
|
||||||
if REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
|
if REVERSE_DIRECTIONS[direction] in unhandled_links_copy:
|
||||||
directions[direction] = REVERSE_DIRECTIONS[
|
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
|
# check if we have any non-cross-through paths left to handle
|
||||||
n_unhandled = len(unhandled_links)
|
n_unhandled = len(unhandled_links)
|
||||||
|
|
@ -856,7 +867,8 @@ class SmartRerouterMapLink(MapLink):
|
||||||
if n_unhandled != 2:
|
if n_unhandled != 2:
|
||||||
links = ", ".join(unhandled_links)
|
links = ", ".join(unhandled_links)
|
||||||
raise MapParserError(
|
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[0]] = unhandled_links[1]
|
||||||
directions[unhandled_links[1]] = unhandled_links[0]
|
directions[unhandled_links[1]] = unhandled_links[0]
|
||||||
|
|
@ -865,6 +877,7 @@ class SmartRerouterMapLink(MapLink):
|
||||||
|
|
||||||
return self.directions.get(start_direction)
|
return self.directions.get(start_direction)
|
||||||
|
|
||||||
|
|
||||||
class SmartTeleporterMapLink(MapLink):
|
class SmartTeleporterMapLink(MapLink):
|
||||||
"""
|
"""
|
||||||
The teleport link works by connecting to nowhere - and will then continue
|
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.
|
-#-t-# - invalid, only one connected link is allowed.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
symbol = 't'
|
|
||||||
|
symbol = "t"
|
||||||
# usually invisible
|
# usually invisible
|
||||||
display_symbol = ' '
|
display_symbol = " "
|
||||||
direction_name = 'teleport'
|
direction_name = "teleport"
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
@ -932,7 +946,9 @@ class SmartTeleporterMapLink(MapLink):
|
||||||
if len(found_teleporters) > 1:
|
if len(found_teleporters) > 1:
|
||||||
raise MapParserError(
|
raise MapParserError(
|
||||||
"found too many matching teleporters (must be exactly one more): "
|
"found too many matching teleporters (must be exactly one more): "
|
||||||
f"{found_teleporters}", self)
|
f"{found_teleporters}",
|
||||||
|
self,
|
||||||
|
)
|
||||||
|
|
||||||
other_teleporter = found_teleporters[0]
|
other_teleporter = found_teleporters[0]
|
||||||
# link the two so we don't need to scan again for the other one
|
# 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:
|
if len(neighbors) != 1:
|
||||||
raise MapParserError("must have exactly one link connected to it.", self)
|
raise MapParserError("must have exactly one link connected to it.", self)
|
||||||
direction, link = next(iter(neighbors.items()))
|
direction, link = next(iter(neighbors.items()))
|
||||||
if hasattr(link, 'node_index'):
|
if hasattr(link, "node_index"):
|
||||||
raise MapParserError("can only connect to a Link. Found {link} in "
|
raise MapParserError(
|
||||||
"direction {direction}.", self)
|
"can only connect to a Link. Found {link} in " "direction {direction}.", self
|
||||||
|
)
|
||||||
# the string 'teleport' will not be understood by the traverser, leading to
|
# the string 'teleport' will not be understood by the traverser, leading to
|
||||||
# this being interpreted as an empty target and the `at_empty_target`
|
# this being interpreted as an empty target and the `at_empty_target`
|
||||||
# hook firing when trying to traverse this link.
|
# hook firing when trying to traverse this link.
|
||||||
|
|
@ -962,12 +979,10 @@ class SmartTeleporterMapLink(MapLink):
|
||||||
if start_direction == direction_name:
|
if start_direction == direction_name:
|
||||||
# called while traversing another teleport
|
# called while traversing another teleport
|
||||||
# - we must make sure we can always access/leave the teleport.
|
# - we must make sure we can always access/leave the teleport.
|
||||||
self.directions = {direction_name: direction,
|
self.directions = {direction_name: direction, direction: direction_name}
|
||||||
direction: direction_name}
|
|
||||||
else:
|
else:
|
||||||
# called while traversing a normal link
|
# called while traversing a normal link
|
||||||
self.directions = {start_direction: direction_name,
|
self.directions = {start_direction: direction_name, direction_name: direction}
|
||||||
direction_name: direction}
|
|
||||||
|
|
||||||
return self.directions.get(start_direction)
|
return self.directions.get(start_direction)
|
||||||
|
|
||||||
|
|
@ -1016,6 +1031,7 @@ class SmartMapLink(MapLink):
|
||||||
# |
|
# |
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
multilink = True
|
multilink = True
|
||||||
|
|
||||||
def get_direction(self, start_direction):
|
def get_direction(self, start_direction):
|
||||||
|
|
@ -1027,8 +1043,11 @@ class SmartMapLink(MapLink):
|
||||||
if not self.directions:
|
if not self.directions:
|
||||||
directions = {}
|
directions = {}
|
||||||
neighbors = self.get_linked_neighbors()
|
neighbors = self.get_linked_neighbors()
|
||||||
nodes = [direction for direction, neighbor in neighbors.items()
|
nodes = [
|
||||||
if hasattr(neighbor, 'node_index')]
|
direction
|
||||||
|
for direction, neighbor in neighbors.items()
|
||||||
|
if hasattr(neighbor, "node_index")
|
||||||
|
]
|
||||||
|
|
||||||
if len(nodes) == 2:
|
if len(nodes) == 2:
|
||||||
# prefer link to these two nodes
|
# prefer link to these two nodes
|
||||||
|
|
@ -1042,7 +1061,9 @@ class SmartMapLink(MapLink):
|
||||||
"must have exactly two connections - either directly to "
|
"must have exactly two connections - either directly to "
|
||||||
"two nodes or connecting directly to one node and with exactly one other "
|
"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 "
|
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
|
self.directions = directions
|
||||||
return self.directions.get(start_direction)
|
return self.directions.get(start_direction)
|
||||||
|
|
@ -1071,20 +1092,26 @@ class InvisibleSmartMapLink(SmartMapLink):
|
||||||
# this allows for normal movement directions even if the invisible-node
|
# this allows for normal movement directions even if the invisible-node
|
||||||
# is marked with a different symbol.
|
# is marked with a different symbol.
|
||||||
direction_aliases = {
|
direction_aliases = {
|
||||||
'n': 'n', 'ne': 'ne', 'e': 'e', 'se': 'se',
|
"n": "n",
|
||||||
's': 's', 'sw': 'sw', 'w': 'w', 'nw': 'nw'
|
"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
|
# replace current link position with what the smart links "should" look like
|
||||||
display_symbol_aliases = {
|
display_symbol_aliases = {
|
||||||
(('n', 's'), ('s', 'n')): '|',
|
(("n", "s"), ("s", "n")): "|",
|
||||||
(('n', 's'),): 'v',
|
(("n", "s"),): "v",
|
||||||
(('s', 'n')): '^',
|
(("s", "n")): "^",
|
||||||
(('e', 'w'), ('w', 'e')): '-',
|
(("e", "w"), ("w", "e")): "-",
|
||||||
(('e', 'w'),): '>',
|
(("e", "w"),): ">",
|
||||||
(('w', 'e'),): '<',
|
(("w", "e"),): "<",
|
||||||
(('nw', 'se'), ('sw', 'ne')): '\\',
|
(("nw", "se"), ("sw", "ne")): "\\",
|
||||||
(('ne', 'sw'), ('sw', 'ne')): '/',
|
(("ne", "sw"), ("sw", "ne")): "/",
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_display_symbol(self):
|
def get_display_symbol(self):
|
||||||
|
|
@ -1098,12 +1125,10 @@ class InvisibleSmartMapLink(SmartMapLink):
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, "_cached_display_symbol"):
|
if not hasattr(self, "_cached_display_symbol"):
|
||||||
legend = self.xymap.legend
|
legend = self.xymap.legend
|
||||||
default_symbol = (
|
default_symbol = self.symbol if self.display_symbol is None else self.display_symbol
|
||||||
self.symbol if self.display_symbol is None else self.display_symbol)
|
|
||||||
self._cached_display_symbol = default_symbol
|
self._cached_display_symbol = default_symbol
|
||||||
|
|
||||||
dirtuple = tuple((key, self.directions[key])
|
dirtuple = tuple((key, self.directions[key]) for key in sorted(self.directions.keys()))
|
||||||
for key in sorted(self.directions.keys()))
|
|
||||||
|
|
||||||
replacement_symbol = self.display_symbol_aliases.get(dirtuple, default_symbol)
|
replacement_symbol = self.display_symbol_aliases.get(dirtuple, default_symbol)
|
||||||
|
|
||||||
|
|
@ -1112,16 +1137,19 @@ class InvisibleSmartMapLink(SmartMapLink):
|
||||||
if node_or_link_class:
|
if node_or_link_class:
|
||||||
# initiate class in the current location and run get_display_symbol
|
# initiate class in the current location and run get_display_symbol
|
||||||
# to get what it would show.
|
# to get what it would show.
|
||||||
self._cached_display_symbol = (
|
self._cached_display_symbol = node_or_link_class(
|
||||||
node_or_link_class(self.x, self.y, self.Z).get_display_symbol())
|
self.x, self.y, self.Z
|
||||||
|
).get_display_symbol()
|
||||||
return self._cached_display_symbol
|
return self._cached_display_symbol
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------
|
# ----------------------------------
|
||||||
# Default nodes and link classes
|
# Default nodes and link classes
|
||||||
|
|
||||||
|
|
||||||
class BasicMapNode(MapNode):
|
class BasicMapNode(MapNode):
|
||||||
"""A map node/room"""
|
"""A map node/room"""
|
||||||
|
|
||||||
symbol = "#"
|
symbol = "#"
|
||||||
prototype = "xyz_room"
|
prototype = "xyz_room"
|
||||||
|
|
||||||
|
|
@ -1129,20 +1157,25 @@ class BasicMapNode(MapNode):
|
||||||
class InterruptMapNode(MapNode):
|
class InterruptMapNode(MapNode):
|
||||||
"""A point of interest node/room. Pathfinder will ignore but auto-stepper will
|
"""A point of interest node/room. Pathfinder will ignore but auto-stepper will
|
||||||
stop here if passing through. Beginner-Tutorial from here is fine."""
|
stop here if passing through. Beginner-Tutorial from here is fine."""
|
||||||
|
|
||||||
symbol = "I"
|
symbol = "I"
|
||||||
display_symbol = "#"
|
display_symbol = "#"
|
||||||
interrupt_path = True
|
interrupt_path = True
|
||||||
prototype = "xyz_room"
|
prototype = "xyz_room"
|
||||||
|
|
||||||
|
|
||||||
class MapTransitionNode(TransitionMapNode):
|
class MapTransitionNode(TransitionMapNode):
|
||||||
"""Transition-target node to other map. This is not actually spawned in-game."""
|
"""Transition-target node to other map. This is not actually spawned in-game."""
|
||||||
|
|
||||||
symbol = "T"
|
symbol = "T"
|
||||||
display_symbol = " "
|
display_symbol = " "
|
||||||
prototype = None # important to leave None!
|
prototype = None # important to leave None!
|
||||||
target_map_xyz = (None, None, None) # must be set manually
|
target_map_xyz = (None, None, None) # must be set manually
|
||||||
|
|
||||||
|
|
||||||
class NSMapLink(MapLink):
|
class NSMapLink(MapLink):
|
||||||
"""Two-way, North-South link"""
|
"""Two-way, North-South link"""
|
||||||
|
|
||||||
symbol = "|"
|
symbol = "|"
|
||||||
display_symbol = "||"
|
display_symbol = "||"
|
||||||
directions = {"n": "s", "s": "n"}
|
directions = {"n": "s", "s": "n"}
|
||||||
|
|
@ -1151,6 +1184,7 @@ class NSMapLink(MapLink):
|
||||||
|
|
||||||
class EWMapLink(MapLink):
|
class EWMapLink(MapLink):
|
||||||
"""Two-way, East-West link"""
|
"""Two-way, East-West link"""
|
||||||
|
|
||||||
symbol = "-"
|
symbol = "-"
|
||||||
directions = {"e": "w", "w": "e"}
|
directions = {"e": "w", "w": "e"}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1158,6 +1192,7 @@ class EWMapLink(MapLink):
|
||||||
|
|
||||||
class NESWMapLink(MapLink):
|
class NESWMapLink(MapLink):
|
||||||
"""Two-way, NorthWest-SouthWest link"""
|
"""Two-way, NorthWest-SouthWest link"""
|
||||||
|
|
||||||
symbol = "/"
|
symbol = "/"
|
||||||
directions = {"ne": "sw", "sw": "ne"}
|
directions = {"ne": "sw", "sw": "ne"}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1165,6 +1200,7 @@ class NESWMapLink(MapLink):
|
||||||
|
|
||||||
class SENWMapLink(MapLink):
|
class SENWMapLink(MapLink):
|
||||||
"""Two-way, SouthEast-NorthWest link"""
|
"""Two-way, SouthEast-NorthWest link"""
|
||||||
|
|
||||||
symbol = "\\"
|
symbol = "\\"
|
||||||
directions = {"se": "nw", "nw": "se"}
|
directions = {"se": "nw", "nw": "se"}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1172,22 +1208,23 @@ class SENWMapLink(MapLink):
|
||||||
|
|
||||||
class PlusMapLink(MapLink):
|
class PlusMapLink(MapLink):
|
||||||
"""Two-way, crossing North-South and East-West links"""
|
"""Two-way, crossing North-South and East-West links"""
|
||||||
|
|
||||||
symbol = "+"
|
symbol = "+"
|
||||||
directions = {"s": "n", "n": "s",
|
directions = {"s": "n", "n": "s", "e": "w", "w": "e"}
|
||||||
"e": "w", "w": "e"}
|
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class CrossMapLink(MapLink):
|
class CrossMapLink(MapLink):
|
||||||
"""Two-way, crossing NorthEast-SouthWest and SouthEast-NorthWest links"""
|
"""Two-way, crossing NorthEast-SouthWest and SouthEast-NorthWest links"""
|
||||||
|
|
||||||
symbol = "x"
|
symbol = "x"
|
||||||
directions = {"ne": "sw", "sw": "ne",
|
directions = {"ne": "sw", "sw": "ne", "se": "nw", "nw": "se"}
|
||||||
"se": "nw", "nw": "se"}
|
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class NSOneWayMapLink(MapLink):
|
class NSOneWayMapLink(MapLink):
|
||||||
"""One-way North-South link"""
|
"""One-way North-South link"""
|
||||||
|
|
||||||
symbol = "v"
|
symbol = "v"
|
||||||
directions = {"n": "s"}
|
directions = {"n": "s"}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1195,6 +1232,7 @@ class NSOneWayMapLink(MapLink):
|
||||||
|
|
||||||
class SNOneWayMapLink(MapLink):
|
class SNOneWayMapLink(MapLink):
|
||||||
"""One-way South-North link"""
|
"""One-way South-North link"""
|
||||||
|
|
||||||
symbol = "^"
|
symbol = "^"
|
||||||
directions = {"s": "n"}
|
directions = {"s": "n"}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1202,6 +1240,7 @@ class SNOneWayMapLink(MapLink):
|
||||||
|
|
||||||
class EWOneWayMapLink(MapLink):
|
class EWOneWayMapLink(MapLink):
|
||||||
"""One-way East-West link"""
|
"""One-way East-West link"""
|
||||||
|
|
||||||
symbol = "<"
|
symbol = "<"
|
||||||
directions = {"e": "w"}
|
directions = {"e": "w"}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1209,6 +1248,7 @@ class EWOneWayMapLink(MapLink):
|
||||||
|
|
||||||
class WEOneWayMapLink(MapLink):
|
class WEOneWayMapLink(MapLink):
|
||||||
"""One-way West-East link"""
|
"""One-way West-East link"""
|
||||||
|
|
||||||
symbol = ">"
|
symbol = ">"
|
||||||
directions = {"w": "e"}
|
directions = {"w": "e"}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1216,21 +1256,39 @@ class WEOneWayMapLink(MapLink):
|
||||||
|
|
||||||
class UpMapLink(SmartMapLink):
|
class UpMapLink(SmartMapLink):
|
||||||
"""Up direction. Note that this stays on the same z-coord so it's a 'fake' up."""
|
"""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.
|
# 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,
|
direction_aliases = {
|
||||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
"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}
|
spawn_aliases = {direction: ("up", "u") for direction in direction_aliases}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class DownMapLink(UpMapLink):
|
class DownMapLink(UpMapLink):
|
||||||
"""Down direction. Note that this stays on the same z-coord, so it's a 'fake' down."""
|
"""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.
|
# 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,
|
direction_aliases = {
|
||||||
's': symbol, 'sw': symbol, 'w': symbol, 'nw': symbol}
|
"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}
|
spawn_aliases = {direction: ("down", "d") for direction in direction_aliases}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
@ -1238,6 +1296,7 @@ class DownMapLink(UpMapLink):
|
||||||
class InterruptMapLink(InvisibleSmartMapLink):
|
class InterruptMapLink(InvisibleSmartMapLink):
|
||||||
"""A (still passable) link. Pathfinder will treat this as any link, but auto-stepper
|
"""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."""
|
will always abort before crossing this link - so this must be crossed manually."""
|
||||||
|
|
||||||
symbol = "i"
|
symbol = "i"
|
||||||
interrupt_path = True
|
interrupt_path = True
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
@ -1250,14 +1309,24 @@ class BlockedMapLink(InvisibleSmartMapLink):
|
||||||
link in any paths.
|
link in any paths.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
symbol = 'b'
|
|
||||||
weights = {'n': BIGVAL, 'ne': BIGVAL, 'e': BIGVAL, 'se': BIGVAL,
|
symbol = "b"
|
||||||
's': BIGVAL, 'sw': BIGVAL, 'w': BIGVAL, 'nw': BIGVAL}
|
weights = {
|
||||||
|
"n": BIGVAL,
|
||||||
|
"ne": BIGVAL,
|
||||||
|
"e": BIGVAL,
|
||||||
|
"se": BIGVAL,
|
||||||
|
"s": BIGVAL,
|
||||||
|
"sw": BIGVAL,
|
||||||
|
"w": BIGVAL,
|
||||||
|
"nw": BIGVAL,
|
||||||
|
}
|
||||||
prototype = "xyz_exit"
|
prototype = "xyz_exit"
|
||||||
|
|
||||||
|
|
||||||
class RouterMapLink(SmartRerouterMapLink):
|
class RouterMapLink(SmartRerouterMapLink):
|
||||||
"""A link that connects other links to build 'knees', pass-throughs etc."""
|
"""A link that connects other links to build 'knees', pass-throughs etc."""
|
||||||
|
|
||||||
symbol = "o"
|
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
|
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.
|
one-way link out of the teleporter on one side.
|
||||||
"""
|
"""
|
||||||
symbol = 't'
|
|
||||||
|
symbol = "t"
|
||||||
|
|
||||||
|
|
||||||
# all map components; used as base if not overridden
|
# all map components; used as base if not overridden
|
||||||
|
|
@ -1291,5 +1361,5 @@ LEGEND = {
|
||||||
"d": DownMapLink,
|
"d": DownMapLink,
|
||||||
"b": BlockedMapLink,
|
"b": BlockedMapLink,
|
||||||
"i": InterruptMapLink,
|
"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.
|
Main grid class. This organizes the Maps based on their name/Z-coordinate.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
"""
|
"""
|
||||||
What we store persistently is data used to create each map (the legends, names etc)
|
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)
|
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.
|
Get one or more exit object at coordinate.
|
||||||
|
|
||||||
|
|
@ -102,7 +103,7 @@ class XYZGrid(DefaultScript):
|
||||||
Queryset: A queryset of XYZExit(s) found.
|
Queryset: A queryset of XYZExit(s) found.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
kwargs['db_key'] = name
|
kwargs["db_key"] = name
|
||||||
return XYZExit.objects.filter_xyz_exit(xyz=xyz, **kwargs)
|
return XYZExit.objects.filter_xyz_exit(xyz=xyz, **kwargs)
|
||||||
|
|
||||||
def maps_from_module(self, module_path):
|
def maps_from_module(self, module_path):
|
||||||
|
|
@ -127,7 +128,7 @@ class XYZGrid(DefaultScript):
|
||||||
if not mapdata:
|
if not mapdata:
|
||||||
self.log(f"Could not find or load map from {module_path}.")
|
self.log(f"Could not find or load map from {module_path}.")
|
||||||
return
|
return
|
||||||
mapdata['module_path'] = module_path
|
mapdata["module_path"] = module_path
|
||||||
return map_data_list
|
return map_data_list
|
||||||
|
|
||||||
def reload(self):
|
def reload(self):
|
||||||
|
|
@ -154,9 +155,9 @@ class XYZGrid(DefaultScript):
|
||||||
# we reload the map from module
|
# we reload the map from module
|
||||||
new_mapdata = loaded_mapdata.get(zcoord)
|
new_mapdata = loaded_mapdata.get(zcoord)
|
||||||
if not new_mapdata:
|
if not new_mapdata:
|
||||||
if 'module_path' in old_mapdata:
|
if "module_path" in old_mapdata:
|
||||||
for mapdata in self.maps_from_module(old_mapdata['module_path']):
|
for mapdata in self.maps_from_module(old_mapdata["module_path"]):
|
||||||
loaded_mapdata[mapdata['zcoord']] = mapdata
|
loaded_mapdata[mapdata["zcoord"]] = mapdata
|
||||||
else:
|
else:
|
||||||
# nowhere to reload from - use what we have
|
# nowhere to reload from - use what we have
|
||||||
loaded_mapdata[zcoord] = old_mapdata
|
loaded_mapdata[zcoord] = old_mapdata
|
||||||
|
|
@ -198,7 +199,7 @@ class XYZGrid(DefaultScript):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for mapdata in mapdatas:
|
for mapdata in mapdatas:
|
||||||
zcoord = mapdata.get('zcoord')
|
zcoord = mapdata.get("zcoord")
|
||||||
if not zcoord:
|
if not zcoord:
|
||||||
raise RuntimeError("XYZGrid.add_map data must contain 'zcoord'.")
|
raise RuntimeError("XYZGrid.add_map data must contain 'zcoord'.")
|
||||||
|
|
||||||
|
|
@ -220,7 +221,7 @@ class XYZGrid(DefaultScript):
|
||||||
if remove_objects:
|
if remove_objects:
|
||||||
# we can't batch-delete because we want to run the .delete
|
# we can't batch-delete because we want to run the .delete
|
||||||
# method that also wipes exits and moves content to save locations
|
# 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()
|
xyzroom.delete()
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
|
|
@ -234,7 +235,7 @@ class XYZGrid(DefaultScript):
|
||||||
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
self.remove_map(*(zcoord for zcoord in self.db.map_data), remove_objects=True)
|
||||||
super().delete()
|
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
|
Create/recreate/update the in-game grid based on the stored Maps or for a specific Map
|
||||||
or coordinate.
|
or coordinate.
|
||||||
|
|
@ -255,7 +256,7 @@ class XYZGrid(DefaultScript):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
x, y, z = xyz
|
x, y, z = xyz
|
||||||
wildcard = '*'
|
wildcard = "*"
|
||||||
|
|
||||||
if z == wildcard:
|
if z == wildcard:
|
||||||
xymaps = self.grid
|
xymaps = self.grid
|
||||||
|
|
@ -293,8 +294,10 @@ def get_xyzgrid(print_errors=True):
|
||||||
xyzgrid.reload()
|
xyzgrid.reload()
|
||||||
return xyzgrid
|
return xyzgrid
|
||||||
elif len(xyzgrid) > 1:
|
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]
|
xyzgrid = xyzgrid[0]
|
||||||
try:
|
try:
|
||||||
if not xyzgrid.ndb.loaded:
|
if not xyzgrid.ndb.loaded:
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,8 @@ class XYZManager(ObjectManager):
|
||||||
efficiently querying the room in the database based on XY coordinates.
|
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
|
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
|
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
|
x, y, z = xyz
|
||||||
wildcard = '*'
|
wildcard = "*"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
self
|
self.filter_family(**kwargs)
|
||||||
.filter_family(**kwargs)
|
|
||||||
.filter(
|
.filter(
|
||||||
Q() if x == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
|
if x == wildcard
|
||||||
|
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q() if y == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
|
if y == wildcard
|
||||||
|
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q() if z == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
|
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.
|
Always return a single matched entity directly. This accepts no `*`-wildcards.
|
||||||
This will also find children of XYZRooms on the given coordinates.
|
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
|
# error - mimic default get() behavior but with a little more info
|
||||||
x, y, z = xyz
|
x, y, z = xyz
|
||||||
inp = (f"Query: xyz=({x},{y},{z}), " +
|
inp = f"Query: xyz=({x},{y},{z}), " + ",".join(
|
||||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
f"{key}={val}" for key, val in kwargs.items()
|
||||||
|
)
|
||||||
if ncount > 1:
|
if ncount > 1:
|
||||||
raise self.model.MultipleObjectsReturned(inp)
|
raise self.model.MultipleObjectsReturned(inp)
|
||||||
else:
|
else:
|
||||||
|
|
@ -108,8 +115,7 @@ class XYZExitManager(XYZManager):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filter_xyz_exit(self, xyz=('*', '*', '*'),
|
def filter_xyz_exit(self, xyz=("*", "*", "*"), xyz_destination=("*", "*", "*"), **kwargs):
|
||||||
xyz_destination=('*', '*', '*'), **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Used by exits (objects with a source and -destination property).
|
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
|
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
|
x, y, z = xyz
|
||||||
xdest, ydest, zdest = xyz_destination
|
xdest, ydest, zdest = xyz_destination
|
||||||
wildcard = '*'
|
wildcard = "*"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
self
|
self.filter_family(**kwargs)
|
||||||
.filter_family(**kwargs)
|
|
||||||
.filter(
|
.filter(
|
||||||
Q() if x == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY))
|
if x == wildcard
|
||||||
|
else Q(db_tags__db_key=str(x), db_tags__db_category=MAP_X_TAG_CATEGORY)
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q() if y == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY))
|
if y == wildcard
|
||||||
|
else Q(db_tags__db_key=str(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q() if z == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY))
|
if z == wildcard
|
||||||
|
else Q(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q() if xdest == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY))
|
if xdest == wildcard
|
||||||
|
else Q(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_TAG_CATEGORY)
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q() if ydest == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY))
|
if ydest == wildcard
|
||||||
|
else Q(db_tags__db_key=str(ydest), db_tags__db_category=MAP_YDEST_TAG_CATEGORY)
|
||||||
|
)
|
||||||
.filter(
|
.filter(
|
||||||
Q() if zdest == wildcard
|
Q()
|
||||||
else Q(db_tags__db_key=str(zdest), db_tags__db_category=MAP_ZDEST_TAG_CATEGORY))
|
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
|
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.
|
exit. All source/destination coordinates (as well as the map's name) are required.
|
||||||
|
|
@ -199,8 +216,7 @@ class XYZExitManager(XYZManager):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
self
|
self.filter(db_tags__db_key=str(z), db_tags__db_category=MAP_Z_TAG_CATEGORY)
|
||||||
.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(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(y), db_tags__db_category=MAP_Y_TAG_CATEGORY)
|
||||||
.filter(db_tags__db_key=str(xdest), db_tags__db_category=MAP_XDEST_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)
|
.get(**kwargs)
|
||||||
)
|
)
|
||||||
except self.model.DoesNotExist:
|
except self.model.DoesNotExist:
|
||||||
inp = (f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," +
|
inp = f"xyz=({x},{y},{z}),xyz_destination=({xdest},{ydest},{zdest})," + ",".join(
|
||||||
",".join(f"{key}={val}" for key, val in kwargs.items()))
|
f"{key}={val}" for key, val in kwargs.items()
|
||||||
raise self.model.DoesNotExist(f"{self.model.__name__} "
|
)
|
||||||
f"matching query {inp} does not exist.")
|
raise self.model.DoesNotExist(
|
||||||
|
f"{self.model.__name__} " f"matching query {inp} does not exist."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class XYZRoom(DefaultRoom):
|
class XYZRoom(DefaultRoom):
|
||||||
|
|
@ -244,10 +262,10 @@ class XYZRoom(DefaultRoom):
|
||||||
|
|
||||||
# default settings for map visualization
|
# default settings for map visualization
|
||||||
map_display = True
|
map_display = True
|
||||||
map_mode = 'nodes' # or 'scan'
|
map_mode = "nodes" # or 'scan'
|
||||||
map_visual_range = 2
|
map_visual_range = 2
|
||||||
map_character_symbol = "|g@|n"
|
map_character_symbol = "|g@|n"
|
||||||
map_align = 'c'
|
map_align = "c"
|
||||||
map_target_path_style = "|y{display_symbol}|n"
|
map_target_path_style = "|y{display_symbol}|n"
|
||||||
map_fill_all = True
|
map_fill_all = True
|
||||||
map_separator_char = "|x~|n"
|
map_separator_char = "|x~|n"
|
||||||
|
|
@ -267,8 +285,10 @@ class XYZRoom(DefaultRoom):
|
||||||
z = self.tags.get(category=MAP_Z_TAG_CATEGORY, return_list=False)
|
z = self.tags.get(category=MAP_Z_TAG_CATEGORY, return_list=False)
|
||||||
if x is None or y is None or z is None:
|
if x is None or y is None or z is None:
|
||||||
# don't cache unfinished coordinate (probably tags have not finished saving)
|
# 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
|
return tuple(
|
||||||
for coord in (x, y, z))
|
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)
|
# 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))
|
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
|
return self._xymap
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Creation method aware of XYZ coordinates.
|
||||||
|
|
||||||
|
|
@ -316,14 +336,18 @@ class XYZRoom(DefaultRoom):
|
||||||
try:
|
try:
|
||||||
x, y, z = xyz
|
x, y, z = xyz
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None, [f"XYRroom.create got `xyz={xyz}` - needs a valid (X,Y,Z) "
|
return None, [
|
||||||
"coordinate of ints/strings."]
|
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))
|
existing_query = cls.objects.filter_xyz(xyz=(x, y, z))
|
||||||
if existing_query.exists():
|
if existing_query.exists():
|
||||||
existing_room = existing_query.first()
|
existing_room = existing_query.first()
|
||||||
return None, [f"XYRoom XYZ=({x},{y},{z}) already exists "
|
return None, [
|
||||||
f"(existing room is named '{existing_room.db_key}')!"]
|
f"XYRoom XYZ=({x},{y},{z}) already exists "
|
||||||
|
f"(existing room is named '{existing_room.db_key}')!"
|
||||||
|
]
|
||||||
|
|
||||||
tags = (
|
tags = (
|
||||||
(str(x), MAP_X_TAG_CATEGORY),
|
(str(x), MAP_X_TAG_CATEGORY),
|
||||||
|
|
@ -410,26 +434,29 @@ class XYZRoom(DefaultRoom):
|
||||||
xyz = self.xyz
|
xyz = self.xyz
|
||||||
xymap = self.xyzgrid.get_map(xyz[2])
|
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.
|
# show the near-area map.
|
||||||
map_character_symbol = kwargs.get(
|
map_character_symbol = kwargs.get(
|
||||||
'map_character_symbol',
|
"map_character_symbol",
|
||||||
xymap.options.get("map_character_symbol", self.map_character_symbol))
|
xymap.options.get("map_character_symbol", self.map_character_symbol),
|
||||||
|
)
|
||||||
map_visual_range = kwargs.get(
|
map_visual_range = kwargs.get(
|
||||||
"map_visual_range", xymap.options.get("map_visual_range", self.map_visual_range))
|
"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_mode = kwargs.get("map_mode", xymap.options.get("map_mode", self.map_mode))
|
||||||
map_align = kwargs.get(
|
map_align = kwargs.get("map_align", xymap.options.get("map_align", self.map_align))
|
||||||
"map_align", xymap.options.get("map_align", self.map_align))
|
|
||||||
map_target_path_style = kwargs.get(
|
map_target_path_style = kwargs.get(
|
||||||
"map_target_path_style",
|
"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_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 = kwargs.get(
|
||||||
"map_separator_char",
|
"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()
|
client_width, _ = looker.sessions.get()[0].get_client_size()
|
||||||
|
|
||||||
|
|
@ -438,15 +465,14 @@ class XYZRoom(DefaultRoom):
|
||||||
if map_area_client:
|
if map_area_client:
|
||||||
display_width = client_width
|
display_width = client_width
|
||||||
else:
|
else:
|
||||||
display_width = max(map_width,
|
display_width = max(map_width, max(len(line) for line in room_desc.split("\n")))
|
||||||
max(len(line) for line in room_desc.split("\n")))
|
|
||||||
|
|
||||||
# align map
|
# align map
|
||||||
map_indent = 0
|
map_indent = 0
|
||||||
sep_width = display_width
|
sep_width = display_width
|
||||||
if map_align == 'r':
|
if map_align == "r":
|
||||||
map_indent = max(0, display_width - map_width)
|
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)
|
map_indent = max(0, (display_width - map_width) // 2)
|
||||||
|
|
||||||
# data set by the goto/path-command, for displaying the shortest path
|
# 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,
|
target_path_style=map_target_path_style,
|
||||||
character=map_character_symbol,
|
character=map_character_symbol,
|
||||||
max_size=(display_width, None),
|
max_size=(display_width, None),
|
||||||
indent=map_indent
|
indent=map_indent,
|
||||||
)
|
)
|
||||||
sep = map_separator_char * sep_width
|
sep = map_separator_char * sep_width
|
||||||
map_display = f"{sep}|n\n{map_display}\n{sep}"
|
map_display = f"{sep}|n\n{map_display}\n{sep}"
|
||||||
|
|
@ -523,8 +549,16 @@ class XYZExit(DefaultExit):
|
||||||
return self._xyz_destination
|
return self._xyz_destination
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, key, account=None, xyz=(0, 0, 'map'), xyz_destination=(0, 0, 'map'),
|
def create(
|
||||||
location=None, destination=None, **kwargs):
|
cls,
|
||||||
|
key,
|
||||||
|
account=None,
|
||||||
|
xyz=(0, 0, "map"),
|
||||||
|
xyz_destination=(0, 0, "map"),
|
||||||
|
location=None,
|
||||||
|
destination=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Creation method aware of coordinates.
|
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`."]
|
return None, ["XYExit.create need either `xyz=(X,Y,Z)` coordinate or a `location`."]
|
||||||
else:
|
else:
|
||||||
source = XYZRoom.objects.get_xyz(xyz=(x, y, z))
|
source = XYZRoom.objects.get_xyz(xyz=(x, y, z))
|
||||||
tags.extend(((str(x), MAP_X_TAG_CATEGORY),
|
tags.extend(
|
||||||
(str(y), MAP_Y_TAG_CATEGORY),
|
(
|
||||||
(str(z), MAP_Z_TAG_CATEGORY)))
|
(str(x), MAP_X_TAG_CATEGORY),
|
||||||
|
(str(y), MAP_Y_TAG_CATEGORY),
|
||||||
|
(str(z), MAP_Z_TAG_CATEGORY),
|
||||||
|
)
|
||||||
|
)
|
||||||
if destination:
|
if destination:
|
||||||
dest = destination
|
dest = destination
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
xdest, ydest, zdest = xyz_destination
|
xdest, ydest, zdest = xyz_destination
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None, ["XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
|
return None, [
|
||||||
"or a `destination`."]
|
"XYExit.create need either `xyz_destination=(X,Y,Z)` coordinate "
|
||||||
|
"or a `destination`."
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
dest = XYZRoom.objects.get_xyz(xyz=(xdest, ydest, zdest))
|
dest = XYZRoom.objects.get_xyz(xyz=(xdest, ydest, zdest))
|
||||||
tags.extend(((str(xdest), MAP_XDEST_TAG_CATEGORY),
|
tags.extend(
|
||||||
(str(ydest), MAP_YDEST_TAG_CATEGORY),
|
(
|
||||||
(str(zdest), MAP_ZDEST_TAG_CATEGORY)))
|
(str(xdest), MAP_XDEST_TAG_CATEGORY),
|
||||||
|
(str(ydest), MAP_YDEST_TAG_CATEGORY),
|
||||||
|
(str(zdest), MAP_ZDEST_TAG_CATEGORY),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
return DefaultExit.create(
|
return DefaultExit.create(
|
||||||
key, source, dest,
|
key, source, dest, account=account, tags=tags, typeclass=cls, **kwargs
|
||||||
account=account, tags=tags, typeclass=cls, **kwargs)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,4 @@ Rolling dice - Griatch, 2012
|
||||||
from .dice import roll # noqa
|
from .dice import roll # noqa
|
||||||
from .dice import roll_dice # noqa
|
from .dice import roll_dice # noqa
|
||||||
from .dice import CmdDice # 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
|
from evennia import default_cmds, CmdSet
|
||||||
|
|
||||||
|
|
||||||
def roll(dicenum, dicetype, modifier=None,
|
def roll(dicenum, dicetype, modifier=None, conditional=None, return_tuple=False):
|
||||||
conditional=None, return_tuple=False):
|
|
||||||
"""
|
"""
|
||||||
This is a standard dice roller.
|
This is a standard dice roller.
|
||||||
|
|
||||||
|
|
@ -141,6 +140,7 @@ def roll(dicenum, dicetype, modifier=None,
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# legacy alias
|
# legacy alias
|
||||||
roll_dice = roll
|
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 ordered_permutation_regex, regex_tuple_from_key_alias # noqa
|
||||||
from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa
|
from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa
|
||||||
from .rpsystem import SdescHandler, RecogHandler # 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 RPSystemCmdSet # noqa
|
||||||
from .rpsystem import ContribRPObject # noqa
|
from .rpsystem import ContribRPObject # noqa
|
||||||
from .rpsystem import ContribRPRoom # noqa
|
from .rpsystem import ContribRPRoom # noqa
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,8 @@ class LanguageHandler(DefaultScript):
|
||||||
raise IndexError(
|
raise IndexError(
|
||||||
"Could not find a matching phoneme for the grammar "
|
"Could not find a matching phoneme for the grammar "
|
||||||
f"'{match.group()}'. Make there is at least one phoneme matching this "
|
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()
|
translation[word.lower()] = new_word.lower()
|
||||||
|
|
||||||
if manual_translations:
|
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()))
|
errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group()))
|
||||||
elif nmatches == 1:
|
elif nmatches == 1:
|
||||||
# a unique match - parse into intermediary representation
|
# a unique match - parse into intermediary representation
|
||||||
case = '~' # retain original case of sdesc
|
case = "~" # retain original case of sdesc
|
||||||
if case_sensitive:
|
if case_sensitive:
|
||||||
# case sensitive mode
|
# case sensitive mode
|
||||||
# internal flags for the case used for the original /query
|
# 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
|
# self-refs are kept as-is, others are parsed by case
|
||||||
matchtext = marker_match.group().lstrip(_PREFIX)
|
matchtext = marker_match.group().lstrip(_PREFIX)
|
||||||
if matchtext.istitle():
|
if matchtext.istitle():
|
||||||
case = 't'
|
case = "t"
|
||||||
elif matchtext.isupper():
|
elif matchtext.isupper():
|
||||||
case = '^'
|
case = "^"
|
||||||
elif matchtext.islower():
|
elif matchtext.islower():
|
||||||
case = 'v'
|
case = "v"
|
||||||
|
|
||||||
key = "#%i%s" % (obj.id, case)
|
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
|
mapping[key] = obj
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
@ -601,8 +601,9 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs):
|
||||||
"""
|
"""
|
||||||
case_sensitive = kwargs.pop("case_sensitive", True)
|
case_sensitive = kwargs.pop("case_sensitive", True)
|
||||||
try:
|
try:
|
||||||
emote, obj_mapping = parse_sdescs_and_recogs(sender, receivers, emote,
|
emote, obj_mapping = parse_sdescs_and_recogs(
|
||||||
case_sensitive=case_sensitive)
|
sender, receivers, emote, case_sensitive=case_sensitive
|
||||||
|
)
|
||||||
emote, language_mapping = parse_language(sender, emote)
|
emote, language_mapping = parse_language(sender, emote)
|
||||||
except (EmoteError, LanguageError) as err:
|
except (EmoteError, LanguageError) as err:
|
||||||
# handle all error messages, don't hide actual coding errors
|
# 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).
|
# (the text could have nested object mappings).
|
||||||
emote = _RE_REF.sub(r"{{#\1}}", emote)
|
emote = _RE_REF.sub(r"{{#\1}}", emote)
|
||||||
# if anonymous_add is passed as a kwarg, collect and remove it from kwargs
|
# if anonymous_add is passed as a kwarg, collect and remove it from kwargs
|
||||||
if 'anonymous_add' in kwargs:
|
if "anonymous_add" in kwargs:
|
||||||
anonymous_add = kwargs.pop('anonymous_add')
|
anonymous_add = kwargs.pop("anonymous_add")
|
||||||
if anonymous_add and not any(1 for tag in obj_mapping if tag.startswith(skey)):
|
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
|
# no self-reference in the emote - add to the end
|
||||||
obj_mapping[skey] = sender
|
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
|
# make sure receiver always sees their real name
|
||||||
rkey_start = "#%i" % receiver.id
|
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)):
|
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
|
# we could have #%i^, #%it etc depending on input case - we want the
|
||||||
# self-reference to retain case.
|
# self-reference to retain case.
|
||||||
receiver_sdesc_mapping[rkey] = process_sdesc(
|
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
|
# do the template replacement of the sdesc/recog {#num} markers
|
||||||
receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs)
|
receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs)
|
||||||
|
|
@ -1709,14 +1711,14 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject):
|
||||||
if not sdesc:
|
if not sdesc:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
ref = kwargs.get('ref', '~') # ~ to keep sdesc unchanged
|
ref = kwargs.get("ref", "~") # ~ to keep sdesc unchanged
|
||||||
if 't' in ref:
|
if "t" in ref:
|
||||||
# we only want to capitalize the first letter if there are many words
|
# we only want to capitalize the first letter if there are many words
|
||||||
sdesc = sdesc.lower()
|
sdesc = sdesc.lower()
|
||||||
sdesc = sdesc[0].upper() + sdesc[1:] if len(sdesc) > 1 else sdesc.upper()
|
sdesc = sdesc[0].upper() + sdesc[1:] if len(sdesc) > 1 else sdesc.upper()
|
||||||
elif '^' in ref:
|
elif "^" in ref:
|
||||||
sdesc = sdesc.upper()
|
sdesc = sdesc.upper()
|
||||||
elif 'v' in ref:
|
elif "v" in ref:
|
||||||
sdesc = sdesc.lower()
|
sdesc = sdesc.lower()
|
||||||
return "|b%s|n" % sdesc
|
return "|b%s|n" % sdesc
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@ class TestRPSystem(BaseEvenniaTest):
|
||||||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
||||||
t2 = time.time()
|
t2 = time.time()
|
||||||
# print(f"t1: {t1 - t0}, t2: {t2 - t1}")
|
# 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))
|
self.assertEqual(result, (Anything, self.speaker, self.speaker.key))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,16 +59,28 @@ class TraitHandlerTest(_TraitHandlerBase):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.traithandler.add("test1", name="Test1", trait_type="trait")
|
self.traithandler.add("test1", name="Test1", trait_type="trait")
|
||||||
self.traithandler.add(
|
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):
|
def test_add_trait(self):
|
||||||
self.assertEqual(
|
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.assertEqual(
|
||||||
self._get_dbstore("test2"),
|
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)
|
self.assertEqual(len(self.traithandler), 2)
|
||||||
|
|
||||||
|
|
@ -328,7 +340,12 @@ class TestTraitCounter(_TraitHandlerBase):
|
||||||
max=10,
|
max=10,
|
||||||
extra_val1="xvalue1",
|
extra_val1="xvalue1",
|
||||||
extra_val2="xvalue2",
|
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")
|
self.trait = self.traithandler.get("test1")
|
||||||
|
|
||||||
|
|
@ -348,7 +365,12 @@ class TestTraitCounter(_TraitHandlerBase):
|
||||||
"max": 10,
|
"max": 10,
|
||||||
"extra_val1": "xvalue1",
|
"extra_val1": "xvalue1",
|
||||||
"extra_val2": "xvalue2",
|
"extra_val2": "xvalue2",
|
||||||
"descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
"descs": {
|
||||||
|
0: "range0",
|
||||||
|
2: "range1",
|
||||||
|
5: "range2",
|
||||||
|
7: "range3",
|
||||||
|
},
|
||||||
"rate": 0,
|
"rate": 0,
|
||||||
"ratetarget": None,
|
"ratetarget": None,
|
||||||
"last_update": None,
|
"last_update": None,
|
||||||
|
|
@ -507,7 +529,12 @@ class TestTraitCounterTimed(_TraitHandlerBase):
|
||||||
max=100,
|
max=100,
|
||||||
extra_val1="xvalue1",
|
extra_val1="xvalue1",
|
||||||
extra_val2="xvalue2",
|
extra_val2="xvalue2",
|
||||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
descs={
|
||||||
|
0: "range0",
|
||||||
|
2: "range1",
|
||||||
|
5: "range2",
|
||||||
|
7: "range3",
|
||||||
|
},
|
||||||
rate=1,
|
rate=1,
|
||||||
ratetarget=None,
|
ratetarget=None,
|
||||||
)
|
)
|
||||||
|
|
@ -579,7 +606,12 @@ class TestTraitGauge(_TraitHandlerBase):
|
||||||
mod=2,
|
mod=2,
|
||||||
extra_val1="xvalue1",
|
extra_val1="xvalue1",
|
||||||
extra_val2="xvalue2",
|
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")
|
self.trait = self.traithandler.get("test1")
|
||||||
|
|
||||||
|
|
@ -598,7 +630,12 @@ class TestTraitGauge(_TraitHandlerBase):
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"extra_val1": "xvalue1",
|
"extra_val1": "xvalue1",
|
||||||
"extra_val2": "xvalue2",
|
"extra_val2": "xvalue2",
|
||||||
"descs": {0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
"descs": {
|
||||||
|
0: "range0",
|
||||||
|
2: "range1",
|
||||||
|
5: "range2",
|
||||||
|
7: "range3",
|
||||||
|
},
|
||||||
"rate": 0,
|
"rate": 0,
|
||||||
"ratetarget": None,
|
"ratetarget": None,
|
||||||
"last_update": None,
|
"last_update": None,
|
||||||
|
|
@ -763,7 +800,12 @@ class TestTraitGaugeTimed(_TraitHandlerBase):
|
||||||
min=0,
|
min=0,
|
||||||
extra_val1="xvalue1",
|
extra_val1="xvalue1",
|
||||||
extra_val2="xvalue2",
|
extra_val2="xvalue2",
|
||||||
descs={0: "range0", 2: "range1", 5: "range2", 7: "range3",},
|
descs={
|
||||||
|
0: "range0",
|
||||||
|
2: "range1",
|
||||||
|
5: "range2",
|
||||||
|
7: "range3",
|
||||||
|
},
|
||||||
rate=1,
|
rate=1,
|
||||||
ratetarget=None,
|
ratetarget=None,
|
||||||
)
|
)
|
||||||
|
|
@ -831,8 +873,20 @@ class TestNumericTraitOperators(BaseEvenniaTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# direct instantiation for testing only; use TraitHandler in production
|
# direct instantiation for testing only; use TraitHandler in production
|
||||||
self.st = traits.Trait({"name": "Strength", "trait_type": "trait", "value": 8,})
|
self.st = traits.Trait(
|
||||||
self.at = traits.Trait({"name": "Attack", "trait_type": "trait", "value": 4,})
|
{
|
||||||
|
"name": "Strength",
|
||||||
|
"trait_type": "trait",
|
||||||
|
"value": 8,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.at = traits.Trait(
|
||||||
|
{
|
||||||
|
"name": "Attack",
|
||||||
|
"trait_type": "trait",
|
||||||
|
"value": 4,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.st, self.at = None, None
|
self.st, self.at = None, None
|
||||||
|
|
|
||||||
|
|
@ -526,6 +526,7 @@ class MandatoryTraitKey:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TraitHandler:
|
class TraitHandler:
|
||||||
"""
|
"""
|
||||||
Factory class that instantiates Trait objects. Must be assigned as a property
|
Factory class that instantiates Trait objects. Must be assigned as a property
|
||||||
|
|
@ -794,10 +795,7 @@ class TraitProperty:
|
||||||
trait = traithandler.get(self._trait_key)
|
trait = traithandler.get(self._trait_key)
|
||||||
if trait is None:
|
if trait is None:
|
||||||
# initialize the trait
|
# initialize the trait
|
||||||
traithandler.add(
|
traithandler.add(self._trait_key, **self._trait_properties)
|
||||||
self._trait_key,
|
|
||||||
**self._trait_properties
|
|
||||||
)
|
|
||||||
trait = traithandler.get(self._trait_key) # caches it in the traithandler
|
trait = traithandler.get(self._trait_key) # caches it in the traithandler
|
||||||
self._cache[instance] = trait
|
self._cache[instance] = trait
|
||||||
return self._cache[instance]
|
return self._cache[instance]
|
||||||
|
|
@ -809,6 +807,7 @@ class TraitProperty:
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Parent Trait class
|
# 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.
|
# Commands for the state when the lid covering the button is closed.
|
||||||
|
|
||||||
|
|
||||||
class CmdPushLidClosed(Command):
|
class CmdPushLidClosed(Command):
|
||||||
"""
|
"""
|
||||||
Push the red button (lid closed)
|
Push the red button (lid closed)
|
||||||
|
|
@ -121,23 +122,27 @@ class CmdSmashGlass(Command):
|
||||||
"""
|
"""
|
||||||
rand = random.random()
|
rand = random.random()
|
||||||
self.caller.location.msg_contents(
|
self.caller.location.msg_contents(
|
||||||
f"{self.caller.name} tries to smash the glass of the button.",
|
f"{self.caller.name} tries to smash the glass of the button.", exclude=self.caller
|
||||||
exclude=self.caller)
|
)
|
||||||
|
|
||||||
if rand < 0.2:
|
if rand < 0.2:
|
||||||
string = ("You smash your hand against the glass"
|
string = (
|
||||||
" with all your might. The lid won't budge"
|
"You smash your hand against the glass"
|
||||||
" but you cause quite the tremor through the button's mount."
|
" with all your might. The lid won't budge"
|
||||||
"\nIt looks like the button's lamp stopped working for the time being, "
|
" but you cause quite the tremor through the button's mount."
|
||||||
"but the lid is still as closed as ever.")
|
"\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 is the button itself
|
||||||
self.obj.break_lamp()
|
self.obj.break_lamp()
|
||||||
elif rand < 0.6:
|
elif rand < 0.6:
|
||||||
string = "You hit the lid hard. It doesn't move an inch."
|
string = "You hit the lid hard. It doesn't move an inch."
|
||||||
else:
|
else:
|
||||||
string = ("You place a well-aimed fist against the glass of the lid."
|
string = (
|
||||||
" Unfortunately all you get is a pain in your hand. Maybe"
|
"You place a well-aimed fist against the glass of the lid."
|
||||||
" you should just try to just ... open the lid instead?")
|
" 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)
|
self.caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -165,8 +170,8 @@ class CmdOpenLid(Command):
|
||||||
string += "the lid will soon close again."
|
string += "the lid will soon close again."
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
self.caller.location.msg_contents(
|
self.caller.location.msg_contents(
|
||||||
f"{self.caller.name} opens the lid of the button.",
|
f"{self.caller.name} opens the lid of the button.", exclude=self.caller
|
||||||
exclude=self.caller)
|
)
|
||||||
self.obj.to_open_state()
|
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
|
# 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.
|
# push command will work. You can also close the lid again.
|
||||||
|
|
||||||
|
|
||||||
class CmdPushLidOpen(Command):
|
class CmdPushLidOpen(Command):
|
||||||
"""
|
"""
|
||||||
Push the red button
|
Push the red button
|
||||||
|
|
@ -225,15 +231,15 @@ class CmdPushLidOpen(Command):
|
||||||
"""
|
"""
|
||||||
# pause a little between each message.
|
# pause a little between each message.
|
||||||
self.caller.msg("You reach out to press the big red button ...")
|
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")
|
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")
|
self.caller.msg("\n\n|xThe world goes dark ...|n")
|
||||||
|
|
||||||
name = self.caller.name
|
name = self.caller.name
|
||||||
self.caller.location.msg_contents(
|
self.caller.location.msg_contents(
|
||||||
f"{name} presses the button. BOOM! {name} is blinded by a flash!",
|
f"{name} presses the button. BOOM! {name} is blinded by a flash!", exclude=self.caller
|
||||||
exclude=self.caller)
|
)
|
||||||
self.obj.blind_target(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.
|
# 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.msg("You close the button's lid. It clicks back into place.")
|
||||||
self.caller.location.msg_contents(
|
self.caller.location.msg_contents(
|
||||||
f"{self.caller.name} closes the button's lid.",
|
f"{self.caller.name} closes the button's lid.", exclude=self.caller
|
||||||
exclude=self.caller)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LidOpenCmdSet(CmdSet):
|
class LidOpenCmdSet(CmdSet):
|
||||||
|
|
@ -286,6 +292,7 @@ class LidOpenCmdSet(CmdSet):
|
||||||
# Commands for when the button has been pushed and the player is blinded. This
|
# 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.
|
# replaces commands on the player making them 'blind' for a while.
|
||||||
|
|
||||||
|
|
||||||
class CmdBlindLook(Command):
|
class CmdBlindLook(Command):
|
||||||
"""
|
"""
|
||||||
Looking around in darkness
|
Looking around in darkness
|
||||||
|
|
@ -317,12 +324,14 @@ class CmdBlindLook(Command):
|
||||||
string = "You fumble around, hands outstretched. You bump your knee."
|
string = "You fumble around, hands outstretched. You bump your knee."
|
||||||
else:
|
else:
|
||||||
# trying to look
|
# trying to look
|
||||||
string = ("You are temporarily blinded by the flash. "
|
string = (
|
||||||
"Until it wears off, all you can do is feel around blindly.")
|
"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.msg(string)
|
||||||
self.caller.location.msg_contents(
|
self.caller.location.msg_contents(
|
||||||
f"{self.caller.name} stumbles around, blinded.",
|
f"{self.caller.name} stumbles around, blinded.", exclude=self.caller
|
||||||
exclude=self.caller)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CmdBlindHelp(Command):
|
class CmdBlindHelp(Command):
|
||||||
|
|
@ -420,20 +429,26 @@ class RedButton(DefaultObject):
|
||||||
`button = create_object(RedButton, ..., attributes=[('desc', 'my desc')])`.
|
`button = create_object(RedButton, ..., attributes=[('desc', 'my desc')])`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# these are the pre-set descriptions. Setting attributes will override
|
# these are the pre-set descriptions. Setting attributes will override
|
||||||
# these on the fly.
|
# these on the fly.
|
||||||
|
|
||||||
desc_closed_lid = ("This is a large red button, inviting yet evil-looking. "
|
desc_closed_lid = (
|
||||||
"A closed glass lid protects it.")
|
"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_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."
|
auto_close_msg = "The button's glass lid silently slides back in place."
|
||||||
lamp_breaks_msg = "The lamp flickers, the button going dark."
|
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."
|
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
|
# note that this is a list. A random message will display each time
|
||||||
blink_msgs = ["The red button flashes briefly.",
|
blink_msgs = [
|
||||||
"The red button blinks invitingly.",
|
"The red button flashes briefly.",
|
||||||
"The red button flashes. You know you wanna push it!"]
|
"The red button blinks invitingly.",
|
||||||
|
"The red button flashes. You know you wanna push it!",
|
||||||
|
]
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -523,9 +538,9 @@ class RedButton(DefaultObject):
|
||||||
self.cmdset.add(LidOpenCmdSet)
|
self.cmdset.add(LidOpenCmdSet)
|
||||||
|
|
||||||
# wait 20s then call self.to_closed_state with a message as argument
|
# wait 20s then call self.to_closed_state with a message as argument
|
||||||
delay(35, self.to_closed_state,
|
delay(
|
||||||
self.db.auto_close_msg or self.auto_close_msg,
|
35, self.to_closed_state, self.db.auto_close_msg or self.auto_close_msg, persistent=True
|
||||||
persistent=True)
|
)
|
||||||
|
|
||||||
def _unblind_target(self, caller):
|
def _unblind_target(self, caller):
|
||||||
"""
|
"""
|
||||||
|
|
@ -536,7 +551,8 @@ class RedButton(DefaultObject):
|
||||||
caller.msg("You blink feverishly as your eyesight slowly returns.")
|
caller.msg("You blink feverishly as your eyesight slowly returns.")
|
||||||
self.location.msg_contents(
|
self.location.msg_contents(
|
||||||
f"{caller.name} seems to be recovering their eyesight, blinking feverishly.",
|
f"{caller.name} seems to be recovering their eyesight, blinking feverishly.",
|
||||||
exclude=caller)
|
exclude=caller,
|
||||||
|
)
|
||||||
|
|
||||||
def blind_target(self, caller):
|
def blind_target(self, caller):
|
||||||
"""
|
"""
|
||||||
|
|
@ -554,8 +570,7 @@ class RedButton(DefaultObject):
|
||||||
|
|
||||||
# wait 20s then call self._unblind to remove blindness effect. The
|
# wait 20s then call self._unblind to remove blindness effect. The
|
||||||
# persistent=True means the delay should survive a server reload.
|
# persistent=True means the delay should survive a server reload.
|
||||||
delay(20, self._unblind_target, caller,
|
delay(20, self._unblind_target, caller, persistent=True)
|
||||||
persistent=True)
|
|
||||||
|
|
||||||
def _unbreak_lamp(self):
|
def _unbreak_lamp(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,10 @@ def info2(caller):
|
||||||
|
|
||||||
|
|
||||||
def info3(caller):
|
def info3(caller):
|
||||||
text = ("'Well ... I'm sort of busy so, have to go. NPC business. "
|
text = (
|
||||||
"Important stuff. You wouldn't understand.'")
|
"'Well ... I'm sort of busy so, have to go. NPC business. "
|
||||||
|
"Important stuff. You wouldn't understand.'"
|
||||||
|
)
|
||||||
|
|
||||||
options = (
|
options = (
|
||||||
{"desc": "Oookay ... I won't keep you. Bye.", "goto": "END"},
|
{"desc": "Oookay ... I won't keep you. Bye.", "goto": "END"},
|
||||||
|
|
@ -91,15 +93,15 @@ def END(caller):
|
||||||
|
|
||||||
class CmdTalk(default_cmds.MuxCommand):
|
class CmdTalk(default_cmds.MuxCommand):
|
||||||
"""
|
"""
|
||||||
Talks to an npc
|
Talks to an npc
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
talk
|
talk
|
||||||
|
|
||||||
This command is only available if a talkative non-player-character
|
This command is only available if a talkative non-player-character
|
||||||
(NPC) is actually present. It will strike up a conversation with
|
(NPC) is actually present. It will strike up a conversation with
|
||||||
that NPC and give you options on what to talk about.
|
that NPC and give you options on what to talk about.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "talk"
|
key = "talk"
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
|
@ -113,8 +115,11 @@ class CmdTalk(default_cmds.MuxCommand):
|
||||||
|
|
||||||
# Initiate the menu. Change this if you are putting this on
|
# Initiate the menu. Change this if you are putting this on
|
||||||
# some other custom NPC class.
|
# some other custom NPC class.
|
||||||
EvMenu(self.caller, "evennia.contrib.tutorials.talking_npc.talking_npc",
|
EvMenu(
|
||||||
startnode="menu_start_node")
|
self.caller,
|
||||||
|
"evennia.contrib.tutorials.talking_npc.talking_npc",
|
||||||
|
startnode="menu_start_node",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TalkingCmdSet(CmdSet):
|
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.
|
|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.
|
|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.)
|
|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.no_more_weapons_msg = "you find nothing else of use."
|
||||||
self.db.available_weapons = ["knife", "dagger", "sword", "club"]
|
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.)"
|
helptext += "\n\n (Write 'give up' if you want to abandon your quest.)"
|
||||||
caller.msg(helptext)
|
caller.msg(helptext)
|
||||||
|
|
||||||
|
|
||||||
# for the @detail command we inherit from MuxCommand, since
|
# for the @detail command we inherit from MuxCommand, since
|
||||||
# we want to make use of MuxCommand's pre-parsing of '=' in the
|
# we want to make use of MuxCommand's pre-parsing of '=' in the
|
||||||
# argument.
|
# argument.
|
||||||
|
|
@ -202,22 +203,26 @@ class CmdTutorialLook(default_cmds.CmdLook):
|
||||||
looking_at_obj.at_desc(looker=caller)
|
looking_at_obj.at_desc(looker=caller)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
class CmdTutorialGiveUp(default_cmds.MuxCommand):
|
class CmdTutorialGiveUp(default_cmds.MuxCommand):
|
||||||
"""
|
"""
|
||||||
Give up the tutorial-world quest and return to Limbo, the start room of the
|
Give up the tutorial-world quest and return to Limbo, the start room of the
|
||||||
server.
|
server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "give up"
|
key = "give up"
|
||||||
aliases = ['abort']
|
aliases = ["abort"]
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
outro_room = OutroRoom.objects.all()
|
outro_room = OutroRoom.objects.all()
|
||||||
if outro_room:
|
if outro_room:
|
||||||
outro_room = outro_room[0]
|
outro_room = outro_room[0]
|
||||||
else:
|
else:
|
||||||
self.caller.msg("That didn't work (seems like a bug). "
|
self.caller.msg(
|
||||||
"Try to use the |wteleport|n command instead.")
|
"That didn't work (seems like a bug). "
|
||||||
|
"Try to use the |wteleport|n command instead."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.caller.move_to(outro_room)
|
self.caller.move_to(outro_room)
|
||||||
|
|
@ -312,6 +317,7 @@ class TutorialStartExit(DefaultExit):
|
||||||
will also clean up the intro command.
|
will also clean up the intro command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
self.cmdset.add(CmdSetEvenniaIntro, persistent=True)
|
self.cmdset.add(CmdSetEvenniaIntro, persistent=True)
|
||||||
|
|
||||||
|
|
@ -397,6 +403,7 @@ SUPERUSER_WARNING = (
|
||||||
#
|
#
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class CmdEvenniaIntro(Command):
|
class CmdEvenniaIntro(Command):
|
||||||
"""
|
"""
|
||||||
Start the Evennia intro wizard.
|
Start the Evennia intro wizard.
|
||||||
|
|
@ -405,10 +412,12 @@ class CmdEvenniaIntro(Command):
|
||||||
intro
|
intro
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = "intro"
|
key = "intro"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
from .intro_menu import init_menu
|
from .intro_menu import init_menu
|
||||||
|
|
||||||
# quell also superusers
|
# quell also superusers
|
||||||
if self.caller.account:
|
if self.caller.account:
|
||||||
self.caller.msg("Auto-quelling permissions while in intro ...")
|
self.caller.msg("Auto-quelling permissions while in intro ...")
|
||||||
|
|
@ -463,6 +472,7 @@ class IntroRoom(TutorialRoom):
|
||||||
character.account.execute_cmd("quell")
|
character.account.execute_cmd("quell")
|
||||||
character.msg("(Auto-quelling while in tutorial-world)")
|
character.msg("(Auto-quelling while in tutorial-world)")
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# Bridge - unique room
|
# Bridge - unique room
|
||||||
|
|
@ -1176,4 +1186,3 @@ class OutroRoom(TutorialRoom):
|
||||||
def at_object_leave(self, character, destination):
|
def at_object_leave(self, character, destination):
|
||||||
if character.account:
|
if character.account:
|
||||||
character.account.execute_cmd("unquell")
|
character.account.execute_cmd("unquell")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,8 @@ from .server import AuditedServerSession
|
||||||
from evennia.server.sessionhandler import SESSIONS
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(AUDIT_MASKS=[])
|
||||||
AUDIT_MASKS=[])
|
|
||||||
class AuditingTest(BaseEvenniaTest):
|
class AuditingTest(BaseEvenniaTest):
|
||||||
|
|
||||||
@patch("evennia.server.sessionhandler._ServerSession", AuditedServerSession)
|
@patch("evennia.server.sessionhandler._ServerSession", AuditedServerSession)
|
||||||
def setup_session(self):
|
def setup_session(self):
|
||||||
"""Overrides default one in EvenniaTest"""
|
"""Overrides default one in EvenniaTest"""
|
||||||
|
|
@ -29,8 +27,10 @@ class AuditingTest(BaseEvenniaTest):
|
||||||
SESSIONS.login(session, self.account, testmode=True)
|
SESSIONS.login(session, self.account, testmode=True)
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
@patch(
|
||||||
"evennia.contrib.utils.auditing.outputs.to_syslog")
|
"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_IN", True)
|
||||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
||||||
def test_mask(self):
|
def test_mask(self):
|
||||||
|
|
@ -100,8 +100,10 @@ class AuditingTest(BaseEvenniaTest):
|
||||||
for secret in secrets:
|
for secret in secrets:
|
||||||
self.assertEqual(self.session.mask(secret), secret)
|
self.assertEqual(self.session.mask(secret), secret)
|
||||||
|
|
||||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_CALLBACK",
|
@patch(
|
||||||
"evennia.contrib.utils.auditing.outputs.to_syslog")
|
"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_IN", True)
|
||||||
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
@patch("evennia.contrib.utils.auditing.server.AUDIT_OUT", True)
|
||||||
def test_audit(self):
|
def test_audit(self):
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,6 @@ FieldFill contrib - Tim Ashley Jenkins 2018
|
||||||
|
|
||||||
from .fieldfill import FieldEvMenu # noqa
|
from .fieldfill import FieldEvMenu # noqa
|
||||||
from .fieldfill import CmdTestMenu # noqa
|
from .fieldfill import CmdTestMenu # noqa
|
||||||
from .fieldfill import init_fill_field # noqa
|
from .fieldfill import init_fill_field # noqa
|
||||||
from .fieldfill import form_template_to_dict # noqa
|
from .fieldfill import form_template_to_dict # noqa
|
||||||
from .fieldfill import display_formdata # 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 RandomStringGenerator # noqa
|
||||||
from .random_string_generator import RandomStringGeneratorScript # 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)
|
self._find_elements(regex)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<evennia.contrib.utils.random_string_generator.RandomStringGenerator for {}>".format(
|
return (
|
||||||
self.name
|
"<evennia.contrib.utils.random_string_generator.RandomStringGenerator for {}>".format(
|
||||||
|
self.name
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_script(self):
|
def _get_script(self):
|
||||||
|
|
@ -169,7 +171,8 @@ class RandomStringGenerator:
|
||||||
script = ScriptDB.objects.get(db_key="generator_script")
|
script = ScriptDB.objects.get(db_key="generator_script")
|
||||||
except ScriptDB.DoesNotExist:
|
except ScriptDB.DoesNotExist:
|
||||||
script = create_script(
|
script = create_script(
|
||||||
"evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript")
|
"evennia.contrib.utils.random_string_generator.RandomStringGeneratorScript"
|
||||||
|
)
|
||||||
|
|
||||||
type(self).script = script
|
type(self).script = script
|
||||||
return script
|
return script
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class Command(BaseCommand):
|
||||||
here. Without setting one, the parent's docstring will show (like now).
|
here. Without setting one, the parent's docstring will show (like now).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Each Command class implements the following methods, called in this order
|
# Each Command class implements the following methods, called in this order
|
||||||
# (only func() is actually required):
|
# (only func() is actually required):
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,6 @@ class Object(DefaultObject):
|
||||||
at_say(speaker, message) - by default, called if an object inside this
|
at_say(speaker, message) - by default, called if an object inside this
|
||||||
object speaks
|
object speaks
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ Search the Django documentation for "URL dispatcher" for more help.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
|
|
||||||
# default evennia patterns
|
# default evennia patterns
|
||||||
from evennia.web.urls import urlpatterns as evennia_default_urlpatterns
|
from evennia.web.urls import urlpatterns as evennia_default_urlpatterns
|
||||||
|
|
||||||
|
|
@ -24,7 +25,6 @@ urlpatterns = [
|
||||||
path("webclient/", include("web.webclient.urls")),
|
path("webclient/", include("web.webclient.urls")),
|
||||||
# web admin
|
# web admin
|
||||||
path("admin/", include("web.admin.urls")),
|
path("admin/", include("web.admin.urls")),
|
||||||
|
|
||||||
# add any extra urls here:
|
# add any extra urls here:
|
||||||
# path("mypath/", include("path.to.my.urls.file")),
|
# path("mypath/", include("path.to.my.urls.file")),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ Each dict is on the form
|
||||||
HELP_ENTRY_DICTS = [
|
HELP_ENTRY_DICTS = [
|
||||||
{
|
{
|
||||||
"key": "evennia",
|
"key": "evennia",
|
||||||
"aliases": ['ev'],
|
"aliases": ["ev"],
|
||||||
"category": "General",
|
"category": "General",
|
||||||
"locks": "read:perm(Developer)",
|
"locks": "read:perm(Developer)",
|
||||||
"text": """
|
"text": """
|
||||||
|
|
@ -51,7 +51,7 @@ HELP_ENTRY_DICTS = [
|
||||||
|
|
||||||
There is also a discord channel you can find from the sidebard on evennia.com.
|
There is also a discord channel you can find from the sidebard on evennia.com.
|
||||||
|
|
||||||
"""
|
""",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "building",
|
"key": "building",
|
||||||
|
|
@ -60,6 +60,6 @@ HELP_ENTRY_DICTS = [
|
||||||
Evennia comes with a bunch of default building commands. You can
|
Evennia comes with a bunch of default building commands. You can
|
||||||
find a building tutorial in the evennia documentation.
|
find a building tutorial in the evennia documentation.
|
||||||
|
|
||||||
"""
|
""",
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,7 @@ from dataclasses import dataclass
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import variable_from_module, make_iter, all_from_module
|
||||||
variable_from_module, make_iter, all_from_module)
|
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils.utils import lazy_property
|
from evennia.utils.utils import lazy_property
|
||||||
from evennia.locks.lockhandler import LockHandler
|
from evennia.locks.lockhandler import LockHandler
|
||||||
|
|
@ -86,6 +85,7 @@ class FileHelpEntry:
|
||||||
help command.
|
help command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
aliases: list
|
aliases: list
|
||||||
help_category: str
|
help_category: str
|
||||||
|
|
@ -147,7 +147,7 @@ class FileHelpEntry:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return reverse(
|
return reverse(
|
||||||
'help-entry-detail',
|
"help-entry-detail",
|
||||||
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
|
kwargs={"category": slugify(self.help_category), "topic": slugify(self.key)},
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -192,8 +192,7 @@ class FileHelpStorageHandler:
|
||||||
"""
|
"""
|
||||||
Initialize the storage.
|
Initialize the storage.
|
||||||
"""
|
"""
|
||||||
self.help_file_modules = [str(part).strip()
|
self.help_file_modules = [str(part).strip() for part in make_iter(help_file_modules)]
|
||||||
for part in make_iter(help_file_modules)]
|
|
||||||
self.help_entries = []
|
self.help_entries = []
|
||||||
self.help_entries_dict = {}
|
self.help_entries_dict = {}
|
||||||
self.load()
|
self.load()
|
||||||
|
|
@ -206,13 +205,11 @@ class FileHelpStorageHandler:
|
||||||
loaded_help_dicts = []
|
loaded_help_dicts = []
|
||||||
|
|
||||||
for module_or_path in self.help_file_modules:
|
for module_or_path in self.help_file_modules:
|
||||||
help_dict_list = variable_from_module(
|
help_dict_list = variable_from_module(module_or_path, variable="HELP_ENTRY_DICTS")
|
||||||
module_or_path, variable="HELP_ENTRY_DICTS"
|
|
||||||
)
|
|
||||||
if not help_dict_list:
|
if not help_dict_list:
|
||||||
help_dict_list = [
|
help_dict_list = [
|
||||||
dct for dct in all_from_module(module_or_path).values()
|
dct for dct in all_from_module(module_or_path).values() if isinstance(dct, dict)
|
||||||
if isinstance(dct, dict)]
|
]
|
||||||
if help_dict_list:
|
if help_dict_list:
|
||||||
loaded_help_dicts.extend(help_dict_list)
|
loaded_help_dicts.extend(help_dict_list)
|
||||||
else:
|
else:
|
||||||
|
|
@ -223,19 +220,23 @@ class FileHelpStorageHandler:
|
||||||
unique_help_entries = {}
|
unique_help_entries = {}
|
||||||
|
|
||||||
for dct in loaded_help_dicts:
|
for dct in loaded_help_dicts:
|
||||||
key = dct.get('key').lower().strip()
|
key = dct.get("key").lower().strip()
|
||||||
category = dct.get('category', _DEFAULT_HELP_CATEGORY).strip()
|
category = dct.get("category", _DEFAULT_HELP_CATEGORY).strip()
|
||||||
aliases = list(dct.get('aliases', []))
|
aliases = list(dct.get("aliases", []))
|
||||||
entrytext = dct.get('text', '')
|
entrytext = dct.get("text", "")
|
||||||
locks = dct.get('locks', '')
|
locks = dct.get("locks", "")
|
||||||
|
|
||||||
if not key and entrytext:
|
if not key and entrytext:
|
||||||
logger.error(f"Cannot load file-help-entry (missing key or text): {dct}")
|
logger.error(f"Cannot load file-help-entry (missing key or text): {dct}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
unique_help_entries[key] = FileHelpEntry(
|
unique_help_entries[key] = FileHelpEntry(
|
||||||
key=key, help_category=category, aliases=aliases, lock_storage=locks,
|
key=key,
|
||||||
entrytext=entrytext)
|
help_category=category,
|
||||||
|
aliases=aliases,
|
||||||
|
lock_storage=locks,
|
||||||
|
entrytext=entrytext,
|
||||||
|
)
|
||||||
|
|
||||||
self.help_entries_dict = unique_help_entries
|
self.help_entries_dict = unique_help_entries
|
||||||
self.help_entries = list(unique_help_entries.values())
|
self.help_entries = list(unique_help_entries.values())
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ class TestParseSubtopics(TestCase):
|
||||||
"""
|
"""
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
|
|
||||||
entry = dedent("""
|
entry = dedent(
|
||||||
|
"""
|
||||||
Main topic text
|
Main topic text
|
||||||
# subtopics
|
# subtopics
|
||||||
## foo
|
## foo
|
||||||
|
|
@ -36,7 +37,9 @@ class TestParseSubtopics(TestCase):
|
||||||
Bar subcategory
|
Bar subcategory
|
||||||
### moo
|
### moo
|
||||||
Bar/Moo subcategory
|
Bar/Moo subcategory
|
||||||
""", indent=0)
|
""",
|
||||||
|
indent=0,
|
||||||
|
)
|
||||||
expected = {
|
expected = {
|
||||||
None: "Main topic text",
|
None: "Main topic text",
|
||||||
"foo": {
|
"foo": {
|
||||||
|
|
@ -45,15 +48,10 @@ class TestParseSubtopics(TestCase):
|
||||||
None: "\nFoo/Moo subsub-category\n",
|
None: "\nFoo/Moo subsub-category\n",
|
||||||
"dum": {
|
"dum": {
|
||||||
None: "\nFoo/Moo/Dum subsubsub-category\n",
|
None: "\nFoo/Moo/Dum subsubsub-category\n",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"bar": {
|
"bar": {None: "\nBar subcategory\n", "moo": {None: "\nBar/Moo subcategory"}},
|
||||||
None: "\nBar subcategory\n",
|
|
||||||
"moo": {
|
|
||||||
None: "\nBar/Moo subcategory"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actual_result = help_utils.parse_entry_for_subcategories(entry)
|
actual_result = help_utils.parse_entry_for_subcategories(entry)
|
||||||
|
|
@ -65,28 +63,30 @@ class TestParseSubtopics(TestCase):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
entry = dedent("""
|
entry = dedent(
|
||||||
|
"""
|
||||||
Main topic text
|
Main topic text
|
||||||
# SUBTOPICS
|
# SUBTOPICS
|
||||||
## creating extra stuff
|
## creating extra stuff
|
||||||
Help on creating extra stuff.
|
Help on creating extra stuff.
|
||||||
""", indent=0)
|
""",
|
||||||
|
indent=0,
|
||||||
|
)
|
||||||
expected = {
|
expected = {
|
||||||
None: "Main topic text",
|
None: "Main topic text",
|
||||||
"creating extra stuff": {
|
"creating extra stuff": {None: "\nHelp on creating extra stuff."},
|
||||||
None: "\nHelp on creating extra stuff."
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
actual_result = help_utils.parse_entry_for_subcategories(entry)
|
actual_result = help_utils.parse_entry_for_subcategories(entry)
|
||||||
self.assertEqual(expected, actual_result)
|
self.assertEqual(expected, actual_result)
|
||||||
|
|
||||||
|
|
||||||
# test filehelp system
|
# test filehelp system
|
||||||
|
|
||||||
HELP_ENTRY_DICTS = [
|
HELP_ENTRY_DICTS = [
|
||||||
{
|
{
|
||||||
"key": "evennia",
|
"key": "evennia",
|
||||||
"aliases": ['ev'],
|
"aliases": ["ev"],
|
||||||
"category": "General",
|
"category": "General",
|
||||||
"text": """
|
"text": """
|
||||||
Evennia is a MUD game server in Python.
|
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.
|
There is also a discord channel you can find from the sidebard on evennia.com.
|
||||||
|
|
||||||
"""
|
""",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "building",
|
"key": "building",
|
||||||
|
|
@ -114,12 +114,11 @@ HELP_ENTRY_DICTS = [
|
||||||
Evennia comes with a bunch of default building commands. You can
|
Evennia comes with a bunch of default building commands. You can
|
||||||
find a building tutorial in the evennia documentation.
|
find a building tutorial in the evennia documentation.
|
||||||
|
|
||||||
"""
|
""",
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestFileHelp(TestCase):
|
class TestFileHelp(TestCase):
|
||||||
"""
|
"""
|
||||||
Test the File-help system
|
Test the File-help system
|
||||||
|
|
@ -135,7 +134,7 @@ class TestFileHelp(TestCase):
|
||||||
result = storage.all()
|
result = storage.all()
|
||||||
|
|
||||||
for inum, helpentry in enumerate(result):
|
for inum, helpentry in enumerate(result):
|
||||||
self.assertEqual(HELP_ENTRY_DICTS[inum]['key'], helpentry.key)
|
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].get("aliases", []), helpentry.aliases)
|
||||||
self.assertEqual(HELP_ENTRY_DICTS[inum]['category'], helpentry.help_category)
|
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]["text"], helpentry.entrytext)
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,9 @@ _LUNR_EXCEPTION = None
|
||||||
_LUNR_GET_BUILDER = None
|
_LUNR_GET_BUILDER = None
|
||||||
_LUNR_BUILDER_PIPELINE = None
|
_LUNR_BUILDER_PIPELINE = None
|
||||||
|
|
||||||
_RE_HELP_SUBTOPICS_START = re.compile(
|
_RE_HELP_SUBTOPICS_START = re.compile(r"^\s*?#\s*?subtopics\s*?$", re.I + re.M)
|
||||||
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_SPLIT = re.compile(r"^\s*?(\#{2,6}\s*?\w+?[a-z0-9 \-\?!,\.]*?)$", re.M + re.I)
|
||||||
_RE_HELP_SUBTOPIC_PARSE = re.compile(
|
_RE_HELP_SUBTOPIC_PARSE = re.compile(r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
|
||||||
r"^(?P<nesting>\#{2,6})\s*?(?P<name>.*?)$", re.I + re.M)
|
|
||||||
|
|
||||||
MAX_SUBTOPIC_NESTING = 5
|
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 get_default_builder as _LUNR_GET_BUILDER
|
||||||
from lunr import stop_word_filter
|
from lunr import stop_word_filter
|
||||||
from lunr.stemmer import stemmer
|
from lunr.stemmer import stemmer
|
||||||
|
|
||||||
# from lunr.trimmer import trimmer
|
# from lunr.trimmer import trimmer
|
||||||
|
|
||||||
# pre-create a lunr index-builder pipeline where we've removed some of
|
# 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.reset()
|
||||||
builder.pipeline.add(*_LUNR_BUILDER_PIPELINE)
|
builder.pipeline.add(*_LUNR_BUILDER_PIPELINE)
|
||||||
|
|
||||||
search_index = _LUNR(
|
search_index = _LUNR(ref="key", fields=fields, documents=indx, builder=builder)
|
||||||
ref="key",
|
|
||||||
fields=fields,
|
|
||||||
documents=indx,
|
|
||||||
builder=builder
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
matches = search_index.search(query)[:suggestion_maxnum]
|
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)
|
topic, *subtopics = _RE_HELP_SUBTOPICS_START.split(entry, maxsplit=1)
|
||||||
structure = {None: topic.strip('\n')}
|
structure = {None: topic.strip("\n")}
|
||||||
|
|
||||||
if subtopics:
|
if subtopics:
|
||||||
subtopics = subtopics[0]
|
subtopics = subtopics[0]
|
||||||
|
|
@ -193,12 +187,13 @@ def parse_entry_for_subcategories(entry):
|
||||||
if subtopic_match:
|
if subtopic_match:
|
||||||
# a new sub(-sub..) category starts.
|
# a new sub(-sub..) category starts.
|
||||||
mdict = subtopic_match.groupdict()
|
mdict = subtopic_match.groupdict()
|
||||||
subtopic = mdict['name'].lower().strip()
|
subtopic = mdict["name"].lower().strip()
|
||||||
new_nesting = len(mdict['nesting']) - 1
|
new_nesting = len(mdict["nesting"]) - 1
|
||||||
|
|
||||||
if new_nesting > MAX_SUBTOPIC_NESTING:
|
if new_nesting > MAX_SUBTOPIC_NESTING:
|
||||||
raise RuntimeError(
|
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
|
nestdiff = new_nesting - current_nesting
|
||||||
if nestdiff < 0:
|
if nestdiff < 0:
|
||||||
|
|
@ -226,7 +221,5 @@ def parse_entry_for_subcategories(entry):
|
||||||
if key in dct:
|
if key in dct:
|
||||||
dct = dct[key]
|
dct = dct[key]
|
||||||
else:
|
else:
|
||||||
dct[key] = {
|
dct[key] = {None: part}
|
||||||
None: part
|
|
||||||
}
|
|
||||||
return structure
|
return structure
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ def true(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def all(*args, **kwargs):
|
def all(*args, **kwargs):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -235,8 +235,11 @@ class LockHandler:
|
||||||
funcname, rest = (part.strip().strip(")") for part in funcstring.split("(", 1))
|
funcname, rest = (part.strip().strip(")") for part in funcstring.split("(", 1))
|
||||||
func = _LOCKFUNCS.get(funcname, None)
|
func = _LOCKFUNCS.get(funcname, None)
|
||||||
if not callable(func):
|
if not callable(func):
|
||||||
elist.append(_("Lock: lock-function '{lockfunc}' is not available.").format(
|
elist.append(
|
||||||
lockfunc=funcstring))
|
_("Lock: lock-function '{lockfunc}' is not available.").format(
|
||||||
|
lockfunc=funcstring
|
||||||
|
)
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
args = list(arg.strip() for arg in rest.split(",") if arg and "=" not in arg)
|
args = list(arg.strip() for arg in rest.split(",") if arg and "=" not in arg)
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
|
|
@ -263,13 +266,16 @@ class LockHandler:
|
||||||
continue
|
continue
|
||||||
if access_type in locks:
|
if access_type in locks:
|
||||||
duplicates += 1
|
duplicates += 1
|
||||||
wlist.append(_(
|
wlist.append(
|
||||||
"LockHandler on {obj}: access type '{access_type}' "
|
_(
|
||||||
"changed from '{source}' to '{goal}' ".format(
|
"LockHandler on {obj}: access type '{access_type}' "
|
||||||
obj=self.obj,
|
"changed from '{source}' to '{goal}' ".format(
|
||||||
access_type=access_type,
|
obj=self.obj,
|
||||||
source=locks[access_type][2],
|
access_type=access_type,
|
||||||
goal=raw_lockstring))
|
source=locks[access_type][2],
|
||||||
|
goal=raw_lockstring,
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
||||||
if wlist and WARNING_LOG:
|
if wlist and WARNING_LOG:
|
||||||
|
|
@ -695,6 +701,7 @@ def check_lockstring(
|
||||||
access_type=access_type,
|
access_type=access_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_perm(obj, permission, no_superuser_bypass=False):
|
def check_perm(obj, permission, no_superuser_bypass=False):
|
||||||
"""
|
"""
|
||||||
Shortcut for checking if an object has the given `permission`. If the
|
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
|
from evennia.locks.lockfuncs import perm
|
||||||
|
|
||||||
if not no_superuser_bypass and obj.is_superuser:
|
if not no_superuser_bypass and obj.is_superuser:
|
||||||
return True
|
return True
|
||||||
return perm(obj, None, permission)
|
return perm(obj, None, permission)
|
||||||
|
|
|
||||||
|
|
@ -215,12 +215,11 @@ class TestPermissionCheck(BaseEvenniaTest):
|
||||||
Test the PermissionHandler.check method
|
Test the PermissionHandler.check method
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_check__success(self):
|
def test_check__success(self):
|
||||||
"""Test combinations that should pass the check"""
|
"""Test combinations that should pass the check"""
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[perm for perm in self.char1.account.permissions.all()],
|
[perm for perm in self.char1.account.permissions.all()], ["developer", "player"]
|
||||||
['developer', 'player']
|
|
||||||
|
|
||||||
)
|
)
|
||||||
self.assertTrue(self.char1.permissions.check("Builder"))
|
self.assertTrue(self.char1.permissions.check("Builder"))
|
||||||
self.assertTrue(self.char1.permissions.check("Builder", "Player"))
|
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("Builder", "dummy", require_all=True))
|
||||||
self.assertFalse(self.char1.permissions.check("Developer", "foobar", 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.char1.account.permissions.add("Builder")
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
[perm for perm in self.char1.account.permissions.all()],
|
[perm for perm in self.char1.account.permissions.all()], ["builder", "player"]
|
||||||
['builder', 'player']
|
|
||||||
)
|
)
|
||||||
self.assertFalse(self.char1.permissions.check("Developer"))
|
self.assertFalse(self.char1.permissions.check("Developer"))
|
||||||
self.assertFalse(self.char1.permissions.check("Developer", "Player", require_all=True))
|
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()
|
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
|
||||||
try:
|
try:
|
||||||
return self.filter(
|
return self.filter(cand_restriction & type_restriction & Q(**querykwargs)).order_by(
|
||||||
cand_restriction & type_restriction & Q(**querykwargs)).order_by("id")
|
"id"
|
||||||
|
)
|
||||||
except exceptions.FieldError:
|
except exceptions.FieldError:
|
||||||
return self.none()
|
return self.none()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
|
@ -335,8 +336,9 @@ class ObjectDBManager(TypedObjectManager):
|
||||||
index_matches = string_partial_matching(key_strings, ostring, ret_index=True)
|
index_matches = string_partial_matching(key_strings, ostring, ret_index=True)
|
||||||
if index_matches:
|
if index_matches:
|
||||||
# a match by key
|
# a match by key
|
||||||
match_ids = [obj.id for ind, obj in enumerate(search_candidates)
|
match_ids = [
|
||||||
if ind in index_matches]
|
obj.id for ind, obj in enumerate(search_candidates) if ind in index_matches
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
# match by alias rather than by key
|
# match by alias rather than by key
|
||||||
search_candidates = search_candidates.filter(
|
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.attributes import ModelAttributeBackend, NickHandler
|
||||||
from evennia.typeclasses.models import TypeclassBase
|
from evennia.typeclasses.models import TypeclassBase
|
||||||
from evennia.utils import ansi, create, funcparser, logger, search
|
from evennia.utils import ansi, create, funcparser, logger, search
|
||||||
from evennia.utils.utils import (class_from_module, is_iter, lazy_property,
|
from evennia.utils.utils import (
|
||||||
list_to_string, make_iter, to_str,
|
class_from_module,
|
||||||
variable_from_module)
|
is_iter,
|
||||||
|
lazy_property,
|
||||||
|
list_to_string,
|
||||||
|
make_iter,
|
||||||
|
to_str,
|
||||||
|
variable_from_module,
|
||||||
|
)
|
||||||
|
|
||||||
_INFLECT = inflect.engine()
|
_INFLECT = inflect.engine()
|
||||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||||
|
|
@ -212,13 +218,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
objects = ObjectManager()
|
objects = ObjectManager()
|
||||||
|
|
||||||
# populated by `return_apperance`
|
# populated by `return_apperance`
|
||||||
appearance_template = '''
|
appearance_template = """
|
||||||
{header}
|
{header}
|
||||||
|c{name}|n
|
|c{name}|n
|
||||||
{desc}
|
{desc}
|
||||||
{exits}{characters}{things}
|
{exits}{characters}{things}
|
||||||
{footer}
|
{footer}
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# on-object properties
|
# on-object properties
|
||||||
|
|
||||||
|
|
@ -532,12 +538,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
# we re-run exact match agains one of the matches to
|
# we re-run exact match agains one of the matches to
|
||||||
# make sure we were not catching partial matches not belonging
|
# make sure we were not catching partial matches not belonging
|
||||||
# to the stack
|
# to the stack
|
||||||
nstack = len(ObjectDB.objects.get_objs_with_key_or_alias(
|
nstack = len(
|
||||||
results[0].key,
|
ObjectDB.objects.get_objs_with_key_or_alias(
|
||||||
exact=True,
|
results[0].key,
|
||||||
candidates=list(results),
|
exact=True,
|
||||||
typeclasses=[typeclass] if typeclass else None
|
candidates=list(results),
|
||||||
))
|
typeclasses=[typeclass] if typeclass else None,
|
||||||
|
)
|
||||||
|
)
|
||||||
if nstack == nresults:
|
if nstack == nresults:
|
||||||
# a valid stack, return multiple results
|
# a valid stack, return multiple results
|
||||||
return list(results)[:stacked]
|
return list(results)[:stacked]
|
||||||
|
|
@ -630,9 +638,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
raw_string = self.nicks.nickreplace(
|
raw_string = self.nicks.nickreplace(
|
||||||
raw_string, categories=("inputline", "channel"), include_account=True
|
raw_string, categories=("inputline", "channel"), include_account=True
|
||||||
)
|
)
|
||||||
return _CMDHANDLER(
|
return _CMDHANDLER(self, raw_string, callertype="object", session=session, **kwargs)
|
||||||
self, raw_string, callertype="object", session=session, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def msg(self, text=None, from_obj=None, session=None, options=None, **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 {}
|
mapping = mapping or {}
|
||||||
you = from_obj or self
|
you = from_obj or self
|
||||||
|
|
||||||
if 'you' not in mapping:
|
if "you" not in mapping:
|
||||||
mapping[you] = you
|
mapping[you] = you
|
||||||
|
|
||||||
contents = self.contents
|
contents = self.contents
|
||||||
|
|
@ -802,14 +808,23 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
# actor-stance replacements
|
# actor-stance replacements
|
||||||
inmessage = _MSG_CONTENTS_PARSER.parse(
|
inmessage = _MSG_CONTENTS_PARSER.parse(
|
||||||
inmessage, raise_errors=True, return_string=True,
|
inmessage,
|
||||||
caller=you, receiver=receiver, mapping=mapping)
|
raise_errors=True,
|
||||||
|
return_string=True,
|
||||||
|
caller=you,
|
||||||
|
receiver=receiver,
|
||||||
|
mapping=mapping,
|
||||||
|
)
|
||||||
|
|
||||||
# director-stance replacements
|
# director-stance replacements
|
||||||
outmessage = inmessage.format(
|
outmessage = inmessage.format(
|
||||||
**{key: obj.get_display_name(looker=receiver)
|
**{
|
||||||
if hasattr(obj, "get_display_name") else str(obj)
|
key: obj.get_display_name(looker=receiver)
|
||||||
for key, obj in mapping.items()})
|
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)
|
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)`
|
7. `self.at_post_move(source_location)`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def logerr(string="", err=None):
|
def logerr(string="", err=None):
|
||||||
"""Simple log helper method"""
|
"""Simple log helper method"""
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
|
|
@ -989,8 +1005,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
if not home:
|
if not home:
|
||||||
obj.location = None
|
obj.location = None
|
||||||
obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin."))
|
obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin."))
|
||||||
logger.log_err("Missing default home - '{name}(#{dbid})' now "
|
logger.log_err(
|
||||||
"has a null location.".format(name=obj.name, dbid=obj.dbid))
|
"Missing default home - '{name}(#{dbid})' now "
|
||||||
|
"has a null location.".format(name=obj.name, dbid=obj.dbid)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj.has_account:
|
if obj.has_account:
|
||||||
|
|
@ -1550,7 +1568,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
# This was created from nowhere and added to an account's
|
# This was created from nowhere and added to an account's
|
||||||
# inventory; it's probably the result of a create command.
|
# inventory; it's probably the result of a create command.
|
||||||
string = _("You now have {name} in your possession.").format(
|
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)
|
self.location.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -1754,13 +1773,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
lists are the actual objects.
|
lists are the actual objects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filter_visible(obj_list):
|
def filter_visible(obj_list):
|
||||||
return [obj for obj in obj_list if obj != looker and obj.access(looker, "view")]
|
return [obj for obj in obj_list if obj != looker and obj.access(looker, "view")]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"exits": filter_visible(self.contents_get(content_type="exit")),
|
"exits": filter_visible(self.contents_get(content_type="exit")),
|
||||||
"characters": filter_visible(self.contents_get(content_type="character")),
|
"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):
|
def get_content_names(self, looker, **kwargs):
|
||||||
|
|
@ -1789,13 +1809,14 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
# a mapping {'exits': [...], 'characters': [...], 'things': [...]}
|
# a mapping {'exits': [...], 'characters': [...], 'things': [...]}
|
||||||
contents_map = self.get_visible_contents(looker, **kwargs)
|
contents_map = self.get_visible_contents(looker, **kwargs)
|
||||||
|
|
||||||
character_names = [char.get_display_name(looker, **kwargs)
|
character_names = [
|
||||||
for char in contents_map['characters']]
|
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']]
|
]
|
||||||
|
exit_names = [exi.get_display_name(looker, **kwargs) for exi in contents_map["exits"]]
|
||||||
|
|
||||||
# group all same-named things under one name
|
# group all same-named things under one name
|
||||||
things = defaultdict(list)
|
things = defaultdict(list)
|
||||||
for thing in contents_map['things']:
|
for thing in contents_map["things"]:
|
||||||
things[thing.get_display_name(looker, **kwargs)].append(thing)
|
things[thing.get_display_name(looker, **kwargs)].append(thing)
|
||||||
|
|
||||||
# pluralize same-named things
|
# pluralize same-named things
|
||||||
|
|
@ -1806,11 +1827,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
singular, plural = thing.get_numbered_name(nthings, looker, key=thingname)
|
singular, plural = thing.get_numbered_name(nthings, looker, key=thingname)
|
||||||
thing_names.append(singular if nthings == 1 else plural)
|
thing_names.append(singular if nthings == 1 else plural)
|
||||||
|
|
||||||
return {
|
return {"exits": exit_names, "characters": character_names, "things": thing_names}
|
||||||
"exits": exit_names,
|
|
||||||
"characters": character_names,
|
|
||||||
"things": thing_names
|
|
||||||
}
|
|
||||||
|
|
||||||
def return_appearance(self, looker, **kwargs):
|
def return_appearance(self, looker, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -1840,7 +1857,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not looker:
|
if not looker:
|
||||||
return ''
|
return ""
|
||||||
|
|
||||||
# ourselves
|
# ourselves
|
||||||
name = self.get_display_name(looker, **kwargs)
|
name = self.get_display_name(looker, **kwargs)
|
||||||
|
|
@ -1848,20 +1865,20 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
# contents
|
# contents
|
||||||
content_names_map = self.get_content_names(looker, **kwargs)
|
content_names_map = self.get_content_names(looker, **kwargs)
|
||||||
exits = list_to_string(content_names_map['exits'])
|
exits = list_to_string(content_names_map["exits"])
|
||||||
characters = list_to_string(content_names_map['characters'])
|
characters = list_to_string(content_names_map["characters"])
|
||||||
things = list_to_string(content_names_map['things'])
|
things = list_to_string(content_names_map["things"])
|
||||||
|
|
||||||
# populate the appearance_template string. It's a good idea to strip it and
|
# populate the appearance_template string. It's a good idea to strip it and
|
||||||
# let the client add any extra spaces instead.
|
# let the client add any extra spaces instead.
|
||||||
return self.appearance_template.format(
|
return self.appearance_template.format(
|
||||||
header='',
|
header="",
|
||||||
name=name,
|
name=name,
|
||||||
desc=desc,
|
desc=desc,
|
||||||
exits=f"|wExits:|n {exits}" if exits else '',
|
exits=f"|wExits:|n {exits}" if exits else "",
|
||||||
characters=f"\n|wCharacters:|n {characters}" if characters else '',
|
characters=f"\n|wCharacters:|n {characters}" if characters else "",
|
||||||
things=f"\n|wYou see:|n {things}" if things else '',
|
things=f"\n|wYou see:|n {things}" if things else "",
|
||||||
footer=''
|
footer="",
|
||||||
).strip()
|
).strip()
|
||||||
|
|
||||||
def at_look(self, target, **kwargs):
|
def at_look(self, target, **kwargs):
|
||||||
|
|
@ -2124,7 +2141,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
msg_type = "whisper"
|
msg_type = "whisper"
|
||||||
msg_self = (
|
msg_self = (
|
||||||
'{self} whisper to {all_receivers}, "|n{speech}|n"'
|
'{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_receivers = msg_receivers or '{object} whispers: "|n{speech}|n"'
|
||||||
msg_location = None
|
msg_location = None
|
||||||
|
|
@ -2332,7 +2350,7 @@ class DefaultCharacter(DefaultObject):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_name(cls, name):
|
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:
|
Args:
|
||||||
name (str) : The name of the character
|
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.
|
self.db.prelogout_location = self.location # save location again to be sure.
|
||||||
else:
|
else:
|
||||||
account.msg(
|
account.msg(
|
||||||
_("|r{obj} has no location and no home is set.|n").format(obj=self),
|
_("|r{obj} has no location and no home is set.|n").format(obj=self), session=session
|
||||||
session=session
|
|
||||||
) # Note to set home.
|
) # Note to set home.
|
||||||
|
|
||||||
def at_post_puppet(self, **kwargs):
|
def at_post_puppet(self, **kwargs):
|
||||||
|
|
@ -2414,8 +2431,10 @@ class DefaultCharacter(DefaultObject):
|
||||||
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
||||||
|
|
||||||
def message(obj, from_obj):
|
def message(obj, from_obj):
|
||||||
obj.msg(_("{name} has entered the game.").format(name=self.get_display_name(obj)),
|
obj.msg(
|
||||||
from_obj=from_obj)
|
_("{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)
|
self.location.for_contents(message, exclude=[self], from_obj=self)
|
||||||
|
|
||||||
|
|
@ -2438,8 +2457,10 @@ class DefaultCharacter(DefaultObject):
|
||||||
if self.location:
|
if self.location:
|
||||||
|
|
||||||
def message(obj, from_obj):
|
def message(obj, from_obj):
|
||||||
obj.msg(_("{name} has left the game.").format(name=self.get_display_name(obj)),
|
obj.msg(
|
||||||
from_obj=from_obj)
|
_("{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.location.for_contents(message, exclude=[self], from_obj=self)
|
||||||
self.db.prelogout_location = self.location
|
self.db.prelogout_location = self.location
|
||||||
|
|
@ -2582,6 +2603,7 @@ class DefaultRoom(DefaultObject):
|
||||||
# Default Exit command, used by the base exit object
|
# Default Exit command, used by the base exit object
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class ExitCommand(_COMMAND_DEFAULT_CLASS):
|
class ExitCommand(_COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
This is a command that simply cause the caller to traverse
|
This is a command that simply cause the caller to traverse
|
||||||
|
|
|
||||||
|
|
@ -202,18 +202,19 @@ class TestContentHandler(BaseEvenniaTest):
|
||||||
|
|
||||||
def test_contents_order(self):
|
def test_contents_order(self):
|
||||||
"""Move object from room to room in various ways"""
|
"""Move object from room to room in various ways"""
|
||||||
self.assertEqual(self.room1.contents, [
|
self.assertEqual(
|
||||||
self.exit, self.obj1, self.obj2, self.char1, self.char2])
|
self.room1.contents, [self.exit, self.obj1, self.obj2, self.char1, self.char2]
|
||||||
|
)
|
||||||
self.assertEqual(self.room2.contents, [])
|
self.assertEqual(self.room2.contents, [])
|
||||||
|
|
||||||
# use move_to hook to move obj1
|
# use move_to hook to move obj1
|
||||||
self.obj1.move_to(self.room2)
|
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])
|
self.assertEqual(self.room2.contents, [self.obj1])
|
||||||
|
|
||||||
# move obj2
|
# move obj2
|
||||||
self.obj2.move_to(self.room2)
|
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])
|
self.assertEqual(self.room2.contents, [self.obj1, self.obj2])
|
||||||
|
|
||||||
# move back and forth - it should
|
# move back and forth - it should
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,8 @@ def _validate_prototype(prototype):
|
||||||
def _format_protfuncs():
|
def _format_protfuncs():
|
||||||
out = []
|
out = []
|
||||||
sorted_funcs = [
|
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:
|
for protfunc_name, protfunc in sorted_funcs:
|
||||||
out.append(
|
out.append(
|
||||||
|
|
@ -2113,8 +2114,9 @@ def _apply_diff(caller, **kwargs):
|
||||||
objects = kwargs["objects"]
|
objects = kwargs["objects"]
|
||||||
back_node = kwargs["back_node"]
|
back_node = kwargs["back_node"]
|
||||||
diff = kwargs.get("diff", None)
|
diff = kwargs.get("diff", None)
|
||||||
num_changed = spawner.batch_update_objects_with_prototype(prototype, diff=diff, objects=objects,
|
num_changed = spawner.batch_update_objects_with_prototype(
|
||||||
caller=caller)
|
prototype, diff=diff, objects=objects, caller=caller
|
||||||
|
)
|
||||||
caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed))
|
caller.msg("|g{num} objects were updated successfully.|n".format(num=num_changed))
|
||||||
return back_node
|
return back_node
|
||||||
|
|
||||||
|
|
@ -2354,7 +2356,7 @@ def node_apply_diff(caller, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def node_prototype_save(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
|
# these are only set if we selected 'yes' to save on a previous pass
|
||||||
prototype = kwargs.get("prototype", None)
|
prototype = kwargs.get("prototype", None)
|
||||||
# set to True/False if answered, None if first pass
|
# set to True/False if answered, None if first pass
|
||||||
|
|
|
||||||
|
|
@ -50,16 +50,20 @@ def protfunc_callable_protkey(*args, **kwargs):
|
||||||
prot_value = prototype[fieldname]
|
prot_value = prototype[fieldname]
|
||||||
else:
|
else:
|
||||||
# check if it's an attribute
|
# check if it's an attribute
|
||||||
for attrtuple in prototype.get('attrs', []):
|
for attrtuple in prototype.get("attrs", []):
|
||||||
if attrtuple[0] == fieldname:
|
if attrtuple[0] == fieldname:
|
||||||
prot_value = attrtuple[1]
|
prot_value = attrtuple[1]
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise AttributeError(f"{fieldname} not found in prototype\n{prototype}\n"
|
raise AttributeError(
|
||||||
"(neither as prototype-field or as an Attribute")
|
f"{fieldname} not found in prototype\n{prototype}\n"
|
||||||
|
"(neither as prototype-field or as an Attribute"
|
||||||
|
)
|
||||||
if callable(prot_value):
|
if callable(prot_value):
|
||||||
raise RuntimeError(f"Error in prototype\n{prototype}\n$protkey can only reference static "
|
raise RuntimeError(
|
||||||
f"values/attributes (found {prot_value})")
|
f"Error in prototype\n{prototype}\n$protkey can only reference static "
|
||||||
|
f"values/attributes (found {prot_value})"
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
return funcparser.funcparser_callable_eval(prot_value, **kwargs)
|
return funcparser.funcparser_callable_eval(prot_value, **kwargs)
|
||||||
except funcparser.ParsingError:
|
except funcparser.ParsingError:
|
||||||
|
|
|
||||||
|
|
@ -131,8 +131,9 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
# attrs must be on form [(key, value, category, lockstr)]
|
# attrs must be on form [(key, value, category, lockstr)]
|
||||||
if not is_iter(attr):
|
if not is_iter(attr):
|
||||||
logger.log_error("Prototype's 'attr' field must "
|
logger.log_error(
|
||||||
f"be a list of tuples: {prototype}")
|
"Prototype's 'attr' field must " f"be a list of tuples: {prototype}"
|
||||||
|
)
|
||||||
elif attr:
|
elif attr:
|
||||||
nattr = len(attr)
|
nattr = len(attr)
|
||||||
if nattr == 1:
|
if nattr == 1:
|
||||||
|
|
@ -147,14 +148,15 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
|
|
||||||
elif key == "prototype_parent":
|
elif key == "prototype_parent":
|
||||||
# homogenize any prototype-parents embedded directly as dicts
|
# homogenize any prototype-parents embedded directly as dicts
|
||||||
protparents = prototype.get('prototype_parent', [])
|
protparents = prototype.get("prototype_parent", [])
|
||||||
if isinstance(protparents, dict):
|
if isinstance(protparents, dict):
|
||||||
protparents = [protparents]
|
protparents = [protparents]
|
||||||
for parent in make_iter(protparents):
|
for parent in make_iter(protparents):
|
||||||
if isinstance(parent, dict):
|
if isinstance(parent, dict):
|
||||||
# recursively homogenize directly embedded prototype parents
|
# recursively homogenize directly embedded prototype parents
|
||||||
homogenized_parents.append(
|
homogenized_parents.append(
|
||||||
homogenize_prototype(parent, custom_keys=custom_keys))
|
homogenize_prototype(parent, custom_keys=custom_keys)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# normal prototype-parent names are added as-is
|
# normal prototype-parent names are added as-is
|
||||||
homogenized_parents.append(parent)
|
homogenized_parents.append(parent)
|
||||||
|
|
@ -170,7 +172,7 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
if homogenized_tags:
|
if homogenized_tags:
|
||||||
homogenized["tags"] = homogenized_tags
|
homogenized["tags"] = homogenized_tags
|
||||||
if homogenized_parents:
|
if homogenized_parents:
|
||||||
homogenized['prototype_parent'] = homogenized_parents
|
homogenized["prototype_parent"] = homogenized_parents
|
||||||
|
|
||||||
# add required missing parts that had defaults before
|
# add required missing parts that had defaults before
|
||||||
|
|
||||||
|
|
@ -190,6 +192,7 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
|
|
||||||
# module/dict-based prototypes
|
# module/dict-based prototypes
|
||||||
|
|
||||||
|
|
||||||
def load_module_prototypes(*mod_or_prototypes, override=True):
|
def load_module_prototypes(*mod_or_prototypes, override=True):
|
||||||
"""
|
"""
|
||||||
Load module prototypes. Also prototype-dicts passed directly to this function are considered
|
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.
|
# prototype dicts that must have 'prototype_key' set.
|
||||||
for prot in prototype_list:
|
for prot in prototype_list:
|
||||||
if not isinstance(prot, dict):
|
if not isinstance(prot, dict):
|
||||||
logger.log_err(f"Prototype read from {mod}.PROTOTYPE_LIST "
|
logger.log_err(
|
||||||
f"is not a dict (skipping): {prot}")
|
f"Prototype read from {mod}.PROTOTYPE_LIST "
|
||||||
|
f"is not a dict (skipping): {prot}"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
elif "prototype_key" not in prot:
|
elif "prototype_key" not in prot:
|
||||||
logger.log_err(f"Prototype read from {mod}.PROTOTYPE_LIST "
|
logger.log_err(
|
||||||
f"is missing the 'prototype_key' (skipping): {prot}")
|
f"Prototype read from {mod}.PROTOTYPE_LIST "
|
||||||
|
f"is missing the 'prototype_key' (skipping): {prot}"
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
prots.append((prot["prototype_key"], homogenize_prototype(prot)))
|
prots.append((prot["prototype_key"], homogenize_prototype(prot)))
|
||||||
else:
|
else:
|
||||||
|
|
@ -270,7 +277,8 @@ def load_module_prototypes(*mod_or_prototypes, override=True):
|
||||||
{
|
{
|
||||||
"prototype_key": actual_prot_key,
|
"prototype_key": actual_prot_key,
|
||||||
"prototype_desc": (
|
"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_locks": (
|
||||||
prototype["prototype_locks"]
|
prototype["prototype_locks"]
|
||||||
if "prototype_locks" in prototype
|
if "prototype_locks" in prototype
|
||||||
|
|
@ -292,9 +300,11 @@ def load_module_prototypes(*mod_or_prototypes, override=True):
|
||||||
|
|
||||||
if isinstance(mod_or_dict, dict):
|
if isinstance(mod_or_dict, dict):
|
||||||
# a single prototype; we must make sure it has its key
|
# 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:
|
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)]
|
prots = [(prototype_key, mod_or_dict)]
|
||||||
mod = None
|
mod = None
|
||||||
else:
|
else:
|
||||||
|
|
@ -307,7 +317,7 @@ def load_module_prototypes(*mod_or_prototypes, override=True):
|
||||||
for prototype_key, prot in prots:
|
for prototype_key, prot in prots:
|
||||||
prototype = _cleanup_prototype(prototype_key, prot, mod=mod)
|
prototype = _cleanup_prototype(prototype_key, prot, mod=mod)
|
||||||
# the key can change since in-proto key is given prio over variable-name-based keys
|
# 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:
|
if actual_prototype_key in _MODULE_PROTOTYPES and not override:
|
||||||
# don't override - useful to still let settings replace dynamic inserts
|
# 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)
|
stored_prototype = DbPrototype.objects.filter(db_key__iexact=prototype_key)
|
||||||
|
|
||||||
if not stored_prototype:
|
if not stored_prototype:
|
||||||
raise PermissionError(_("Prototype {prototype_key} was not found.").format(
|
raise PermissionError(
|
||||||
prototype_key=prototype_key))
|
_("Prototype {prototype_key} was not found.").format(prototype_key=prototype_key)
|
||||||
|
)
|
||||||
|
|
||||||
stored_prototype = stored_prototype[0]
|
stored_prototype = stored_prototype[0]
|
||||||
if caller:
|
if caller:
|
||||||
if not stored_prototype.access(caller, "edit"):
|
if not stored_prototype.access(caller, "edit"):
|
||||||
raise PermissionError(
|
raise PermissionError(
|
||||||
_("{caller} needs explicit 'edit' permissions to "
|
_(
|
||||||
"delete prototype {prototype_key}.").format(
|
"{caller} needs explicit 'edit' permissions to "
|
||||||
caller=caller, prototype_key=prototype_key)
|
"delete prototype {prototype_key}."
|
||||||
|
).format(caller=caller, prototype_key=prototype_key)
|
||||||
)
|
)
|
||||||
stored_prototype.delete()
|
stored_prototype.delete()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def search_prototype(key=None, tags=None, require_single=False, return_iterators=False,
|
def search_prototype(
|
||||||
no_db=False):
|
key=None, tags=None, require_single=False, return_iterators=False, no_db=False
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Find prototypes based on key and/or tags, or all prototypes.
|
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
|
# prototype keys are always in lowecase
|
||||||
if key:
|
if key:
|
||||||
key = key.lower()
|
key = key.lower()
|
||||||
|
|
||||||
# search module prototypes
|
# search module prototypes
|
||||||
|
|
||||||
|
|
@ -587,10 +600,10 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
nmodules = len(module_prototypes)
|
nmodules = len(module_prototypes)
|
||||||
ndbprots = db_matches.count() if db_matches else 0
|
ndbprots = db_matches.count() if db_matches else 0
|
||||||
if nmodules + ndbprots != 1:
|
if nmodules + ndbprots != 1:
|
||||||
raise KeyError(_(
|
raise KeyError(
|
||||||
"Found {num} matching prototypes among {module_prototypes}.").format(
|
_("Found {num} matching prototypes among {module_prototypes}.").format(
|
||||||
num=nmodules + ndbprots,
|
num=nmodules + ndbprots, module_prototypes=module_prototypes
|
||||||
module_prototypes=module_prototypes)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if return_iterators:
|
if return_iterators:
|
||||||
|
|
@ -670,7 +683,7 @@ class PrototypeEvMore(EvMore):
|
||||||
else:
|
else:
|
||||||
# get the correct slice, adjusted for the db-prototypes
|
# get the correct slice, adjusted for the db-prototypes
|
||||||
pageno = max(0, pageno - self._npages_db)
|
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):
|
def page_formatter(self, page):
|
||||||
"""
|
"""
|
||||||
|
|
@ -809,12 +822,15 @@ def validate_prototype(
|
||||||
if is_prototype_base:
|
if is_prototype_base:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
_("Prototype {protkey} requires `typeclass` " "or 'prototype_parent'.").format(
|
_("Prototype {protkey} requires `typeclass` " "or 'prototype_parent'.").format(
|
||||||
protkey=protkey)
|
protkey=protkey
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_flags["warnings"].append(
|
_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:
|
if strict and typeclass:
|
||||||
|
|
@ -822,9 +838,10 @@ def validate_prototype(
|
||||||
class_from_module(typeclass)
|
class_from_module(typeclass)
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
_("{err}: Prototype {protkey} is based on typeclass {typeclass}, "
|
_(
|
||||||
"which could not be imported!").format(
|
"{err}: Prototype {protkey} is based on typeclass {typeclass}, "
|
||||||
err=err, protkey=protkey, typeclass=typeclass)
|
"which could not be imported!"
|
||||||
|
).format(err=err, protkey=protkey, typeclass=typeclass)
|
||||||
)
|
)
|
||||||
|
|
||||||
if prototype_parent and isinstance(prototype_parent, dict):
|
if prototype_parent and isinstance(prototype_parent, dict):
|
||||||
|
|
@ -840,20 +857,24 @@ def validate_prototype(
|
||||||
else:
|
else:
|
||||||
protstring = protstring.lower()
|
protstring = protstring.lower()
|
||||||
if protkey is not None and protstring == protkey:
|
if protkey is not None and protstring == protkey:
|
||||||
_flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format(
|
_flags["errors"].append(
|
||||||
protkey=protkey))
|
_("Prototype {protkey} tries to parent itself.").format(protkey=protkey)
|
||||||
|
)
|
||||||
protparent = protparents.get(protstring)
|
protparent = protparents.get(protstring)
|
||||||
if not protparent:
|
if not protparent:
|
||||||
_flags["errors"].append(
|
_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
|
# check for infinite recursion
|
||||||
if id(prototype) in _flags["visited"]:
|
if id(prototype) in _flags["visited"]:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
_("{protkey} has infinite nesting of prototypes.").format(
|
_("{protkey} has infinite nesting of prototypes.").format(
|
||||||
protkey=protkey or prototype)
|
protkey=protkey or prototype
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if _flags["errors"]:
|
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 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"]:
|
if strict and is_prototype_base and _flags["depth"] <= 0 and not _flags["typeclass"]:
|
||||||
_flags["errors"].append(
|
_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 {protkey} has no `typeclass` defined anywhere in its parent\n "
|
||||||
"prototype with a typeclass.").format(protkey=protkey)
|
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||||
|
"prototype with a typeclass."
|
||||||
|
).format(protkey=protkey)
|
||||||
)
|
)
|
||||||
|
|
||||||
if _flags["depth"] <= 0:
|
if _flags["depth"] <= 0:
|
||||||
|
|
@ -901,8 +924,9 @@ def validate_prototype(
|
||||||
prototype["prototype_locks"] = prototype_locks
|
prototype["prototype_locks"] = prototype_locks
|
||||||
|
|
||||||
|
|
||||||
def protfunc_parser(value, available_functions=None, testing=False, stacktrace=False,
|
def protfunc_parser(
|
||||||
caller=None, **kwargs):
|
value, available_functions=None, testing=False, stacktrace=False, caller=None, **kwargs
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Parse a prototype value string for a protfunc and process it.
|
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)
|
stype = type(value)
|
||||||
if is_iter(value):
|
if is_iter(value):
|
||||||
if stype == dict:
|
if stype == dict:
|
||||||
return {value_to_obj_or_any(key): value_to_obj_or_any(val)
|
return {
|
||||||
for key, val in value.items()}
|
value_to_obj_or_any(key): value_to_obj_or_any(val) for key, val in value.items()
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
return stype([value_to_obj_or_any(val) for val in value])
|
return stype([value_to_obj_or_any(val) for val in value])
|
||||||
return dbid_to_obj(value, ObjectDB)
|
return dbid_to_obj(value, ObjectDB)
|
||||||
|
|
|
||||||
|
|
@ -154,8 +154,13 @@ from evennia.prototypes.prototypes import (
|
||||||
|
|
||||||
|
|
||||||
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
|
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
|
||||||
_PROTOTYPE_META_NAMES = ("prototype_key", "prototype_desc", "prototype_tags",
|
_PROTOTYPE_META_NAMES = (
|
||||||
"prototype_locks", "prototype_parent")
|
"prototype_key",
|
||||||
|
"prototype_desc",
|
||||||
|
"prototype_tags",
|
||||||
|
"prototype_locks",
|
||||||
|
"prototype_parent",
|
||||||
|
)
|
||||||
_PROTOTYPE_ROOT_NAMES = (
|
_PROTOTYPE_ROOT_NAMES = (
|
||||||
"typeclass",
|
"typeclass",
|
||||||
"key",
|
"key",
|
||||||
|
|
@ -235,9 +240,7 @@ def _get_prototype(inprot, protparents, uninherited=None, _workprot=None):
|
||||||
parent_prototype = protparents.get(prototype.lower(), {})
|
parent_prototype = protparents.get(prototype.lower(), {})
|
||||||
|
|
||||||
# Build the prot dictionary in reverse order, overloading
|
# Build the prot dictionary in reverse order, overloading
|
||||||
new_prot = _get_prototype(
|
new_prot = _get_prototype(parent_prototype, protparents, _workprot=_workprot)
|
||||||
parent_prototype, protparents, _workprot=_workprot
|
|
||||||
)
|
|
||||||
|
|
||||||
# attrs, tags have internal structure that should be inherited separately
|
# attrs, tags have internal structure that should be inherited separately
|
||||||
new_prot["attrs"] = _inherit_attrs(
|
new_prot["attrs"] = _inherit_attrs(
|
||||||
|
|
@ -276,8 +279,9 @@ def flatten_prototype(prototype, validate=False, no_db=False):
|
||||||
|
|
||||||
if prototype:
|
if prototype:
|
||||||
prototype = protlib.homogenize_prototype(prototype)
|
prototype = protlib.homogenize_prototype(prototype)
|
||||||
protparents = {prot["prototype_key"].lower(): prot
|
protparents = {
|
||||||
for prot in protlib.search_prototype(no_db=no_db)}
|
prot["prototype_key"].lower(): prot for prot in protlib.search_prototype(no_db=no_db)
|
||||||
|
}
|
||||||
protlib.validate_prototype(
|
protlib.validate_prototype(
|
||||||
prototype, None, protparents, is_prototype_base=validate, strict=validate
|
prototype, None, protparents, is_prototype_base=validate, strict=validate
|
||||||
)
|
)
|
||||||
|
|
@ -342,7 +346,7 @@ def prototype_from_object(obj):
|
||||||
prot["aliases"] = aliases
|
prot["aliases"] = aliases
|
||||||
tags = sorted(
|
tags = sorted(
|
||||||
[(tag.db_key, tag.db_category, tag.db_data) for tag in obj.tags.all(return_objs=True)],
|
[(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:
|
if tags:
|
||||||
prot["tags"] = tags
|
prot["tags"] = tags
|
||||||
|
|
@ -351,7 +355,7 @@ def prototype_from_object(obj):
|
||||||
(attr.key, attr.value, attr.category, ";".join(attr.locks.all()))
|
(attr.key, attr.value, attr.category, ";".join(attr.locks.all()))
|
||||||
for attr in obj.attributes.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:
|
if attrs:
|
||||||
prot["attrs"] = attrs
|
prot["attrs"] = attrs
|
||||||
|
|
@ -489,8 +493,10 @@ def flatten_diff(diff):
|
||||||
out.extend(_get_all_nested_diff_instructions(val))
|
out.extend(_get_all_nested_diff_instructions(val))
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(
|
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
|
return out
|
||||||
|
|
||||||
|
|
@ -627,8 +633,9 @@ def format_diff(diff, minimal=True):
|
||||||
return "\n ".join(line for line in texts if line)
|
return "\n ".join(line for line in texts if line)
|
||||||
|
|
||||||
|
|
||||||
def batch_update_objects_with_prototype(prototype, diff=None, objects=None,
|
def batch_update_objects_with_prototype(
|
||||||
exact=False, caller=None):
|
prototype, diff=None, objects=None, exact=False, caller=None
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Update existing objects with the latest version of the prototype.
|
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)
|
val = prot.pop("location", None)
|
||||||
create_kwargs["db_location"] = init_spawn_value(
|
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)
|
val = prot.pop("home", None)
|
||||||
if val:
|
if val:
|
||||||
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, caller=caller,
|
create_kwargs["db_home"] = init_spawn_value(
|
||||||
prototype=prototype)
|
val, value_to_obj, caller=caller, prototype=prototype
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
create_kwargs["db_home"] = init_spawn_value(
|
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:
|
except ObjectDB.DoesNotExist:
|
||||||
# settings.DEFAULT_HOME not existing is common for unittests
|
# settings.DEFAULT_HOME not existing is common for unittests
|
||||||
pass
|
pass
|
||||||
|
|
||||||
val = prot.pop("destination", None)
|
val = prot.pop("destination", None)
|
||||||
create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj, caller=caller,
|
create_kwargs["db_destination"] = init_spawn_value(
|
||||||
prototype=prototype)
|
val, value_to_obj, caller=caller, prototype=prototype
|
||||||
|
)
|
||||||
|
|
||||||
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
||||||
create_kwargs["db_typeclass_path"] = init_spawn_value(val, str, caller=caller,
|
create_kwargs["db_typeclass_path"] = init_spawn_value(
|
||||||
prototype=prototype)
|
val, str, caller=caller, prototype=prototype
|
||||||
|
)
|
||||||
|
|
||||||
# extract calls to handlers
|
# extract calls to handlers
|
||||||
val = prot.pop("permissions", [])
|
val = prot.pop("permissions", [])
|
||||||
|
|
@ -974,8 +986,13 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
val = prot.pop("tags", [])
|
val = prot.pop("tags", [])
|
||||||
tags = []
|
tags = []
|
||||||
for (tag, category, *data) in val:
|
for (tag, category, *data) in val:
|
||||||
tags.append((init_spawn_value(tag, str, caller=caller, prototype=prototype),
|
tags.append(
|
||||||
category, data[0] if data else None))
|
(
|
||||||
|
init_spawn_value(tag, str, caller=caller, prototype=prototype),
|
||||||
|
category,
|
||||||
|
data[0] if data else None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
prototype_key = prototype.get("prototype_key", None)
|
prototype_key = prototype.get("prototype_key", None)
|
||||||
if prototype_key:
|
if prototype_key:
|
||||||
|
|
@ -987,8 +1004,10 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
|
|
||||||
# extract ndb assignments
|
# extract ndb assignments
|
||||||
nattributes = dict(
|
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()
|
for key, val in prot.items()
|
||||||
if key.startswith("ndb_")
|
if key.startswith("ndb_")
|
||||||
)
|
)
|
||||||
|
|
@ -998,8 +1017,13 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
attributes = []
|
attributes = []
|
||||||
for (attrname, value, *rest) in val:
|
for (attrname, value, *rest) in val:
|
||||||
attributes.append(
|
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 = []
|
simple_attributes = []
|
||||||
for key, value in (
|
for key, value in (
|
||||||
|
|
@ -1010,8 +1034,14 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
simple_attributes.append(
|
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
|
attributes = attributes + simple_attributes
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ _PROTPARENTS = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestSpawner(BaseEvenniaTest):
|
class TestSpawner(BaseEvenniaTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
@ -340,7 +341,6 @@ class TestProtLib(BaseEvenniaTest):
|
||||||
|
|
||||||
|
|
||||||
class TestProtFuncs(BaseEvenniaTest):
|
class TestProtFuncs(BaseEvenniaTest):
|
||||||
|
|
||||||
@override_settings(PROT_FUNC_MODULES=["evennia.prototypes.protfuncs"])
|
@override_settings(PROT_FUNC_MODULES=["evennia.prototypes.protfuncs"])
|
||||||
def test_protkey_protfunc(self):
|
def test_protkey_protfunc(self):
|
||||||
test_prot = {"key1": "value1", "key2": 2}
|
test_prot = {"key1": "value1", "key2": 2}
|
||||||
|
|
@ -350,8 +350,7 @@ class TestProtFuncs(BaseEvenniaTest):
|
||||||
"value1",
|
"value1",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
protlib.protfunc_parser("$protkey(key2)", testing=True, prototype=test_prot),
|
protlib.protfunc_parser("$protkey(key2)", testing=True, prototype=test_prot), 2
|
||||||
2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -908,6 +907,7 @@ class Test2474(BaseEvenniaTest):
|
||||||
that of its prototype_parent.
|
that of its prototype_parent.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
prototypes = {
|
prototypes = {
|
||||||
"WEAPON": {
|
"WEAPON": {
|
||||||
"typeclass": "evennia.objects.objects.DefaultObject",
|
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||||
|
|
@ -951,14 +951,14 @@ class TestPartialTagAttributes(BaseEvenniaTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.prot = {
|
self.prot = {
|
||||||
'prototype_key': 'rock',
|
"prototype_key": "rock",
|
||||||
'typeclass': 'evennia.objects.objects.DefaultObject',
|
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||||
'key': 'a rock',
|
"key": "a rock",
|
||||||
'tags': [('quantity', 'groupable')], # missing data field
|
"tags": [("quantity", "groupable")], # missing data field
|
||||||
'attrs': [('quantity', 1)], # missing category and lock fields
|
"attrs": [("quantity", 1)], # missing category and lock fields
|
||||||
'desc': 'A good way to get stoned.'
|
"desc": "A good way to get stoned.",
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_partial_spawn(self):
|
def test_partial_spawn(self):
|
||||||
obj = spawner.spawn(self.prot)
|
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
|
# if this an Attribute with a category we should differentiate
|
||||||
fieldname = self._attr_category_fieldname(
|
fieldname = self._attr_category_fieldname(
|
||||||
fieldname, obj.db_category
|
fieldname,
|
||||||
if fieldname == "db_value" and hasattr(obj, "db_category") else None
|
obj.db_category if fieldname == "db_value" and hasattr(obj, "db_category") else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
to_delete = []
|
to_delete = []
|
||||||
|
|
@ -124,8 +124,7 @@ class MonitorHandler(object):
|
||||||
for (obj, fieldname, idstring) in to_delete:
|
for (obj, fieldname, idstring) in to_delete:
|
||||||
del self.monitors[obj][fieldname][idstring]
|
del self.monitors[obj][fieldname][idstring]
|
||||||
|
|
||||||
def add(self, obj, fieldname, callback, idstring="", persistent=False,
|
def add(self, obj, fieldname, callback, idstring="", persistent=False, category=None, **kwargs):
|
||||||
category=None, **kwargs):
|
|
||||||
"""
|
"""
|
||||||
Add monitoring to a given field or Attribute. A field must
|
Add monitoring to a given field or Attribute. A field must
|
||||||
be specified with the full db_* name or it will be assumed
|
be specified with the full db_* name or it will be assumed
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,12 @@ class ScriptHandler(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
next_repeat = "?"
|
next_repeat = "?"
|
||||||
string += _("\n '{key}' ({next_repeat}/{interval}, {repeats} repeats): {desc}").format(
|
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,
|
interval=interval,
|
||||||
repeats=repeats,
|
repeats=repeats,
|
||||||
desc=script.desc)
|
desc=script.desc,
|
||||||
|
)
|
||||||
return string.strip()
|
return string.strip()
|
||||||
|
|
||||||
def add(self, scriptclass, key=None, autostart=True):
|
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.start(20, now=False, start_delay=10, count_start=1)
|
||||||
|
|
||||||
loopcall.__call__.assert_not_called()
|
loopcall.__call__.assert_not_called()
|
||||||
self.assertEqual(loopcall.interval , 20)
|
self.assertEqual(loopcall.interval, 20)
|
||||||
loopcall._scheduleFrom.assert_called_with(121)
|
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.
|
on-demand rather than using a ticker.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Ticker(object):
|
class Ticker(object):
|
||||||
"""
|
"""
|
||||||
Represents a repeatedly running task that calls
|
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