diff --git a/CHANGELOG.md b/CHANGELOG.md index de0c55d58a..c04ee27e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,8 @@ Up requirements to Django 3.2+ infinite recursion when wanting to set up Script to delete-on-stop. - Command executions now done on copies to make sure `yield` don't cause crossovers. Add `Command.retain_instance` flag for reusing the same command instance. +- The `typeclass` command will now correctly search the correct database-table for the target + obj (avoids mistakenly assigning an AccountDB-typeclass to a Character etc). ### Evennia 0.9.5 (2019-2020) diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index 9802de7d23..161deb52c2 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -1914,7 +1914,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): typeclass[/switch] [= typeclass.path] typeclass/prototype = prototype_key - typeclass/list/show [typeclass.path] + typeclasses or typeclass/list/show [typeclass.path] swap - this is a shorthand for using /force/reset flags. update - this is a shorthand for using the /force/reload flag. @@ -1956,17 +1956,63 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): """ key = "typeclass" - aliases = ["type", "parent", "swap", "update"] + aliases = ["type", "parent", "swap", "update", "typeclasses"] switch_options = ("show", "examine", "update", "reset", "force", "list", "prototype") locks = "cmd:perm(typeclass) or perm(Builder)" help_category = "Building" + def _generic_search(self, query, typeclass_path): + + caller = self.caller + if typeclass_path: + # make sure we search the right database table + try: + new_typeclass = class_from_module(typeclass_path) + except ImportError: + # this could be a prototype and not a typeclass at all + return caller.search(query) + + dbclass = new_typeclass.__dbclass__ + + if caller.__dbclass__ == dbclass: + # object or account match + obj = caller.search(query) + if not obj: + return + elif (self.account and self.account.__dbclass__ == dbclass): + # applying account while caller is object + caller.msg(f"Trying to search {new_typeclass} with query '{self.lhs}'.") + obj = self.account.search(query) + if not obj: + return + elif hasattr(caller, "puppet") and caller.puppet.__dbclass__ == dbclass: + # applying object while caller is account + caller.msg(f"Trying to search {new_typeclass} with query '{self.lhs}'.") + obj = caller.puppet.search(query) + if not obj: + return + else: + # other mismatch between caller and specified typeclass + caller.msg(f"Trying to search {new_typeclass} with query '{self.lhs}'.") + obj = new_typeclass.search(query) + if not obj: + if isinstance(obj, list): + caller.msg(f"Could not find {new_typeclass} with query '{self.lhs}'.") + return + else: + # no rhs, use caller's typeclass + obj = caller.search(query) + if not obj: + return + + return obj + def func(self): """Implements command""" caller = self.caller - if "list" in self.switches: + if "list" in self.switches or self.cmdname == 'typeclasses': tclasses = get_all_typeclasses() contribs = [key for key in sorted(tclasses) if key.startswith("evennia.contrib")] or [ "" @@ -2029,8 +2075,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): ) return - # get object to swap on - obj = caller.search(self.lhs) + obj = self._generic_search(self.lhs, self.rhs) if not obj: return @@ -2084,10 +2129,8 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS): is_same = obj.is_typeclass(new_typeclass, exact=True) if is_same and "force" not in self.switches: - string = "%s already has the typeclass '%s'. Use /force to override." % ( - obj.name, - new_typeclass, - ) + string = (f"{obj.name} already has the typeclass '{new_typeclass}'. " + "Use /force to override.") else: update = "update" in self.switches reset = "reset" in self.switches diff --git a/evennia/typeclasses/models.py b/evennia/typeclasses/models.py index 036ce173e2..63e74555dd 100644 --- a/evennia/typeclasses/models.py +++ b/evennia/typeclasses/models.py @@ -485,6 +485,23 @@ class TypedObject(SharedMemoryModel): # Object manipulation methods # + @classmethod + def search(cls, query, **kwargs): + """ + Overridden by class children. This implements a common API. + + Args: + query (str): A search query. + **kwargs: Other search parameters. + + Returns: + list: A list of 0, 1 or more matches, only of this typeclass. + + """ + if cls.objects.dbref(query): + return [cls.objects.get_id(query)] + return list(cls.objects.filter(db_key__lower=query)) + def is_typeclass(self, typeclass, exact=False): """ Returns true if this object has this type OR has a typeclass