custom list display not working (adv3Lite)

The adv3Lite Manual (Messages > Lists and {prev}) says…

It’s not working for me.

In my game, there is an ATM built into the exterior wall of the apartment building where Harry lives. The desc text for the street corner contains the text An ATM was built into an exterior wall of the apartment building.

The game engine then adds Harry could see an ATM there.

This is redundant. I prefer my text, so I have modified the lookAroundWithin() method of Thing to suppress the default display of room contents for the street corner…

            // don't use system-generated contents list
            if(harry.location != streetCorner)
            {
                /* List our contents. */
                "<.roomcontents>";
                listContents();
                "<./roomcontents>";
            }

So far, so good. Now the game displays only my text.

But this has the unfortunate side effect of not listing things that Harry may have dropped on the street corner. If he drops his cell phone, for example, and then comes back later to find it, it will not be listed when he looks at the street corner.

To get around this, I am trying to use a custom list of listable contents on the street corner that still excludes the ATM…

streetCorner: Room 'Street Corner' 'street
    corner in front of apt'
    "Harry lived in an apartment above the corner of O'Farrell and 
    Leavenworth. \b
    An ATM was built into an exterior wall of the apartment building.
    <.p>
    <<getRoomContents>><<if customRoomContents.length > 0>>There <<list of
      customRoomContents>> lying on the sidewalk.
    <.p>"
    customRoomContents = []
    
    getRoomContents()
    {
        local lst = [];
        foreach(local obj in listableContents)
        {
            if(obj != atm)
                lst += obj;
        }
        if(lst.length > 0)
            customRoomContents = makeListStr(lst);
    }

When I run this code in my game, after the list is built but just before it gets displayed I get a property cannot be set for object error, and the debugger shows the offending line to be line 3149 of english.t, the obj.mentioned = true line in the following excerpt…

makeListStr(objList, nameProp = &aName, conjunction = 'and')
{
    local lst = [];
    local i = 0;
    local obj;
    objList = valToList(objList);
    
    /* 
     *   Sort the list by listOrder, but only if the items it contains provide
     *   the property, and only if they use it to define an order. If all the
     *   items in the list have the same sortOrder, we don't want to shuffle
     *   them out of their original order by performing an unnecessary sort.
     */       
    if(objList.length > 0 && objList[1].propDefined(&listOrder) &&
       objList.indexWhich({x: x.listOrder != objList[1].listOrder}))
        objList = objList.sort(SortAsc, {a, b: a.listOrder - b.listOrder});
    
    /* Go through every item in our sorted list */
    for(i = 1, obj in objList ; ; ++i)
    {
        /* Mark it as having been mentioned in a list */
        obj.mentioned = true;        

Here’s a test-bed isolation of what I’m trying to do…

#charset "us-ascii"

#include <tads.h>
#include "advlite.h"

versionInfo: GameID
    IFID = '445C38A3-AD1B-4729-957A-F584600DE5C1'
    name = 'test'
    byline = 'by Jerry Ford'
    htmlByline = 'by <a href="mailto:jerry.o.ford@gmail.com">
                  Jerry Ford</a>'
    version = '1'
    authorEmail = 'Jerry Ford <jerry.o.ford@gmail.com>'
    desc = 'Testing custom room contents list.'
    htmlDesc = 'Testing custom room contents list.'

;

gameMain: GameMainDef
    /* the initial player character is 'harry' */
    initialPlayerChar = harry
    paraBrksBtwnSubcontents = nil
    usePastTense = true
    
;

// harry, main character
harry: Actor 'Harry;;man self;him' @streetCorner
    "Harry. <.p>"
    globalParamName = 'harry'
    person = 3   

    day = 1
;
+ cellPhone: Thing 'cell phone;;telephone phone cell mobile'
    "The cell phone was an older model, no data plan, just basic telephone
    service, and, of course, a camera. <.p>"
;

streetCorner: Room 'Street Corner' 'street
    corner in front of apt'
    "Harry lived in an apartment above the corner of O'Farrell and 
    Leavenworth. \b
    An ATM was built into an exterior wall of the apartment building. <.p>
    <<getRoomContents>><<if customRoomContents.length > 0>>There <<list of
      customRoomContents>> lying on the sidewalk.
    <.p>"
    customRoomContents = []
    
    getRoomContents()
    {
        local lst = [];
        foreach(local obj in listableContents)
        {
            if(obj != atm)
                lst += obj;
        }
        if(lst.length > 0)
            customRoomContents = makeListStr(lst);
    }
;
+ atm: Container, Immovable 'an ATM;automated teller cash money;machine ATM '
    "An automated teller machine. "
; 

modify Thing
    lookAroundWithin()
    {
        unmention(contents);
        unmentionRemoteContents();
        "<.roomname><<roomHeadline(gPlayerChar)>><./roomname>\n";
        if(isIlluminated)
        {
            "<.roomdesc><<interiorDesc>><./roomdesc><.p>";
            
            // don't use system-generated contents list
            if(harry.location != streetCorner)
            {
                // List our contents. 
                "<.roomcontents>";
                listContents();
                "<./roomcontents>";
            }
            seen = true;
            visited = true;
        }
        else
        {
            "<.roomdesc><<darkDesc>><./roomdesc>";
            if(recognizableInDark)
            {
                visited = true;
                setKnown();
            }
        
        }
        "<.p>";
        if(gExitLister != nil)
            gExitLister.lookAroundShowExits(gActor, self, isIlluminated);
    }
;

I looks to me like the custome string code is trying to set an object property on the string that was created to represent the object. In the code above, obj has a value of ‘a cell phone’ rather than cellPhone when I examine its value in the debugger.

Jerry

This wouldn’t happen if you defined isFixed = true on the ATM (or isListed = nil), which would happen automatically if you made it a Fixture rather than Immovable (there’s no need to define it as Immovable here). Then you wouldn’t need your modification to Thing or your custom code on streetCorner and you could just write:

#charset "us-ascii"

#include <tads.h>
#include "advlite.h"

versionInfo: GameID
    IFID = '445C38A3-AD1B-4729-957A-F584600DE5C1'
    name = 'test'
    byline = 'by Jerry Ford'
    htmlByline = 'by <a href="mailto:jerry.o.ford@gmail.com">
                  Jerry Ford</a>'
    version = '1'
    authorEmail = 'Jerry Ford <jerry.o.ford@gmail.com>'
    desc = 'Testing custom room contents list.'
    htmlDesc = 'Testing custom room contents list.'

;

gameMain: GameMainDef
    /* the initial player character is 'harry' */
    initialPlayerChar = harry
    paraBrksBtwnSubcontents = nil
    usePastTense = true
   
;

// harry, main character
harry: Actor 'Harry;;man self;him' @streetCorner
    "Harry. <.p>"
    globalParamName = 'harry'
    person = 3   

    day = 1
;
+ cellPhone: Thing 'cell phone;;telephone phone cell mobile'
    "The cell phone was an older model, no data plan, just basic telephone
    service, and, of course, a camera. <.p>"
;

streetCorner: Room 'Street Corner' 'street
    corner in front of apt'
    "Harry lived in an apartment above the corner of O'Farrell and
    Leavenworth. \b
    An ATM was built into an exterior wall of the apartment building. "  
;

+ atm: Container, Fixture 'an ATM;automated teller cash money;machine ATM '
    "An automated teller machine. "
;

All the rest is just unnecessary complication.

The Immovable class is only for things that aren’t obviously fixed in place (like a canon ball that looks like it could be picked up but turns out to be too heavy). For something that’s obviously fixed in place, such as at ATM, you should always use the fixture class.

But, just for the sake of completeness, here’s a quick and dirty demonstration of how makeListStr() works; change the definition of the ATM object as follows and see what you get:

+ atm: Container, Fixture 'an ATM;automated teller cash money;machine ATM '
    "An automated teller machine. "
    
    specialDesc = "Now, here's a list of everything that was in the room:
        <<makeListStr(location.allContents, &aName, 'as well as')>>. "
;

The problem with your code is that you first use your getRoomContents method to construct a string version of list of objects, and then you then try to pass that string to to a function that expects a list of objects, so you’re almost bound to get a runtime error. The offending code is:

<<getRoomContents>><<if customRoomContents.length > 0>>There <<list of
      customRoomContents>> lying on the sidewalk.

When getRoomContents executes, it populates customRoomContents with a single quoted string, since you’ve defined:

getRoomContents()
    {
        local lst = [];
        foreach(local obj in listableContents)
        {
            if(obj != atm)
                lst += obj;
        }
        if(lst.length > 0)
            customRoomContents = makeListStr(lst);
    }

And the purpose of makeListStr() is to return a single-quoted string (representing the list passed to it via the lst parameter).

You then go on to call:

<<list of customRoomContents>>

Which is just syntactic sugar for:

<<makeListInStr(customRoomContents)>>

The library defines this function as:

makeListInStr(objList)
{   
     return makeListStr(objList);    
}

So what you are doing is effectively calling:

makeListStr(customRoomContents);

When customRoomContents is a string, not a list.

The fourth statement of makeListStr then converts this string into a one-element list of strings:

objList = valToList(objList);

So, when you get to the statement which generates the run-time error:

obj.mentioned = true;

obj is the single-quoted string ‘a cell phone’, which can’t possibly have a mentioned property (or any other property), hence the run-time error. But this is down to the fact that you’ve written code that turns a list of objects into a string, and then passes that string to a function that expects a list of objects.

The moral of the story is probably not to try to implement a terribly complex solution to an extremely simple problem" :slight_smile:

Sometimes I just need to be smacked soundly with the clue bat. :slight_smile:

I wrote the ATM machine some time back, when I was still feeling my way around TADS 3, let alone adv3Lite. Immovable sounded like a good idea at the time, but I eventually did discover the virtues of Fixture and started using it elsewhere.

I just never updated the ATM.

Thanks.

Jerry