Player showInventory doesn't show in banner window

using TADS adv3 with banners splitting my screen.I want to send the inventory objects to a banner window. Instead the text “Inventory” shows in the banner window but the inventory lister shows details in the main window. See code.

   invWindow.writeToBanner('Inventory: <<me.showInventory(ListTall)>>');

Should be simple, but I’m not understanding why writeToBanner isn’t working.

I think the problem you’re running into is that showInventory() itself writes directly to the main window (via, several layers deep, calls to say()).

I think what you probably want here is to use showInventoryWith() and a custom lister. But I did a quick search and that turned up a couple people reporting similar issues, but no solutions. So I don’t know if there’s a simpler solution than rolling your own custom lister.

As jbg says, the inventory lister calls say() in the end, which is defined as:

say(val)
{
    /* send output to the active output stream */
    outputManager.curOutputStream.writeToStream(val);
}

Whereas writeToBanner() is:

writeToBanner(txt)
{
    /* write the text to our underlying output stream */
    outputStream_.writeToStream(txt);
}

I tried setting outputManager.curOutputStream to invWindow.outputStream_ just before the inventory output, and then setting it back to the main window again (one could probably also achieve the same with outputManager.withOutputStream()):

+ me: Actor
    showPlayersInventory()
    {   
        invWindow.writeToBanner('Inventory: ');
        // setOutputStream returns the previously active output stream,
        // so we store the main window's stream here:
        local mainStream = outputManager.setOutputStream(invWindow.outputStream_);
        me.showInventory(true);
        // restore output to main window:
        outputManager.setOutputStream(mainStream);
    }
;

I don’t know if this is a reliable and robust solution, but the result looks okay at first glance:

1 Like

I think I love this, although it does seem like a bit of a tablecloth trick (in that it gives me a vaguely queasy feeling that if something goes wrong it will go very, very wrong). It’s definitely more concise than rolling your own Lister class/instance, though.

Output handling is one of those bits of T3 that I constantly feel like I must be missing something, like there’s a secret library of convenience methods that I just don’t know about or something like that.

1 Like

I have been using an inventory window in my WIP for the last 3 years. I don’t know if my solution is kludge-minimal, but I can confirm that it works.
-I do in fact use a custom lister, because I frequently end up wanting to name objects even more tersely in the window than their regular theName, and because I use my own indentation to represent containment levels. And because it’s a tall listing, and people that type ‘i’ may want to see the wide listing.
-I use captureOutput, and then write the captured string to writeToBanner.
-I run a promptDaemon to update the inventory window. To minimize lag or flicker, I set flags to notify the inventory window if an object has moved into or out of the pc’s allContents, so the inventory only redraws when there is a change.
-I also have a command that allows the player to toggle the invwin off if they want.

If you’re interested in the details, I’d be happy to dig up the relevant code.

1 Like

Probably nitpicky, but it’s a good idea to make sure the game doesn’t get stuck in a weird state where everything is sent to the banner in case an unhandled exception occurs. So you might want:

showPlayersInventory()
{   
    invWindow.writeToBanner('Inventory: ');
    local prevStream = outputManager.setOutputStream(invWindow.outputStream_);
    try {
        me.showInventory(true);
    }
    finally {
        outputManager.setOutputStream(prevStream);
    }
}
1 Like

Hopefully someone can find this useful. Written in adv3 but could easily be adapted to Lite…


enum leftSide, rightSide ;

    /* NOTE: if you are using border banners, or any other banners that subdivide the window sooner than the inventory window, you will have to make the appropriate changes here, to determine which banner this should be shown after */

inventoryWindow: BannerWindow
    alignTo = leftSide
        /* it would be nice to be able to use sizeToContents(), but then items with a space in their names are likely to be broken into two rows */
    size = 23 /* user's preference: I tended towards narrow, and used terse inventory names */
    show() { 
        showBanner(nil, BannerAfter, statuslineBanner, BannerTypeText, 	
                alignTo==leftSide ? BannerAlignLeft : BannerAlignRight, size, BannerSizeAbsolute, BannerStyleVScroll);
        invNote(); 
        }
   initBannerWindow() {
     	if(inited_)  return;
     	statuslineBanner.initBannerWindow();
      	inherited();  
        } 
;

inventoryWindowDaemon: PromptDaemon, InitObject
    execute() { construct(self, &beforePrompt); }
    beforePrompt() { 
        //if(any special conds such as being in an intro screen) return;
        if(invChanged) { 
            local save = InventoryAction.inventoryMode;
            try { 
                gPlayerChar.usingInvWin = true;
                InventoryAction.inventoryMode = InventoryTall;
                    /*I forced a typewriter font in the invwin so that indenting to show containment will look consistent and balanced. */
                local str = '<tt>' + mainOutputStream.captureOutput({:gPlayerChar.showInventory(true)}) + '</tt>';  
                inventoryWindow.clearWindow();
                inventoryWindow.writeToBanner(str);
                } 
            finally { 
                InventoryAction.inventoryMode = save;
                invChanged = nil;
                gPlayerChar.usingInvWin = nil; } } }
	invChanged = nil	
;
	
function invNote() { inventoryWindowDaemon.invChanged = true; }

modify me   /* or whatever your PC is named */
    usingInvWin = nil
	inventoryLister { if(usingInvWin) return invWinLister; else return stdInvLister; }
	stdInvLister: actorSingleInventoryLister { /* */}
	invWinLister: actorSingleInventoryLister {
		contentsListedSeparately(obj) { return nil; }
		showListIndent(options, indent)
			{ if ((options & ListTall) != 0)
				{ for (local i = 0 ; i + 1 < indent ; ++i) "\ \ "; } }
		showListContentsPrefixTall(itemCount, pov, parent)
        		{ "<<parent.listName>>"; }
		showContentsList(pov, obj, options, indent, infoTab) 		
			{ obj.showInventoryContents(pov, gPlayerChar.invWinLister, options, indent, infoTab); }  
				//* using invWinLister instead of ctsLister ^ ^
		showInlineContentsList(pov, obj, options, indent, infoTab) 	
			{ obj.showInventoryContents(pov, gPlayerChar.invWinLister, options, indent, infoTab); } 
            /* an example of sticking something at the top of the list: perhaps a bag of holding */
		showArrangedList(a,parent,lst,c,indent,e,f,g,h,i,j) {
			local ix = lst.indexOf(burlap); 
			if(ix) { 
                lst = lst.removeElementAt(ix); 
                lst = lst.insertAt(1,burlap); 
                }
			inherited(a,parent,lst,c,indent,e,f,g,h,i,j); 
            }
            /* ComplexContainers in inventory look wonky without special treatment. A simplistic approach is to tell the ComplexComponent subLocations not to list their contents, because we will modify the ComplexContainer to gather all of its subLocations' contents in one list */
		contentsListed(obj) { return obj.ctsListed && !obj.oK(ComplexComponent); }
;

modify Thing 
    listName = (gPlayerChar.usingInvWin ? invWinName : inherited)
    invWinName = name
       /* here we alert the inventory window to refresh its contents */
	baseMoveInto(newContainer) { 
		if(location && location.isOrIsIn(gPlayerChar) || newContainer && newContainer.isOrIsIn(gPlayerChar)) 
			invNote(); 
		inherited(newContainer); }	
;
modify ComplexContainer
    getListedContents(lister, infoTab) {
        if(lister==gPlayerChar.invWinLister) { 
            local vec = new Vector(10);
            foreach(local cont in allSubLocations) { 
                if(cont) {
                    foreach(local cur in cont.contents)  vec.append(cur); 
                    } 
                }
            return vec.subset({x: lister.isListed(x)}); 
            }
        else return getContentsForExamine(lister, infoTab).subset({x: lister.isListed(x)}); 
        } 
;