diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index a589b5131e..cdcbadc103 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -2993,22 +2993,22 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): try: prot = protlib.save_prototype(**prototype) if not prot: - caller.msg("|rError saving:|R {}.|n".format(key)) + caller.msg("|rError saving:|R {}.|n".format(prototype_key)) return - except PermissionError as err: + except protlib.PermissionError as err: caller.msg("|rError saving:|R {}|n".format(err)) return - caller.msg("|gSaved prototype:|n {}".format(key)) + caller.msg("|gSaved prototype:|n {}".format(prototype_key)) # check if we want to update existing objects - existing_objects = spawner.search_objects_with_prototype(key) + existing_objects = protlib.search_objects_with_prototype(prototype_key) if existing_objects: if 'update' not in self.switches: n_existing = len(existing_objects) slow = " (note that this may be slow)" if n_existing > 10 else "" string = ("There are {} objects already created with an older version " "of prototype {}. Should it be re-applied to them{}? [Y]/N".format( - n_existing, key, slow)) + n_existing, prototype_key, slow)) answer = yield(string) if answer.lower() in ["n", "no"]: caller.msg("|rNo update was done of existing objects. " @@ -3036,7 +3036,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS): return try: success = protlib.delete_db_prototype(caller, self.args) - except PermissionError as err: + except protlib.PermissionError as err: caller.msg("|rError deleting:|R {}|n".format(err)) caller.msg("Deletion {}.".format( 'successful' if success else 'failed (does the prototype exist?)')) diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index f047b3a458..709e7154ba 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -28,7 +28,7 @@ from evennia.utils import ansi, utils, gametime from evennia.server.sessionhandler import SESSIONS from evennia import search_object from evennia import DefaultObject, DefaultCharacter -from evennia.prototypes import spawner, prototypes as protlib +from evennia.prototypes import prototypes as protlib # set up signal here since we are not starting the server @@ -46,7 +46,7 @@ class CommandTest(EvenniaTest): Tests a command """ def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, - receiver=None, cmdstring=None, obj=None): + receiver=None, cmdstring=None, obj=None, inputs=None): """ Test a command by assigning all the needed properties to cmdobj and running @@ -75,14 +75,31 @@ class CommandTest(EvenniaTest): cmdobj.obj = obj or (caller if caller else self.char1) # test old_msg = receiver.msg + inputs = inputs or [] + try: receiver.msg = Mock() if cmdobj.at_pre_cmd(): return cmdobj.parse() ret = cmdobj.func() + + # handle func's with yield in them (generators) if isinstance(ret, types.GeneratorType): - ret.next() + while True: + try: + inp = inputs.pop() if inputs else None + if inp: + try: + ret.send(inp) + except TypeError: + ret.next() + ret = ret.send(inp) + else: + ret.next() + except StopIteration: + break + cmdobj.at_post_cmd() except StopIteration: pass @@ -95,7 +112,7 @@ class CommandTest(EvenniaTest): # Get the first element of a tuple if msg received a tuple instead of a string stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg] if msg is not None: - returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg) + returned_msg = "||".join(_RE.sub("", str(mess)) for mess in stored_msg) returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip() if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()): sep1 = "\n" + "=" * 30 + "Wanted message" + "=" * 34 + "\n" @@ -369,13 +386,13 @@ class TestBuilding(CommandTest): self.call(building.CmdSpawn(), " ", "Usage: @spawn") # Tests "@spawn " without specifying location. - with mock.patch('evennia.commands.default.func', return_value=iter(['y'])) as mock_iter: - self.call(building.CmdSpawn(), - "/save {'prototype_key': 'testprot', 'key':'Test Char', " - "'typeclass':'evennia.objects.objects.DefaultCharacter'}", "") - mock_iter.assert_called() - self.call(building.CmdSpawn(), "/list", "foo") + self.call(building.CmdSpawn(), + "/save {'prototype_key': 'testprot', 'key':'Test Char', " + "'typeclass':'evennia.objects.objects.DefaultCharacter'}", + "Saved prototype: testprot", inputs=['y']) + + self.call(building.CmdSpawn(), "/list", "| Key ") self.call(building.CmdSpawn(), 'testprot', "Spawned Test Char") # Tests that the spawned object's location is the same as the caharacter's location, since @@ -401,10 +418,14 @@ class TestBuilding(CommandTest): goblin.delete() - protlib.create_prototype(**{'key': 'Ball', 'prototype': 'GOBLIN', 'prototype_key': 'testball'}) + # create prototype + protlib.create_prototype(**{'key': 'Ball', + 'typeclass': 'evennia.objects.objects.DefaultCharacter', + 'prototype_key': 'testball'}) # Tests "@spawn " self.call(building.CmdSpawn(), "testball", "Spawned Ball") + ball = getObject(self, "Ball") self.assertEqual(ball.location, self.char1.location) self.assertIsInstance(ball, DefaultObject) @@ -417,10 +438,14 @@ class TestBuilding(CommandTest): self.assertIsNone(ball.location) ball.delete() + self.call(building.CmdSpawn(), + "/noloc {'prototype_parent':'TESTBALL', 'prototype_key': 'testball', 'location':'%s'}" + % spawnLoc.dbref, "Error: Prototype testball tries to parent itself.") + # Tests "@spawn/noloc ...", but DO specify a location. # Location should be the specified location. self.call(building.CmdSpawn(), - "/noloc {'prototype':'TESTBALL', 'location':'%s'}" + "/noloc {'prototype_parent':'TESTBALL', 'key': 'Ball', 'prototype_key': 'foo', 'location':'%s'}" % spawnLoc.dbref, "Spawned Ball") ball = getObject(self, "Ball") self.assertEqual(ball.location, spawnLoc) diff --git a/evennia/contrib/tutorial_world/objects.py b/evennia/contrib/tutorial_world/objects.py index 807b4d5e09..1e088aa8d0 100644 --- a/evennia/contrib/tutorial_world/objects.py +++ b/evennia/contrib/tutorial_world/objects.py @@ -905,19 +905,19 @@ WEAPON_PROTOTYPES = { "magic": False, "desc": "A generic blade."}, "knife": { - "prototype": "weapon", + "prototype_parent": "weapon", "aliases": "sword", "key": "Kitchen knife", "desc": "A rusty kitchen knife. Better than nothing.", "damage": 3}, "dagger": { - "prototype": "knife", + "prototype_parent": "knife", "key": "Rusty dagger", "aliases": ["knife", "dagger"], "desc": "A double-edged dagger with a nicked edge and a wooden handle.", "hit": 0.25}, "sword": { - "prototype": "weapon", + "prototype_parent": "weapon", "key": "Rusty sword", "aliases": ["sword"], "desc": "A rusty shortsword. It has a leather-wrapped handle covered i food grease.", @@ -925,28 +925,28 @@ WEAPON_PROTOTYPES = { "damage": 5, "parry": 0.5}, "club": { - "prototype": "weapon", + "prototype_parent": "weapon", "key": "Club", "desc": "A heavy wooden club, little more than a heavy branch.", "hit": 0.4, "damage": 6, "parry": 0.2}, "axe": { - "prototype": "weapon", + "prototype_parent": "weapon", "key": "Axe", "desc": "A woodcutter's axe with a keen edge.", "hit": 0.4, "damage": 6, "parry": 0.2}, "ornate longsword": { - "prototype": "sword", + "prototype_parent": "sword", "key": "Ornate longsword", "desc": "A fine longsword with some swirling patterns on the handle.", "hit": 0.5, "magic": True, "damage": 5}, "warhammer": { - "prototype": "club", + "prototype_parent": "club", "key": "Silver Warhammer", "aliases": ["hammer", "warhammer", "war"], "desc": "A heavy war hammer with silver ornaments. This huge weapon causes massive damage - if you can hit.", @@ -954,21 +954,21 @@ WEAPON_PROTOTYPES = { "magic": True, "damage": 8}, "rune axe": { - "prototype": "axe", + "prototype_parent": "axe", "key": "Runeaxe", "aliases": ["axe"], "hit": 0.4, "magic": True, "damage": 6}, "thruning": { - "prototype": "ornate longsword", + "prototype_parent": "ornate longsword", "key": "Broadsword named Thruning", "desc": "This heavy bladed weapon is marked with the name 'Thruning'. It is very powerful in skilled hands.", "hit": 0.6, "parry": 0.6, "damage": 7}, "slayer waraxe": { - "prototype": "rune axe", + "prototype_parent": "rune axe", "key": "Slayer waraxe", "aliases": ["waraxe", "war", "slayer"], "desc": "A huge double-bladed axe marked with the runes for 'Slayer'." @@ -976,7 +976,7 @@ WEAPON_PROTOTYPES = { "hit": 0.7, "damage": 8}, "ghostblade": { - "prototype": "ornate longsword", + "prototype_parent": "ornate longsword", "key": "The Ghostblade", "aliases": ["blade", "ghost"], "desc": "This massive sword is large as you are tall, yet seems to weigh almost nothing." @@ -985,7 +985,7 @@ WEAPON_PROTOTYPES = { "parry": 0.8, "damage": 10}, "hawkblade": { - "prototype": "ghostblade", + "prototype_parent": "ghostblade", "key": "The Hawkblade", "aliases": ["hawk", "blade"], "desc": "The weapon of a long-dead heroine and a more civilized age," diff --git a/evennia/prototypes/prototypes.py b/evennia/prototypes/prototypes.py index 57087b133f..df9674b4e7 100644 --- a/evennia/prototypes/prototypes.py +++ b/evennia/prototypes/prototypes.py @@ -13,7 +13,7 @@ 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, - get_all_typeclasses, to_str) + get_all_typeclasses, to_str, dbref) from evennia.locks.lockhandler import validate_lockstring, check_lockstring from evennia.utils import logger from evennia.utils import inlinefuncs @@ -91,6 +91,10 @@ def protfunc_parser(value, available_functions=None, testing=False, stacktrace=F """ if not isinstance(value, basestring): + try: + value = value.dbref + except AttributeError: + pass value = to_str(value, force_string=True) available_functions = _PROT_FUNCS if available_functions is None else available_functions @@ -577,7 +581,7 @@ def validate_prototype(prototype, protkey=None, protparents=None, 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)) + _flags['errors'].append("Prototype {} tries to parent itself.".format(protkey)) protparent = protparents.get(protstring) if not protparent: _flags['errors'].append("Prototype {}'s prototype_parent '{}' was not found.".format( @@ -610,7 +614,7 @@ def validate_prototype(prototype, protkey=None, protparents=None, # make sure prototype_locks are set to defaults prototype_locks = [lstring.split(":", 1) - for lstring in prototype.get("prototype_locks", "").split(';')] + for lstring in prototype.get("prototype_locks", "").split(';') if ":" in lstring] locktypes = [tup[0].strip() for tup in prototype_locks] if "spawn" not in locktypes: prototype_locks.append(("spawn", "all()")) diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 8932b368c1..221200672d 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -114,24 +114,41 @@ class TestUtils(EvenniaTest): self.assertEqual( pdiff, - {'aliases': 'REMOVE', - 'attrs': 'REPLACE', - 'home': 'KEEP', - 'key': 'UPDATE', - 'location': 'KEEP', - 'locks': 'KEEP', - 'new': 'UPDATE', - 'permissions': 'UPDATE', - 'prototype_desc': 'UPDATE', - 'prototype_key': 'UPDATE', - 'prototype_locks': 'KEEP', - 'prototype_tags': 'KEEP', - 'test': 'UPDATE', - 'typeclass': 'KEEP'}) + ({'aliases': 'REMOVE', + 'attrs': 'REPLACE', + 'home': 'KEEP', + 'key': 'UPDATE', + 'location': 'KEEP', + 'locks': 'KEEP', + 'new': 'UPDATE', + 'permissions': 'UPDATE', + 'prototype_desc': 'UPDATE', + 'prototype_key': 'UPDATE', + 'prototype_locks': 'KEEP', + 'prototype_tags': 'KEEP', + 'test': 'UPDATE', + 'typeclass': 'KEEP'}, + {'attrs': [('oldtest', 'to_remove', None, ['']), + ('test', 'testval', None, [''])], + 'prototype_locks': 'spawn:all();edit:all()', + 'prototype_key': Something, + 'locks': ['call:true()', 'control:perm(Developer)', + 'delete:perm(Admin)', 'edit:perm(Admin)', + 'examine:perm(Builder)', 'get:all()', + 'puppet:pperm(Developer)', 'tell:perm(Admin)', + 'view:all()'], + 'prototype_tags': [], + 'location': self.room1, + 'key': 'NewObj', + 'home': self.room1, + 'typeclass': 'evennia.objects.objects.DefaultObject', + 'prototype_desc': 'Built from NewObj', + 'aliases': 'foo'}) + ) # apply diff count = spawner.batch_update_objects_with_prototype( - old_prot, diff=pdiff, objects=[self.obj1]) + old_prot, diff=pdiff[0], objects=[self.obj1]) self.assertEqual(count, 1) new_prot = spawner.prototype_from_object(self.obj1) @@ -470,7 +487,7 @@ class TestOLCMenu(TestEvMenu): menutree = "evennia.prototypes.menus" startnode = "node_index" - debug_output = True + # debug_output = True expect_all_nodes = True expected_node_texts = { @@ -480,15 +497,37 @@ class TestOLCMenu(TestEvMenu): expected_tree = \ ['node_index', ['node_prototype_key', + ['node_index', + 'node_index', + 'node_validate_prototype', + ['node_index'], + 'node_index'], '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']] + ['node_key', + ['node_typeclass', + 'node_key', + 'node_index', + 'node_validate_prototype', + 'node_validate_prototype'], + 'node_index', + 'node_index', + 'node_index', + 'node_validate_prototype', + 'node_validate_prototype'], + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype', + 'node_validate_prototype']]