diff --git a/evennia/objects/models.py b/evennia/objects/models.py index f9e7799d48..b0fdcc801a 100644 --- a/evennia/objects/models.py +++ b/evennia/objects/models.py @@ -13,6 +13,7 @@ Attributes are separate objects that store values persistently onto the database object. Like everything else, they can be accessed transparently through the decorating TypeClass. """ +from collections import defaultdict from django.conf import settings from django.db import models from django.core.exceptions import ObjectDoesNotExist @@ -42,8 +43,9 @@ class ContentsHandler(object): """ self.obj = obj - self._pkcache = {} + self._pkcache = set() self._idcache = obj.__class__.__instance_cache__ + self._typecache = defaultdict(set) self.init() def init(self): @@ -51,25 +53,30 @@ class ContentsHandler(object): Re-initialize the content cache """ - self._pkcache.update( - dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk) - ) + objects = [obj for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk] + self._pkcache = {obj.pk for obj in objects} + for obj in objects: + for ctype in obj._content_types: + self._typecache[ctype].add(obj.pk) - def get(self, exclude=None): + def get(self, exclude=None, category=None): """ Return the contents of the cache. Args: exclude (Object or list of Object): object(s) to ignore + category (str or None): Filter list by a content-type. If None, don't filter. Returns: objects (list): the Objects inside this location """ - if exclude: - pks = [pk for pk in self._pkcache if pk not in [excl.pk for excl in make_iter(exclude)]] + if category is not None: + pks = self._typecache[category] else: pks = self._pkcache + if exclude: + pks = pks - {excl.pk for excl in make_iter(exclude)} try: return [self._idcache[pk] for pk in pks] except KeyError: @@ -91,7 +98,9 @@ class ContentsHandler(object): obj (Object): object to add """ - self._pkcache[obj.pk] = None + self._pkcache.add(obj.pk) + for ctype in obj._content_types: + self._typecache[ctype].add(obj.pk) def remove(self, obj): """ @@ -101,7 +110,9 @@ class ContentsHandler(object): obj (Object): object to remove """ - self._pkcache.pop(obj.pk, None) + self._pkcache.remove(obj.pk) + for ctype in obj._content_types: + self._typecache[ctype].discard(obj.pk) def clear(self): """ @@ -109,6 +120,7 @@ class ContentsHandler(object): """ self._pkcache = {} + self._typecache = defaultdict(set) self.init() diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 23d623bfb9..936c173455 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -203,6 +203,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): without `obj.save()` having to be called explicitly. """ + # Used for sorting / filtering. + _content_types = ("object") # lockstring of newly created objects, for easy overloading. # Will be formatted with the appropriate attributes. @@ -257,7 +259,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): and not self.db_account.attributes.get("_quell") ) - def contents_get(self, exclude=None): + def contents_get(self, exclude=None, category=None): """ Returns the contents of this object, i.e. all objects that has this object set as its location. @@ -266,6 +268,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): Args: exclude (Object): Object to exclude from returned contents list + category (str): A category to filter by. None for no filtering. Returns: contents (list): List of contents of this Object. @@ -1656,20 +1659,25 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). """ + def filter_visible(obj_list): + # Helper method to determine if objects are visible to the looker. + return [obj for obj in obj_list if obj != looker and obj.access(looker, "view")] + if not looker: return "" + # get and identify all objects - visible = (con for con in self.contents if con != looker and con.access(looker, "view")) - exits, users, things = [], [], defaultdict(list) - for con in visible: - key = con.get_display_name(looker) - if con.destination: - exits.append(key) - elif con.has_account: - users.append("|c%s|n" % key) - else: - # things can be pluralized - things[key].append(con) + exits_list = filter_visible(self.contents_get(category='exit')) + users_list = filter_visible(self.contents_get(category='character')) + things_list = filter_visible(self.contents_get(category="object")) + + things = defaultdict(list) + + for thing in things_list: + things[thing.key].append(thing) + users = [f"|c{user.key}|n" for user in users_list] + exits = [ex.key for ex in exits_list] + # get description, build string string = "|c%s|n\n" % self.get_display_name(looker) desc = self.db.desc @@ -2026,7 +2034,7 @@ class DefaultCharacter(DefaultObject): a character avatar controlled by an account. """ - + _content_types = ("character") # lockstring of newly created rooms, for easy overloading. # Will be formatted with the appropriate attributes. lockstring = "puppet:id({character_id}) or pid({account_id}) or perm(Developer) or pperm(Developer);delete:id({account_id}) or perm(Admin)" @@ -2277,7 +2285,7 @@ class DefaultRoom(DefaultObject): This is the base room object. It's just like any Object except its location is always `None`. """ - + _content_types = ("room", "object") # lockstring of newly created rooms, for easy overloading. # Will be formatted with the {id} of the creating object. lockstring = ( @@ -2427,7 +2435,7 @@ class DefaultExit(DefaultObject): exits simply by giving the exit-object's name on its own. """ - + _content_types = ("exit") exit_command = ExitCommand priority = 101