Interior/Exterior Components on a Vehicle

Say you have a car, and the car has some features (steering wheel, pedals) that should only be accessible when the player is inside the car, and some features (bumper, bumper sticker) which should only be accessible when the player is outside the car. Is there a standard way of handling this sort of thing in TADS3/adv3?

Here’s my current approach: define an abstract VehicleComponent class for “sided” components; use it to define two more abstract classes, InteriorComponent and ExteriorComponent, have these two classes define sightPresence() (and the other sense presence checks) based on whether the actor is in the same location as the component; declare all of the car parts as instances of InteriorComponent and ExteriorComponent as appropriate. Example code:

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

class VehicleComponent: Component
        inMyVehicle(actor?) {
                if(actor == nil) actor = gActor;
                return(location == actor.location);
        }
;

class InteriorComponent: VehicleComponent
        sightPresence = (inMyVehicle)
        soundPresence = (inMyVehicle)
        smellPresence = (inMyVehicle)
        touchPresence = (inMyVehicle)
;

class ExteriorComponent: VehicleComponent
        sightPresence = (!inMyVehicle)
        soundPresence = (!inMyVehicle)
        smellPresence = (!inMyVehicle)
        touchPresence = (!inMyVehicle)
;

startRoom:      Room 'Void'
        "This is a featureless void. "
;
+me:    Person;
+myCar: Heavy, Booth, Vehicle 'car' 'car'
        "It's a generic car. "
        specialDesc = "There's a car parked here. "
        useSpecialDesc = (!gPlayerChar.isIn(self))
        defaultPosture = sitting
        allowedPostures = [ sitting ]
        obviousPostures = [ sitting ]
        roomListActorPosture(actor) {}
;
++ InteriorComponent, Decoration 'pedals' 'pedals'
        "From left to right there's a brake pedal and gas pedal. "
;
++ InteriorComponent, Decoration '(brake) pedal' 'brake pedal'
        "It's to the left of the gas pedal. "
;
++ InteriorComponent, Decoration '(gas) pedal' 'gas pedal'
        "It's to the right of the brake pedal. "
;
++ InteriorComponent, Decoration '(steering) wheel' 'steering wheel'
        "It's round. "
;
++ ExteriorComponent, Decoration 'bumper' 'bumper'
        "There's a bumper sticker on the bumper. "
;
++ ExteriorComponent, Decoration '(bumper) sticker' 'bumper sticker'
        "<q>Honk If You XYZZY</q>"
;

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
;

Sample transcript:

Void
This is a featureless void.

There's a car parked here.

>x bumper sticker
"Honk If You XYZZY"

>x steering wheel
You see no steering wheel here.

>in
(the car)
Okay, you're now sitting in the car.

>x steering wheel
It's round.

>x bumper sticker
You see no bumper sticker here.

This works, but it seems like the sort of thing that might already be a solved problem, and I don’t want to re-invent the wheel (so to speak) if there’s already a standard solution out there.

Have you checked out Occluders?

And/or OutOfReach, since it’s probably more important for the car parts to be not reachable more so than completely out of scope.

I hadn’t looked specifically at Occluder but I had considered looking it it as a SenseConnector problem. I don’t think that’s the way I want to go, though, because a) it doesn’t turn out to be any easier (because you still end up having to define classes to handle the inside/outside distinction, or end up having to fiddle around with how each individual component interacts with the SenseConnector; and b) SenseConnector stuff in general works very well for simple things/default design cases and gets out of hand in a hurry for any fiddly special cases. Or at least in my experience.

I think the fundamental underlying problem here is that, as far as I can tell, TADS3/adv3 doesn’t really have any notion of the distinction I’m trying to make. It has a notion of being inside a container as opposed to outside a container, but it really doesn’t have the sort of “part of the container, but on the outside” versus “part of the container, but on the inside”. If that makes sense.

And most SenseConnector solutions seem to become complicated when they involve situations where the sort of “implicit mobility” characters are normally expected to have in IF is constrained. For example, if you have a room in a house and a location in the back yard behind the house and they’re connected by a window, it makes sense that you can see the back yard from the room and the room from the back yard. And further it doesn’t seem odd or out of place if the player examines a tree in the back yard and then looks through the window to notice a bookcase in the room, even if making the two observations would “really” involve a bunch of movements around the location. Same with examining the bookcase while inside the room and then looking out into the back yard. We don’t necessarily expect (or want) a bunch of narrative description of the movements within the room that would be required in order to switch between “implicit viewpoints” or whatever you want to call them.

But when you have a situation like looking at a car in a parking lot, it makes sense to have this sort of “implicit movement” from the outside (making it possible for the player to examine a bumper sticker on the back of the car and then look through the window at the steering wheel without having to do a bunch of complicated guess-the-verb movements). But if they’re in the car, looking out from the window doesn’t work the way it works in a room with a view of the back yard. The player’s viewpoint doesn’t have the same kind of “implicit movement” available—it would probably come across as weird if the player automagically tried clambering into the back seat to look out the rear window to see if they could view the rear bumper, for example. And so considering the window as a SenseConnector suddenly becomes complicated, because the visibility rules are asymmertical and have a bunch of funny gotcha corner cases: there will be things you can see inside the car that you can’t from outside, things you can see from outside you can’t see from inside, some things you see from both, and some things that will look (sound, smell, whatever) differently depending on whether your inside or outside.

If I’m missing something and there is a cleaner way of handling this I’d be delighted to be proven wrong, though.

Jbs’s approach will work fine with outside part like bumpers, but not with inside parts like the steering wheel, whose can surely be seen from the outside (for obvious reasons, manual-driven cars & similiar vehicles must be trasparent at least in the direction of travel…)

Best regards from Italy,
dott. Piergiorgio.

1 Like

With an Occluder you could have the car window a SenseConn like normal so all that behavior will be there. ExtComp and IntComp could just be marker classes so the Occluder (which would just be an addtl superclass of the SenseConn) just has to occlude ExtComps if pc is in, IntComps if player is out. Or, it doesn’t occlude IntComps from the outside, since you could prob see them, but OutOFReach determines whether you can touch (door is open etc.)…

1 Like

Maybe, but that seems to be an implementation wart that will depend on the particular component. Like if this is in a mystery game you might be able to notice that there’s mud on the steering wheel if you’re looking through the window, but you’d have to get inside the car for a closer look to tell it’s a right-hand handprint.

One approach would be to just change the sense presence on the individual component:

++ InteriorComponent, Decoration '(steering) wheel' 'steering wheel'
        "<<if inMyVehicle()>>
        It's a leather-wrapped steering wheel.  There appears to be some
        mud on the right hand side of the wheel, concentrated near the back.
        <<else>>
        There's something dark on the steering wheel, but you can't tell
        what it is from here.
        <<end>> "
        sightPresence = true
;

If there are a bunch of these, you could just make a InteriorViewableComponent class and use it instead:

class InteriorViewableComponent: InteriorComponent
        sightPresence = true
        desc = (inMyVehicle ? interiorDesc : exteriorDesc)
        interiorDesc = nil
        exteriorDesc = nil
;

++ InteriorViewableComponent, Decoration '(steering) wheel' 'steering wheel'
        interiorDesc = "It's a leather-wrapped steering wheel.  There appears
                to be some mud on the right hand side of the wheel,
                concentrated near the back. "
        exteriorDesc = "There's something dark on the steering wheel, but you
                can't tell what it is from here. "
;

It would also be fairly easy to just “fake” it by using two components, one for the interior view and another for the exterior.

Main point being that I really don’t see any way of doing any of it without some kind of interior/exterior flagging, even if you end up having to fiddle with individual special cases that are kinda neither or both.

1 Like

Right, but what does that actually buy us here? You still have to define your extra classes and put them on all your components. What does including SenseConnectors in the mix provide except extra complexity?

The only thing I can think of is if you’re using the Vehicle as a “regular” container and want it to just obey normal container visibility (and other sense reachability) rules…which seems unlikely to be the case—it’s probably a tangle of special cases that you want to handle individually anyway (things that sit nicely on the passenger-side seat…unless there’s been a passenger…in which case they’d be on the floor in the back seat or under one of the seats or in the glovebox or tucked inside the center console or…).

1 Like

I’ve just had some dissatisfactory results before by twiddling [sense]Presence alone. I defined a sightCond property on Thing that goes deeper into canObjBeSensed than sightPresence does. But if it gets your job done, that’s great.

1 Like

Yeah, I can see the argument for SenseConnector in the abstract; like I said, that’s where I started out thinking about the problem. But as near as I can tell you still end up either defining a bunch of utility classes to group different categories of components based on positional visibility (or whatever you want to call it)…or you fake it with a bunch of different objects. And once you’ve done that, then the fiddly special cases that you’d have to hand code rules for are the same regardless of whether or not you’re using SenseConnector.

IF authoring systems in general seem to be bad about this sort of thing. In addition to things like inside components versus outside components, there always seem to be problems with things that have facings and that kind of thing. Like if we’re looking for someone with a scar on their face and there’s someone across the room with their back turned, we end up either having a jumble of special cases (single object with a bunch of conditionals) or fake it using multiple objects (the murder suspect as an Actor in general versus a one-off scenery item in a specific scripted sequence).

2 Likes