diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index e0f125079e..8ce409300f 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -3330,7 +3330,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): # handle the search result err = None if not prototypes: - err = f"No prototype named '{prototype_key}'." + err = f"No prototype named '{prototype_key}' was found." elif nprots > 1: err = "Found {} prototypes matching '{}':\n {}".format( nprots, @@ -3430,7 +3430,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): if prototypes: return "\n".join(protlib.prototype_to_str(prot) for prot in prototypes) elif query: - self.caller.msg(f"No prototype found to match the query '{query}'.") + self.caller.msg(f"No prototype named '{query}' was found.") else: self.caller.msg(f"No prototypes found.") @@ -3583,24 +3583,44 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): # store a prototype to the database store if not self.args: caller.msg( - "Usage: spawn/save [;desc[;tag,tag[,...][;lockstring]]] = " + "Usage: spawn/save [[;desc[;tag,tag[,...][;lockstring]]]] = " ) return + if self.rhs: + # input on the form key = prototype + prototype_key, prototype_desc, prototype_tags = self._parse_key_desc_tags(self.lhs) + prototype_key = None if not prototype_key else prototype_key + prototype_desc = None if not prototype_desc else prototype_desc + prototype_tags = None if not prototype_tags else prototype_tags + prototype_input = self.rhs.strip() + else: + prototype_key = prototype_desc = None + prototype_tags = None + prototype_input = self.lhs.strip() - prototype_key, prototype_desc, prototype_tags = self._parse_key_desc_tags(self.lhs) - - # handle rhs: - prototype = self._parse_prototype(self.rhs.strip()) + # handle parsing + prototype = self._parse_prototype(prototype_input) if not prototype: return - if prototype.get("prototype_key") != prototype_key: + prot_prototype_key = prototype.get("prototype_key") + + if not (prototype_key or prot_prototype_key): + caller.msg("A prototype_key must be given, either as `prototype_key = ` " + "or as a key 'prototype_key' inside the prototype structure.") + return + + if prototype_key is None: + prototype_key = prot_prototype_key + + if prot_prototype_key != prototype_key: caller.msg("(Replacing `prototype_key` in prototype with given key.)") prototype['prototype_key'] = prototype_key - if prototype_desc and prototype.get("prototype_desc") != prototype_desc: + + if prototype_desc is not None and prot_prototype_key != prototype_desc: caller.msg("(Replacing `prototype_desc` in prototype with given desc.)") prototype['prototype_desc'] = prototype_desc - if prototype_tags and prototype.get("prototype_tags") != prototype_tags: + if prototype_tags is not None and prototype.get("prototype_tags") != prototype_tags: caller.msg("(Replacing `prototype_tags` in prototype with given tag(s))" ) prototype['prototype_tags'] = prototype_tags diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index d41a1ca4ca..4edaccb51d 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -1228,13 +1228,22 @@ class TestBuilding(CommandTest): inputs=["y"], ) + self.call( + building.CmdSpawn(), + "/save testprot2 = {'key':'Test Char', " + "'typeclass':'evennia.objects.objects.DefaultCharacter'}", + "(Replacing `prototype_key` in prototype with given key.)|Saved prototype: testprot2", + inputs=["y"], + ) + self.call(building.CmdSpawn(), "/search ", "Key ") self.call(building.CmdSpawn(), "/search test;test2", "") self.call( building.CmdSpawn(), "/save {'key':'Test Char', " "'typeclass':'evennia.objects.objects.DefaultCharacter'}", - "To save a prototype it must have the 'prototype_key' set.", + "A prototype_key must be given, either as `prototype_key = ` or as " + "a key 'prototype_key' inside the prototype structure.", ) self.call(building.CmdSpawn(), "/list", "Key ") @@ -1312,7 +1321,7 @@ class TestBuilding(CommandTest): ball.delete() # test calling spawn with an invalid prototype. - self.call(building.CmdSpawn(), "'NO_EXIST'", "No prototype named 'NO_EXIST'") + self.call(building.CmdSpawn(), "'NO_EXIST'", "No prototype named 'NO_EXIST' was found.") # Test listing commands self.call(building.CmdSpawn(), "/list", "Key ") @@ -1343,13 +1352,12 @@ class TestBuilding(CommandTest): # spawn/edit with invalid prototype msg = self.call( - building.CmdSpawn(), "/edit NO_EXISTS", "No prototype 'NO_EXISTS' was found." + building.CmdSpawn(), "/edit NO_EXISTS", "No prototype named 'NO_EXISTS' was found." ) # spawn/examine (missing prototype) # lists all prototypes that exist - msg = self.call(building.CmdSpawn(), "/examine") - assert "testball" in msg and "testprot" in msg + self.call(building.CmdSpawn(), "/examine", "You need to specify a prototype-key to show.") # spawn/examine with valid prototype # prints the prototype @@ -1358,7 +1366,7 @@ class TestBuilding(CommandTest): # spawn/examine with invalid prototype # shows error - self.call(building.CmdSpawn(), "/examine NO_EXISTS", "No prototype 'NO_EXISTS' was found.") + self.call(building.CmdSpawn(), "/examine NO_EXISTS", "No prototype named 'NO_EXISTS' was found.") class TestComms(CommandTest): diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index fcadc55488..5d29d15b44 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -56,7 +56,7 @@ PROTOTYPE_TAG_CATEGORY = "from_prototype" _PROTOTYPE_TAG_META_CATEGORY = "db_prototype" PROT_FUNCS = {} -_PROTOTYPE_FALLBACK_LOCK = "spawn:all();edit:perm(Admin)" +_PROTOTYPE_FALLBACK_LOCK = "spawn:all();edit:all()" class PermissionError(RuntimeError): diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index 222e4ed49c..fc8015bfbd 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -166,6 +166,18 @@ _PROTOTYPE_ROOT_NAMES = ( _NON_CREATE_KWARGS = _CREATE_OBJECT_KWARGS + _PROTOTYPE_META_NAMES +class Unset: + """ + Helper class representing a non-set diff element. + + """ + def __bool__(self): + return False + def __str__(self): + return "" + + + # Helper @@ -351,12 +363,6 @@ def prototype_diff(prototype1, prototype2, maxdepth=2, homogenize=False, implici instruction can be one of "REMOVE", "ADD", "UPDATE" or "KEEP". """ - class Unset: - def __bool__(self): - return False - def __str__(self): - return "" - _unset = Unset() def _recursive_diff(old, new, depth=0): diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 2c7d5f0426..dfa592adbb 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -11,7 +11,7 @@ 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 import protfuncs as protofuncs +from evennia.prototypes import protfuncs as protofuncs, spawner from evennia.prototypes.prototypes import _PROTOTYPE_TAG_META_CATEGORY @@ -212,22 +212,21 @@ class TestUtils(EvenniaTest): "puppet:pperm(Developer);tell:perm(Admin);view:all()", "KEEP", ), - "prototype_tags": {}, + "prototype_tags": (None, None, 'KEEP'), "attrs": { "oldtest": ( ("oldtest", "to_keep", None, ""), ("oldtest", "to_keep", None, ""), "KEEP", ), - "test": (("test", "testval", None, ""), None, "REMOVE"), - "desc": (("desc", "changed desc", None, ""), None, "REMOVE"), - "fooattr": (None, ("fooattr", "fooattrval", None, ""), "ADD"), + "desc": (("desc", "changed desc", None, ""), None, "KEEP"), + "fooattr": (Something, ("fooattr", "fooattrval", None, ""), "ADD"), "test": ( ("test", "testval", None, ""), ("test", "testval_changed", None, ""), "UPDATE", ), - "new": (None, ("new", "new_val", None, ""), "ADD"), + "new": (Something, ("new", "new_val", None, ""), "ADD"), }, "key": ("Obj", "Obj", "KEEP"), "typeclass": ( @@ -246,7 +245,7 @@ class TestUtils(EvenniaTest): spawner.flatten_diff(pdiff), { "aliases": "REMOVE", - "attrs": "REPLACE", + "attrs": "UPDATE", "home": "KEEP", "key": "KEEP", "location": "KEEP", @@ -270,7 +269,9 @@ class TestUtils(EvenniaTest): new_prot = spawner.prototype_from_object(self.obj1) self.assertEqual( { + "aliases": ['foo'], "attrs": [ + ("desc", "changed desc", None, ""), ("fooattr", "fooattrval", None, ""), ("new", "new_val", None, ""), ("oldtest", "to_keep", None, ""), @@ -293,6 +294,9 @@ class TestUtils(EvenniaTest): "view:all()", ] ), + 'tags': [ + ('footag', 'foocategory', None), + (Something, 'from_prototype', None)], "permissions": ["builder"], "prototype_desc": "Built from Obj", "prototype_key": Something, @@ -912,24 +916,20 @@ class TestMenuModule(EvenniaTest): texts, options = olc_menus._format_diff_text_and_options(obj_diff) self.assertEqual( - "\n".join(texts), - "- |wattrs:|n \n" - " |gKEEP|W:|n desc |W=|n This is User #1. |W(category:|n None|W, locks:|n |W)|n\n" - " |c[1] |yADD|n|W:|n None |W->|n foo |W=|n bar |W(category:|n None|W, locks:|n |W)|n\n" - " |gKEEP|W:|n prelogout_location |W=|n #2 |W(category:|n None|W, locks:|n |W)|n\n" - "- |whome:|n |gKEEP|W:|n #2\n" - "- |wkey:|n |gKEEP|W:|n TestChar\n" - "- |wlocks:|n |gKEEP|W:|n boot:false();call:false();control:perm(Developer);delete:false();edit:false();examine:perm(Developer);get:false();msg:all();puppet:false();tell:perm(Admin);view:all()\n" - "- |wpermissions:|n \n" - " |gKEEP|W:|n developer\n" - "- |wprototype_desc:|n |c[2] |rREMOVE|n|W:|n Testobject build |W->|n None\n" - "- |wprototype_key:|n |gKEEP|W:|n TestDiffKey\n" - "- |wprototype_locks:|n |gKEEP|W:|n spawn:all();edit:all()\n" - "- |wprototype_tags:|n \n" - "- |wtags:|n \n" - " |c[3] |yADD|n|W:|n None |W->|n foo |W(category:|n None|W)|n\n" - "- |wtypeclass:|n |gKEEP|W:|n typeclasses.characters.Character", + "\n".join(txt.strip() for txt in texts), + "- |wattrs:|n |c[1] |yADD|n: foo |W=|n bar |W(category:|n None|W, locks:|n |W)|n" + "\n- |whome:|n" + "\n- |wkey:|n" + "\n- |wlocks:|n" + "\n- |wpermissions:|n" + "\n- |wprototype_desc:|n |c[2] |rREMOVE|n: Testobject build" + "\n- |wprototype_key:|n" + "\n- |wprototype_locks:|n" + "\n- |wprototype_tags:|n" + "\n- |wtags:|n |c[3] |yADD|n: foo |W(category:|n None|W)|n" + "\n- |wtypeclass:|n" ) + self.assertEqual( options, [