Refactor return_apperance hook to make it easier to override. Resolves #2530, Resolves #2362.

This commit is contained in:
Griatch 2021-10-08 01:04:55 +02:00
parent 074f3bf068
commit bf2c4f151b
2 changed files with 130 additions and 42 deletions

View file

@ -2,7 +2,7 @@
## Evennia 1.0 (2019-) (develop branch, WIP)
Up requirements to Django 3.2+
Up requirements to Django 3.2+, Twisted 21+
- New `drop:holds()` lock default to limit dropping nonsensical things. Access check
defaults to True for backwards-compatibility in 0.9, will be False in 1.0
@ -90,12 +90,20 @@ Up requirements to Django 3.2+
while /Tall becomes 'Tall man'. One can turn this off if wanting the old style.
- Change `EvTable` fixed-height rebalance algorithm to fill with empty lines at end of
column instead of inserting rows based on cell-size (could be mistaken for a bug).
- Split `return_appearance` hook with helper methods and have it use a template
string in order to make it easier to override.
### Evennia 0.9.5 (2019-2020)
Released 2020-11-14.
A transitional release, including new doc system.
Backported from develop: Python 3.8, 3.9 support. Django 3.2+ support, Twisted 21+ support.
- `is_typeclass(obj (Object), exact (bool))` now defaults to exact=False
- `py` command now reroutes stdout to output results in-game client. `py`
without arguments starts a full interactive Python console.

View file

@ -216,6 +216,15 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
objects = ObjectManager()
# populated by `return_apperance`
appearance_template = '''
{header}
|c{name}|n
{desc}
{exits}{characters}{things}
{footer}
'''
# on-object properties
@lazy_property
@ -1725,59 +1734,130 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
# hooks called by the default cmdset.
def get_visible_contents(self, looker, **kwargs):
"""
Get all contents of this object that a looker can see (whatever that means, by default it
checks the 'view' lock), grouped by type. Helper method to return_appearance.
Args:
looker (Object): The entity looking.
**kwargs (any): Passed from `return_appearance`. Unused by default.
Returns:
dict: A dict of lists categorized by type. Byt default this
contains 'exits', 'characters' and 'things'. The elements of these
lists are the actual objects.
"""
def filter_visible(obj_list):
return [obj for obj in obj_list if obj != looker and obj.access(looker, "view")]
return {
"exits": filter_visible(self.contents_get(content_type="exit")),
"characters": filter_visible(self.contents_get(content_type="character")),
"things": filter_visible(self.contents_get(content_type="object"))
}
def get_content_names(self, looker, **kwargs):
"""
Get the proper names for all contents of this object. Helper method
for return_appearance.
Args:
looker (Object): The entity looking.
**kwargs (any): Passed from `return_appearance`. Passed into
`get_display_name` for each found entity.
Returns:
dict: A dict of lists categorized by type. Byt default this
contains 'exits', 'characters' and 'things'. The elements
of these lists are strings - names of the objects that
can depend on the looker and also be grouped in the case
of multiple same-named things etc.
Notes:
This method shouldn't add extra coloring to the names beyond what is
already given by the .get_display_name() (and the .name field) already.
Per-type coloring can be applied in `return_apperance`.
"""
# a mapping {'exits': [...], 'characters': [...], 'things': [...]}
contents_map = self.get_visible_contents(looker, **kwargs)
character_names = [char.get_display_name(looker, **kwargs)
for char in contents_map['characters']]
exit_names = [exi.get_display_name(looker, **kwargs) for exi in contents_map['exits']]
# group all same-named things under one name
things = defaultdict(list)
for thing in contents_map['things']:
things[thing.get_display_name(looker, **kwargs)].append(thing)
# pluralize same-named things
thing_names = []
for thingname, thinglist in sorted(things.items()):
nthings = len(thinglist)
thing = thinglist[0]
singular, plural = thing.get_numbered_name(nthings, looker, key=thingname)
thing_names.append(singular if nthings == 1 else plural)
return {
"exits": exit_names,
"characters": character_names,
"things": thing_names
}
def return_appearance(self, looker, **kwargs):
"""
This formats a description. It is the hook a 'look' command
should call.
Main callback used by 'look' for the object to describe itself.
This formats a description. By default, this looks for the `appearance_template`
string set on this class and populates it with formatting keys
'name', 'desc', 'exits', 'characters', 'things' as well as
(currently empty) 'header'/'footer'.
Args:
looker (Object): Object doing the looking.
**kwargs (dict): Arbitrary, optional arguments for users
overriding the call (unused by default).
overriding the call. This is passed into the helper
methods and into `get_display_name` calls.
Returns:
str: The description of this entity. By default this includes
the entity's name, description and any contents inside it.
Notes:
To simply change the layout of how the object displays itself (like
adding some line decorations or change colors of different sections),
you can simply edit `.appearance_template`. You only need to override
this method (and/or its helpers) if you want to change what is passed
into the template or want the most control over output.
"""
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 ""
return ''
# get and identify all objects
exits_list = filter_visible(self.contents_get(content_type="exit"))
users_list = filter_visible(self.contents_get(content_type="character"))
things_list = filter_visible(self.contents_get(content_type="object"))
# ourselves
name = self.get_display_name(looker, **kwargs)
desc = self.db.desc or "You see nothing special."
things = defaultdict(list)
# contents
content_names_map = self.get_content_names(looker, **kwargs)
exits = list_to_string(content_names_map['exits'])
characters = list_to_string(content_names_map['characters'])
things = list_to_string(content_names_map['things'])
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
if desc:
string += "%s" % desc
if exits:
string += "\n|wExits:|n " + list_to_string(exits)
if users or things:
# handle pluralization of things (never pluralize users)
thing_strings = []
for key, itemlist in sorted(things.items()):
nitem = len(itemlist)
if nitem == 1:
key, _ = itemlist[0].get_numbered_name(nitem, looker, key=key)
else:
key = [item.get_numbered_name(nitem, looker, key=key)[1] for item in itemlist][
0
]
thing_strings.append(key)
string += "\n|wYou see:|n " + list_to_string(users + thing_strings)
return string
# populate the appearance_template string. It's a good idea to strip it and
# let the client add any extra spaces instead.
return self.appearance_template.format(
header='',
name=name,
desc=desc,
exits=f"|wExits:|n {exits}" if exits else '',
characters=f"\n|wCharacters:|n {characters}" if characters else '',
things=f"\n|wYou see:|n {things}" if things else '',
footer=''
).strip()
def at_look(self, target, **kwargs):
"""