Open Door Patterns

I am interested in implementing ‘look through open door’ capability, and I have the sense I am doing more than needed to make it happen. The solution I worked touches four objects, which seems kind of spread out for TADS? Also unattractive to implement over and over again. Figured I would check with the board if there is an easier, more contained pattern out there. I am entertaining creating a new “Door” class that incorporates the adjacent room remoteDesc, and SenseConnector (though it does feel wrong to remove remoteDesc’s from their actual object). Here is the solution I would digest:

mainRoom : Room 'Main Room' 'main room'
     "The main room here.  "
     north = doorMainOutside
     remoteDesc(pov) {       // needed for every open-door connected room
        "The main room you recently left.  ";
    }
;
+ doorMainOutside : Door 'main north northern n door/doorway' 'main door'
     "The only door out of the main room.  "
     //  Remapping "look through", requires 'doorway' be in vocab
     //
     dobjFor(LookThrough)
          remapTo(Examine, (gPlayerChar.isIn(mainRoom) ? adjacentRoom : mainRoom))
;
adjacentRoom : Room 'Adjacent Room' 'adjacent room'
     "A room adjacent to the main one.  "
     south = doorMainInside
     remoteDesc(pov) {     // yup, here also
          "Another room next to this one.  ";
     }
;  
+ doorMainInside : Door ->doorMainOutside 'main south southern s door/doorway' 'main doorway'
     "The only door back to the main room.  "
;
//  SenseConnectors needed for every doorway
//
SenseConnector, Intangible 'doorway' 'doorway'
    locationList = [mainRoom, adjacentRoom]
    connectorMaterial = (doorMainOutside.isOpen ? fineMesh : adventium)
;

I feel like I saw something similar in an extension… maybe ConSpace? Someone else might know offhand…

1 Like

If you’re using Adv3Lite, my SpringThing game actually has this implemented as a major gameplay feature. Also works for looking through windows.

It’s not simple, but there is a way to modify classes enough to standardize it, and make it much easier to work with.

EDIT: Unfortunately, as I posted on a previous topic somewhere, SpringThing has basically consumed my life, and March is a major final sprint to the finish line. I can write an Adv3Lite extension for you to implement this, but it will have to be after SpringThing.

2 Likes

Thanks, but have not yet moved to adv3lite. Next game! And certainly, focus on your baby!

1 Like

I have some code that does something similar, only I was concerned with things viewable through a window instead of a door. Simple illustration:

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

class RoomWithAView: Room
        adjustLookAroundTable(tab, pov, actor) {
                inherited(tab, pov, actor);
                if(_roomToBeViewed != nil)
                        return;

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

class RoomWithAViewWindow: SenseConnector, Fixture
        connectorMaterial = adventium
        dobjFor(LookThrough) {
                verify() {}
                check() {}
                action() {
                        local otherLocation;

                        connectorMaterial = glass;
                        if(gActor.isIn(locationList[1]))
                                otherLocation = locationList[2];
                        else
                                otherLocation = locationList[1];

                        gActor.location.listRemoteContents(otherLocation);
                }
        }
;

modify Thing
        _roomToBeViewed = nil

        listRemoteContents(otherLocation) {
                _roomToBeViewed = otherLocation;
                try {
                        lookAround(gActor,
                                LookListSpecials | LookListPortables);
                }
                finally {
                        _roomToBeViewed = nil;
                }
        }

        adjustLookAroundTable(tab, pov, actor) {
                inherited(tab, pov, actor);
                if(_roomToBeViewed != nil) {
                        tab.keysToList().forEach(function(o) {
                                if(!o.isIn(_roomToBeViewed))
                                        tab.removeElement(o);
                        });
                }
        }
                
;

startRoom: RoomWithAView 'Void'
        "This is a featureless void with a window. "
        north = windowRoom
;
+me: Person;

windowRoom: Room 'A Viewable Room'
        "This is a room viewable through the window. "
        south = startRoom
;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. ";

window: RoomWithAViewWindow 'window' 'window'
        locationList = static [ startRoom, windowRoom ]
;


versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;

This provides a class for Room instances you can view other rooms from, and a SenseConnector class that handles the connection. The connector starts out opaque, but becomes transparent when looked through. The rest of the logic is twiddling sense tables.

Transcript:

Void
This is a featureless void with a window.

>x pebble
You see no pebble here.

>l through window
In the a viewable room, you see a pebble.

>x pebble
A small, round pebble.

The disadvantages to this approach (that I know of) are:

  • It requires you to define the Room in a particular way, which seems counterintuitive—I’d rather if everything could be encapsulated in the connector class
  • It doesn’t work with any complicated room nesting situations: everything relies on testing object/actor locations via isIn(), which is brittle if you’re dealing with complicated containment rules
  • Examining a remote object ends up working exactly like examining an object in the same location once the sense connector is transparent. This appears to be a limitation in adv3 that is nontrivial to work around—the library doesn’t provide any way of signalling to an object what sense path it’s being examined (or otherwise sensed) through. So there’s no straightforward way of responding to >X PEBBLE with a connector-specific description. The closest thing you can do is use a generic remoteDesc, which might work if you have exactly one remote vantage point the object can be viewed from, but otherwise it ends up having to be a bunch of conditional spaghetti

On the last bit, I also have a bunch of code for handling examining remote objects where you can specify what description to use based on vantage point, but the code is a lot more unwieldy (it has to contain big chunks of cut and pasted adv3 code where there’s just one or two lines modified to break out how remote descriptions are displayed). Let me know if you’re interested and I can clean up what I have for posting.

3 Likes

Quick update - ConSpace was in fact the extension that had an implementation. Am deep in the weeds playing with that and @jbg implementation above. Trying to see if I can come up with a concisely usable solution and already am seeing complexity/usability tradeoffs that might steer me away from a more general solution. Will report back once I settle out. @inventor200 wouldn’t mind getting a look at your code snippets for inspiration but certainly on your time.

2 Likes

I can probably send you something that is fairly reduced but I cannot guarantee it will be compatible with Adv3, and I cannot guarantee it will compile as-is. But I can at least gut anything irrelevant, and also consolidate anything important. I’ve got a lot of systems that are interacting with each other, so isolating it might create unexpected bugs. But if you’re specifically looking for inspiration or a starting point, then that would probably be all that you need.

A properly-working version I could get tested and compiling after SpringThing, if you’d rather see it with a demonstration, too.

1 Like

I went ahead and stuffed the slightly kludgier-but-easier-to-use version of what I posted above into a module here.

An example:

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

#include "remoteView.h"

startRoom: Room 'Void'
        "This is a featureless void with a window.  The other room is
                to the north. "
        north = otherRoom
;
+me: Person;
+rock: Thing 'ordinary rock' 'rock' "An ordinary rock. ";

window: RemoteViewConnector 'window' 'window'
        locationList = static [ startRoom, otherRoom ]
        oneWay = true
        oneWayFailure = 'This side of the window is tinted, so you
                can\'t see through it from here. '
;

otherRoom: Room 'Other Room'
        "This is the other room.  The void lies to the south. "
        south = startRoom
;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. ";
++RemoteView ->window 'It looks like a pebble seen through a window. ';

versionInfo:    GameID;
gameMain:       GameMainDef initialPlayerChar = me;

Transcript:

Void
This is a featureless void with a window.  The other room is to the north.

You see a rock here.

>x rock
An ordinary rock.

>x pebble
You see no pebble here.

>l through window
In the other room, you see a pebble.

>x pebble
It looks like a pebble seen through a window.

>n
Other Room
This is the other room.  The void lies to the south.

You see a pebble here.

>l through window
This side of the window is tinted, so you can't see through it from here.

>x rock
You see no rock here.

The difference here is that most of the “stuff” is encapsulated in a new SenseConnector class, RemoteViewConnector, window in the example above. When you declare a RemoteViewConnector, you need to define its locationList as a two (and only two) element List containing the two locations to connect.

The connector will start out opaque and become transparent the first time the player manages to >LOOK THROUGH it. After that, objects visible only via that connector won’t be listed when examining the “non-remote” location, but they can be examined directly.

Optionally, you can define on objects what they should look like when viewed through a specific connector. That’s the ++RemoteView declaration on the pebble. The template is RemoteView ->connectorObject '[description when viewed through connector]'.

There are a couple of optional things. In the example, the connector is declared with oneWay = true, which makes the connector “work” only in one direction (you can see otherRoom from startRoom but you can’t see startRoom from otherRoom), and you can customize the failure message by declaring oneWayFailure.

You can also define a prefix and suffix on the RemoteViewConnector to add text (via reportBefore and reportAfter, respectively) when the player tries to >LOOK THROUGH the connector.

This all does more or less what I needed a “gimmick” sense connector for, but it’s still pretty limited. It also has a couple implementation warts, like the fact that looking into an empty room can return empty output (just a bare newline) because under the hood it’s using lookAround(), which can output an empty double-quoted string (which will squash anything you add as a defaultReport()).

And the simplified syntax of this method (compared to what I posted earlier) comes at the expense of re-writing Thing.basicExamine() to break out one of the conditionals into its own method. This works fine in all my test cases, but it’ll probably not play well with anything else that twiddles basicExamine().

But, with all those caveats, it seems to work pretty well if you basically just have a few cases where the player needs to scan/witness a distant scene in a way that isn’t easily implementable with stock adv3.

Hope it helps.

3 Likes

Awesome! I grabbed a copy, shaking it out now, thanks! Fwiw, I was also chasing the two-room-only special purpose SenseConnector. This is like gnomes came in last night and fixed my shoes!

Geez, I hope that reference lands. Its gonna read deeply weird if it doesn’t.

Boy was I way on the wrong side of that one, though.

3 Likes

Out of curiosity, what happens if you put the pebble somewhere else?

Falls through to be handled by the next applicable state.

The logic is inserted into Thing.basicExamine(). The first thing it checks for is if there’s a remoteDesc defined on the object and if it applies (that is, if the actor and the object are in different locations, determined by comparing the outermost containing rooms).

The module contains a replacement basicExamine() that checks to see if there’s a RemoteView instance on the object that describes the situation (location of the actor relative to the location of the object, as defined by the locationList on the connector). If there’s a match, the description associated with the match is displayed (and processing stops there).

If there is no match, then processing falls through and it’s handled exactly as it would be otherwise: next it’ll check if there’s a remoteDesc defined on the object, then it’ll check if the object is obscured and there’s a obscuredDesc defined, then the checks for distantDesc, and so on.

1 Like

Ohhhh!! Awesome!

Sorry for the delay, this gnome just got to their computer today, lol.

Okay, so I tried to condense and refactor stuff as much as I could. This is just a snippet, though, and won’t compile into a working game. There needs to be rooms designed and stuff, and a game definition. However, the functionality is here, if there’s anything of value to be taken from it. After SpringThing, I have plans to build independent modules out of the stuff I made for this game.

It works with Adv3Lite, and defines a PEEK INTO, PEEK THROUGH, and PEEK DIRECTION action, and a framework for making objects become ways for player to peek into other rooms. The Door class is modified, and reveals ways to also modify other TravelConnectors to support peeking, as well.

Code snippet here
#charset "us-ascii"
#include <tads.h>
#include "advlite.h"

#define peekExpansion 'peek'|'peer'|'spy'|'check'|'watch'|'p'

VerbRule(PeekThrough)
    (peekExpansion) ('through'|'thru'|'between'|) singleDobj
    : VerbProduction
    action = PeekThrough
    verbPhrase = 'peek/peeking through (what)'
    missingQ = 'what do you want to peek through'    
;

DefineTAction(PeekThrough)
    turnsTaken = 0
    implicitAnnouncement(success) {
        if (success) {
            return 'peeking through {the dobj}';
        }
        return 'failing to peek through {the dobj}';
    }
;

VerbRule(PeekInto)
    [badness 100] (peekExpansion) ('in'|'into'|'inside' 'of') singleDobj
    : VerbProduction
    action = PeekInto
    verbPhrase = 'peek/peeking into (what)'
    missingQ = 'what do you want to peek into'    
;

DefineTAction(PeekInto)
    implicitAnnouncement(success) {
        if (success) {
            return 'peeking into {the dobj}';
        }
        return 'failing to peek into {the dobj}';
    }
;

VerbRule(PeekDirection)
    (peekExpansion|'look'|'x'|'l') singleDir
    : VerbProduction
    action = PeekDirection
    verbPhrase = 'peek/peeking (where)'  
;

DefineTAction(PeekDirection)
    direction = nil

    execCycle(cmd) {
        direction = cmd.verbProd.dirMatch.dir;
        inherited(cmd);
    }

    execAction(cmd) {
        local loc = gActor.getOutermostRoom();
        local conn = nil;

        // See if the room has a special case for this first
        local specialTarget = loc.getSpecialPeekDirectionTarget(direction);
        if (specialTarget != nil) {
            doNested(PeekThrough, specialTarget);
            return;
        }

        // Get destination
        local clear = true;
        if (loc.propType(direction.dirProp) == TypeObject) {
            conn = loc.(direction.dirProp);
            
            if (conn == nil) clear = nil;
            if (conn != nil) {
                if (!conn.isConnectorApparent) {
                    clear = nil;
                }
            }
        }

        if (!clear || conn == nil) {
            "{I} {cannot} peek that way. ";
            exit;
        }

        local dest = conn.destination;

        // Exhaust all possible Things that might be connecting
        local scpList = Q.scopeList(gActor).toList();
        for (local i = 1; i <= scpList.length; i++) {
            local obj = scpList[i];
            if (obj.ofKind(TravelConnector) && obj.ofKind(Thing) && !obj.ofKind(Room)) {
                if (obj.destination == dest) {
                    doNested(PeekThrough, obj);
                    return;
                }
            }
        }

        // At this point, it is a simple room connection
        // Make sure we are on the floor
        local stagingLoc = gActor.getOutermostRoom();
        if (gActor.location != stagingLoc) {
            if (!actorInStagingLocation.checkPreCondition(gActor, true)) {
                exit;
            }
        }

        "{I} carefully peek <<direction.name>>...<.p>";
        //conn.destination.getOutermostRoom().peekInto();
        conn.destination.getOutermostRoom().observeFrom(
            gActor, 'to the <<direction.name>>'
        );
    }
;

actorHasPeekAngle: PreCondition {
    checkPreCondition(obj, allowImplicit) {
        if (!obj.requiresPeekAngle) return true;
        return actorInStagingLocation.checkPreCondition(obj, allowImplicit);
    }
}

modify Thing {
    // Does the gActor need to be in the stagingLocation to peek?
    requiresPeekAngle = nil
    // Does the gActor not get listed in remote observations?
    skipInRemoteList = nil

    dobjFor(PeekThrough) asDobjFor(LookThrough)
    dobjFor(PeekInto) asDobjFor(LookIn)

    dobjFor(LookThrough) {
        preCond = [actorHasPeekAngle, containerOpen]
    }

    dobjFor(LookIn) {
        preCond = [objVisible, touchObj, actorHasPeekAngle, containerOpen]
    }
}

// Example of a TravelConnector that you can PEEK THROUGH
modify Door {
    // One must be on the staging location to peek through me
    requiresPeekAngle = true

    allowPeek = (isOpen || isTransparent)

    dobjFor(PeekInto) asDobjFor(LookThrough)
    dobjFor(LookIn) asDobjFor(LookThrough)
    dobjFor(LookThrough) {
        verify() {
            if (!allowPeek) {
                illogical('{I} {cannot} peek through an opaque door. ');
            }
        }
        action() { }
        report() {
            "{I} peek{s/ed} through <<theName>>...<.p>";
            otherSide.getOutermostRoom().observeFrom(
                gActor, 'through <<theName>>'
            );
        }
    }
}

// Example of a Fixture that you can PEEK THROUGH
//
// Don't forget to define the other half of this Window, and
// assign halfA.otherSide = halfB and halfB.otherSide = halfA !!!
class Window: Fixture { 
    otherSide = nil
    canLookThroughMe = true
    // This is to make sure the other half of this window is not added to
    // the list of remotely-observed objects, lol
    skipInRemoteList = true

    dobjFor(LookIn) asDobjFor(LookThrough)
    dobjFor(PeekInto) asDobjFor(LookThrough)
    dobjFor(PeekThrough) asDobjFor(LookThrough)
    dobjFor(LookThrough) {
        action() { }
        report() {
            otherSide.getOutermostRoom().observeFrom(
                gActor, otherSide.remoteHeader
            );
        }
    }

    // This is in case both rooms with either half of a Window are in the same
    // SenseRegion. This will prevent the player from getting a disambiguation
    // check for each half of the window.
    filterResolveList(np, cmd, mode) {
        if (np.matches.length > 1 && getOutermostRoom() != gActor.getOutermostRoom()) {
            np.matches = np.matches.subset({m: m.obj != self});
        }
    }
}

modify Room {
    observedRemotely = nil

    // dirObj is something like northDir, southDir, etc
    //
    // If you want to handle "LOOK NORTH" as "PEEK THROUGH OBJECT",
    // then you can check the dirObj and return the relevant object.
    getSpecialPeekDirectionTarget(dirObj) {
        return nil;
    }

    // Gathering stuff visible through a window
    getWindowList(pov) {
        local scopeList = Q.scopeList(self);
        local spottedItems = new Vector(8);

        for (local i = 1; i <= scopeList.length; i++) {
            local obj = scopeList[i];
            if (obj.skipInRemoteList) continue;
            if (obj.sightSize == small) continue;
            if (obj.getOutermostRoom() != self) continue;
            if (!pov.canSee(obj)) continue;
            if (obj.ofKind(Door)) {
                if (!obj.isOpen) continue;
            }
            else {
                if (!obj.isListed) continue;
            }
            spottedItems.appendUnique(obj);
        }

        return valToList(spottedItems);
    }

    // Can be overridden
    // Basically, if you want a room to be described a certain way
    // from a certain POV, then that goes here.
    descFrom(pov) {
        desc();
    }

    // For tracking remote observation history for NPCs and player,
    // particularly when playing in brief mode.
    getObservationProp(pov) {
        return &observedRemotely;
    }

    // locPhrase is something like 'through the window'
    observeFrom(pov, locPhrase) {
        local lst = getWindowList(pov);
        local obProp = getObservationProp(pov);
        local lookedThru = nil;

        local botherToGiveDescription;
        if (gameMain.verbose) {
            // In verbose mode, give the description if we either
            // haven't looked in from outside before or haven't
            // visited before.
            botherToGiveDescription = !self.(obProp) || !visited;
        }
        else {
            // In brief mode, only give the description if we both
            // have not peeked inside already, and have not visited.
            botherToGiveDescription = !self.(obProp) && !visited;
        }

        "<.p><i>(Looking into <<roomTitle>>...)</i><.p>";
        if(botherToGiveDescription) {
            "<.roomdesc>";
            descFrom(pov);
            "<./roomdesc><.p>";
            self.(obProp) = true;
            lookedThru = true;
        }

        if (lst.length == 0) {
            "{I} {see} nothing<<if lookedThru>> else<<end>> <<locPhrase>>. ";
            return;
        }
        "{I}<<if lookedThru>> also<<end>>{aac} {see} <<makeListStr(lst, &aName, 'and')>> <<locPhrase>>. ";
    }
}
2 Likes

Spent some time with it, and it looks pretty close to what I wanted. I tweaked it just a bit to be in line with my door model:

modify RemoteViewConnector
	masterDoor = nil
	locationList = (masterDoor == nil ?
          nil : [masterDoor.location, masterDoor.destination])

	// Connector starts out totally opaque.
	connectorMaterial() {
		if(_remoteViewToggle != true) return(adventium);
		//======================================================
		//   if tied to Door, make sure its open
		if ((masterDoor != nil) && !masterDoor.isOpen) return(adventium);
		//======================================================
		if(oneWay == true) {
			if(gActor.isIn(locationList[1]))
				return(glass);
			else
				return(adventium);
		}
		return(glass);
	}
;

Added a nestedAction(Examine, otherLocation); to the dobjFor(LookThrough)

Need to look at some weird Connector scope cases, but I suspect other code is the problem there. The only rough patch was it seemed to behave oneway, regardless of property setting. I traced it to this:

	dobjFor(LookThrough) {
		verify() {
			if(!gActor.isIn(locationList[1])) {
				if(oneWayFailure)
					illogicalNow(oneWayFailure);
				// else       // fails with these two lines in, works if comment out these two
					// illogicalNow(&remoteViewFailure);
			}
		}

I couldn’t figure out the point of this test, the non one-way else clause I mean. Fails like one way, but with different error message? Running with it commented out and plowing ahead. Thanks for the leg up! Will let you know if anything comes of the scope thing.

@inventor200 still going to poke at your version a bit, just not tonight!

2 Likes

Yes, that’s what the check is for…but there is a bug(-ish thing) there. The verify method should be:

                verify() {
                        if((oneWay == true) && !gActor.isIn(locationList[1])) {
                                if(oneWayFailure)
                                        illogicalNow(oneWayFailure);
                                else
                                        illogicalNow(&remoteViewFailure);
                        }
                }

It should only care whether the actor is in the first location if the connector is declared as oneWay. The inner conditional is checking whether there’s a custom failure message defined for the instance (oneWayFailure), and uses a module-provided default otherwise.

If you’re always defining oneWayFailure on all the connectors you technically don’t need the bit that handles the default. But if you comment it out, the failure mode becomes sneaky: looking the wrong way through a one-way connector will succeed.

In the WIP I pulled this from the connectors are always (so far) one-way: things like looking out from a window in a multi-story building at something happening in the street below. So there are probably other weird edge cases that aren’t handled well, so let me know if you notice anything else like this.

1 Like

To close out the thread (for now), I have a tentative path forward. @jbg and @inventor200 thanks so much for the code to paw through! The solution is subclassed from jbg’s RemoveViewConnector, details in a moment. I do appreciate both codebases, because while I didn’t use Joey’s (Jess’?), that code actually showed me some things about scope and directional commands I had barely started flirting with!

Here is the room solution I went with. Want to shake it out some more before uploading it anywhere, but may try to lib it at some point. I mixed in Occluder to give more control on position (ie things on the wall behind the doorway). Then used it to eliminate reporting of these Connectors.

class PassageSenseConnector : Occluder, RemoteViewConnector
// do not use "mask until first look through" capability.  nor oneWay
    _remoteViewToggle = true
    oneWay = nil
// override at instantiation or init
    masterThruWay = nil
	locationList = (masterThruWay == nil ?
		nil : [masterThruWay.location, masterThruWay.destination])
// keep them implicit
	isListedInContents = nil

//  occlude remote PassageSenseConnectors.  want them to be implicit too
//
    isOccludedBy(occluder, sense, pov) {
        // do not occlude if adjacent to this instance, otherwise do
        return (locationList.indexOf(pov.location) == nil);
    }

// only 'open' sense connection if adjacent to it, and intervening doors
// are open
//
	connectorMaterial() {
		if (!gActor.isIn(locationList[1]) && !gActor.isIn(locationList[2]))
			return(adventium);
		if ((masterThruWay != nil) && masterThruWay.ofKind(BasicDoor)
		   	&& !masterThruWay.isOpen)
			return(adventium);
        return inherited;
	}

//  overriding RemoteViewConnector to add implicit examine room and closed door check
//  otherwise same
//
    dobjFor(LookThrough) {
		check() {
			if ((masterThruWay != nil) && masterThruWay.ofKind(BasicDoor)
		   		&& !masterThruWay.isOpen)
				failCheck('{You/he} {cannot} see through that right now.  ');
		}
    	action() {
			local otherLocation;

			// Mark the connector as visually transparent.
			//connectorMaterial = glass;
			_remoteViewToggle = true;

			// Figure out which location in our locationList
			// the current actor ISNT in.
			if(me.isIn(locationList[1]))
				otherLocation = locationList[2];
			else
				otherLocation = locationList[1];

			if(prefix) reportBefore(prefix);
			if(suffix) reportAfter(suffix);
			//======================================================
			// Added this line only
			nestedAction(Examine, otherLocation);
			//======================================================
			// Look around the OTHER location FROM THIS ONE.
			me.location.remoteViewLister(otherLocation);
		}
    }
;

// passageSenseConnectors dont want this, and wonky in rooms anyway
modify Room
    contentsListedInExamine = nil
;

//  associate to PassageSenseConnector with ThroughPassage
//  (and redirect LookThrough)
//  internal link, set by preInit
//
modify ThroughPassage
    _thruSenseConnector = nil // set by preinit
    dobjFor(LookThrough)
		maybeRemapTo((_thruSenseConnector != nil), LookThrough,
		   	_thruSenseConnector)
;
passageViewPreinit: PreinitObject
	execute() {
		forEachInstance(PassageSenseConnector, function(pscObj) {
			if (pscObj.masterThruWay == nil ) return;
            pscObj.masterThruWay._thruSenseConnector = pscObj;
            if (pscObj.masterThruWay.otherSide == nil) return;
            pscObj.masterThruWay.otherSide._thruSenseConnector = pscObj;
		});
	}
;

In classic @jbg style, here is the demo:

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

#include "remoteView.h"

startRoom: Room 'Void'
        "This is a featureless void with a window.  A door to the other room is
		to the north. "
	north = doorToOtherRoom
;
+me: Person;
+rock: Thing 'ordinary rock' 'rock' "An ordinary rock. ";
+doorToOtherRoom: Door 'ordinary door' 'door' "An ordinary door.  ";

doorwayToOtherRoom:  PassageSenseConnector 'ordinary doorway' 'ordinary doorway'
	masterThruWay = doorToOtherRoom
;

otherRoom: Room 'Other Room'
	"This is the other room.  A door to the void lies to the south.
		Behind an opening to the north is yet another room. "
	south = doorToStartRoom
	north = yetAnotherRoom
;
+doorToStartRoom: Door ->doorToOtherRoom 'ordinary door'  'door'
	"Backside of an ordinary door.  "
;
+pebble: Thing 'small round pebble' 'pebble' "A small, round pebble. "; 
++RemoteView ->doorwayToOtherRoom 'It looks like a pebble seen through a doorway. ';
++RemoteView ->passageToYARoom 'It looks like a pebble seen through a passageway.  ';

yetAnotherRoom: Room 'Yet Another Room'
	"Still another room.  The other room is through an opening to the south.  "
	south = otherRoom
;
passageToYARoom: PassageSenseConnector 'ordinary opening' 'ordinary opening'
	locationList = [otherRoom, yetAnotherRoom]
;

And transcript:

Void
This is a featureless void.  A door to the other room is to the north.

You see a rock here.

>look through doorway
You cannot see through that right now.

>open door
Opened.

>look through doorway
This is the other room.  A door to the void lies to the south.  Behind an
opening to the north is yet another room.

In the other room, you see a pebble.

>x pebble
It looks like a pebble seen through a doorway.

>n
Other Room
This is the other room.  A door to the void lies to the south.  Behind an
opening to the north is yet another room.

You see a pebble here.

>n
Yet Another Room
Still another room.  The other room is through an opening to the south.

>look through opening
This is the other room.  A door to the void lies to the south.  Behind an
opening to the north is yet another room.

In the other room, you see a pebble.

>x pebble
It looks like a pebble seen through a passageway.

If the standard room descriptions read kludgy from the next room, you can use remoteDesc(pov) to clean those up. Occluder functionality can be used to manipulate how much is visible. This cocktail seems to get me what I wanted so far. The most eggregious assumption I’m making is that Rooms listing their contents on Examine is wonky. I stand by that, but others might want to make contentsListedInExamine more nuanced.

2 Likes

Yayyyyyy! I helped!

Either works! :smile: You can do a coin toss, if you’d like!

3 Likes