diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 0020f807c1..bb917a8dc4 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -416,3 +416,45 @@ def list_prototypes(caller, key=None, tags=None, show_non_use=False, show_non_ed table.reformat_column(2, width=11, align='c') table.reformat_column(3, width=16) return table + + +def validate_prototype(prototype, protkey=None, protparents=None, _visited=None): + """ + Run validation on a prototype, checking for inifinite regress. + + Args: + prototype (dict): Prototype to validate. + protkey (str, optional): The name of the prototype definition. If not given, the prototype + 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. + Raises: + RuntimeError: If prototype has invalid structure. + + """ + 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 id(prototype) in _visited: + raise RuntimeError("%s has infinite nesting of prototypes." % protkey or prototype) + + _visited.append(id(prototype)) + protstrings = prototype.get("prototype") + + 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) diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 8cadd43656..5a1196513a 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -426,10 +426,12 @@ def spawn(*prototypes, **kwargs): """ # get available protparents - protparents = {prot['prototype_key']: prot for prot in protlib.search_prototype()} + protparents = {prot['prototype_key'].lower(): prot for prot in protlib.search_prototype()} # overload module's protparents with specifically given protparents - protparents.update(kwargs.get("prototype_parents", {})) + 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) diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 1b8e340377..e9ef4bce9f 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -50,10 +50,10 @@ class TestSpawner(EvenniaTest): def test_spawn(self): obj1 = spawner.spawn(self.prot1) # check spawned objects have the right tag - self.assertEqual(list(spawner.search_objects_with_prototype("testprototype")), obj1) + self.assertEqual(list(protlib.search_objects_with_prototype("testprototype")), obj1) self.assertEqual([o.key for o in spawner.spawn( _PROTPARENTS["GOBLIN"], _PROTPARENTS["GOBLIN_ARCHWIZARD"], - prototype_parents=_PROTPARENTS)], []) + prototype_parents=_PROTPARENTS)], ['goblin grunt', 'goblin archwizard']) class TestPrototypeStorage(EvenniaTest):