Lists and Listers

by Eric Eve

At many points in a TADS 3 game the player will see a list of objects: in a room description, in examining the player character's inventory, in looking in, under or behind certain objects and so on. The default behaviour of the TADS 3 library generally handles these lists pretty well, but there will be probably come a point when you want to customize one or more of these lists. This article will explore some of the ways you can do this.

This is actually quite a complex topic, since there are so many aspects of lists that can be customized, including:

To attempt to cover every single aspect of Lists and Listers in this article would probably prove more confusing than helpful; it would almost certainly prove overwhelming. Instead we shall concentrate on those aspects of Lists and Listers that are useful for the kind of listing tasks most likely to crop up in TADS 3 games. Even this will prove a fairly long and complex journey, so we provide an index below so that you can jump to a particular part of the discussion if you already know what it is you need particular help with.

Really Simple Lists

The TADS 3 Library uses a variety of Lister objects (i.e. objects derived from the Lister class) to produce the many lists that appear in a typical TADS 3 game. If you look at the definition of Lister and its subclasses in the library, you'll see a bewildering array of methods, many of which look bewilderingly complex. You may be wondering whether it's possible to get a Lister just to display a straightforward list from a list of objects (or even strings) passed to it. You may sometimes wish you could just use code like this:

objectLister.showSimpleList([orange, apple, pear]);

stringLister.showSimpleList(['red', 'white', 'blue']);

To produce properly formatted lists like:

an orange, an apple, and a pear

red, white, and blue

Well, the good news is that the library now provide this; it now defines:

class SimpleLister: Lister
    showSimpleList(lst) { showListAll(lst, 0, 0); }
    isListed(obj) { return true; }
    makeSimpleList(lst)
    {
        return mainOutputStream.captureOutput({: showSimpleList(lst) });
    }
;

objectLister: SimpleLister    
;

stringLister: SimpleLister
    showListItem (str, options, pov, infoTab) { say(str); }
    getArrangedListCardinality(singles, groups, groupTab)
    {
        return singles.length();
    }    
;

This allows us not only to display a simple list, but also to capture it in single-quoted string (using the makeSimpleList(lst) method).

This may not seem particularly useful when we're just passing a constant list to the showSimpleList() method, since we could more simply have displayed the strings "an orange, an apple and a pear" or "red, white and blue", but these simple listers could prove more useful when we're building the list of objects or strings programmatically, and then want to display a neatly formatted list. An example of this might be when we're defining a LabeledDial and want to include a list of its validSettings in its descriptions, e.g.:

  myDial: LabeledDial, Component 'big red dial' 'big red dial'
     "The dial can be turned to a number of settings, in particular: 
      <<stringLister.showSimpleList(validSettings)>>. "
      validSettings = ['freezing', 'cold', 'tepid', 'hot', 'boiling']
  ;      

Should we then want to add a 'warm' setting, we can add this to the validSettings property knowing that the description will be automatically taken care of.

Most of the lists (and Listers) you will encounter are actually rather more complex than this. The Listers in the library tend to assume that they're producing a list of objects that can be sensed by a particular actor in a particular context, and so they use a series of more complex methods with more complex parameter lists. This is usually what is in fact needed in the context of a TADS 3 game. But in addition to providing code you can use for displaying custom lists you construct in your own code, the above examples provide a useful introduction to Listers. Listers are objects that take a list of objects, usually along with other information (such as sensory information) and output a properly formatted list. The list is displayed by calling the appropriate method of the Lister; the showListAll() method is the simplest, because it ignores all the sensory information; the library more commonly calls showList(), but that's more complicated than we need to go into here.

In what follows, we shall look at various ways in which the output from the library's Listers can be customized. These lists are not normally ones we build by hand (as in the simple example above), but lists of objects in a particular place (such as a room or container) that are or become visible to the player character at a particular time.


Including and Excluding Items in Lists

The simplest way to control whether an item is or is not shown in a particular list is to set the appropriate isListedXXX property/method:

The library has sensible defaults for all these properties and methods, but if you want something other than the default behaviour, it's easy enough to override the appropriate method accordingly. For example, supposing Myrtle is carrying a bunch of flowers and a revolver, and wearing a red dress, but that she's somehow managed to conceal the revolver in her dress (or in the bunch of flowers?). To prevent the revolver being listed in Myrtle's inventory we could do this:

myrtle: Person 'pretty myrtle/woman' 'Myrtle' @westRoom
    "You think she's very pretty. "    
    isHer = true
    isProperName = true
;
        
+ Thing 'bunch/flowers' 'bunch of flowers'
;

+ Wearable 'red dress' 'red dress'
    wornBy = myrtle    
;

+ Thing 'revolver/run' 'revolver'
    isListedInInventory = (!isIn(myrtle))
;

This will prevent the revolver being listed in Myrtle's inventory (but not in the player character's inventory should he later acquire the revolver). Actually, this would not be the best way to handle this particular issue, since the player can still examine the revolver when Myrtle's carrying it even though it's not listed; a better solution would be to make the revolver of class Hidden and then to call its discover() method at the appropriate moment. Nevertheless, the example serves to illustrate the principle.

Perhaps a more realistic example might be a large black pot that contains a small silver coin. Normally, it would be appear in a room description as:

You see a large black pot (which contains a small silver coin) here. 

We may think it more realistic for the player not to notice the coin unless he explicitly examines (or looks in) the pot. We could achieve this like so:

+ pot: Container 'large black pot' 'large black pot'
;

++ coin: Thing 'small silver coin*coins' 'small silver coin'
    isListed = (!isIn(pot))
;

Note how we override isListed so that it's only nil when the coin is in the pot; once the coin is removed from the pot, we'd probably want it to be listed normally.

It's possible that we think examining the pot is not enough to reveal the coin, and that the player character would only notice it if he actually looks in the pot. We then need to override isListedInContents rather than isListed, but the complication is that we want isListedInContents to be true for a LOOK IN command but not for an EXAMINE command, so we'd have to do something like this:

++ coin: Thing 'small silver coin*coins' 'small silver coin'    
    isListedInContents = (!isIn(pot) || gActionIs(LookIn))
;

Note that if we override isListedInContents we don't need to override isListed as well in this case, since by default isListed takes its value from isListedInContents. However, the fact that the solution is beginning to look a little convoluted is a sign that there may be a better way of going about this. In particular, we might do better to adjust properties of the pot rather than properties of the coin, since it's the pot that's tending to conceal its contents. The time has come to look at a couple of useful properties of containers:

Using these properties, we could make the pot hide its contents from a room description, revealing them only when the pot is explictly examined or looked in, like so:

+ pot: Container 'large black pot' 'large black pot'
    contentsListed = nil
;

++ coin: Thing 'small silver coin*coins' 'small silver coin'        
;

This would give us a transcript like:

East Room
This is the east room. An exit leads west. 

You see a large black pot here. 

>x pot
It contains a small silver coin. 

>look in pot
The large black pot contains a small silver coin. 

If we also want the contents of the pot to remain unlisted until the player explicitly looks in the pot, we can set contentsListedInExamine to nil:

+ pot: Container 'large black pot' 'large black pot'
    contentsListedInExamine = nil
;

++ coin: Thing 'small silver coin*coins' 'small silver coin'        
;

This would result in the slightly different transcript:

East Room
This is the east room. An exit leads west. 

You see a large black pot here. 

>x pot
You see nothing unusual about it.  

>look in pot
The large black pot contains a small silver coin. 

For a container that doesn't obviously display its contents until it's explicitly examined and/or looked in, this is clearly a neater solution than manipulating the isListedXXX properties of it contents.

Between them, the isListedXXX and contentsListedXXX properties provide the most straightforward means of excluding items from lists, and between them they will probably cover most cases. For more complex cases you might need to start overriding some of the methods of the Lister that's responsible for displaying the list in question. We'll discuss Listers – including how to identify which Lister is used in which situation – further below. In the meantime we'll just list the Lister methods that might be useful in adjusting the contents of a list:

One further technique that can be used to adjust the list of objects to be displayed in a room description is to override the adjustLookAroundTable() method of the room in question. This technique is illustrated in the Looking Through the Window section of Getting Started in TADS 3.


Ordering and Grouping List Items

Ordering

A typical room description might contain a list of items present in something like the following form:

A large box rests in the corner. 

A table stands in the middle of the room. 

You see a chair, a bucket of water, a pen, and a piece of paper here. On the table are a saucer, 
a cup, and a priceless antique vase. 

Myrtle is standing here.

In this list two items with a specialDesc (or initSpecialDesc) property defined are listed first (the box and the table), then a list of miscellaneous items (followed by a list of items on the table), and finally an NPC who happens to be present. There is not a great deal we can do to determine the order of the items within a miscellaneous list (e.g. the chair, bucket, pen, and piece of paper) in the general case, but in particular cases we should probably be able to achieve the effect we want by grouping (see below).

On the other hand, defining the order of items that get paragraphs to themselves (i.e. those that have a specialDesc or initSpecialDesc property defined) is quite straightforward; we simply need to adjust the values of one or both of the following two properties:

Suppose we wanted to arrange the listing above so that Myrtle is mentioned first, before the table, and the box is mentioned last of all, after all the miscellaneous items. Then we simply need to change specialDescBeforeContents on Myrtle to true, and specialDescOrder on Myrtle to 50 (say) to make sure she's mentioned before the table, and then change specialDescBeforeContents on the box to nil. We'd then see the items listed thus:

Myrtle is standing here. 

A table stands in the middle of the room. 

You see a chair, a bucket of water, a pen, and a piece of paper here. On the table are a saucer,
a cup, and a priceless antique vase. 

A large box rests in the corner. 


Grouping

There is no standard listOrder property for miscellaneous items (analagous to specialDescOrder), but just about any custom ordering of miscellaneous item can be achieved by grouping them. Indeed, you could even assign every single item in your game to the same list group, and then define a listOrder property of your own that your list group could then be made to sort on. But let's take things one step at a time.

To group miscellaneous items in a list you need to:

  1. Define an object of class ListGroup (or rather, one of its subclasses)
  2. Assign the listWith property of all the items you want listed in this group to this ListGroup object

This should hopefully become much clearer with an example. Suppose we add a pencil, a ruler, and a bottle of ink to the miscellaneous items in the room. We might then see a listing like:

You see a chair, a ruler, a pencil, a bucket of water, a bottle of ink, a pen, 
and a piece of paper here.

But since the ruler, the pencil, the ink, the pen and the paper are all writing implements of a sort, we might prefer them all to be listed together. The first step is to create an appropriate ListGroup:

writingMaterials: ListGroupSorted    
;

Then we assign each of the objects in question to this ListGroup, e.g.:

+ ruler: Thing 'ruler' 'ruler'
    listWith = [writingMaterials]
;

Note that the listWith property takes a list of ListGroups, since the same item can belong to more than one ListGroup. We'll say a bit more about multiple ListGroups in this property later; for now we'll keep it simple and stick to one. Once we've defined our writingMaterials ListGroup and assigned all the appropriate objects to it in this way, the list will display something like:

You see a chair; a ruler, a pencil, a bottle of ink, a pen, and a piece of paper; 
and a bucket of water here.  

Note that we made our writingMaterials list group a ListGroupSorted and not just a ListGroup. This is because the base ListGroup class doesn't actually display anything when it comes to listing its members; it's nearly always more useful, therefore, to use a subclass of ListGroup, ListGroupSorted being perhaps the most general-purpose of those subclasses. As its name suggest, ListGroupSorted can also sort the items it lists. Suppose, for example, we not only wanted to group the writing implements together, but wanted them to appear in the order: pen, pencil, ink, paper, ruler. We can do this by first defining a compareGroupItems() method on our writingMaterials list group, which could make use of a custom listOrder property on the writing implements themselves:

writingMaterials: ListGroupSorted    
    /* 
     * Return an integer > 0 if the first item sorts after the second item;
     * Return an integer < 0 if the second item sorts after the first item;
     * Return zero if the two items are at the same sorting order.
     */
    compareGroupItems (a, b) { return a.listOrder - b.listOrder; }    
;

+ pencil: Thing 'pencil' 'pencil'
    listWith = [writingMaterials]
    listOrder = 20
;

+ ink: Thing 'bottle/ink' 'bottle of ink'
    listWith = [writingMaterials]
    listOrder = 30
;

/* etc. */

Note that we could have called the listOrder property anything we liked, so long as we use the same name in writingMaterials.compareGroupItems() and on the items in question. With our modified writingMaterials list group we'll now get a list like:

You see a chair; a pen, a pencil, a bottle of ink, a piece of paper, and a ruler; 
and a bucket of water here.

ListGroupSorted has a couple of subclasses we can also use for particular purposes. We could use ListGroupPrefixSuffix to explicitly introduce the list of writing implements as "some writing implements":

writingMaterials: ListGroupPrefixSuffix   
    compareGroupItems (a, b)
    {
        /* Return 1 if the first item sorts after the second item */
        if(a.listOrder > b.listOrder)
            return 1;
        
        /* Return -1 if the first item sorts before the second item */
        if(a.listOrder < b.listOrder)
            return -1;
        
        /* Return 0 if the two items are at the same sorting order */
        return 0;
    }
    
    groupPrefix = "some writing materials: "
;

This would result in:

You see a chair; some writing materials: a pen, a pencil, a bottle of ink, 
a piece of paper, and a ruler; and a bucket of water here. 

Note that we could likewise use the groupSuffix property to append text to the end of the list of writing materials. In a more complex situation we could instead override the methods showGroupPrefix(pov, lst) and showGroupSuffix(pov, lst), which by default just display the groupPrefix and groupSuffix properties; in a more complex case we might want to use these methods to vary the text shown according to the value of the pov parameter (normally the actor doing the looking) or the lst parameter (the list of items about to be listed).

We can use ListGroupParen to give a general description of the items (e.g. "five writing implements" followed by the actual items listed in parentheses:

writingMaterials: ListGroupParen    
    compareGroupItems (a, b)
    {
        /* Return 1 if the first item sorts after the second item */
        if(a.listOrder > b.listOrder)
            return 1;
        
        /* Return -1 if the first item sorts before the second item */
        if(a.listOrder < b.listOrder)
            return -1;
        
        /* Return 0 if the two items are at the same sorting order */
        return 0;
    }  
    showGroupCountName(lst)
    {
        "<<spellInt(lst.length)>> writing implements";
    }    
;

Which results in:

You see a chair, five writing implements (a pen, a pencil, a bottle of ink, 
a piece of paper, and a ruler), and a bucket of water here.

Note that if we hadn't overridden showGroupCountName() it would simply have used the count name of the first item in the list, so the list would have been shown as "five pens (a pen, a pencil, ...)" intead of "five writing implements (a pen, a pencil, ...)"

Finally, we could use ListGroupCustom (a subclass of ListGroup, but not of ListGroupSorted) just to give a summary name for the pen, pencil etc. without listing the individual items at all:


writingMaterials: ListGroupCustom
    showGroupMsg (lst) { "some writing materials"; }
;

Resulting it:

You see a chair, some writing materials, and a bucket of water here.

This is probably not a good idea unless the individual items answer to the vocabulary "writing materials" (perhaps as a plural), since otherwise the player won't be able to refer to them!

In summary, the ListGroup classes that are most useful, together with the properties you'll commonly want to override on them, are:

There are one or two other ListGroup classes defined in the library (ListGroupEquivalent, SuggestionListGroup and RoomActorGrouper), but you are less likely to use these in your own code; they're automatically employed by the standard library as they're needed.

There's a couple more points to bear in mind about these ListGroups. First, you may be wondering what happens if only one member of a group is present; it would be ugly - or at least needlessly pedantic - to see a list like "You see one writing implement (a pen) here". This is taken care of by the minGroupSize property on the ListGroup object. By default the value of this property is two, which means that the ListGroup will only be used to group its members in a list if at least two of them are present. This is the behaviour we'd normally want, but obviously we can always change this property to something other than two if we need to.

The other main point is the complication introduced above: the listWith property of an object contains a list of ListGroup objects, and that list can contain more than one member, meaning that an object can be grouped in more than one way. When multiple groups are specified here, the order is significant:

One final point: the property specialDescListWith can be used to group items displaying specialDescs together in the same kind of way we have seen for ordinary items. That is, the specialDescListWith property can contain a list of one or more ListGroup objects that will be used to group the specialDescs. In game code, this is only likely to be at all useful with a ListGroupSorted, which could then be used as an alternative to specialDescOrder to group related items together and, if desired, list them in a certain sequence. Note that any grouping performed by this means will take precedence over the order specified by the specialDescOrder property.


Introductory and Concluding Text

Container Contents

A room description might typically include a list of objects like the following:

You see a chair, a table (on which are a saucer, a cup, and a priceless antique vase), a bucket
of water, a pen, and a piece of paper here. 

Myrtle is standing here. 

Here the paragraph about Myrtle (an NPC) stands out separately from the miscellaneous objects listed first. These miscellaneous objects are introduced with the text "You see" and the list is concluded with "here." Within the list is a bracketed sublist of items on the table, introduced with the words "on which are" (by the way, if you wanted to see the items on the table listed separately here, and not in a bracketed sublist, you'd just need to set the table's contentsListedSeparately property to true).

Let's start by thinking about that bracketed sublist, and the ways in which we might want to change how it's introduced. Suppose, for example, than instead of the relatively colourless "on which are" we wanted the slightly more "across which are strewn". How would we go about this?

Obviously we need to know where the text "on which are" is coming from. In fact it comes from the Lister object that controls how the contents of the table (i.e. what's on the table) is displayed. Working out just what that object is is a little complicated. The table is presumably a Surface, and if you look up the definition of Surface in the Library Reference Manual (or the library source code in objects.t) you'll find it has four properties related to listers:

In fact, these four properties are all defined on Thing, and hence exist on all subclasses of Thing. But they are overridden on Surface to use Listers that are suitable for listing objects that are on something; other subclasses may need listers that describe their contents as being in, under or behind their parent object.

The property we're after right now is the inlineContentsLister, since this is the one that's responsible for displaying the object's contents parenthetically as part of its list entry in a second-level contents listing (or, in other words, in producing a sublist in brackets after an object that's itself being shown as part of a list). If we look at the definition of inlineContentsLister on Surface, we'll find that it simply points to the surfaceInLineContentsLister object. If we look this up in turn we'll find that it's defined as:

surfaceInlineContentsLister: inlineListingContentsLister
    showListPrefixWide(cnt, pov, parent)
    {
        " (on which <<cnt == 1 ? tSel('is', 'was')
                               : tSel('are', 'were')>> ";
    }
;

From this we can already see where the text "(on which is/are ... _)" is coming from, but for the sake of completeness we might want to see the definition of inLineContentsLister, from which surfaceInlineContentsLister inherits:

inlineListingContentsLister: ContentsLister
    showListEmpty(pov, parent) { }
    showListPrefixWide(cnt, pov, parent)
        { " (which contain<<parent.verbEndingSEd>> "; }
    showListSuffixWide(itemCount, pov, parent)
        { ")"; }
;

It should be reasonably self-evident what the methods shown here do. First, showListEmpty defines what is to be shown when there's nothing in the list (in this case, nothing; in other circumstances it may be appropriate to display a message explicitly stating that a container is empty). The other two methods, showListPrefixWide and showListSuffixWide define the text with which the list is introduced and concluded (when the list is not empty). The cnt or itemCount parameter can be used to test whether there is one or more items in the list, so that where a verb is used it can be made singular or plural accordingly (e.g. 'is' or 'are'), so that we avoid seeing lists like "On the table are a cup" or "On the table is a cup and a saucer". Note that the case where there's a single item that looks plural (e.g. "some flowers") is automatically taken of; in this case a plural number will still be passed to these routines so that we'll get "On the table are some flowers", not "On the table is some flowers." The library takes care of this for us, so we don't need to worry about it.

So, if we want to see "across which are scattered" instead of "on which are", we need to override the showListPrefixWide() method; but the next question is, where exactly should we override it? Unless we want to see "across which are scattered" introducing the list of items on top of every Surface in our game, we probably don't want to modify inLineListingContentsLister. We could create a special new lister object just for our table, but if we only want to use it on our table and not for any other object in our game this is probably best done as an anonymous object defined on the table itself:

+ table: Surface 'table' 'table'
    inlineContentsLister: surfaceInlineContentsLister
    {
        showListPrefixWide(cnt, pov, parent)
        {
            " (scattered across which <<cnt == 1 ? tSel('is', 'was')
              : tSel('are', 'were')>> ";
        }
        
    }
;

And now we'll see:

You see a chair, a table (scattered across which are a saucer, a cup, and a priceless antique
vase), a bucket of water, a pen, and a piece of paper here.

The main things to note here are that (1) we've subclassed our custom inLineContentsLister from the object a Surface would otherwise have used, to ensure that we get behaviour appropriate to a Surface for every aspect of the Lister we haven't explicitly customized ourselves; (2) we use the cnt parameter of the showListPrefixWide() method to ensure we get the singular or plural form of the verb ("is scattered" or "are scattered") as appropriate, and (3) we use the tSel macro to ensure we get the present or past tense as appropriate; if we're never going to change tense in our game (and in most games we probably won't) then this last step won't actually be necessary. (For more information on changing tenses in games, see the Technical Article on Writing a Game in the Past Tense).

Had we wanted to use this "scattered" wording on more than one object in our game, we could instead have defined a separate custom lister object which we then attached to the objects we wanted to use it on, e.g.:

+ smallTable: Surface 'small table*tables' 'small table'
    inlineContentsLister = scatteredSurfaceInlineContentsLister
;

+ largeTable: Surface 'large table*tables' 'small table'
    inlineContentsLister = scatteredSurfaceInlineContentsLister
;   
    
scatteredSurfaceInlineContentsLister: surfaceInlineContentsLister    
    showListPrefixWide(cnt, pov, parent)
    {
         " (scattered across which <<cnt == 1 ? tSel('is', 'was')
           : tSel('are', 'were')>> ";
    }   
;

The same method can be used to customize the way what's on the table is described when we EXAMINE or SEARCH the table. We just need to customize the appropriate Listers in the same way:

+ table: Surface 'table' 'table'
    inlineContentsLister: surfaceInlineContentsLister
    {
        showListPrefixWide(cnt, pov, parent)
        {
            " (scattered across which <<cnt == 1 ? tSel('is', 'was')
              : tSel('are', 'were')>> ";
        }
        
    }
    
    descContentsLister: surfaceDescContentsLister
    {
        showListPrefixWide(itemCount, pov, parent)
        {
            "Scattered across <<parent.theNameObj>>
            <<itemCount == 1 ? tSel('is', 'was')
              : tSel('are', 'were')>> ";
        }
        
    }    
    
    lookInLister: surfaceLookInContentsLister
    {
        showListPrefixWide(itemCount, pov, parent)
        {
            "Scattered across <<parent.theNameObj>>
            <<itemCount == 1 ? tSel('is', 'was')
              : tSel('are', 'were')>> ";
        }
        
    } 
;

The only thing that is new in principle here is the use of the parent parameter to supply the name of the parent object (the object being examined or searched) in the text introducing the list of its contents.

As yet, we've not seen an example of customizing the remaining Lister property, contentsLister. In our table example, this would most likely be useful if we gave the table a specialDesc or initSpecialDesc or else made it a NonPortable (e.g. a Heavy or CustomFixture). It that case the table is not listed among the miscellaneous items in the room, so its own contents would then appear in a separate list, not in a sublist; we'd then see something like this:

You see a chair, a bucket of water, a pen, and a piece of paper here. On the table are a saucer,
a cup, and a priceless antique vase.

Tto get the "scattered across the table" version, it's the contentsLister property we need to override:

+ table: Surface, Heavy 'table' 'table'
    specialDesc = "A table stands in the middle of the room. "
    contentsLister: surfaceContentsLister
    {
        showListPrefixWide(itemCount, pov, parent)
        {
            "Scattered across <<parent.theNameObj>>
            <<itemCount == 1 ? tSel('is', 'was')
              : tSel('are', 'were')>> ";
        }        
    }
;

Finally, we should note that we are not restricted to changing the text that introduces the list; we can also change the text that concludes it, which would allow us, for example, to turn the sentence round so that the table is mentioned at the end rather than the beginning. We could even use the lister to describe the presence of the table in the room, rather than (also) mentioning it in the room description or giving it a specialDesc. For example, instead of the example above, we could have written:

+ table: Surface, Heavy 'table' 'table'
    contentsLister: surfaceContentsLister
    {
        showListPrefixWide(itemCount, pov, parent)
        {
            "\^";            
        }
        showListSuffixWide(itemCount, pov, parent)
        {
            " <<itemCount == 1 ? tSel('is', 'was') : tSel('are', 'were')>>
            scattered across the table that stands in the middle of the room. ";
        }        
    }
    
    specialDesc = "A table stands in the middle of the room. "
    useSpecialDesc { return contents.length() == 0; }
;

Here we override useSpecialDesc so that the specialDesc is only used if there's nothing on the table. If there is something on the table, the table will be mentioned in connection with the list of objects resting on it, so rather than mentioning the table twice, here we incorporate the full description of the table's location ("in the middle of the room") into the list of objects scattered across the table.

One further point to note: so far all the methods we have been customizing have had names ending in "Wide" (such as showListPrefixWide()). These are the methods used for displaying lists that run horizontally across the screen (the default way of listing, used in all our examples so far). But in some contexts (e.g. where the player has issued a command like INVENTORY TALL) items may be listed in a vertical column down the screen; to customize the introductions and conclusions to lists in this vertical format you need to override the appropriate methods with names ending in "Tall", i.e. showListPrefixTall() and showListContentsPrefixTall(). There is no showListSuffixTall() method.

The examples used above have all applied to a Surface. The same principles would apply when listing the contents of a Container, an Underside, or a RearSurface/RearContainer. You'd override precisely the same Lister properties on the container-type object in question, but you'd to subclass a different lister object in each case. You can always look up which Lister property uses which Lister object on which container class in the Library Reference Manual, but you may find it more convenient to use the table below:

Lister Properties and Objects
Lister PropertyContainerSurfaceUnderside RearContainer
contentsLister thingContentsLister surfaceContentsLister undersideContentsLister rearContentsLister
descContentsLister thingDescContentsLister surfaceDescContentsLister undersideDescContentsLister rearDescContentsLister
inlineContentsLister inlineListingContentsLister surfaceInlineContentsLister undersideInlineContentsLister rearInlineContentsLister
lookInContentsLister thingLookInLister surfaceLookInLister undersideLookUnderLister rearLookBehindLister

Notes:

  1. RearSurface uses the same lister objects as RearContainer
  2. Container uses the same lister objects as Thing (from which it inherits its lister properties)
  3. In the main the lister objects are named in a fairly consistent pattern, but note inlineListingContentsLister not thingInlineContentsLister. Note also surfaceLookUnderLister and rearLookBehindLister
  4. In many cases, if all you want to do is to change the preposition a lister uses to describe its contents (e.g. "on top of" instead of "on" or "beneath" instead of "under"), you don't need to override the lister at all; instead you can simply override objInPrep on the object whose contents are to be listed. This should work for all listers in the table above (except inLineListingContentsLister, which uses "contains" rather than the preposition "in").
  5. Openable overrides descContentsLister to openableContentsLister, so that, for example, an OpenableContainer uses openableContentsLister rather than thingDescContentsLister.

This last point is particularly worth noting, since OpenableContainers are far from uncommon, and also because (which may be far from initially obvious), openableContentsLister also reports whether an Openable object is open or closed:

openableContentsLister: thingContentsLister
    showListEmpty(pov, parent)
    {
        "\^<<parent.openStatus>>. ";
    }
    showListPrefixWide(itemCount, pov, parent)
    {
        "\^<<parent.openStatus>>, and contain<<parent.verbEndingSEd>> ";
    }
;

So, for example, if you have an OpenableContainer which is reporting "It's closed" when the player examines it, this is where the message is coming from (and this is the place to change it if, for example, you only want the container to report when it's open, not when it's closed). It's also the place where you could add an explicit "It's empty" message to an empty, open container. e.g.:

+ box: OpenableContainer 'box' 'box'
    descContentsLister: openableContentsLister
    {
        showListEmpty(pov, parent)
        {
            if(parent.isOpen)
                "\^<<parent.openStatus>> but empty. ";            
        }
    }
;

Which could give you a transcript like:

>x box
You see nothing unusual about it. 

>open box
Opened. 

>x it
It’s open but empty. 

>put pen in box
(first taking the pen)
Done. 

>x box
It’s open, and contains a pen. 

>close box
Closed. 

>x it
You see nothing unusual about it. 


Other Lister Properties and Objects (including Room and Inventory Listings)

We have now covered the four standard lister properties found on all Things, but there are also some others that are specific to particular classes (and which deal with specific situations). These include:

Note that these are the names of the properties that point to Lister objects, not the lister objects themselves. For example, on an Openable the openingLister property points to the openableOpeningLister object.

Other commonly used Lister objects (some of which have just been mentioned above) include:

Room Contents Listings

A couple of examples should suffice to show how these might be useful. Suppose that instead of having a room description say "You see X, Y and Z here" you want it to say "X, Y and Z are lying around on the floor. ". This can be achieved by overriding the roomContentsLister property on the Room in question, for example:

westRoom: Room 'West Room'
    "This is the west room. An exit leads east. "
    east = eastRoom
 
    roomContentsLister: roomLister
    {
        /* show the prefix/suffix in wide mode */
        showListPrefixWide(itemCount, pov, parent) { "\^"; }
        showListSuffixWide(itemCount, pov, parent) 
        { 
            " <<itemCount > 1 ? tSel('are', 'were') : tSel('is', 'was')>>
            lying around on the floor. "; 
        }        
    }
;

In conjunction with the other tweaks we've already made above, this could produce something like:

West Room
This is the west room. An exit leads east. 

A chair, a bucket of water, a pen, a box, and a piece of paper are lying around on the floor. 
A saucer, a cup, and a priceless antique vase are scattered across the table that stands in 
the middle of the room. 

Inventory Listings

The standard form of an inventory listing (the listing of the player character's inventory shown in response to an INVENTORY command) typically looks something like this:

>i
You are carrying a walking stick, a map, and an iceberg lettuce, and you’re wearing a 
long overcoat and a pair of brown shoes.

You can change the format to show a single list, with worn items shown as "(being worn)" simply by overriding the inventoryLister property on the gPlayerChar object (typically me) to singleActorInventoryLister. For example this:

me: Actor
   location = westRoom   
   inventoryLister = actorSingleInventoryLister
;

Will give you this:

>i
You are carrying a long overcoat (being worn), a pair of brown shoes (being worn), a 
walking stick, a map, and an iceberg lettuce.

To customize further, e.g. to make an inventory lister that replaces "carrying" with "holding" and "wearing" with "sporting" requires rather more work, since the standard lister that divides the PC's possessions into what's being carried and what's being worn needs to define several methods to keep track of several possibilities. By copying and adapting the library's definition of actorInventoryLister one might come up with something like this:

me: Actor
    location = westRoom   
    inventoryLister: actorInventoryLister
    {
        showInventoryEmpty(parent)
        {
            /* empty inventory */
            "<<buildSynthParam('The/he', parent)>> {is} holding absolutely
            nothing. ";
        }
        showInventoryWearingOnly(parent, wearing)
        {
            /* we're carrying nothing but wearing some items */
            "<<buildSynthParam('The/he', parent)>> {is}n\'t holding anything,
            but {is} sporting <<wearing>>. ";
        }
        showInventoryCarryingOnly(parent, carrying)
        {
            /* we have only carried items to report */
            "<<buildSynthParam('The/he', parent)>> {is} holding <<carrying>>. ";
        }
        showInventoryShortLists(parent, carrying, wearing)
        {
            local nm = gSynthMessageParam(parent);
            
            /* short lists - combine carried and worn in a single sentence */
            "<<buildParam('The/he', nm)>> {is} holding <<carrying>>,
            and <<buildParam('it\'s', nm)>>{subj} sporting <<wearing>>. ";
        }
        showInventoryLongLists(parent, carrying, wearing)
        {
            local nm = gSynthMessageParam(parent);
            
            /* long lists - show carried and worn in separate sentences */
            "<<buildParam('The/he', nm)>> {is} holding <<carrying>>.
            <<buildParam('It\'s', nm)>> sporting <<wearing>>. ";
        }
        /*
         *   For 'tall' listings, we'll use the standard listing style, so we
         *   need to provide the framing messages for the tall-mode 
         *   listing.  
         */
        showListPrefixTall(itemCount, pov, parent)
            { "<<buildSynthParam('The/he', parent)>> {is} holding:"; }
        showListContentsPrefixTall(itemCount, pov, parent)
            { "<<buildSynthParam('A/he', parent)>>, who {is} holding:"; }           
    }    
;

This is probably slightly more complex than it needs to be. In the most common case where the player character remains the same throughout the game, the game remains in the present tense throughout, and is always told in the second person, a lot of the stuff that looks like <<buildParam('The/he', nm)>> {is} could be replaced with You are; but if you've copied and pasted this code from the library you may just as well leave it as it is. The effect is to give you a transcript like this:

>i
You are holding a walking stick, a map, and an iceberg lettuce, and you’re sporting a long
overcoat and a pair of brown shoes. 

>i tall
You are holding:
	a long overcoat (being worn)
	a pair of brown shoes (being worn)
	a walking stick
	a map
	an iceberg lettuce

>drop all
long overcoat:
(first taking off the long overcoat)
Dropped. 

pair of brown shoes:
(first taking off the pair of brown shoes)
Dropped. 

walking stick: Dropped. 
map: Dropped. 
iceberg lettuce: Dropped. 

>i wide
You are holding absolutely nothing. 

>wear shoes
(first taking the pair of brown shoes)
Okay, you’re now wearing the pair of brown shoes. 

>i
You aren't holding anything, but are sporting a pair of brown shoes. 

Changing the way the inventory of an NPC is listed is similar, except that one instead needs to override the holdingDescInventoryLister property of the NPC. For example, one might typically see something like this:

>x myrtle
You think she's very pretty. 

Myrtle is carrying a bunch of flowers, and she’s wearing a red dress. 

But here the description of Myrtle is so short, that you might want to combine it with the descripition of what she's carrying. This can be done by using actorHoldingDescInventoryListerShort instead:

myrtle: Person 'pretty myrtle/woman' 'Myrtle' @westRoom
    "You think she's very pretty. "
    holdingDescInventoryLister = actorHoldingDescInventoryListerShort
    isHer = true
    isProperName = true
;

You'll then get:

>x myrtle
You think she's very pretty. She is carrying a bunch of flowers, and she’s wearing a red dress.

For any more complex customization, you'll need to use the same kind of techniques as we've already seen exemplified for listing the player character's inventory, except that you'll be working with the NPC's holdingDescInventoryLister property instead, and subclassing from actorHoldingDescInventoryListerShort or actorHoldingDescInventoryListerLong.


Conclusion

There is more to lists and listers than we've covered here. We could go on to describe every single Lister method or to give lots of convoluted examples of listing complex groupings of objects in remote locations, but we have now covered the basics, and that should hopefully be enough to enable you to tweak the lists that appear in your own games in most of the ways that are likely to prove useful. If you need to go beyond what this article covers, then hopefully it will have covered enough ground to enable you to find what else you need to know from the Library Reference Manual. At the very least, it should have given you a better idea where to look.