diff --git a/evennia/prototypes/menus.py b/evennia/prototypes/menus.py index e63acb98c2..34f8eaf648 100644 --- a/evennia/prototypes/menus.py +++ b/evennia/prototypes/menus.py @@ -42,14 +42,15 @@ def _get_menu_prototype(caller): return prototype -def _get_flat_menu_prototype(caller, refresh=False): +def _get_flat_menu_prototype(caller, refresh=False, validate=False): """Return prototype where parent values are included""" flat_prototype = None if not refresh and hasattr(caller.ndb._menutree, "olc_flat_prototype"): flat_prototype = caller.ndb._menutree.olc_flat_prototype if not flat_prototype: prot = _get_menu_prototype(caller) - caller.ndb._menutree.olc_flat_prototype = flat_prototype = spawner.flatten_prototype(prot) + caller.ndb._menutree.olc_flat_prototype = \ + flat_prototype = spawner.flatten_prototype(prot, validate=validate) return flat_prototype @@ -305,11 +306,11 @@ def node_index(caller): {"desc": "|WPrototype-Key|n|n{}".format( _format_option_value("Key", "prototype_key" not in prototype, prototype, None)), "goto": "node_prototype_key"}) - for key in ('Prototype-parent', 'Typeclass', 'Key', 'Aliases', 'Attrs', 'Tags', 'Locks', + for key in ('Prototype_parent', 'Typeclass', 'Key', 'Aliases', 'Attrs', 'Tags', 'Locks', 'Permissions', 'Location', 'Home', 'Destination'): required = False cropper = None - if key in ("Prototype-parent", "Typeclass"): + if key in ("Prototype_parent", "Typeclass"): required = ("prototype_parent" not in prototype) and ("typeclass" not in prototype) if key == 'Typeclass': cropper = _path_cropper @@ -429,11 +430,24 @@ def _prototype_parent_examine(caller, prototype_name): caller.msg("Prototype not registered.") -def _prototype_parent_select(caller, prototype): - ret = _set_property(caller, "", - prop="prototype_parent", processor=str, next_node="node_typeclass") - caller.msg("Selected prototype |y{}|n.".format(prototype)) +def _prototype_parent_select(caller, new_parent): + ret = None + prototype_parent = protlib.search_prototype(new_parent) + try: + if prototype_parent: + spawner.flatten_prototype(prototype_parent[0], validate=True) + else: + raise RuntimeError("Not found.") + except RuntimeError as err: + caller.msg("Selected prototype parent {} " + "caused Error(s):\n|r{}|n".format(new_parent, err)) + else: + ret = _set_property(caller, new_parent, + prop="prototype_parent", + processor=str, next_node="node_prototype_parent") + _get_flat_menu_prototype(caller, refresh=True) + caller.msg("Selected prototype parent |c{}|n.".format(new_parent)) return ret @@ -441,12 +455,12 @@ def _prototype_parent_select(caller, prototype): def node_prototype_parent(caller): prototype = _get_menu_prototype(caller) - prot_parent_key = prototype.get('prototype') + prot_parent_keys = prototype.get('prototype_parent') text = """ The |cPrototype Parent|n allows you to |winherit|n prototype values from another named - prototype (given as that prototype's |wprototype_key|). If not changing these values in the - current prototype, the parent's value will be used. Pick the available prototypes below. + prototype (given as that prototype's |wprototype_key|n). If not changing these values in + the current prototype, the parent's value will be used. Pick the available prototypes below. Note that somewhere in the prototype's parentage, a |ctypeclass|n must be specified. If no parent is given, this prototype must define the typeclass (next menu node). @@ -459,18 +473,23 @@ def node_prototype_parent(caller): prototype to be valid. """ - if prot_parent_key: - prot_parent = protlib.search_prototype(prot_parent_key) - if prot_parent: - text = text.format( - current="Current parent prototype is {}:\n{}".format( - protlib.prototype_to_str(prot_parent))) - else: - text = text.format( - current="Current parent prototype |r{prototype}|n " - "does not appear to exist.".format(prot_parent_key)) - else: - text = text.format(current="Parent prototype is not set") + ptexts = [] + if prot_parent_keys: + for pkey in utils.make_iter(prot_parent_keys): + prot_parent = protlib.search_prototype(pkey) + if prot_parent: + prot_parent = prot_parent[0] + ptexts.append("|c -- {pkey} -- |n\n{prot}".format( + pkey=pkey, + prot=protlib.prototype_to_str(prot_parent))) + else: + ptexts.append("Prototype parent |r{pkey} was not found.".format(pkey=pkey)) + + if not ptexts: + ptexts.append("[No prototype_parent set]") + + text = text.format(current="\n\n".join(ptexts)) + text = (text, helptext) options = _wizard_options("prototype_parent", "prototype_key", "typeclass", color="|W") @@ -993,7 +1012,7 @@ def node_destination(caller): the exit 'leads to'. It's usually unset for all other types of objects. {current} - """.format(current=_get_current_node(caller, "destination")) + """.format(current=_get_current_value(caller, "destination")) helptext = """ The destination can be given as a #dbref but can also be explicitly searched for using diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 011445b039..767919a7a9 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -539,7 +539,7 @@ def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_ed def validate_prototype(prototype, protkey=None, protparents=None, - is_prototype_base=True, _flags=None): + is_prototype_base=True, strict=True, _flags=None): """ Run validation on a prototype, checking for inifinite regress. @@ -552,6 +552,8 @@ def validate_prototype(prototype, protkey=None, protparents=None, 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. + strict (bool, optional): If unset, don't require needed keys, only check against infinite + recursion etc. _flags (dict, optional): Internal work dict that should not be set externally. Raises: RuntimeError: If prototype has invalid structure. @@ -570,14 +572,14 @@ def validate_prototype(prototype, protkey=None, protparents=None, protkey = protkey and protkey.lower() or prototype.get('prototype_key', None) - if not bool(protkey): + if strict and not bool(protkey): _flags['errors'].append("Prototype lacks a `prototype_key`.") protkey = "[UNSET]" typeclass = prototype.get('typeclass') prototype_parent = prototype.get('prototype_parent', []) - if not (typeclass or prototype_parent): + if strict and not (typeclass or prototype_parent): if is_prototype_base: _flags['errors'].append("Prototype {} requires `typeclass` " "or 'prototype_parent'.".format(protkey)) @@ -585,7 +587,7 @@ def validate_prototype(prototype, protkey=None, protparents=None, _flags['warnings'].append("Prototype {} can only be used as a mixin since it lacks " "a typeclass or a prototype_parent.".format(protkey)) - if typeclass and typeclass not in get_all_typeclasses("evennia.objects.models.ObjectDB"): + if strict and 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)) @@ -615,7 +617,7 @@ def validate_prototype(prototype, protkey=None, protparents=None, _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']: + if strict and 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)) diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 31a77ce303..3dd8e11d67 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -161,13 +161,14 @@ def _get_prototype(dic, prot, protparents): return prot -def flatten_prototype(prototype): +def flatten_prototype(prototype, validate=False): """ Produce a 'flattened' prototype, where all prototype parents in the inheritance tree have been merged into a final prototype. Args: prototype (dict): Prototype to flatten. Its `prototype_parent` field will be parsed. + validate (bool, optional): Validate for valid keys etc. Returns: flattened (dict): The final, flattened prototype. @@ -175,7 +176,8 @@ def flatten_prototype(prototype): """ if prototype: protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()} - protlib.validate_prototype(prototype, None, protparents, is_prototype_base=True) + protlib.validate_prototype(prototype, None, protparents, + is_prototype_base=validate, strict=validate) return _get_prototype(prototype, {}, protparents) return {}