Rename prototype to prototype_parent, fixing olc menu
This commit is contained in:
parent
e601e03884
commit
9360dc71f1
6 changed files with 155 additions and 59 deletions
|
|
@ -13,7 +13,7 @@ from evennia.utils import create, utils, search
|
|||
from evennia.utils.utils import inherits_from, class_from_module, get_all_typeclasses
|
||||
from evennia.utils.eveditor import EvEditor
|
||||
from evennia.utils.evmore import EvMore
|
||||
from evennia.prototypes import spawner, prototypes as protlib
|
||||
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
|
||||
from evennia.utils.ansi import raw
|
||||
|
||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
|
@ -2917,7 +2917,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
|||
elif prototype:
|
||||
# one match
|
||||
prototype = prototype[0]
|
||||
spawner.start_olc(caller, session=self.session, prototype=prototype)
|
||||
olc_menus.start_olc(caller, session=self.session, prototype=prototype)
|
||||
return
|
||||
|
||||
if 'search' in self.switches:
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ from django.conf import settings
|
|||
from evennia.utils.evmenu import EvMenu, list_node
|
||||
from evennia.utils.ansi import strip_ansi
|
||||
from evennia.utils import utils
|
||||
from evennia.utils.prototypes import prototypes as protlib
|
||||
from evennia.utils.prototypes import spawner
|
||||
from evennia.prototypes import prototypes as protlib
|
||||
from evennia.prototypes import spawner
|
||||
|
||||
# ------------------------------------------------------------
|
||||
#
|
||||
|
|
@ -43,12 +43,6 @@ def _is_new_prototype(caller):
|
|||
return hasattr(caller.ndb._menutree, "olc_new")
|
||||
|
||||
|
||||
def _set_menu_prototype(caller, field, value):
|
||||
prototype = _get_menu_prototype(caller)
|
||||
prototype[field] = value
|
||||
caller.ndb._menutree.olc_prototype = prototype
|
||||
|
||||
|
||||
def _format_property(prop, required=False, prototype=None, cropper=None):
|
||||
|
||||
if prototype is not None:
|
||||
|
|
@ -67,6 +61,13 @@ def _format_property(prop, required=False, prototype=None, cropper=None):
|
|||
return " ({}|n)".format(cropper(out) if cropper else utils.crop(out, _MENU_CROP_WIDTH))
|
||||
|
||||
|
||||
def _set_prototype_value(caller, field, value):
|
||||
prototype = _get_menu_prototype(caller)
|
||||
prototype[field] = value
|
||||
caller.ndb._menutree.olc_prototype = prototype
|
||||
return prototype
|
||||
|
||||
|
||||
def _set_property(caller, raw_string, **kwargs):
|
||||
"""
|
||||
Update a property. To be called by the 'goto' option variable.
|
||||
|
|
@ -102,22 +103,26 @@ def _set_property(caller, raw_string, **kwargs):
|
|||
if not value:
|
||||
return next_node
|
||||
|
||||
prototype = _get_menu_prototype(caller)
|
||||
prototype = _set_prototype_value(caller, "prototype_key", value)
|
||||
|
||||
# typeclass and prototype can't co-exist
|
||||
# typeclass and prototype_parent can't co-exist
|
||||
if propname_low == "typeclass":
|
||||
prototype.pop("prototype", None)
|
||||
if propname_low == "prototype":
|
||||
prototype.pop("prototype_parent", None)
|
||||
if propname_low == "prototype_parent":
|
||||
prototype.pop("typeclass", None)
|
||||
|
||||
caller.ndb._menutree.olc_prototype = prototype
|
||||
|
||||
caller.msg("Set {prop} to '{value}'.".format(prop, value=str(value)))
|
||||
caller.msg("Set {prop} to '{value}'.".format(prop=prop, value=str(value)))
|
||||
|
||||
return next_node
|
||||
|
||||
|
||||
def _wizard_options(curr_node, prev_node, next_node, color="|W"):
|
||||
"""
|
||||
Creates default navigation options available in the wizard.
|
||||
|
||||
"""
|
||||
options = []
|
||||
if prev_node:
|
||||
options.append({"key": ("|wb|Wack", "b"),
|
||||
|
|
@ -154,8 +159,8 @@ def node_index(caller):
|
|||
text = ("|c --- Prototype wizard --- |n\n\n"
|
||||
"Define the |yproperties|n of the prototype. All prototype values can be "
|
||||
"over-ridden at the time of spawning an instance of the prototype, but some are "
|
||||
"required.\n\n'|wMeta'-properties|n are not used in the prototype itself but are used "
|
||||
"to organize and list prototypes. The 'Meta-Key' uniquely identifies the prototype "
|
||||
"required.\n\n'|wprototype-'-properties|n are not used in the prototype itself but are used "
|
||||
"to organize and list prototypes. The 'prototype-key' uniquely identifies the prototype "
|
||||
"and allows you to edit an existing prototype or save a new one for use by you or "
|
||||
"others later.\n\n(make choice; q to abort. If unsure, start from 1.)")
|
||||
|
||||
|
|
@ -192,9 +197,12 @@ def node_validate_prototype(caller, raw_string, **kwargs):
|
|||
errors = "\n\n|g No validation errors found.|n (but errors could still happen at spawn-time)"
|
||||
try:
|
||||
# validate, don't spawn
|
||||
spawner.spawn(prototype, return_prototypes=True)
|
||||
spawner.spawn(prototype, only_validate=True)
|
||||
except RuntimeError as err:
|
||||
errors = "\n\n|rError: {}|n".format(err)
|
||||
errors = "\n\n|r{}|n".format(err)
|
||||
except RuntimeWarning as err:
|
||||
errors = "\n\n|y{}|n".format(err)
|
||||
|
||||
text = (txt + errors)
|
||||
|
||||
options = _wizard_options(None, kwargs.get("back"), None)
|
||||
|
|
@ -287,7 +295,9 @@ def node_prototype(caller):
|
|||
|
||||
|
||||
def _all_typeclasses(caller):
|
||||
return list(sorted(utils.get_all_typeclasses().keys()))
|
||||
return list(name for name in
|
||||
sorted(utils.get_all_typeclasses("evennia.objects.models.ObjectDB").keys())
|
||||
if name != "evennia.objects.models.ObjectDB")
|
||||
|
||||
|
||||
def _typeclass_examine(caller, typeclass_path):
|
||||
|
|
@ -403,7 +413,7 @@ def _add_attr(caller, attr_string, **kwargs):
|
|||
if attrname:
|
||||
prot = _get_menu_prototype(caller)
|
||||
prot['attrs'][attrname] = value
|
||||
_set_menu_prototype(caller, "prototype", prot)
|
||||
_set_prototype_value(caller, "prototype", prot)
|
||||
text = "Added"
|
||||
else:
|
||||
text = "Attribute must be given as 'attrname = <value>' where <value> uses valid Python."
|
||||
|
|
@ -468,7 +478,7 @@ def _add_tag(caller, tag, **kwargs):
|
|||
else:
|
||||
tags = [tag]
|
||||
prototype['tags'] = tags
|
||||
_set_menu_prototype(caller, "prototype", prototype)
|
||||
_set_prototype_value(caller, "prototype", prototype)
|
||||
text = kwargs.get("text")
|
||||
if not text:
|
||||
text = "Added tag {}. (return to continue)".format(tag)
|
||||
|
|
@ -485,7 +495,7 @@ def _edit_tag(caller, old_tag, new_tag, **kwargs):
|
|||
new_tag = new_tag.strip().lower()
|
||||
tags[tags.index(old_tag)] = new_tag
|
||||
prototype['tags'] = tags
|
||||
_set_menu_prototype(caller, 'prototype', prototype)
|
||||
_set_prototype_value(caller, 'prototype', prototype)
|
||||
|
||||
text = kwargs.get('text')
|
||||
if not text:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ from evennia.scripts.scripts import DefaultScript
|
|||
from evennia.objects.models import ObjectDB
|
||||
from evennia.utils.create import create_script
|
||||
from evennia.utils.utils import (
|
||||
all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module)
|
||||
all_from_module, make_iter, is_iter, dbid_to_obj, callables_from_module,
|
||||
get_all_typeclasses)
|
||||
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
||||
from evennia.utils import logger
|
||||
from evennia.utils import inlinefuncs
|
||||
|
|
@ -143,10 +144,10 @@ def prototype_to_str(prototype):
|
|||
header = (
|
||||
"|cprototype key:|n {}, |ctags:|n {}, |clocks:|n {} \n"
|
||||
"|cdesc:|n {} \n|cprototype:|n ".format(
|
||||
prototype['prototype_key'],
|
||||
", ".join(prototype['prototype_tags']),
|
||||
prototype['prototype_locks'],
|
||||
prototype['prototype_desc']))
|
||||
prototype.get('prototype_key', None),
|
||||
", ".join(prototype.get('prototype_tags', ['None'])),
|
||||
prototype.get('prototype_locks', None),
|
||||
prototype.get('prototype_desc', None)))
|
||||
proto = ("{{\n {} \n}}".format(
|
||||
"\n ".join(
|
||||
"{!r}: {!r},".format(key, value) for key, value in
|
||||
|
|
@ -513,7 +514,8 @@ def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_ed
|
|||
return table
|
||||
|
||||
|
||||
def validate_prototype(prototype, protkey=None, protparents=None, _visited=None):
|
||||
def validate_prototype(prototype, protkey=None, protparents=None,
|
||||
is_prototype_base=True, _flags=None):
|
||||
"""
|
||||
Run validation on a prototype, checking for inifinite regress.
|
||||
|
||||
|
|
@ -523,33 +525,77 @@ def validate_prototype(prototype, protkey=None, protparents=None, _visited=None)
|
|||
dict needs to have the `prototype_key` field set.
|
||||
protpartents (dict, optional): The available prototype parent library. If
|
||||
note given this will be determined from settings/database.
|
||||
_visited (list, optional): This is an internal work array and should not be set manually.
|
||||
is_prototype_base (bool, optional): We are trying to create a new object *based on this
|
||||
object*. This means we can't allow 'mixin'-style prototypes without typeclass/parent
|
||||
etc.
|
||||
_flags (dict, optional): Internal work dict that should not be set externally.
|
||||
Raises:
|
||||
RuntimeError: If prototype has invalid structure.
|
||||
RuntimeWarning: If prototype has issues that would make it unsuitable to build an object
|
||||
with (it may still be useful as a mix-in prototype).
|
||||
|
||||
"""
|
||||
assert isinstance(prototype, dict)
|
||||
|
||||
if _flags is None:
|
||||
_flags = {"visited": [], "depth": 0, "typeclass": False, "errors": [], "warnings": []}
|
||||
|
||||
if not protparents:
|
||||
protparents = {prototype['prototype_key']: prototype for prototype in search_prototype()}
|
||||
if _visited is None:
|
||||
_visited = []
|
||||
|
||||
protkey = protkey and protkey.lower() or prototype.get('prototype_key', None)
|
||||
|
||||
assert isinstance(prototype, dict)
|
||||
if not bool(protkey):
|
||||
_flags['errors'].append("Prototype lacks a `prototype_key`.")
|
||||
protkey = "[UNSET]"
|
||||
|
||||
if id(prototype) in _visited:
|
||||
raise RuntimeError("%s has infinite nesting of prototypes." % protkey or prototype)
|
||||
typeclass = prototype.get('typeclass')
|
||||
prototype_parent = prototype.get('prototype_parent', [])
|
||||
|
||||
_visited.append(id(prototype))
|
||||
protstrings = prototype.get("prototype")
|
||||
if not (typeclass or prototype_parent):
|
||||
if is_prototype_base:
|
||||
_flags['errors'].append("Prototype {} requires `typeclass` "
|
||||
"or 'prototype_parent'.".format(protkey))
|
||||
else:
|
||||
_flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks "
|
||||
"a typeclass or a prototype_parent.".format(protkey))
|
||||
|
||||
if protstrings:
|
||||
for protstring in make_iter(protstrings):
|
||||
protstring = protstring.lower()
|
||||
if protkey is not None and protstring == protkey:
|
||||
raise RuntimeError("%s tries to prototype itself." % protkey or prototype)
|
||||
protparent = protparents.get(protstring)
|
||||
if not protparent:
|
||||
raise RuntimeError(
|
||||
"%s's prototype '%s' was not found." % (protkey or prototype, protstring))
|
||||
validate_prototype(protparent, protstring, protparents, _visited)
|
||||
if typeclass and typeclass not in get_all_typeclasses("evennia.objects.models.ObjectDB"):
|
||||
_flags['errors'].append(
|
||||
"Prototype {} is based on typeclass {} which could not be imported!".format(
|
||||
protkey, typeclass))
|
||||
|
||||
# recursively traverese prototype_parent chain
|
||||
|
||||
if id(prototype) in _flags['visited']:
|
||||
_flags['errors'].append(
|
||||
"{} has infinite nesting of prototypes.".format(protkey or prototype))
|
||||
|
||||
_flags['visited'].append(id(prototype))
|
||||
|
||||
for protstring in make_iter(prototype_parent):
|
||||
protstring = protstring.lower()
|
||||
if protkey is not None and protstring == protkey:
|
||||
_flags['errors'].append("Protototype {} tries to parent itself.".format(protkey))
|
||||
protparent = protparents.get(protstring)
|
||||
if not protparent:
|
||||
_flags['errors'].append("Prototype {}'s prototype_parent '{}' was not found.".format(
|
||||
(protkey, protstring)))
|
||||
_flags['depth'] += 1
|
||||
validate_prototype(protparent, protstring, protparents, _flags)
|
||||
_flags['depth'] -= 1
|
||||
|
||||
if typeclass and not _flags['typeclass']:
|
||||
_flags['typeclass'] = typeclass
|
||||
|
||||
# if we get back to the current level without a typeclass it's an error.
|
||||
if is_prototype_base and _flags['depth'] <= 0 and not _flags['typeclass']:
|
||||
_flags['errors'].append("Prototype {} has no `typeclass` defined anywhere in its parent "
|
||||
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||
"prototype with a typeclass.".format(protkey))
|
||||
|
||||
if _flags['depth'] <= 0:
|
||||
if _flags['errors']:
|
||||
raise RuntimeError("Error: " + "\nError: ".join(_flags['errors']))
|
||||
if _flags['warnings']:
|
||||
raise RuntimeWarning("Warning: " + "\nWarning: ".join(_flags['warnings']))
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Possible keywords are:
|
|||
prototype_tags(list, optional): List of tags or tuples (tag, category) used to group prototype
|
||||
in listings
|
||||
|
||||
parent (str, tuple or callable, optional): name (prototype_key) of eventual parent prototype, or
|
||||
prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent prototype, or
|
||||
a list of parents, for multiple left-to-right inheritance.
|
||||
prototype: Deprecated. Same meaning as 'parent'.
|
||||
typeclass (str or callable, optional): if not set, will use typeclass of parent prototype or use
|
||||
|
|
@ -75,13 +75,13 @@ import random
|
|||
|
||||
|
||||
GOBLIN_WIZARD = {
|
||||
"parent": GOBLIN,
|
||||
"prototype_parent": GOBLIN,
|
||||
"key": "goblin wizard",
|
||||
"spells": ["fire ball", "lighting bolt"]
|
||||
}
|
||||
|
||||
GOBLIN_ARCHER = {
|
||||
"parent": GOBLIN,
|
||||
"prototype_parent": GOBLIN,
|
||||
"key": "goblin archer",
|
||||
"attack_skill": (random, (5, 10))"
|
||||
"attacks": ["short bow"]
|
||||
|
|
@ -97,7 +97,7 @@ ARCHWIZARD = {
|
|||
|
||||
GOBLIN_ARCHWIZARD = {
|
||||
"key" : "goblin archwizard"
|
||||
"parent": (GOBLIN_WIZARD, ARCHWIZARD),
|
||||
"prototype_parent": (GOBLIN_WIZARD, ARCHWIZARD),
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -460,11 +460,15 @@ def spawn(*prototypes, **kwargs):
|
|||
prototype_parents (dict): A dictionary holding a custom
|
||||
prototype-parent dictionary. Will overload same-named
|
||||
prototypes from prototype_modules.
|
||||
return_prototypes (bool): Only return a list of the
|
||||
return_parents (bool): Only return a dict of the
|
||||
prototype-parents (no object creation happens)
|
||||
only_validate (bool): Only run validation of prototype/parents
|
||||
(no object creation) and return the create-kwargs.
|
||||
|
||||
Returns:
|
||||
object (Object): Spawned object.
|
||||
object (Object, dict or list): Spawned object. If `only_validate` is given, return
|
||||
a list of the creation kwargs to build the object(s) without actually creating it. If
|
||||
`return_parents` is set, return dict of prototype parents.
|
||||
|
||||
"""
|
||||
# get available protparents
|
||||
|
|
@ -474,17 +478,14 @@ def spawn(*prototypes, **kwargs):
|
|||
protparents.update(
|
||||
{key.lower(): value for key, value in kwargs.get("prototype_parents", {}).items()})
|
||||
|
||||
for key, prototype in protparents.items():
|
||||
protlib.validate_prototype(prototype, key.lower(), protparents)
|
||||
|
||||
if "return_prototypes" in kwargs:
|
||||
if "return_parents" in kwargs:
|
||||
# only return the parents
|
||||
return copy.deepcopy(protparents)
|
||||
|
||||
objsparams = []
|
||||
for prototype in prototypes:
|
||||
|
||||
protlib.validate_prototype(prototype, None, protparents)
|
||||
protlib.validate_prototype(prototype, None, protparents, is_prototype_base=True)
|
||||
prot = _get_prototype(prototype, {}, protparents)
|
||||
if not prot:
|
||||
continue
|
||||
|
|
@ -556,4 +557,6 @@ def spawn(*prototypes, **kwargs):
|
|||
objsparams.append((create_kwargs, permission_string, lock_string,
|
||||
alias_string, nattributes, attributes, tags, execs))
|
||||
|
||||
if kwargs.get("only_validate"):
|
||||
return objsparams
|
||||
return batch_create_object(*objsparams)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import mock
|
|||
from anything import Something
|
||||
from django.test.utils import override_settings
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.utils.tests.test_evmenu import TestEvMenu
|
||||
from evennia.prototypes import spawner, prototypes as protlib
|
||||
from evennia.prototypes import menus as olc_menus
|
||||
|
||||
from evennia.prototypes.prototypes import _PROTOTYPE_TAG_META_CATEGORY
|
||||
|
||||
|
|
@ -304,3 +306,37 @@ class TestPrototypeStorage(EvenniaTest):
|
|||
self.assertEqual(list(protlib.search_prototype(tags="foo1")), [prot1b, prot2, prot3])
|
||||
|
||||
self.assertTrue(str(unicode(protlib.list_prototypes(self.char1))))
|
||||
|
||||
|
||||
@mock.patch("evennia.prototypes.menus.protlib.search_prototype", new=mock.MagicMock(
|
||||
return_value=[{"prototype_key": "TestPrototype",
|
||||
"typeclass": "TypeClassTest", "key": "TestObj"}]))
|
||||
@mock.patch("evennia.utils.utils.get_all_typeclasses", new=mock.MagicMock(
|
||||
return_value={"TypeclassTest": None}))
|
||||
class TestOLCMenu(TestEvMenu):
|
||||
|
||||
maxDiff = None
|
||||
menutree = "evennia.prototypes.menus"
|
||||
startnode = "node_index"
|
||||
|
||||
debug_output = True
|
||||
|
||||
expected_node_texts = {
|
||||
"node_index": "|c --- Prototype wizard --- |n"
|
||||
}
|
||||
|
||||
expected_tree = \
|
||||
['node_index',
|
||||
['node_prototype_key',
|
||||
'node_typeclass',
|
||||
'node_aliases',
|
||||
'node_attrs',
|
||||
'node_tags',
|
||||
'node_locks',
|
||||
'node_permissions',
|
||||
'node_location',
|
||||
'node_home',
|
||||
'node_destination',
|
||||
'node_prototype_desc',
|
||||
'node_prototype_tags',
|
||||
'node_prototype_locks']]
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class TestEvMenu(TestCase):
|
|||
|
||||
def _debug_output(self, indent, msg):
|
||||
if self.debug_output:
|
||||
print(" " * indent + msg)
|
||||
print(" " * indent + ansi.strip_ansi(msg))
|
||||
|
||||
def _test_menutree(self, menu):
|
||||
"""
|
||||
|
|
@ -168,6 +168,7 @@ class TestEvMenu(TestCase):
|
|||
self.caller2.msg = MagicMock()
|
||||
self.session = MagicMock()
|
||||
self.session2 = MagicMock()
|
||||
|
||||
self.menu = evmenu.EvMenu(self.caller, self.menutree, startnode=self.startnode,
|
||||
cmdset_mergetype=self.cmdset_mergetype,
|
||||
cmdset_priority=self.cmdset_priority,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue