Climb and Down Vaguely (Adv3Lite problem)

Okay, so…

I have a room with a big tree, and a second room that represents “sitting in the tree”. The tree itself is a MutliLoc that is found in both of these rooms, so that if you type in >CLIMB TREE or >GET OUT OF TREE then the parser knows what you’re talking about. (I don’t often have much luck setting up Doers)

(Might tweak this later, to remove the MultiLoc, and have an unlisted “second tree” up in the “within-tree” room, so it soaks up the vocabulary words, without being listed in the room while you’re supposed to be in the tree already. But that’s not important for this question. Sorry.)

ANYWAYS, whenever I try to set up the >CLIMB UP and >CLIMB DOWN actions (specifically so the player does not have to explicitly mention the tree), the parser fails. These seem to be linked with the ClimbUpVague and ClimbDownVague actions, but if I try to intercept and handle them in roomBeforeAction(), it doesn’t have any effect at all. The parser just says I don't see down here or (in the case of climbing up) it corrects “up” to “us”, so the command is forced to become >CLIMB US, and then the parser confuses itself.

To be clear, these two rooms are positioned up and down from each other, so you can use >UP and >DOWN in addition to the transitive climbing actions. But, again, for some reason, the intransitive ClimbUpVague and ClimbDownVague actions are not getting handled, and this is a really jarring and very likely spot for players to get hung up on, especially considering how clever they need to be to even find the room that contains the tree in the first place. It would be a real shame to reward them with poor parser responses.

Any and all help would be greatly appreciated. I’ve been searching all over the docs, and am coming up dry.

1 Like

Without knowing all that the tree object needs to perform (other than be climbed), I would suggest making two tree objects (one for the foot of the tree, one for sitting within its branches) and have each inherit from StairwayUp and StairwayDown, respectively.

You can also link the objects to the appropriate direction properties for each room.

1 Like

Awesome! I’ll try this once I get a chance today and let you know how it goes!

1 Like

It seems like you could override execAction of ClimbXVague, to check outermost room and perform a replaceAction if PC is in one of the tree locations…

1 Like

Oh, but you’re saying the parser doesn’t even get to the ClimbVague code…

1 Like

Do you have any debug routines that print the gAction?

1 Like

I’m wondering if the ClimbVague can ever be accessed as the library stands? Because it has a badness of 200, and there is a Climb verb that takes a direct object, so the parser will probably always choose the Climb construction and assume ‘up’ or ‘down’ is an unknown dobj…

1 Like

An ugly hack could be to put ‘up’ and ‘down’ in the tree vocab, and under dobjFor Climb check the dobjWords to do a replaceAction based on whether they said up or down… but it seems like the parser ought to choose ClimbVague if no dobj matches the entered vocab…

1 Like

Yeah, I’m noticing this is probably a problem.

I was really tempted to do this, but this would probably interfere with other navigation commands if you just use >UP or >DOWN.

Right now I have a really awful headache, probably because I was sleeping in a rather contorted position last night. Once my headache calms down, I’ll try @jnelson’s solution (which will probably work, as I was reading the docs on my phone earlier). I’m guessing StairwayUp and StairwayDown have specific traits implemented that create a better response for ClimbUpVague and 'ClimbDownVague`.

Either way, I’ll keep you updated.

Okay, so…

@jnelson’s solution was partial, and handled most of the use cases, and actually cleaned up the file quite a bit (thank you!!!). However, >CLIMB UP and >CLIMB DOWN still had the same results (corrected to >CLIMB US and broke, or There is no down here).

I originally attempted to use a Doer to handle this, but they never actually triggered. It’s like I never even added them at all. So I gutted those, and uh…did something a little cursed, but effective

So in addition to Jim’s professional and clean fix, I managed to do the other half with some inspiration from my man Ziegler, who’s been in the verbose TADS trenches with me, lol:

What I wound up doing was poking at some Unthing functionality, and creating an object in each room called “up/upward” and “down/downard”, respectively. Each of these two objects have a parser filter that forces them to opt out, if there’s any other possible matches available, and swapping itself out with an alternative for any actions that it should not be handling.

So, in short, if the player uses up, upward, down, or downward in a command in one of these two rooms, then the nearby object will check for the command being used. If the command isn’t something that it’s explicitly supposed to handle, then it either opts out, or passes the match onto a fallback.

The result is a hilarious abomination, and I’ve cleaned it up as well as I could. Hopefully this is useful to someone in the future.

Code page is as follows:

/*
 *  This entire area is completely dark, but the player is still able
 *  to operate in it.
 *  This code page is modified from the in-game version to
 *  avoid spoilers.
 *  Additionally, some properties and code that configures
 *  the darkness behavior has been removed for simplicity.
 *  As a result, a lot of the description properties are actually
 *  supposed to be printed when FEELing the environment,
 *  but have been moved around and modified here,
 *  so that people using vanilla Adv3Lite can test this code page.
 */

// The room that the player and Anabel arrive in, after discovering a secret easter egg elsewhere in the game.
SecretRoom: Room { 'Mysterious Realm'
    "The realm seems to extend infinitely in all directions. "
    floorObj = secretGround

    up = mysteriousTree

    doNotLeaveBehindMsg = 'I feel like we <i>really</i> do not want to leave anything here... ';

    cannotGoThatWay(dir) {
        switch (dir) {
            case downDir:
                "I can't go underground, silly! ";
                return;
            default:
                "I travel for a ways, aimlessly jogging through the unchanging dark.\b
                \"<i>Markka!</i>\" I call out.\b
                \"<i>Paulla!</i>\" Anabel calls back, giving me a rough idea of direction and distance.\b
                \"I don't think there's anything out here!\"\b
                \"Well, <i>yeah,</i>\" Anabel shouts back. \"My lidar showed me as much! Now, come back to me, ya goof!\"\b
                I smile to myself and jog back to Anabel.";
        }
    }

    roomBeforeAction() {
        if (gActionIs(Drop)) {
            "<<doNotLeaveBehindMsg>>";
            abort;
        }
    }
}

+secretGround: Floor { 'the ground;;dirt soil floor'
    "The damp, freezing, soft soil resists your hand pressure, but quickly hardens. "
}

// This handles CLIMB TREE, CLIMB UP TREE, and UP
+mysteriousTree: StairwayUp { 'tree;large gnarled'
    "It is an immense tree. The bark is rough, and feels like it might be petrified.
    It would take three adults&mdash;hand-in-hand&mdash;to encompass the circumference of the trunk. "

    destination = InMysteriousTree
    travelDesc = "<<first time>>Using childhood skills I never thought I had, <<only>>I ascent the strange tree.
    <<first time>>Its branches feel gnarled and lifeless, but sturdy enough to hold me. <<only>>Once I'm secure, I reach down, and
    help Anabel climb up with me. "

    specialDesc = "The tree dominates the space. ";
}

// This, specifically, handles CLIMB UP
+secretUpward: Decoration { 'up;;upward'
    "<<secretSky.desc>>"
    decorationActions = [Examine, Climb, ClimbUp, ClimbUpVague]

    // This is to handle other commands that use "up", which we don't want interacting with this object
    filterResolveList(np, cmd, mode) {
        if(!(cmd.action == Examine || cmd.action == Climb || cmd.action == ClimbUp || cmd.action == ClimbUpVague)) {
            if (np.matches.length == 1) { // The parser only found us, but it has the wrong object
                np.matches[1].obj = secretSky; // Force a correction by providing a fallback object
            }
            else if (np.matches.length > 1) { // There are better matches
                np.matches = np.matches.subset({m: m.obj != self}); // Remove us
            }
        }
    }

    dobjFor(Climb) { remap = mysteriousTree }
    dobjFor(ClimbUp) asDobjFor(Climb)
    dobjFor(ClimbUpVague) asDobjFor(Climb)
}

secretSky: MultiLoc, Thing { 'sky;dark black empty night nighttime;darkness blackness void moon sun stars'
    "The perfect blackness is all I can see. When I look up, there are no stars to be seen, and no moon.\n
    The first conclusion is that we are <i>deep</i> underground, in a cavern so vast that it does not echo.\n
    The second conclusion is that we stand on a planet that is <i>so far away</i> from any galaxy, that the sky is entirely empty.\b
    Now, I don't know how remote such a planet would have to be, but I'm also pretty sure that even the largest caves still echo... "
    isDecoration = true
    notImportantMsg = 'The black is impenetrable and infinitely far away. '
    locationList = [SecretRoom, InMysteriousTree]
}

InMysteriousTree: Room { 'In the Mysterious Tree'
    "I sit among the branches of the strange tree. The perfect blackness that consumes me makes the
    ground below feel infinitely-far. "
    floorObj = secretTreeFakeFloor

    down = secretTreeBranches
    out asExit(down)

    roomBeforeAction() {
        if (gActionIs(Drop)) {
            "<<SecretRoom.doNotLeaveBehindMsg>>";
            abort;
        }
    }

    dobjFor(Feel) { remap = secretTreeBranches }
}

+secretTreeFakeFloor: Floor { 'the ground;;dirt soil floor'
    "Congrats if you actually manage to read this! "
    decorationActions = [Examine, TakeFrom, Feel, Smell, Taste]

    groundIsTooFarMsg = 'The ground is too far below, and the total darkness exacerbates this. '

    dobjFor(Examine) {
        verify() {
            illogical(groundIsTooFarMsg);
        }
    }

    dobjFor(Feel) {
        verify() {
            illogical(groundIsTooFarMsg);
        }
    }

    dobjFor(Smell) {
        verify() {
            illogical(groundIsTooFarMsg);
        }
    }

    dobjFor(Taste) {
        verify() {
            illogical(groundIsTooFarMsg);
        }
    }
}

// This handles CLIMB OUT, GET OUT, GET OFF, CLIMB DOWN BRANCHES, and DOWN
+secretTreeBranches: StairwayDown { 'branches;surrounding gnarled dead'
    "The branches are many, gnarled, and without leaves. However, they are also incredibly strong, and do not easily bend. "
    destination = SecretRoom
    isConnectorVisible = true
    isConnectorApparent = true
    isConnectorListed = nil
    isClimbable = true
    travelDesc = "<<first time>>The terror of falling is a lot more acute as <<only>>I climb down<<first time>>, but I make it<<only>>.
    As Anabel descends, I help her keep her balance and her nerves. "

    dobjFor(Climb) {
        action() {
            travelVia(gActor);
        }
    }
    dobjFor(GetOutOf) asDobjFor(Climb)
    dobjFor(GetOff) asDobjFor(Climb)
    dobjFor(ClimbDown) asDobjFor(Climb)
    dobjFor(ClimbDownVague) asDobjFor(Climb)
}

// This, specifically, handles CLIMB DOWN
+secretDownward: Decoration { 'down;;downward'
    "<<secretTreeFakeFloor.groundIsTooFarMsg>>"
    decorationActions = [Examine, Climb, ClimbDown, ClimbDownVague]

    // This is to handle other commands that use "down", which we don't want interacting with this object
    filterResolveList(np, cmd, mode) {
        if(!(cmd.action == Examine || cmd.action == Climb || cmd.action == ClimbDown || cmd.action == ClimbDownVague)) {
            if (np.matches.length == 1) { // The parser only found us, but it has the wrong object
                np.matches[1].obj = secretTreeBranches; // Force a correction by providing a fallback object
            }
            else if (np.matches.length > 1) { // There are better matches
                np.matches = np.matches.subset({m: m.obj != self}); // Remove us
            }
        }
    }

    dobjFor(Climb) { remap = secretTreeBranches }
    dobjFor(ClimbDown) asDobjFor(Climb)
    dobjFor(ClimbDownVague) asDobjFor(Climb)
}

I have a tree in my work-in-progress and had to check how I resolved the CLIMB UP / CLIMB DOWN problem. I added the words to the StairwayUp / StairwayDown vocab, like so:

treeBase: StairwayUp 'a tree;;up' @poppyField
  "The branches of the tree loom high overhead.  "
;

treeTop: StairwayDown 'a tree;;down' @inTheTree
  "The tree branches don't seem so high now that you're among them.  "
;

With these objects hooked up to the appropriate up / down direction properties in their Rooms, this resolves most of the desirable possibilities. It also means LOOK UP and LOOK DOWN give reasonable responses.

1 Like

It sounds like the parser isn’t set up right… what about adding ‘climb’ ‘up’ to the grammar of the simple UpAction? And if necessary bump the priority of the Up verb over the Climb (dobj) verb so that it will never try to parse ‘up’ as a dobj. In that setup your tree rooms just become the value of the “up” property of the other part of the tree…

1 Like

This is one of those moments where the actual solution is revealed, and now it seems so obvious, and I wonder why I didn’t realize it.

Thank you so much!! Marked this is the new solution!

1 Like

hopefully not too late for Joey, but I noted in the changelog of the 1.6ß version of A3Lite (the one on github, the one on IF Archive being the current stable, 1.5) this entry:

“The VerbRule for the ClimbUpVague action has been changed to match only CLIMB UP in order to avoid clashing with commands like GO UP or WALK UP that should triggle [sic] a Travel Action instead.”

Dunno if is related to Joey’s problem, but I don’t know what version of A3Lite she’s using.

Best regards from Italy,
dott. Piergiorgio.

1 Like

It just occurred to me that I should maybe compare the version on GitHub to the version I got from the library author’s own website, in case the website is no longer maintained.

There’s been a few weird bugs I’ve found now, which aren’t caused by my own code. I could actually be using an out-of-date version of A3Lite, lol! Thank you for the information!

John Ziegler’s diagnosis of the problem here would seem to be correct, so I’ve just removed the [badness 200] from the relevant VerbRules for the upcoming version 1.6. A quick test indicates that this indeed leads to better behaviour.

4 Likes