Recipe/pattern for SenseConnector that's not "always on"?

Is there a straighforward design pattern/recipe for a window (or other SenseConnector) that only “transmits” when directly interacted with?

The basic behavior I want is to have stuff in another room visible when looking through a window, but only when looking through a window. The examples in the T3 documentation illustrate how to set up a window that doesn’t cause remote objects to be listed until it is looked through (via swapping the connectorMaterial in dobjFor(LookThrough), but that results in remote objects always showing up in the room contents after the window has been looked through once. Swapping the material back would prevent this behavior, but would prevent the remote objects from being directly examined, which is also undesirable.

Here’s sample code illustrating what I have, which is not quite what I want:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

startRoom:      Room 'Void'
        "This is a featureless void with a window. "
;

windowRoom:     Room 'Room With A View'
        "This is the room with a window. "
;
+ pebble: Thing 'small round pebble' 'pebble'
        "It's a small, round pebble. "
;

window: SenseConnector, Fixture 'window' 'window'
        connectorMaterial = adventium
        locationList = [ startRoom, windowRoom ]
        dobjFor(LookThrough) {
                verify() {}
                check() {}
                action() {
                        local otherLocation;

                        connectorMaterial = glass;
                        if(gActor.isIn(startRoom)) {
                                otherLocation = windowRoom;
                                "You look through the window at the other
                                        room. ";
                        } else {
                                otherLocation = startRoom;
                                "You look through the window at the start
                                        room. ";
                        }
                        gActor.location.listRemoteContents(otherLocation);
                }
        }
;

modify Thing
        listLocation_ = nil

        listRemoteContents(otherLocation) {
                listLocation_ = otherLocation;
                try {
                        lookAround(gActor, LookListSpecials | LookListPortables);
                }
                finally {
                        listLocation_ = nil;
                }
        }
        adjustLookAroundTable(tab, pov, actor) {
                inherited(tab, pov, actor);
                if(listLocation_ != nil) {
                        local lst = tab.keysToList();
                        lst.forEach(function(cur) {
                                if(!cur.isIn(listLocation_))
                                        tab.removeElement(cur);
                        });
                }
        }
;

me:     Person
        location = startRoom
;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

This yields:

Void
This is a featureless void with a window.

>x pebble
You see no pebble here.

>l through window
You look through the window at the other room.

In the room with a view, you see a pebble.

>x pebble
It's a small, round pebble.

>l
Void
This is a featureless void with a window.

In the room with a view, you see a pebble.

And this is mostly what I want, except I would like for the pebble to not show up in the description of startRoom even after the window has been looked through.

This is certainly approachable as kludge; you can always just implement a bunch of fake scenery objects that shadow the appearance of, like:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

startRoom:      Room 'Void'
        "This is a featureless void with a window. "
;
+ window: Fixture 'window' 'window'
        "This is a window that's not a SenseConnector. "
        dobjFor(LookThrough) {
                verify() {}
                check() {}
                action() {
                        "You look through the window at the other
                                room.
                        <.reveal window>
                        <<if (pebble.location == windowRoom)>>
                                Sitting on the ground in the other room
                                you notice a pebble.
                        <<end>> ";
                }
        }
;
+ fakePebble: Fixture 'small round pebble' 'pebble'
        desc() {
                if(gRevealed('window')) {
                        pebble.desc();  
                } else {
                        "You don't see any pebble here. ";
                }
        }
;

windowRoom:     Room 'Room With A View'
        "This is the room with a window. "
;
+ pebble: Thing 'small round pebble' 'pebble'
        "It's a small, round pebble. "
;

me:     Person
        location = startRoom
;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

Which does what I want:

Void
This is a featureless void with a window.

>x pebble
You don't see any pebble here.

>l through window
You look through the window at the other room.  Sitting on the ground in the
other room you notice a pebble.

>x pebble
It's a small, round pebble.

>l
Void
This is a featureless void with a window.

This actually works out to be more straightforward for this simple example, but rapidly gets out of hand as the number of objects that have to be “duplicated” grows.

I did a window earlier… I’ll check my code soon to see if it does what you’re talking about or not…

I didn’t do extensive checking to see if this does what you’re looking for, but you could try it…
Note, if an NPC is on one side or the other, you may not want their takeTurn() messages to show if you’re not in the same room with them. You’ll have to tweak either the class or the particular actor for that behavior…

  class DynamicWindow: SenseConnector, Fixture
	//* typically set remoteRoomContentsLister to return new WindowLister(pref,suff,windowObj,remoteRoom)
	//* and specialContentsLister = static new WindowSDLister(windowObj) for rooms on both sides of window
	//locationList = [] [1] should be inside of window
	connectorMaterial = glass
	insideDesc = ""
	outsideDesc = ""
  	lookThroughDesc1 = ""
  	lookThroughDesc2 = ""
	desc { gac.isIn(locationList[1]) ? insideDesc : outsideDesc; }
	dF(LookThrough) {Ac gac.isIn(locationList[1]) ? lookThroughDesc1() : lookThroughDesc2(); 
		listingOn = true; 
		local save = connectorMaterial;
		try { connectorMaterial = glass; goR.listRemoteCts(locationList.valW({x:!gac.isIn(x)}),self); }
		finally { listingOn = nil; connectorMaterial = save; } } }	
   	listingOn = nil
;

  class WindowLister: CustomRoomLister, RemoteRoomLister
	construct(prefix,suffix,win,room?) { 
		prefixStr = prefix; suffixStr = suffix; window = win; remoteRoom = room; }
	window = nil
    showList(a,b,c,d,e,f,g) { if(!window.listingOn) return; else inherited(a,b,c,d,e,f,g); }
    ;
    
  class WindowSDLister: specialDescLister
  	construct(win) { window = win; }
  	window = nil
  	isListed(obj) { return gpc.getOutermostRoom==obj.getOutermostRoom || window.listingOn; } 
;

outsideRoom: OutdoorRoom
	desc = "You can see an uncurtained window on the front of the building. "
	remoteRoomContentsLister(other) { return new WindowLister('Inside the building you can see ',' through the window. ',myWindow,other); }
	specialContentsLister = static new WindowSDLister(myWindow)
;

insideRoom: Room	
	desc = "A window looks outside. "
	remoteRoomContentsLister(other) { return new WindowLister('Outside you can see ',' through the window. ',myWindow,other); }
	specialContentsLister = static new WindowSDLister(myWindow)
;



myWindow: DynamicWindow  'window' 'window'
	locationList = [insideRoom,outsideRoom] //inside first, for descs to be right
	connectorMaterial = glass
	insideDesc = "This is what the window looks like from in the room. "
	outsideDesc = "This is what the window looks like from outside. "
	lookThroughDesc1 = "This is a general description of the view looking out the window. Listable objects will appear afterwards if present. "
	lookThroughDesc2 = " " " looking in the building through the window. " " " "
;
1 Like

Sorry, I used some macros in there.
gac = gActor
gpc = gPlayerChar
valW = valWhich
goR = gPlayerChar.getOutermostRoom

If you don’t want listable/portable objects to be in scope when you’re not on the same side of the window, just set the Window object material to adventium. Then it will list the objects only on explicit LookThrough…
As it is, I believe objects on both sides are in scope but aren’t looked in room desc unless it’s the same room as you…

Thanks. I’m not sure if that’s what I’m looking for, but I think I’ve got a simpler kinda-sorta solution:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

class NonRemoteRoom: Room
        adjustLookAroundTable(tab, pov, actor) {
                local lst;

                inherited(tab, pov, actor);
                if(listLocation_ != nil)
                        return;

                lst = tab.keysToList();
                lst.forEach(function(cur) {
                        if(!cur.isIn(actor.location))
                                tab.removeElement(cur);
                });
        }
;

startRoom:      NonRemoteRoom 'Void'
        "This is a featureless void with a window. "
;

windowRoom:     Room 'Room With A View'
        "This is the room with a window. "
;
+ pebble: Thing 'small round pebble' 'pebble'
        "It's a small, round pebble. "
;

window: SenseConnector, Fixture 'window' 'window'
        connectorMaterial = adventium
        locationList = [ startRoom, windowRoom ]
        dobjFor(LookThrough) {
                verify() {}
                check() {}
                action() {
                        local otherLocation;

                        connectorMaterial = glass;
                        if(gActor.isIn(startRoom)) {
                                otherLocation = windowRoom;
                                "You look through the window at the other
                                        room. ";
                        } else {
                                otherLocation = startRoom;
                                "You look through the window at the start
                                        room. ";
                        }
                        gActor.location.listRemoteContents(otherLocation);
                }
        }
;

modify Thing
        listLocation_ = nil

        listRemoteContents(otherLocation) {
                listLocation_ = otherLocation;
                try {
                        lookAround(gActor, LookListSpecials | LookListPortables);
                }
                finally {
                        listLocation_ = nil;
                }
        }
        adjustLookAroundTable(tab, pov, actor) {
                local lst;

                inherited(tab, pov, actor);
                if(listLocation_ != nil) {
                        lst = tab.keysToList();
                        lst.forEach(function(cur) {
                                if(!cur.isIn(listLocation_))
                                        tab.removeElement(cur);
                        });
                }
        }
;

me:     Person
        location = startRoom
;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

In the example code I posted, the changes to the Thing class are taken more or less verbatim from the examples in Getting Started in TADS 3. It, among other things, tweaks adjustLookAroundTable() on Thing to only list objects in the remote location when you’re looking through the window.

In the revised example above, it implements a NonRemoteRoom class that has its own adjustLookAroundTable() method, and it excludes objects that aren’t in the point of view actor’s location (except in the case when we’re looking through a window at them).

This is a little brittle in that it will fail if the NonRemoteRoom is a part of a nested room or something like that, but I think it is probably good enough for what I’m trying to do.

It produces the desired behavior, at least in my somewhat limited testing:

Void
This is a featureless void with a window.

>x pebble
You see no pebble here.

>l through window
You look through the window at the other room.

In the room with a view, you see a pebble.

>x pebble
It's a small, round pebble.

>l
Void
This is a featureless void with a window.

The DynamicWindow class could exclude otherRoom objects from being in scope by starting as adventium. Then it could be permanently changed to glass on the first LookThrough. Were there other ways that it doesn’t match what you’re trying for? I forgot to say that the Getting Started tweaks to adjustLookAroundTable etc. are also included in my code (which is where the call to listRemoteCts comes in)…

I’m not sure if I’m misunderstanding you, but changing the window material from adventium to glass when the window is first looked through is how the example in the first post works. It doesn’t do what I want because when the SenseConnector is glass, then remote objects are listed in the room description. The material can be changed back to occlude sight, but then that prevents remote objects from being examined at all, which I also don’t want.

Did you compile my code, by chance? It doesn’t list otherRoom objects in your own room description, only on LookThroughAction. They are, however, in scope if your Window object is set to glass (not just for LookThrough).
It’s the WindowLister that controls it… I’m not sure if I’m misunderstanding you too…

No, sorry. I looked through it but I didn’t try compiling it because of all the macros–in addition to the ones you mentioned there’s also dF which I assume is dobjFor and Ac which appears to be action.

But I understand the idea of implementing a CustomRoomLister. That makes sense as a potential solution, but it looks more complicated than another modification to adjustLookAroundTable(). Is there something I’m missing?

Basically whenever I can implement something with fewer moving parts I generally prefer that solution, in the assumption that a couple weeks from now I’m going to forget about half of it and have to re-learn it when I need to touch it again.

Nope, I just thought you had tried the code and it wasn’t working for you. If your solution works for you, great!
Does your NonRemoteRoom approach allow you to customize the lister text for what you see through WindowConnectors? Or is the default adequate?

well, it’s reasonable that thing like gPlayerChar.getOutermostRoom (copypasted, of course) sorely needs a shorthand macro, so your choice is fully understandable.

of course, if you release sources, please put the macros on the top of the source if monolithic or in an easily identifiable file, e.g. macros.t if divided in multiple files.

OTOH, after the appropriate find&replace, your code works as expected.

Best regards from Italy,
dott. Piergiorgio.

I understand about the macros, I was just in a hurry and didn’t thoroughly look back over the code I pasted…

It certainly could, but the example code I posted doesn’t. At this stage I’m really more concerned about the general shape of the SenseConnector problem.

Essentially the thing that bugs me (about any of the three potential solutions discussed so far) is that there doesn’t appear to be any way to encapsulate all of the desired behaviors in SenseConnector (or a derived class) itself. The NonRemoteRoom idea I posted adjusts the behavior of the SenseConnector by changing the class of the room that contains it. Your DynamicWindow solution creates two new classes of Lister that then have to be added to each Room instance that contains the SenseConnector. And the third solution is to treat everything as a special case and not use SenseConnector at all.

It would be nice if this could all be packed into something like a PassiveSenseConnector class or something, so that you could just declare a window (or whatever) to be an instance of that, and all the other stuff would just take care of itself.

Or at least I think that if I just implemented any of these solutions today and then moved on to work on the rest of the game, in a couple weeks I’ll have forgotten about all the individual pieces that need to be touched in order to make it all work.

Without having done any experimenting, I wonder if the standard remote room listers could be modified to behave appropriately when a window is present. Then you wouldn’t manually have to add listers to rooms containing windows…